From cbf4b3472b1da35937ff12c06072214a2e5cbad7 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 13 Jan 2023 19:18:56 +0100
Subject: [PATCH 001/127] Automatic launch argument for AMD GPUs
This commit adds a few lines to detect if the system has an AMD gpu and adds an environment variable needed for torch to recognize the gpu.
---
webui.sh | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index c4d6521d..23629ef9 100755
--- a/webui.sh
+++ b/webui.sh
@@ -165,5 +165,11 @@ else
printf "\n%s\n" "${delimiter}"
printf "Launching launch.py..."
printf "\n%s\n" "${delimiter}"
- exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ gpu_info=$(lspci | grep VGA)
+ if echo "$gpu_info" | grep -q "AMD"
+ then
+ HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ else
+ exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ fi
fi
From eaebcf638391071172d504568d661931f7e3c740 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 13 Jan 2023 19:20:18 +0100
Subject: [PATCH 002/127] GPU detection script
This commit adds a script that detects which GPU is currently used in Windows and Linux
---
detection.py | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 detection.py
diff --git a/detection.py b/detection.py
new file mode 100644
index 00000000..eb4db0df
--- /dev/null
+++ b/detection.py
@@ -0,0 +1,45 @@
+# This script detects which GPU is currently used in Windows and Linux
+import os
+import sys
+
+def check_gpu():
+ # First, check if the `lspci` command is available
+ if not os.system("which lspci > /dev/null") == 0:
+ # If the `lspci` command is not available, try the `dxdiag` command on Windows
+ if os.name == "nt":
+ # On Windows, run the `dxdiag` command and check the output for the "Card name" field
+ # Create the dxdiag.txt file
+ os.system("dxdiag /t dxdiag.txt")
+
+ # Read the dxdiag.txt file
+ with open("dxdiag.txt", "r") as f:
+ output = f.read()
+
+ if "Card name" in output:
+ card_name_start = output.index("Card name: ") + len("Card name: ")
+ card_name_end = output.index("\n", card_name_start)
+ card_name = output[card_name_start:card_name_end]
+ else:
+ card_name = "Unknown"
+ print(f"Card name: {card_name}")
+ os.remove("dxdiag.txt")
+ if "AMD" in card_name:
+ return "AMD"
+ elif "Intel" in card_name:
+ return "Intel"
+ elif "NVIDIA" in card_name:
+ return "NVIDIA"
+ else:
+ return "Unknown"
+ else:
+ # If the `lspci` command is available, use it to get the GPU vendor and model information
+ output = os.popen("lspci | grep -i vga").read()
+ if "AMD" in output:
+ return "AMD"
+ elif "Intel" in output:
+ return "Intel"
+ elif "NVIDIA" in output:
+ return "NVIDIA"
+ else:
+ return "Unknown"
+
From a407c9f0147c779865c940cbf62c7019dbc1f7b4 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 13 Jan 2023 19:22:23 +0100
Subject: [PATCH 003/127] Automatic torch install for amd on linux
This commit allows the launch script to automatically download rocm's torch version for AMD GPUs using an external GPU detection script. It also prints the operative system and GPU in use.
---
launch.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/launch.py b/launch.py
index bcbb792c..668548f1 100644
--- a/launch.py
+++ b/launch.py
@@ -7,6 +7,7 @@ import shlex
import platform
import argparse
import json
+import detection
dir_repos = "repositories"
dir_extensions = "extensions"
@@ -15,6 +16,12 @@ git = os.environ.get('GIT', "git")
index_url = os.environ.get('INDEX_URL', "")
stored_commit_hash = None
+# Get the GPU vendor and the operating system
+gpu = detection.check_gpu()
+if os.name == "posix":
+ os_name = platform.uname().system
+else:
+ os_name = os.name
def commit_hash():
global stored_commit_hash
@@ -173,7 +180,11 @@ def run_extensions_installers(settings_file):
def prepare_environment():
- torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
+ if gpu == "AMD" and os_name !="nt":
+ torch_command = os.environ.get('TORCH_COMMAND', "pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2")
+ else:
+ torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
+
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
commandline_args = os.environ.get('COMMANDLINE_ARGS', "")
@@ -295,6 +306,8 @@ def tests(test_dir):
def start():
+ print(f"Operating System: {os_name}")
+ print(f"GPU: {gpu}")
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}")
import webui
if '--nowebui' in sys.argv:
From 6eb72fd13f34d94d5459290dd1a0bf0e9ddeda82 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sat, 14 Jan 2023 13:38:10 +0300
Subject: [PATCH 004/127] bump gradio to 3.16.1
---
modules/ui.py | 13 ++++-----
requirements.txt | 2 +-
requirements_versions.txt | 2 +-
style.css | 57 ++++++++++++++++++++++++++-------------
webui.py | 3 +--
5 files changed, 49 insertions(+), 28 deletions(-)
diff --git a/modules/ui.py b/modules/ui.py
index e86a624b..202e84e5 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -605,7 +605,7 @@ def create_ui():
setup_progressbar(progressbar, txt2img_preview, 'txt2img')
with gr.Row().style(equal_height=False):
- with gr.Column(variant='panel', elem_id="txt2img_settings"):
+ with gr.Column(variant='compact', elem_id="txt2img_settings"):
for category in ordered_ui_categories():
if category == "sampler":
steps, sampler_index = create_sampler_and_steps_selection(samplers, "txt2img")
@@ -794,7 +794,7 @@ def create_ui():
setup_progressbar(progressbar, img2img_preview, 'img2img')
with FormRow().style(equal_height=False):
- with gr.Column(variant='panel', elem_id="img2img_settings"):
+ with gr.Column(variant='compact', elem_id="img2img_settings"):
with gr.Tabs(elem_id="mode_img2img"):
with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img:
init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA").style(height=480)
@@ -1026,7 +1026,7 @@ def create_ui():
with gr.Blocks(analytics_enabled=False) as extras_interface:
with gr.Row().style(equal_height=False):
- with gr.Column(variant='panel'):
+ with gr.Column(variant='compact'):
with gr.Tabs(elem_id="mode_extras"):
with gr.TabItem('Single Image', elem_id="extras_single_tab"):
extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image")
@@ -1127,8 +1127,8 @@ def create_ui():
with gr.Blocks(analytics_enabled=False) as modelmerger_interface:
with gr.Row().style(equal_height=False):
- with gr.Column(variant='panel'):
- gr.HTML(value="
A merger of the two checkpoints will be generated in your checkpoint directory.
")
+ with gr.Column(variant='compact'):
+ gr.HTML(value="A merger of the two checkpoints will be generated in your checkpoint directory.
")
with FormRow():
primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)")
@@ -1150,7 +1150,8 @@ def create_ui():
config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method")
- modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary')
+ with gr.Row():
+ modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary')
with gr.Column(variant='panel'):
submit_result = gr.Textbox(elem_id="modelmerger_result", show_label=False)
diff --git a/requirements.txt b/requirements.txt
index e1dbf8e5..6cdea781 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@ fairscale==0.4.4
fonts
font-roboto
gfpgan
-gradio==3.15.0
+gradio==3.16.1
invisible-watermark
numpy
omegaconf
diff --git a/requirements_versions.txt b/requirements_versions.txt
index d2899292..cc06d2b4 100644
--- a/requirements_versions.txt
+++ b/requirements_versions.txt
@@ -3,7 +3,7 @@ transformers==4.19.2
accelerate==0.12.0
basicsr==1.4.2
gfpgan==1.3.8
-gradio==3.15.0
+gradio==3.16.1
numpy==1.23.3
Pillow==9.4.0
realesrgan==0.3.0
diff --git a/style.css b/style.css
index ffd6307f..14b15191 100644
--- a/style.css
+++ b/style.css
@@ -20,7 +20,7 @@
padding-right: 0.25em;
margin: 0.1em 0;
opacity: 0%;
- cursor: default;
+ cursor: default;
}
.output-html p {margin: 0 0.5em;}
@@ -221,7 +221,10 @@ fieldset span.text-gray-500, .gr-block.gr-box span.text-gray-500, label.block s
.dark fieldset span.text-gray-500, .dark .gr-block.gr-box span.text-gray-500, .dark label.block span{
background-color: rgb(31, 41, 55);
- box-shadow: 6px 0 6px 0px rgb(31, 41, 55), -6px 0 6px 0px rgb(31, 41, 55);
+ box-shadow: none;
+ border: 1px solid rgba(128, 128, 128, 0.1);
+ border-radius: 6px;
+ padding: 0.1em 0.5em;
}
#txt2img_column_batch, #img2img_column_batch{
@@ -371,7 +374,7 @@ input[type="range"]{
grid-area: tile;
}
-.modalClose,
+.modalClose,
.modalZoom,
.modalTileImage {
color: white;
@@ -509,29 +512,20 @@ input[type="range"]{
}
#quicksettings > div{
- border: none;
- background: none;
- flex: unset;
- gap: 1em;
-}
-
-#quicksettings > div > div{
- max-width: 32em;
+ max-width: 24em;
min-width: 24em;
padding: 0;
+ border: none;
+ box-shadow: none;
+ background: none;
}
-#quicksettings > div > div > div > div > label > span {
+#quicksettings > div > div > div > label > span {
position: relative;
margin-right: 9em;
margin-bottom: -1em;
}
-#quicksettings > div > div > label > span {
- position: relative;
- margin-bottom: -1em;
-}
-
canvas[key="mask"] {
z-index: 12 !important;
filter: invert();
@@ -666,7 +660,10 @@ footer {
font-weight: bold;
}
-#txt2img_checkboxes > div > div{
+#txt2img_checkboxes, #img2img_checkboxes{
+ margin-bottom: 0.5em;
+}
+#txt2img_checkboxes > div > div, #img2img_checkboxes > div > div{
flex: 0;
white-space: nowrap;
min-width: auto;
@@ -676,6 +673,30 @@ footer {
opacity: 0.5;
}
+.gr-compact {
+ border: none;
+ padding-top: 1em;
+}
+
+.dark .gr-compact{
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity));
+ margin-left: 0.8em;
+}
+
+.gr-compact > *{
+ margin-top: 0.5em !important;
+}
+
+.gr-compact .gr-block, .gr-compact .gr-form{
+ border: none;
+ box-shadow: none;
+}
+
+.gr-compact .gr-box{
+ border-radius: .5rem !important;
+ border-width: 1px !important;
+}
+
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
diff --git a/webui.py b/webui.py
index 1fff80da..84159515 100644
--- a/webui.py
+++ b/webui.py
@@ -157,7 +157,7 @@ def webui():
shared.demo = modules.ui.create_ui()
- app, local_url, share_url = shared.demo.queue(default_enabled=False).launch(
+ app, local_url, share_url = shared.demo.launch(
share=cmd_opts.share,
server_name=server_name,
server_port=cmd_opts.port,
@@ -185,7 +185,6 @@ def webui():
create_api(app)
modules.script_callbacks.app_started_callback(shared.demo, app)
- modules.script_callbacks.app_started_callback(shared.demo, app)
wait_on_server(shared.demo)
print('Restarting UI...')
From 5f8685237ed6427c9a8e502124074c740ea7696a Mon Sep 17 00:00:00 2001
From: bbc_mc
Date: Sat, 14 Jan 2023 20:00:00 +0900
Subject: [PATCH 005/127] Exclude clip index from merge
---
modules/extras.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/modules/extras.py b/modules/extras.py
index a03d558e..22668fcd 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -326,8 +326,14 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
print("Merging...")
+ chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
+
for key in tqdm.tqdm(theta_0.keys()):
if 'model' in key and key in theta_1:
+
+ if key in chckpoint_dict_skip_on_merge:
+ continue
+
a = theta_0[key]
b = theta_1[key]
@@ -352,6 +358,10 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
# I believe this part should be discarded, but I'll leave it for now until I am sure
for key in theta_1.keys():
if 'model' in key and key not in theta_0:
+
+ if key in chckpoint_dict_skip_on_merge:
+ continue
+
theta_0[key] = theta_1[key]
if save_as_half:
theta_0[key] = theta_0[key].half()
From 54fa77facc1849fbbfe61c1ca6d99b117d609d67 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 12:10:45 +0100
Subject: [PATCH 006/127] Fix detection script on macos
This fixes the script on macos
---
detection.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/detection.py b/detection.py
index eb4db0df..442c4be5 100644
--- a/detection.py
+++ b/detection.py
@@ -31,6 +31,8 @@ def check_gpu():
return "NVIDIA"
else:
return "Unknown"
+ else:
+ return "Unknown"
else:
# If the `lspci` command is available, use it to get the GPU vendor and model information
output = os.popen("lspci | grep -i vga").read()
From 865228a83736bea9ede33e98041f2a7d0ca5daaa Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sat, 14 Jan 2023 14:56:39 +0300
Subject: [PATCH 007/127] change style dropdowns to multiselect
---
javascript/localization.js | 6 ++---
modules/img2img.py | 4 +--
modules/styles.py | 12 ++++++---
modules/txt2img.py | 4 +--
modules/ui.py | 53 +++++++++++++++++++++-----------------
style.css | 15 ++++++++---
6 files changed, 57 insertions(+), 37 deletions(-)
diff --git a/javascript/localization.js b/javascript/localization.js
index f92d2d24..bf9e1506 100644
--- a/javascript/localization.js
+++ b/javascript/localization.js
@@ -10,10 +10,8 @@ ignore_ids_for_localization={
modelmerger_tertiary_model_name: 'OPTION',
train_embedding: 'OPTION',
train_hypernetwork: 'OPTION',
- txt2img_style_index: 'OPTION',
- txt2img_style2_index: 'OPTION',
- img2img_style_index: 'OPTION',
- img2img_style2_index: 'OPTION',
+ txt2img_styles: 'OPTION',
+ img2img_styles 'OPTION',
setting_random_artist_categories: 'SPAN',
setting_face_restoration_model: 'SPAN',
setting_realesrgan_enabled_models: 'SPAN',
diff --git a/modules/img2img.py b/modules/img2img.py
index f62783c6..79382cc1 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -59,7 +59,7 @@ def process_batch(p, input_dir, output_dir, args):
processed_image.save(os.path.join(output_dir, filename))
-def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args):
+def img2img(mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args):
is_batch = mode == 5
if mode == 0: # img2img
@@ -101,7 +101,7 @@ def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, pro
outpath_grids=opts.outdir_grids or opts.outdir_img2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
- styles=[prompt_style, prompt_style2],
+ styles=prompt_styles,
seed=seed,
subseed=subseed,
subseed_strength=subseed_strength,
diff --git a/modules/styles.py b/modules/styles.py
index ce6e71ca..990d5623 100644
--- a/modules/styles.py
+++ b/modules/styles.py
@@ -40,12 +40,18 @@ def apply_styles_to_prompt(prompt, styles):
class StyleDatabase:
def __init__(self, path: str):
self.no_style = PromptStyle("None", "", "")
- self.styles = {"None": self.no_style}
+ self.styles = {}
+ self.path = path
- if not os.path.exists(path):
+ self.reload()
+
+ def reload(self):
+ self.styles.clear()
+
+ if not os.path.exists(self.path):
return
- with open(path, "r", encoding="utf-8-sig", newline='') as file:
+ with open(self.path, "r", encoding="utf-8-sig", newline='') as file:
reader = csv.DictReader(file)
for row in reader:
# Support loading old CSV format with "name, text"-columns
diff --git a/modules/txt2img.py b/modules/txt2img.py
index 38b5f591..5a71793b 100644
--- a/modules/txt2img.py
+++ b/modules/txt2img.py
@@ -8,13 +8,13 @@ import modules.processing as processing
from modules.ui import plaintext_to_html
-def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, *args):
+def txt2img(prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, *args):
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples,
outpath_grids=opts.outdir_grids or opts.outdir_txt2img_grids,
prompt=prompt,
- styles=[prompt_style, prompt_style2],
+ styles=prompt_styles,
negative_prompt=negative_prompt,
seed=seed,
subseed=subseed,
diff --git a/modules/ui.py b/modules/ui.py
index 202e84e5..db198a47 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -180,7 +180,7 @@ def add_style(name: str, prompt: str, negative_prompt: str):
# reserialize all styles every time we save them
shared.prompt_styles.save_styles(shared.styles_filename)
- return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(4)]
+ return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(2)]
def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y):
@@ -197,11 +197,11 @@ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resiz
return f"resize: from {p.width}x{p.height} to {p.hr_resize_x or p.hr_upscale_to_x}x{p.hr_resize_y or p.hr_upscale_to_y}"
-def apply_styles(prompt, prompt_neg, style1_name, style2_name):
- prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, [style1_name, style2_name])
- prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, [style1_name, style2_name])
+def apply_styles(prompt, prompt_neg, styles):
+ prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles)
+ prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles)
- return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value="None"), gr.Dropdown.update(value="None")]
+ return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])]
def interrogate(image):
@@ -374,13 +374,10 @@ def create_toprow(is_img2img):
)
with gr.Row():
- with gr.Column(scale=1, elem_id="style_pos_col"):
- prompt_style = gr.Dropdown(label="Style 1", elem_id=f"{id_part}_style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())))
+ prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in shared.prompt_styles.styles.items()], value=[], multiselect=True)
+ create_refresh_button(prompt_styles, shared.prompt_styles.reload, lambda: {"choices": [k for k, v in shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles")
- with gr.Column(scale=1, elem_id="style_neg_col"):
- prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())))
-
- return prompt, prompt_style, negative_prompt, prompt_style2, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button
+ return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button
def setup_progressbar(*args, **kwargs):
@@ -590,7 +587,7 @@ def create_ui():
modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False)
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
- txt2img_prompt, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, _,txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False)
+ txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _,txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False)
dummy_component = gr.Label(visible=False)
txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False)
@@ -684,8 +681,7 @@ def create_ui():
inputs=[
txt2img_prompt,
txt2img_negative_prompt,
- txt2img_prompt_style,
- txt2img_prompt_style2,
+ txt2img_prompt_styles,
steps,
sampler_index,
restore_faces,
@@ -780,7 +776,7 @@ def create_ui():
modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True)
with gr.Blocks(analytics_enabled=False) as img2img_interface:
- img2img_prompt, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True)
+ img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True)
with gr.Row(elem_id='img2img_progress_row'):
img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False)
@@ -921,8 +917,7 @@ def create_ui():
dummy_component,
img2img_prompt,
img2img_negative_prompt,
- img2img_prompt_style,
- img2img_prompt_style2,
+ img2img_prompt_styles,
init_img,
sketch,
init_img_with_mask,
@@ -977,7 +972,7 @@ def create_ui():
)
prompts = [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]
- style_dropdowns = [(txt2img_prompt_style, txt2img_prompt_style2), (img2img_prompt_style, img2img_prompt_style2)]
+ style_dropdowns = [txt2img_prompt_styles, img2img_prompt_styles]
style_js_funcs = ["update_txt2img_tokens", "update_img2img_tokens"]
for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], prompts):
@@ -987,15 +982,15 @@ def create_ui():
# Have to pass empty dummy component here, because the JavaScript and Python function have to accept
# the same number of parameters, but we only know the style-name after the JavaScript prompt
inputs=[dummy_component, prompt, negative_prompt],
- outputs=[txt2img_prompt_style, img2img_prompt_style, txt2img_prompt_style2, img2img_prompt_style2],
+ outputs=[txt2img_prompt_styles, img2img_prompt_styles],
)
- for button, (prompt, negative_prompt), (style1, style2), js_func in zip([txt2img_prompt_style_apply, img2img_prompt_style_apply], prompts, style_dropdowns, style_js_funcs):
+ for button, (prompt, negative_prompt), styles, js_func in zip([txt2img_prompt_style_apply, img2img_prompt_style_apply], prompts, style_dropdowns, style_js_funcs):
button.click(
fn=apply_styles,
_js=js_func,
- inputs=[prompt, negative_prompt, style1, style2],
- outputs=[prompt, negative_prompt, style1, style2],
+ inputs=[prompt, negative_prompt, styles],
+ outputs=[prompt, negative_prompt, styles],
)
token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter])
@@ -1530,6 +1525,7 @@ def create_ui():
previous_section = None
current_tab = None
+ current_row = None
with gr.Tabs(elem_id="settings"):
for i, (k, item) in enumerate(opts.data_labels.items()):
section_must_be_skipped = item.section[0] is None
@@ -1538,10 +1534,14 @@ def create_ui():
elem_id, text = item.section
if current_tab is not None:
+ current_row.__exit__()
current_tab.__exit__()
+ gr.Group()
current_tab = gr.TabItem(elem_id="settings_{}".format(elem_id), label=text)
current_tab.__enter__()
+ current_row = gr.Column(variant='compact')
+ current_row.__enter__()
previous_section = item.section
@@ -1556,6 +1556,7 @@ def create_ui():
components.append(component)
if current_tab is not None:
+ current_row.__exit__()
current_tab.__exit__()
with gr.TabItem("Actions"):
@@ -1774,7 +1775,13 @@ def create_ui():
apply_field(x, 'value')
if type(x) == gr.Dropdown:
- apply_field(x, 'value', lambda val: val in x.choices, getattr(x, 'init_field', None))
+ def check_dropdown(val):
+ if x.multiselect:
+ return all([value in x.choices for value in val])
+ else:
+ return val in x.choices
+
+ apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None))
visit(txt2img_interface, loadsave, "txt2img")
visit(img2img_interface, loadsave, "img2img")
diff --git a/style.css b/style.css
index 14b15191..c1ebd501 100644
--- a/style.css
+++ b/style.css
@@ -114,6 +114,7 @@
min-width: unset !important;
flex-grow: 0 !important;
padding: 0.4em 0;
+ gap: 0;
}
#roll_col > button {
@@ -141,10 +142,14 @@
min-width: 8em !important;
}
-#txt2img_style_index, #txt2img_style2_index, #img2img_style_index, #img2img_style2_index{
+#txt2img_styles, #img2img_styles{
margin-top: 1em;
}
+#txt2img_styles ul, #img2img_styles ul{
+ max-height: 35em;
+}
+
.gr-form{
background: transparent;
}
@@ -154,10 +159,14 @@
margin-bottom: 0;
}
-#toprow div{
+#toprow div.gr-box, #toprow div.gr-form{
border: none;
gap: 0;
background: transparent;
+ box-shadow: none;
+}
+#toprow div{
+ gap: 0;
}
#resize_mode{
@@ -615,7 +624,7 @@ canvas[key="mask"] {
max-width: 2.5em;
min-width: 2.5em !important;
height: 2.4em;
- margin: 0.55em 0;
+ margin: 1.6em 0 0 0;
}
#quicksettings .gr-button-tool{
From 934cba0f4ca3e80a2079a657ebb6ca8c1ee2d10b Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 15:43:29 +0100
Subject: [PATCH 008/127] Delete detection.py
---
detection.py | 47 -----------------------------------------------
1 file changed, 47 deletions(-)
delete mode 100644 detection.py
diff --git a/detection.py b/detection.py
deleted file mode 100644
index 442c4be5..00000000
--- a/detection.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# This script detects which GPU is currently used in Windows and Linux
-import os
-import sys
-
-def check_gpu():
- # First, check if the `lspci` command is available
- if not os.system("which lspci > /dev/null") == 0:
- # If the `lspci` command is not available, try the `dxdiag` command on Windows
- if os.name == "nt":
- # On Windows, run the `dxdiag` command and check the output for the "Card name" field
- # Create the dxdiag.txt file
- os.system("dxdiag /t dxdiag.txt")
-
- # Read the dxdiag.txt file
- with open("dxdiag.txt", "r") as f:
- output = f.read()
-
- if "Card name" in output:
- card_name_start = output.index("Card name: ") + len("Card name: ")
- card_name_end = output.index("\n", card_name_start)
- card_name = output[card_name_start:card_name_end]
- else:
- card_name = "Unknown"
- print(f"Card name: {card_name}")
- os.remove("dxdiag.txt")
- if "AMD" in card_name:
- return "AMD"
- elif "Intel" in card_name:
- return "Intel"
- elif "NVIDIA" in card_name:
- return "NVIDIA"
- else:
- return "Unknown"
- else:
- return "Unknown"
- else:
- # If the `lspci` command is available, use it to get the GPU vendor and model information
- output = os.popen("lspci | grep -i vga").read()
- if "AMD" in output:
- return "AMD"
- elif "Intel" in output:
- return "Intel"
- elif "NVIDIA" in output:
- return "NVIDIA"
- else:
- return "Unknown"
-
From 629612935372aa7faeb764b5660431e49b93de24 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 15:45:07 +0100
Subject: [PATCH 009/127] Revert detection code
---
launch.py | 15 +--------------
1 file changed, 1 insertion(+), 14 deletions(-)
diff --git a/launch.py b/launch.py
index 668548f1..bcbb792c 100644
--- a/launch.py
+++ b/launch.py
@@ -7,7 +7,6 @@ import shlex
import platform
import argparse
import json
-import detection
dir_repos = "repositories"
dir_extensions = "extensions"
@@ -16,12 +15,6 @@ git = os.environ.get('GIT', "git")
index_url = os.environ.get('INDEX_URL', "")
stored_commit_hash = None
-# Get the GPU vendor and the operating system
-gpu = detection.check_gpu()
-if os.name == "posix":
- os_name = platform.uname().system
-else:
- os_name = os.name
def commit_hash():
global stored_commit_hash
@@ -180,11 +173,7 @@ def run_extensions_installers(settings_file):
def prepare_environment():
- if gpu == "AMD" and os_name !="nt":
- torch_command = os.environ.get('TORCH_COMMAND', "pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2")
- else:
- torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
-
+ torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
commandline_args = os.environ.get('COMMANDLINE_ARGS', "")
@@ -306,8 +295,6 @@ def tests(test_dir):
def start():
- print(f"Operating System: {os_name}")
- print(f"GPU: {gpu}")
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}")
import webui
if '--nowebui' in sys.argv:
From 6192a222bf7131771a2cd7655a64a5b24a1e6e2e Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 15:46:23 +0100
Subject: [PATCH 010/127] Export TORCH_COMMAND for AMD from the webui
---
webui.sh | 1 +
1 file changed, 1 insertion(+)
diff --git a/webui.sh b/webui.sh
index 23629ef9..35f52f2a 100755
--- a/webui.sh
+++ b/webui.sh
@@ -168,6 +168,7 @@ else
gpu_info=$(lspci | grep VGA)
if echo "$gpu_info" | grep -q "AMD"
then
+ export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
else
exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
From c4ba34928ec7f977585494f0fa5925496c887698 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 15:58:50 +0100
Subject: [PATCH 011/127] Quick format fix
---
webui.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index 35f52f2a..fcba6b7d 100755
--- a/webui.sh
+++ b/webui.sh
@@ -168,7 +168,7 @@ else
gpu_info=$(lspci | grep VGA)
if echo "$gpu_info" | grep -q "AMD"
then
- export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
+ export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
else
exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
From fad850fc3d33e7cda2ce4b3a32ab7976c313db53 Mon Sep 17 00:00:00 2001
From: Vladimir Mandic
Date: Sat, 14 Jan 2023 11:18:05 -0500
Subject: [PATCH 012/127] add server_start to shared.state
---
modules/shared.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/modules/shared.py b/modules/shared.py
index e0ec3136..ef93637c 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -168,6 +168,7 @@ class State:
textinfo = None
time_start = None
need_restart = False
+ server_start = None
def skip(self):
self.skipped = True
@@ -241,6 +242,7 @@ class State:
state = State()
+state.server_start = time.time()
artist_db = modules.artists.ArtistsDatabase(os.path.join(script_path, 'artists.csv'))
From 86359535d6fb0899fa9e838d27f2006b929331d5 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sat, 14 Jan 2023 22:43:01 +0300
Subject: [PATCH 013/127] add buttons to copy images between img2img tabs
---
javascript/ui.js | 21 +++++++++++++++++++--
modules/ui.py | 42 +++++++++++++++++++++++++++++++++++++++++-
style.css | 18 ++++++++++++++++++
3 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/javascript/ui.js b/javascript/ui.js
index 1e04a8f4..f8279124 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -45,10 +45,27 @@ function switch_to_txt2img(){
return args_to_array(arguments);
}
-function switch_to_img2img(){
+function switch_to_img2img_tab(no){
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
- gradioApp().getElementById('mode_img2img').querySelectorAll('button')[0].click();
+ gradioApp().getElementById('mode_img2img').querySelectorAll('button')[no].click();
+}
+function switch_to_img2img(){
+ switch_to_img2img_tab(0);
+ return args_to_array(arguments);
+}
+function switch_to_sketch(){
+ switch_to_img2img_tab(1);
+ return args_to_array(arguments);
+}
+
+function switch_to_inpaint(){
+ switch_to_img2img_tab(2);
+ return args_to_array(arguments);
+}
+
+function switch_to_inpaint_sketch(){
+ switch_to_img2img_tab(3);
return args_to_array(arguments);
}
diff --git a/modules/ui.py b/modules/ui.py
index 2625ae32..2425c66f 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -795,19 +795,39 @@ def create_ui():
with FormRow().style(equal_height=False):
with gr.Column(variant='panel', elem_id="img2img_settings"):
+ copy_image_buttons = []
+ copy_image_destinations = {}
+
+ def add_copy_image_controls(tab_name, elem):
+ with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"):
+ gr.HTML("Copy image to: ", elem_id=f"img2img_label_copy_to_{tab_name}")
+
+ for title, name in zip(['img2img', 'sketch', 'inpaint', 'inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']):
+ if name == tab_name:
+ gr.Button(title, interactive=False)
+ copy_image_destinations[name] = elem
+ continue
+
+ button = gr.Button(title)
+ copy_image_buttons.append((button, name, elem))
+
with gr.Tabs(elem_id="mode_img2img"):
with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img:
init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA").style(height=480)
+ add_copy_image_controls('img2img', init_img)
with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch:
sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA").style(height=480)
+ add_copy_image_controls('sketch', sketch)
with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint:
init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA").style(height=480)
+ add_copy_image_controls('inpaint', init_img_with_mask)
with gr.TabItem('Inpaint sketch', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color:
inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA").style(height=480)
inpaint_color_sketch_orig = gr.State(None)
+ add_copy_image_controls('inpaint_sketch', inpaint_color_sketch)
def update_orig(image, state):
if image is not None:
@@ -824,10 +844,29 @@ def create_ui():
with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch:
hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else ''
- gr.HTML(f"Process images in a directory on the same machine where the server is running.
Use an empty output directory to save pictures normally instead of writing to the output directory.{hidden}
")
+ gr.HTML(f"Process images in a directory on the same machine where the server is running.
Use an empty output directory to save pictures normally instead of writing to the output directory.{hidden}
")
img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir")
img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir")
+ def copy_image(img):
+ if isinstance(img, dict) and 'image' in img:
+ return img['image']
+
+ return img
+
+ for button, name, elem in copy_image_buttons:
+ button.click(
+ fn=copy_image,
+ inputs=[elem],
+ outputs=[copy_image_destinations[name]],
+ )
+ button.click(
+ fn=lambda: None,
+ _js="switch_to_"+name.replace(" ", "_"),
+ inputs=[],
+ outputs=[],
+ )
+
with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls:
with FormRow():
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur")
@@ -856,6 +895,7 @@ def create_ui():
outputs=[inpaint_controls, mask_alpha],
)
+
with FormRow():
resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)"], type="index", value="Just resize")
diff --git a/style.css b/style.css
index ffd6307f..2d484e06 100644
--- a/style.css
+++ b/style.css
@@ -676,6 +676,24 @@ footer {
opacity: 0.5;
}
+#mode_img2img > div > div{
+ gap: 0 !important;
+}
+
+[id*='img2img_copy_to_'] {
+ border: none;
+}
+
+[id*='img2img_copy_to_'] > button {
+}
+
+[id*='img2img_label_copy_to_'] {
+ font-size: 1.0em;
+ font-weight: bold;
+ text-align: center;
+ line-height: 2.4em;
+}
+
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
From 2e172cf831a928223e93803b94896325bd4c22a7 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 22:25:32 +0100
Subject: [PATCH 014/127] Only set TORCH_COMMAND if wasn't set webui-user
---
webui.sh | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index fcba6b7d..35542ed6 100755
--- a/webui.sh
+++ b/webui.sh
@@ -168,7 +168,10 @@ else
gpu_info=$(lspci | grep VGA)
if echo "$gpu_info" | grep -q "AMD"
then
- export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
+ if [ -z ${TORCH_COMMAND+x} ]
+ then
+ export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
+ fi
HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
else
exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
From ba077e2110cab891a46d14665fb161ce0669f31e Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Sat, 14 Jan 2023 23:19:52 +0100
Subject: [PATCH 015/127] Fix TORCH_COMMAND check
---
webui.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index 35542ed6..6e07778f 100755
--- a/webui.sh
+++ b/webui.sh
@@ -168,7 +168,7 @@ else
gpu_info=$(lspci | grep VGA)
if echo "$gpu_info" | grep -q "AMD"
then
- if [ -z ${TORCH_COMMAND+x} ]
+ if [[ -z "${TORCH_COMMAND}" ]]
then
export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
fi
From cbbdfc3609097fb8b31e32387396ee1ae299fc6f Mon Sep 17 00:00:00 2001
From: Josh R
Date: Sat, 14 Jan 2023 14:45:08 -0800
Subject: [PATCH 016/127] Fix Aspect Ratio Overlay / AROverlay to work with
Inpaint & Sketch
---
javascript/aspectRatioOverlay.js | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js
index 66f26a22..0f164b82 100644
--- a/javascript/aspectRatioOverlay.js
+++ b/javascript/aspectRatioOverlay.js
@@ -21,11 +21,16 @@ function dimensionChange(e, is_width, is_height){
var targetElement = null;
var tabIndex = get_tab_index('mode_img2img')
- if(tabIndex == 0){
+ if(tabIndex == 0){ // img2img
targetElement = gradioApp().querySelector('div[data-testid=image] img');
- } else if(tabIndex == 1){
+ } else if(tabIndex == 1){ //Sketch
+ targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img');
+ } else if(tabIndex == 2){ // Inpaint
targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img');
+ } else if(tabIndex == 3){ // Inpaint sketch
+ targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img');
}
+
if(targetElement){
From 9ef41df6f9043d58fbbeea1f06be8e5c8622248b Mon Sep 17 00:00:00 2001
From: Josh R
Date: Sat, 14 Jan 2023 15:26:45 -0800
Subject: [PATCH 017/127] add inpaint masking controls to orderable section
that the settings can order
---
modules/shared.py | 1 +
modules/ui.py | 58 +++++++++++++++++++++++------------------------
2 files changed, 30 insertions(+), 29 deletions(-)
diff --git a/modules/shared.py b/modules/shared.py
index 51df056c..7ce8003f 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -116,6 +116,7 @@ restricted_opts = {
}
ui_reorder_categories = [
+ "masking",
"sampler",
"dimensions",
"cfg",
diff --git a/modules/ui.py b/modules/ui.py
index 2425c66f..174930ab 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -867,35 +867,6 @@ def create_ui():
outputs=[],
)
- with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls:
- with FormRow():
- mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur")
- mask_alpha = gr.Slider(label="Mask transparency", visible=False, elem_id="img2img_mask_alpha")
-
- with FormRow():
- inpainting_mask_invert = gr.Radio(label='Mask mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", elem_id="img2img_mask_mode")
-
- with FormRow():
- inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='original', type="index", elem_id="img2img_inpainting_fill")
-
- with FormRow():
- with gr.Column():
- inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res")
-
- with gr.Column(scale=4):
- inpaint_full_res_padding = gr.Slider(label='Only masked padding, pixels', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding")
-
- def select_img2img_tab(tab):
- return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3),
-
- for i, elem in enumerate([tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch]):
- elem.select(
- fn=lambda tab=i: select_img2img_tab(tab),
- inputs=[],
- outputs=[inpaint_controls, mask_alpha],
- )
-
-
with FormRow():
resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)"], type="index", value="Just resize")
@@ -937,6 +908,35 @@ def create_ui():
with FormGroup(elem_id="img2img_script_container"):
custom_inputs = modules.scripts.scripts_img2img.setup_ui()
+ elif category == "masking":
+ with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls:
+ with FormRow():
+ mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur")
+ mask_alpha = gr.Slider(label="Mask transparency", visible=False, elem_id="img2img_mask_alpha")
+
+ with FormRow():
+ inpainting_mask_invert = gr.Radio(label='Mask mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", elem_id="img2img_mask_mode")
+
+ with FormRow():
+ inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='original', type="index", elem_id="img2img_inpainting_fill")
+
+ with FormRow():
+ with gr.Column():
+ inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res")
+
+ with gr.Column(scale=4):
+ inpaint_full_res_padding = gr.Slider(label='Only masked padding, pixels', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding")
+
+ def select_img2img_tab(tab):
+ return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3),
+
+ for i, elem in enumerate([tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch]):
+ elem.select(
+ fn=lambda tab=i: select_img2img_tab(tab),
+ inputs=[],
+ outputs=[inpaint_controls, mask_alpha],
+ )
+
img2img_gallery, generation_info, html_info, html_log = create_output_panel("img2img", opts.outdir_img2img_samples)
parameters_copypaste.bind_buttons({"img2img": img2img_paste}, None, img2img_prompt)
From d97f467c0d27695d23edad5e4f8898a57e0ccb00 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 09:24:21 +0300
Subject: [PATCH 018/127] add license file
---
LICENSE.txt | 663 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 663 insertions(+)
create mode 100644 LICENSE.txt
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..14577543
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,663 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (c) 2023 AUTOMATIC1111
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
From eef1990a5e6c41ecb6943ff5529316ad5ededb2a Mon Sep 17 00:00:00 2001
From: brkirch
Date: Sun, 15 Jan 2023 08:13:33 -0500
Subject: [PATCH 019/127] Fix Approx NN on devices other than CUDA
---
modules/sd_vae_approx.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py
index 0a58542d..0027343a 100644
--- a/modules/sd_vae_approx.py
+++ b/modules/sd_vae_approx.py
@@ -36,7 +36,7 @@ def model():
if sd_vae_approx_model is None:
sd_vae_approx_model = VAEApprox()
- sd_vae_approx_model.load_state_dict(torch.load(os.path.join(paths.models_path, "VAE-approx", "model.pt")))
+ sd_vae_approx_model.load_state_dict(torch.load(os.path.join(paths.models_path, "VAE-approx", "model.pt"), map_location='cpu' if devices.device.type != 'cuda' else None))
sd_vae_approx_model.eval()
sd_vae_approx_model.to(devices.device, devices.dtype)
From f0312565e5b4d56a421af889a9a8eaea0ba92959 Mon Sep 17 00:00:00 2001
From: Vladimir Mandic
Date: Sun, 15 Jan 2023 09:42:34 -0500
Subject: [PATCH 020/127] increase block size
---
modules/hashes.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/modules/hashes.py b/modules/hashes.py
index 14231771..b85a7580 100644
--- a/modules/hashes.py
+++ b/modules/hashes.py
@@ -34,9 +34,10 @@ def cache(subsection):
def calculate_sha256(filename):
hash_sha256 = hashlib.sha256()
+ blksize = 1024 * 1024
with open(filename, "rb") as f:
- for chunk in iter(lambda: f.read(4096), b""):
+ for chunk in iter(lambda: f.read(blksize), b""):
hash_sha256.update(chunk)
return hash_sha256.hexdigest()
From d8b90ac121cbf0c18b1dc9d56a5e1d14ca51e74e Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 18:50:56 +0300
Subject: [PATCH 021/127] big rework of progressbar/preview system to allow
multiple users to prompts at the same time and do not get previews of each
other
---
javascript/progressbar.js | 257 +++++++++++-------
javascript/textualInversion.js | 13 +-
javascript/ui.js | 35 ++-
modules/call_queue.py | 19 +-
modules/hypernetworks/hypernetwork.py | 6 +-
modules/img2img.py | 2 +-
modules/progress.py | 96 +++++++
modules/sd_samplers.py | 2 +-
modules/shared.py | 16 +-
modules/textual_inversion/preprocess.py | 2 +-
.../textual_inversion/textual_inversion.py | 6 +-
modules/txt2img.py | 2 +-
modules/ui.py | 41 +--
modules/ui_progress.py | 101 -------
style.css | 74 +++--
webui.py | 3 +
16 files changed, 395 insertions(+), 280 deletions(-)
create mode 100644 modules/progress.py
delete mode 100644 modules/ui_progress.py
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index d6323ed9..b7524ef7 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -1,82 +1,25 @@
// code related to showing and updating progressbar shown as the image is being made
-global_progressbars = {}
+
+
galleries = {}
+storedGallerySelections = {}
galleryObservers = {}
-// this tracks launches of window.setTimeout for progressbar to prevent starting a new timeout when the previous is still running
-timeoutIds = {}
-
-function check_progressbar(id_part, id_progressbar, id_progressbar_span, id_skip, id_interrupt, id_preview, id_gallery){
- // gradio 3.8's enlightened approach allows them to create two nested div elements inside each other with same id
- // every time you use gr.HTML(elem_id='xxx'), so we handle this here
- var progressbar = gradioApp().querySelector("#"+id_progressbar+" #"+id_progressbar)
- var progressbarParent
- if(progressbar){
- progressbarParent = gradioApp().querySelector("#"+id_progressbar)
- } else{
- progressbar = gradioApp().getElementById(id_progressbar)
- progressbarParent = null
- }
-
- var skip = id_skip ? gradioApp().getElementById(id_skip) : null
- var interrupt = gradioApp().getElementById(id_interrupt)
-
- if(opts.show_progress_in_title && progressbar && progressbar.offsetParent){
- if(progressbar.innerText){
- let newtitle = '[' + progressbar.innerText.trim() + '] Stable Diffusion';
- if(document.title != newtitle){
- document.title = newtitle;
- }
- }else{
- let newtitle = 'Stable Diffusion'
- if(document.title != newtitle){
- document.title = newtitle;
- }
- }
- }
-
- if(progressbar!= null && progressbar != global_progressbars[id_progressbar]){
- global_progressbars[id_progressbar] = progressbar
-
- var mutationObserver = new MutationObserver(function(m){
- if(timeoutIds[id_part]) return;
-
- preview = gradioApp().getElementById(id_preview)
- gallery = gradioApp().getElementById(id_gallery)
-
- if(preview != null && gallery != null){
- preview.style.width = gallery.clientWidth + "px"
- preview.style.height = gallery.clientHeight + "px"
- if(progressbarParent) progressbar.style.width = progressbarParent.clientWidth + "px"
-
- //only watch gallery if there is a generation process going on
- check_gallery(id_gallery);
-
- var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
- if(progressDiv){
- timeoutIds[id_part] = window.setTimeout(function() {
- timeoutIds[id_part] = null
- requestMoreProgress(id_part, id_progressbar_span, id_skip, id_interrupt)
- }, 500)
- } else{
- if (skip) {
- skip.style.display = "none"
- }
- interrupt.style.display = "none"
-
- //disconnect observer once generation finished, so user can close selected image if they want
- if (galleryObservers[id_gallery]) {
- galleryObservers[id_gallery].disconnect();
- galleries[id_gallery] = null;
- }
- }
- }
-
- });
- mutationObserver.observe( progressbar, { childList:true, subtree:true })
- }
+function rememberGallerySelection(id_gallery){
+ storedGallerySelections[id_gallery] = getGallerySelectedIndex(id_gallery)
}
+function getGallerySelectedIndex(id_gallery){
+ let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item')
+ let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2')
+
+ let currentlySelectedIndex = -1
+ galleryButtons.forEach(function(v, i){ if(v==galleryBtnSelected) { currentlySelectedIndex = i } })
+
+ return currentlySelectedIndex
+}
+
+// this is a workaround for https://github.com/gradio-app/gradio/issues/2984
function check_gallery(id_gallery){
let gallery = gradioApp().getElementById(id_gallery)
// if gallery has no change, no need to setting up observer again.
@@ -85,10 +28,16 @@ function check_gallery(id_gallery){
if(galleryObservers[id_gallery]){
galleryObservers[id_gallery].disconnect();
}
- let prevSelectedIndex = selected_gallery_index();
+
+ storedGallerySelections[id_gallery] = -1
+
galleryObservers[id_gallery] = new MutationObserver(function (){
let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item')
let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2')
+ let currentlySelectedIndex = getGallerySelectedIndex(id_gallery)
+ prevSelectedIndex = storedGallerySelections[id_gallery]
+ storedGallerySelections[id_gallery] = -1
+
if (prevSelectedIndex !== -1 && galleryButtons.length>prevSelectedIndex && !galleryBtnSelected) {
// automatically re-open previously selected index (if exists)
activeElement = gradioApp().activeElement;
@@ -120,30 +69,150 @@ function check_gallery(id_gallery){
}
onUiUpdate(function(){
- check_progressbar('txt2img', 'txt2img_progressbar', 'txt2img_progress_span', 'txt2img_skip', 'txt2img_interrupt', 'txt2img_preview', 'txt2img_gallery')
- check_progressbar('img2img', 'img2img_progressbar', 'img2img_progress_span', 'img2img_skip', 'img2img_interrupt', 'img2img_preview', 'img2img_gallery')
- check_progressbar('ti', 'ti_progressbar', 'ti_progress_span', '', 'ti_interrupt', 'ti_preview', 'ti_gallery')
+ check_gallery('txt2img_gallery')
+ check_gallery('img2img_gallery')
})
-function requestMoreProgress(id_part, id_progressbar_span, id_skip, id_interrupt){
- btn = gradioApp().getElementById(id_part+"_check_progress");
- if(btn==null) return;
-
- btn.click();
- var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
- var skip = id_skip ? gradioApp().getElementById(id_skip) : null
- var interrupt = gradioApp().getElementById(id_interrupt)
- if(progressDiv && interrupt){
- if (skip) {
- skip.style.display = "block"
+function request(url, data, handler, errorHandler){
+ var xhr = new XMLHttpRequest();
+ var url = url;
+ xhr.open("POST", url, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ var js = JSON.parse(xhr.responseText);
+ handler(js)
+ } else{
+ errorHandler()
+ }
}
- interrupt.style.display = "block"
+ };
+ var js = JSON.stringify(data);
+ xhr.send(js);
+}
+
+function pad2(x){
+ return x<10 ? '0'+x : x
+}
+
+function formatTime(secs){
+ if(secs > 3600){
+ return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60)
+ } else if(secs > 60){
+ return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60)
+ } else{
+ return Math.floor(secs) + "s"
}
}
-function requestProgress(id_part){
- btn = gradioApp().getElementById(id_part+"_check_progress_initial");
- if(btn==null) return;
-
- btn.click();
+function randomId(){
+ return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")"
+}
+
+// starts sending progress requests to "/internal/progress" uri, creating progressbar above progressbarContainer element and
+// preview inside gallery element. Cleans up all created stuff when the task is over and calls atEnd.
+// calls onProgress every time there is a progress update
+function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgress){
+ var dateStart = new Date()
+ var wasEverActive = false
+ var parentProgressbar = progressbarContainer.parentNode
+ var parentGallery = gallery.parentNode
+
+ var divProgress = document.createElement('div')
+ divProgress.className='progressDiv'
+ var divInner = document.createElement('div')
+ divInner.className='progress'
+
+ divProgress.appendChild(divInner)
+ parentProgressbar.insertBefore(divProgress, progressbarContainer)
+
+ var livePreview = document.createElement('div')
+ livePreview.className='livePreview'
+ parentGallery.insertBefore(livePreview, gallery)
+
+ var removeProgressBar = function(){
+ parentProgressbar.removeChild(divProgress)
+ parentGallery.removeChild(livePreview)
+ atEnd()
+ }
+
+ var fun = function(id_task, id_live_preview){
+ request("/internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){
+ console.log(res)
+
+ if(res.completed){
+ removeProgressBar()
+ return
+ }
+
+ var rect = progressbarContainer.getBoundingClientRect()
+
+ if(rect.width){
+ divProgress.style.width = rect.width + "px";
+ }
+
+ progressText = ""
+
+ divInner.style.width = ((res.progress || 0) * 100.0) + '%'
+
+ if(res.progress > 0){
+ progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%'
+ }
+
+ if(res.eta){
+ progressText += " ETA: " + formatTime(res.eta)
+ } else if(res.textinfo){
+ progressText += " " + res.textinfo
+ }
+
+ divInner.textContent = progressText
+
+ var elapsedFromStart = (new Date() - dateStart) / 1000
+
+ if(res.active) wasEverActive = true;
+
+ if(! res.active && wasEverActive){
+ removeProgressBar()
+ return
+ }
+
+ if(elapsedFromStart > 5 && !res.queued && !res.active){
+ removeProgressBar()
+ return
+ }
+
+
+ if(res.live_preview){
+ var img = new Image();
+ img.onload = function() {
+ var rect = gallery.getBoundingClientRect()
+ if(rect.width){
+ livePreview.style.width = rect.width + "px"
+ livePreview.style.height = rect.height + "px"
+ }
+
+ livePreview.innerHTML = ''
+ livePreview.appendChild(img)
+ if(livePreview.childElementCount > 2){
+ livePreview.removeChild(livePreview.firstElementChild)
+ }
+ }
+ img.src = res.live_preview;
+ }
+
+
+ if(onProgress){
+ onProgress(res)
+ }
+
+ setTimeout(() => {
+ fun(id_task, res.id_live_preview);
+ }, 500)
+ }, function(){
+ removeProgressBar()
+ })
+ }
+
+ fun(id_task, 0)
}
diff --git a/javascript/textualInversion.js b/javascript/textualInversion.js
index 8061be08..0354b860 100644
--- a/javascript/textualInversion.js
+++ b/javascript/textualInversion.js
@@ -1,8 +1,17 @@
+
function start_training_textual_inversion(){
- requestProgress('ti')
gradioApp().querySelector('#ti_error').innerHTML=''
- return args_to_array(arguments)
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('ti_output'), gradioApp().getElementById('ti_gallery'), function(){}, function(progress){
+ gradioApp().getElementById('ti_progress').innerHTML = progress.textinfo
+ })
+
+ var res = args_to_array(arguments)
+
+ res[0] = id
+
+ return res
}
diff --git a/javascript/ui.js b/javascript/ui.js
index f8279124..ecf97cb3 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -126,18 +126,41 @@ function create_submit_args(args){
return res
}
-function submit(){
- requestProgress('txt2img')
+function showSubmitButtons(tabname, show){
+ gradioApp().getElementById(tabname+'_interrupt').style.display = show ? "none" : "block"
+ gradioApp().getElementById(tabname+'_skip').style.display = show ? "none" : "block"
+}
- return create_submit_args(arguments)
+function submit(){
+ rememberGallerySelection('txt2img_gallery')
+ showSubmitButtons('txt2img', false)
+
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function(){
+ showSubmitButtons('txt2img', true)
+
+ })
+
+ var res = create_submit_args(arguments)
+
+ res[0] = id
+
+ return res
}
function submit_img2img(){
- requestProgress('img2img')
+ rememberGallerySelection('img2img_gallery')
+ showSubmitButtons('img2img', false)
- res = create_submit_args(arguments)
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function(){
+ showSubmitButtons('img2img', true)
+ })
- res[0] = get_tab_index('mode_img2img')
+ var res = create_submit_args(arguments)
+
+ res[0] = id
+ res[1] = get_tab_index('mode_img2img')
return res
}
diff --git a/modules/call_queue.py b/modules/call_queue.py
index 4cd49533..92097c15 100644
--- a/modules/call_queue.py
+++ b/modules/call_queue.py
@@ -4,7 +4,7 @@ import threading
import traceback
import time
-from modules import shared
+from modules import shared, progress
queue_lock = threading.Lock()
@@ -22,12 +22,23 @@ def wrap_queued_call(func):
def wrap_gradio_gpu_call(func, extra_outputs=None):
def f(*args, **kwargs):
- shared.state.begin()
+ # if the first argument is a string that says "task(...)", it is treated as a job id
+ if len(args) > 0 and type(args[0]) == str and args[0][0:5] == "task(" and args[0][-1] == ")":
+ id_task = args[0]
+ progress.add_task_to_queue(id_task)
+ else:
+ id_task = None
with queue_lock:
- res = func(*args, **kwargs)
+ shared.state.begin()
+ progress.start_task(id_task)
- shared.state.end()
+ try:
+ res = func(*args, **kwargs)
+ finally:
+ progress.finish_task(id_task)
+
+ shared.state.end()
return res
diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py
index 3aebefa8..ae6af516 100644
--- a/modules/hypernetworks/hypernetwork.py
+++ b/modules/hypernetworks/hypernetwork.py
@@ -453,7 +453,7 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None,
shared.reload_hypernetworks()
-def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_hypernetwork_every, template_filename, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height):
+def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_hypernetwork_every, template_filename, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height):
# images allows training previews to have infotext. Importing it at the top causes a circular import problem.
from modules import images
@@ -629,7 +629,6 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step,
description = f"Training hypernetwork [Epoch {epoch_num}: {epoch_step+1}/{steps_per_epoch}]loss: {loss_step:.7f}"
pbar.set_description(description)
- shared.state.textinfo = description
if hypernetwork_dir is not None and steps_done % save_hypernetwork_every == 0:
# Before saving, change name to match current checkpoint.
hypernetwork_name_every = f'{hypernetwork_name}-{steps_done}'
@@ -701,7 +700,8 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, gradient_step,
torch.cuda.set_rng_state_all(cuda_rng_state)
hypernetwork.train()
if image is not None:
- shared.state.current_image = image
+ shared.state.assign_current_image(image)
+
last_saved_image, last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False)
last_saved_image += f", prompt: {preview_text}"
diff --git a/modules/img2img.py b/modules/img2img.py
index f62783c6..f4a03c57 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -59,7 +59,7 @@ def process_batch(p, input_dir, output_dir, args):
processed_image.save(os.path.join(output_dir, filename))
-def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args):
+def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, *args):
is_batch = mode == 5
if mode == 0: # img2img
diff --git a/modules/progress.py b/modules/progress.py
new file mode 100644
index 00000000..3327b883
--- /dev/null
+++ b/modules/progress.py
@@ -0,0 +1,96 @@
+import base64
+import io
+import time
+
+import gradio as gr
+from pydantic import BaseModel, Field
+
+from modules.shared import opts
+
+import modules.shared as shared
+
+
+current_task = None
+pending_tasks = {}
+finished_tasks = []
+
+
+def start_task(id_task):
+ global current_task
+
+ current_task = id_task
+ pending_tasks.pop(id_task, None)
+
+
+def finish_task(id_task):
+ global current_task
+
+ if current_task == id_task:
+ current_task = None
+
+ finished_tasks.append(id_task)
+ if len(finished_tasks) > 16:
+ finished_tasks.pop(0)
+
+
+def add_task_to_queue(id_job):
+ pending_tasks[id_job] = time.time()
+
+
+class ProgressRequest(BaseModel):
+ id_task: str = Field(default=None, title="Task ID", description="id of the task to get progress for")
+ id_live_preview: int = Field(default=-1, title="Live preview image ID", description="id of last received last preview image")
+
+
+class ProgressResponse(BaseModel):
+ active: bool = Field(title="Whether the task is being worked on right now")
+ queued: bool = Field(title="Whether the task is in queue")
+ completed: bool = Field(title="Whether the task has already finished")
+ progress: float = Field(default=None, title="Progress", description="The progress with a range of 0 to 1")
+ eta: float = Field(default=None, title="ETA in secs")
+ live_preview: str = Field(default=None, title="Live preview image", description="Current live preview; a data: uri")
+ id_live_preview: int = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image")
+ textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.")
+
+
+def setup_progress_api(app):
+ return app.add_api_route("/internal/progress", progressapi, methods=["POST"], response_model=ProgressResponse)
+
+
+def progressapi(req: ProgressRequest):
+ active = req.id_task == current_task
+ queued = req.id_task in pending_tasks
+ completed = req.id_task in finished_tasks
+
+ if not active:
+ return ProgressResponse(active=active, queued=queued, completed=completed, id_live_preview=-1, textinfo="In queue..." if queued else "Waiting...")
+
+ progress = 0
+
+ if shared.state.job_count > 0:
+ progress += shared.state.job_no / shared.state.job_count
+ if shared.state.sampling_steps > 0:
+ progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps
+
+ progress = min(progress, 1)
+
+ elapsed_since_start = time.time() - shared.state.time_start
+ predicted_duration = elapsed_since_start / progress if progress > 0 else None
+ eta = predicted_duration - elapsed_since_start if predicted_duration is not None else None
+
+ id_live_preview = req.id_live_preview
+ shared.state.set_current_image()
+ if opts.live_previews_enable and shared.state.id_live_preview != req.id_live_preview:
+ image = shared.state.current_image
+ if image is not None:
+ buffered = io.BytesIO()
+ image.save(buffered, format="png")
+ live_preview = 'data:image/png;base64,' + base64.b64encode(buffered.getvalue()).decode("ascii")
+ id_live_preview = shared.state.id_live_preview
+ else:
+ live_preview = None
+ else:
+ live_preview = None
+
+ return ProgressResponse(active=active, queued=queued, completed=completed, progress=progress, eta=eta, live_preview=live_preview, id_live_preview=id_live_preview, textinfo=shared.state.textinfo)
+
diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py
index 7616fded..76e0e0d5 100644
--- a/modules/sd_samplers.py
+++ b/modules/sd_samplers.py
@@ -140,7 +140,7 @@ def store_latent(decoded):
if opts.live_previews_enable and opts.show_progress_every_n_steps > 0 and shared.state.sampling_step % opts.show_progress_every_n_steps == 0:
if not shared.parallel_processing_allowed:
- shared.state.current_image = sample_to_image(decoded)
+ shared.state.assign_current_image(sample_to_image(decoded))
class InterruptedException(BaseException):
diff --git a/modules/shared.py b/modules/shared.py
index 51df056c..de99aca9 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -152,6 +152,7 @@ def reload_hypernetworks():
hypernetwork.load_hypernetwork(opts.sd_hypernetwork)
+
class State:
skipped = False
interrupted = False
@@ -165,6 +166,7 @@ class State:
current_latent = None
current_image = None
current_image_sampling_step = 0
+ id_live_preview = 0
textinfo = None
time_start = None
need_restart = False
@@ -207,6 +209,7 @@ class State:
self.current_latent = None
self.current_image = None
self.current_image_sampling_step = 0
+ self.id_live_preview = 0
self.skipped = False
self.interrupted = False
self.textinfo = None
@@ -220,8 +223,8 @@ class State:
devices.torch_gc()
- """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this"""
def set_current_image(self):
+ """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this"""
if not parallel_processing_allowed:
return
@@ -234,12 +237,16 @@ class State:
import modules.sd_samplers
if opts.show_progress_grid:
- self.current_image = modules.sd_samplers.samples_to_image_grid(self.current_latent)
+ self.assign_current_image(modules.sd_samplers.samples_to_image_grid(self.current_latent))
else:
- self.current_image = modules.sd_samplers.sample_to_image(self.current_latent)
+ self.assign_current_image(modules.sd_samplers.sample_to_image(self.current_latent))
self.current_image_sampling_step = self.sampling_step
+ def assign_current_image(self, image):
+ self.current_image = image
+ self.id_live_preview += 1
+
state = State()
state.server_start = time.time()
@@ -424,8 +431,6 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"),
}))
options_templates.update(options_section(('ui', "User interface"), {
- "show_progressbar": OptionInfo(True, "Show progressbar"),
- "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
"return_grid": OptionInfo(True, "Show grid in results for web"),
"do_not_show_images": OptionInfo(False, "Do not show any images in results for web"),
"add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"),
@@ -446,6 +451,7 @@ options_templates.update(options_section(('ui', "User interface"), {
options_templates.update(options_section(('ui', "Live previews"), {
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
+ "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
"show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}),
"show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}),
"live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}),
diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py
index 3c1042ad..64abff4d 100644
--- a/modules/textual_inversion/preprocess.py
+++ b/modules/textual_inversion/preprocess.py
@@ -12,7 +12,7 @@ from modules.shared import opts, cmd_opts
from modules.textual_inversion import autocrop
-def preprocess(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False):
+def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False):
try:
if process_caption:
shared.interrogator.load()
diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py
index 63935878..7e4a6d24 100644
--- a/modules/textual_inversion/textual_inversion.py
+++ b/modules/textual_inversion/textual_inversion.py
@@ -345,7 +345,7 @@ def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, dat
assert log_directory, "Log directory is empty"
-def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_embedding_every, template_filename, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height):
+def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, create_image_every, save_embedding_every, template_filename, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height):
save_embedding_every = save_embedding_every or 0
create_image_every = create_image_every or 0
template_file = textual_inversion_templates.get(template_filename, None)
@@ -510,7 +510,6 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_
description = f"Training textual inversion [Epoch {epoch_num}: {epoch_step+1}/{steps_per_epoch}] loss: {loss_step:.7f}"
pbar.set_description(description)
- shared.state.textinfo = description
if embedding_dir is not None and steps_done % save_embedding_every == 0:
# Before saving, change name to match current checkpoint.
embedding_name_every = f'{embedding_name}-{steps_done}'
@@ -560,7 +559,8 @@ def train_embedding(embedding_name, learn_rate, batch_size, gradient_step, data_
shared.sd_model.first_stage_model.to(devices.cpu)
if image is not None:
- shared.state.current_image = image
+ shared.state.assign_current_image(image)
+
last_saved_image, last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False)
last_saved_image += f", prompt: {preview_text}"
diff --git a/modules/txt2img.py b/modules/txt2img.py
index 38b5f591..ca5d4550 100644
--- a/modules/txt2img.py
+++ b/modules/txt2img.py
@@ -8,7 +8,7 @@ import modules.processing as processing
from modules.ui import plaintext_to_html
-def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, *args):
+def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, *args):
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples,
diff --git a/modules/ui.py b/modules/ui.py
index 2425c66f..ff33236b 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -356,7 +356,7 @@ def create_toprow(is_img2img):
button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru")
with gr.Column(scale=1):
- with gr.Row():
+ with gr.Row(elem_id=f"{id_part}_generate_box"):
skip = gr.Button('Skip', elem_id=f"{id_part}_skip")
interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt")
submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
@@ -384,9 +384,7 @@ def create_toprow(is_img2img):
def setup_progressbar(*args, **kwargs):
- import modules.ui_progress
-
- modules.ui_progress.setup_progressbar(*args, **kwargs)
+ pass
def apply_setting(key, value):
@@ -479,8 +477,8 @@ Requested path was: {f}
else:
sp.Popen(["xdg-open", path])
- with gr.Column(variant='panel'):
- with gr.Group():
+ with gr.Column(variant='panel', elem_id=f"{tabname}_results"):
+ with gr.Group(elem_id=f"{tabname}_gallery_container"):
result_gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery").style(grid=4)
generation_info = None
@@ -595,15 +593,6 @@ def create_ui():
dummy_component = gr.Label(visible=False)
txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False)
- with gr.Row(elem_id='txt2img_progress_row'):
- with gr.Column(scale=1):
- pass
-
- with gr.Column(scale=1):
- progressbar = gr.HTML(elem_id="txt2img_progressbar")
- txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
- setup_progressbar(progressbar, txt2img_preview, 'txt2img')
-
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel', elem_id="txt2img_settings"):
for category in ordered_ui_categories():
@@ -682,6 +671,7 @@ def create_ui():
fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']),
_js="submit",
inputs=[
+ dummy_component,
txt2img_prompt,
txt2img_negative_prompt,
txt2img_prompt_style,
@@ -782,16 +772,7 @@ def create_ui():
with gr.Blocks(analytics_enabled=False) as img2img_interface:
img2img_prompt, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True)
- with gr.Row(elem_id='img2img_progress_row'):
- img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False)
-
- with gr.Column(scale=1):
- pass
-
- with gr.Column(scale=1):
- progressbar = gr.HTML(elem_id="img2img_progressbar")
- img2img_preview = gr.Image(elem_id='img2img_preview', visible=False)
- setup_progressbar(progressbar, img2img_preview, 'img2img')
+ img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False)
with FormRow().style(equal_height=False):
with gr.Column(variant='panel', elem_id="img2img_settings"):
@@ -958,6 +939,7 @@ def create_ui():
fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']),
_js="submit_img2img",
inputs=[
+ dummy_component,
dummy_component,
img2img_prompt,
img2img_negative_prompt,
@@ -1335,15 +1317,11 @@ def create_ui():
script_callbacks.ui_train_tabs_callback(params)
- with gr.Column():
- progressbar = gr.HTML(elem_id="ti_progressbar")
+ with gr.Column(elem_id='ti_gallery_container'):
ti_output = gr.Text(elem_id="ti_output", value="", show_label=False)
-
ti_gallery = gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(grid=4)
- ti_preview = gr.Image(elem_id='ti_preview', visible=False)
ti_progress = gr.HTML(elem_id="ti_progress", value="")
ti_outcome = gr.HTML(elem_id="ti_error", value="")
- setup_progressbar(progressbar, ti_preview, 'ti', textinfo=ti_progress)
create_embedding.click(
fn=modules.textual_inversion.ui.create_embedding,
@@ -1384,6 +1362,7 @@ def create_ui():
fn=wrap_gradio_gpu_call(modules.textual_inversion.ui.preprocess, extra_outputs=[gr.update()]),
_js="start_training_textual_inversion",
inputs=[
+ dummy_component,
process_src,
process_dst,
process_width,
@@ -1411,6 +1390,7 @@ def create_ui():
fn=wrap_gradio_gpu_call(modules.textual_inversion.ui.train_embedding, extra_outputs=[gr.update()]),
_js="start_training_textual_inversion",
inputs=[
+ dummy_component,
train_embedding_name,
embedding_learn_rate,
batch_size,
@@ -1443,6 +1423,7 @@ def create_ui():
fn=wrap_gradio_gpu_call(modules.hypernetworks.ui.train_hypernetwork, extra_outputs=[gr.update()]),
_js="start_training_textual_inversion",
inputs=[
+ dummy_component,
train_hypernetwork_name,
hypernetwork_learn_rate,
batch_size,
diff --git a/modules/ui_progress.py b/modules/ui_progress.py
deleted file mode 100644
index 7cd312e4..00000000
--- a/modules/ui_progress.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import time
-
-import gradio as gr
-
-from modules.shared import opts
-
-import modules.shared as shared
-
-
-def calc_time_left(progress, threshold, label, force_display, show_eta):
- if progress == 0:
- return ""
- else:
- time_since_start = time.time() - shared.state.time_start
- eta = (time_since_start/progress)
- eta_relative = eta-time_since_start
- if (eta_relative > threshold and show_eta) or force_display:
- if eta_relative > 3600:
- return label + time.strftime('%H:%M:%S', time.gmtime(eta_relative))
- elif eta_relative > 60:
- return label + time.strftime('%M:%S', time.gmtime(eta_relative))
- else:
- return label + time.strftime('%Ss', time.gmtime(eta_relative))
- else:
- return ""
-
-
-def check_progress_call(id_part):
- if shared.state.job_count == 0:
- return "", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
-
- progress = 0
-
- if shared.state.job_count > 0:
- progress += shared.state.job_no / shared.state.job_count
- if shared.state.sampling_steps > 0:
- progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps
-
- # Show progress percentage and time left at the same moment, and base it also on steps done
- show_eta = progress >= 0.01 or shared.state.sampling_step >= 10
-
- time_left = calc_time_left(progress, 1, " ETA: ", shared.state.time_left_force_display, show_eta)
- if time_left != "":
- shared.state.time_left_force_display = True
-
- progress = min(progress, 1)
-
- progressbar = ""
- if opts.show_progressbar:
- progressbar = f"""{" " * 2 + str(int(progress*100))+"%" + time_left if show_eta else ""}
"""
-
- image = gr.update(visible=False)
- preview_visibility = gr.update(visible=False)
-
- if opts.live_previews_enable:
- shared.state.set_current_image()
- image = shared.state.current_image
-
- if image is None:
- image = gr.update(value=None)
- else:
- preview_visibility = gr.update(visible=True)
-
- if shared.state.textinfo is not None:
- textinfo_result = gr.HTML.update(value=shared.state.textinfo, visible=True)
- else:
- textinfo_result = gr.update(visible=False)
-
- return f"{time.time()}{progressbar}
", preview_visibility, image, textinfo_result
-
-
-def check_progress_call_initial(id_part):
- shared.state.job_count = -1
- shared.state.current_latent = None
- shared.state.current_image = None
- shared.state.textinfo = None
- shared.state.time_start = time.time()
- shared.state.time_left_force_display = False
-
- return check_progress_call(id_part)
-
-
-def setup_progressbar(progressbar, preview, id_part, textinfo=None):
- if textinfo is None:
- textinfo = gr.HTML(visible=False)
-
- check_progress = gr.Button('Check progress', elem_id=f"{id_part}_check_progress", visible=False)
- check_progress.click(
- fn=lambda: check_progress_call(id_part),
- show_progress=False,
- inputs=[],
- outputs=[progressbar, preview, preview, textinfo],
- )
-
- check_progress_initial = gr.Button('Check progress (first)', elem_id=f"{id_part}_check_progress_initial", visible=False)
- check_progress_initial.click(
- fn=lambda: check_progress_call_initial(id_part),
- show_progress=False,
- inputs=[],
- outputs=[progressbar, preview, preview, textinfo],
- )
diff --git a/style.css b/style.css
index 2d484e06..786b71d1 100644
--- a/style.css
+++ b/style.css
@@ -305,26 +305,42 @@ input[type="range"]{
}
.progressDiv{
- width: 100%;
- height: 20px;
- background: #b4c0cc;
- border-radius: 8px;
+ position: absolute;
+ height: 20px;
+ top: -20px;
+ background: #b4c0cc;
+ border-radius: 8px !important;
}
.dark .progressDiv{
- background: #424c5b;
+ background: #424c5b;
}
.progressDiv .progress{
- width: 0%;
- height: 20px;
- background: #0060df;
- color: white;
- font-weight: bold;
- line-height: 20px;
- padding: 0 8px 0 0;
- text-align: right;
- border-radius: 8px;
+ width: 0%;
+ height: 20px;
+ background: #0060df;
+ color: white;
+ font-weight: bold;
+ line-height: 20px;
+ padding: 0 8px 0 0;
+ text-align: right;
+ border-radius: 8px;
+ overflow: visible;
+ white-space: nowrap;
+}
+
+.livePreview{
+ position: absolute;
+ z-index: 300;
+ background-color: white;
+ margin: -4px;
+}
+
+.livePreview img{
+ object-fit: contain;
+ width: 100%;
+ height: 100%;
}
#lightboxModal{
@@ -450,23 +466,25 @@ input[type="range"]{
display:none
}
-#txt2img_interrupt, #img2img_interrupt{
- position: absolute;
- width: 50%;
- height: 72px;
- background: #b4c0cc;
- border-radius: 0px;
- display: none;
+#txt2img_generate_box, #img2img_generate_box{
+ position: relative;
}
+#txt2img_interrupt, #img2img_interrupt, #txt2img_skip, #img2img_skip{
+ position: absolute;
+ width: 50%;
+ height: 100%;
+ background: #b4c0cc;
+ display: none;
+}
+
+#txt2img_interrupt, #img2img_interrupt{
+ right: 0;
+ border-radius: 0 0.5rem 0.5rem 0;
+}
#txt2img_skip, #img2img_skip{
- position: absolute;
- width: 50%;
- right: 0px;
- height: 72px;
- background: #b4c0cc;
- border-radius: 0px;
- display: none;
+ left: 0;
+ border-radius: 0.5rem 0 0 0.5rem;
}
.red {
diff --git a/webui.py b/webui.py
index 1fff80da..4624fe18 100644
--- a/webui.py
+++ b/webui.py
@@ -34,6 +34,7 @@ import modules.sd_vae
import modules.txt2img
import modules.script_callbacks
import modules.textual_inversion.textual_inversion
+import modules.progress
import modules.ui
from modules import modelloader
@@ -181,6 +182,8 @@ def webui():
app.add_middleware(GZipMiddleware, minimum_size=1000)
+ modules.progress.setup_progress_api(app)
+
if launch_api:
create_api(app)
From 388708f7b13dfbc890135cad678bfbcebd7baf37 Mon Sep 17 00:00:00 2001
From: pangbo13 <373108669@qq.com>
Date: Mon, 16 Jan 2023 00:56:24 +0800
Subject: [PATCH 022/127] fix when show_progress_every_n_steps == -1
---
modules/shared.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/shared.py b/modules/shared.py
index de99aca9..f857ccde 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -228,7 +228,7 @@ class State:
if not parallel_processing_allowed:
return
- if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable:
+ if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1:
self.do_set_current_image()
def do_set_current_image(self):
From 16f410893eb96c7810cbbd812541ba35e0e92524 Mon Sep 17 00:00:00 2001
From: AngelBottomless <35677394+aria1th@users.noreply.github.com>
Date: Mon, 16 Jan 2023 02:08:47 +0900
Subject: [PATCH 023/127] fix missing 'mean loss' for tensorboard integration
---
modules/hypernetworks/hypernetwork.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py
index ae6af516..bbd1f673 100644
--- a/modules/hypernetworks/hypernetwork.py
+++ b/modules/hypernetworks/hypernetwork.py
@@ -644,7 +644,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
if shared.opts.training_enable_tensorboard:
epoch_num = hypernetwork.step // len(ds)
epoch_step = hypernetwork.step - (epoch_num * len(ds)) + 1
-
+ mean_loss = sum(sum(x) for x in loss_dict.values()) / sum(len(x) for x in loss_dict.values())
textual_inversion.tensorboard_add(tensorboard_writer, loss=mean_loss, global_step=hypernetwork.step, step=epoch_step, learn_rate=scheduler.learn_rate, epoch_num=epoch_num)
textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, steps_per_epoch, {
From f6aac4c65a681383616f6e72e3865002600f476f Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 20:20:29 +0300
Subject: [PATCH 024/127] eliminate flicker for live previews
---
javascript/progressbar.js | 14 +++++++-------
style.css | 1 +
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index b7524ef7..8f22c018 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -184,15 +184,15 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
if(res.live_preview){
+
+ var rect = gallery.getBoundingClientRect()
+ if(rect.width){
+ livePreview.style.width = rect.width + "px"
+ livePreview.style.height = rect.height + "px"
+ }
+
var img = new Image();
img.onload = function() {
- var rect = gallery.getBoundingClientRect()
- if(rect.width){
- livePreview.style.width = rect.width + "px"
- livePreview.style.height = rect.height + "px"
- }
-
- livePreview.innerHTML = ''
livePreview.appendChild(img)
if(livePreview.childElementCount > 2){
livePreview.removeChild(livePreview.firstElementChild)
diff --git a/style.css b/style.css
index 786b71d1..5bf1c6f9 100644
--- a/style.css
+++ b/style.css
@@ -338,6 +338,7 @@ input[type="range"]{
}
.livePreview img{
+ position: absolute;
object-fit: contain;
width: 100%;
height: 100%;
From a534bdfc801e0c83e378dfaa2d04cf865d7109f9 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 20:27:39 +0300
Subject: [PATCH 025/127] add setting for progressbar update period
---
javascript/progressbar.js | 2 +-
modules/shared.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index 8f22c018..59173c83 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -208,7 +208,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
setTimeout(() => {
fun(id_task, res.id_live_preview);
- }, 500)
+ }, opts.live_preview_refresh_period || 500)
}, function(){
removeProgressBar()
})
diff --git a/modules/shared.py b/modules/shared.py
index de99aca9..3483db1c 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -455,6 +455,7 @@ options_templates.update(options_section(('ui', "Live previews"), {
"show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}),
"show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}),
"live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}),
+ "live_preview_refresh_period": OptionInfo(1000, "Progressbar/preview update period, in milliseconds")
}))
options_templates.update(options_section(('sampler-params', "Sampler parameters"), {
From b6ce041cdf722b400df9b5eac306d0cb049923d7 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 20:29:48 +0300
Subject: [PATCH 026/127] put interrupt and skip buttons back where they were
---
modules/ui.py | 2 +-
style.css | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/modules/ui.py b/modules/ui.py
index ff33236b..7a357f9a 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -357,8 +357,8 @@ def create_toprow(is_img2img):
with gr.Column(scale=1):
with gr.Row(elem_id=f"{id_part}_generate_box"):
- skip = gr.Button('Skip', elem_id=f"{id_part}_skip")
interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt")
+ skip = gr.Button('Skip', elem_id=f"{id_part}_skip")
submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
skip.click(
diff --git a/style.css b/style.css
index 5bf1c6f9..750fe315 100644
--- a/style.css
+++ b/style.css
@@ -480,13 +480,13 @@ input[type="range"]{
}
#txt2img_interrupt, #img2img_interrupt{
- right: 0;
- border-radius: 0 0.5rem 0.5rem 0;
-}
-#txt2img_skip, #img2img_skip{
left: 0;
border-radius: 0.5rem 0 0 0.5rem;
}
+#txt2img_skip, #img2img_skip{
+ right: 0;
+ border-radius: 0 0.5rem 0.5rem 0;
+}
.red {
color: red;
From 110d1a2d598bcfacffe3d524df1a3422b4cbd8ec Mon Sep 17 00:00:00 2001
From: Vladimir Mandic
Date: Sun, 15 Jan 2023 12:41:00 -0500
Subject: [PATCH 027/127] add fields to settings file
---
modules/textual_inversion/logging.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/logging.py
index 31e50b64..734a4b6f 100644
--- a/modules/textual_inversion/logging.py
+++ b/modules/textual_inversion/logging.py
@@ -2,7 +2,7 @@ import datetime
import json
import os
-saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "clip_grad_mode", "clip_grad_value", "gradient_step", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file"}
+saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "clip_grad_mode", "clip_grad_value", "gradient_step", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file", "gradient_step", "latent_sampling_method"}
saved_params_ti = {"embedding_name", "num_vectors_per_token", "save_embedding_every", "save_image_with_stored_embedding"}
saved_params_hypernet = {"hypernetwork_name", "layer_structure", "activation_func", "weight_init", "add_layer_norm", "use_dropout", "save_hypernetwork_every"}
saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet
From 598f7fcd84f655dd204ad5e258dc1c41cc806cde Mon Sep 17 00:00:00 2001
From: aria1th <35677394+aria1th@users.noreply.github.com>
Date: Mon, 16 Jan 2023 02:46:21 +0900
Subject: [PATCH 028/127] Fix loss_dict problem
---
modules/hypernetworks/hypernetwork.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py
index bbd1f673..438e3e9f 100644
--- a/modules/hypernetworks/hypernetwork.py
+++ b/modules/hypernetworks/hypernetwork.py
@@ -561,6 +561,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
_loss_step = 0 #internal
# size = len(ds.indexes)
# loss_dict = defaultdict(lambda : deque(maxlen = 1024))
+ loss_logging = []
# losses = torch.zeros((size,))
# previous_mean_losses = [0]
# previous_mean_loss = 0
@@ -601,6 +602,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
else:
c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory)
loss = shared.sd_model(x, c)[0] / gradient_step
+ loss_logging.append(loss.item())
del x
del c
@@ -644,7 +646,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
if shared.opts.training_enable_tensorboard:
epoch_num = hypernetwork.step // len(ds)
epoch_step = hypernetwork.step - (epoch_num * len(ds)) + 1
- mean_loss = sum(sum(x) for x in loss_dict.values()) / sum(len(x) for x in loss_dict.values())
+ mean_loss = sum(loss_logging) / len(loss_logging)
textual_inversion.tensorboard_add(tensorboard_writer, loss=mean_loss, global_step=hypernetwork.step, step=epoch_step, learn_rate=scheduler.learn_rate, epoch_num=epoch_num)
textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, steps_per_epoch, {
From 13445738d974edcca5ff2f4f8f3833c1f3433e5e Mon Sep 17 00:00:00 2001
From: aria1th <35677394+aria1th@users.noreply.github.com>
Date: Mon, 16 Jan 2023 03:02:54 +0900
Subject: [PATCH 029/127] Fix tensorboard related functions
---
modules/hypernetworks/hypernetwork.py | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py
index 438e3e9f..c963fc40 100644
--- a/modules/hypernetworks/hypernetwork.py
+++ b/modules/hypernetworks/hypernetwork.py
@@ -561,7 +561,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
_loss_step = 0 #internal
# size = len(ds.indexes)
# loss_dict = defaultdict(lambda : deque(maxlen = 1024))
- loss_logging = []
+ loss_logging = deque(maxlen=len(ds) * 3) # this should be configurable parameter, this is 3 * epoch(dataset size)
# losses = torch.zeros((size,))
# previous_mean_losses = [0]
# previous_mean_loss = 0
@@ -602,7 +602,6 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
else:
c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory)
loss = shared.sd_model(x, c)[0] / gradient_step
- loss_logging.append(loss.item())
del x
del c
@@ -612,7 +611,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
# go back until we reach gradient accumulation steps
if (j + 1) % gradient_step != 0:
continue
-
+ loss_logging.append(_loss_step)
if clip_grad:
clip_grad(weights, clip_grad_sched.learn_rate)
@@ -690,9 +689,6 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
processed = processing.process_images(p)
image = processed.images[0] if len(processed.images) > 0 else None
-
- if shared.opts.training_enable_tensorboard and shared.opts.training_tensorboard_save_images:
- textual_inversion.tensorboard_add_image(tensorboard_writer, f"Validation at epoch {epoch_num}", image, hypernetwork.step)
if unload:
shared.sd_model.cond_stage_model.to(devices.cpu)
@@ -703,7 +699,10 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
hypernetwork.train()
if image is not None:
shared.state.assign_current_image(image)
-
+ if shared.opts.training_enable_tensorboard and shared.opts.training_tensorboard_save_images:
+ textual_inversion.tensorboard_add_image(tensorboard_writer,
+ f"Validation at epoch {epoch_num}", image,
+ hypernetwork.step)
last_saved_image, last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False)
last_saved_image += f", prompt: {preview_text}"
From 8e2aeee4a127b295bfc880800e4a312e0f049b85 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 22:29:53 +0300
Subject: [PATCH 030/127] add BREAK keyword to end current text chunk and start
the next
---
modules/prompt_parser.py | 7 ++++++-
modules/sd_hijack_clip.py | 17 +++++++++++++----
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py
index 870218db..69665372 100644
--- a/modules/prompt_parser.py
+++ b/modules/prompt_parser.py
@@ -274,6 +274,7 @@ re_attention = re.compile(r"""
:
""", re.X)
+re_break = re.compile(r"\s*\bBREAK\b\s*", re.S)
def parse_prompt_attention(text):
"""
@@ -339,7 +340,11 @@ def parse_prompt_attention(text):
elif text == ']' and len(square_brackets) > 0:
multiply_range(square_brackets.pop(), square_bracket_multiplier)
else:
- res.append([text, 1.0])
+ parts = re.split(re_break, text)
+ for i, part in enumerate(parts):
+ if i > 0:
+ res.append(["BREAK", -1])
+ res.append([part, 1.0])
for pos in round_brackets:
multiply_range(pos, round_bracket_multiplier)
diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py
index 852afc66..9fa5c5c5 100644
--- a/modules/sd_hijack_clip.py
+++ b/modules/sd_hijack_clip.py
@@ -96,13 +96,18 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
token_count = 0
last_comma = -1
- def next_chunk():
- """puts current chunk into the list of results and produces the next one - empty"""
+ def next_chunk(is_last=False):
+ """puts current chunk into the list of results and produces the next one - empty;
+ if is_last is true, tokens tokens at the end won't add to token_count"""
nonlocal token_count
nonlocal last_comma
nonlocal chunk
- token_count += len(chunk.tokens)
+ if is_last:
+ token_count += len(chunk.tokens)
+ else:
+ token_count += self.chunk_length
+
to_add = self.chunk_length - len(chunk.tokens)
if to_add > 0:
chunk.tokens += [self.id_end] * to_add
@@ -116,6 +121,10 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
chunk = PromptChunk()
for tokens, (text, weight) in zip(tokenized, parsed):
+ if text == 'BREAK' and weight == -1:
+ next_chunk()
+ continue
+
position = 0
while position < len(tokens):
token = tokens[position]
@@ -159,7 +168,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
position += embedding_length_in_tokens
if len(chunk.tokens) > 0 or len(chunks) == 0:
- next_chunk()
+ next_chunk(is_last=True)
return chunks, token_count
From db9b11617997ad02e5eb68be306078b3b8d3e2cf Mon Sep 17 00:00:00 2001
From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com>
Date: Sun, 15 Jan 2023 23:13:58 +0300
Subject: [PATCH 031/127] fix paths with parentheses
---
webui.bat | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.bat b/webui.bat
index e6a7a429..3165b94d 100644
--- a/webui.bat
+++ b/webui.bat
@@ -1,7 +1,7 @@
@echo off
if not defined PYTHON (set PYTHON=python)
-if not defined VENV_DIR (set VENV_DIR=%~dp0%venv)
+if not defined VENV_DIR (set "VENV_DIR=%~dp0%venv")
set ERROR_REPORTING=FALSE
From fc25af3939f0366f147892a8ae5b9da56495352b Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 23:22:51 +0300
Subject: [PATCH 032/127] remove unneeded log from progressbar
---
javascript/progressbar.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index 59173c83..5072c13f 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -139,8 +139,6 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
var fun = function(id_task, id_live_preview){
request("/internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){
- console.log(res)
-
if(res.completed){
removeProgressBar()
return
From 89314e79da21ac71ad3133ccf5ac3e85d4c24052 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 23:23:16 +0300
Subject: [PATCH 033/127] fix an error that happens when you send an empty
image from txt2img to img2img
---
modules/generation_parameters_copypaste.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py
index 593d99ef..a381ff59 100644
--- a/modules/generation_parameters_copypaste.py
+++ b/modules/generation_parameters_copypaste.py
@@ -37,6 +37,9 @@ def quote(text):
def image_from_url_text(filedata):
+ if filedata is None:
+ return None
+
if type(filedata) == list and len(filedata) > 0 and type(filedata[0]) == dict and filedata[0].get("is_file", False):
filedata = filedata[0]
From 3db22e6ee45193559a2c3ba44ab672b067245f99 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 23:32:38 +0300
Subject: [PATCH 034/127] rename masking to inpaint in UI make inpaint go to
the right place for users who don't have it in config string
---
modules/shared.py | 2 +-
modules/ui.py | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/modules/shared.py b/modules/shared.py
index 3bdc375b..f06ae610 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -116,7 +116,7 @@ restricted_opts = {
}
ui_reorder_categories = [
- "masking",
+ "inpaint",
"sampler",
"dimensions",
"cfg",
diff --git a/modules/ui.py b/modules/ui.py
index b3d4af3e..20b66165 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -570,9 +570,9 @@ def create_sampler_and_steps_selection(choices, tabname):
def ordered_ui_categories():
- user_order = {x.strip(): i for i, x in enumerate(shared.opts.ui_reorder.split(","))}
+ user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder.split(","))}
- for i, category in sorted(enumerate(shared.ui_reorder_categories), key=lambda x: user_order.get(x[1], x[0] + 1000)):
+ for i, category in sorted(enumerate(shared.ui_reorder_categories), key=lambda x: user_order.get(x[1], x[0] * 2 + 0)):
yield category
@@ -889,7 +889,7 @@ def create_ui():
with FormGroup(elem_id="img2img_script_container"):
custom_inputs = modules.scripts.scripts_img2img.setup_ui()
- elif category == "masking":
+ elif category == "inpaint":
with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls:
with FormRow():
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur")
From 9a43acf94ead6bc15da2782c39ab5a3107c3f06c Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sun, 15 Jan 2023 23:37:34 +0300
Subject: [PATCH 035/127] add background color for live previews in dark mode
---
style.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/style.css b/style.css
index 750fe315..78fa9838 100644
--- a/style.css
+++ b/style.css
@@ -337,6 +337,10 @@ input[type="range"]{
margin: -4px;
}
+.dark .livePreview{
+ background-color: rgb(17 24 39 / var(--tw-bg-opacity));
+}
+
.livePreview img{
position: absolute;
object-fit: contain;
From 3f887f7f61d69fa699a272166b79fdb787e9ce1d Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 00:44:46 +0300
Subject: [PATCH 036/127] support old configs that say "auto" for ssd_vae
change sd_vae_as_default to True by default as it's a more sensible setting
---
modules/sd_vae.py | 6 ++++--
modules/shared.py | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/modules/sd_vae.py b/modules/sd_vae.py
index add5cecf..e9c6bb40 100644
--- a/modules/sd_vae.py
+++ b/modules/sd_vae.py
@@ -94,8 +94,10 @@ def resolve_vae(checkpoint_file):
if shared.cmd_opts.vae_path is not None:
return shared.cmd_opts.vae_path, 'from commandline argument'
+ is_automatic = shared.opts.sd_vae in {"Automatic", "auto"} # "auto" for people with old config
+
vae_near_checkpoint = find_vae_near_checkpoint(checkpoint_file)
- if vae_near_checkpoint is not None and (shared.opts.sd_vae_as_default or shared.opts.sd_vae == "Automatic"):
+ if vae_near_checkpoint is not None and (shared.opts.sd_vae_as_default or is_automatic):
return vae_near_checkpoint, 'found near the checkpoint'
if shared.opts.sd_vae == "None":
@@ -105,7 +107,7 @@ def resolve_vae(checkpoint_file):
if vae_from_options is not None:
return vae_from_options, 'specified in settings'
- if shared.opts.sd_vae != "Automatic":
+ if is_automatic:
print(f"Couldn't find VAE named {shared.opts.sd_vae}; using None instead")
return None, None
diff --git a/modules/shared.py b/modules/shared.py
index f06ae610..c5fc250e 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -394,7 +394,7 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), {
"sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
"sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
"sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": ["Automatic", "None"] + list(sd_vae.vae_dict)}, refresh=sd_vae.refresh_vae_list),
- "sd_vae_as_default": OptionInfo(False, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them"),
+ "sd_vae_as_default": OptionInfo(True, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them"),
"sd_hypernetwork": OptionInfo("None", "Hypernetwork", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks),
"sd_hypernetwork_strength": OptionInfo(1.0, "Hypernetwork strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.001}),
"inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
From ff6a5bcec1ce25aa8f08b157ea957d764be23d8d Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 01:28:20 +0300
Subject: [PATCH 037/127] bugfix for previous commit
---
modules/sd_vae.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/sd_vae.py b/modules/sd_vae.py
index e9c6bb40..b2af2ce7 100644
--- a/modules/sd_vae.py
+++ b/modules/sd_vae.py
@@ -107,7 +107,7 @@ def resolve_vae(checkpoint_file):
if vae_from_options is not None:
return vae_from_options, 'specified in settings'
- if is_automatic:
+ if not is_automatic:
print(f"Couldn't find VAE named {shared.opts.sd_vae}; using None instead")
return None, None
From f202ff1901c27d1f82d5e2684dba9e1ed24ffdf2 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sun, 15 Jan 2023 19:43:34 -0800
Subject: [PATCH 038/127] Make XY grid cancellation much faster
---
scripts/xy_grid.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index bd3087d4..13a3a046 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -406,6 +406,9 @@ class Script(scripts.Script):
grid_infotext = [None]
def cell(x, y):
+ if shared.state.interrupted:
+ return Processed(p, [], p.seed, "")
+
pc = copy(p)
x_opt.apply(pc, x, xs)
y_opt.apply(pc, y, ys)
From 029260b4ca7267d7a75319dbc11bca2a8c52774e Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:40:57 -0800
Subject: [PATCH 039/127] Optimize XY grid to run slower axes fewer times
---
scripts/xy_grid.py | 123 ++++++++++++++++++++++++++-------------------
1 file changed, 70 insertions(+), 53 deletions(-)
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index 13a3a046..074ee919 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -175,76 +175,87 @@ def str_permutations(x):
"""dummy function for specifying it in AxisOption's type when you want to get a list of permutations"""
return x
-AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value", "confirm"])
-AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value", "confirm"])
+AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value", "confirm", "cost"])
+AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value", "confirm", "cost"])
axis_options = [
- AxisOption("Nothing", str, do_nothing, format_nothing, None),
- AxisOption("Seed", int, apply_field("seed"), format_value_add_label, None),
- AxisOption("Var. seed", int, apply_field("subseed"), format_value_add_label, None),
- AxisOption("Var. strength", float, apply_field("subseed_strength"), format_value_add_label, None),
- AxisOption("Steps", int, apply_field("steps"), format_value_add_label, None),
- AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label, None),
- AxisOption("Prompt S/R", str, apply_prompt, format_value, None),
- AxisOption("Prompt order", str_permutations, apply_order, format_value_join_list, None),
- AxisOption("Sampler", str, apply_sampler, format_value, confirm_samplers),
- AxisOption("Checkpoint name", str, apply_checkpoint, format_value, confirm_checkpoints),
- AxisOption("Hypernetwork", str, apply_hypernetwork, format_value, confirm_hypernetworks),
- AxisOption("Hypernet str.", float, apply_hypernetwork_strength, format_value_add_label, None),
- AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label, None),
- AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label, None),
- AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label, None),
- AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label, None),
- AxisOption("Eta", float, apply_field("eta"), format_value_add_label, None),
- AxisOption("Clip skip", int, apply_clip_skip, format_value_add_label, None),
- AxisOption("Denoising", float, apply_field("denoising_strength"), format_value_add_label, None),
- AxisOption("Hires upscaler", str, apply_field("hr_upscaler"), format_value_add_label, None),
- AxisOption("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight"), format_value_add_label, None),
- AxisOption("VAE", str, apply_vae, format_value_add_label, None),
- AxisOption("Styles", str, apply_styles, format_value_add_label, None),
+ AxisOption("Nothing", str, do_nothing, format_nothing, None, 0),
+ AxisOption("Seed", int, apply_field("seed"), format_value_add_label, None, 0),
+ AxisOption("Var. seed", int, apply_field("subseed"), format_value_add_label, None, 0),
+ AxisOption("Var. strength", float, apply_field("subseed_strength"), format_value_add_label, None, 0),
+ AxisOption("Steps", int, apply_field("steps"), format_value_add_label, None, 0),
+ AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label, None, 0),
+ AxisOption("Prompt S/R", str, apply_prompt, format_value, None, 0),
+ AxisOption("Prompt order", str_permutations, apply_order, format_value_join_list, None, 0),
+ AxisOption("Sampler", str, apply_sampler, format_value, confirm_samplers, 0),
+ AxisOption("Checkpoint name", str, apply_checkpoint, format_value, confirm_checkpoints, 1.0),
+ AxisOption("Hypernetwork", str, apply_hypernetwork, format_value, confirm_hypernetworks, 0.2),
+ AxisOption("Hypernet str.", float, apply_hypernetwork_strength, format_value_add_label, None, 0),
+ AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label, None, 0),
+ AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label, None, 0),
+ AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label, None, 0),
+ AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label, None, 0),
+ AxisOption("Eta", float, apply_field("eta"), format_value_add_label, None, 0),
+ AxisOption("Clip skip", int, apply_clip_skip, format_value_add_label, None, 0),
+ AxisOption("Denoising", float, apply_field("denoising_strength"), format_value_add_label, None, 0),
+ AxisOption("Hires upscaler", str, apply_field("hr_upscaler"), format_value_add_label, None, 0),
+ AxisOption("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight"), format_value_add_label, None, 0),
+ AxisOption("VAE", str, apply_vae, format_value_add_label, None, 0.7),
+ AxisOption("Styles", str, apply_styles, format_value_add_label, None, 0),
]
-def draw_xy_grid(p, xs, ys, x_labels, y_labels, cell, draw_legend, include_lone_images):
+def draw_xy_grid(p, xs, ys, x_labels, y_labels, cell, draw_legend, include_lone_images, swap_axes_processing_order):
ver_texts = [[images.GridAnnotation(y)] for y in y_labels]
hor_texts = [[images.GridAnnotation(x)] for x in x_labels]
# Temporary list of all the images that are generated to be populated into the grid.
# Will be filled with empty images for any individual step that fails to process properly
- image_cache = []
+ image_cache = [None] * (len(xs) * len(ys))
processed_result = None
cell_mode = "P"
- cell_size = (1,1)
+ cell_size = (1, 1)
state.job_count = len(xs) * len(ys) * p.n_iter
- for iy, y in enumerate(ys):
+ def process_cell(x, y, ix, iy):
+ nonlocal image_cache, processed_result, cell_mode, cell_size
+
+ state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}"
+
+ processed: Processed = cell(x, y)
+
+ try:
+ # this dereference will throw an exception if the image was not processed
+ # (this happens in cases such as if the user stops the process from the UI)
+ processed_image = processed.images[0]
+
+ if processed_result is None:
+ # Use our first valid processed result as a template container to hold our full results
+ processed_result = copy(processed)
+ cell_mode = processed_image.mode
+ cell_size = processed_image.size
+ processed_result.images = [Image.new(cell_mode, cell_size)]
+
+ image_cache[ix + iy * len(xs)] = processed_image
+ if include_lone_images:
+ processed_result.images.append(processed_image)
+ processed_result.all_prompts.append(processed.prompt)
+ processed_result.all_seeds.append(processed.seed)
+ processed_result.infotexts.append(processed.infotexts[0])
+ except:
+ image_cache[ix + iy * len(xs)] = Image.new(cell_mode, cell_size)
+
+ if swap_axes_processing_order:
for ix, x in enumerate(xs):
- state.job = f"{ix + iy * len(xs) + 1} out of {len(xs) * len(ys)}"
-
- processed:Processed = cell(x, y)
- try:
- # this dereference will throw an exception if the image was not processed
- # (this happens in cases such as if the user stops the process from the UI)
- processed_image = processed.images[0]
-
- if processed_result is None:
- # Use our first valid processed result as a template container to hold our full results
- processed_result = copy(processed)
- cell_mode = processed_image.mode
- cell_size = processed_image.size
- processed_result.images = [Image.new(cell_mode, cell_size)]
-
- image_cache.append(processed_image)
- if include_lone_images:
- processed_result.images.append(processed_image)
- processed_result.all_prompts.append(processed.prompt)
- processed_result.all_seeds.append(processed.seed)
- processed_result.infotexts.append(processed.infotexts[0])
- except:
- image_cache.append(Image.new(cell_mode, cell_size))
+ for iy, y in enumerate(ys):
+ process_cell(x, y, ix, iy)
+ else:
+ for iy, y in enumerate(ys):
+ for ix, x in enumerate(xs):
+ process_cell(x, y, ix, iy)
if not processed_result:
print("Unexpected error: draw_xy_grid failed to return even a single processed image")
@@ -405,6 +416,11 @@ class Script(scripts.Script):
grid_infotext = [None]
+ # If one of the axes is very slow to change between (like SD model
+ # checkpoint), then make sure it is in the outer iteration of the nested
+ # `for` loop.
+ swap_axes_processing_order = x_opt.cost > y_opt.cost
+
def cell(x, y):
if shared.state.interrupted:
return Processed(p, [], p.seed, "")
@@ -443,7 +459,8 @@ class Script(scripts.Script):
y_labels=[y_opt.format_value(p, y_opt, y) for y in ys],
cell=cell,
draw_legend=draw_legend,
- include_lone_images=include_lone_images
+ include_lone_images=include_lone_images,
+ swap_axes_processing_order=swap_axes_processing_order
)
if opts.grid_save:
From 2144c2eb7f5842caed1227d4ec7e659c79a84ce9 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:41:58 -0800
Subject: [PATCH 040/127] Add swap axes button for XY Grid
---
scripts/xy_grid.py | 24 +++++++++++++++++++-----
style.css | 10 ++++++++++
2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index 13a3a046..99a660c1 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -23,6 +23,9 @@ import os
import re
+up_down_arrow_symbol = "\u2195\ufe0f"
+
+
def apply_field(field):
def fun(p, x, xs):
setattr(p, field, x)
@@ -293,17 +296,28 @@ class Script(scripts.Script):
current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img]
with gr.Row():
- x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, type="index", elem_id=self.elem_id("x_type"))
- x_values = gr.Textbox(label="X values", lines=1, elem_id=self.elem_id("x_values"))
+ with gr.Column(scale=1, elem_id="xy_grid_button_column"):
+ swap_axes_button = gr.Button(value=up_down_arrow_symbol, elem_id="xy_grid_swap_axes")
+ with gr.Column(scale=19):
+ with gr.Row():
+ x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, type="index", elem_id=self.elem_id("x_type"))
+ x_values = gr.Textbox(label="X values", lines=1, elem_id=self.elem_id("x_values"))
- with gr.Row():
- y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[0].label, type="index", elem_id=self.elem_id("y_type"))
- y_values = gr.Textbox(label="Y values", lines=1, elem_id=self.elem_id("y_values"))
+ with gr.Row():
+ y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[0].label, type="index", elem_id=self.elem_id("y_type"))
+ y_values = gr.Textbox(label="Y values", lines=1, elem_id=self.elem_id("y_values"))
draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
include_lone_images = gr.Checkbox(label='Include Separate Images', value=False, elem_id=self.elem_id("include_lone_images"))
no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
+ def swap_axes(x_type, x_values, y_type, y_values):
+ nonlocal current_axis_options
+ return current_axis_options[y_type].label, y_values, current_axis_options[x_type].label, x_values
+
+ swap_args = [x_type, x_values, y_type, y_values]
+ swap_axes_button.click(swap_axes, inputs=swap_args, outputs=swap_args)
+
return [x_type, x_values, y_type, y_values, draw_legend, include_lone_images, no_fixed_seeds]
def run(self, p, x_type, x_values, y_type, y_values, draw_legend, include_lone_images, no_fixed_seeds):
diff --git a/style.css b/style.css
index 78fa9838..1fddfcc2 100644
--- a/style.css
+++ b/style.css
@@ -717,6 +717,16 @@ footer {
line-height: 2.4em;
}
+#xy_grid_button_column {
+ min-width: 38px !important;
+}
+
+#xy_grid_button_column button {
+ height: 100%;
+ margin-bottom: 0.7em;
+ margin-left: 1em;
+}
+
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
From 972f5785073b8ba5957add72debd74fc56ee9329 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 09:27:52 +0300
Subject: [PATCH 041/127] fix problems related to checkpoint/VAE switching in
XY plot
---
scripts/xy_grid.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index 13a3a046..0cdfa952 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -263,14 +263,12 @@ class SharedSettingsStackHelper(object):
def __enter__(self):
self.CLIP_stop_at_last_layers = opts.CLIP_stop_at_last_layers
self.hypernetwork = opts.sd_hypernetwork
- self.model = shared.sd_model
self.vae = opts.sd_vae
def __exit__(self, exc_type, exc_value, tb):
- modules.sd_models.reload_model_weights(self.model)
-
opts.data["sd_vae"] = self.vae
- modules.sd_vae.reload_vae_weights(self.model)
+ modules.sd_models.reload_model_weights()
+ modules.sd_vae.reload_vae_weights()
hypernetwork.load_hypernetwork(self.hypernetwork)
hypernetwork.apply_strength()
From 064983c0adb00cd9e88d2f06f66c9a1d5bc116c3 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 12:56:30 +0300
Subject: [PATCH 042/127] return an option to hide progressbar
---
javascript/progressbar.js | 1 +
modules/shared.py | 1 +
2 files changed, 2 insertions(+)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index 5072c13f..da6709bc 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -121,6 +121,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
var divProgress = document.createElement('div')
divProgress.className='progressDiv'
+ divProgress.style.display = opts.show_progressbar ? "" : "none"
var divInner = document.createElement('div')
divInner.className='progress'
diff --git a/modules/shared.py b/modules/shared.py
index c5fc250e..483c4c62 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -451,6 +451,7 @@ options_templates.update(options_section(('ui', "User interface"), {
}))
options_templates.update(options_section(('ui', "Live previews"), {
+ "show_progressbar": OptionInfo(True, "Show progressbar"),
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
"show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
"show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}),
From 55947857f035040d00249f02b17e39370033a99b Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 17:36:56 +0300
Subject: [PATCH 043/127] add a button for XY Plot to fill in available values
for axes that support this
---
javascript/hints.js | 1 +
scripts/xy_grid.py | 101 +++++++++++++++++++++++++++++---------------
style.css | 12 +-----
3 files changed, 68 insertions(+), 46 deletions(-)
diff --git a/javascript/hints.js b/javascript/hints.js
index 244bfde2..fa5e5ae8 100644
--- a/javascript/hints.js
+++ b/javascript/hints.js
@@ -20,6 +20,7 @@ titles = {
"\u{1f4be}": "Save style",
"\U0001F5D1": "Clear prompt",
"\u{1f4cb}": "Apply selected styles to current prompt",
+ "\u{1f4d2}": "Paste available values into the field",
"Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt",
"SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back",
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index e06c11cb..bf4ba92f 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -10,7 +10,7 @@ import numpy as np
import modules.scripts as scripts
import gradio as gr
-from modules import images, paths, sd_samplers, processing
+from modules import images, paths, sd_samplers, processing, sd_models, sd_vae
from modules.hypernetworks import hypernetwork
from modules.processing import process_images, Processed, StableDiffusionProcessingTxt2Img
from modules.shared import opts, cmd_opts, state
@@ -22,8 +22,9 @@ import glob
import os
import re
+from modules.ui_components import ToolButton
-up_down_arrow_symbol = "\u2195\ufe0f"
+fill_values_symbol = "\U0001f4d2" # 📒
def apply_field(field):
@@ -178,34 +179,49 @@ def str_permutations(x):
"""dummy function for specifying it in AxisOption's type when you want to get a list of permutations"""
return x
-AxisOption = namedtuple("AxisOption", ["label", "type", "apply", "format_value", "confirm", "cost"])
-AxisOptionImg2Img = namedtuple("AxisOptionImg2Img", ["label", "type", "apply", "format_value", "confirm", "cost"])
+
+class AxisOption:
+ def __init__(self, label, type, apply, format_value=format_value_add_label, confirm=None, cost=0.0, choices=None):
+ self.label = label
+ self.type = type
+ self.apply = apply
+ self.format_value = format_value
+ self.confirm = confirm
+ self.cost = cost
+ self.choices = choices
+ self.is_img2img = False
+
+
+class AxisOptionImg2Img(AxisOption):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.is_img2img = False
axis_options = [
- AxisOption("Nothing", str, do_nothing, format_nothing, None, 0),
- AxisOption("Seed", int, apply_field("seed"), format_value_add_label, None, 0),
- AxisOption("Var. seed", int, apply_field("subseed"), format_value_add_label, None, 0),
- AxisOption("Var. strength", float, apply_field("subseed_strength"), format_value_add_label, None, 0),
- AxisOption("Steps", int, apply_field("steps"), format_value_add_label, None, 0),
- AxisOption("CFG Scale", float, apply_field("cfg_scale"), format_value_add_label, None, 0),
- AxisOption("Prompt S/R", str, apply_prompt, format_value, None, 0),
- AxisOption("Prompt order", str_permutations, apply_order, format_value_join_list, None, 0),
- AxisOption("Sampler", str, apply_sampler, format_value, confirm_samplers, 0),
- AxisOption("Checkpoint name", str, apply_checkpoint, format_value, confirm_checkpoints, 1.0),
- AxisOption("Hypernetwork", str, apply_hypernetwork, format_value, confirm_hypernetworks, 0.2),
- AxisOption("Hypernet str.", float, apply_hypernetwork_strength, format_value_add_label, None, 0),
- AxisOption("Sigma Churn", float, apply_field("s_churn"), format_value_add_label, None, 0),
- AxisOption("Sigma min", float, apply_field("s_tmin"), format_value_add_label, None, 0),
- AxisOption("Sigma max", float, apply_field("s_tmax"), format_value_add_label, None, 0),
- AxisOption("Sigma noise", float, apply_field("s_noise"), format_value_add_label, None, 0),
- AxisOption("Eta", float, apply_field("eta"), format_value_add_label, None, 0),
- AxisOption("Clip skip", int, apply_clip_skip, format_value_add_label, None, 0),
- AxisOption("Denoising", float, apply_field("denoising_strength"), format_value_add_label, None, 0),
- AxisOption("Hires upscaler", str, apply_field("hr_upscaler"), format_value_add_label, None, 0),
- AxisOption("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight"), format_value_add_label, None, 0),
- AxisOption("VAE", str, apply_vae, format_value_add_label, None, 0.7),
- AxisOption("Styles", str, apply_styles, format_value_add_label, None, 0),
+ AxisOption("Nothing", str, do_nothing, format_value=format_nothing),
+ AxisOption("Seed", int, apply_field("seed")),
+ AxisOption("Var. seed", int, apply_field("subseed")),
+ AxisOption("Var. strength", float, apply_field("subseed_strength")),
+ AxisOption("Steps", int, apply_field("steps")),
+ AxisOption("CFG Scale", float, apply_field("cfg_scale")),
+ AxisOption("Prompt S/R", str, apply_prompt, format_value=format_value),
+ AxisOption("Prompt order", str_permutations, apply_order, format_value=format_value_join_list),
+ AxisOption("Sampler", str, apply_sampler, format_value=format_value, confirm=confirm_samplers, choices=lambda: [x.name for x in sd_samplers.samplers]),
+ AxisOption("Checkpoint name", str, apply_checkpoint, format_value=format_value, confirm=confirm_checkpoints, cost=1.0, choices=lambda: list(sd_models.checkpoints_list)),
+ AxisOption("Hypernetwork", str, apply_hypernetwork, format_value=format_value, confirm=confirm_hypernetworks, cost=0.2, choices=lambda: list(shared.hypernetworks)),
+ AxisOption("Hypernet str.", float, apply_hypernetwork_strength),
+ AxisOption("Sigma Churn", float, apply_field("s_churn")),
+ AxisOption("Sigma min", float, apply_field("s_tmin")),
+ AxisOption("Sigma max", float, apply_field("s_tmax")),
+ AxisOption("Sigma noise", float, apply_field("s_noise")),
+ AxisOption("Eta", float, apply_field("eta")),
+ AxisOption("Clip skip", int, apply_clip_skip),
+ AxisOption("Denoising", float, apply_field("denoising_strength")),
+ AxisOption("Hires upscaler", str, apply_field("hr_upscaler"), choices=lambda: [x.name for x in shared.sd_upscalers]),
+ AxisOption("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")),
+ AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: list(sd_vae.vae_dict)),
+ AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)),
]
@@ -262,7 +278,7 @@ def draw_xy_grid(p, xs, ys, x_labels, y_labels, cell, draw_legend, include_lone_
if not processed_result:
print("Unexpected error: draw_xy_grid failed to return even a single processed image")
- return Processed()
+ return Processed(p, [])
grid = images.image_grid(image_cache, rows=len(ys))
if draw_legend:
@@ -302,23 +318,25 @@ class Script(scripts.Script):
return "X/Y plot"
def ui(self, is_img2img):
- current_axis_options = [x for x in axis_options if type(x) == AxisOption or type(x) == AxisOptionImg2Img and is_img2img]
+ current_axis_options = [x for x in axis_options if type(x) == AxisOption or x.is_img2img and is_img2img]
with gr.Row():
- with gr.Column(scale=1, elem_id="xy_grid_button_column"):
- swap_axes_button = gr.Button(value=up_down_arrow_symbol, elem_id="xy_grid_swap_axes")
with gr.Column(scale=19):
with gr.Row():
x_type = gr.Dropdown(label="X type", choices=[x.label for x in current_axis_options], value=current_axis_options[1].label, type="index", elem_id=self.elem_id("x_type"))
x_values = gr.Textbox(label="X values", lines=1, elem_id=self.elem_id("x_values"))
+ fill_x_button = ToolButton(value=fill_values_symbol, elem_id="xy_grid_fill_x_tool_button", visible=False)
with gr.Row():
y_type = gr.Dropdown(label="Y type", choices=[x.label for x in current_axis_options], value=current_axis_options[0].label, type="index", elem_id=self.elem_id("y_type"))
y_values = gr.Textbox(label="Y values", lines=1, elem_id=self.elem_id("y_values"))
-
- draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
- include_lone_images = gr.Checkbox(label='Include Separate Images', value=False, elem_id=self.elem_id("include_lone_images"))
- no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
+ fill_y_button = ToolButton(value=fill_values_symbol, elem_id="xy_grid_fill_y_tool_button", visible=False)
+
+ with gr.Row(variant="compact"):
+ draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
+ include_lone_images = gr.Checkbox(label='Include Separate Images', value=False, elem_id=self.elem_id("include_lone_images"))
+ no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
+ swap_axes_button = gr.Button(value="Swap axes", elem_id="xy_grid_swap_axes_button")
def swap_axes(x_type, x_values, y_type, y_values):
nonlocal current_axis_options
@@ -327,6 +345,19 @@ class Script(scripts.Script):
swap_args = [x_type, x_values, y_type, y_values]
swap_axes_button.click(swap_axes, inputs=swap_args, outputs=swap_args)
+ def fill(x_type):
+ axis = axis_options[x_type]
+ return ", ".join(axis.choices()) if axis.choices else gr.update()
+
+ fill_x_button.click(fn=fill, inputs=[x_type], outputs=[x_values])
+ fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values])
+
+ def select_axis(x_type):
+ return gr.Button.update(visible=axis_options[x_type].choices is not None)
+
+ x_type.change(fn=select_axis, inputs=[x_type], outputs=[fill_x_button])
+ y_type.change(fn=select_axis, inputs=[y_type], outputs=[fill_y_button])
+
return [x_type, x_values, y_type, y_values, draw_legend, include_lone_images, no_fixed_seeds]
def run(self, p, x_type, x_values, y_type, y_values, draw_legend, include_lone_images, no_fixed_seeds):
diff --git a/style.css b/style.css
index 1fddfcc2..97f9402a 100644
--- a/style.css
+++ b/style.css
@@ -644,7 +644,7 @@ canvas[key="mask"] {
max-width: 2.5em;
min-width: 2.5em !important;
height: 2.4em;
- margin: 0.55em 0;
+ margin: 0.55em 0.7em 0.55em 0;
}
#quicksettings .gr-button-tool{
@@ -717,16 +717,6 @@ footer {
line-height: 2.4em;
}
-#xy_grid_button_column {
- min-width: 38px !important;
-}
-
-#xy_grid_button_column button {
- height: 100%;
- margin-bottom: 0.7em;
- margin-left: 1em;
-}
-
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
From 52f6e94338f31c286361802b08ee5210b8244141 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 20:13:23 +0300
Subject: [PATCH 044/127] add --skip-install option to prevent running pip in
launch.py and speedup launch a bit
---
launch.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/launch.py b/launch.py
index bcbb792c..715427fd 100644
--- a/launch.py
+++ b/launch.py
@@ -14,6 +14,7 @@ python = sys.executable
git = os.environ.get('GIT', "git")
index_url = os.environ.get('INDEX_URL', "")
stored_commit_hash = None
+skip_install = False
def commit_hash():
@@ -89,6 +90,9 @@ def run_python(code, desc=None, errdesc=None):
def run_pip(args, desc=None):
+ if skip_install:
+ return
+
index_url_line = f' --index-url {index_url}' if index_url != '' else ''
return run(f'"{python}" -m pip {args} --prefer-binary{index_url_line}', desc=f"Installing {desc}", errdesc=f"Couldn't install {desc}")
@@ -173,6 +177,8 @@ def run_extensions_installers(settings_file):
def prepare_environment():
+ global skip_install
+
torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
commandline_args = os.environ.get('COMMANDLINE_ARGS', "")
@@ -206,6 +212,7 @@ def prepare_environment():
sys.argv, reinstall_xformers = extract_arg(sys.argv, '--reinstall-xformers')
sys.argv, update_check = extract_arg(sys.argv, '--update-check')
sys.argv, run_tests, test_dir = extract_opt(sys.argv, '--tests')
+ sys.argv, skip_install = extract_arg(sys.argv, '--skip-install')
xformers = '--xformers' in sys.argv
ngrok = '--ngrok' in sys.argv
From 9991967f40120b88a1dc925fdf7d747d5e016888 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 22:59:46 +0300
Subject: [PATCH 045/127] Add a check and explanation for tensor with all NaNs.
---
modules/devices.py | 28 ++++++++++++++++++++++++++++
modules/processing.py | 3 +++
modules/sd_samplers.py | 2 ++
3 files changed, 33 insertions(+)
diff --git a/modules/devices.py b/modules/devices.py
index caeb0276..6f034948 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -106,6 +106,33 @@ def autocast(disable=False):
return torch.autocast("cuda")
+class NansException(Exception):
+ pass
+
+
+def test_for_nans(x, where):
+ from modules import shared
+
+ if not torch.all(torch.isnan(x)).item():
+ return
+
+ if where == "unet":
+ message = "A tensor with all NaNs was produced in Unet."
+
+ if not shared.cmd_opts.no_half:
+ message += " This could be either because there's not enough precision to represent the picture, or because your video card does not support half type. Try using --no-half commandline argument to fix this."
+
+ elif where == "vae":
+ message = "A tensor with all NaNs was produced in VAE."
+
+ if not shared.cmd_opts.no_half and not shared.cmd_opts.no_half_vae:
+ message += " This could be because there's not enough precision to represent the picture. Try adding --no-half-vae commandline argument to fix this."
+ else:
+ message = "A tensor with all NaNs was produced."
+
+ raise NansException(message)
+
+
# MPS workaround for https://github.com/pytorch/pytorch/issues/79383
orig_tensor_to = torch.Tensor.to
def tensor_to_fix(self, *args, **kwargs):
@@ -156,3 +183,4 @@ if has_mps():
torch.Tensor.cumsum = lambda self, *args, **kwargs: ( cumsum_fix(self, orig_Tensor_cumsum, *args, **kwargs) )
orig_narrow = torch.narrow
torch.narrow = lambda *args, **kwargs: ( orig_narrow(*args, **kwargs).clone() )
+
diff --git a/modules/processing.py b/modules/processing.py
index 849f6b19..ab7b3b7d 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -608,6 +608,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts)
x_samples_ddim = [decode_first_stage(p.sd_model, samples_ddim[i:i+1].to(dtype=devices.dtype_vae))[0].cpu() for i in range(samples_ddim.size(0))]
+ for x in x_samples_ddim:
+ devices.test_for_nans(x, "vae")
+
x_samples_ddim = torch.stack(x_samples_ddim).float()
x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0)
diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py
index 76e0e0d5..6261d1f7 100644
--- a/modules/sd_samplers.py
+++ b/modules/sd_samplers.py
@@ -351,6 +351,8 @@ class CFGDenoiser(torch.nn.Module):
x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond={"c_crossattn": [uncond], "c_concat": [image_cond_in[-uncond.shape[0]:]]})
+ devices.test_for_nans(x_out, "unet")
+
if opts.live_preview_content == "Prompt":
store_latent(x_out[0:uncond.shape[0]])
elif opts.live_preview_content == "Negative prompt":
From e0e80050091ea7f58ae17c69f31d1b5de5e0ae20 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Mon, 16 Jan 2023 23:09:08 +0300
Subject: [PATCH 046/127] make StableDiffusionProcessing class not hold a
reference to shared.sd_model object
---
modules/processing.py | 9 +++++----
scripts/xy_grid.py | 1 -
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/modules/processing.py b/modules/processing.py
index ab7b3b7d..9c3673de 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -94,7 +94,7 @@ def txt2img_image_conditioning(sd_model, x, width, height):
return image_conditioning
-class StableDiffusionProcessing():
+class StableDiffusionProcessing:
"""
The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing
"""
@@ -102,7 +102,6 @@ class StableDiffusionProcessing():
if sampler_index is not None:
print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr)
- self.sd_model = sd_model
self.outpath_samples: str = outpath_samples
self.outpath_grids: str = outpath_grids
self.prompt: str = prompt
@@ -156,6 +155,10 @@ class StableDiffusionProcessing():
self.all_subseeds = None
self.iteration = 0
+ @property
+ def sd_model(self):
+ return shared.sd_model
+
def txt2img_image_conditioning(self, x, width=None, height=None):
self.is_using_inpainting_conditioning = self.sd_model.model.conditioning_key in {'hybrid', 'concat'}
@@ -236,7 +239,6 @@ class StableDiffusionProcessing():
raise NotImplementedError()
def close(self):
- self.sd_model = None
self.sampler = None
@@ -471,7 +473,6 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
if k == 'sd_model_checkpoint':
sd_models.reload_model_weights() # make onchange call for changing SD model
- p.sd_model = shared.sd_model
if k == 'sd_vae':
sd_vae.reload_vae_weights() # make onchange call for changing VAE
diff --git a/scripts/xy_grid.py b/scripts/xy_grid.py
index bf4ba92f..6629f5d5 100644
--- a/scripts/xy_grid.py
+++ b/scripts/xy_grid.py
@@ -86,7 +86,6 @@ def apply_checkpoint(p, x, xs):
if info is None:
raise RuntimeError(f"Unknown checkpoint: {x}")
modules.sd_models.reload_model_weights(shared.sd_model, info)
- p.sd_model = shared.sd_model
def confirm_checkpoints(p, xs):
From eb2223340cfdd58efaa157662c279fbf6c90c7d9 Mon Sep 17 00:00:00 2001
From: fuggy <45698918+nonetrix@users.noreply.github.com>
Date: Mon, 16 Jan 2023 21:50:30 -0600
Subject: [PATCH 047/127] Fix typo
---
modules/errors.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/errors.py b/modules/errors.py
index a668c014..a10e8708 100644
--- a/modules/errors.py
+++ b/modules/errors.py
@@ -19,7 +19,7 @@ def display(e: Exception, task):
message = str(e)
if "copying a param with shape torch.Size([640, 1024]) from checkpoint, the shape in current model is torch.Size([640, 768])" in message:
print_error_explanation("""
-The most likely cause of this is you are trying to load Stable Diffusion 2.0 model without specifying its connfig file.
+The most likely cause of this is you are trying to load Stable Diffusion 2.0 model without specifying its config file.
See https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#stable-diffusion-20 for how to solve this.
""")
From c361b89026442f3412162657f330d500b803e052 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Tue, 17 Jan 2023 11:04:56 +0300
Subject: [PATCH 048/127] disable the new NaN check for the CI
---
launch.py | 2 ++
modules/devices.py | 3 +++
modules/shared.py | 1 +
3 files changed, 6 insertions(+)
diff --git a/launch.py b/launch.py
index 715427fd..5afb2956 100644
--- a/launch.py
+++ b/launch.py
@@ -286,6 +286,8 @@ def tests(test_dir):
sys.argv.append("./test/test_files/empty.pt")
if "--skip-torch-cuda-test" not in sys.argv:
sys.argv.append("--skip-torch-cuda-test")
+ if "--disable-nan-check" not in sys.argv:
+ sys.argv.append("--disable-nan-check")
print(f"Launching Web UI in another process for testing with arguments: {' '.join(sys.argv[1:])}")
diff --git a/modules/devices.py b/modules/devices.py
index 6f034948..206184fb 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -113,6 +113,9 @@ class NansException(Exception):
def test_for_nans(x, where):
from modules import shared
+ if shared.cmd_opts.disable_nan_check:
+ return
+
if not torch.all(torch.isnan(x)).item():
return
diff --git a/modules/shared.py b/modules/shared.py
index 483c4c62..a708f23c 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -64,6 +64,7 @@ parser.add_argument("--sub-quad-chunk-threshold", type=int, help="the percentage
parser.add_argument("--opt-split-attention-invokeai", action='store_true', help="force-enables InvokeAI's cross-attention layer optimization. By default, it's on when cuda is unavailable.")
parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find")
parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization")
+parser.add_argument("--disable-nan-check", action='store_true', help="do not check if produced images/latent spaces have nans; useful for running without a checkpoint in CI")
parser.add_argument("--use-cpu", nargs='+', help="use CPU as torch device for specified modules", default=[], type=str.lower)
parser.add_argument("--listen", action='store_true', help="launch gradio with 0.0.0.0 as server name, allowing to respond to network requests")
parser.add_argument("--port", type=int, help="launch gradio with given server port, you need root/admin rights for ports < 1024, defaults to 7860 if available", default=None)
From 4688bfff55dd6607e6608524fb219f97dc6fe8bb Mon Sep 17 00:00:00 2001
From: dan
Date: Tue, 17 Jan 2023 17:16:43 +0800
Subject: [PATCH 049/127] Add auto-sized cropping UI
---
modules/textual_inversion/preprocess.py | 38 +++++++++++++++++++++++--
modules/ui.py | 28 +++++++++++++++++-
2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py
index 64abff4d..86c1cd33 100644
--- a/modules/textual_inversion/preprocess.py
+++ b/modules/textual_inversion/preprocess.py
@@ -12,7 +12,7 @@ from modules.shared import opts, cmd_opts
from modules.textual_inversion import autocrop
-def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False):
+def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None):
try:
if process_caption:
shared.interrogator.load()
@@ -20,7 +20,7 @@ def preprocess(id_task, process_src, process_dst, process_width, process_height,
if process_caption_deepbooru:
deepbooru.model.start()
- preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru, split_threshold, overlap_ratio, process_focal_crop, process_focal_crop_face_weight, process_focal_crop_entropy_weight, process_focal_crop_edges_weight, process_focal_crop_debug)
+ preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru, split_threshold, overlap_ratio, process_focal_crop, process_focal_crop_face_weight, process_focal_crop_entropy_weight, process_focal_crop_edges_weight, process_focal_crop_debug, process_multicrop, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold)
finally:
@@ -109,8 +109,32 @@ def split_pic(image, inverse_xy, width, height, overlap_ratio):
splitted = image.crop((0, y, to_w, y + to_h))
yield splitted
+# not using torchvision.transforms.CenterCrop because it doesn't allow float regions
+def center_crop(image: Image, w: int, h: int):
+ iw, ih = image.size
+ if ih / h < iw / w:
+ sw = w * ih / h
+ box = (iw - sw) / 2, 0, iw - (iw - sw) / 2, ih
+ else:
+ sh = h * iw / w
+ box = 0, (ih - sh) / 2, iw, ih - (ih - sh) / 2
+ return image.resize((w, h), Image.Resampling.LANCZOS, box)
-def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False):
+
+def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold):
+ iw, ih = image.size
+ err = lambda w, h: 1-(lambda x: x if x < 1 else 1/x)(iw/ih/(w/h))
+ try:
+ w, h = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
+ if minarea <= w * h <= maxarea and err(w, h) <= threshold),
+ key= lambda wh: ((objective=='Maximize area')*wh[0]*wh[1], -err(*wh))
+ )
+ except ValueError:
+ return
+ return center_crop(image, w, h)
+
+
+def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None):
width = process_width
height = process_height
src = os.path.abspath(process_src)
@@ -194,6 +218,14 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre
save_pic(focal, index, params, existing_caption=existing_caption)
process_default_resize = False
+ if process_multicrop:
+ cropped = multicrop_pic(img, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold)
+ if cropped is not None:
+ save_pic(cropped, index, params, existing_caption=existing_caption)
+ else:
+ print(f"skipped {img.width}x{img.height} image {filename} (can't find suitable size within error threshold)")
+ process_default_resize = False
+
if process_default_resize:
img = images.resize_image(1, img, width, height)
save_pic(img, index, params, existing_caption=existing_caption)
diff --git a/modules/ui.py b/modules/ui.py
index 20b66165..bbce9acd 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1226,6 +1226,7 @@ def create_ui():
process_flip = gr.Checkbox(label='Create flipped copies', elem_id="train_process_flip")
process_split = gr.Checkbox(label='Split oversized images', elem_id="train_process_split")
process_focal_crop = gr.Checkbox(label='Auto focal point crop', elem_id="train_process_focal_crop")
+ process_multicrop = gr.Checkbox(label='Auto-sized crop', elem_id="train_process_multicrop")
process_caption = gr.Checkbox(label='Use BLIP for caption', elem_id="train_process_caption")
process_caption_deepbooru = gr.Checkbox(label='Use deepbooru for caption', visible=True, elem_id="train_process_caption_deepbooru")
@@ -1238,7 +1239,19 @@ def create_ui():
process_focal_crop_entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_entropy_weight")
process_focal_crop_edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_edges_weight")
process_focal_crop_debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug")
-
+
+ with gr.Column(visible=False) as process_multicrop_col:
+ gr.Markdown('Each image is center-cropped with an automatically chosen width and height.')
+ with gr.Row():
+ process_multicrop_mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id="train_process_multicrop_mindim")
+ process_multicrop_maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id="train_process_multicrop_maxdim")
+ with gr.Row():
+ process_multicrop_minarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area lower bound", value=64*64, elem_id="train_process_multicrop_minarea")
+ process_multicrop_maxarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area upper bound", value=640*640, elem_id="train_process_multicrop_maxarea")
+ with gr.Row():
+ process_multicrop_objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="train_process_multicrop_objective")
+ process_multicrop_threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="train_process_multicrop_threshold")
+
with gr.Row():
with gr.Column(scale=3):
gr.HTML(value="")
@@ -1260,6 +1273,12 @@ def create_ui():
outputs=[process_focal_crop_row],
)
+ process_multicrop.change(
+ fn=lambda show: gr_show(show),
+ inputs=[process_multicrop],
+ outputs=[process_multicrop_col],
+ )
+
def get_textual_inversion_template_names():
return sorted([x for x in textual_inversion.textual_inversion_templates])
@@ -1379,6 +1398,13 @@ def create_ui():
process_focal_crop_entropy_weight,
process_focal_crop_edges_weight,
process_focal_crop_debug,
+ process_multicrop,
+ process_multicrop_mindim,
+ process_multicrop_maxdim,
+ process_multicrop_minarea,
+ process_multicrop_maxarea,
+ process_multicrop_objective,
+ process_multicrop_threshold,
],
outputs=[
ti_output,
From aede265f1d6d512ca9e51a305e98a96a215366c4 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Tue, 17 Jan 2023 13:57:55 +0300
Subject: [PATCH 050/127] Fix unable to find Real-ESRGAN model info error
(AttributeError: 'NoneType' object has no attribute 'data_path') #6841 #5170
---
modules/realesrgan_model.py | 12 ++++--------
modules/upscaler.py | 1 +
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/modules/realesrgan_model.py b/modules/realesrgan_model.py
index 3ac0b97a..47f70251 100644
--- a/modules/realesrgan_model.py
+++ b/modules/realesrgan_model.py
@@ -38,13 +38,13 @@ class UpscalerRealESRGAN(Upscaler):
return img
info = self.load_model(path)
- if not os.path.exists(info.data_path):
+ if not os.path.exists(info.local_data_path):
print("Unable to load RealESRGAN model: %s" % info.name)
return img
upsampler = RealESRGANer(
scale=info.scale,
- model_path=info.data_path,
+ model_path=info.local_data_path,
model=info.model(),
half=not cmd_opts.no_half,
tile=opts.ESRGAN_tile,
@@ -58,17 +58,13 @@ class UpscalerRealESRGAN(Upscaler):
def load_model(self, path):
try:
- info = None
- for scaler in self.scalers:
- if scaler.data_path == path:
- info = scaler
+ info = next(iter([scaler for scaler in self.scalers if scaler.data_path == path]), None)
if info is None:
print(f"Unable to find model info: {path}")
return None
- model_file = load_file_from_url(url=info.data_path, model_dir=self.model_path, progress=True)
- info.data_path = model_file
+ info.local_data_path = load_file_from_url(url=info.data_path, model_dir=self.model_path, progress=True)
return info
except Exception as e:
print(f"Error making Real-ESRGAN models list: {e}", file=sys.stderr)
diff --git a/modules/upscaler.py b/modules/upscaler.py
index 231680cb..a5bf5acb 100644
--- a/modules/upscaler.py
+++ b/modules/upscaler.py
@@ -95,6 +95,7 @@ class UpscalerData:
def __init__(self, name: str, path: str, upscaler: Upscaler = None, scale: int = 4, model=None):
self.name = name
self.data_path = path
+ self.local_data_path = path
self.scaler = upscaler
self.scale = scale
self.model = model
From 38b7186e6e3a4dffc93225308b822f0dae43a47d Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Tue, 17 Jan 2023 14:15:47 +0300
Subject: [PATCH 051/127] update sending input event in java script to not
cause exception in browser https://github.com/gradio-app/gradio/issues/2981
---
javascript/edit-attention.js | 5 ++---
javascript/extensions.js | 2 +-
javascript/ui.js | 8 ++++++++
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js
index b947cbec..ccc8344a 100644
--- a/javascript/edit-attention.js
+++ b/javascript/edit-attention.js
@@ -69,7 +69,6 @@ addEventListener('keydown', (event) => {
target.selectionStart = selectionStart;
target.selectionEnd = selectionEnd;
}
- // Since we've modified a Gradio Textbox component manually, we need to simulate an `input` DOM event to ensure its
- // internal Svelte data binding remains in sync.
- target.dispatchEvent(new Event("input", { bubbles: true }));
+
+ updateInput(target)
});
diff --git a/javascript/extensions.js b/javascript/extensions.js
index 59179ca6..ac6e35b9 100644
--- a/javascript/extensions.js
+++ b/javascript/extensions.js
@@ -29,7 +29,7 @@ function install_extension_from_index(button, url){
textarea = gradioApp().querySelector('#extension_to_install textarea')
textarea.value = url
- textarea.dispatchEvent(new Event("input", { bubbles: true }))
+ updateInput(textarea)
gradioApp().querySelector('#install_extension_button').click()
}
diff --git a/javascript/ui.js b/javascript/ui.js
index ecf97cb3..954beadd 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -278,3 +278,11 @@ function restart_reload(){
return []
}
+
+// Simulate an `input` DOM event for Gradio Textbox component. Needed after you edit its contents in javascript, otherwise your edits
+// will only visible on web page and not sent to python.
+function updateInput(target){
+ let e = new Event("input", { bubbles: true })
+ Object.defineProperty(e, "target", {value: target})
+ target.dispatchEvent(e);
+}
From 6e08da2c315c346225aa834017f4e32cfc0de200 Mon Sep 17 00:00:00 2001
From: ddPn08
Date: Tue, 17 Jan 2023 23:50:41 +0900
Subject: [PATCH 052/127] Add `--vae-dir` argument
---
modules/sd_vae.py | 7 +++++++
modules/shared.py | 1 +
2 files changed, 8 insertions(+)
diff --git a/modules/sd_vae.py b/modules/sd_vae.py
index b2af2ce7..da1bf15c 100644
--- a/modules/sd_vae.py
+++ b/modules/sd_vae.py
@@ -72,6 +72,13 @@ def refresh_vae_list():
os.path.join(shared.cmd_opts.ckpt_dir, '**/*.vae.safetensors'),
]
+ if shared.cmd_opts.vae_dir is not None and os.path.isdir(shared.cmd_opts.vae_dir):
+ paths += [
+ os.path.join(shared.cmd_opts.vae_dir, '**/*.ckpt'),
+ os.path.join(shared.cmd_opts.vae_dir, '**/*.pt'),
+ os.path.join(shared.cmd_opts.vae_dir, '**/*.safetensors'),
+ ]
+
candidates = []
for path in paths:
candidates += glob.iglob(path, recursive=True)
diff --git a/modules/shared.py b/modules/shared.py
index a708f23c..a1345ad3 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -26,6 +26,7 @@ parser = argparse.ArgumentParser()
parser.add_argument("--config", type=str, default=os.path.join(script_path, "configs/v1-inference.yaml"), help="path to config which constructs model",)
parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",)
parser.add_argument("--ckpt-dir", type=str, default=None, help="Path to directory with stable diffusion checkpoints")
+parser.add_argument("--vae-dir", type=str, default=None, help="Path to directory with stable VAE files")
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default=None)
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
From 5e15a0b422981c0b5484885d0b4d28af6913c76f Mon Sep 17 00:00:00 2001
From: EllangoK
Date: Tue, 17 Jan 2023 11:42:44 -0500
Subject: [PATCH 053/127] Changed params.txt save to after manual init call
---
modules/processing.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/modules/processing.py b/modules/processing.py
index 9c3673de..4a1f033e 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -538,10 +538,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if p.scripts is not None:
p.scripts.process(p)
- with open(os.path.join(shared.script_path, "params.txt"), "w", encoding="utf8") as file:
- processed = Processed(p, [], p.seed, "")
- file.write(processed.infotext(p, 0))
-
infotexts = []
output_images = []
@@ -572,6 +568,10 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
with devices.autocast():
p.init(p.all_prompts, p.all_seeds, p.all_subseeds)
+ with open(os.path.join(shared.script_path, "params.txt"), "w", encoding="utf8") as file:
+ processed = Processed(p, [], p.seed, "")
+ file.write(processed.infotext(p, 0))
+
if state.job_count == -1:
state.job_count = p.n_iter
From 3a0d6b77295162146d0a8d04278804334da6f1b4 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Tue, 17 Jan 2023 23:54:23 +0300
Subject: [PATCH 054/127] make it so that PNG images with EXIF do not lose
parameters in PNG info tab
---
modules/images.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/modules/images.py b/modules/images.py
index c3a5fc8b..3b1c5f34 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -605,8 +605,9 @@ def read_info_from_image(image):
except ValueError:
exif_comment = exif_comment.decode('utf8', errors="ignore")
- items['exif comment'] = exif_comment
- geninfo = exif_comment
+ if exif_comment:
+ items['exif comment'] = exif_comment
+ geninfo = exif_comment
for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif',
'loop', 'background', 'timestamp', 'duration']:
From d906f87043d809e6d4d8de3c9926e184169b330f Mon Sep 17 00:00:00 2001
From: ddPn08
Date: Wed, 18 Jan 2023 07:52:10 +0900
Subject: [PATCH 055/127] fix typo
---
modules/shared.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/shared.py b/modules/shared.py
index a1345ad3..a42279ec 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -26,7 +26,7 @@ parser = argparse.ArgumentParser()
parser.add_argument("--config", type=str, default=os.path.join(script_path, "configs/v1-inference.yaml"), help="path to config which constructs model",)
parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",)
parser.add_argument("--ckpt-dir", type=str, default=None, help="Path to directory with stable diffusion checkpoints")
-parser.add_argument("--vae-dir", type=str, default=None, help="Path to directory with stable VAE files")
+parser.add_argument("--vae-dir", type=str, default=None, help="Path to directory with VAE files")
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default=None)
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
From a255dac4f8c5ee11c15b634563d3df513f1834b4 Mon Sep 17 00:00:00 2001
From: brkirch
Date: Thu, 12 Jan 2023 08:00:38 -0500
Subject: [PATCH 056/127] Fix cumsum for MPS in newer torch
The prior fix assumed that testing int16 was enough to determine if a fix is needed, but a recent fix for cumsum has int16 working but not bool.
---
modules/devices.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/modules/devices.py b/modules/devices.py
index caeb0276..ac3ae0c9 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -139,8 +139,10 @@ orig_Tensor_cumsum = torch.Tensor.cumsum
def cumsum_fix(input, cumsum_func, *args, **kwargs):
if input.device.type == 'mps':
output_dtype = kwargs.get('dtype', input.dtype)
- if any(output_dtype == broken_dtype for broken_dtype in [torch.bool, torch.int8, torch.int16, torch.int64]):
+ if output_dtype == torch.int64:
return cumsum_func(input.cpu(), *args, **kwargs).to(input.device)
+ elif cumsum_needs_bool_fix and output_dtype == torch.bool or cumsum_needs_int_fix and (output_dtype == torch.int8 or output_dtype == torch.int16):
+ return cumsum_func(input.to(torch.int32), *args, **kwargs).to(torch.int64)
return cumsum_func(input, *args, **kwargs)
@@ -151,8 +153,9 @@ if has_mps():
torch.nn.functional.layer_norm = layer_norm_fix
torch.Tensor.numpy = numpy_fix
elif version.parse(torch.__version__) > version.parse("1.13.1"):
- if not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.Tensor([1,1]).to(torch.device("mps")).cumsum(0, dtype=torch.int16)):
- torch.cumsum = lambda input, *args, **kwargs: ( cumsum_fix(input, orig_cumsum, *args, **kwargs) )
- torch.Tensor.cumsum = lambda self, *args, **kwargs: ( cumsum_fix(self, orig_Tensor_cumsum, *args, **kwargs) )
+ cumsum_needs_int_fix = not torch.Tensor([1,2]).to(torch.device("mps")).equal(torch.ShortTensor([1,1]).to(torch.device("mps")).cumsum(0))
+ cumsum_needs_bool_fix = not torch.BoolTensor([True,True]).to(device=torch.device("mps"), dtype=torch.int64).equal(torch.BoolTensor([True,False]).to(torch.device("mps")).cumsum(0))
+ torch.cumsum = lambda input, *args, **kwargs: ( cumsum_fix(input, orig_cumsum, *args, **kwargs) )
+ torch.Tensor.cumsum = lambda self, *args, **kwargs: ( cumsum_fix(self, orig_Tensor_cumsum, *args, **kwargs) )
orig_narrow = torch.narrow
torch.narrow = lambda *args, **kwargs: ( orig_narrow(*args, **kwargs).clone() )
From dac59b9b073f86508d3ec787ff731af2e101fbcc Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 06:13:45 +0300
Subject: [PATCH 057/127] return progress percentage to title bar
---
javascript/progressbar.js | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index da6709bc..b8473ebf 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -106,6 +106,19 @@ function formatTime(secs){
}
}
+function setTitle(progress){
+ var title = 'Stable Diffusion'
+
+ if(opts.show_progress_in_title && progress){
+ title = '[' + progress.trim() + '] ' + title;
+ }
+
+ if(document.title != title){
+ document.title = title;
+ }
+}
+
+
function randomId(){
return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")"
}
@@ -133,6 +146,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
parentGallery.insertBefore(livePreview, gallery)
var removeProgressBar = function(){
+ setTitle("")
parentProgressbar.removeChild(divProgress)
parentGallery.removeChild(livePreview)
atEnd()
@@ -165,6 +179,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
progressText += " " + res.textinfo
}
+ setTitle(progressText)
divInner.textContent = progressText
var elapsedFromStart = (new Date() - dateStart) / 1000
From d8f8bcb821fa62e943eb95ee05b8a949317326fe Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 13:20:47 +0300
Subject: [PATCH 058/127] enable progressbar without gallery
---
javascript/progressbar.js | 24 +++++++++++++++---------
style.css | 19 +++----------------
2 files changed, 18 insertions(+), 25 deletions(-)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index b8473ebf..18c771a2 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -130,7 +130,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
var dateStart = new Date()
var wasEverActive = false
var parentProgressbar = progressbarContainer.parentNode
- var parentGallery = gallery.parentNode
+ var parentGallery = gallery ? gallery.parentNode : null
var divProgress = document.createElement('div')
divProgress.className='progressDiv'
@@ -141,14 +141,16 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
divProgress.appendChild(divInner)
parentProgressbar.insertBefore(divProgress, progressbarContainer)
- var livePreview = document.createElement('div')
- livePreview.className='livePreview'
- parentGallery.insertBefore(livePreview, gallery)
+ if(parentGallery){
+ var livePreview = document.createElement('div')
+ livePreview.className='livePreview'
+ parentGallery.insertBefore(livePreview, gallery)
+ }
var removeProgressBar = function(){
setTitle("")
parentProgressbar.removeChild(divProgress)
- parentGallery.removeChild(livePreview)
+ if(parentGallery) parentGallery.removeChild(livePreview)
atEnd()
}
@@ -168,6 +170,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
progressText = ""
divInner.style.width = ((res.progress || 0) * 100.0) + '%'
+ divInner.style.background = res.progress ? "" : "transparent"
if(res.progress > 0){
progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%'
@@ -175,11 +178,15 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
if(res.eta){
progressText += " ETA: " + formatTime(res.eta)
- } else if(res.textinfo){
- progressText += " " + res.textinfo
}
+
setTitle(progressText)
+
+ if(res.textinfo && res.textinfo.indexOf("\n") == -1){
+ progressText = res.textinfo + " " + progressText
+ }
+
divInner.textContent = progressText
var elapsedFromStart = (new Date() - dateStart) / 1000
@@ -197,8 +204,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
}
- if(res.live_preview){
-
+ if(res.live_preview && gallery){
var rect = gallery.getBoundingClientRect()
if(rect.width){
livePreview.style.width = rect.width + "px"
diff --git a/style.css b/style.css
index 97f9402a..b1d47df6 100644
--- a/style.css
+++ b/style.css
@@ -290,26 +290,12 @@ input[type="range"]{
min-height: unset !important;
}
-#txt2img_progressbar, #img2img_progressbar, #ti_progressbar{
- position: absolute;
- z-index: 1000;
- right: 0;
- padding-left: 5px;
- padding-right: 5px;
- display: block;
-}
-
-#txt2img_progress_row, #img2img_progress_row{
- margin-bottom: 10px;
- margin-top: -18px;
-}
-
.progressDiv{
position: absolute;
height: 20px;
top: -20px;
background: #b4c0cc;
- border-radius: 8px !important;
+ border-radius: 3px !important;
}
.dark .progressDiv{
@@ -325,9 +311,10 @@ input[type="range"]{
line-height: 20px;
padding: 0 8px 0 0;
text-align: right;
- border-radius: 8px;
+ border-radius: 3px;
overflow: visible;
white-space: nowrap;
+ padding: 0 0.5em;
}
.livePreview{
From 0c5913b9c28017523011ac6bf83b38ed5de8c11f Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 14:14:50 +0300
Subject: [PATCH 059/127] re-enable image dragging on non-firefox browsers
---
javascript/imageviewer.js | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js
index 1f29ad7b..aac2ee82 100644
--- a/javascript/imageviewer.js
+++ b/javascript/imageviewer.js
@@ -148,7 +148,15 @@ function showGalleryImage() {
if(e && e.parentElement.tagName == 'DIV'){
e.style.cursor='pointer'
e.style.userSelect='none'
- e.addEventListener('mousedown', function (evt) {
+
+ var isFirefox = isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
+
+ // For Firefox, listening on click first switched to next image then shows the lightbox.
+ // If you know how to fix this without switching to mousedown event, please.
+ // For other browsers the event is click to make it possiblr to drag picture.
+ var event = isFirefox ? 'mousedown' : 'click'
+
+ e.addEventListener(event, function (evt) {
if(!opts.js_modal_lightbox || evt.button != 0) return;
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed)
evt.preventDefault()
From 6faae2323963f9b0e0086a85b9d0472a24fbaa73 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 14:33:09 +0300
Subject: [PATCH 060/127] repair broken quicksettings when some form-requiring
options are added to it
---
modules/ui.py | 2 +-
style.css | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/ui.py b/modules/ui.py
index e1f98d23..6d70a795 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1659,7 +1659,7 @@ def create_ui():
interfaces += [(extensions_interface, "Extensions", "extensions")]
with gr.Blocks(css=css, analytics_enabled=False, title="Stable Diffusion") as demo:
- with gr.Row(elem_id="quicksettings"):
+ with gr.Row(elem_id="quicksettings", variant="compact"):
for i, k, item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])):
component = create_setting_component(k, is_quicksettings=True)
component_dict[k] = component
diff --git a/style.css b/style.css
index b6239142..fb58b6c3 100644
--- a/style.css
+++ b/style.css
@@ -530,7 +530,7 @@ input[type="range"]{
gap: 0.4em;
}
-#quicksettings > div{
+#quicksettings > div, #quicksettings > fieldset{
max-width: 24em;
min-width: 24em;
padding: 0;
From 05a779b0cd7cfe98d525e70362154a8a4d8b5e09 Mon Sep 17 00:00:00 2001
From: Vladimir Mandic
Date: Wed, 18 Jan 2023 09:47:38 -0500
Subject: [PATCH 061/127] fix syntax error
---
javascript/localization.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/localization.js b/javascript/localization.js
index bf9e1506..1a5a1dbb 100644
--- a/javascript/localization.js
+++ b/javascript/localization.js
@@ -11,7 +11,7 @@ ignore_ids_for_localization={
train_embedding: 'OPTION',
train_hypernetwork: 'OPTION',
txt2img_styles: 'OPTION',
- img2img_styles 'OPTION',
+ img2img_styles: 'OPTION',
setting_random_artist_categories: 'SPAN',
setting_face_restoration_model: 'SPAN',
setting_realesrgan_enabled_models: 'SPAN',
From 8683427bd9315d2fda0d2f9644c8b1f6a182da55 Mon Sep 17 00:00:00 2001
From: Vladimir Repin <32306715+mezotaken@users.noreply.github.com>
Date: Wed, 18 Jan 2023 20:16:52 +0300
Subject: [PATCH 062/127] Process interrogation on all img2img subtabs
---
javascript/ui.js | 7 +++++++
modules/ui.py | 50 +++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/javascript/ui.js b/javascript/ui.js
index 954beadd..7d3d57a3 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -109,6 +109,13 @@ function get_extras_tab_index(){
return [get_tab_index('mode_extras'), get_tab_index('extras_resize_mode'), ...args]
}
+function get_img2img_tab_index() {
+ let res = args_to_array(arguments)
+ res.splice(-2)
+ res[0] = get_tab_index('mode_img2img')
+ return res
+}
+
function create_submit_args(args){
res = []
for(var i=0;i
Date: Wed, 18 Jan 2023 20:29:44 +0300
Subject: [PATCH 063/127] make live previews not obscure multiselect dropdowns
---
style.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/style.css b/style.css
index fb58b6c3..61279a19 100644
--- a/style.css
+++ b/style.css
@@ -148,6 +148,7 @@
#txt2img_styles ul, #img2img_styles ul{
max-height: 35em;
+ z-index: 2000;
}
.gr-form{
From 924e222004ab54273806c5f2ca7a0e7cfa76ad83 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 23:04:24 +0300
Subject: [PATCH 064/127] add option to show/hide warnings removed hiding
warnings from LDSR fixed/reworked few places that produced warnings
---
extensions-builtin/LDSR/ldsr_model_arch.py | 3 --
javascript/localization.js | 2 +-
modules/hypernetworks/hypernetwork.py | 7 +++-
modules/sd_hijack.py | 8 ----
modules/sd_hijack_checkpoint.py | 38 ++++++++++++++++++-
modules/shared.py | 1 +
.../textual_inversion/textual_inversion.py | 6 ++-
modules/ui.py | 31 ++++++++-------
scripts/prompts_from_file.py | 2 +-
style.css | 5 +--
10 files changed, 71 insertions(+), 32 deletions(-)
diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py
index 0ad49f4e..bc11cc6e 100644
--- a/extensions-builtin/LDSR/ldsr_model_arch.py
+++ b/extensions-builtin/LDSR/ldsr_model_arch.py
@@ -1,7 +1,6 @@
import os
import gc
import time
-import warnings
import numpy as np
import torch
@@ -15,8 +14,6 @@ from ldm.models.diffusion.ddim import DDIMSampler
from ldm.util import instantiate_from_config, ismap
from modules import shared, sd_hijack
-warnings.filterwarnings("ignore", category=UserWarning)
-
cached_ldsr_model: torch.nn.Module = None
diff --git a/javascript/localization.js b/javascript/localization.js
index bf9e1506..1a5a1dbb 100644
--- a/javascript/localization.js
+++ b/javascript/localization.js
@@ -11,7 +11,7 @@ ignore_ids_for_localization={
train_embedding: 'OPTION',
train_hypernetwork: 'OPTION',
txt2img_styles: 'OPTION',
- img2img_styles 'OPTION',
+ img2img_styles: 'OPTION',
setting_random_artist_categories: 'SPAN',
setting_face_restoration_model: 'SPAN',
setting_realesrgan_enabled_models: 'SPAN',
diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py
index c963fc40..74e78582 100644
--- a/modules/hypernetworks/hypernetwork.py
+++ b/modules/hypernetworks/hypernetwork.py
@@ -12,7 +12,7 @@ import torch
import tqdm
from einops import rearrange, repeat
from ldm.util import default
-from modules import devices, processing, sd_models, shared, sd_samplers, hashes
+from modules import devices, processing, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint
from modules.textual_inversion import textual_inversion, logging
from modules.textual_inversion.learn_schedule import LearnRateScheduler
from torch import einsum
@@ -575,6 +575,8 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi
pbar = tqdm.tqdm(total=steps - initial_step)
try:
+ sd_hijack_checkpoint.add()
+
for i in range((steps-initial_step) * gradient_step):
if scheduler.finished:
break
@@ -724,6 +726,9 @@ Last saved image: {html.escape(last_saved_image)}
pbar.close()
hypernetwork.eval()
#report_statistics(loss_dict)
+ sd_hijack_checkpoint.remove()
+
+
filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt')
hypernetwork.optimizer_name = optimizer_name
diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py
index 6b0d95af..870eba88 100644
--- a/modules/sd_hijack.py
+++ b/modules/sd_hijack.py
@@ -69,12 +69,6 @@ def undo_optimizations():
ldm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward
-def fix_checkpoint():
- ldm.modules.attention.BasicTransformerBlock.forward = sd_hijack_checkpoint.BasicTransformerBlock_forward
- ldm.modules.diffusionmodules.openaimodel.ResBlock.forward = sd_hijack_checkpoint.ResBlock_forward
- ldm.modules.diffusionmodules.openaimodel.AttentionBlock.forward = sd_hijack_checkpoint.AttentionBlock_forward
-
-
class StableDiffusionModelHijack:
fixes = None
comments = []
@@ -106,8 +100,6 @@ class StableDiffusionModelHijack:
self.optimization_method = apply_optimizations()
self.clip = m.cond_stage_model
-
- fix_checkpoint()
def flatten(el):
flattened = [flatten(children) for children in el.children()]
diff --git a/modules/sd_hijack_checkpoint.py b/modules/sd_hijack_checkpoint.py
index 5712972f..2604d969 100644
--- a/modules/sd_hijack_checkpoint.py
+++ b/modules/sd_hijack_checkpoint.py
@@ -1,10 +1,46 @@
from torch.utils.checkpoint import checkpoint
+import ldm.modules.attention
+import ldm.modules.diffusionmodules.openaimodel
+
+
def BasicTransformerBlock_forward(self, x, context=None):
return checkpoint(self._forward, x, context)
+
def AttentionBlock_forward(self, x):
return checkpoint(self._forward, x)
+
def ResBlock_forward(self, x, emb):
- return checkpoint(self._forward, x, emb)
\ No newline at end of file
+ return checkpoint(self._forward, x, emb)
+
+
+stored = []
+
+
+def add():
+ if len(stored) != 0:
+ return
+
+ stored.extend([
+ ldm.modules.attention.BasicTransformerBlock.forward,
+ ldm.modules.diffusionmodules.openaimodel.ResBlock.forward,
+ ldm.modules.diffusionmodules.openaimodel.AttentionBlock.forward
+ ])
+
+ ldm.modules.attention.BasicTransformerBlock.forward = BasicTransformerBlock_forward
+ ldm.modules.diffusionmodules.openaimodel.ResBlock.forward = ResBlock_forward
+ ldm.modules.diffusionmodules.openaimodel.AttentionBlock.forward = AttentionBlock_forward
+
+
+def remove():
+ if len(stored) == 0:
+ return
+
+ ldm.modules.attention.BasicTransformerBlock.forward = stored[0]
+ ldm.modules.diffusionmodules.openaimodel.ResBlock.forward = stored[1]
+ ldm.modules.diffusionmodules.openaimodel.AttentionBlock.forward = stored[2]
+
+ stored.clear()
+
diff --git a/modules/shared.py b/modules/shared.py
index a708f23c..ddb97f99 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -369,6 +369,7 @@ options_templates.update(options_section(('face-restoration', "Face restoration"
}))
options_templates.update(options_section(('system', "System"), {
+ "show_warnings": OptionInfo(False, "Show warnings in console."),
"memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}),
"samples_log_stdout": OptionInfo(False, "Always print all generation info to standard output"),
"multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job."),
diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py
index 7e4a6d24..5a7be422 100644
--- a/modules/textual_inversion/textual_inversion.py
+++ b/modules/textual_inversion/textual_inversion.py
@@ -15,7 +15,7 @@ import numpy as np
from PIL import Image, PngImagePlugin
from torch.utils.tensorboard import SummaryWriter
-from modules import shared, devices, sd_hijack, processing, sd_models, images, sd_samplers
+from modules import shared, devices, sd_hijack, processing, sd_models, images, sd_samplers, sd_hijack_checkpoint
import modules.textual_inversion.dataset
from modules.textual_inversion.learn_schedule import LearnRateScheduler
@@ -452,6 +452,8 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st
pbar = tqdm.tqdm(total=steps - initial_step)
try:
+ sd_hijack_checkpoint.add()
+
for i in range((steps-initial_step) * gradient_step):
if scheduler.finished:
break
@@ -617,9 +619,11 @@ Last saved image: {html.escape(last_saved_image)}
pbar.close()
shared.sd_model.first_stage_model.to(devices.device)
shared.parallel_processing_allowed = old_parallel_processing_allowed
+ sd_hijack_checkpoint.remove()
return embedding, filename
+
def save_embedding(embedding, optimizer, checkpoint, embedding_name, filename, remove_cached_checksum=True):
old_embedding_name = embedding.name
old_sd_checkpoint = embedding.sd_checkpoint if hasattr(embedding, "sd_checkpoint") else None
diff --git a/modules/ui.py b/modules/ui.py
index 6d70a795..25818fb0 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -11,6 +11,7 @@ import tempfile
import time
import traceback
from functools import partial, reduce
+import warnings
import gradio as gr
import gradio.routes
@@ -41,6 +42,8 @@ from modules.textual_inversion import textual_inversion
import modules.hypernetworks.ui
from modules.generation_parameters_copypaste import image_from_url_text
+warnings.filterwarnings("default" if opts.show_warnings else "ignore", category=UserWarning)
+
# this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI
mimetypes.init()
mimetypes.add_type('application/javascript', '.js')
@@ -417,17 +420,16 @@ def apply_setting(key, value):
return value
-def update_generation_info(args):
- generation_info, html_info, img_index = args
+def update_generation_info(generation_info, html_info, img_index):
try:
generation_info = json.loads(generation_info)
if img_index < 0 or img_index >= len(generation_info["infotexts"]):
- return html_info
- return plaintext_to_html(generation_info["infotexts"][img_index])
+ return html_info, gr.update()
+ return plaintext_to_html(generation_info["infotexts"][img_index]), gr.update()
except Exception:
pass
# if the json parse or anything else fails, just return the old html_info
- return html_info
+ return html_info, gr.update()
def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id):
@@ -508,10 +510,9 @@ Requested path was: {f}
generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
generation_info_button.click(
fn=update_generation_info,
- _js="(x, y) => [x, y, selected_gallery_index()]",
- inputs=[generation_info, html_info],
- outputs=[html_info],
- preprocess=False
+ _js="function(x, y, z){ console.log(x, y, z); return [x, y, selected_gallery_index()] }",
+ inputs=[generation_info, html_info, html_info],
+ outputs=[html_info, html_info],
)
save.click(
@@ -526,7 +527,8 @@ Requested path was: {f}
outputs=[
download_files,
html_log,
- ]
+ ],
+ show_progress=False,
)
save_zip.click(
@@ -588,7 +590,7 @@ def create_ui():
txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _,txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False)
dummy_component = gr.Label(visible=False)
- txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="bytes", visible=False)
+ txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False)
with gr.Row().style(equal_height=False):
with gr.Column(variant='compact', elem_id="txt2img_settings"):
@@ -768,7 +770,7 @@ def create_ui():
with gr.Blocks(analytics_enabled=False) as img2img_interface:
img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True)
- img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="bytes", visible=False)
+ img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False)
with FormRow().style(equal_height=False):
with gr.Column(variant='compact', elem_id="img2img_settings"):
@@ -1768,7 +1770,10 @@ def create_ui():
if saved_value is None:
ui_settings[key] = getattr(obj, field)
elif condition and not condition(saved_value):
- print(f'Warning: Bad ui setting value: {key}: {saved_value}; Default value "{getattr(obj, field)}" will be used instead.')
+ pass
+
+ # this warning is generally not useful;
+ # print(f'Warning: Bad ui setting value: {key}: {saved_value}; Default value "{getattr(obj, field)}" will be used instead.')
else:
setattr(obj, field, saved_value)
if init_field is not None:
diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py
index f3e711d7..76dc5778 100644
--- a/scripts/prompts_from_file.py
+++ b/scripts/prompts_from_file.py
@@ -116,7 +116,7 @@ class Script(scripts.Script):
checkbox_iterate_batch = gr.Checkbox(label="Use same random seed for all lines", value=False, elem_id=self.elem_id("checkbox_iterate_batch"))
prompt_txt = gr.Textbox(label="List of prompt inputs", lines=1, elem_id=self.elem_id("prompt_txt"))
- file = gr.File(label="Upload prompt inputs", type='bytes', elem_id=self.elem_id("file"))
+ file = gr.File(label="Upload prompt inputs", type='binary', elem_id=self.elem_id("file"))
file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt])
diff --git a/style.css b/style.css
index 61279a19..0845519a 100644
--- a/style.css
+++ b/style.css
@@ -299,9 +299,8 @@ input[type="range"]{
}
/* more gradio's garbage cleanup */
-.min-h-\[4rem\] {
- min-height: unset !important;
-}
+.min-h-\[4rem\] { min-height: unset !important; }
+.min-h-\[6rem\] { min-height: unset !important; }
.progressDiv{
position: absolute;
From b186d44dcd0df9d127a663b297334a5bd8258b58 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Wed, 18 Jan 2023 23:20:23 +0300
Subject: [PATCH 065/127] use DDIM in hires fix is the sampler is PLMS
---
modules/processing.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/modules/processing.py b/modules/processing.py
index 9c3673de..8c18ac53 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -857,7 +857,8 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
shared.state.nextjob()
- self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
+ img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM
+ self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model)
samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2]
From bb0978ecfd3177d0bfd7cacd1ac8796d7eec2d79 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 00:44:51 +0300
Subject: [PATCH 066/127] fix hires fix ui weirdness caused by gradio update
---
modules/ui.py | 6 +++---
style.css | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/modules/ui.py b/modules/ui.py
index 8b7f1dfb..09a3c92e 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -638,7 +638,7 @@ def create_ui():
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img')
elif category == "checkboxes":
- with FormRow(elem_id="txt2img_checkboxes"):
+ with FormRow(elem_id="txt2img_checkboxes", variant="compact"):
restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces")
tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling")
enable_hr = gr.Checkbox(label='Hires. fix', value=False, elem_id="txt2img_enable_hr")
@@ -646,12 +646,12 @@ def create_ui():
elif category == "hires_fix":
with FormGroup(visible=False, elem_id="txt2img_hires_fix") as hr_options:
- with FormRow(elem_id="txt2img_hires_fix_row1"):
+ with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"):
hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode)
hr_second_pass_steps = gr.Slider(minimum=0, maximum=150, step=1, label='Hires steps', value=0, elem_id="txt2img_hires_steps")
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.7, elem_id="txt2img_denoising_strength")
- with FormRow(elem_id="txt2img_hires_fix_row2"):
+ with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"):
hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale")
hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x")
hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y")
diff --git a/style.css b/style.css
index 0845519a..a6abd93d 100644
--- a/style.css
+++ b/style.css
@@ -686,7 +686,7 @@ footer {
#txt2img_checkboxes, #img2img_checkboxes{
margin-bottom: 0.5em;
}
-#txt2img_checkboxes > div > div, #img2img_checkboxes > div > div{
+#txt2img_checkboxes > div, #img2img_checkboxes > div{
flex: 0;
white-space: nowrap;
min-width: auto;
From 956263b8a4f0393dcb47ed497f367717add4f0e9 Mon Sep 17 00:00:00 2001
From: facu
Date: Wed, 18 Jan 2023 19:15:53 -0300
Subject: [PATCH 067/127] fixing error using lspci on macOsX
---
webui.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index 6e07778f..1edf921d 100755
--- a/webui.sh
+++ b/webui.sh
@@ -165,7 +165,7 @@ else
printf "\n%s\n" "${delimiter}"
printf "Launching launch.py..."
printf "\n%s\n" "${delimiter}"
- gpu_info=$(lspci | grep VGA)
+ gpu_info=$(lspci 2>/dev/null | grep VGA)
if echo "$gpu_info" | grep -q "AMD"
then
if [[ -z "${TORCH_COMMAND}" ]]
From 99207bc816d027b522e1c49001748c63fd426b53 Mon Sep 17 00:00:00 2001
From: EllangoK
Date: Wed, 18 Jan 2023 19:13:15 -0500
Subject: [PATCH 068/127] check model name values are set before merging
---
modules/extras.py | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/modules/extras.py b/modules/extras.py
index 22668fcd..29eb1f07 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -287,10 +287,19 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
def add_difference(theta0, theta1_2_diff, alpha):
return theta0 + (alpha * theta1_2_diff)
+ if not primary_model_name:
+ shared.state.textinfo = "Failed: Merging requires a primary model."
+ shared.state.end()
+ return ["Failed: Merging requires a primary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+
primary_model_info = sd_models.checkpoints_list[primary_model_name]
+
+ if not secondary_model_name:
+ shared.state.textinfo = "Failed: Merging requires a secondary model."
+ shared.state.end()
+ return ["Failed: Merging requires a secondary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+
secondary_model_info = sd_models.checkpoints_list[secondary_model_name]
- tertiary_model_info = sd_models.checkpoints_list.get(tertiary_model_name, None)
- result_is_inpainting_model = False
theta_funcs = {
"Weighted sum": (None, weighted_sum),
@@ -298,10 +307,15 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
}
theta_func1, theta_func2 = theta_funcs[interp_method]
- if theta_func1 and not tertiary_model_info:
+ tertiary_model_info = None
+ if theta_func1 and not tertiary_model_name:
shared.state.textinfo = "Failed: Interpolation method requires a tertiary model."
shared.state.end()
- return ["Failed: Interpolation method requires a tertiary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ return [f"Failed: Interpolation method ({interp_method}) requires a tertiary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ else:
+ tertiary_model_info = sd_models.checkpoints_list.get(tertiary_model_name, None)
+
+ result_is_inpainting_model = False
shared.state.textinfo = f"Loading {secondary_model_info.filename}..."
print(f"Loading {secondary_model_info.filename}...")
From 26a6a78b16f88a6f88f4cca3f378db3b83fc94f8 Mon Sep 17 00:00:00 2001
From: EllangoK
Date: Wed, 18 Jan 2023 21:21:52 -0500
Subject: [PATCH 069/127] only lookup tertiary model if theta_func1 is set
---
modules/extras.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/modules/extras.py b/modules/extras.py
index 29eb1f07..88eea22e 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -307,13 +307,12 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
}
theta_func1, theta_func2 = theta_funcs[interp_method]
- tertiary_model_info = None
if theta_func1 and not tertiary_model_name:
shared.state.textinfo = "Failed: Interpolation method requires a tertiary model."
shared.state.end()
return [f"Failed: Interpolation method ({interp_method}) requires a tertiary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
- else:
- tertiary_model_info = sd_models.checkpoints_list.get(tertiary_model_name, None)
+
+ tertiary_model_info = sd_models.checkpoints_list[tertiary_model_name] if theta_func1 else None
result_is_inpainting_model = False
From 308b51012a5def38edb1c2e127e736c43aa6e1a3 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 08:41:37 +0300
Subject: [PATCH 070/127] fix an unlikely division by 0 error
---
modules/progress.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/modules/progress.py b/modules/progress.py
index 3327b883..f9e005d3 100644
--- a/modules/progress.py
+++ b/modules/progress.py
@@ -67,10 +67,13 @@ def progressapi(req: ProgressRequest):
progress = 0
- if shared.state.job_count > 0:
- progress += shared.state.job_no / shared.state.job_count
- if shared.state.sampling_steps > 0:
- progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps
+ job_count, job_no = shared.state.job_count, shared.state.job_no
+ sampling_steps, sampling_step = shared.state.sampling_steps, shared.state.sampling_step
+
+ if job_count > 0:
+ progress += job_no / job_count
+ if sampling_steps > 0:
+ progress += 1 / job_count * sampling_step / sampling_steps
progress = min(progress, 1)
From 7cfc6450305125683799208fb7bc27c0b12586b3 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 08:53:50 +0300
Subject: [PATCH 071/127] eliminate repetition of code in #6910
---
modules/extras.py | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/modules/extras.py b/modules/extras.py
index 88eea22e..367c15cc 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -278,6 +278,11 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
shared.state.begin()
shared.state.job = 'model-merge'
+ def fail(message):
+ shared.state.textinfo = message
+ shared.state.end()
+ return [message, *[gr.update() for _ in range(4)]]
+
def weighted_sum(theta0, theta1, alpha):
return ((1 - alpha) * theta0) + (alpha * theta1)
@@ -288,16 +293,12 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
return theta0 + (alpha * theta1_2_diff)
if not primary_model_name:
- shared.state.textinfo = "Failed: Merging requires a primary model."
- shared.state.end()
- return ["Failed: Merging requires a primary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ return fail("Failed: Merging requires a primary model.")
primary_model_info = sd_models.checkpoints_list[primary_model_name]
if not secondary_model_name:
- shared.state.textinfo = "Failed: Merging requires a secondary model."
- shared.state.end()
- return ["Failed: Merging requires a secondary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ return fail("Failed: Merging requires a secondary model.")
secondary_model_info = sd_models.checkpoints_list[secondary_model_name]
@@ -308,9 +309,7 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
theta_func1, theta_func2 = theta_funcs[interp_method]
if theta_func1 and not tertiary_model_name:
- shared.state.textinfo = "Failed: Interpolation method requires a tertiary model."
- shared.state.end()
- return [f"Failed: Interpolation method ({interp_method}) requires a tertiary model."] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ return fail(f"Failed: Interpolation method ({interp_method}) requires a tertiary model.")
tertiary_model_info = sd_models.checkpoints_list[tertiary_model_name] if theta_func1 else None
From c7e50425f63c07242068f8dcccce70a4ef28a17f Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 09:25:37 +0300
Subject: [PATCH 072/127] add progress bar to modelmerger
---
javascript/ui.js | 11 +++++++++++
modules/extras.py | 18 +++++++++++++++---
modules/progress.py | 2 +-
modules/ui.py | 13 ++++++++-----
style.css | 5 +++++
5 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/javascript/ui.js b/javascript/ui.js
index 7d3d57a3..428375d4 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -172,6 +172,17 @@ function submit_img2img(){
return res
}
+function modelmerger(){
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('modelmerger_results_panel'), null, function(){})
+
+ gradioApp().getElementById('modelmerger_result').innerHTML = ''
+
+ var res = create_submit_args(arguments)
+ res[0] = id
+ return res
+}
+
function ask_for_style_name(_, prompt_text, negative_prompt_text) {
name_ = prompt('Style name:')
diff --git a/modules/extras.py b/modules/extras.py
index 367c15cc..034f28e4 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -274,14 +274,15 @@ def create_config(ckpt_result, config_source, a, b, c):
shutil.copyfile(cfg, checkpoint_filename)
-def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source):
+def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source):
shared.state.begin()
shared.state.job = 'model-merge'
+ shared.state.job_count = 1
def fail(message):
shared.state.textinfo = message
shared.state.end()
- return [message, *[gr.update() for _ in range(4)]]
+ return [*[gr.update() for _ in range(4)], message]
def weighted_sum(theta0, theta1, alpha):
return ((1 - alpha) * theta0) + (alpha * theta1)
@@ -320,9 +321,12 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
theta_1 = sd_models.read_state_dict(secondary_model_info.filename, map_location='cpu')
if theta_func1:
+ shared.state.job_count += 1
+
print(f"Loading {tertiary_model_info.filename}...")
theta_2 = sd_models.read_state_dict(tertiary_model_info.filename, map_location='cpu')
+ shared.state.sampling_steps = len(theta_1.keys())
for key in tqdm.tqdm(theta_1.keys()):
if 'model' in key:
if key in theta_2:
@@ -330,8 +334,12 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
theta_1[key] = theta_func1(theta_1[key], t2)
else:
theta_1[key] = torch.zeros_like(theta_1[key])
+
+ shared.state.sampling_step += 1
del theta_2
+ shared.state.nextjob()
+
shared.state.textinfo = f"Loading {primary_model_info.filename}..."
print(f"Loading {primary_model_info.filename}...")
theta_0 = sd_models.read_state_dict(primary_model_info.filename, map_location='cpu')
@@ -340,6 +348,7 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
+ shared.state.sampling_steps = len(theta_0.keys())
for key in tqdm.tqdm(theta_0.keys()):
if 'model' in key and key in theta_1:
@@ -367,6 +376,8 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
if save_as_half:
theta_0[key] = theta_0[key].half()
+ shared.state.sampling_step += 1
+
# I believe this part should be discarded, but I'll leave it for now until I am sure
for key in theta_1.keys():
if 'model' in key and key not in theta_0:
@@ -393,6 +404,7 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
output_modelname = os.path.join(ckpt_dir, filename)
+ shared.state.nextjob()
shared.state.textinfo = f"Saving to {output_modelname}..."
print(f"Saving to {output_modelname}...")
@@ -410,4 +422,4 @@ def run_modelmerger(primary_model_name, secondary_model_name, tertiary_model_nam
shared.state.textinfo = "Checkpoint saved to " + output_modelname
shared.state.end()
- return ["Checkpoint saved to " + output_modelname] + [gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)]
+ return [*[gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)], "Checkpoint saved to " + output_modelname]
diff --git a/modules/progress.py b/modules/progress.py
index f9e005d3..c69ecf3d 100644
--- a/modules/progress.py
+++ b/modules/progress.py
@@ -72,7 +72,7 @@ def progressapi(req: ProgressRequest):
if job_count > 0:
progress += job_no / job_count
- if sampling_steps > 0:
+ if sampling_steps > 0 and job_count > 0:
progress += 1 / job_count * sampling_step / sampling_steps
progress = min(progress, 1)
diff --git a/modules/ui.py b/modules/ui.py
index 09a3c92e..aeee7853 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1208,8 +1208,9 @@ def create_ui():
with gr.Row():
modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary')
- with gr.Column(variant='panel'):
- submit_result = gr.Textbox(elem_id="modelmerger_result", show_label=False)
+ with gr.Column(variant='compact', elem_id="modelmerger_results_container"):
+ with gr.Group(elem_id="modelmerger_results_panel"):
+ modelmerger_result = gr.HTML(elem_id="modelmerger_result", show_label=False)
with gr.Blocks(analytics_enabled=False) as train_interface:
with gr.Row().style(equal_height=False):
@@ -1753,12 +1754,14 @@ def create_ui():
print("Error loading/saving model file:", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
modules.sd_models.list_models() # to remove the potentially missing models from the list
- return [f"Error merging checkpoints: {e}"] + [gr.Dropdown.update(choices=modules.sd_models.checkpoint_tiles()) for _ in range(4)]
+ return [*[gr.Dropdown.update(choices=modules.sd_models.checkpoint_tiles()) for _ in range(4)], f"Error merging checkpoints: {e}"]
return results
modelmerger_merge.click(
- fn=modelmerger,
+ fn=wrap_gradio_gpu_call(modelmerger, extra_outputs=lambda: [gr.update() for _ in range(4)]),
+ _js='modelmerger',
inputs=[
+ dummy_component,
primary_model_name,
secondary_model_name,
tertiary_model_name,
@@ -1770,11 +1773,11 @@ def create_ui():
config_source,
],
outputs=[
- submit_result,
primary_model_name,
secondary_model_name,
tertiary_model_name,
component_dict['sd_model_checkpoint'],
+ modelmerger_result,
]
)
diff --git a/style.css b/style.css
index a6abd93d..32ba4753 100644
--- a/style.css
+++ b/style.css
@@ -737,6 +737,11 @@ footer {
line-height: 2.4em;
}
+#modelmerger_results_container{
+ margin-top: 1em;
+ overflow: visible;
+}
+
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
From 0f5dbfffd0b7202a48e404d8e74b5cc9a3e5b135 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 10:39:51 +0300
Subject: [PATCH 073/127] allow baking in VAE in checkpoint merger tab do not
save config if it's the default for checkpoint merger tab change file naming
scheme for checkpoint merger tab allow just saving A without any merging for
checkpoint merger tab some stylistic changes for UI in checkpoint merger tab
---
javascript/hints.js | 1 +
javascript/ui.js | 2 -
modules/extras.py | 114 +++++++++++++++++++++++++++-----------------
modules/sd_vae.py | 9 +++-
modules/shared.py | 3 +-
modules/ui.py | 17 +++++--
style.css | 15 ++++--
7 files changed, 102 insertions(+), 59 deletions(-)
diff --git a/javascript/hints.js b/javascript/hints.js
index fa5e5ae8..e746e20d 100644
--- a/javascript/hints.js
+++ b/javascript/hints.js
@@ -92,6 +92,7 @@ titles = {
"Weighted sum": "Result = A * (1 - M) + B * M",
"Add difference": "Result = A + (B - C) * M",
+ "No interpolation": "Result = A",
"Initialization text": "If the number of tokens is more than the number of vectors, some may be skipped.\nLeave the textbox empty to start with zeroed out vectors",
"Learning rate": "How fast should training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.",
diff --git a/javascript/ui.js b/javascript/ui.js
index 428375d4..37788a3e 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -176,8 +176,6 @@ function modelmerger(){
var id = randomId()
requestProgress(id, gradioApp().getElementById('modelmerger_results_panel'), null, function(){})
- gradioApp().getElementById('modelmerger_result').innerHTML = ''
-
var res = create_submit_args(arguments)
res[0] = id
return res
diff --git a/modules/extras.py b/modules/extras.py
index 034f28e4..fe701a0e 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -15,7 +15,7 @@ from typing import Callable, List, OrderedDict, Tuple
from functools import partial
from dataclasses import dataclass
-from modules import processing, shared, images, devices, sd_models, sd_samplers
+from modules import processing, shared, images, devices, sd_models, sd_samplers, sd_vae
from modules.shared import opts
import modules.gfpgan_model
from modules.ui import plaintext_to_html
@@ -251,7 +251,8 @@ def run_pnginfo(image):
def create_config(ckpt_result, config_source, a, b, c):
def config(x):
- return sd_models.find_checkpoint_config(x) if x else None
+ res = sd_models.find_checkpoint_config(x) if x else None
+ return res if res != shared.sd_default_config else None
if config_source == 0:
cfg = config(a) or config(b) or config(c)
@@ -274,10 +275,12 @@ def create_config(ckpt_result, config_source, a, b, c):
shutil.copyfile(cfg, checkpoint_filename)
-def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source):
+chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
+
+
+def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae):
shared.state.begin()
shared.state.job = 'model-merge'
- shared.state.job_count = 1
def fail(message):
shared.state.textinfo = message
@@ -293,41 +296,68 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
def add_difference(theta0, theta1_2_diff, alpha):
return theta0 + (alpha * theta1_2_diff)
+ def filename_weighed_sum():
+ a = primary_model_info.model_name
+ b = secondary_model_info.model_name
+ Ma = round(1 - multiplier, 2)
+ Mb = round(multiplier, 2)
+
+ return f"{Ma}({a}) + {Mb}({b})"
+
+ def filename_add_differnece():
+ a = primary_model_info.model_name
+ b = secondary_model_info.model_name
+ c = tertiary_model_info.model_name
+ M = round(multiplier, 2)
+
+ return f"{a} + {M}({b} - {c})"
+
+ def filename_nothing():
+ return primary_model_info.model_name
+
+ theta_funcs = {
+ "Weighted sum": (filename_weighed_sum, None, weighted_sum),
+ "Add difference": (filename_add_differnece, get_difference, add_difference),
+ "No interpolation": (filename_nothing, None, None),
+ }
+ filename_generator, theta_func1, theta_func2 = theta_funcs[interp_method]
+ shared.state.job_count = (1 if theta_func1 else 0) + (1 if theta_func2 else 0)
+
if not primary_model_name:
return fail("Failed: Merging requires a primary model.")
primary_model_info = sd_models.checkpoints_list[primary_model_name]
- if not secondary_model_name:
+ if theta_func2 and not secondary_model_name:
return fail("Failed: Merging requires a secondary model.")
-
- secondary_model_info = sd_models.checkpoints_list[secondary_model_name]
- theta_funcs = {
- "Weighted sum": (None, weighted_sum),
- "Add difference": (get_difference, add_difference),
- }
- theta_func1, theta_func2 = theta_funcs[interp_method]
+ secondary_model_info = sd_models.checkpoints_list[secondary_model_name] if theta_func2 else None
if theta_func1 and not tertiary_model_name:
return fail(f"Failed: Interpolation method ({interp_method}) requires a tertiary model.")
-
+
tertiary_model_info = sd_models.checkpoints_list[tertiary_model_name] if theta_func1 else None
result_is_inpainting_model = False
- shared.state.textinfo = f"Loading {secondary_model_info.filename}..."
- print(f"Loading {secondary_model_info.filename}...")
- theta_1 = sd_models.read_state_dict(secondary_model_info.filename, map_location='cpu')
+ if theta_func2:
+ shared.state.textinfo = f"Loading B"
+ print(f"Loading {secondary_model_info.filename}...")
+ theta_1 = sd_models.read_state_dict(secondary_model_info.filename, map_location='cpu')
+ else:
+ theta_1 = None
if theta_func1:
- shared.state.job_count += 1
-
+ shared.state.textinfo = f"Loading C"
print(f"Loading {tertiary_model_info.filename}...")
theta_2 = sd_models.read_state_dict(tertiary_model_info.filename, map_location='cpu')
+ shared.state.textinfo = 'Merging B and C'
shared.state.sampling_steps = len(theta_1.keys())
for key in tqdm.tqdm(theta_1.keys()):
+ if key in chckpoint_dict_skip_on_merge:
+ continue
+
if 'model' in key:
if key in theta_2:
t2 = theta_2.get(key, torch.zeros_like(theta_1[key]))
@@ -345,12 +375,10 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
theta_0 = sd_models.read_state_dict(primary_model_info.filename, map_location='cpu')
print("Merging...")
-
- chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
-
+ shared.state.textinfo = 'Merging A and B'
shared.state.sampling_steps = len(theta_0.keys())
for key in tqdm.tqdm(theta_0.keys()):
- if 'model' in key and key in theta_1:
+ if theta_1 and 'model' in key and key in theta_1:
if key in chckpoint_dict_skip_on_merge:
continue
@@ -358,7 +386,6 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
a = theta_0[key]
b = theta_1[key]
- shared.state.textinfo = f'Merging layer {key}'
# this enables merging an inpainting model (A) with another one (B);
# where normal model would have 4 channels, for latenst space, inpainting model would
# have another 4 channels for unmasked picture's latent space, plus one channel for mask, for a total of 9
@@ -378,34 +405,31 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
shared.state.sampling_step += 1
- # I believe this part should be discarded, but I'll leave it for now until I am sure
- for key in theta_1.keys():
- if 'model' in key and key not in theta_0:
-
- if key in chckpoint_dict_skip_on_merge:
- continue
-
- theta_0[key] = theta_1[key]
- if save_as_half:
- theta_0[key] = theta_0[key].half()
del theta_1
+ bake_in_vae_filename = sd_vae.vae_dict.get(bake_in_vae, None)
+ if bake_in_vae_filename is not None:
+ print(f"Baking in VAE from {bake_in_vae_filename}")
+ shared.state.textinfo = 'Baking in VAE'
+ vae_dict = sd_vae.load_vae_dict(bake_in_vae_filename, map_location='cpu')
+
+ for key in vae_dict.keys():
+ theta_0_key = 'first_stage_model.' + key
+ if theta_0_key in theta_0:
+ theta_0[theta_0_key] = vae_dict[key].half() if save_as_half else vae_dict[key]
+
+ del vae_dict
+
ckpt_dir = shared.cmd_opts.ckpt_dir or sd_models.model_path
- filename = \
- primary_model_info.model_name + '_' + str(round(1-multiplier, 2)) + '-' + \
- secondary_model_info.model_name + '_' + str(round(multiplier, 2)) + '-' + \
- interp_method.replace(" ", "_") + \
- '-merged.' + \
- ("inpainting." if result_is_inpainting_model else "") + \
- checkpoint_format
-
- filename = filename if custom_name == '' else (custom_name + '.' + checkpoint_format)
+ filename = filename_generator() if custom_name == '' else custom_name
+ filename += ".inpainting" if result_is_inpainting_model else ""
+ filename += "." + checkpoint_format
output_modelname = os.path.join(ckpt_dir, filename)
shared.state.nextjob()
- shared.state.textinfo = f"Saving to {output_modelname}..."
+ shared.state.textinfo = "Saving"
print(f"Saving to {output_modelname}...")
_, extension = os.path.splitext(output_modelname)
@@ -418,8 +442,8 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
create_config(output_modelname, config_source, primary_model_info, secondary_model_info, tertiary_model_info)
- print("Checkpoint saved.")
- shared.state.textinfo = "Checkpoint saved to " + output_modelname
+ print(f"Checkpoint saved to {output_modelname}.")
+ shared.state.textinfo = "Checkpoint saved"
shared.state.end()
return [*[gr.Dropdown.update(choices=sd_models.checkpoint_tiles()) for _ in range(4)], "Checkpoint saved to " + output_modelname]
diff --git a/modules/sd_vae.py b/modules/sd_vae.py
index da1bf15c..4ce238b8 100644
--- a/modules/sd_vae.py
+++ b/modules/sd_vae.py
@@ -120,6 +120,12 @@ def resolve_vae(checkpoint_file):
return None, None
+def load_vae_dict(filename, map_location):
+ vae_ckpt = sd_models.read_state_dict(filename, map_location=map_location)
+ vae_dict_1 = {k: v for k, v in vae_ckpt.items() if k[0:4] != "loss" and k not in vae_ignore_keys}
+ return vae_dict_1
+
+
def load_vae(model, vae_file=None, vae_source="from unknown source"):
global vae_dict, loaded_vae_file
# save_settings = False
@@ -137,8 +143,7 @@ def load_vae(model, vae_file=None, vae_source="from unknown source"):
print(f"Loading VAE weights {vae_source}: {vae_file}")
store_base_vae(model)
- vae_ckpt = sd_models.read_state_dict(vae_file, map_location=shared.weight_load_location)
- vae_dict_1 = {k: v for k, v in vae_ckpt.items() if k[0:4] != "loss" and k not in vae_ignore_keys}
+ vae_dict_1 = load_vae_dict(vae_file, map_location=shared.weight_load_location)
_load_vae_dict(model, vae_dict_1)
if cache_enabled:
diff --git a/modules/shared.py b/modules/shared.py
index 77e5e91c..29b28bff 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -20,10 +20,11 @@ from modules.paths import models_path, script_path, sd_path
demo = None
+sd_default_config = os.path.join(script_path, "configs/v1-inference.yaml")
sd_model_file = os.path.join(script_path, 'model.ckpt')
default_sd_model_file = sd_model_file
parser = argparse.ArgumentParser()
-parser.add_argument("--config", type=str, default=os.path.join(script_path, "configs/v1-inference.yaml"), help="path to config which constructs model",)
+parser.add_argument("--config", type=str, default=sd_default_config, help="path to config which constructs model",)
parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",)
parser.add_argument("--ckpt-dir", type=str, default=None, help="Path to directory with stable diffusion checkpoints")
parser.add_argument("--vae-dir", type=str, default=None, help="Path to directory with VAE files")
diff --git a/modules/ui.py b/modules/ui.py
index aeee7853..4e381a49 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -20,7 +20,7 @@ import numpy as np
from PIL import Image, PngImagePlugin
from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call
-from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru
+from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae
from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML
from modules.paths import script_path
@@ -1185,7 +1185,7 @@ def create_ui():
with gr.Column(variant='compact'):
gr.HTML(value="A merger of the two checkpoints will be generated in your checkpoint directory.
")
- with FormRow():
+ with FormRow(elem_id="modelmerger_models"):
primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)")
create_refresh_button(primary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_A")
@@ -1197,13 +1197,20 @@ def create_ui():
custom_name = gr.Textbox(label="Custom Name (Optional)", elem_id="modelmerger_custom_name")
interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Multiplier (M) - set to 0 to get model A', value=0.3, elem_id="modelmerger_interp_amount")
- interp_method = gr.Radio(choices=["Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method")
+ interp_method = gr.Radio(choices=["No interpolation", "Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method")
with FormRow():
checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="ckpt", label="Checkpoint format", elem_id="modelmerger_checkpoint_format")
save_as_half = gr.Checkbox(value=False, label="Save as float16", elem_id="modelmerger_save_as_half")
- config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method")
+ with FormRow():
+ with gr.Column():
+ config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method")
+
+ with gr.Column():
+ with FormRow():
+ bake_in_vae = gr.Dropdown(choices=["None"] + list(sd_vae.vae_dict), value="None", label="Bake in VAE", elem_id="modelmerger_bake_in_vae")
+ create_refresh_button(bake_in_vae, sd_vae.refresh_vae_list, lambda: {"choices": ["None"] + list(sd_vae.vae_dict)}, "modelmerger_refresh_bake_in_vae")
with gr.Row():
modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary')
@@ -1757,6 +1764,7 @@ def create_ui():
return [*[gr.Dropdown.update(choices=modules.sd_models.checkpoint_tiles()) for _ in range(4)], f"Error merging checkpoints: {e}"]
return results
+ modelmerger_merge.click(fn=lambda: '', inputs=[], outputs=[modelmerger_result])
modelmerger_merge.click(
fn=wrap_gradio_gpu_call(modelmerger, extra_outputs=lambda: [gr.update() for _ in range(4)]),
_js='modelmerger',
@@ -1771,6 +1779,7 @@ def create_ui():
custom_name,
checkpoint_format,
config_source,
+ bake_in_vae,
],
outputs=[
primary_model_name,
diff --git a/style.css b/style.css
index 32ba4753..c10e32a1 100644
--- a/style.css
+++ b/style.css
@@ -641,6 +641,16 @@ canvas[key="mask"] {
margin: 0.6em 0em 0.55em 0;
}
+#modelmerger_results_container{
+ margin-top: 1em;
+ overflow: visible;
+}
+
+#modelmerger_models{
+ gap: 0;
+}
+
+
#quicksettings .gr-button-tool{
margin: 0;
}
@@ -737,11 +747,6 @@ footer {
line-height: 2.4em;
}
-#modelmerger_results_container{
- margin-top: 1em;
- overflow: visible;
-}
-
/* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running
From 54674674b813894b908283531ddaab4ccfeac721 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 12:12:09 +0300
Subject: [PATCH 074/127] allow having at half precision when there is only one
checkpoint in merger tab
---
modules/extras.py | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/modules/extras.py b/modules/extras.py
index fe701a0e..d03f976e 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -278,6 +278,13 @@ def create_config(ckpt_result, config_source, a, b, c):
chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
+def to_half(tensor, enable):
+ if enable and tensor.dtype == torch.float:
+ return tensor.half()
+
+ return tensor
+
+
def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae):
shared.state.begin()
shared.state.job = 'model-merge'
@@ -400,8 +407,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
else:
theta_0[key] = theta_func2(a, b, multiplier)
- if save_as_half:
- theta_0[key] = theta_0[key].half()
+ theta_0[key] = to_half(theta_0[key], save_as_half)
shared.state.sampling_step += 1
@@ -416,10 +422,14 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
for key in vae_dict.keys():
theta_0_key = 'first_stage_model.' + key
if theta_0_key in theta_0:
- theta_0[theta_0_key] = vae_dict[key].half() if save_as_half else vae_dict[key]
+ theta_0[theta_0_key] = to_half(vae_dict[key], save_as_half)
del vae_dict
+ if save_as_half and not theta_func2:
+ for key in theta_0.keys():
+ theta_0[key] = to_half(theta_0[key], save_as_half)
+
ckpt_dir = shared.cmd_opts.ckpt_dir or sd_models.model_path
filename = filename_generator() if custom_name == '' else custom_name
From 18a09c7e0032e2e655269e8e2b4f1ca6ed0cc7d3 Mon Sep 17 00:00:00 2001
From: dan
Date: Thu, 19 Jan 2023 17:36:23 +0800
Subject: [PATCH 075/127] Simplification and bugfix
---
modules/textual_inversion/preprocess.py | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py
index 86c1cd33..454dcc36 100644
--- a/modules/textual_inversion/preprocess.py
+++ b/modules/textual_inversion/preprocess.py
@@ -124,13 +124,11 @@ def center_crop(image: Image, w: int, h: int):
def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold):
iw, ih = image.size
err = lambda w, h: 1-(lambda x: x if x < 1 else 1/x)(iw/ih/(w/h))
- try:
- w, h = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
- if minarea <= w * h <= maxarea and err(w, h) <= threshold),
- key= lambda wh: ((objective=='Maximize area')*wh[0]*wh[1], -err(*wh))
- )
- except ValueError:
- return
+ w, h = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
+ if minarea <= w * h <= maxarea and err(w, h) <= threshold),
+ key= lambda wh: (wh[0]*wh[1], -err(*wh))[::1 if objective=='Maximize area' else -1],
+ default=None
+ )
return center_crop(image, w, h)
From 2985b317d719f0f0580d2ff93f3008ccabb9c251 Mon Sep 17 00:00:00 2001
From: dan
Date: Thu, 19 Jan 2023 17:39:30 +0800
Subject: [PATCH 076/127] Fix of fix
---
modules/textual_inversion/preprocess.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py
index 454dcc36..c0ac11d3 100644
--- a/modules/textual_inversion/preprocess.py
+++ b/modules/textual_inversion/preprocess.py
@@ -124,12 +124,12 @@ def center_crop(image: Image, w: int, h: int):
def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold):
iw, ih = image.size
err = lambda w, h: 1-(lambda x: x if x < 1 else 1/x)(iw/ih/(w/h))
- w, h = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
+ wh = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
if minarea <= w * h <= maxarea and err(w, h) <= threshold),
key= lambda wh: (wh[0]*wh[1], -err(*wh))[::1 if objective=='Maximize area' else -1],
default=None
)
- return center_crop(image, w, h)
+ return wh and center_crop(image, *wh)
def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None):
From b271e22f7ac1b2cabca8985b1e4437ab685a2c21 Mon Sep 17 00:00:00 2001
From: vt-idiot <81622808+vt-idiot@users.noreply.github.com>
Date: Thu, 19 Jan 2023 06:12:19 -0500
Subject: [PATCH 077/127] Update shared.py
`Witdth/Height` was driving me insane. -> `Width/Height`
---
modules/shared.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/shared.py b/modules/shared.py
index 29b28bff..2f366454 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -448,7 +448,7 @@ options_templates.update(options_section(('ui', "User interface"), {
"js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"),
"show_progress_in_title": OptionInfo(True, "Show generation progress in window title."),
"samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group"),
- "dimensions_and_batch_together": OptionInfo(True, "Show Witdth/Height and Batch sliders in same row"),
+ "dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row"),
'quicksettings': OptionInfo("sd_model_checkpoint", "Quicksettings list"),
'ui_reorder': OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"),
'localization': OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)),
From c12d7ddd725c485682c1caa025627c9ee936d743 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 15:58:32 +0300
Subject: [PATCH 078/127] add handling to some places in javascript that can
potentially cause issues #6898
---
.../javascript/prompt-bracket-checker.js | 10 ++++++----
javascript/progressbar.js | 9 +++++++--
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
index eccfb0f9..251a1f57 100644
--- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
+++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
@@ -93,10 +93,12 @@ function checkBrackets(evt) {
}
var shadowRootLoaded = setInterval(function() {
- var shadowTextArea = document.querySelector('gradio-app').shadowRoot.querySelectorAll('#txt2img_prompt > label > textarea');
- if(shadowTextArea.length < 1) {
- return false;
- }
+ var sahdowRoot = document.querySelector('gradio-app').shadowRoot;
+ if(! sahdowRoot) return false;
+
+ var shadowTextArea = sahdowRoot.querySelectorAll('#txt2img_prompt > label > textarea');
+ if(shadowTextArea.length < 1) return false;
+
clearInterval(shadowRootLoaded);
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index 18c771a2..2514d2e2 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -81,8 +81,13 @@ function request(url, data, handler, errorHandler){
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
- var js = JSON.parse(xhr.responseText);
- handler(js)
+ try {
+ var js = JSON.parse(xhr.responseText);
+ handler(js)
+ } catch (error) {
+ console.error(error);
+ errorHandler()
+ }
} else{
errorHandler()
}
From 81276cde90ebecfab317cc62a0100d298c3c43c4 Mon Sep 17 00:00:00 2001
From: poiuty
Date: Thu, 19 Jan 2023 16:56:45 +0300
Subject: [PATCH 079/127] internal progress relative path
---
javascript/progressbar.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index 2514d2e2..ff6d757b 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -160,7 +160,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre
}
var fun = function(id_task, id_live_preview){
- request("/internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){
+ request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){
if(res.completed){
removeProgressBar()
return
From d1ea518dea3d7584be2927cc486d15ec3e18ddb0 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 18:07:37 +0300
Subject: [PATCH 080/127] remember the list of checkpoints after you press
refresh button and reload the page
---
modules/ui.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/modules/ui.py b/modules/ui.py
index af416d5f..0c5ba358 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1771,8 +1771,17 @@ def create_ui():
component_keys = [k for k in opts.data_labels.keys() if k in component_dict]
+ def get_value_for_setting(key):
+ value = getattr(opts, key)
+
+ info = opts.data_labels[key]
+ args = info.component_args() if callable(info.component_args) else info.component_args or {}
+ args = {k: v for k, v in args.items() if k not in {'precision'}}
+
+ return gr.update(value=value, **args)
+
def get_settings_values():
- return [getattr(opts, key) for key in component_keys]
+ return [get_value_for_setting(key) for key in component_keys]
demo.load(
fn=get_settings_values,
From f2ae2529877072874ebaac0257fe4af48c5855a4 Mon Sep 17 00:00:00 2001
From: EllangoK
Date: Thu, 19 Jan 2023 10:24:17 -0500
Subject: [PATCH 081/127] fixes minor typos around run_modelmerger
---
modules/extras.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/modules/extras.py b/modules/extras.py
index d03f976e..1218f88f 100644
--- a/modules/extras.py
+++ b/modules/extras.py
@@ -275,7 +275,7 @@ def create_config(ckpt_result, config_source, a, b, c):
shutil.copyfile(cfg, checkpoint_filename)
-chckpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
+checkpoint_dict_skip_on_merge = ["cond_stage_model.transformer.text_model.embeddings.position_ids"]
def to_half(tensor, enable):
@@ -303,7 +303,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
def add_difference(theta0, theta1_2_diff, alpha):
return theta0 + (alpha * theta1_2_diff)
- def filename_weighed_sum():
+ def filename_weighted_sum():
a = primary_model_info.model_name
b = secondary_model_info.model_name
Ma = round(1 - multiplier, 2)
@@ -311,7 +311,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
return f"{Ma}({a}) + {Mb}({b})"
- def filename_add_differnece():
+ def filename_add_difference():
a = primary_model_info.model_name
b = secondary_model_info.model_name
c = tertiary_model_info.model_name
@@ -323,8 +323,8 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
return primary_model_info.model_name
theta_funcs = {
- "Weighted sum": (filename_weighed_sum, None, weighted_sum),
- "Add difference": (filename_add_differnece, get_difference, add_difference),
+ "Weighted sum": (filename_weighted_sum, None, weighted_sum),
+ "Add difference": (filename_add_difference, get_difference, add_difference),
"No interpolation": (filename_nothing, None, None),
}
filename_generator, theta_func1, theta_func2 = theta_funcs[interp_method]
@@ -362,7 +362,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
shared.state.textinfo = 'Merging B and C'
shared.state.sampling_steps = len(theta_1.keys())
for key in tqdm.tqdm(theta_1.keys()):
- if key in chckpoint_dict_skip_on_merge:
+ if key in checkpoint_dict_skip_on_merge:
continue
if 'model' in key:
@@ -387,7 +387,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_
for key in tqdm.tqdm(theta_0.keys()):
if theta_1 and 'model' in key and key in theta_1:
- if key in chckpoint_dict_skip_on_merge:
+ if key in checkpoint_dict_skip_on_merge:
continue
a = theta_0[key]
From c1928cdd6194928af0f53f70c51d59479b7025e2 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 18:58:08 +0300
Subject: [PATCH 082/127] bring back short hashes to sd checkpoint selection
---
modules/sd_models.py | 15 +++++++++++----
modules/ui.py | 23 ++++++++++++-----------
2 files changed, 23 insertions(+), 15 deletions(-)
diff --git a/modules/sd_models.py b/modules/sd_models.py
index 6a681cef..12083848 100644
--- a/modules/sd_models.py
+++ b/modules/sd_models.py
@@ -41,14 +41,16 @@ class CheckpointInfo:
if name.startswith("\\") or name.startswith("/"):
name = name[1:]
- self.title = name
+ self.name = name
self.model_name = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
self.hash = model_hash(filename)
- self.sha256 = hashes.sha256_from_cache(self.filename, "checkpoint/" + self.title)
+ self.sha256 = hashes.sha256_from_cache(self.filename, "checkpoint/" + name)
self.shorthash = self.sha256[0:10] if self.sha256 else None
- self.ids = [self.hash, self.model_name, self.title, f'{name} [{self.hash}]'] + ([self.shorthash, self.sha256] if self.shorthash else [])
+ self.title = name if self.shorthash is None else f'{name} [{self.shorthash}]'
+
+ self.ids = [self.hash, self.model_name, self.title, name, f'{name} [{self.hash}]'] + ([self.shorthash, self.sha256, f'{self.name} [{self.shorthash}]'] if self.shorthash else [])
def register(self):
checkpoints_list[self.title] = self
@@ -56,13 +58,15 @@ class CheckpointInfo:
checkpoint_alisases[id] = self
def calculate_shorthash(self):
- self.sha256 = hashes.sha256(self.filename, "checkpoint/" + self.title)
+ self.sha256 = hashes.sha256(self.filename, "checkpoint/" + self.name)
self.shorthash = self.sha256[0:10]
if self.shorthash not in self.ids:
self.ids += [self.shorthash, self.sha256]
self.register()
+ self.title = f'{self.name} [{self.shorthash}]'
+
return self.shorthash
@@ -225,7 +229,10 @@ def read_state_dict(checkpoint_file, print_global_state=False, map_location=None
def load_model_weights(model, checkpoint_info: CheckpointInfo):
+ title = checkpoint_info.title
sd_model_hash = checkpoint_info.calculate_shorthash()
+ if checkpoint_info.title != title:
+ shared.opts.data["sd_model_checkpoint"] = checkpoint_info.title
cache_enabled = shared.opts.sd_checkpoint_cache > 0
diff --git a/modules/ui.py b/modules/ui.py
index 0c5ba358..13d80ae2 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -439,7 +439,7 @@ def apply_setting(key, value):
opts.data_labels[key].onchange()
opts.save(shared.config_filename)
- return value
+ return getattr(opts, key)
def update_generation_info(generation_info, html_info, img_index):
@@ -597,6 +597,16 @@ def ordered_ui_categories():
yield category
+def get_value_for_setting(key):
+ value = getattr(opts, key)
+
+ info = opts.data_labels[key]
+ args = info.component_args() if callable(info.component_args) else info.component_args or {}
+ args = {k: v for k, v in args.items() if k not in {'precision'}}
+
+ return gr.update(value=value, **args)
+
+
def create_ui():
import modules.img2img
import modules.txt2img
@@ -1600,7 +1610,7 @@ def create_ui():
opts.save(shared.config_filename)
- return gr.update(value=value), opts.dumpjson()
+ return get_value_for_setting(key), opts.dumpjson()
with gr.Blocks(analytics_enabled=False) as settings_interface:
with gr.Row():
@@ -1771,15 +1781,6 @@ def create_ui():
component_keys = [k for k in opts.data_labels.keys() if k in component_dict]
- def get_value_for_setting(key):
- value = getattr(opts, key)
-
- info = opts.data_labels[key]
- args = info.component_args() if callable(info.component_args) else info.component_args or {}
- args = {k: v for k, v in args.items() if k not in {'precision'}}
-
- return gr.update(value=value, **args)
-
def get_settings_values():
return [get_value_for_setting(key) for key in component_keys]
From 4599e8ad0acaae3f13bd3a7bef4db7632aac8504 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Thu, 19 Jan 2023 17:00:51 +0100
Subject: [PATCH 083/127] Environment variable on launch just for Navi cards
Setting HSA_OVERRIDE_GFX_VERSION=10.3.0 for all AMD cards seems to break compatibility for polaris and vega cards so it should just be enabled on Navi
---
webui.sh | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index 1edf921d..a35a5f35 100755
--- a/webui.sh
+++ b/webui.sh
@@ -172,7 +172,12 @@ else
then
export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
fi
- HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ if echo "$gpu_info" | grep -q "Navi"
+ then
+ HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ else
+ exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
+ fi
else
exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
fi
From 6073456c8348d15716b9bc5276d994fe8554e4ca Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Thu, 19 Jan 2023 20:39:03 +0300
Subject: [PATCH 084/127] write a comment for fix_checkpoint function
---
modules/sd_hijack.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py
index 870eba88..f9652d21 100644
--- a/modules/sd_hijack.py
+++ b/modules/sd_hijack.py
@@ -69,6 +69,13 @@ def undo_optimizations():
ldm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward
+def fix_checkpoint():
+ """checkpoints are now added and removed in embedding/hypernet code, since torch doesn't want
+ checkpoints to be added when not training (there's a warning)"""
+
+ pass
+
+
class StableDiffusionModelHijack:
fixes = None
comments = []
From c09fb3d8f1f71bc66d7c4cea603885619d6a1cd4 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Thu, 19 Jan 2023 19:21:02 +0100
Subject: [PATCH 085/127] Simplify GPU check
---
webui.sh | 25 ++++++++++---------------
1 file changed, 10 insertions(+), 15 deletions(-)
diff --git a/webui.sh b/webui.sh
index a35a5f35..aa4f875c 100755
--- a/webui.sh
+++ b/webui.sh
@@ -104,6 +104,12 @@ then
fi
# Check prerequisites
+gpu_info=$(lspci 2>/dev/null | grep VGA)
+if echo "$gpu_info" | grep -q "Navi"
+then
+ export HSA_OVERRIDE_GFX_VERSION=10.3.0
+fi
+
for preq in "${GIT}" "${python_cmd}"
do
if ! hash "${preq}" &>/dev/null
@@ -165,20 +171,9 @@ else
printf "\n%s\n" "${delimiter}"
printf "Launching launch.py..."
printf "\n%s\n" "${delimiter}"
- gpu_info=$(lspci 2>/dev/null | grep VGA)
- if echo "$gpu_info" | grep -q "AMD"
+ if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]]
then
- if [[ -z "${TORCH_COMMAND}" ]]
- then
- export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
- fi
- if echo "$gpu_info" | grep -q "Navi"
- then
- HSA_OVERRIDE_GFX_VERSION=10.3.0 exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
- else
- exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
- fi
- else
- exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
- fi
+ export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
+ fi
+ exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
fi
From 48045545d9a3f174621a62086812d9bbfb3ce1c2 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Thu, 19 Jan 2023 19:23:40 +0100
Subject: [PATCH 086/127] Small reformat of the GPU check
---
webui.sh | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/webui.sh b/webui.sh
index aa4f875c..ff410e15 100755
--- a/webui.sh
+++ b/webui.sh
@@ -109,6 +109,10 @@ if echo "$gpu_info" | grep -q "Navi"
then
export HSA_OVERRIDE_GFX_VERSION=10.3.0
fi
+if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]]
+then
+ export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
+fi
for preq in "${GIT}" "${python_cmd}"
do
@@ -170,10 +174,6 @@ then
else
printf "\n%s\n" "${delimiter}"
printf "Launching launch.py..."
- printf "\n%s\n" "${delimiter}"
- if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]]
- then
- export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
- fi
+ printf "\n%s\n" "${delimiter}"
exec "${python_cmd}" "${LAUNCH_SCRIPT}" "$@"
fi
From 36364bd76c4634820e08070a287f0a5ad27c35f6 Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Thu, 19 Jan 2023 20:05:49 +0100
Subject: [PATCH 087/127] GFX env just for RDNA 1 and 2
This commit specifies which GPUs should use the GFX variable, RDNA 3 is excluded since it uses a newer GFX version
---
webui.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index ff410e15..91c95e47 100755
--- a/webui.sh
+++ b/webui.sh
@@ -105,7 +105,7 @@ fi
# Check prerequisites
gpu_info=$(lspci 2>/dev/null | grep VGA)
-if echo "$gpu_info" | grep -q "Navi"
+if echo "$gpu_info" | grep -qE "Navi (1|2)"
then
export HSA_OVERRIDE_GFX_VERSION=10.3.0
fi
From 912285ae64e4e1186feb54caf82b4a0b11c6cb7f Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Thu, 19 Jan 2023 23:42:12 +0100
Subject: [PATCH 088/127] Experimental support for Renoir
This adds the GFX version 9.0.0 in order to use Renoir GPUs with at least 4 GB of VRAM (it's possible to increase the virtual VRAM from the BIOS settings of some vendors). This will only work if the remaining ram is at least 12 GB to avoid the system to become unresponsive on launch.).
This change also changes the GPU check to a case statement to be able to add more GPUs efficiently.
---
webui.sh | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/webui.sh b/webui.sh
index 91c95e47..27933c04 100755
--- a/webui.sh
+++ b/webui.sh
@@ -105,10 +105,14 @@ fi
# Check prerequisites
gpu_info=$(lspci 2>/dev/null | grep VGA)
-if echo "$gpu_info" | grep -qE "Navi (1|2)"
-then
- export HSA_OVERRIDE_GFX_VERSION=10.3.0
-fi
+case "$gpu_info" in
+ *"Navi 1"*|*"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0
+ ;;
+ *"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0
+ ;;
+ *)
+ ;;
+esac
if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]]
then
export TORCH_COMMAND="pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/rocm5.2"
From 0684a6819dfaec40732271ca5ef32392c36f17ba Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 20 Jan 2023 00:21:05 +0100
Subject: [PATCH 089/127] Usage explanation for Renoir users
---
webui.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/webui.sh b/webui.sh
index 27933c04..4da51880 100755
--- a/webui.sh
+++ b/webui.sh
@@ -109,6 +109,9 @@ case "$gpu_info" in
*"Navi 1"*|*"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0
;;
*"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0
+ printf "\n%s\n" "${delimiter}"
+ printf "Make sure to have at least 4GB of VRAM and 10GB of RAM or enable cpu mode: --use-cpu all --no-half"
+ printf "\n%s\n" "${delimiter}"
;;
*)
;;
From fd651bd0bcceb4c746c86b202702bca029cbd6db Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 20 Jan 2023 00:21:51 +0100
Subject: [PATCH 090/127] Update webui.sh
---
webui.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/webui.sh b/webui.sh
index 4da51880..d5e7b3c5 100755
--- a/webui.sh
+++ b/webui.sh
@@ -110,8 +110,8 @@ case "$gpu_info" in
;;
*"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0
printf "\n%s\n" "${delimiter}"
- printf "Make sure to have at least 4GB of VRAM and 10GB of RAM or enable cpu mode: --use-cpu all --no-half"
- printf "\n%s\n" "${delimiter}"
+ printf "Make sure to have at least 4GB of VRAM and 10GB of RAM or enable cpu mode: --use-cpu all --no-half"
+ printf "\n%s\n" "${delimiter}"
;;
*)
;;
From 6c7a50d783c4e406d8597f9cf354bb8128026f6c Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Fri, 20 Jan 2023 08:36:30 +0300
Subject: [PATCH 091/127] remove some unnecessary logging to javascript console
---
javascript/hires_fix.js | 3 ---
modules/ui.py | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/javascript/hires_fix.js b/javascript/hires_fix.js
index 07fba549..0629475f 100644
--- a/javascript/hires_fix.js
+++ b/javascript/hires_fix.js
@@ -1,6 +1,5 @@
function setInactive(elem, inactive){
- console.log(elem)
if(inactive){
elem.classList.add('inactive')
} else{
@@ -9,8 +8,6 @@ function setInactive(elem, inactive){
}
function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y){
- console.log(enable, width, height, hr_scale, hr_resize_x, hr_resize_y)
-
hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale')
hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x')
hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y')
diff --git a/modules/ui.py b/modules/ui.py
index 13d80ae2..eb45a128 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -532,7 +532,7 @@ Requested path was: {f}
generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
generation_info_button.click(
fn=update_generation_info,
- _js="function(x, y, z){ console.log(x, y, z); return [x, y, selected_gallery_index()] }",
+ _js="function(x, y, z){ return [x, y, selected_gallery_index()] }",
inputs=[generation_info, html_info, html_info],
outputs=[html_info, html_info],
)
From 98466da4bc312c0fa9c8cea4c825afc64194cb58 Mon Sep 17 00:00:00 2001
From: EllangoK
Date: Fri, 20 Jan 2023 00:48:15 -0500
Subject: [PATCH 092/127] adds descriptions for merging methods in ui
---
modules/ui.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/modules/ui.py b/modules/ui.py
index eb45a128..ee434bde 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1190,10 +1190,19 @@ def create_ui():
outputs=[html, generation_info, html2],
)
+ def update_interp_description(value):
+ interp_description_css = "{}
"
+ interp_descriptions = {
+ "No interpolation": interp_description_css.format("No interpolation will be used. Requires one model; A. Allows for format conversion and VAE baking."),
+ "Weighted sum": interp_description_css.format("A weighted sum will be used for interpolation. Requires two models; A and B. The result is calculated as A * (1 - M) + B * M"),
+ "Add difference": interp_description_css.format("The difference between the last two models will be added to the first. Requires three models; A, B and C. The result is calculated as A + (B - C) * M")
+ }
+ return interp_descriptions[value]
+
with gr.Blocks(analytics_enabled=False) as modelmerger_interface:
with gr.Row().style(equal_height=False):
with gr.Column(variant='compact'):
- gr.HTML(value="A merger of the two checkpoints will be generated in your checkpoint directory.
")
+ interp_description = gr.HTML(value=update_interp_description("Weighted sum"), elem_id="modelmerger_interp_description")
with FormRow(elem_id="modelmerger_models"):
primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)")
@@ -1208,6 +1217,7 @@ def create_ui():
custom_name = gr.Textbox(label="Custom Name (Optional)", elem_id="modelmerger_custom_name")
interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Multiplier (M) - set to 0 to get model A', value=0.3, elem_id="modelmerger_interp_amount")
interp_method = gr.Radio(choices=["No interpolation", "Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method")
+ interp_method.change(fn=update_interp_description, inputs=[interp_method], outputs=[interp_description])
with FormRow():
checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="ckpt", label="Checkpoint format", elem_id="modelmerger_checkpoint_format")
@@ -1903,6 +1913,9 @@ def create_ui():
with open(ui_config_file, "w", encoding="utf8") as file:
json.dump(ui_settings, file, indent=4)
+ # Required as a workaround for change() event not triggering when loading values from ui-config.json
+ interp_description.value = update_interp_description(interp_method.value)
+
return demo
From 20a59ab3b171f398abd09087108c1ed087dbea9b Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Fri, 20 Jan 2023 10:18:41 +0300
Subject: [PATCH 093/127] move token counter to the location of the prompt, add
token counting for the negative prompt
---
.../javascript/prompt-bracket-checker.js | 45 +++++++++---------
javascript/ui.js | 29 ++++++++----
modules/ui.py | 25 +++++-----
style.css | 47 ++++++++++++-------
4 files changed, 87 insertions(+), 59 deletions(-)
diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
index 251a1f57..4a85c8eb 100644
--- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
+++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js
@@ -4,16 +4,10 @@
// Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs.
// If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong.
-function checkBrackets(evt) {
- textArea = evt.target;
- tabName = evt.target.parentElement.parentElement.id.split("_")[0];
- counterElt = document.querySelector('gradio-app').shadowRoot.querySelector('#' + tabName + '_token_counter');
-
- promptName = evt.target.parentElement.parentElement.id.includes('neg') ? ' negative' : '';
-
- errorStringParen = '(' + tabName + promptName + ' prompt) - Different number of opening and closing parentheses detected.\n';
- errorStringSquare = '[' + tabName + promptName + ' prompt] - Different number of opening and closing square brackets detected.\n';
- errorStringCurly = '{' + tabName + promptName + ' prompt} - Different number of opening and closing curly brackets detected.\n';
+function checkBrackets(evt, textArea, counterElt) {
+ errorStringParen = '(...) - Different number of opening and closing parentheses detected.\n';
+ errorStringSquare = '[...] - Different number of opening and closing square brackets detected.\n';
+ errorStringCurly = '{...} - Different number of opening and closing curly brackets detected.\n';
openBracketRegExp = /\(/g;
closeBracketRegExp = /\)/g;
@@ -86,24 +80,31 @@ function checkBrackets(evt) {
}
if(counterElt.title != '') {
- counterElt.style = 'color: #FF5555;';
+ counterElt.classList.add('error');
} else {
- counterElt.style = '';
+ counterElt.classList.remove('error');
}
}
+function setupBracketChecking(id_prompt, id_counter){
+ var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea");
+ var counter = gradioApp().getElementById(id_counter)
+ textarea.addEventListener("input", function(evt){
+ checkBrackets(evt, textarea, counter)
+ });
+}
+
var shadowRootLoaded = setInterval(function() {
- var sahdowRoot = document.querySelector('gradio-app').shadowRoot;
- if(! sahdowRoot) return false;
+ var shadowRoot = document.querySelector('gradio-app').shadowRoot;
+ if(! shadowRoot) return false;
- var shadowTextArea = sahdowRoot.querySelectorAll('#txt2img_prompt > label > textarea');
- if(shadowTextArea.length < 1) return false;
+ var shadowTextArea = shadowRoot.querySelectorAll('#txt2img_prompt > label > textarea');
+ if(shadowTextArea.length < 1) return false;
+ clearInterval(shadowRootLoaded);
- clearInterval(shadowRootLoaded);
-
- document.querySelector('gradio-app').shadowRoot.querySelector('#txt2img_prompt').onkeyup = checkBrackets;
- document.querySelector('gradio-app').shadowRoot.querySelector('#txt2img_neg_prompt').onkeyup = checkBrackets;
- document.querySelector('gradio-app').shadowRoot.querySelector('#img2img_prompt').onkeyup = checkBrackets;
- document.querySelector('gradio-app').shadowRoot.querySelector('#img2img_neg_prompt').onkeyup = checkBrackets;
+ setupBracketChecking('txt2img_prompt', 'txt2img_token_counter')
+ setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter')
+ setupBracketChecking('img2img_prompt', 'imgimg_token_counter')
+ setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter')
}, 1000);
diff --git a/javascript/ui.js b/javascript/ui.js
index 37788a3e..3ba90ca8 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -230,14 +230,26 @@ onUiUpdate(function(){
json_elem.parentElement.style.display="none"
- if (!txt2img_textarea) {
- txt2img_textarea = gradioApp().querySelector("#txt2img_prompt > label > textarea");
- txt2img_textarea?.addEventListener("input", () => update_token_counter("txt2img_token_button"));
- }
- if (!img2img_textarea) {
- img2img_textarea = gradioApp().querySelector("#img2img_prompt > label > textarea");
- img2img_textarea?.addEventListener("input", () => update_token_counter("img2img_token_button"));
- }
+ function registerTextarea(id, id_counter, id_button){
+ var prompt = gradioApp().getElementById(id)
+ var counter = gradioApp().getElementById(id_counter)
+ var textarea = gradioApp().querySelector("#" + id + " > label > textarea");
+
+ if(counter.parentElement == prompt.parentElement){
+ return
+ }
+
+ prompt.parentElement.insertBefore(counter, prompt)
+ counter.classList.add("token-counter")
+ prompt.parentElement.style.position = "relative"
+
+ textarea.addEventListener("input", () => update_token_counter(id_button));
+ }
+
+ registerTextarea('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button')
+ registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button')
+ registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button')
+ registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button')
show_all_pages = gradioApp().getElementById('settings_show_all_pages')
settings_tabs = gradioApp().querySelector('#settings div')
@@ -249,6 +261,7 @@ onUiUpdate(function(){
})
}
}
+
})
diff --git a/modules/ui.py b/modules/ui.py
index eb45a128..06c11848 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -335,28 +335,23 @@ def update_token_counter(text, steps):
flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules)
prompts = [prompt_text for step, prompt_text in flat_prompts]
token_count, max_length = max([model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0])
- style_class = ' class="red"' if (token_count > max_length) else ""
- return f"{token_count}/{max_length}"
+ return f"{token_count}/{max_length}"
def create_toprow(is_img2img):
id_part = "img2img" if is_img2img else "txt2img"
- with gr.Row(elem_id="toprow"):
- with gr.Column(scale=6):
+ with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"):
+ with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6):
with gr.Row():
with gr.Column(scale=80):
with gr.Row():
- prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=2,
- placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)"
- )
+ prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=2, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)")
with gr.Row():
with gr.Column(scale=80):
with gr.Row():
- negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=2,
- placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)"
- )
+ negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=2, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)")
with gr.Column(scale=1, elem_id="roll_col"):
paste = gr.Button(value=paste_symbol, elem_id="paste")
@@ -365,6 +360,8 @@ def create_toprow(is_img2img):
clear_prompt_button = gr.Button(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt")
token_counter = gr.HTML(value="", elem_id=f"{id_part}_token_counter")
token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button")
+ negative_token_counter = gr.HTML(value="", elem_id=f"{id_part}_negative_token_counter")
+ negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button")
clear_prompt_button.click(
fn=lambda *x: x,
@@ -402,7 +399,7 @@ def create_toprow(is_img2img):
prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in shared.prompt_styles.styles.items()], value=[], multiselect=True)
create_refresh_button(prompt_styles, shared.prompt_styles.reload, lambda: {"choices": [k for k, v in shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles")
- return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button
+ return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, token_counter, token_button, negative_token_counter, negative_token_button
def setup_progressbar(*args, **kwargs):
@@ -619,7 +616,7 @@ def create_ui():
modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False)
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
- txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _,txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button = create_toprow(is_img2img=False)
+ txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=False)
dummy_component = gr.Label(visible=False)
txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False)
@@ -795,12 +792,13 @@ def create_ui():
]
token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[token_counter])
+ negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter])
modules.scripts.scripts_current = modules.scripts.scripts_img2img
modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True)
with gr.Blocks(analytics_enabled=False) as img2img_interface:
- img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste,token_counter, token_button = create_toprow(is_img2img=True)
+ img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=True)
img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False)
@@ -1064,6 +1062,7 @@ def create_ui():
)
token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter])
+ negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter])
img2img_paste_fields = [
(img2img_prompt, "Prompt"),
diff --git a/style.css b/style.css
index c10e32a1..994932de 100644
--- a/style.css
+++ b/style.css
@@ -2,12 +2,26 @@
max-width: 100%;
}
-#txt2img_token_counter {
- height: 0px;
+.token-counter{
+ position: absolute;
+ display: inline-block;
+ right: 2em;
+ min-width: 0 !important;
+ width: auto;
+ z-index: 100;
}
-#img2img_token_counter {
- height: 0px;
+.token-counter.error span{
+ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075);
+ border: 2px solid rgba(255,0,0,0.4) !important;
+}
+
+.token-counter div{
+ display: inline;
+}
+
+.token-counter span{
+ padding: 0.1em 0.75em;
}
#sh{
@@ -113,7 +127,7 @@
#roll_col{
min-width: unset !important;
flex-grow: 0 !important;
- padding: 0.4em 0;
+ padding: 0 1em 0 0;
gap: 0;
}
@@ -160,16 +174,6 @@
margin-bottom: 0;
}
-#toprow div.gr-box, #toprow div.gr-form{
- border: none;
- gap: 0;
- background: transparent;
- box-shadow: none;
-}
-#toprow div{
- gap: 0;
-}
-
#resize_mode{
flex: 1.5;
}
@@ -706,6 +710,14 @@ footer {
opacity: 0.5;
}
+[id*='_prompt_container']{
+ gap: 0;
+}
+
+[id*='_prompt_container'] > div{
+ margin: -0.4em 0 0 0;
+}
+
.gr-compact {
border: none;
}
@@ -715,8 +727,11 @@ footer {
margin-left: 0.8em;
}
+.gr-compact{
+ overflow: visible;
+}
+
.gr-compact > *{
- margin-top: 0.5em !important;
}
.gr-compact .gr-block, .gr-compact .gr-form{
From 7d3fb5cb03cc8520a32b4f56509f2e13e36911bd Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Fri, 20 Jan 2023 12:12:02 +0300
Subject: [PATCH 094/127] add margin to interrogate column in img2img UI
---
style.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/style.css b/style.css
index 994932de..3a515ebd 100644
--- a/style.css
+++ b/style.css
@@ -145,6 +145,7 @@
#interrogate_col{
min-width: 0 !important;
max-width: 8em !important;
+ margin-right: 1em;
}
#interrogate, #deepbooru{
margin: 0em 0.25em 0.9em 0.25em;
From e33cace2c2074ef342d027c1f31ffc4b3c3e877e Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Fri, 20 Jan 2023 12:19:30 +0300
Subject: [PATCH 095/127] fix ctrl+up/down that stopped working
---
javascript/edit-attention.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js
index ccc8344a..cec6a530 100644
--- a/javascript/edit-attention.js
+++ b/javascript/edit-attention.js
@@ -1,6 +1,6 @@
addEventListener('keydown', (event) => {
let target = event.originalTarget || event.composedPath()[0];
- if (!target.matches("#toprow textarea.gr-text-input[placeholder]")) return;
+ if (!target.matches("[id*='_toprow'] textarea.gr-text-input[placeholder]")) return;
if (! (event.metaKey || event.ctrlKey)) return;
From e0b6092bc99efe311261a51289dec67cbf4845bc Mon Sep 17 00:00:00 2001
From: DaniAndTheWeb <57776841+DaniAndTheWeb@users.noreply.github.com>
Date: Fri, 20 Jan 2023 15:31:27 +0100
Subject: [PATCH 096/127] Update webui.sh
---
webui.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webui.sh b/webui.sh
index d5e7b3c5..8cdad22d 100755
--- a/webui.sh
+++ b/webui.sh
@@ -110,7 +110,7 @@ case "$gpu_info" in
;;
*"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0
printf "\n%s\n" "${delimiter}"
- printf "Make sure to have at least 4GB of VRAM and 10GB of RAM or enable cpu mode: --use-cpu all --no-half"
+ printf "Experimental support for Renoir: make sure to have at least 4GB of VRAM and 10GB of RAM or enable cpu mode: --use-cpu all --no-half"
printf "\n%s\n" "${delimiter}"
;;
*)
From 40ff6db5325fc34ad4fa35e80cb1e7768d9f7e75 Mon Sep 17 00:00:00 2001
From: AUTOMATIC <16777216c@gmail.com>
Date: Sat, 21 Jan 2023 08:36:07 +0300
Subject: [PATCH 097/127] extra networks UI rework of hypernets: rather than
via settings, hypernets are added directly to prompt as
---
html/card-no-preview.png | Bin 0 -> 84440 bytes
html/extra-networks-card.html | 11 +
html/extra-networks-no-cards.html | 8 +
javascript/extraNetworks.js | 60 ++++++
javascript/hints.js | 2 +
javascript/ui.js | 9 +-
modules/api/api.py | 7 +-
modules/extra_networks.py | 147 +++++++++++++
modules/extra_networks_hypernet.py | 21 ++
modules/generation_parameters_copypaste.py | 12 +-
modules/hypernetworks/hypernetwork.py | 107 +++++++---
modules/hypernetworks/ui.py | 5 +-
modules/processing.py | 24 ++-
modules/sd_hijack_optimizations.py | 10 +-
modules/shared.py | 21 +-
.../textual_inversion/textual_inversion.py | 2 +
modules/ui.py | 50 +++--
modules/ui_components.py | 10 +
modules/ui_extra_networks.py | 149 ++++++++++++++
modules/ui_extra_networks_hypernets.py | 34 +++
.../ui_extra_networks_textual_inversion.py | 32 +++
script.js | 13 +-
scripts/xy_grid.py | 29 ---
style.css | 194 ++++++++++--------
webui.py | 26 ++-
25 files changed, 767 insertions(+), 216 deletions(-)
create mode 100644 html/card-no-preview.png
create mode 100644 html/extra-networks-card.html
create mode 100644 html/extra-networks-no-cards.html
create mode 100644 javascript/extraNetworks.js
create mode 100644 modules/extra_networks.py
create mode 100644 modules/extra_networks_hypernet.py
create mode 100644 modules/ui_extra_networks.py
create mode 100644 modules/ui_extra_networks_hypernets.py
create mode 100644 modules/ui_extra_networks_textual_inversion.py
diff --git a/html/card-no-preview.png b/html/card-no-preview.png
new file mode 100644
index 0000000000000000000000000000000000000000..e2beb2692067db56ac5f7bd5bfc3d895d9063371
GIT binary patch
literal 84440
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