From d710f32f700a01f8761eaf2ce8ba28f954633076 Mon Sep 17 00:00:00 2001 From: kaj <40004347+KAJdev@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:42:40 -0800 Subject: [PATCH] working inference by default --- .../src-tauri/resources/defaultGraph.js | 832 ++++++++++++++++++ .../stablestudio-ui/src-tauri/src/main.rs | 28 +- .../stablestudio-ui/src-tauri/src/server.rs | 2 +- .../stablestudio-ui/src-tauri/tauri.conf.json | 6 +- packages/stablestudio-ui/src/App/index.tsx | 2 +- packages/stablestudio-ui/src/Comfy/index.tsx | 8 +- .../src/Generation/Image/Create/index.tsx | 48 +- .../Image/Model/StableDiffusionV1/index.ts | 7 +- 8 files changed, 914 insertions(+), 19 deletions(-) create mode 100644 packages/stablestudio-ui/src-tauri/resources/defaultGraph.js diff --git a/packages/stablestudio-ui/src-tauri/resources/defaultGraph.js b/packages/stablestudio-ui/src-tauri/resources/defaultGraph.js new file mode 100644 index 0000000..c599a0c --- /dev/null +++ b/packages/stablestudio-ui/src-tauri/resources/defaultGraph.js @@ -0,0 +1,832 @@ +export const defaultGraph = { + last_node_id: 19, + last_link_id: 30, + nodes: [ + { + id: 14, + type: "PrimitiveNode", + pos: [-230.75965115736872, 325.6648804704241], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 8, + mode: 0, + outputs: [ + { + name: "INT", + type: "INT", + links: [20], + widget: { + name: "seed", + config: [ + "INT", + { + default: 0, + min: 0, + max: 18446744073709552000, + }, + ], + }, + }, + ], + title: "StableStudio Seed", + properties: {}, + widgets_values: [557382126738322, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 12, + type: "PrimitiveNode", + pos: [-247.75965115736852, 374.6648804704238], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 6, + mode: 0, + outputs: [ + { + name: "COMBO", + type: "euler,euler_ancestral,heun,dpm_2,dpm_2_ancestral,lms,dpm_fast,dpm_adaptive,dpmpp_2s_ancestral,dpmpp_sde,dpmpp_sde_gpu,dpmpp_2m,dpmpp_2m_sde,dpmpp_2m_sde_gpu,ddim,uni_pc,uni_pc_bh2", + links: [16], + widget: { + name: "sampler_name", + config: [ + [ + "euler", + "euler_ancestral", + "heun", + "dpm_2", + "dpm_2_ancestral", + "lms", + "dpm_fast", + "dpm_adaptive", + "dpmpp_2s_ancestral", + "dpmpp_sde", + "dpmpp_sde_gpu", + "dpmpp_2m", + "dpmpp_2m_sde", + "dpmpp_2m_sde_gpu", + "ddim", + "uni_pc", + "uni_pc_bh2", + ], + ], + }, + }, + ], + title: "StableStudio Sampler", + properties: {}, + widgets_values: ["dpmpp_sde", "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 15, + type: "PrimitiveNode", + pos: [-232.75965115736875, 426.6648804704238], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 9, + mode: 0, + outputs: [ + { + name: "INT", + type: "INT", + links: [22], + widget: { + name: "steps", + config: [ + "INT", + { + default: 20, + min: 1, + max: 10000, + }, + ], + }, + }, + ], + title: "StableStudio Steps", + properties: {}, + widgets_values: [50, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 11, + type: "PrimitiveNode", + pos: [-232.75965115736875, 481.6648804704238], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 5, + mode: 0, + outputs: [ + { + name: "COMBO", + type: "v2-1_768-ema-pruned.safetensors", + links: [14], + widget: { + name: "ckpt_name", + config: [["v2-1_768-ema-pruned.safetensors"]], + }, + }, + ], + title: "StableStudio Model", + properties: {}, + widgets_values: ["v2-1_768-ema-pruned.safetensors", "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 13, + type: "PrimitiveNode", + pos: [-215.75965115736875, 536.6648804704238], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 7, + mode: 0, + outputs: [ + { + name: "FLOAT", + type: "FLOAT", + links: [18], + widget: { + name: "cfg", + config: [ + "FLOAT", + { + default: 8, + min: 0, + max: 100, + }, + ], + }, + }, + ], + title: "StableStudio cfg", + properties: {}, + widgets_values: [8, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 17, + type: "PrimitiveNode", + pos: [-286.7596511573686, 589.6648804704237], + size: [235.1999969482422, 75.99998569488525], + flags: { + collapsed: true, + }, + order: 0, + mode: 0, + outputs: [ + { + name: "STRING", + type: "STRING", + links: [26], + widget: { + name: "text", + config: [ + "STRING", + { + multiline: true, + }, + ], + }, + }, + ], + title: "StableStudio Positive Prompt", + properties: {}, + widgets_values: [ + "Glowing aurora borealis over a frozen lake, with towering mountains in the distance, ethereal, magical, winter landscape, high detail", + ], + color: "#323", + bgcolor: "#535", + }, + { + id: 10, + type: "PrimitiveNode", + pos: [-261.7596511573686, 708.6648804704237], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 4, + mode: 0, + outputs: [ + { + name: "INT", + type: "INT", + links: [12], + slot_index: 0, + widget: { + name: "batch_size", + config: [ + "INT", + { + default: 1, + min: 1, + max: 64, + }, + ], + }, + }, + ], + title: "StableStudio Batch Size", + properties: {}, + widgets_values: [4, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 18, + type: "PrimitiveNode", + pos: [-233.7596511573687, 769.6648804704237], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 2, + mode: 0, + outputs: [ + { + name: "INT", + type: "INT", + links: [28], + widget: { + name: "width", + config: [ + "INT", + { + default: 512, + min: 64, + max: 8192, + step: 8, + }, + ], + }, + }, + ], + title: "StableStudio Width", + properties: {}, + widgets_values: [768, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 19, + type: "PrimitiveNode", + pos: [-233.7596511573687, 823.6648804704237], + size: { + 0: 210, + 1: 82, + }, + flags: { + collapsed: true, + }, + order: 3, + mode: 0, + outputs: [ + { + name: "INT", + type: "INT", + links: [30], + widget: { + name: "height", + config: [ + "INT", + { + default: 512, + min: 64, + max: 8192, + step: 8, + }, + ], + }, + }, + ], + title: "StableStudio Height", + properties: {}, + widgets_values: [768, "fixed"], + color: "#323", + bgcolor: "#535", + }, + { + id: 16, + type: "PrimitiveNode", + pos: [-286.7808471339315, 648.0887886735504], + size: { + 0: 235.1999969482422, + 1: 76, + }, + flags: { + collapsed: true, + }, + order: 1, + mode: 0, + outputs: [ + { + name: "STRING", + type: "STRING", + links: [24], + widget: { + name: "text", + config: [ + "STRING", + { + multiline: true, + }, + ], + }, + }, + ], + title: "StableStudio Negative Prompt", + properties: {}, + widgets_values: [""], + color: "#323", + bgcolor: "#535", + }, + { + id: 6, + type: "CLIPTextEncode", + pos: [-1, 591], + size: { + 0: 210, + 1: 54, + }, + flags: { + collapsed: true, + }, + order: 12, + mode: 0, + inputs: [ + { + name: "clip", + type: "CLIP", + link: 3, + }, + { + name: "text", + type: "STRING", + link: 26, + widget: { + name: "text", + config: [ + "STRING", + { + multiline: true, + }, + ], + }, + slot_index: 1, + }, + ], + outputs: [ + { + name: "CONDITIONING", + type: "CONDITIONING", + links: [4], + slot_index: 0, + }, + ], + properties: { + "Node name for S&R": "CLIPTextEncode", + }, + widgets_values: [ + "Glowing aurora borealis over a frozen lake, with towering mountains in the distance, ethereal, magical, winter landscape, high detail", + ], + }, + { + id: 7, + type: "CLIPTextEncode", + pos: [-2, 649], + size: { + 0: 210, + 1: 54, + }, + flags: { + collapsed: true, + }, + order: 13, + mode: 0, + inputs: [ + { + name: "clip", + type: "CLIP", + link: 5, + }, + { + name: "text", + type: "STRING", + link: 24, + widget: { + name: "text", + config: [ + "STRING", + { + multiline: true, + }, + ], + }, + slot_index: 1, + }, + ], + outputs: [ + { + name: "CONDITIONING", + type: "CONDITIONING", + links: [6], + slot_index: 0, + }, + ], + properties: { + "Node name for S&R": "CLIPTextEncode", + }, + widgets_values: [""], + }, + { + id: 5, + type: "EmptyLatentImage", + pos: [6, 720], + size: { + 0: 315, + 1: 106, + }, + flags: {}, + order: 10, + mode: 0, + inputs: [ + { + name: "batch_size", + type: "INT", + link: 12, + widget: { + name: "batch_size", + config: [ + "INT", + { + default: 1, + min: 1, + max: 64, + }, + ], + }, + }, + { + name: "width", + type: "INT", + link: 28, + widget: { + name: "width", + config: [ + "INT", + { + default: 512, + min: 64, + max: 8192, + step: 8, + }, + ], + }, + slot_index: 1, + }, + { + name: "height", + type: "INT", + link: 30, + widget: { + name: "height", + config: [ + "INT", + { + default: 512, + min: 64, + max: 8192, + step: 8, + }, + ], + }, + slot_index: 2, + }, + ], + outputs: [ + { + name: "LATENT", + type: "LATENT", + links: [2], + slot_index: 0, + }, + ], + properties: { + "Node name for S&R": "EmptyLatentImage", + }, + widgets_values: [768, 768, 4], + }, + { + id: 4, + type: "CheckpointLoaderSimple", + pos: [-6, 489], + size: { + 0: 315, + 1: 98, + }, + flags: { + collapsed: true, + }, + order: 11, + mode: 0, + inputs: [ + { + name: "ckpt_name", + type: "v2-1_768-ema-pruned.safetensors", + link: 14, + widget: { + name: "ckpt_name", + config: [["v2-1_768-ema-pruned.safetensors"]], + }, + slot_index: 0, + }, + ], + outputs: [ + { + name: "MODEL", + type: "MODEL", + links: [1], + slot_index: 0, + }, + { + name: "CLIP", + type: "CLIP", + links: [3, 5], + slot_index: 1, + }, + { + name: "VAE", + type: "VAE", + links: [8], + slot_index: 2, + }, + ], + properties: { + "Node name for S&R": "CheckpointLoaderSimple", + }, + widgets_values: ["v2-1_768-ema-pruned.safetensors"], + }, + { + id: 3, + type: "KSampler", + pos: [374, 556], + size: { + 0: 315, + 1: 262, + }, + flags: {}, + order: 14, + mode: 0, + inputs: [ + { + name: "model", + type: "MODEL", + link: 1, + }, + { + name: "positive", + type: "CONDITIONING", + link: 4, + }, + { + name: "negative", + type: "CONDITIONING", + link: 6, + }, + { + name: "latent_image", + type: "LATENT", + link: 2, + }, + { + name: "sampler_name", + type: "euler,euler_ancestral,heun,dpm_2,dpm_2_ancestral,lms,dpm_fast,dpm_adaptive,dpmpp_2s_ancestral,dpmpp_sde,dpmpp_sde_gpu,dpmpp_2m,dpmpp_2m_sde,dpmpp_2m_sde_gpu,ddim,uni_pc,uni_pc_bh2", + link: 16, + widget: { + name: "sampler_name", + config: [ + [ + "euler", + "euler_ancestral", + "heun", + "dpm_2", + "dpm_2_ancestral", + "lms", + "dpm_fast", + "dpm_adaptive", + "dpmpp_2s_ancestral", + "dpmpp_sde", + "dpmpp_sde_gpu", + "dpmpp_2m", + "dpmpp_2m_sde", + "dpmpp_2m_sde_gpu", + "ddim", + "uni_pc", + "uni_pc_bh2", + ], + ], + }, + slot_index: 4, + }, + { + name: "cfg", + type: "FLOAT", + link: 18, + widget: { + name: "cfg", + config: [ + "FLOAT", + { + default: 8, + min: 0, + max: 100, + }, + ], + }, + slot_index: 5, + }, + { + name: "seed", + type: "INT", + link: 20, + widget: { + name: "seed", + config: [ + "INT", + { + default: 0, + min: 0, + max: 18446744073709552000, + }, + ], + }, + slot_index: 6, + }, + { + name: "steps", + type: "INT", + link: 22, + widget: { + name: "steps", + config: [ + "INT", + { + default: 20, + min: 1, + max: 10000, + }, + ], + }, + slot_index: 7, + }, + ], + outputs: [ + { + name: "LATENT", + type: "LATENT", + links: [7], + slot_index: 0, + }, + ], + properties: { + "Node name for S&R": "KSampler", + }, + widgets_values: [ + 656198241643977, + "randomize", + 50, + 8, + "dpmpp_sde", + "normal", + 1, + ], + }, + { + id: 8, + type: "VAEDecode", + pos: [384, 452], + size: { + 0: 210, + 1: 46, + }, + flags: {}, + order: 15, + mode: 0, + inputs: [ + { + name: "samples", + type: "LATENT", + link: 7, + }, + { + name: "vae", + type: "VAE", + link: 8, + }, + ], + outputs: [ + { + name: "IMAGE", + type: "IMAGE", + links: [9], + slot_index: 0, + }, + ], + properties: { + "Node name for S&R": "VAEDecode", + }, + }, + { + id: 9, + type: "SaveImage", + pos: [730, 362], + size: [404.36515014648444, 451.49707824707025], + flags: {}, + order: 16, + mode: 0, + inputs: [ + { + name: "images", + type: "IMAGE", + link: 9, + }, + ], + properties: {}, + widgets_values: ["ComfyUI"], + }, + ], + links: [ + [1, 4, 0, 3, 0, "MODEL"], + [2, 5, 0, 3, 3, "LATENT"], + [3, 4, 1, 6, 0, "CLIP"], + [4, 6, 0, 3, 1, "CONDITIONING"], + [5, 4, 1, 7, 0, "CLIP"], + [6, 7, 0, 3, 2, "CONDITIONING"], + [7, 3, 0, 8, 0, "LATENT"], + [8, 4, 2, 8, 1, "VAE"], + [9, 8, 0, 9, 0, "IMAGE"], + [12, 10, 0, 5, 0, "INT"], + [14, 11, 0, 4, 0, "v2-1_768-ema-pruned.safetensors"], + [ + 16, + 12, + 0, + 3, + 4, + "euler,euler_ancestral,heun,dpm_2,dpm_2_ancestral,lms,dpm_fast,dpm_adaptive,dpmpp_2s_ancestral,dpmpp_sde,dpmpp_sde_gpu,dpmpp_2m,dpmpp_2m_sde,dpmpp_2m_sde_gpu,ddim,uni_pc,uni_pc_bh2", + ], + [18, 13, 0, 3, 5, "FLOAT"], + [20, 14, 0, 3, 6, "INT"], + [22, 15, 0, 3, 7, "INT"], + [24, 16, 0, 7, 1, "STRING"], + [26, 17, 0, 6, 1, "STRING"], + [28, 18, 0, 5, 1, "INT"], + [30, 19, 0, 5, 2, "INT"], + ], + groups: [ + { + title: "StableStudio Inputs", + bounding: [-327, 236, 286, 629], + color: "#a1309b", + }, + ], + config: {}, + extra: {}, + version: 0.4, +}; diff --git a/packages/stablestudio-ui/src-tauri/src/main.rs b/packages/stablestudio-ui/src-tauri/src/main.rs index 20da6e0..fc33fc6 100644 --- a/packages/stablestudio-ui/src-tauri/src/main.rs +++ b/packages/stablestudio-ui/src-tauri/src/main.rs @@ -58,7 +58,7 @@ fn main() { Ok(()) }) .plugin(tauri_plugin_upload::init()) - .invoke_handler(tauri::generate_handler![extract_zip, launch_comfy]) + .invoke_handler(tauri::generate_handler![extract_comfy, launch_comfy]) .build(context) .expect("error while building tauri application") .run(move |_app_handle, event| match event { @@ -73,17 +73,35 @@ fn main() { // tauri command to extract a zip from an arbitrary file path #[tauri::command] -fn extract_zip(path: String, target_dir: String) -> Result { +fn extract_comfy( + handle: tauri::AppHandle, + path: String, + target_dir: String, +) -> Result { println!("extracting zip from {} to {}", path, target_dir); let file = File::open(path).unwrap(); let mut archive = zip::ZipArchive::new(file).unwrap(); // unarchive in a thread - let handle = std::thread::spawn(move || { - archive.extract(target_dir).unwrap(); + let extract_parent = target_dir.clone(); + let extract_thread = std::thread::spawn(move || { + archive.extract(extract_parent).unwrap(); }); - handle.join().unwrap(); + // dont block the main thread + extract_thread.join().unwrap(); + + // move resources/defaultGraph.js to APPDATA/ComfyUI/ComfyUI/web/scripts/defaultGraph.js + let resource_path = handle + .path_resolver() + .resolve_resource("resources/defaultGraph.js") + .expect("failed to resolve resource"); + + let mut dest = std::path::PathBuf::from(target_dir); + dest.push("ComfyUI/ComfyUI/web/scripts/defaultGraph.js"); + + std::fs::create_dir_all(dest.parent().unwrap()).unwrap(); + std::fs::copy(resource_path, dest).unwrap(); println!("extracted zip"); Ok("completed".to_string()) diff --git a/packages/stablestudio-ui/src-tauri/src/server.rs b/packages/stablestudio-ui/src-tauri/src/server.rs index 0e0b49f..5d73626 100644 --- a/packages/stablestudio-ui/src-tauri/src/server.rs +++ b/packages/stablestudio-ui/src-tauri/src/server.rs @@ -103,7 +103,7 @@ impl Builder { // replace "/ws" with "/api/ws" in the file let mut file_contents = fs::read_to_string(&path_name).unwrap(); file_contents = file_contents.replace( - "`ws${window.location.protocol === \"https:\" ? \"s\" : \"\"}://${location.host}/ws${existingSession}`", + "`ws${window.location.protocol === \"https:\" ? \"s\" : \"\"}://${this.api_host}${this.api_base}/ws${existingSession}`", "`ws://localhost:5000/ws${existingSession}`" ); diff --git a/packages/stablestudio-ui/src-tauri/tauri.conf.json b/packages/stablestudio-ui/src-tauri/tauri.conf.json index 5d45edf..c22c6e9 100644 --- a/packages/stablestudio-ui/src-tauri/tauri.conf.json +++ b/packages/stablestudio-ui/src-tauri/tauri.conf.json @@ -13,7 +13,7 @@ "allowlist": { "fs": { "all": true, - "scope": ["$APPDATA/**"] + "scope": ["$APPDATA/**", "$RESOURCE/**"] }, "path": { "all": true @@ -43,7 +43,9 @@ "providerShortName": null, "signingIdentity": null }, - "resources": [], + "resources": [ + "resources/*" + ], "shortDescription": "", "targets": "all", "windows": { diff --git a/packages/stablestudio-ui/src/App/index.tsx b/packages/stablestudio-ui/src/App/index.tsx index 67ce90a..f104065 100644 --- a/packages/stablestudio-ui/src/App/index.tsx +++ b/packages/stablestudio-ui/src/App/index.tsx @@ -159,7 +159,7 @@ export namespace App { setMessage("Extracting ComfyUI..."); setProgress(null); try { - const result = await invoke("extract_zip", { + const result = await invoke("extract_comfy", { path: `${appDataPath}/comfyui.zip`, targetDir: `${appDataPath}`, }); diff --git a/packages/stablestudio-ui/src/Comfy/index.tsx b/packages/stablestudio-ui/src/Comfy/index.tsx index 35f8552..f958f8a 100644 --- a/packages/stablestudio-ui/src/Comfy/index.tsx +++ b/packages/stablestudio-ui/src/Comfy/index.tsx @@ -21,7 +21,11 @@ export type ComfyApp = { _nodes: { title: string; type: string; - widgets_values: any[]; + widgets: { + name: string; + type: string; + value: any; + }[]; }[]; }; }; @@ -161,7 +165,7 @@ export namespace Comfy { (output as ComfyOutput).images.map(async (image) => { console.log("image", image); const resp = await fetch( - `http://localhost:3000/view?filename=${image.filename}&subfolder=${ + `/view?filename=${image.filename}&subfolder=${ image.subfolder || "" }&type=${image.type}`, { diff --git a/packages/stablestudio-ui/src/Generation/Image/Create/index.tsx b/packages/stablestudio-ui/src/Generation/Image/Create/index.tsx index b747ee2..022197b 100644 --- a/packages/stablestudio-ui/src/Generation/Image/Create/index.tsx +++ b/packages/stablestudio-ui/src/Generation/Image/Create/index.tsx @@ -60,12 +60,48 @@ export namespace Create { console.log(Comfy.get()); - Comfy.get()?.graph._nodes?.forEach((node) => { - if (node.title === "StableStudio Image Count") { - node.widgets_values = count as any; - } - }); - await Comfy.get()?.queuePrompt(1, 1); + Comfy.get() + ?.graph._nodes?.filter( + (node) => + node.type === "PrimitiveNode" && + node.title.toLowerCase().startsWith("stablestudio") + ) + .forEach((node) => { + const widget = node.widgets.find((widget) => widget.name === "value"); + if (widget) { + const normalized = node.title + .toLowerCase() + .replace("stablestudio", "") + .trim() + .replace(" ", "_"); + if (normalized === "batch_size") { + widget.value = count; + } else if (normalized === "model" && input.model) { + widget.value = input.model; + } else if ( + normalized === "sampler" && + (input.sampler?.id?.length || 0) > 2 + ) { + widget.value = input.sampler?.id; + } else if (normalized === "steps" && input.steps) { + widget.value = input.steps; + } else if (normalized === "seed" && input.seed !== 0) { + widget.value = input.seed; + } else if (normalized === "cfg" && input.cfgScale) { + widget.value = input.cfgScale; + } else if (normalized === "width") { + widget.value = input.width; + } else if (normalized === "height") { + widget.value = input.height; + } else if (normalized === "positive_prompt") { + widget.value = input.prompts.find((p) => p.weight > 0)?.text; + } else if (normalized === "negative_prompt") { + widget.value = input.prompts.find((p) => p.weight < 0)?.text; + } + } + }); + Comfy.get()?.refreshComboInNodes(); + await Comfy.get()?.queuePrompt(-1, 1); } catch (caught: unknown) { const exception = Generation.Image.Exception.create(caught); diff --git a/packages/stablestudio-ui/src/Generation/Image/Model/StableDiffusionV1/index.ts b/packages/stablestudio-ui/src/Generation/Image/Model/StableDiffusionV1/index.ts index f3ee926..cfa9803 100644 --- a/packages/stablestudio-ui/src/Generation/Image/Model/StableDiffusionV1/index.ts +++ b/packages/stablestudio-ui/src/Generation/Image/Model/StableDiffusionV1/index.ts @@ -53,8 +53,11 @@ export namespace StableDiffusionV1 { { text: "", weight: -0.75 }, ], - model: pluginDefaultInput?.model ?? "stable-diffusion-xl-beta-v2-2-2", - sampler: pluginDefaultInput?.sampler ?? { id: "0", name: "DDIM" }, + model: pluginDefaultInput?.model ?? "v2-1_768-ema-pruned.safetensors", + sampler: pluginDefaultInput?.sampler ?? { + id: "dpmpp_sde", + name: "DPMPP SDE", + }, height: pluginDefaultInput?.width ?? 512, width: pluginDefaultInput?.height ?? 512, steps: pluginDefaultInput?.steps ?? 50,