From a1825ee741bb21b35561d58db8cb316d7f5d0c79 Mon Sep 17 00:00:00 2001 From: Splendide Imaginarius <119545140+Splendide-Imaginarius@users.noreply.github.com> Date: Thu, 3 Aug 2023 02:03:35 +0000 Subject: [PATCH 01/67] Make StableDiffusionProcessingImg2Img.mask_blur a property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes breakage when mask_blur is set after construction. See https://github.com/Coyote-A/ultimate-upscale-for-automatic1111/issues/111#issuecomment-1652091424 Thanks to Алексей Трофимов and eunnone for reporting the issue. --- modules/processing.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) mode change 100644 => 100755 modules/processing.py diff --git a/modules/processing.py b/modules/processing.py old mode 100644 new mode 100755 index b0992ee15..44d20fb74 --- a/modules/processing.py +++ b/modules/processing.py @@ -1232,11 +1232,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.image_mask = mask self.latent_mask = None self.mask_for_overlay = None - if mask_blur is not None: - mask_blur_x = mask_blur - mask_blur_y = mask_blur self.mask_blur_x = mask_blur_x self.mask_blur_y = mask_blur_y + if mask_blur is not None: + self.mask_blur = mask_blur self.inpainting_fill = inpainting_fill self.inpaint_full_res = inpaint_full_res self.inpaint_full_res_padding = inpaint_full_res_padding @@ -1246,6 +1245,22 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.nmask = None self.image_conditioning = None + @property + def mask_blur(self): + if self.mask_blur_x == self.mask_blur_y: + return self.mask_blur_x + return None + + @mask_blur.setter + def mask_blur(self, value): + self.mask_blur_x = value + self.mask_blur_y = value + + @mask_blur.deleter + def mask_blur(self): + del self.mask_blur_x + del self.mask_blur_y + def init(self, all_prompts, all_seeds, all_subseeds): self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) crop_region = None From 56888644a67298253260eda84ceb2d6cd0ce5099 Mon Sep 17 00:00:00 2001 From: Splendide Imaginarius <119545140+Splendide-Imaginarius@users.noreply.github.com> Date: Sat, 5 Aug 2023 04:54:23 +0000 Subject: [PATCH 02/67] Reduce mask blur kernel size to 2.5 sigmas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This more closely matches the old behavior of PIL's Gaussian blur, and fixes breakage when tiling. See https://github.com/Coyote-A/ultimate-upscale-for-automatic1111/issues/111#issuecomment-1663504109 Thanks to Алексей Трофимов and eunnone for reporting the issue. --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 44d20fb74..63cd025ca 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -1275,13 +1275,13 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if self.mask_blur_x > 0: np_mask = np.array(image_mask) - kernel_size = 2 * int(4 * self.mask_blur_x + 0.5) + 1 + kernel_size = 2 * int(2.5 * self.mask_blur_x + 0.5) + 1 np_mask = cv2.GaussianBlur(np_mask, (kernel_size, 1), self.mask_blur_x) image_mask = Image.fromarray(np_mask) if self.mask_blur_y > 0: np_mask = np.array(image_mask) - kernel_size = 2 * int(4 * self.mask_blur_y + 0.5) + 1 + kernel_size = 2 * int(2.5 * self.mask_blur_y + 0.5) + 1 np_mask = cv2.GaussianBlur(np_mask, (1, kernel_size), self.mask_blur_y) image_mask = Image.fromarray(np_mask) From ed01d2ee3b22f7d01f218e3b7a3571dc7a2c54c0 Mon Sep 17 00:00:00 2001 From: Danil Boldyrev Date: Thu, 10 Aug 2023 13:45:25 +0300 Subject: [PATCH 03/67] a another fix, a different approach --- .../canvas-zoom-and-pan/javascript/zoom.js | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index e7616b981..30a748348 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -12,6 +12,7 @@ onUiLoaded(async() => { "Sketch": elementIDs.sketch }; + // Helper functions // Get active tab function getActiveTab(elements, all = false) { @@ -657,17 +658,20 @@ onUiLoaded(async() => { // Simulation of the function to put a long image into the screen. // We detect if an image has a scroll bar or not, make a fullscreen to reveal the image, then reduce it to fit into the element. // We hide the image and show it to the user when it is ready. - function autoExpand(e) { + + targetElement.isExpanded = false; + function autoExpand() { const canvas = document.querySelector(`${elemId} canvas[key="interface"]`); const isMainTab = activeElement === elementIDs.inpaint || activeElement === elementIDs.inpaintSketch || activeElement === elementIDs.sketch; if (canvas && isMainTab) { - if (hasHorizontalScrollbar(targetElement)) { + if (hasHorizontalScrollbar(targetElement) && targetElement.isExpanded === false) { targetElement.style.visibility = "hidden"; setTimeout(() => { fitToScreen(); resetZoom(); targetElement.style.visibility = "visible"; + targetElement.isExpanded = true; }, 10); } } @@ -675,9 +679,24 @@ onUiLoaded(async() => { targetElement.addEventListener("mousemove", getMousePosition); + //observers + // Creating an observer with a callback function to handle DOM changes + const observer = new MutationObserver((mutationsList, observer) => { + for (let mutation of mutationsList) { + // If the style attribute of the canvas has changed, by observation it happens only when the picture changes + if (mutation.type === 'attributes' && mutation.attributeName === 'style' && + mutation.target.tagName.toLowerCase() === 'canvas') { + targetElement.isExpanded = false; + setTimeout(resetZoom, 10); + } + } + }); + // Apply auto expand if enabled if (hotkeysConfig.canvas_auto_expand) { targetElement.addEventListener("mousemove", autoExpand); + // Set up an observer to track attribute changes + observer.observe(targetElement, {attributes: true, childList: true, subtree: true}); } // Handle events only inside the targetElement From 045f7408926aec4bf7e8fcb09333a3f45783d239 Mon Sep 17 00:00:00 2001 From: Danil Boldyrev Date: Thu, 10 Aug 2023 16:17:52 +0300 Subject: [PATCH 04/67] Height fix --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 30a748348..72c8ba879 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -378,6 +378,11 @@ onUiLoaded(async() => { toggleOverlap("off"); fullScreenMode = false; + const closeBtn = targetElement.querySelector("button[aria-label='Remove Image']"); + if (closeBtn) { + closeBtn.addEventListener("click", resetZoom); + } + if ( canvas && parseFloat(canvas.style.width) > 865 && From a75d756a6fc3a9d66d3c1601d5b8aafcbcd57bde Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:43:55 +0900 Subject: [PATCH 05/67] use default value if value error --- modules/ui_loadsave.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py index a96c71b29..9a40cf4fc 100644 --- a/modules/ui_loadsave.py +++ b/modules/ui_loadsave.py @@ -48,13 +48,13 @@ class UiLoadsave: elif condition and not condition(saved_value): pass else: - if isinstance(x, gr.Textbox) and field == 'value': # due to an undersirable behavior of gr.Textbox, if you give it an int value instead of str, everything dies + if isinstance(x, gr.Textbox) and field == 'value': # due to an undesirable behavior of gr.Textbox, if you give it an int value instead of str, everything dies saved_value = str(saved_value) elif isinstance(x, gr.Number) and field == 'value': try: saved_value = float(saved_value) except ValueError: - saved_value = -1 + return setattr(obj, field, saved_value) if init_field is not None: From d456fb797ad9e1f6daddbdaf284ae34cfb2a0656 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:04:49 -0400 Subject: [PATCH 06/67] fix: Properly return None when VAE hash is None --- modules/sd_vae.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 1db01992d..fd9a1c2a1 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -31,7 +31,9 @@ def get_loaded_vae_hash(): if loaded_vae_file is None: return None - return hashes.sha256(loaded_vae_file, 'vae')[0:10] + sha256 = hashes.sha256(loaded_vae_file, 'vae') + + return sha256[0:10] if sha256 else None def get_base_vae(model): From 4fafc34e498130dcbb2d1a44fbc55fdba31e32d4 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:42:58 -0400 Subject: [PATCH 07/67] Fix to make LoRA old method setting work --- extensions-builtin/Lora/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index bc722e90c..7e3415acb 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -357,7 +357,7 @@ def network_forward(module, input, original_forward): if module is None: continue - y = module.forward(y, input) + y = module.forward(input, y) return y From 77c52ea701bef8d436dd1f05253412807ddff42c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 10 Aug 2023 18:43:27 +0300 Subject: [PATCH 08/67] fix accordion style on img2img --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 5163e53c2..4cdce87cf 100644 --- a/style.css +++ b/style.css @@ -192,7 +192,7 @@ button.custom-button{ text-align: center; } -div.gradio-accordion { +div.block.gradio-accordion { border: 1px solid var(--block-border-color) !important; border-radius: 8px !important; margin: 2px 0; From 7c9c19b2a23d28d22694320e2db8f1fc83971c5e Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:32:12 -0400 Subject: [PATCH 09/67] Refactor postprocessing to use generator to resolve OOM issues --- modules/postprocessing.py | 61 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 136e9c887..cf04d38b0 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -11,37 +11,32 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, shared.state.begin(job="extras") - image_data = [] - image_names = [] outputs = [] - if extras_mode == 1: - for img in image_folder: - if isinstance(img, Image.Image): - image = img - fn = '' - else: - image = Image.open(os.path.abspath(img.name)) - fn = os.path.splitext(img.orig_name)[0] - image_data.append(image) - image_names.append(fn) - elif extras_mode == 2: - assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' - assert input_dir, 'input directory not selected' + def get_images(extras_mode, image, image_folder, input_dir): + if extras_mode == 1: + for img in image_folder: + if isinstance(img, Image.Image): + image = img + fn = '' + else: + image = Image.open(os.path.abspath(img.name)) + fn = os.path.splitext(img.orig_name)[0] + yield image, fn + elif extras_mode == 2: + assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' + assert input_dir, 'input directory not selected' - image_list = shared.listfiles(input_dir) - for filename in image_list: - try: - image = Image.open(filename) - except Exception: - continue - image_data.append(image) - image_names.append(filename) - else: - assert image, 'image not selected' - - image_data.append(image) - image_names.append(None) + image_list = shared.listfiles(input_dir) + for filename in image_list: + try: + image = Image.open(filename) + except Exception: + continue + yield image, filename + else: + assert image, 'image not selected' + yield image, None if extras_mode == 2 and output_dir != '': outpath = output_dir @@ -50,14 +45,16 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, infotext = '' - for image, name in zip(image_data, image_names): + for image_data, name in get_images(extras_mode, image, image_folder, input_dir): + image_data: Image.Image + shared.state.textinfo = name - parameters, existing_pnginfo = images.read_info_from_image(image) + parameters, existing_pnginfo = images.read_info_from_image(image_data) if parameters: existing_pnginfo["parameters"] = parameters - pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB")) + pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGB")) scripts.scripts_postproc.run(pp, args) @@ -78,6 +75,8 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, if extras_mode != 2 or show_extras_results: outputs.append(pp.image) + image_data.close() + devices.torch_gc() return outputs, ui_common.plaintext_to_html(infotext), '' From af27b716e53671c52308d4e101214b0fd4fd5e80 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:22:11 -0400 Subject: [PATCH 10/67] Fix color correction by converting image to RGB --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index c048ca25c..131c4c3c2 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -57,7 +57,7 @@ def apply_color_correction(correction, original_image): image = blendLayers(image, original_image, BlendType.LUMINOSITY) - return image + return image.convert('RGB') def apply_overlay(image, paste_loc, index, overlays): From f57bc1a21ba3979061752bd7b66e881bd25cc64f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 12:06:31 +0900 Subject: [PATCH 11/67] disable extensions installer with arg --- modules/launch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 2782872e0..65eb684ff 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -243,7 +243,7 @@ def list_extensions(settings_file): disabled_extensions = set(settings.get('disabled_extensions', [])) disable_all_extensions = settings.get('disable_all_extensions', 'none') - if disable_all_extensions != 'none': + if disable_all_extensions != 'none' or args.disable_extra_extensions or args.disable_all_extensions: return [] return [x for x in os.listdir(extensions_dir) if x not in disabled_extensions] From 64311faa6848d641cc452115e4e1eb47d2a7b519 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 12:39:59 +0300 Subject: [PATCH 12/67] put refiner into main UI, into the new accordions section add VAE from main model into infotext, not from refiner model option to make scripts UI without gr.Group fix inconsistencies with refiner when usings samplers that do more denoising than steps --- modules/processing.py | 22 ++++++---- modules/processing_scripts/refiner.py | 55 +++++++++++++++++++++++++ modules/scripts.py | 24 +++++++---- modules/sd_models.py | 3 ++ modules/sd_samplers_cfg_denoiser.py | 6 ++- modules/sd_samplers_common.py | 40 ++++++++++-------- modules/sd_samplers_kdiffusion.py | 3 +- modules/shared_items.py | 4 +- modules/shared_options.py | 2 - modules/ui.py | 58 +++++++++++++++------------ modules/ui_components.py | 18 +++++++-- style.css | 32 +++++++++------ 12 files changed, 188 insertions(+), 79 deletions(-) create mode 100644 modules/processing_scripts/refiner.py diff --git a/modules/processing.py b/modules/processing.py index 131c4c3c2..5996cbac1 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -373,9 +373,10 @@ class StableDiffusionProcessing: negative_prompts = prompt_parser.SdConditioning(self.negative_prompts, width=self.width, height=self.height, is_negative_prompt=True) sampler_config = sd_samplers.find_sampler_config(self.sampler_name) - self.step_multiplier = 2 if sampler_config and sampler_config.options.get("second_order", False) else 1 - self.uc = self.get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, self.steps * self.step_multiplier, [self.cached_uc], self.extra_network_data) - self.c = self.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, self.steps * self.step_multiplier, [self.cached_c], self.extra_network_data) + total_steps = sampler_config.total_steps(self.steps) if sampler_config else self.steps + self.step_multiplier = total_steps // self.steps + self.uc = self.get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, total_steps, [self.cached_uc], self.extra_network_data) + self.c = self.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, total_steps, [self.cached_c], self.extra_network_data) def get_conds(self): return self.c, self.uc @@ -579,8 +580,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Size": f"{p.width}x{p.height}", "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), "Model": (None if not opts.add_model_name_to_info else shared.sd_model.sd_checkpoint_info.name_for_extra), - "VAE hash": sd_vae.get_loaded_vae_hash() if opts.add_model_hash_to_info else None, - "VAE": sd_vae.get_loaded_vae_name() if opts.add_model_name_to_info else None, + "VAE hash": p.loaded_vae_hash if opts.add_model_hash_to_info else None, + "VAE": p.loaded_vae_name if opts.add_model_name_to_info else None, "Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])), "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength), "Seed resize from": (None if p.seed_resize_from_w <= 0 or p.seed_resize_from_h <= 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), @@ -669,6 +670,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.tiling is None: p.tiling = opts.tiling + p.loaded_vae_name = sd_vae.get_loaded_vae_name() + p.loaded_vae_hash = sd_vae.get_loaded_vae_hash() + modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() @@ -1188,8 +1192,12 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): hr_prompts = prompt_parser.SdConditioning(self.hr_prompts, width=self.hr_upscale_to_x, height=self.hr_upscale_to_y) hr_negative_prompts = prompt_parser.SdConditioning(self.hr_negative_prompts, width=self.hr_upscale_to_x, height=self.hr_upscale_to_y, is_negative_prompt=True) - self.hr_uc = self.get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, self.steps * self.step_multiplier, [self.cached_hr_uc, self.cached_uc], self.hr_extra_network_data) - self.hr_c = self.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, self.steps * self.step_multiplier, [self.cached_hr_c, self.cached_c], self.hr_extra_network_data) + sampler_config = sd_samplers.find_sampler_config(self.hr_sampler_name or self.sampler_name) + steps = self.hr_second_pass_steps or self.steps + total_steps = sampler_config.total_steps(steps) if sampler_config else steps + + self.hr_uc = self.get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, total_steps, [self.cached_hr_uc, self.cached_uc], self.hr_extra_network_data) + self.hr_c = self.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, total_steps, [self.cached_hr_c, self.cached_c], self.hr_extra_network_data) def setup_conds(self): super().setup_conds() diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py new file mode 100644 index 000000000..5a82991ab --- /dev/null +++ b/modules/processing_scripts/refiner.py @@ -0,0 +1,55 @@ +import gradio as gr + +from modules import scripts, sd_models +from modules.ui_common import create_refresh_button +from modules.ui_components import InputAccordion + + +class ScriptRefiner(scripts.Script): + section = "accordions" + create_group = False + + def __init__(self): + pass + + def title(self): + return "Refiner" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with InputAccordion(False, label="Refiner", elem_id=self.elem_id("enable")) as enable_refiner: + with gr.Row(): + refiner_checkpoint = gr.Dropdown(label='Checkpoint', elem_id=self.elem_id("checkpoint"), choices=sd_models.checkpoint_tiles(), value='', tooltip="switch to another model in the middle of generation") + create_refresh_button(refiner_checkpoint, sd_models.list_models, lambda: {"choices": sd_models.checkpoint_tiles()}, self.elem_id("checkpoint_refresh")) + + refiner_switch_at = gr.Slider(value=0.8, label="Switch at", minimum=0.01, maximum=1.0, step=0.01, elem_id=self.elem_id("switch_at"), tooltip="fraction of sampling steps when the swtch to refiner model should happen; 1=never, 0.5=switch in the middle of generation") + + def lookup_checkpoint(title): + info = sd_models.get_closet_checkpoint_match(title) + return None if info is None else info.title + + self.infotext_fields = [ + (enable_refiner, lambda d: 'Refiner' in d), + (refiner_checkpoint, lambda d: lookup_checkpoint(d.get('Refiner'))), + (refiner_switch_at, 'Refiner switch at'), + ] + + return enable_refiner, refiner_checkpoint, refiner_switch_at + + def before_process(self, p, enable_refiner, refiner_checkpoint, refiner_switch_at): + # the actual implementation is in sd_samplers_common.py, apply_refiner + + p.refiner_checkpoint_info = None + p.refiner_switch_at = None + + if not enable_refiner or refiner_checkpoint in (None, "", "None"): + return + + refiner_checkpoint_info = sd_models.get_closet_checkpoint_match(refiner_checkpoint) + if refiner_checkpoint_info is None: + raise Exception(f'Could not find checkpoint with name {refiner_checkpoint}') + + p.refiner_checkpoint_info = refiner_checkpoint_info + p.refiner_switch_at = refiner_switch_at diff --git a/modules/scripts.py b/modules/scripts.py index f7d060aa5..51da732a6 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -37,7 +37,10 @@ class Script: is_img2img = False group = None - """A gr.Group component that has all script's UI inside it""" + """A gr.Group component that has all script's UI inside it.""" + + create_group = True + """If False, for alwayson scripts, a group component will not be created.""" infotext_fields = None """if set in ui(), this is a list of pairs of gradio component + text; the text will be used when @@ -232,6 +235,7 @@ class Script: """ pass + current_basedir = paths.script_path @@ -250,7 +254,7 @@ postprocessing_scripts_data = [] ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"]) -def list_scripts(scriptdirname, extension): +def list_scripts(scriptdirname, extension, *, include_extensions=True): scripts_list = [] basedir = os.path.join(paths.script_path, scriptdirname) @@ -258,8 +262,9 @@ def list_scripts(scriptdirname, extension): for filename in sorted(os.listdir(basedir)): scripts_list.append(ScriptFile(paths.script_path, filename, os.path.join(basedir, filename))) - for ext in extensions.active(): - scripts_list += ext.list_files(scriptdirname, extension) + if include_extensions: + for ext in extensions.active(): + scripts_list += ext.list_files(scriptdirname, extension) scripts_list = [x for x in scripts_list if os.path.splitext(x.path)[1].lower() == extension and os.path.isfile(x.path)] @@ -288,7 +293,7 @@ def load_scripts(): postprocessing_scripts_data.clear() script_callbacks.clear_callbacks() - scripts_list = list_scripts("scripts", ".py") + scripts_list = list_scripts("scripts", ".py") + list_scripts("modules/processing_scripts", ".py", include_extensions=False) syspath = sys.path @@ -429,10 +434,13 @@ class ScriptRunner: if script.alwayson and script.section != section: continue - with gr.Group(visible=script.alwayson) as group: - self.create_script_ui(script) + if script.create_group: + with gr.Group(visible=script.alwayson) as group: + self.create_script_ui(script) - script.group = group + script.group = group + else: + self.create_script_ui(script) def prepare_ui(self): self.inputs = [None] diff --git a/modules/sd_models.py b/modules/sd_models.py index a178adcac..f6fbdcd60 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -147,6 +147,9 @@ re_strip_checksum = re.compile(r"\s*\[[^]]+]\s*$") def get_closet_checkpoint_match(search_string): + if not search_string: + return None + checkpoint_info = checkpoint_aliases.get(search_string, None) if checkpoint_info is not None: return checkpoint_info diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index a532e0137..113425b2a 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -45,6 +45,11 @@ class CFGDenoiser(torch.nn.Module): self.nmask = None self.init_latent = None self.steps = None + """number of steps as specified by user in UI""" + + self.total_steps = None + """expected number of calls to denoiser calculated from self.steps and specifics of the selected sampler""" + self.step = 0 self.image_cfg_scale = None self.padded_cond_uncond = False @@ -56,7 +61,6 @@ class CFGDenoiser(torch.nn.Module): def inner_model(self): raise NotImplementedError() - def combine_denoised(self, x_out, conds_list, uncond, cond_scale): denoised_uncond = x_out[-uncond.shape[0]:] denoised = torch.clone(denoised_uncond) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 35c4d657f..85f3c7e06 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -7,7 +7,16 @@ from modules import devices, images, sd_vae_approx, sd_samplers, sd_vae_taesd, s from modules.shared import opts, state import k_diffusion.sampling -SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) + +SamplerDataTuple = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options']) + + +class SamplerData(SamplerDataTuple): + def total_steps(self, steps): + if self.options.get("second_order", False): + steps = steps * 2 + + return steps def setup_img2img_steps(p, steps=None): @@ -131,31 +140,26 @@ def replace_torchsde_browinan(): replace_torchsde_browinan() -def apply_refiner(sampler): - completed_ratio = sampler.step / sampler.steps +def apply_refiner(cfg_denoiser): + completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps + refiner_switch_at = cfg_denoiser.p.refiner_switch_at + refiner_checkpoint_info = cfg_denoiser.p.refiner_checkpoint_info - if completed_ratio <= shared.opts.sd_refiner_switch_at: + if refiner_switch_at is not None and completed_ratio <= refiner_switch_at: return False - if shared.opts.sd_refiner_checkpoint == "None": + if refiner_checkpoint_info is None or shared.sd_model.sd_checkpoint_info == refiner_checkpoint_info: return False - if shared.sd_model.sd_checkpoint_info.title == shared.opts.sd_refiner_checkpoint: - return False - - refiner_checkpoint_info = sd_models.get_closet_checkpoint_match(shared.opts.sd_refiner_checkpoint) - if refiner_checkpoint_info is None: - raise Exception(f'Could not find checkpoint with name {shared.opts.sd_refiner_checkpoint}') - - sampler.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title - sampler.p.extra_generation_params['Refiner switch at'] = shared.opts.sd_refiner_switch_at + cfg_denoiser.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title + cfg_denoiser.p.extra_generation_params['Refiner switch at'] = refiner_switch_at with sd_models.SkipWritingToConfig(): sd_models.reload_model_weights(info=refiner_checkpoint_info) devices.torch_gc() - sampler.p.setup_conds() - sampler.update_inner_model() + cfg_denoiser.p.setup_conds() + cfg_denoiser.update_inner_model() return True @@ -192,7 +196,7 @@ class Sampler: self.sampler_noises = None self.stop_at = None self.eta = None - self.config = None # set by the function calling the constructor + self.config: SamplerData = None # set by the function calling the constructor self.last_latent = None self.s_min_uncond = None self.s_churn = 0.0 @@ -208,6 +212,7 @@ class Sampler: self.p = None self.model_wrap_cfg = None self.sampler_extra_args = None + self.options = {} def callback_state(self, d): step = d['i'] @@ -220,6 +225,7 @@ class Sampler: def launch_sampling(self, steps, func): self.model_wrap_cfg.steps = steps + self.model_wrap_cfg.total_steps = self.config.total_steps(steps) state.sampling_steps = steps state.sampling_step = 0 diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index d10fe12eb..1f8e9c4b9 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -64,9 +64,10 @@ class CFGDenoiserKDiffusion(sd_samplers_cfg_denoiser.CFGDenoiser): class KDiffusionSampler(sd_samplers_common.Sampler): - def __init__(self, funcname, sd_model): + def __init__(self, funcname, sd_model, options=None): super().__init__(funcname) + self.options = options or {} self.func = funcname if callable(funcname) else getattr(k_diffusion.sampling, self.funcname) self.model_wrap_cfg = CFGDenoiserKDiffusion(self) diff --git a/modules/shared_items.py b/modules/shared_items.py index e4ec40a8b..754166d22 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -69,8 +69,8 @@ def reload_hypernetworks(): ui_reorder_categories_builtin_items = [ "inpaint", "sampler", + "accordions", "checkboxes", - "hires_fix", "dimensions", "cfg", "seed", @@ -86,7 +86,7 @@ def ui_reorder_categories(): sections = {} for script in scripts.scripts_txt2img.scripts + scripts.scripts_img2img.scripts: - if isinstance(script.section, str): + if isinstance(script.section, str) and script.section not in ui_reorder_categories_builtin_items: sections[script.section] = 1 yield from sections diff --git a/modules/shared_options.py b/modules/shared_options.py index 1e5b64eaf..9ae51f186 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -140,8 +140,6 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU", "NV"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"), "tiling": OptionInfo(False, "Tiling", infotext='Tiling').info("produce a tileable picture"), - "sd_refiner_checkpoint": OptionInfo("None", "Refiner checkpoint", gr.Dropdown, lambda: {"choices": ["None"] + shared_items.list_checkpoint_tiles()}, refresh=shared_items.refresh_checkpoints, infotext="Refiner").info("switch to another model in the middle of generation"), - "sd_refiner_switch_at": OptionInfo(1.0, "Refiner switch at", gr.Slider, {"minimum": 0.01, "maximum": 1.0, "step": 0.01}, infotext='Refiner switch at').info("fraction of sampling steps when the swtch to refiner model should happen; 1=never, 0.5=switch in the middle of generation"), })) options_templates.update(options_section(('sdxl', "Stable Diffusion XL"), { diff --git a/modules/ui.py b/modules/ui.py index 052927341..3321b94d1 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -438,35 +438,38 @@ def create_ui(): with FormRow(elem_classes="checkboxes-row", variant="compact"): pass - elif category == "hires_fix": - with InputAccordion(False, label="Hires. fix") as enable_hr: - with enable_hr.extra(): - hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False, min_width=0) + elif category == "accordions": + with gr.Row(elem_id="txt2img_accordions", elem_classes="accordions"): + with InputAccordion(False, label="Hires. fix", elem_id="txt2img_hr") as enable_hr: + with enable_hr.extra(): + hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False, min_width=0) - 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_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", 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") + 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") - with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact", visible=opts.hires_fix_show_sampler) as hr_sampler_container: + with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact", visible=opts.hires_fix_show_sampler) as hr_sampler_container: - hr_checkpoint_name = gr.Dropdown(label='Hires checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") - create_refresh_button(hr_checkpoint_name, modules.sd_models.list_models, lambda: {"choices": ["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True)}, "hr_checkpoint_refresh") + hr_checkpoint_name = gr.Dropdown(label='Hires checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") + create_refresh_button(hr_checkpoint_name, modules.sd_models.list_models, lambda: {"choices": ["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True)}, "hr_checkpoint_refresh") - hr_sampler_name = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") + hr_sampler_name = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") - with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container: - with gr.Column(scale=80): - with gr.Row(): - hr_prompt = gr.Textbox(label="Hires prompt", elem_id="hires_prompt", show_label=False, lines=3, placeholder="Prompt for hires fix pass.\nLeave empty to use the same prompt as in first pass.", elem_classes=["prompt"]) - with gr.Column(scale=80): - with gr.Row(): - hr_negative_prompt = gr.Textbox(label="Hires negative prompt", elem_id="hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt for hires fix pass.\nLeave empty to use the same negative prompt as in first pass.", elem_classes=["prompt"]) + with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container: + with gr.Column(scale=80): + with gr.Row(): + hr_prompt = gr.Textbox(label="Hires prompt", elem_id="hires_prompt", show_label=False, lines=3, placeholder="Prompt for hires fix pass.\nLeave empty to use the same prompt as in first pass.", elem_classes=["prompt"]) + with gr.Column(scale=80): + with gr.Row(): + hr_negative_prompt = gr.Textbox(label="Hires negative prompt", elem_id="hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt for hires fix pass.\nLeave empty to use the same negative prompt as in first pass.", elem_classes=["prompt"]) + + scripts.scripts_txt2img.setup_ui_for_section(category) elif category == "batch": if not opts.dimensions_and_batch_together: @@ -482,7 +485,7 @@ def create_ui(): with FormGroup(elem_id="txt2img_script_container"): custom_inputs = scripts.scripts_txt2img.setup_ui() - else: + if category not in {"accordions"}: scripts.scripts_txt2img.setup_ui_for_section(category) hr_resolution_preview_inputs = [enable_hr, width, height, hr_scale, hr_resize_x, hr_resize_y] @@ -794,6 +797,10 @@ def create_ui(): with FormRow(elem_classes="checkboxes-row", variant="compact"): pass + elif category == "accordions": + with gr.Row(elem_id="img2img_accordions", elem_classes="accordions"): + scripts.scripts_img2img.setup_ui_for_section(category) + elif category == "batch": if not opts.dimensions_and_batch_together: with FormRow(elem_id="img2img_column_batch"): @@ -836,7 +843,8 @@ def create_ui(): inputs=[], outputs=[inpaint_controls, mask_alpha], ) - else: + + if category not in {"accordions"}: scripts.scripts_img2img.setup_ui_for_section(category) img2img_gallery, generation_info, html_info, html_log = create_output_panel("img2img", opts.outdir_img2img_samples) diff --git a/modules/ui_components.py b/modules/ui_components.py index bfe2fbd97..d08b2b997 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -87,13 +87,23 @@ class InputAccordion(gr.Checkbox): self.accordion_id = f"input-accordion-{InputAccordion.global_index}" InputAccordion.global_index += 1 - kwargs['elem_id'] = self.accordion_id + "-checkbox" - kwargs['visible'] = False - super().__init__(value, **kwargs) + kwargs_checkbox = { + **kwargs, + "elem_id": f"{self.accordion_id}-checkbox", + "visible": False, + } + super().__init__(value, **kwargs_checkbox) self.change(fn=None, _js='function(checked){ inputAccordionChecked("' + self.accordion_id + '", checked); }', inputs=[self]) - self.accordion = gr.Accordion(kwargs.get('label', 'Accordion'), open=value, elem_id=self.accordion_id, elem_classes=['input-accordion']) + kwargs_accordion = { + **kwargs, + "elem_id": self.accordion_id, + "label": kwargs.get('label', 'Accordion'), + "elem_classes": ['input-accordion'], + "open": value, + } + self.accordion = gr.Accordion(**kwargs_accordion) def extra(self): """Allows you to put something into the label of the accordion. diff --git a/style.css b/style.css index 4cdce87cf..260b1056d 100644 --- a/style.css +++ b/style.css @@ -166,16 +166,6 @@ a{ color: var(--button-secondary-text-color-hover); } -.checkboxes-row{ - margin-bottom: 0.5em; - margin-left: 0em; -} -.checkboxes-row > div{ - flex: 0; - white-space: nowrap; - min-width: auto !important; -} - button.custom-button{ border-radius: var(--button-large-radius); padding: var(--button-large-padding); @@ -352,7 +342,7 @@ div.block.gradio-accordion { } div.dimensions-tools{ - min-width: 0 !important; + min-width: 1.6em !important; max-width: fit-content; flex-direction: column; place-content: center; @@ -1012,10 +1002,28 @@ div.block.gradio-box.popup-dialog > div:last-child, .popup-dialog > div:last-chi } div.block.input-accordion{ - margin-bottom: 0.4em; + } .input-accordion-extra{ flex: 0 0 auto !important; margin: 0 0.5em 0 auto; } + +div.accordions > div.input-accordion{ + min-width: fit-content !important; +} + +div.accordions > div.gradio-accordion .label-wrap span{ + white-space: nowrap; + margin-right: 0.25em; +} + +div.accordions{ + gap: 0.5em; +} + +div.accordions > div.input-accordion.input-accordion-open{ + flex: 1 auto; +} + From b293ed30610c040e621e1840d63047ae298f0650 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 12:54:32 +0300 Subject: [PATCH 13/67] make it possible to use hires fix together with refiner --- modules/processing.py | 6 ++++++ modules/processing_scripts/refiner.py | 2 +- modules/sd_samplers_common.py | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 5996cbac1..6ad105d7d 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -1200,6 +1200,12 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.hr_c = self.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, total_steps, [self.cached_hr_c, self.cached_c], self.hr_extra_network_data) def setup_conds(self): + if self.is_hr_pass: + # if we are in hr pass right now, the call is being made from the refiner, and we don't need to setup firstpass cons or switch model + self.hr_c = None + self.calculate_hr_conds() + return + super().setup_conds() self.hr_uc = None diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py index 5a82991ab..773ec5d0b 100644 --- a/modules/processing_scripts/refiner.py +++ b/modules/processing_scripts/refiner.py @@ -24,7 +24,7 @@ class ScriptRefiner(scripts.Script): refiner_checkpoint = gr.Dropdown(label='Checkpoint', elem_id=self.elem_id("checkpoint"), choices=sd_models.checkpoint_tiles(), value='', tooltip="switch to another model in the middle of generation") create_refresh_button(refiner_checkpoint, sd_models.list_models, lambda: {"choices": sd_models.checkpoint_tiles()}, self.elem_id("checkpoint_refresh")) - refiner_switch_at = gr.Slider(value=0.8, label="Switch at", minimum=0.01, maximum=1.0, step=0.01, elem_id=self.elem_id("switch_at"), tooltip="fraction of sampling steps when the swtch to refiner model should happen; 1=never, 0.5=switch in the middle of generation") + refiner_switch_at = gr.Slider(value=0.8, label="Switch at", minimum=0.01, maximum=1.0, step=0.01, elem_id=self.elem_id("switch_at"), tooltip="fraction of sampling steps when the switch to refiner model should happen; 1=never, 0.5=switch in the middle of generation") def lookup_checkpoint(title): info = sd_models.get_closet_checkpoint_match(title) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 85f3c7e06..40c7aae09 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -151,6 +151,9 @@ def apply_refiner(cfg_denoiser): if refiner_checkpoint_info is None or shared.sd_model.sd_checkpoint_info == refiner_checkpoint_info: return False + if getattr(cfg_denoiser.p, "enable_hr", False) and not cfg_denoiser.p.is_hr_pass: + return False + cfg_denoiser.p.extra_generation_params['Refiner'] = refiner_checkpoint_info.short_title cfg_denoiser.p.extra_generation_params['Refiner switch at'] = refiner_switch_at From c8d453e91561e57fbb76119f688a5eae9d1a6aef Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 22:14:19 +0900 Subject: [PATCH 14/67] bring back csv mode --- scripts/xyz_grid.py | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index d37b428fc..80cb3cfbd 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -182,6 +182,10 @@ def str_permutations(x): """dummy function for specifying it in AxisOption's type when you want to get a list of permutations""" return x +def list_to_csv_string(data_list): + with StringIO() as o: + csv.writer(o).writerow(data_list) + return o.getvalue().strip() class AxisOption: def __init__(self, label, type, apply, format_value=format_value_add_label, confirm=None, cost=0.0, choices=None): @@ -377,6 +381,8 @@ re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*") re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*") +use_dropdown = True + class Script(scripts.Script): def title(self): @@ -430,28 +436,34 @@ class Script(scripts.Script): xz_swap_args = [x_type, x_values, x_values_dropdown, z_type, z_values, z_values_dropdown] swap_xz_axes_button.click(swap_axes, inputs=xz_swap_args, outputs=xz_swap_args) - def fill(x_type): - axis = self.current_axis_options[x_type] - return axis.choices() if axis.choices else gr.update() + def fill(axis_type): + axis = self.current_axis_options[axis_type] + if axis.choices: + if use_dropdown: + return gr.update(), axis.choices() + else: + return list_to_csv_string(axis.choices()), gr.update() + else: + return gr.update(), gr.update() - fill_x_button.click(fn=fill, inputs=[x_type], outputs=[x_values_dropdown]) - fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values_dropdown]) - fill_z_button.click(fn=fill, inputs=[z_type], outputs=[z_values_dropdown]) + fill_x_button.click(fn=fill, inputs=[x_type], outputs=[x_values, x_values_dropdown]) + fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values, y_values_dropdown]) + fill_z_button.click(fn=fill, inputs=[z_type], outputs=[z_values, z_values_dropdown]) - def select_axis(axis_type,axis_values_dropdown): + def select_axis(axis_type, axis_values_dropdown): choices = self.current_axis_options[axis_type].choices has_choices = choices is not None current_values = axis_values_dropdown if has_choices: choices = choices() - if isinstance(current_values,str): + if isinstance(current_values, str): current_values = current_values.split(",") current_values = list(filter(lambda x: x in choices, current_values)) - return gr.Button.update(visible=has_choices),gr.Textbox.update(visible=not has_choices),gr.update(choices=choices if has_choices else None,visible=has_choices,value=current_values) + return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or not use_dropdown), gr.update(choices=choices if has_choices else None, visible=has_choices and use_dropdown, value=current_values) - x_type.change(fn=select_axis, inputs=[x_type,x_values_dropdown], outputs=[fill_x_button,x_values,x_values_dropdown]) - y_type.change(fn=select_axis, inputs=[y_type,y_values_dropdown], outputs=[fill_y_button,y_values,y_values_dropdown]) - z_type.change(fn=select_axis, inputs=[z_type,z_values_dropdown], outputs=[fill_z_button,z_values,z_values_dropdown]) + x_type.change(fn=select_axis, inputs=[x_type, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) + y_type.change(fn=select_axis, inputs=[y_type, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) + z_type.change(fn=select_axis, inputs=[z_type, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) def get_dropdown_update_from_params(axis,params): val_key = f"{axis} Values" @@ -484,7 +496,7 @@ class Script(scripts.Script): if opt.label == 'Nothing': return [0] - if opt.choices is not None: + if opt.choices is not None and use_dropdown: valslist = vals_dropdown else: valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x] @@ -545,17 +557,17 @@ class Script(scripts.Script): return valslist x_opt = self.current_axis_options[x_type] - if x_opt.choices is not None: + if x_opt.choices is not None and use_dropdown: x_values = ",".join(x_values_dropdown) xs = process_axis(x_opt, x_values, x_values_dropdown) y_opt = self.current_axis_options[y_type] - if y_opt.choices is not None: + if y_opt.choices is not None and use_dropdown: y_values = ",".join(y_values_dropdown) ys = process_axis(y_opt, y_values, y_values_dropdown) z_opt = self.current_axis_options[z_type] - if z_opt.choices is not None: + if z_opt.choices is not None and use_dropdown: z_values = ",".join(z_values_dropdown) zs = process_axis(z_opt, z_values, z_values_dropdown) From d20eb11c9e210eb92b2c4821141f9647bfaaa9d2 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 22:24:00 +0900 Subject: [PATCH 15/67] format --- scripts/xyz_grid.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 80cb3cfbd..d8101dca5 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -175,18 +175,22 @@ def do_nothing(p, x, xs): def format_nothing(p, opt, x): return "" + def format_remove_path(p, opt, x): return os.path.basename(x) + def str_permutations(x): """dummy function for specifying it in AxisOption's type when you want to get a list of permutations""" return x + def list_to_csv_string(data_list): with StringIO() as o: csv.writer(o).writerow(data_list) return o.getvalue().strip() + class AxisOption: def __init__(self, label, type, apply, format_value=format_value_add_label, confirm=None, cost=0.0, choices=None): self.label = label @@ -203,6 +207,7 @@ class AxisOptionImg2Img(AxisOption): super().__init__(*args, **kwargs) self.is_img2img = True + class AxisOptionTxt2Img(AxisOption): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -290,11 +295,10 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend cell_size = (processed_result.width, processed_result.height) if processed_result.images[0] is not None: cell_mode = processed_result.images[0].mode - #This corrects size in case of batches: + # This corrects size in case of batches: cell_size = processed_result.images[0].size processed_result.images[idx] = Image.new(cell_mode, cell_size) - if first_axes_processed == 'x': for ix, x in enumerate(xs): if second_axes_processed == 'y': @@ -352,9 +356,9 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend if draw_legend: z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) processed_result.images.insert(0, z_grid) - #TODO: Deeper aspects of the program rely on grid info being misaligned between metadata arrays, which is not ideal. - #processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) - #processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) + # TODO: Deeper aspects of the program rely on grid info being misaligned between metadata arrays, which is not ideal. + # processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) + # processed_result.all_seeds.insert(0, processed_result.all_seeds[0]) processed_result.infotexts.insert(0, processed_result.infotexts[0]) return processed_result @@ -396,19 +400,19 @@ class Script(scripts.Script): with gr.Row(): x_type = gr.Dropdown(label="X type", choices=[x.label for x in self.current_axis_options], value=self.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")) - x_values_dropdown = gr.Dropdown(label="X values",visible=False,multiselect=True,interactive=True) + x_values_dropdown = gr.Dropdown(label="X values", visible=False, multiselect=True, interactive=True) fill_x_button = ToolButton(value=fill_values_symbol, elem_id="xyz_grid_fill_x_tool_button", visible=False) with gr.Row(): y_type = gr.Dropdown(label="Y type", choices=[x.label for x in self.current_axis_options], value=self.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")) - y_values_dropdown = gr.Dropdown(label="Y values",visible=False,multiselect=True,interactive=True) + y_values_dropdown = gr.Dropdown(label="Y values", visible=False, multiselect=True, interactive=True) fill_y_button = ToolButton(value=fill_values_symbol, elem_id="xyz_grid_fill_y_tool_button", visible=False) with gr.Row(): z_type = gr.Dropdown(label="Z type", choices=[x.label for x in self.current_axis_options], value=self.current_axis_options[0].label, type="index", elem_id=self.elem_id("z_type")) z_values = gr.Textbox(label="Z values", lines=1, elem_id=self.elem_id("z_values")) - z_values_dropdown = gr.Dropdown(label="Z values",visible=False,multiselect=True,interactive=True) + z_values_dropdown = gr.Dropdown(label="Z values", visible=False, multiselect=True, interactive=True) fill_z_button = ToolButton(value=fill_values_symbol, elem_id="xyz_grid_fill_z_tool_button", visible=False) with gr.Row(variant="compact", elem_id="axis_options"): @@ -465,22 +469,22 @@ class Script(scripts.Script): y_type.change(fn=select_axis, inputs=[y_type, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) z_type.change(fn=select_axis, inputs=[z_type, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) - def get_dropdown_update_from_params(axis,params): + def get_dropdown_update_from_params(axis, params): val_key = f"{axis} Values" - vals = params.get(val_key,"") + vals = params.get(val_key, "") valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x] - return gr.update(value = valslist) + return gr.update(value=valslist) self.infotext_fields = ( (x_type, "X Type"), (x_values, "X Values"), - (x_values_dropdown, lambda params:get_dropdown_update_from_params("X",params)), + (x_values_dropdown, lambda params: get_dropdown_update_from_params("X", params)), (y_type, "Y Type"), (y_values, "Y Values"), - (y_values_dropdown, lambda params:get_dropdown_update_from_params("Y",params)), + (y_values_dropdown, lambda params: get_dropdown_update_from_params("Y", params)), (z_type, "Z Type"), (z_values, "Z Values"), - (z_values_dropdown, lambda params:get_dropdown_update_from_params("Z",params)), + (z_values_dropdown, lambda params: get_dropdown_update_from_params("Z", params)), ) return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size] @@ -515,8 +519,8 @@ class Script(scripts.Script): valslist_ext += list(range(start, end, step)) elif mc is not None: start = int(mc.group(1)) - end = int(mc.group(2)) - num = int(mc.group(3)) if mc.group(3) is not None else 1 + end = int(mc.group(2)) + num = int(mc.group(3)) if mc.group(3) is not None else 1 valslist_ext += [int(x) for x in np.linspace(start=start, stop=end, num=num).tolist()] else: @@ -537,8 +541,8 @@ class Script(scripts.Script): valslist_ext += np.arange(start, end + step, step).tolist() elif mc is not None: start = float(mc.group(1)) - end = float(mc.group(2)) - num = int(mc.group(3)) if mc.group(3) is not None else 1 + end = float(mc.group(2)) + num = int(mc.group(3)) if mc.group(3) is not None else 1 valslist_ext += np.linspace(start=start, stop=end, num=num).tolist() else: @@ -572,7 +576,7 @@ class Script(scripts.Script): zs = process_axis(z_opt, z_values, z_values_dropdown) # this could be moved to common code, but unlikely to be ever triggered anywhere else - Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes + Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes grid_mp = round(len(xs) * len(ys) * len(zs) * p.width * p.height / 1000000) assert grid_mp < opts.img_max_size_mp, f'Error: Resulting grid would be too large ({grid_mp} MPixels) (max configured size is {opts.img_max_size_mp} MPixels)' @@ -732,7 +736,7 @@ class Script(scripts.Script): # Auto-save main and sub-grids: grid_count = z_count + 1 if z_count > 1 else 1 for g in range(grid_count): - #TODO: See previous comment about intentional data misalignment. + # TODO: See previous comment about intentional data misalignment. adj_g = g-1 if g > 0 else g images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[adj_g], seed=processed.all_seeds[adj_g], grid=True, p=processed) From fd617fad006bdaeb8061fae0dda642da6499c3df Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 22:24:59 +0900 Subject: [PATCH 16/67] Redundant character escape '\]' in RegExp --- scripts/xyz_grid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index d8101dca5..0ed0f2629 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -382,8 +382,8 @@ class SharedSettingsStackHelper(object): re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*") re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*") -re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*\])?\s*") -re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*\])?\s*") +re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*])?\s*") +re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*])?\s*") use_dropdown = True From 6aa26a26d5beb317d708c4fa85c38056347ea5d3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 16:47:39 +0300 Subject: [PATCH 17/67] change quicksettings items to have variable width --- style.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/style.css b/style.css index 260b1056d..f936e9a0f 100644 --- a/style.css +++ b/style.css @@ -386,14 +386,17 @@ div#extras_scale_to_tab div.form{ } #quicksettings > div, #quicksettings > fieldset{ - max-width: 24em; - min-width: 24em; - width: 24em; + max-width: 36em; + width: fit-content; + flex: auto; padding: 0; border: none; box-shadow: none; background: none; } +#quicksettings > div.gradio-dropdown{ + min-width: 24em !important; +} #settings{ display: block; From f131f84e13d6f1d06ffc6ac75c100357d0e7b3e7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:12:33 +0900 Subject: [PATCH 18/67] dropdown mode chackbox --- scripts/xyz_grid.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 0ed0f2629..81597ade0 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -385,8 +385,6 @@ re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d re_range_count = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\[(\d+)\s*])?\s*") re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\[(\d+(?:.\d*)?)\s*])?\s*") -use_dropdown = True - class Script(scripts.Script): def title(self): @@ -422,6 +420,8 @@ class Script(scripts.Script): with gr.Column(): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) + with gr.Column(): + use_dropdown = gr.Checkbox(label='use dropdown', value=True, elem_id=self.elem_id("use_dropdown")) with gr.Column(): margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) @@ -443,7 +443,7 @@ class Script(scripts.Script): def fill(axis_type): axis = self.current_axis_options[axis_type] if axis.choices: - if use_dropdown: + if use_dropdown.value: return gr.update(), axis.choices() else: return list_to_csv_string(axis.choices()), gr.update() @@ -463,12 +463,21 @@ class Script(scripts.Script): if isinstance(current_values, str): current_values = current_values.split(",") current_values = list(filter(lambda x: x in choices, current_values)) - return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or not use_dropdown), gr.update(choices=choices if has_choices else None, visible=has_choices and use_dropdown, value=current_values) + return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or not use_dropdown.value), gr.update(choices=choices if has_choices else None, visible=has_choices and use_dropdown.value, value=current_values) x_type.change(fn=select_axis, inputs=[x_type, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) y_type.change(fn=select_axis, inputs=[y_type, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) z_type.change(fn=select_axis, inputs=[z_type, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) + def change_choice_mode(_use_dropdown, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown): + use_dropdown.value = _use_dropdown + _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values_dropdown) + _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values_dropdown) + _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values_dropdown) + return _fill_x_button, _x_values, _x_values_dropdown, _fill_y_button, _y_values, _y_values_dropdown, _fill_z_button, _z_values, _z_values_dropdown + + use_dropdown.change(fn=change_choice_mode, inputs=[use_dropdown, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) + def get_dropdown_update_from_params(axis, params): val_key = f"{axis} Values" vals = params.get(val_key, "") @@ -487,9 +496,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params: get_dropdown_update_from_params("Z", params)), ) - return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size] + return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, use_dropdown] - def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size): + def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, use_dropdown): if not no_fixed_seeds: modules.processing.fix_seed(p) From 7a68ac661574dc64f510b0c4cd3403443ca2f875 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:40:05 +0900 Subject: [PATCH 19/67] rename to csv mode --- scripts/xyz_grid.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 81597ade0..677982022 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -421,7 +421,7 @@ class Script(scripts.Script): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Column(): - use_dropdown = gr.Checkbox(label='use dropdown', value=True, elem_id=self.elem_id("use_dropdown")) + csv_mode = gr.Checkbox(label='CSV mode', value=False, elem_id=self.elem_id("CSV mode")) with gr.Column(): margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) @@ -443,10 +443,10 @@ class Script(scripts.Script): def fill(axis_type): axis = self.current_axis_options[axis_type] if axis.choices: - if use_dropdown.value: - return gr.update(), axis.choices() - else: + if csv_mode.value: return list_to_csv_string(axis.choices()), gr.update() + else: + return gr.update(), axis.choices() else: return gr.update(), gr.update() @@ -463,20 +463,20 @@ class Script(scripts.Script): if isinstance(current_values, str): current_values = current_values.split(",") current_values = list(filter(lambda x: x in choices, current_values)) - return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or not use_dropdown.value), gr.update(choices=choices if has_choices else None, visible=has_choices and use_dropdown.value, value=current_values) + return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode.value), gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode.value, value=current_values) x_type.change(fn=select_axis, inputs=[x_type, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) y_type.change(fn=select_axis, inputs=[y_type, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) z_type.change(fn=select_axis, inputs=[z_type, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) - def change_choice_mode(_use_dropdown, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown): - use_dropdown.value = _use_dropdown + def change_choice_mode(_csv_mode, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown): + csv_mode.value = _csv_mode _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values_dropdown) _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values_dropdown) _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values_dropdown) return _fill_x_button, _x_values, _x_values_dropdown, _fill_y_button, _y_values, _y_values_dropdown, _fill_z_button, _z_values, _z_values_dropdown - use_dropdown.change(fn=change_choice_mode, inputs=[use_dropdown, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) + csv_mode.change(fn=change_choice_mode, inputs=[csv_mode, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) def get_dropdown_update_from_params(axis, params): val_key = f"{axis} Values" @@ -496,9 +496,9 @@ class Script(scripts.Script): (z_values_dropdown, lambda params: get_dropdown_update_from_params("Z", params)), ) - return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, use_dropdown] + return [x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, csv_mode] - def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, use_dropdown): + def run(self, p, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown, draw_legend, include_lone_images, include_sub_grids, no_fixed_seeds, margin_size, csv_mode): if not no_fixed_seeds: modules.processing.fix_seed(p) @@ -509,7 +509,7 @@ class Script(scripts.Script): if opt.label == 'Nothing': return [0] - if opt.choices is not None and use_dropdown: + if opt.choices is not None and not csv_mode: valslist = vals_dropdown else: valslist = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(vals))) if x] @@ -570,17 +570,17 @@ class Script(scripts.Script): return valslist x_opt = self.current_axis_options[x_type] - if x_opt.choices is not None and use_dropdown: + if x_opt.choices is not None and not csv_mode: x_values = ",".join(x_values_dropdown) xs = process_axis(x_opt, x_values, x_values_dropdown) y_opt = self.current_axis_options[y_type] - if y_opt.choices is not None and use_dropdown: + if y_opt.choices is not None and not csv_mode: y_values = ",".join(y_values_dropdown) ys = process_axis(y_opt, y_values, y_values_dropdown) z_opt = self.current_axis_options[z_type] - if z_opt.choices is not None and use_dropdown: + if z_opt.choices is not None and not csv_mode: z_values = ",".join(z_values_dropdown) zs = process_axis(z_opt, z_values, z_values_dropdown) From f0b72b81211881e083c84cff585380bb70d17271 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 17:46:13 +0300 Subject: [PATCH 20/67] move seed, variation seed and variation seed strength to a single row, dump resize seed from UI add a way for scripts to register a callback for before/after just a single component's creation --- modules/img2img.py | 8 +- modules/processing_scripts/seed.py | 95 +++++++++++++++++++++++ modules/scripts.py | 77 ++++++++++++++++++- modules/shared_items.py | 1 + modules/txt2img.py | 8 +- modules/ui.py | 119 +++-------------------------- style.css | 9 ++- 7 files changed, 191 insertions(+), 126 deletions(-) create mode 100644 modules/processing_scripts/seed.py diff --git a/modules/img2img.py b/modules/img2img.py index c7bbbac8f..ac9fd3f84 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -116,7 +116,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal process_images(p) -def img2img(id_task: str, 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_name: str, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_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, selected_scale_tab: int, height: int, width: int, scale_by: float, 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, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args): +def img2img(id_task: str, 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_name: str, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, 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, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args): override_settings = create_override_settings_dict(override_settings_texts) is_batch = mode == 5 @@ -166,12 +166,6 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s prompt=prompt, negative_prompt=negative_prompt, styles=prompt_styles, - seed=seed, - subseed=subseed, - subseed_strength=subseed_strength, - seed_resize_from_h=seed_resize_from_h, - seed_resize_from_w=seed_resize_from_w, - seed_enable_extras=seed_enable_extras, sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py new file mode 100644 index 000000000..e0911bbea --- /dev/null +++ b/modules/processing_scripts/seed.py @@ -0,0 +1,95 @@ +import json + +import gradio as gr + +from modules import scripts, ui, errors +from modules.shared import cmd_opts +from modules.ui_components import ToolButton + + +class ScriptSeed(scripts.ScriptBuiltin): + section = "seed" + create_group = False + + def __init__(self): + self.seed = None + self.reuse_seed = None + self.reuse_subseed = None + + def title(self): + return "Seed" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Row(elem_id=self.elem_id("seed_row")): + if cmd_opts.use_textbox_seed: + self.seed = gr.Textbox(label='Seed', value="", elem_id=self.elem_id("seed")) + else: + self.seed = gr.Number(label='Seed', value=-1, elem_id=self.elem_id("seed"), precision=0) + + random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), label='Random seed') + reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), label='Reuse seed') + + subseed = gr.Number(label='Variation seed', value=-1, elem_id=self.elem_id("subseed"), precision=0) + + random_subseed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_subseed")) + reuse_subseed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_subseed")) + + subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=self.elem_id("subseed_strength")) + + random_seed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("seed") + "')}", show_progress=False, inputs=[], outputs=[]) + random_subseed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("subseed") + "')}", show_progress=False, inputs=[], outputs=[]) + + self.infotext_fields = [ + (self.seed, "Seed"), + (subseed, "Variation seed"), + (subseed_strength, "Variation seed strength"), + ] + + self.on_after_component(lambda x: connect_reuse_seed(self.seed, reuse_seed, x.component, False), elem_id=f'generation_info_{self.tabname}') + self.on_after_component(lambda x: connect_reuse_seed(self.seed, reuse_subseed, x.component, True), elem_id=f'generation_info_{self.tabname}') + + return self.seed, subseed, subseed_strength + + def before_process(self, p, seed, subseed, subseed_strength): + p.seed = seed + + if subseed_strength > 0: + p.subseed = subseed + p.subseed_strength = subseed_strength + + +def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): + """ Connects a 'reuse (sub)seed' button's click event so that it copies last used + (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength + was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" + + def copy_seed(gen_info_string: str, index): + res = -1 + + try: + gen_info = json.loads(gen_info_string) + index -= gen_info.get('index_of_first_image', 0) + + if is_subseed and gen_info.get('subseed_strength', 0) > 0: + all_subseeds = gen_info.get('all_subseeds', [-1]) + res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] + else: + all_seeds = gen_info.get('all_seeds', [-1]) + res = all_seeds[index if 0 <= index < len(all_seeds) else 0] + + except json.decoder.JSONDecodeError: + if gen_info_string: + errors.report(f"Error parsing JSON generation info: {gen_info_string}") + + return [res, gr.update()] + + reuse_seed.click( + fn=copy_seed, + _js="(x, y) => [x, selected_gallery_index()]", + show_progress=False, + inputs=[generation_info, seed], + outputs=[seed, seed] + ) diff --git a/modules/scripts.py b/modules/scripts.py index 51da732a6..66fbec0dc 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -3,6 +3,7 @@ import re import sys import inspect from collections import namedtuple +from dataclasses import dataclass import gradio as gr @@ -21,6 +22,11 @@ class PostprocessBatchListArgs: self.images = images +@dataclass +class OnComponent: + component: gr.blocks.Block + + class Script: name = None """script's internal name derived from title""" @@ -35,6 +41,7 @@ class Script: is_txt2img = False is_img2img = False + tabname = None group = None """A gr.Group component that has all script's UI inside it.""" @@ -55,6 +62,12 @@ class Script: api_info = None """Generated value of type modules.api.models.ScriptInfo with information about the script for API""" + on_before_component_elem_id = [] + """list of callbacks to be called before a component with an elem_id is created""" + + on_after_component_elem_id = [] + """list of callbacks to be called after a component with an elem_id is created""" + def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -215,6 +228,24 @@ class Script: pass + def on_before_component(self, callback, *, elem_id): + """ + Calls callback before a component is created. The callback function is called with a single argument of type OnComponent. + + This function is an alternative to before_component in that it also cllows to run before a component is created, but + it doesn't require to be called for every created component - just for the one you need. + """ + + self.on_before_component_elem_id.append((elem_id, callback)) + + def on_after_component(self, callback, *, elem_id): + """ + Calls callback after a component is created. The callback function is called with a single argument of type OnComponent. + """ + + self.on_after_component_elem_id.append((elem_id, callback)) + + def describe(self): """unused""" return "" @@ -236,6 +267,17 @@ class Script: pass +class ScriptBuiltin(Script): + + def elem_id(self, item_id): + """helper function to generate id for a HTML element, constructs final id out of tab and user-supplied item_id""" + + need_tabname = self.show(True) == self.show(False) + tabname = ('img2img' if self.is_img2img else 'txt2txt') + "_" if need_tabname else "" + + return f'{tabname}{item_id}' + + current_basedir = paths.script_path @@ -354,10 +396,17 @@ class ScriptRunner: self.selectable_scripts = [] self.alwayson_scripts = [] self.titles = [] + self.title_map = {} self.infotext_fields = [] self.paste_field_names = [] self.inputs = [None] + self.on_before_component_elem_id = {} + """dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks""" + + self.on_after_component_elem_id = {} + """dict of callbacks to be called after an element is created; key=elem_id, value=list of callbacks""" + def initialize_scripts(self, is_img2img): from modules import scripts_auto_postprocessing @@ -372,6 +421,7 @@ class ScriptRunner: script.filename = script_data.path script.is_txt2img = not is_img2img script.is_img2img = is_img2img + script.tabname = "img2img" if is_img2img else "txt2img" visibility = script.show(script.is_img2img) @@ -446,6 +496,8 @@ class ScriptRunner: self.inputs = [None] def setup_ui(self): + all_titles = [wrap_call(script.title, script.filename, "title") or script.filename for script in self.scripts] + self.title_map = {title.lower(): script for title, script in zip(all_titles, self.scripts)} self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts] self.setup_ui_for_section(None) @@ -492,6 +544,13 @@ class ScriptRunner: self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None')))) self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) + for script in self.scripts: + for elem_id, callback in script.on_before_component_elem_id: + self.on_before_component_elem_id.get(elem_id, []).append((callback, script)) + + for elem_id, callback in script.on_after_component_elem_id: + self.on_after_component_elem_id.get(elem_id, []).append((callback, script)) + return self.inputs def run(self, p, *args): @@ -585,6 +644,13 @@ class ScriptRunner: errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) def before_component(self, component, **kwargs): + for callbacks in self.on_before_component_elem_id.get(kwargs.get("elem_id"), []): + for callback, script in callbacks: + try: + callback(OnComponent(component=component)) + except Exception: + errors.report(f"Error running on_before_component: {script.filename}", exc_info=True) + for script in self.scripts: try: script.before_component(component, **kwargs) @@ -592,12 +658,22 @@ class ScriptRunner: errors.report(f"Error running before_component: {script.filename}", exc_info=True) def after_component(self, component, **kwargs): + for callbacks in self.on_after_component_elem_id.get(component.elem_id, []): + for callback, script in callbacks: + try: + callback(OnComponent(component=component)) + except Exception: + errors.report(f"Error running on_after_component: {script.filename}", exc_info=True) + for script in self.scripts: try: script.after_component(component, **kwargs) except Exception: errors.report(f"Error running after_component: {script.filename}", exc_info=True) + def script(self, title): + return self.title_map.get(title.lower()) + def reload_sources(self, cache): for si, script in list(enumerate(self.scripts)): args_from = script.args_from @@ -616,7 +692,6 @@ class ScriptRunner: self.scripts[si].args_from = args_from self.scripts[si].args_to = args_to - def before_hr(self, p): for script in self.alwayson_scripts: try: diff --git a/modules/shared_items.py b/modules/shared_items.py index 754166d22..84d69c8df 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -73,6 +73,7 @@ ui_reorder_categories_builtin_items = [ "checkboxes", "dimensions", "cfg", + "denoising", "seed", "batch", "override_settings", diff --git a/modules/txt2img.py b/modules/txt2img.py index 5ea96bbaf..1ee592ad9 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -9,7 +9,7 @@ from modules.ui import plaintext_to_html import gradio as gr -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_name: str, 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, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, request: gr.Request, *args): +def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_name: str, n_iter: int, batch_size: int, cfg_scale: float, 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, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, request: gr.Request, *args): override_settings = create_override_settings_dict(override_settings_texts) p = processing.StableDiffusionProcessingTxt2Img( @@ -19,12 +19,6 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step prompt=prompt, styles=prompt_styles, negative_prompt=negative_prompt, - seed=seed, - subseed=subseed, - subseed_strength=subseed_strength, - seed_resize_from_h=seed_resize_from_h, - seed_resize_from_w=seed_resize_from_w, - seed_enable_extras=seed_enable_extras, sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, diff --git a/modules/ui.py b/modules/ui.py index 3321b94d1..a6b1f964b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1,5 +1,4 @@ import datetime -import json import mimetypes import os import sys @@ -13,7 +12,7 @@ from PIL import Image, PngImagePlugin # noqa: F401 from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call from modules import gradio_extensons # noqa: F401 -from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, errors, shared_items, ui_settings, timer, sysinfo, ui_checkpoint_merger, ui_prompt_styles, scripts, sd_samplers, processing, ui_extra_networks +from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, shared_items, ui_settings, timer, sysinfo, ui_checkpoint_merger, ui_prompt_styles, scripts, sd_samplers, processing, ui_extra_networks from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML, InputAccordion from modules.paths import script_path from modules.ui_common import create_refresh_button @@ -142,45 +141,6 @@ def interrogate_deepbooru(image): return gr.update() if prompt is None else prompt -def create_seed_inputs(target_interface): - with FormRow(elem_id=f"{target_interface}_seed_row", variant="compact"): - if cmd_opts.use_textbox_seed: - seed = gr.Textbox(label='Seed', value="", elem_id=f"{target_interface}_seed") - else: - seed = gr.Number(label='Seed', value=-1, elem_id=f"{target_interface}_seed", precision=0) - - random_seed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_seed", label='Random seed') - reuse_seed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_seed", label='Reuse seed') - - seed_checkbox = gr.Checkbox(label='Extra', elem_id=f"{target_interface}_subseed_show", value=False) - - # Components to show/hide based on the 'Extra' checkbox - seed_extras = [] - - with FormRow(visible=False, elem_id=f"{target_interface}_subseed_row") as seed_extra_row_1: - seed_extras.append(seed_extra_row_1) - subseed = gr.Number(label='Variation seed', value=-1, elem_id=f"{target_interface}_subseed", precision=0) - random_subseed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_subseed") - reuse_subseed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_subseed") - subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{target_interface}_subseed_strength") - - with FormRow(visible=False) as seed_extra_row_2: - seed_extras.append(seed_extra_row_2) - seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=f"{target_interface}_seed_resize_from_w") - seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=f"{target_interface}_seed_resize_from_h") - - random_seed.click(fn=None, _js="function(){setRandomSeed('" + target_interface + "_seed')}", show_progress=False, inputs=[], outputs=[]) - random_subseed.click(fn=None, _js="function(){setRandomSeed('" + target_interface + "_subseed')}", show_progress=False, inputs=[], outputs=[]) - - def change_visibility(show): - return {comp: gr_show(show) for comp in seed_extras} - - seed_checkbox.change(change_visibility, show_progress=False, inputs=[seed_checkbox], outputs=seed_extras) - - return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox - - - def connect_clear_prompt(button): """Given clear button, prompt, and token_counter objects, setup clear prompt button click event""" button.click( @@ -191,39 +151,6 @@ def connect_clear_prompt(button): ) -def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): - """ Connects a 'reuse (sub)seed' button's click event so that it copies last used - (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength - was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" - def copy_seed(gen_info_string: str, index): - res = -1 - - try: - gen_info = json.loads(gen_info_string) - index -= gen_info.get('index_of_first_image', 0) - - if is_subseed and gen_info.get('subseed_strength', 0) > 0: - all_subseeds = gen_info.get('all_subseeds', [-1]) - res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0] - else: - all_seeds = gen_info.get('all_seeds', [-1]) - res = all_seeds[index if 0 <= index < len(all_seeds) else 0] - - except json.decoder.JSONDecodeError: - if gen_info_string: - errors.report(f"Error parsing JSON generation info: {gen_info_string}") - - return [res, gr_show(False)] - - reuse_seed.click( - fn=copy_seed, - _js="(x, y) => [x, selected_gallery_index()]", - show_progress=False, - inputs=[generation_info, dummy_component], - outputs=[seed, dummy_component] - ) - - def update_token_counter(text, steps): try: text, _ = extra_networks.parse_prompt(text) @@ -429,10 +356,8 @@ def create_ui(): batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size") elif category == "cfg": - cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="txt2img_cfg_scale") - - elif category == "seed": - seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img') + with gr.Row(): + cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="txt2img_cfg_scale") elif category == "checkboxes": with FormRow(elem_classes="checkboxes-row", variant="compact"): @@ -509,9 +434,6 @@ def create_ui(): txt2img_gallery, generation_info, html_info, html_log = create_output_panel("txt2img", opts.outdir_txt2img_samples) - connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), _js="submit", @@ -525,8 +447,6 @@ def create_ui(): batch_count, batch_size, cfg_scale, - seed, - subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, height, width, enable_hr, @@ -577,15 +497,9 @@ def create_ui(): (steps, "Steps"), (sampler_name, "Sampler"), (cfg_scale, "CFG scale"), - (seed, "Seed"), (width, "Size-1"), (height, "Size-2"), (batch_size, "Batch size"), - (seed_checkbox, lambda d: "Variation seed" in d or "Seed resize from-1" in d), - (subseed, "Variation seed"), - (subseed_strength, "Variation seed strength"), - (seed_resize_from_w, "Seed resize from-1"), - (seed_resize_from_h, "Seed resize from-2"), (toprow.ui_styles.dropdown, lambda d: d["Styles array"] if isinstance(d.get("Styles array"), list) else gr.update()), (denoising_strength, "Denoising strength"), (enable_hr, lambda d: "Denoising strength" in d and ("Hires upscale" in d or "Hires upscaler" in d or "Hires resize-1" in d)), @@ -613,7 +527,7 @@ def create_ui(): steps, sampler_name, cfg_scale, - seed, + scripts.scripts_txt2img.script('Seed').seed, width, height, ] @@ -783,15 +697,13 @@ def create_ui(): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size") - elif category == "cfg": - with FormGroup(): - with FormRow(): - cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale") - image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=False) - denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") + elif category == "denoising": + denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") - elif category == "seed": - seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('img2img') + elif category == "cfg": + with gr.Row(): + cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale") + image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=False) elif category == "checkboxes": with FormRow(elem_classes="checkboxes-row", variant="compact"): @@ -849,9 +761,6 @@ def create_ui(): img2img_gallery, generation_info, html_info, html_log = create_output_panel("img2img", opts.outdir_img2img_samples) - connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - img2img_args = dict( fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), _js="submit_img2img", @@ -878,8 +787,6 @@ def create_ui(): cfg_scale, image_cfg_scale, denoising_strength, - seed, - subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, selected_scale_tab, height, width, @@ -966,15 +873,9 @@ def create_ui(): (sampler_name, "Sampler"), (cfg_scale, "CFG scale"), (image_cfg_scale, "Image CFG scale"), - (seed, "Seed"), (width, "Size-1"), (height, "Size-2"), (batch_size, "Batch size"), - (seed_checkbox, lambda d: "Variation seed" in d or "Seed resize from-1" in d), - (subseed, "Variation seed"), - (subseed_strength, "Variation seed strength"), - (seed_resize_from_w, "Seed resize from-1"), - (seed_resize_from_h, "Seed resize from-2"), (toprow.ui_styles.dropdown, lambda d: d["Styles array"] if isinstance(d.get("Styles array"), list) else gr.update()), (denoising_strength, "Denoising strength"), (mask_blur, "Mask blur"), diff --git a/style.css b/style.css index f936e9a0f..92093b897 100644 --- a/style.css +++ b/style.css @@ -222,14 +222,18 @@ div.block.gradio-accordion { padding: 0.1em 0.75em; } +[id$=_seed], [id$=_subseed]{ + max-width: 10em; +} + [id$=_subseed_show]{ min-width: auto !important; flex-grow: 0 !important; display: flex; } -[id$=_subseed_show] label{ - margin-bottom: 0.5em; +[id$=_subseed_show] .label-wrap{ + margin: 0 0 0 0.5em; align-self: end; } @@ -1028,5 +1032,6 @@ div.accordions{ div.accordions > div.input-accordion.input-accordion-open{ flex: 1 auto; + flex-flow: column; } From 4e8690906c02f14a81974200775bfc81718a9250 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 18:00:30 +0300 Subject: [PATCH 21/67] update seed/subseed HTML widths --- modules/processing_scripts/seed.py | 25 +++++++++++++++---------- style.css | 15 --------------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index e0911bbea..1ec203399 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -24,20 +24,25 @@ class ScriptSeed(scripts.ScriptBuiltin): def ui(self, is_img2img): with gr.Row(elem_id=self.elem_id("seed_row")): - if cmd_opts.use_textbox_seed: - self.seed = gr.Textbox(label='Seed', value="", elem_id=self.elem_id("seed")) - else: - self.seed = gr.Number(label='Seed', value=-1, elem_id=self.elem_id("seed"), precision=0) + with gr.Column(scale=1, min_width=205): + with gr.Row(): + if cmd_opts.use_textbox_seed: + self.seed = gr.Textbox(label='Seed', value="", elem_id=self.elem_id("seed"), min_width=100) + else: + self.seed = gr.Number(label='Seed', value=-1, elem_id=self.elem_id("seed"), min_width=100, precision=0) - random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), label='Random seed') - reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), label='Reuse seed') + random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), label='Random seed') + reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), label='Reuse seed') - subseed = gr.Number(label='Variation seed', value=-1, elem_id=self.elem_id("subseed"), precision=0) + with gr.Column(scale=1, min_width=205): + with gr.Row(): + subseed = gr.Number(label='Variation seed', value=-1, elem_id=self.elem_id("subseed"), min_width=100, precision=0) - random_subseed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_subseed")) - reuse_subseed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_subseed")) + random_subseed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_subseed")) + reuse_subseed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_subseed")) - subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=self.elem_id("subseed_strength")) + with gr.Column(scale=2, min_width=100): + subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=self.elem_id("subseed_strength")) random_seed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("seed") + "')}", show_progress=False, inputs=[], outputs=[]) random_subseed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("subseed") + "')}", show_progress=False, inputs=[], outputs=[]) diff --git a/style.css b/style.css index 92093b897..dc44d2ccc 100644 --- a/style.css +++ b/style.css @@ -222,21 +222,6 @@ div.block.gradio-accordion { padding: 0.1em 0.75em; } -[id$=_seed], [id$=_subseed]{ - max-width: 10em; -} - -[id$=_subseed_show]{ - min-width: auto !important; - flex-grow: 0 !important; - display: flex; -} - -[id$=_subseed_show] .label-wrap{ - margin: 0 0 0 0.5em; - align-self: end; -} - .html-log .comments{ padding-top: 0.5em; } From 6816ad5ed806b9ada81b4fab82e21a9455fa5720 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 18:36:30 +0300 Subject: [PATCH 22/67] fix broken reuse seed --- modules/processing_scripts/seed.py | 2 +- modules/scripts.py | 42 +++++++++++++++++------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 1ec203399..cc90775aa 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -54,7 +54,7 @@ class ScriptSeed(scripts.ScriptBuiltin): ] self.on_after_component(lambda x: connect_reuse_seed(self.seed, reuse_seed, x.component, False), elem_id=f'generation_info_{self.tabname}') - self.on_after_component(lambda x: connect_reuse_seed(self.seed, reuse_subseed, x.component, True), elem_id=f'generation_info_{self.tabname}') + self.on_after_component(lambda x: connect_reuse_seed(subseed, reuse_subseed, x.component, True), elem_id=f'generation_info_{self.tabname}') return self.seed, subseed, subseed_strength diff --git a/modules/scripts.py b/modules/scripts.py index 66fbec0dc..c6459b45d 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -62,10 +62,10 @@ class Script: api_info = None """Generated value of type modules.api.models.ScriptInfo with information about the script for API""" - on_before_component_elem_id = [] + on_before_component_elem_id = None """list of callbacks to be called before a component with an elem_id is created""" - on_after_component_elem_id = [] + on_after_component_elem_id = None """list of callbacks to be called after a component with an elem_id is created""" def title(self): @@ -235,6 +235,8 @@ class Script: This function is an alternative to before_component in that it also cllows to run before a component is created, but it doesn't require to be called for every created component - just for the one you need. """ + if self.on_before_component_elem_id is None: + self.on_before_component_elem_id = [] self.on_before_component_elem_id.append((elem_id, callback)) @@ -242,6 +244,8 @@ class Script: """ Calls callback after a component is created. The callback function is called with a single argument of type OnComponent. """ + if self.on_after_component_elem_id is None: + self.on_after_component_elem_id = [] self.on_after_component_elem_id.append((elem_id, callback)) @@ -545,11 +549,15 @@ class ScriptRunner: self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) for script in self.scripts: - for elem_id, callback in script.on_before_component_elem_id: - self.on_before_component_elem_id.get(elem_id, []).append((callback, script)) + for elem_id, callback in script.on_before_component_elem_id or []: + items = self.on_before_component_elem_id.get(elem_id, []) + items.append((callback, script)) + self.on_before_component_elem_id[elem_id] = items - for elem_id, callback in script.on_after_component_elem_id: - self.on_after_component_elem_id.get(elem_id, []).append((callback, script)) + for elem_id, callback in script.on_after_component_elem_id or []: + items = self.on_after_component_elem_id.get(elem_id, []) + items.append((callback, script)) + self.on_after_component_elem_id[elem_id] = items return self.inputs @@ -644,12 +652,11 @@ class ScriptRunner: errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) def before_component(self, component, **kwargs): - for callbacks in self.on_before_component_elem_id.get(kwargs.get("elem_id"), []): - for callback, script in callbacks: - try: - callback(OnComponent(component=component)) - except Exception: - errors.report(f"Error running on_before_component: {script.filename}", exc_info=True) + for callback, script in self.on_before_component_elem_id.get(kwargs.get("elem_id"), []): + try: + callback(OnComponent(component=component)) + except Exception: + errors.report(f"Error running on_before_component: {script.filename}", exc_info=True) for script in self.scripts: try: @@ -658,12 +665,11 @@ class ScriptRunner: errors.report(f"Error running before_component: {script.filename}", exc_info=True) def after_component(self, component, **kwargs): - for callbacks in self.on_after_component_elem_id.get(component.elem_id, []): - for callback, script in callbacks: - try: - callback(OnComponent(component=component)) - except Exception: - errors.report(f"Error running on_after_component: {script.filename}", exc_info=True) + for callback, script in self.on_after_component_elem_id.get(component.elem_id, []): + try: + callback(OnComponent(component=component)) + except Exception: + errors.report(f"Error running on_after_component: {script.filename}", exc_info=True) for script in self.scripts: try: From 9d0ec135968d80420b84ca83f7958f5fc8e534c2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 18:42:59 +0300 Subject: [PATCH 23/67] fix quicksettings on Chrome --- style.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/style.css b/style.css index dc44d2ccc..716126ed4 100644 --- a/style.css +++ b/style.css @@ -370,14 +370,13 @@ div#extras_scale_to_tab div.form{ /* settings */ #quicksettings { - width: fit-content; align-items: end; } #quicksettings > div, #quicksettings > fieldset{ max-width: 36em; width: fit-content; - flex: auto; + flex: 0 1 fit-content; padding: 0; border: none; box-shadow: none; From b2080756fcdc328292fc38998c06ccf23e53bd7e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 12 Aug 2023 19:03:33 +0300 Subject: [PATCH 24/67] make "send to" buttons into small tool buttons --- modules/ui_common.py | 12 ++++++++---- style.css | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index 99d19ff01..4c035f2a3 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -137,13 +137,17 @@ Requested path was: {f} generation_info = None with gr.Column(): with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): - open_folder_button = gr.Button(folder_symbol, visible=not shared.cmd_opts.hide_ui_dir_config) + open_folder_button = ToolButton(folder_symbol, elem_id=f'{tabname}_open_folder', visible=not shared.cmd_opts.hide_ui_dir_config, tooltip="Open images output directory.") if tabname != "extras": - save = gr.Button('Save', elem_id=f'save_{tabname}') - save_zip = gr.Button('Zip', elem_id=f'save_zip_{tabname}') + save = ToolButton('💾', elem_id=f'save_{tabname}', tooltip=f"Save the image to a dedicated directory ({shared.opts.outdir_save}).") + save_zip = ToolButton('🗃️', elem_id=f'save_zip_{tabname}', tooltip=f"Save zip archive with images to a dedicated directory ({shared.opts.outdir_save})") - buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) + buttons = { + 'img2img': ToolButton('🖼️', elem_id=f'{tabname}_send_to_img2img', tooltip="Send image and generation parameters to img2img tab."), + 'inpaint': ToolButton('🎨️', elem_id=f'{tabname}_send_to_inpaint', tooltip="Send image and generation parameters to img2img inpaint tab."), + 'extras': ToolButton('📐', elem_id=f'{tabname}_send_to_extras', tooltip="Send image and generation parameters to extras tab.") + } open_folder_button.click( fn=lambda: open_folder(shared.opts.outdir_samples or outdir), diff --git a/style.css b/style.css index 716126ed4..dc528422f 100644 --- a/style.css +++ b/style.css @@ -348,8 +348,8 @@ div#extras_scale_to_tab div.form{ z-index: 5; } -.image-buttons button{ - min-width: auto; +.image-buttons > .form{ + justify-content: center; } .infotext { From 8d9ca46e0a09c414b0d07fa8a680de10c0e72ffe Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:05:20 +0900 Subject: [PATCH 25/67] convert value when switching mode --- scripts/xyz_grid.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 677982022..bfaf9661c 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -454,29 +454,36 @@ class Script(scripts.Script): fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values, y_values_dropdown]) fill_z_button.click(fn=fill, inputs=[z_type], outputs=[z_values, z_values_dropdown]) - def select_axis(axis_type, axis_values_dropdown): + def select_axis(axis_type, axis_values, axis_values_dropdown): choices = self.current_axis_options[axis_type].choices has_choices = choices is not None - current_values = axis_values_dropdown + + current_values = axis_values + current_dropdown_values = axis_values_dropdown if has_choices: choices = choices() - if isinstance(current_values, str): - current_values = current_values.split(",") - current_values = list(filter(lambda x: x in choices, current_values)) - return gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode.value), gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode.value, value=current_values) + if csv_mode.value: + current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) + current_values = list_to_csv_string(current_dropdown_values) + else: + current_dropdown_values = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(axis_values)))] + current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) - x_type.change(fn=select_axis, inputs=[x_type, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) - y_type.change(fn=select_axis, inputs=[y_type, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) - z_type.change(fn=select_axis, inputs=[z_type, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) + return (gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode.value, value=current_values), + gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode.value, value=current_dropdown_values)) - def change_choice_mode(_csv_mode, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown): + x_type.change(fn=select_axis, inputs=[x_type, x_values, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) + y_type.change(fn=select_axis, inputs=[y_type, y_values, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) + z_type.change(fn=select_axis, inputs=[z_type, z_values, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) + + def change_choice_mode(_csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown): csv_mode.value = _csv_mode - _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values_dropdown) - _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values_dropdown) - _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values_dropdown) + _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values, x_values_dropdown) + _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values, y_values_dropdown) + _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values, z_values_dropdown) return _fill_x_button, _x_values, _x_values_dropdown, _fill_y_button, _y_values, _y_values_dropdown, _fill_z_button, _z_values, _z_values_dropdown - csv_mode.change(fn=change_choice_mode, inputs=[csv_mode, x_type, x_values_dropdown, y_type, y_values_dropdown, z_type, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) + csv_mode.change(fn=change_choice_mode, inputs=[csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) def get_dropdown_update_from_params(axis, params): val_key = f"{axis} Values" From 299eb543083668a945d6074753ea8e8f13e7d66d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:17:13 +0900 Subject: [PATCH 26/67] pass csv_mode --- scripts/xyz_grid.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index bfaf9661c..254a75f4a 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -440,21 +440,21 @@ class Script(scripts.Script): xz_swap_args = [x_type, x_values, x_values_dropdown, z_type, z_values, z_values_dropdown] swap_xz_axes_button.click(swap_axes, inputs=xz_swap_args, outputs=xz_swap_args) - def fill(axis_type): + def fill(axis_type, csv_mode): axis = self.current_axis_options[axis_type] if axis.choices: - if csv_mode.value: + if csv_mode: return list_to_csv_string(axis.choices()), gr.update() else: return gr.update(), axis.choices() else: return gr.update(), gr.update() - fill_x_button.click(fn=fill, inputs=[x_type], outputs=[x_values, x_values_dropdown]) - fill_y_button.click(fn=fill, inputs=[y_type], outputs=[y_values, y_values_dropdown]) - fill_z_button.click(fn=fill, inputs=[z_type], outputs=[z_values, z_values_dropdown]) + fill_x_button.click(fn=fill, inputs=[x_type, csv_mode], outputs=[x_values, x_values_dropdown]) + fill_y_button.click(fn=fill, inputs=[y_type, csv_mode], outputs=[y_values, y_values_dropdown]) + fill_z_button.click(fn=fill, inputs=[z_type, csv_mode], outputs=[z_values, z_values_dropdown]) - def select_axis(axis_type, axis_values, axis_values_dropdown): + def select_axis(axis_type, axis_values, axis_values_dropdown, csv_mode): choices = self.current_axis_options[axis_type].choices has_choices = choices is not None @@ -462,25 +462,24 @@ class Script(scripts.Script): current_dropdown_values = axis_values_dropdown if has_choices: choices = choices() - if csv_mode.value: + if csv_mode: current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) current_values = list_to_csv_string(current_dropdown_values) else: current_dropdown_values = [x.strip() for x in chain.from_iterable(csv.reader(StringIO(axis_values)))] current_dropdown_values = list(filter(lambda x: x in choices, current_dropdown_values)) - return (gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode.value, value=current_values), - gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode.value, value=current_dropdown_values)) + return (gr.Button.update(visible=has_choices), gr.Textbox.update(visible=not has_choices or csv_mode, value=current_values), + gr.update(choices=choices if has_choices else None, visible=has_choices and not csv_mode, value=current_dropdown_values)) - x_type.change(fn=select_axis, inputs=[x_type, x_values, x_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown]) - y_type.change(fn=select_axis, inputs=[y_type, y_values, y_values_dropdown], outputs=[fill_y_button, y_values, y_values_dropdown]) - z_type.change(fn=select_axis, inputs=[z_type, z_values, z_values_dropdown], outputs=[fill_z_button, z_values, z_values_dropdown]) + x_type.change(fn=select_axis, inputs=[x_type, x_values, x_values_dropdown, csv_mode], outputs=[fill_x_button, x_values, x_values_dropdown]) + y_type.change(fn=select_axis, inputs=[y_type, y_values, y_values_dropdown, csv_mode], outputs=[fill_y_button, y_values, y_values_dropdown]) + z_type.change(fn=select_axis, inputs=[z_type, z_values, z_values_dropdown, csv_mode], outputs=[fill_z_button, z_values, z_values_dropdown]) - def change_choice_mode(_csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown): - csv_mode.value = _csv_mode - _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values, x_values_dropdown) - _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values, y_values_dropdown) - _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values, z_values_dropdown) + def change_choice_mode(csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown): + _fill_x_button, _x_values, _x_values_dropdown = select_axis(x_type, x_values, x_values_dropdown, csv_mode) + _fill_y_button, _y_values, _y_values_dropdown = select_axis(y_type, y_values, y_values_dropdown, csv_mode) + _fill_z_button, _z_values, _z_values_dropdown = select_axis(z_type, z_values, z_values_dropdown, csv_mode) return _fill_x_button, _x_values, _x_values_dropdown, _fill_y_button, _y_values, _y_values_dropdown, _fill_z_button, _z_values, _z_values_dropdown csv_mode.change(fn=change_choice_mode, inputs=[csv_mode, x_type, x_values, x_values_dropdown, y_type, y_values, y_values_dropdown, z_type, z_values, z_values_dropdown], outputs=[fill_x_button, x_values, x_values_dropdown, fill_y_button, y_values, y_values_dropdown, fill_z_button, z_values, z_values_dropdown]) From dc5b5ee9c6beef9fa6ac9e8f9041063075e8d1e6 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:21:04 +0900 Subject: [PATCH 27/67] properly convert this into CSV string --- scripts/xyz_grid.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 254a75f4a..0350e52a5 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -577,17 +577,17 @@ class Script(scripts.Script): x_opt = self.current_axis_options[x_type] if x_opt.choices is not None and not csv_mode: - x_values = ",".join(x_values_dropdown) + x_values = list_to_csv_string(x_values_dropdown) xs = process_axis(x_opt, x_values, x_values_dropdown) y_opt = self.current_axis_options[y_type] if y_opt.choices is not None and not csv_mode: - y_values = ",".join(y_values_dropdown) + y_values = list_to_csv_string(y_values_dropdown) ys = process_axis(y_opt, y_values, y_values_dropdown) z_opt = self.current_axis_options[z_type] if z_opt.choices is not None and not csv_mode: - z_values = ",".join(z_values_dropdown) + z_values = list_to_csv_string(z_values_dropdown) zs = process_axis(z_opt, z_values, z_values_dropdown) # this could be moved to common code, but unlikely to be ever triggered anywhere else From bd4da4474bef5c9c1f690c62b971704ee73d2860 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:27:39 +0800 Subject: [PATCH 28/67] Add extra norm module into built-in lora ext refer to LyCORIS 1.9.0.dev6 add new option and module for training norm layer (Which is reported to be good for style) --- extensions-builtin/Lora/network.py | 7 +- extensions-builtin/Lora/network_norm.py | 29 +++++++++ extensions-builtin/Lora/networks.py | 64 ++++++++++++++++--- .../Lora/scripts/lora_script.py | 16 +++++ 4 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 extensions-builtin/Lora/network_norm.py diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 0a18d69eb..b7b890618 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -133,7 +133,7 @@ class NetworkModule: return 1.0 - def finalize_updown(self, updown, orig_weight, output_shape): + def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None): if self.bias is not None: updown = updown.reshape(self.bias.shape) updown += self.bias.to(orig_weight.device, dtype=orig_weight.dtype) @@ -145,7 +145,10 @@ class NetworkModule: if orig_weight.size().numel() == updown.size().numel(): updown = updown.reshape(orig_weight.shape) - return updown * self.calc_scale() * self.multiplier() + if ex_bias is None: + ex_bias = 0 + + return updown * self.calc_scale() * self.multiplier(), ex_bias * self.multiplier() def calc_updown(self, target): raise NotImplementedError() diff --git a/extensions-builtin/Lora/network_norm.py b/extensions-builtin/Lora/network_norm.py new file mode 100644 index 000000000..dab8b684d --- /dev/null +++ b/extensions-builtin/Lora/network_norm.py @@ -0,0 +1,29 @@ +import network + + +class ModuleTypeNorm(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["w_norm", "b_norm"]): + return NetworkModuleNorm(net, weights) + + return None + + +class NetworkModuleNorm(network.NetworkModule): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + print("NetworkModuleNorm") + + self.w_norm = weights.w.get("w_norm") + self.b_norm = weights.w.get("b_norm") + + def calc_updown(self, orig_weight): + output_shape = self.w_norm.shape + updown = self.w_norm.to(orig_weight.device, dtype=orig_weight.dtype) + + if self.b_norm is not None: + ex_bias = self.b_norm.to(orig_weight.device, dtype=orig_weight.dtype) + else: + ex_bias = None + + return self.finalize_updown(updown, orig_weight, output_shape, ex_bias) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 7e3415acb..74cefe435 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -7,6 +7,7 @@ import network_hada import network_ia3 import network_lokr import network_full +import network_norm import torch from typing import Union @@ -19,6 +20,7 @@ module_types = [ network_ia3.ModuleTypeIa3(), network_lokr.ModuleTypeLokr(), network_full.ModuleTypeFull(), + network_norm.ModuleTypeNorm(), ] @@ -31,6 +33,8 @@ suffix_conversion = { "resnets": { "conv1": "in_layers_2", "conv2": "out_layers_3", + "norm1": "in_layers_0", + "norm2": "out_layers_0", "time_emb_proj": "emb_layers_1", "conv_shortcut": "skip_connection", } @@ -258,20 +262,25 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No purge_networks_from_memory() -def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): +def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]): weights_backup = getattr(self, "network_weights_backup", None) + bias_backup = getattr(self, "network_bias_backup", None) - if weights_backup is None: + if weights_backup is None and bias_backup is None: return - if isinstance(self, torch.nn.MultiheadAttention): - self.in_proj_weight.copy_(weights_backup[0]) - self.out_proj.weight.copy_(weights_backup[1]) - else: - self.weight.copy_(weights_backup) + if weights_backup is not None: + if isinstance(self, torch.nn.MultiheadAttention): + self.in_proj_weight.copy_(weights_backup[0]) + self.out_proj.weight.copy_(weights_backup[1]) + else: + self.weight.copy_(weights_backup) + + if bias_backup is not None: + self.bias.copy_(bias_backup) -def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): +def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]): """ Applies the currently selected set of networks to the weights of torch layer self. If weights already have this particular set of networks applied, does nothing. @@ -294,6 +303,11 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn self.network_weights_backup = weights_backup + bias_backup = getattr(self, "network_bias_backup", None) + if bias_backup is None and getattr(self, 'bias', None) is not None: + bias_backup = self.bias.to(devices.cpu, copy=True) + self.network_bias_backup = bias_backup + if current_names != wanted_names: network_restore_weights_from_backup(self) @@ -301,13 +315,15 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn module = net.modules.get(network_layer_name, None) if module is not None and hasattr(self, 'weight'): with torch.no_grad(): - updown = module.calc_updown(self.weight) + updown, ex_bias = module.calc_updown(self.weight) if len(self.weight.shape) == 4 and self.weight.shape[1] == 9: # inpainting model. zero pad updown to make channel[1] 4 to 9 updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) self.weight += updown + if getattr(self, 'bias', None) is not None: + self.bias += ex_bias continue module_q = net.modules.get(network_layer_name + "_q_proj", None) @@ -397,6 +413,36 @@ def network_Conv2d_load_state_dict(self, *args, **kwargs): return torch.nn.Conv2d_load_state_dict_before_network(self, *args, **kwargs) +def network_GroupNorm_forward(self, input): + if shared.opts.lora_functional: + return network_forward(self, input, torch.nn.GroupNorm_forward_before_network) + + network_apply_weights(self) + + return torch.nn.GroupNorm_forward_before_network(self, input) + + +def network_GroupNorm_load_state_dict(self, *args, **kwargs): + network_reset_cached_weight(self) + + return torch.nn.GroupNorm_load_state_dict_before_network(self, *args, **kwargs) + + +def network_LayerNorm_forward(self, input): + if shared.opts.lora_functional: + return network_forward(self, input, torch.nn.LayerNorm_forward_before_network) + + network_apply_weights(self) + + return torch.nn.LayerNorm_forward_before_network(self, input) + + +def network_LayerNorm_load_state_dict(self, *args, **kwargs): + network_reset_cached_weight(self) + + return torch.nn.LayerNorm_load_state_dict_before_network(self, *args, **kwargs) + + def network_MultiheadAttention_forward(self, *args, **kwargs): network_apply_weights(self) diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 6ab8b6e7c..dc307f8cc 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -40,6 +40,18 @@ if not hasattr(torch.nn, 'Conv2d_forward_before_network'): if not hasattr(torch.nn, 'Conv2d_load_state_dict_before_network'): torch.nn.Conv2d_load_state_dict_before_network = torch.nn.Conv2d._load_from_state_dict +if not hasattr(torch.nn, 'GroupNorm_forward_before_network'): + torch.nn.GroupNorm_forward_before_network = torch.nn.GroupNorm.forward + +if not hasattr(torch.nn, 'GroupNorm_load_state_dict_before_network'): + torch.nn.GroupNorm_load_state_dict_before_network = torch.nn.GroupNorm._load_from_state_dict + +if not hasattr(torch.nn, 'LayerNorm_forward_before_network'): + torch.nn.LayerNorm_forward_before_network = torch.nn.LayerNorm.forward + +if not hasattr(torch.nn, 'LayerNorm_load_state_dict_before_network'): + torch.nn.LayerNorm_load_state_dict_before_network = torch.nn.LayerNorm._load_from_state_dict + if not hasattr(torch.nn, 'MultiheadAttention_forward_before_network'): torch.nn.MultiheadAttention_forward_before_network = torch.nn.MultiheadAttention.forward @@ -50,6 +62,10 @@ torch.nn.Linear.forward = networks.network_Linear_forward torch.nn.Linear._load_from_state_dict = networks.network_Linear_load_state_dict torch.nn.Conv2d.forward = networks.network_Conv2d_forward torch.nn.Conv2d._load_from_state_dict = networks.network_Conv2d_load_state_dict +torch.nn.GroupNorm.forward = networks.network_GroupNorm_forward +torch.nn.GroupNorm._load_from_state_dict = networks.network_GroupNorm_load_state_dict +torch.nn.LayerNorm.forward = networks.network_LayerNorm_forward +torch.nn.LayerNorm._load_from_state_dict = networks.network_LayerNorm_load_state_dict torch.nn.MultiheadAttention.forward = networks.network_MultiheadAttention_forward torch.nn.MultiheadAttention._load_from_state_dict = networks.network_MultiheadAttention_load_state_dict From a2b83050965a1a117f2762d3b5fa8b4841777e8f Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:35:04 +0800 Subject: [PATCH 29/67] return None if no ex_bias --- extensions-builtin/Lora/network.py | 6 +++--- extensions-builtin/Lora/networks.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index b7b890618..d8e8dfb7f 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -145,10 +145,10 @@ class NetworkModule: if orig_weight.size().numel() == updown.size().numel(): updown = updown.reshape(orig_weight.shape) - if ex_bias is None: - ex_bias = 0 + if ex_bias is not None: + ex_bias = ex_bias * self.multiplier() - return updown * self.calc_scale() * self.multiplier(), ex_bias * self.multiplier() + return updown * self.calc_scale() * self.multiplier(), ex_bias def calc_updown(self, target): raise NotImplementedError() diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 74cefe435..ba6211390 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -322,7 +322,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) self.weight += updown - if getattr(self, 'bias', None) is not None: + if ex_bias is not None and getattr(self, 'bias', None) is not None: self.bias += ex_bias continue From 5881dcb8873b3f87b9c6545e9cb8d1d77023f4fe Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:36:02 +0800 Subject: [PATCH 30/67] remove debug print --- extensions-builtin/Lora/network_norm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions-builtin/Lora/network_norm.py b/extensions-builtin/Lora/network_norm.py index dab8b684d..ce4501580 100644 --- a/extensions-builtin/Lora/network_norm.py +++ b/extensions-builtin/Lora/network_norm.py @@ -12,7 +12,6 @@ class ModuleTypeNorm(network.ModuleType): class NetworkModuleNorm(network.NetworkModule): def __init__(self, net: network.Network, weights: network.NetworkWeights): super().__init__(net, weights) - print("NetworkModuleNorm") self.w_norm = weights.w.get("w_norm") self.b_norm = weights.w.get("b_norm") From fa9370b7411166c19e8e386400dc4e6082f47b2d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 06:07:30 +0300 Subject: [PATCH 31/67] add refiner to StableDiffusionProcessing class write out correct model name in infotext, rather than the refiner model --- modules/processing.py | 38 +++++++++++++++++++++------ modules/processing_scripts/refiner.py | 16 ++++------- modules/sd_samplers_common.py | 2 +- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 6ad105d7d..b47ddaa8d 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -111,7 +111,7 @@ class StableDiffusionProcessing: cached_uc = [None, None] cached_c = [None, None] - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = None, tiling: bool = None, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_min_uncond: float = 0.0, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = None, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = None, tiling: bool = None, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_min_uncond: float = 0.0, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = None, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, refiner_checkpoint: str = None, refiner_switch_at: float = None, script_args: list = None): if sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) @@ -153,10 +153,14 @@ class StableDiffusionProcessing: self.s_noise = s_noise if s_noise is not None else opts.s_noise self.override_settings = {k: v for k, v in (override_settings or {}).items() if k not in shared.restricted_opts} self.override_settings_restore_afterwards = override_settings_restore_afterwards + self.refiner_checkpoint = refiner_checkpoint + self.refiner_switch_at = refiner_switch_at + self.is_using_inpainting_conditioning = False self.disable_extra_networks = False self.token_merging_ratio = 0 self.token_merging_ratio_hr = 0 + self.refiner_checkpoint_info = None if not seed_enable_extras: self.subseed = -1 @@ -191,6 +195,11 @@ class StableDiffusionProcessing: self.user = None + self.sd_model_name = None + self.sd_model_hash = None + self.sd_vae_name = None + self.sd_vae_hash = None + @property def sd_model(self): return shared.sd_model @@ -408,7 +417,10 @@ class Processed: self.batch_size = p.batch_size self.restore_faces = p.restore_faces self.face_restoration_model = opts.face_restoration_model if p.restore_faces else None - self.sd_model_hash = shared.sd_model.sd_model_hash + self.sd_model_name = p.sd_model_name + self.sd_model_hash = p.sd_model_hash + self.sd_vae_name = p.sd_vae_name + self.sd_vae_hash = p.sd_vae_hash self.seed_resize_from_w = p.seed_resize_from_w self.seed_resize_from_h = p.seed_resize_from_h self.denoising_strength = getattr(p, 'denoising_strength', None) @@ -459,7 +471,10 @@ class Processed: "batch_size": self.batch_size, "restore_faces": self.restore_faces, "face_restoration_model": self.face_restoration_model, + "sd_model_name": self.sd_model_name, "sd_model_hash": self.sd_model_hash, + "sd_vae_name": self.sd_vae_name, + "sd_vae_hash": self.sd_vae_hash, "seed_resize_from_w": self.seed_resize_from_w, "seed_resize_from_h": self.seed_resize_from_h, "denoising_strength": self.denoising_strength, @@ -578,10 +593,10 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], "Face restoration": opts.face_restoration_model if p.restore_faces else None, "Size": f"{p.width}x{p.height}", - "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), - "Model": (None if not opts.add_model_name_to_info else shared.sd_model.sd_checkpoint_info.name_for_extra), - "VAE hash": p.loaded_vae_hash if opts.add_model_hash_to_info else None, - "VAE": p.loaded_vae_name if opts.add_model_name_to_info else None, + "Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None, + "Model": p.sd_model_name if opts.add_model_name_to_info else None, + "VAE hash": p.sd_vae_hash if opts.add_model_hash_to_info else None, + "VAE": p.sd_vae_name if opts.add_model_name_to_info else None, "Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])), "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength), "Seed resize from": (None if p.seed_resize_from_w <= 0 or p.seed_resize_from_h <= 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), @@ -670,8 +685,15 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.tiling is None: p.tiling = opts.tiling - p.loaded_vae_name = sd_vae.get_loaded_vae_name() - p.loaded_vae_hash = sd_vae.get_loaded_vae_hash() + if p.refiner_checkpoint not in (None, "", "None"): + p.refiner_checkpoint_info = sd_models.get_closet_checkpoint_match(p.refiner_checkpoint) + if p.refiner_checkpoint_info is None: + raise Exception(f'Could not find checkpoint with name {p.refiner_checkpoint}') + + p.sd_model_name = shared.sd_model.sd_checkpoint_info.name_for_extra + p.sd_model_hash = shared.sd_model.sd_model_hash + p.sd_vae_name = sd_vae.get_loaded_vae_name() + p.sd_vae_hash = sd_vae.get_loaded_vae_hash() modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py index 773ec5d0b..7b946d056 100644 --- a/modules/processing_scripts/refiner.py +++ b/modules/processing_scripts/refiner.py @@ -41,15 +41,9 @@ class ScriptRefiner(scripts.Script): def before_process(self, p, enable_refiner, refiner_checkpoint, refiner_switch_at): # the actual implementation is in sd_samplers_common.py, apply_refiner - p.refiner_checkpoint_info = None - p.refiner_switch_at = None - if not enable_refiner or refiner_checkpoint in (None, "", "None"): - return - - refiner_checkpoint_info = sd_models.get_closet_checkpoint_match(refiner_checkpoint) - if refiner_checkpoint_info is None: - raise Exception(f'Could not find checkpoint with name {refiner_checkpoint}') - - p.refiner_checkpoint_info = refiner_checkpoint_info - p.refiner_switch_at = refiner_switch_at + p.refiner_checkpoint_info = None + p.refiner_switch_at = None + else: + p.refiner_checkpoint = refiner_checkpoint + p.refiner_switch_at = refiner_switch_at diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 40c7aae09..380cdd5f7 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -145,7 +145,7 @@ def apply_refiner(cfg_denoiser): refiner_switch_at = cfg_denoiser.p.refiner_switch_at refiner_checkpoint_info = cfg_denoiser.p.refiner_checkpoint_info - if refiner_switch_at is not None and completed_ratio <= refiner_switch_at: + if refiner_switch_at is not None and completed_ratio < refiner_switch_at: return False if refiner_checkpoint_info is None or shared.sd_model.sd_checkpoint_info == refiner_checkpoint_info: From 0e3bac8132e63a9adfca0223b4791f353ecb1057 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 13 Aug 2023 04:12:37 +0900 Subject: [PATCH 32/67] rephrase and move --- scripts/xyz_grid.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 0350e52a5..da0e48aa5 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -420,10 +420,11 @@ class Script(scripts.Script): with gr.Column(): include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) - with gr.Column(): - csv_mode = gr.Checkbox(label='CSV mode', value=False, elem_id=self.elem_id("CSV mode")) with gr.Column(): margin_size = gr.Slider(label="Grid margins (px)", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) + with gr.Column(): + csv_mode = gr.Checkbox(label='Use text inputs instead of dropdowns', value=False, elem_id=self.elem_id("csv_mode")) + with gr.Row(variant="compact", elem_id="swap_axes"): swap_xy_axes_button = gr.Button(value="Swap X/Y axes", elem_id="xy_grid_swap_axes_button") From 599f61a1e0bddf463dd3c6adb84509b3d9db1941 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 08:24:16 +0300 Subject: [PATCH 33/67] use dataclass for StableDiffusionProcessing --- modules/processing.py | 310 ++++++++++++++++++---------------- modules/sd_samplers_common.py | 5 +- 2 files changed, 172 insertions(+), 143 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index b47ddaa8d..007a4e05a 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -1,9 +1,11 @@ +from __future__ import annotations import json import logging import math import os import sys import hashlib +from dataclasses import dataclass, field import torch import numpy as np @@ -11,7 +13,7 @@ from PIL import Image, ImageOps import random import cv2 from skimage import exposure -from typing import Any, Dict, List +from typing import Any import modules.sd_hijack from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, extra_networks, sd_vae_approx, scripts, sd_samplers_common, sd_unet, errors, rng @@ -104,106 +106,126 @@ def txt2img_image_conditioning(sd_model, x, width, height): return x.new_zeros(x.shape[0], 5, 1, 1, dtype=x.dtype, device=x.device) +@dataclass(repr=False) class StableDiffusionProcessing: - """ - The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing - """ + sd_model: object = None + outpath_samples: str = None + outpath_grids: str = None + prompt: str = "" + prompt_for_display: str = None + negative_prompt: str = "" + styles: list[str] = field(default_factory=list) + seed: int = -1 + subseed: int = -1 + subseed_strength: float = 0 + seed_resize_from_h: int = -1 + seed_resize_from_w: int = -1 + seed_enable_extras: bool = True + sampler_name: str = None + batch_size: int = 1 + n_iter: int = 1 + steps: int = 50 + cfg_scale: float = 7.0 + width: int = 512 + height: int = 512 + restore_faces: bool = None + tiling: bool = None + do_not_save_samples: bool = False + do_not_save_grid: bool = False + extra_generation_params: dict[str, Any] = None + overlay_images: list = None + eta: float = None + do_not_reload_embeddings: bool = False + denoising_strength: float = 0 + ddim_discretize: str = None + s_min_uncond: float = None + s_churn: float = None + s_tmax: float = None + s_tmin: float = None + s_noise: float = None + override_settings: dict[str, Any] = None + override_settings_restore_afterwards: bool = True + sampler_index: int = None + refiner_checkpoint: str = None + refiner_switch_at: float = None + token_merging_ratio = 0 + token_merging_ratio_hr = 0 + disable_extra_networks: bool = False + + script_args: list = None + cached_uc = [None, None] cached_c = [None, None] - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = None, tiling: bool = None, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_min_uncond: float = 0.0, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = None, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, refiner_checkpoint: str = None, refiner_switch_at: float = None, script_args: list = None): - if sampler_index is not None: + sampler: sd_samplers_common.Sampler | None = field(default=None, init=False) + is_using_inpainting_conditioning: bool = field(default=False, init=False) + paste_to: tuple | None = field(default=None, init=False) + + is_hr_pass: bool = field(default=False, init=False) + + c: tuple = field(default=None, init=False) + uc: tuple = field(default=None, init=False) + + rng: rng.ImageRNG | None = field(default=None, init=False) + step_multiplier: int = field(default=1, init=False) + color_corrections: list = field(default=None, init=False) + + scripts: list = field(default=None, init=False) + all_prompts: list = field(default=None, init=False) + all_negative_prompts: list = field(default=None, init=False) + all_seeds: list = field(default=None, init=False) + all_subseeds: list = field(default=None, init=False) + iteration: int = field(default=0, init=False) + main_prompt: str = field(default=None, init=False) + main_negative_prompt: str = field(default=None, init=False) + + prompts: list = field(default=None, init=False) + negative_prompts: list = field(default=None, init=False) + seeds: list = field(default=None, init=False) + subseeds: list = field(default=None, init=False) + extra_network_data: dict = field(default=None, init=False) + + user: str = field(default=None, init=False) + + sd_model_name: str = field(default=None, init=False) + sd_model_hash: str = field(default=None, init=False) + sd_vae_name: str = field(default=None, init=False) + sd_vae_hash: str = field(default=None, init=False) + + def __post_init__(self): + if self.sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) - self.outpath_samples: str = outpath_samples - self.outpath_grids: str = outpath_grids - self.prompt: str = prompt - self.prompt_for_display: str = None - self.negative_prompt: str = (negative_prompt or "") - self.styles: list = styles or [] - self.seed: int = seed - self.subseed: int = subseed - self.subseed_strength: float = subseed_strength - self.seed_resize_from_h: int = seed_resize_from_h - self.seed_resize_from_w: int = seed_resize_from_w - self.sampler_name: str = sampler_name - self.batch_size: int = batch_size - self.n_iter: int = n_iter - self.steps: int = steps - self.cfg_scale: float = cfg_scale - self.width: int = width - self.height: int = height - self.restore_faces: bool = restore_faces - self.tiling: bool = tiling - self.do_not_save_samples: bool = do_not_save_samples - self.do_not_save_grid: bool = do_not_save_grid - self.extra_generation_params: dict = extra_generation_params or {} - self.overlay_images = overlay_images - self.eta = eta - self.do_not_reload_embeddings = do_not_reload_embeddings - self.paste_to = None - self.color_corrections = None - self.denoising_strength: float = denoising_strength self.sampler_noise_scheduler_override = None - self.ddim_discretize = ddim_discretize or opts.ddim_discretize - self.s_min_uncond = s_min_uncond or opts.s_min_uncond - self.s_churn = s_churn or opts.s_churn - self.s_tmin = s_tmin or opts.s_tmin - self.s_tmax = (s_tmax if s_tmax is not None else opts.s_tmax) or float('inf') - self.s_noise = s_noise if s_noise is not None else opts.s_noise - self.override_settings = {k: v for k, v in (override_settings or {}).items() if k not in shared.restricted_opts} - self.override_settings_restore_afterwards = override_settings_restore_afterwards - self.refiner_checkpoint = refiner_checkpoint - self.refiner_switch_at = refiner_switch_at + self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond + self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn + self.s_tmin = self.s_tmin if self.s_tmin is not None else opts.s_tmin + self.s_tmax = (self.s_tmax if self.s_tmax is not None else opts.s_tmax) or float('inf') + self.s_noise = self.s_noise if self.s_noise is not None else opts.s_noise + + self.extra_generation_params = self.extra_generation_params or {} + self.override_settings = self.override_settings or {} + self.script_args = self.script_args or {} - self.is_using_inpainting_conditioning = False - self.disable_extra_networks = False - self.token_merging_ratio = 0 - self.token_merging_ratio_hr = 0 self.refiner_checkpoint_info = None - if not seed_enable_extras: + if not self.seed_enable_extras: self.subseed = -1 self.subseed_strength = 0 self.seed_resize_from_h = 0 self.seed_resize_from_w = 0 - self.scripts = None - self.script_args = script_args - self.all_prompts = None - self.all_negative_prompts = None - self.all_seeds = None - self.all_subseeds = None - self.iteration = 0 - self.is_hr_pass = False - self.sampler = None - self.main_prompt = None - self.main_negative_prompt = None - - self.prompts = None - self.negative_prompts = None - self.extra_network_data = None - self.seeds = None - self.subseeds = None - - self.step_multiplier = 1 self.cached_uc = StableDiffusionProcessing.cached_uc self.cached_c = StableDiffusionProcessing.cached_c - self.uc = None - self.c = None - self.rng: rng.ImageRNG = None - - self.user = None - - self.sd_model_name = None - self.sd_model_hash = None - self.sd_vae_name = None - self.sd_vae_hash = None @property def sd_model(self): return shared.sd_model + @sd_model.setter + def sd_model(self, value): + pass + def txt2img_image_conditioning(self, x, width=None, height=None): self.is_using_inpainting_conditioning = self.sd_model.model.conditioning_key in {'hybrid', 'concat'} @@ -932,49 +954,51 @@ def old_hires_fix_first_pass_dimensions(width, height): return width, height +@dataclass(repr=False) class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): - sampler = None + enable_hr: bool = False + denoising_strength: float = 0.75 + firstphase_width: int = 0 + firstphase_height: int = 0 + hr_scale: float = 2.0 + hr_upscaler: str = None + hr_second_pass_steps: int = 0 + hr_resize_x: int = 0 + hr_resize_y: int = 0 + hr_checkpoint_name: str = None + hr_sampler_name: str = None + hr_prompt: str = '' + hr_negative_prompt: str = '' + cached_hr_uc = [None, None] cached_hr_c = [None, None] - def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, hr_checkpoint_name: str = None, hr_sampler_name: str = None, hr_prompt: str = '', hr_negative_prompt: str = '', **kwargs): - super().__init__(**kwargs) - self.enable_hr = enable_hr - self.denoising_strength = denoising_strength - self.hr_scale = hr_scale - self.hr_upscaler = hr_upscaler - self.hr_second_pass_steps = hr_second_pass_steps - self.hr_resize_x = hr_resize_x - self.hr_resize_y = hr_resize_y - self.hr_upscale_to_x = hr_resize_x - self.hr_upscale_to_y = hr_resize_y - self.hr_checkpoint_name = hr_checkpoint_name - self.hr_checkpoint_info = None - self.hr_sampler_name = hr_sampler_name - self.hr_prompt = hr_prompt - self.hr_negative_prompt = hr_negative_prompt - self.all_hr_prompts = None - self.all_hr_negative_prompts = None - self.latent_scale_mode = None + hr_checkpoint_info: dict = field(default=None, init=False) + hr_upscale_to_x: int = field(default=0, init=False) + hr_upscale_to_y: int = field(default=0, init=False) + truncate_x: int = field(default=0, init=False) + truncate_y: int = field(default=0, init=False) + applied_old_hires_behavior_to: tuple = field(default=None, init=False) + latent_scale_mode: dict = field(default=None, init=False) + hr_c: tuple | None = field(default=None, init=False) + hr_uc: tuple | None = field(default=None, init=False) + all_hr_prompts: list = field(default=None, init=False) + all_hr_negative_prompts: list = field(default=None, init=False) + hr_prompts: list = field(default=None, init=False) + hr_negative_prompts: list = field(default=None, init=False) + hr_extra_network_data: list = field(default=None, init=False) - if firstphase_width != 0 or firstphase_height != 0: + def __post_init__(self): + super().__post_init__() + + if self.firstphase_width != 0 or self.firstphase_height != 0: self.hr_upscale_to_x = self.width self.hr_upscale_to_y = self.height - self.width = firstphase_width - self.height = firstphase_height - - self.truncate_x = 0 - self.truncate_y = 0 - self.applied_old_hires_behavior_to = None - - self.hr_prompts = None - self.hr_negative_prompts = None - self.hr_extra_network_data = None + self.width = self.firstphase_width + self.height = self.firstphase_height self.cached_hr_uc = StableDiffusionProcessingTxt2Img.cached_hr_uc self.cached_hr_c = StableDiffusionProcessingTxt2Img.cached_hr_c - self.hr_c = None - self.hr_uc = None def calculate_target_resolution(self): if opts.use_old_hires_fix_width_height and self.applied_old_hires_behavior_to != (self.width, self.height): @@ -1252,7 +1276,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): return super().get_conds() - def parse_extra_network_prompts(self): res = super().parse_extra_network_prompts() @@ -1265,32 +1288,37 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): return res +@dataclass(repr=False) class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): - sampler = None + init_images: list = None + resize_mode: int = 0 + denoising_strength: float = 0.75 + image_cfg_scale: float = None + mask: Any = None + mask_blur_x: int = 4 + mask_blur_y: int = 4 + mask_blur: int = None + inpainting_fill: int = 0 + inpaint_full_res: bool = True + inpaint_full_res_padding: int = 0 + inpainting_mask_invert: int = 0 + initial_noise_multiplier: float = None + latent_mask: Image = None - def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.75, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = None, mask_blur_x: int = 4, mask_blur_y: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, **kwargs): - super().__init__(**kwargs) + image_mask: Any = field(default=None, init=False) - self.init_images = init_images - self.resize_mode: int = resize_mode - self.denoising_strength: float = denoising_strength - self.image_cfg_scale: float = image_cfg_scale if shared.sd_model.cond_stage_key == "edit" else None - self.init_latent = None - self.image_mask = mask - self.latent_mask = None - self.mask_for_overlay = None - self.mask_blur_x = mask_blur_x - self.mask_blur_y = mask_blur_y - if mask_blur is not None: - self.mask_blur = mask_blur - self.inpainting_fill = inpainting_fill - self.inpaint_full_res = inpaint_full_res - self.inpaint_full_res_padding = inpaint_full_res_padding - self.inpainting_mask_invert = inpainting_mask_invert - self.initial_noise_multiplier = opts.initial_noise_multiplier if initial_noise_multiplier is None else initial_noise_multiplier + nmask: torch.Tensor = field(default=None, init=False) + image_conditioning: torch.Tensor = field(default=None, init=False) + init_img_hash: str = field(default=None, init=False) + mask_for_overlay: Image = field(default=None, init=False) + init_latent: torch.Tensor = field(default=None, init=False) + + def __post_init__(self): + super().__post_init__() + + self.image_mask = self.mask self.mask = None - self.nmask = None - self.image_conditioning = None + self.initial_noise_multiplier = opts.initial_noise_multiplier if self.initial_noise_multiplier is None else self.initial_noise_multiplier @property def mask_blur(self): @@ -1300,15 +1328,13 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): @mask_blur.setter def mask_blur(self, value): - self.mask_blur_x = value - self.mask_blur_y = value - - @mask_blur.deleter - def mask_blur(self): - del self.mask_blur_x - del self.mask_blur_y + if isinstance(value, int): + self.mask_blur_x = value + self.mask_blur_y = value def init(self, all_prompts, all_seeds, all_subseeds): + self.image_cfg_scale: float = self.image_cfg_scale if shared.sd_model.cond_stage_key == "edit" else None + self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) crop_region = None diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 380cdd5f7..09d1e11e3 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -305,5 +305,8 @@ class Sampler: current_iter_seeds = p.all_seeds[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] return BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=current_iter_seeds) + def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): + raise NotImplementedError() - + def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): + raise NotImplementedError() From 7fa5ee54b15904bef6598800df76ba1291d44ec6 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 02:32:54 -0400 Subject: [PATCH 34/67] Support search and display of hashes for all extra network items --- extensions-builtin/Lora/ui_extra_networks_lora.py | 3 ++- modules/ui_extra_networks_checkpoints.py | 1 + modules/ui_extra_networks_hypernets.py | 6 +++++- modules/ui_extra_networks_textual_inversion.py | 3 ++- modules/ui_extra_networks_user_metadata.py | 4 +++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 3629e5c0c..55409a782 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -25,9 +25,10 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): item = { "name": name, "filename": lora_on_disk.filename, + "shorthash": lora_on_disk.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), - "search_term": self.search_terms_from_path(lora_on_disk.filename), + "search_term": self.search_terms_from_path(lora_on_disk.filename) + " " + (lora_on_disk.hash or ""), "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": lora_on_disk.metadata, "sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)}, diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 778850222..ebb5249f7 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -19,6 +19,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): return { "name": checkpoint.name_for_extra, "filename": checkpoint.filename, + "shorthash": checkpoint.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 514a45624..4cedf0851 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -2,6 +2,7 @@ import os from modules import shared, ui_extra_networks from modules.ui_extra_networks import quote_js +from modules.hashes import sha256_from_cache class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): @@ -14,13 +15,16 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): def create_item(self, name, index=None, enable_filter=True): full_path = shared.hypernetworks[name] path, ext = os.path.splitext(full_path) + sha256 = sha256_from_cache(full_path, f'hypernet/{name}') + shorthash = sha256[0:10] if sha256 else None return { "name": name, "filename": full_path, + "shorthash": shorthash, "preview": self.find_preview(path), "description": self.find_description(path), - "search_term": self.search_terms_from_path(path), + "search_term": self.search_terms_from_path(path) + " " + (sha256 or ""), "prompt": quote_js(f""), "local_preview": f"{path}.preview.{shared.opts.samples_format}", "sort_keys": {'default': index, **self.get_sort_keys(path + ext)}, diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 73134698e..55ef0ea7b 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -19,9 +19,10 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): return { "name": name, "filename": embedding.filename, + "shorthash": embedding.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), - "search_term": self.search_terms_from_path(embedding.filename), + "search_term": self.search_terms_from_path(embedding.filename) + " " + (embedding.hash or ""), "prompt": quote_js(embedding.name), "local_preview": f"{path}.preview.{shared.opts.samples_format}", "sort_keys": {'default': index, **self.get_sort_keys(embedding.filename)}, diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index cda471e4e..b11622a1a 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -93,11 +93,13 @@ class UserMetadataEditor: item = self.page.items.get(name, {}) try: filename = item["filename"] + shorthash = item.get("shorthash", None) stats = os.stat(filename) params = [ ('Filename: ', os.path.basename(filename)), ('File size: ', sysinfo.pretty_bytes(stats.st_size)), + ('Hash: ', shorthash), ('Modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')), ] @@ -115,7 +117,7 @@ class UserMetadataEditor: errors.display(e, f"reading metadata info for {name}") params = [] - table = '' + "".join(f"" for name, value in params) + '' + table = '' + "".join(f"" for name, value in params if value is not None) + '' return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name), user_metadata.get('notes', '') From 822597db49218de17e105e62075096284dfcfd41 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 04:16:48 -0400 Subject: [PATCH 35/67] Encode batches separately Significantly reduces VRAM. This makes encoding more inline with how decoding currently functions. --- modules/sd_samplers_common.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 09d1e11e3..f9d034ca1 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -92,7 +92,15 @@ def images_tensor_to_samples(image, approximation=None, model=None): model = shared.sd_model image = image.to(shared.device, dtype=devices.dtype_vae) image = image * 2 - 1 - x_latent = model.get_first_stage_encoding(model.encode_first_stage(image)) + if len(image) > 1: + x_latent = torch.stack([ + model.get_first_stage_encoding( + model.encode_first_stage(torch.unsqueeze(img, 0)) + )[0] + for img in image + ]) + else: + x_latent = model.get_first_stage_encoding(model.encode_first_stage(image)) return x_latent From 69f49c8d394220331eaa6609825b477eed60e0f4 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 04:40:34 -0400 Subject: [PATCH 36/67] Clear sampler before decoding images More significant VRAM reduction. --- modules/processing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 007a4e05a..16104f92c 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -1192,6 +1192,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) + self.sampler = None + devices.torch_gc() + decoded_samples = decode_latent_batch(self.sd_model, samples, target_device=devices.cpu, check_for_nans=True) self.is_hr_pass = False From 1ae9dacb4b036a6cb4b5fb9b9ff030962f43908e Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 07:57:29 -0400 Subject: [PATCH 37/67] Add DPM-Solver++(3M) SDE --- modules/launch_utils.py | 2 +- modules/sd_samplers_kdiffusion.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 65eb684ff..e30fbac80 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -319,7 +319,7 @@ def prepare_environment(): stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf") stable_diffusion_xl_commit_hash = os.environ.get('STABLE_DIFFUSION_XL_COMMIT_HASH', "5c10deee76adad0032b412294130090932317a87") - k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "c9fe758757e022f05ca5a53fa8fac28889e4f1cf") + k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "ab527a9a6d347f364e3d185ba6d714e22d80cb3c") codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 1f8e9c4b9..a48a563f5 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -22,6 +22,9 @@ samplers_k_diffusion = [ ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), + ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {"brownian_noise": True}), + ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), + ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {"uses_ensd": True}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {"uses_ensd": True}), ('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}), From 60a74051656e1e430aa7b466cfee8c13c6dc1a12 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:06:40 -0400 Subject: [PATCH 38/67] Update description of eta setting --- modules/shared_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 9ae51f186..96db759b9 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -285,7 +285,7 @@ options_templates.update(options_section(('ui', "Live previews"), { options_templates.update(options_section(('sampler-params', "Sampler parameters"), { "hide_samplers": OptionInfo([], "Hide samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in shared_items.list_samplers()]}).needs_reload_ui(), "eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta DDIM').info("noise multiplier; higher = more unperdictable results"), - "eta_ancestral": OptionInfo(1.0, "Eta for ancestral samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta').info("noise multiplier; applies to Euler a and other samplers that have a in them"), + "eta_ancestral": OptionInfo(1.0, "Eta for k-diffusion samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext='Eta').info("noise multiplier; currently only applies to ancestral samplers (i.e. Euler a) and SDE samplers"), "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform', 'quad']}), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 100.0, "step": 0.01}, infotext='Sigma churn').info('amount of stochasticity; only applies to Euler, Heun, and DPM2'), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 10.0, "step": 0.01}, infotext='Sigma tmin').info('enable stochasticity; start value of the sigma range; only applies to Euler, Heun, and DPM2'), From d8419762c1454ba51baa710d9ce8e762efc056ef Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 15:07:37 +0300 Subject: [PATCH 39/67] Lora: output warnings in UI rather than fail for unfitting loras; switch to logging for error output in console --- .../Lora/extra_networks_lora.py | 12 +++- extensions-builtin/Lora/networks.py | 59 +++++++++++-------- .../Lora/scripts/lora_script.py | 6 +- modules/processing.py | 13 ++-- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index ba2945c6f..32e32cab6 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -1,4 +1,4 @@ -from modules import extra_networks, shared +from modules import extra_networks, shared, sd_hijack import networks @@ -6,9 +6,14 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): def __init__(self): super().__init__('lora') + self.errors = {} + """mapping of network names to the number of errors the network had during operation""" + def activate(self, p, params_list): additional = shared.opts.sd_lora + self.errors.clear() + if additional != "None" and additional in networks.available_networks and not any(x for x in params_list if x.items[0] == additional): p.all_prompts = [x + f"" for x in p.all_prompts] params_list.append(extra_networks.ExtraNetworkParams(items=[additional, shared.opts.extra_networks_default_multiplier])) @@ -56,4 +61,7 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes) def deactivate(self, p): - pass + if self.errors: + p.comment("Networks with errors: " + ", ".join(f"{k} ({v})" for k, v in self.errors.items())) + + self.errors.clear() diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index ba6211390..c252ed9e6 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -1,3 +1,4 @@ +import logging import os import re @@ -194,7 +195,7 @@ def load_network(name, network_on_disk): net.modules[key] = net_module if keys_failed_to_match: - print(f"Failed to match keys when loading network {network_on_disk.filename}: {keys_failed_to_match}") + logging.debug(f"Network {network_on_disk.filename} didn't match keys: {keys_failed_to_match}") return net @@ -207,7 +208,6 @@ def purge_networks_from_memory(): devices.torch_gc() - def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=None): already_loaded = {} @@ -248,7 +248,7 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No if net is None: failed_to_load_networks.append(name) - print(f"Couldn't find network with name {name}") + logging.info(f"Couldn't find network with name {name}") continue net.te_multiplier = te_multipliers[i] if te_multipliers else 1.0 @@ -257,7 +257,7 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No loaded_networks.append(net) if failed_to_load_networks: - sd_hijack.model_hijack.comments.append("Failed to find networks: " + ", ".join(failed_to_load_networks)) + sd_hijack.model_hijack.comments.append("Networks not found: " + ", ".join(failed_to_load_networks)) purge_networks_from_memory() @@ -314,17 +314,22 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn for net in loaded_networks: module = net.modules.get(network_layer_name, None) if module is not None and hasattr(self, 'weight'): - with torch.no_grad(): - updown, ex_bias = module.calc_updown(self.weight) + try: + with torch.no_grad(): + updown, ex_bias = module.calc_updown(self.weight) - if len(self.weight.shape) == 4 and self.weight.shape[1] == 9: - # inpainting model. zero pad updown to make channel[1] 4 to 9 - updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) + if len(self.weight.shape) == 4 and self.weight.shape[1] == 9: + # inpainting model. zero pad updown to make channel[1] 4 to 9 + updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) - self.weight += updown - if ex_bias is not None and getattr(self, 'bias', None) is not None: - self.bias += ex_bias - continue + self.weight += updown + if ex_bias is not None and getattr(self, 'bias', None) is not None: + self.bias += ex_bias + except RuntimeError as e: + logging.debug(f"Network {net.name} layer {network_layer_name}: {e}") + extra_network_lora.errors[net.name] = extra_network_lora.errors.get(net.name, 0) + 1 + + continue module_q = net.modules.get(network_layer_name + "_q_proj", None) module_k = net.modules.get(network_layer_name + "_k_proj", None) @@ -332,21 +337,28 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn module_out = net.modules.get(network_layer_name + "_out_proj", None) if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out: - with torch.no_grad(): - updown_q = module_q.calc_updown(self.in_proj_weight) - updown_k = module_k.calc_updown(self.in_proj_weight) - updown_v = module_v.calc_updown(self.in_proj_weight) - updown_qkv = torch.vstack([updown_q, updown_k, updown_v]) - updown_out = module_out.calc_updown(self.out_proj.weight) + try: + with torch.no_grad(): + updown_q = module_q.calc_updown(self.in_proj_weight) + updown_k = module_k.calc_updown(self.in_proj_weight) + updown_v = module_v.calc_updown(self.in_proj_weight) + updown_qkv = torch.vstack([updown_q, updown_k, updown_v]) + updown_out = module_out.calc_updown(self.out_proj.weight) - self.in_proj_weight += updown_qkv - self.out_proj.weight += updown_out - continue + self.in_proj_weight += updown_qkv + self.out_proj.weight += updown_out + + except RuntimeError as e: + logging.debug(f"Network {net.name} layer {network_layer_name}: {e}") + extra_network_lora.errors[net.name] = extra_network_lora.errors.get(net.name, 0) + 1 + + continue if module is None: continue - print(f'failed to calculate network weights for layer {network_layer_name}') + logging.debug(f"Network {net.name} layer {network_layer_name}: couldn't find supported operation") + extra_network_lora.errors[net.name] = extra_network_lora.errors.get(net.name, 0) + 1 self.network_current_names = wanted_names @@ -519,6 +531,7 @@ def infotext_pasted(infotext, params): if added: params["Prompt"] += "\n" + "".join(added) +extra_network_lora = None available_networks = {} available_network_aliases = {} diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index dc307f8cc..4c6e774a5 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -23,9 +23,9 @@ def unload(): def before_ui(): ui_extra_networks.register_page(ui_extra_networks_lora.ExtraNetworksPageLora()) - extra_network = extra_networks_lora.ExtraNetworkLora() - extra_networks.register_extra_network(extra_network) - extra_networks.register_extra_network_alias(extra_network, "lyco") + networks.extra_network_lora = extra_networks_lora.ExtraNetworkLora() + extra_networks.register_extra_network(networks.extra_network_lora) + extra_networks.register_extra_network_alias(networks.extra_network_lora, "lyco") if not hasattr(torch.nn, 'Linear_forward_before_network'): diff --git a/modules/processing.py b/modules/processing.py index 007a4e05a..10749aa21 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -157,6 +157,7 @@ class StableDiffusionProcessing: cached_uc = [None, None] cached_c = [None, None] + comments: dict = None sampler: sd_samplers_common.Sampler | None = field(default=None, init=False) is_using_inpainting_conditioning: bool = field(default=False, init=False) paste_to: tuple | None = field(default=None, init=False) @@ -196,6 +197,8 @@ class StableDiffusionProcessing: if self.sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) + self.comments = {} + self.sampler_noise_scheduler_override = None self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn @@ -226,6 +229,9 @@ class StableDiffusionProcessing: def sd_model(self, value): pass + def comment(self, text): + self.comments[text] = 1 + def txt2img_image_conditioning(self, x, width=None, height=None): self.is_using_inpainting_conditioning = self.sd_model.model.conditioning_key in {'hybrid', 'concat'} @@ -429,7 +435,7 @@ class Processed: self.subseed = subseed self.subseed_strength = p.subseed_strength self.info = info - self.comments = comments + self.comments = "".join(f"{comment}\n" for comment in p.comments) self.width = p.width self.height = p.height self.sampler_name = p.sampler_name @@ -720,8 +726,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() - comments = {} - p.setup_prompts() if type(seed) == list: @@ -801,7 +805,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.setup_conds() for comment in model_hijack.comments: - comments[comment] = 1 + p.comment(comment) p.extra_generation_params.update(model_hijack.extra_generation_params) @@ -930,7 +934,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: images_list=output_images, seed=p.all_seeds[0], info=infotexts[0], - comments="".join(f"{comment}\n" for comment in comments), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, infotexts=infotexts, From d1a70c3f0534b88665d55bdac1e6b48a63f7f035 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:22:24 -0400 Subject: [PATCH 40/67] Add s_noise param to more samplers --- modules/sd_samplers_common.py | 8 ++++---- modules/sd_samplers_kdiffusion.py | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 09d1e11e3..d2fb21f45 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -276,19 +276,19 @@ class Sampler: s_tmax = getattr(opts, 's_tmax', p.s_tmax) or self.s_tmax # 0 = inf s_noise = getattr(opts, 's_noise', p.s_noise) - if s_churn != self.s_churn: + if 's_churn' in extra_params_kwargs and s_churn != self.s_churn: extra_params_kwargs['s_churn'] = s_churn p.s_churn = s_churn p.extra_generation_params['Sigma churn'] = s_churn - if s_tmin != self.s_tmin: + if 's_tmin' in extra_params_kwargs and s_tmin != self.s_tmin: extra_params_kwargs['s_tmin'] = s_tmin p.s_tmin = s_tmin p.extra_generation_params['Sigma tmin'] = s_tmin - if s_tmax != self.s_tmax: + if 's_tmax' in extra_params_kwargs and s_tmax != self.s_tmax: extra_params_kwargs['s_tmax'] = s_tmax p.s_tmax = s_tmax p.extra_generation_params['Sigma tmax'] = s_tmax - if s_noise != self.s_noise: + if 's_noise' in extra_params_kwargs and s_noise != self.s_noise: extra_params_kwargs['s_noise'] = s_noise p.s_noise = s_noise p.extra_generation_params['Sigma noise'] = s_noise diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index a48a563f5..9f5dfd6dd 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -45,6 +45,12 @@ sampler_extra_params = { 'sample_euler': ['s_churn', 's_tmin', 's_tmax', 's_noise'], 'sample_heun': ['s_churn', 's_tmin', 's_tmax', 's_noise'], 'sample_dpm_2': ['s_churn', 's_tmin', 's_tmax', 's_noise'], + 'sample_dpm_fast': ['s_noise'], + 'sample_dpm_2_ancestral': ['s_noise'], + 'sample_dpmpp_2s_ancestral': ['s_noise'], + 'sample_dpmpp_sde': ['s_noise'], + 'sample_dpmpp_2m_sde': ['s_noise'], + 'sample_dpmpp_3m_sde': ['s_noise'], } k_diffusion_samplers_map = {x.name: x for x in samplers_data_k_diffusion} From f4757032e7a0663abe2695c95048fdfff3fc5e2f Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:24:28 -0400 Subject: [PATCH 41/67] Fix s_noise description --- modules/shared_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index 9ae51f186..279e9f546 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -290,7 +290,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 100.0, "step": 0.01}, infotext='Sigma churn').info('amount of stochasticity; only applies to Euler, Heun, and DPM2'), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 10.0, "step": 0.01}, infotext='Sigma tmin').info('enable stochasticity; start value of the sigma range; only applies to Euler, Heun, and DPM2'), 's_tmax': OptionInfo(0.0, "sigma tmax", gr.Slider, {"minimum": 0.0, "maximum": 999.0, "step": 0.01}, infotext='Sigma tmax').info("0 = inf; end value of the sigma range; only applies to Euler, Heun, and DPM2"), - 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.1, "step": 0.001}, infotext='Sigma noise').info('amount of additional noise to counteract loss of detail during sampling; only applies to Euler, Heun, and DPM2'), + 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.1, "step": 0.001}, infotext='Sigma noise').info('amount of additional noise to counteract loss of detail during sampling'), 'k_sched_type': OptionInfo("Automatic", "Scheduler type", gr.Dropdown, {"choices": ["Automatic", "karras", "exponential", "polyexponential"]}, infotext='Schedule type').info("lets you override the noise schedule for k-diffusion samplers; choosing Automatic disables the three parameters below"), 'sigma_min': OptionInfo(0.0, "sigma min", gr.Number, infotext='Schedule max sigma').info("0 = default (~0.03); minimum noise strength for k-diffusion noise scheduler"), 'sigma_max': OptionInfo(0.0, "sigma max", gr.Number, infotext='Schedule min sigma').info("0 = default (~14.6); maximum noise strength for k-diffusion noise scheduler"), From ac790fc49b314761a7ad711989d7cc88bba64578 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:46:07 -0400 Subject: [PATCH 42/67] Discard penultimate sigma for DPM-Solver++(3M) SDE --- modules/sd_samplers_kdiffusion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index a48a563f5..c8b81fa1c 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -22,9 +22,9 @@ samplers_k_diffusion = [ ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), - ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {"brownian_noise": True}), - ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), - ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), + ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'discard_next_to_last_sigma': True, "brownian_noise": True}), + ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "brownian_noise": True}), + ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {"uses_ensd": True}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {"uses_ensd": True}), ('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}), From 525b55b1e9dc589a1133a8031ed3b1646e5cccff Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 13 Aug 2023 09:08:34 -0400 Subject: [PATCH 43/67] Restore extra_params that was lost in merge --- modules/sd_samplers_kdiffusion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 1f8e9c4b9..9a89e7fa7 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -67,6 +67,8 @@ class KDiffusionSampler(sd_samplers_common.Sampler): def __init__(self, funcname, sd_model, options=None): super().__init__(funcname) + self.extra_params = sampler_extra_params.get(funcname, []) + self.options = options or {} self.func = funcname if callable(funcname) else getattr(k_diffusion.sampling, self.funcname) From db40d26d086b6e980bd28ce7eb3008ec09b1f0e8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 16:38:10 +0300 Subject: [PATCH 44/67] linter --- extensions-builtin/Lora/extra_networks_lora.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 32e32cab6..005ff32cb 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -1,4 +1,4 @@ -from modules import extra_networks, shared, sd_hijack +from modules import extra_networks, shared import networks From 3163d1269af7f9fd95382e58bb1581fd741b5119 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 16:51:21 +0300 Subject: [PATCH 45/67] fix for the broken run_git calls --- modules/launch_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e30fbac80..4fc254a25 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -173,9 +173,9 @@ def git_clone(url, dir, name, commithash=None): if current_hash == commithash: return - run_git('fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False) + run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False) - run_git('checkout', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) + run_git(dir, name, 'checkout', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) return From abfa4ad8bc995dcaf832c07a7cf75b6e295a8ca9 Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 8 May 2023 18:16:01 -0400 Subject: [PATCH 46/67] Use fixed size for sub-quadratic chunking on MPS Even if this causes chunks to be much smaller, performance isn't significantly impacted. This will usually reduce memory usage but should also help with poor performance when free memory is low. --- modules/sd_hijack_optimizations.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 0e810eec8..b3e712707 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -1,6 +1,7 @@ from __future__ import annotations import math import psutil +import platform import torch from torch import einsum @@ -427,7 +428,10 @@ def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_ qk_matmul_size_bytes = batch_x_heads * bytes_per_token * q_tokens * k_tokens if chunk_threshold is None: - chunk_threshold_bytes = int(get_available_vram() * 0.9) if q.device.type == 'mps' else int(get_available_vram() * 0.7) + if q.device.type == 'mps': + chunk_threshold_bytes = 268435456 * (2 if platform.processor() == 'i386' else bytes_per_token) + else: + chunk_threshold_bytes = int(get_available_vram() * 0.7) elif chunk_threshold == 0: chunk_threshold_bytes = None else: From 87dd685224b5f7dbbd832fc73cc08e7e470c9f28 Mon Sep 17 00:00:00 2001 From: brkirch Date: Sun, 21 May 2023 05:00:27 -0400 Subject: [PATCH 47/67] Make sub-quadratic the default for MPS --- modules/sd_hijack_optimizations.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index b3e712707..7f9e328d0 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -95,7 +95,10 @@ class SdOptimizationSdp(SdOptimizationSdpNoMem): class SdOptimizationSubQuad(SdOptimization): name = "sub-quadratic" cmd_opt = "opt_sub_quad_attention" - priority = 10 + + @property + def priority(self): + return 1000 if shared.device.type == 'mps' else 10 def apply(self): ldm.modules.attention.CrossAttention.forward = sub_quad_attention_forward @@ -121,7 +124,7 @@ class SdOptimizationInvokeAI(SdOptimization): @property def priority(self): - return 1000 if not torch.cuda.is_available() else 10 + return 1000 if shared.device.type != 'mps' and not torch.cuda.is_available() else 10 def apply(self): ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward_invokeAI From 2489252099c299bed49a9d4a39a4ead73b6b6f10 Mon Sep 17 00:00:00 2001 From: brkirch Date: Tue, 25 Jul 2023 03:03:06 -0400 Subject: [PATCH 48/67] `torch.empty` can create issues; use `torch.zeros` For MPS, using a tensor created with `torch.empty()` can cause `torch.baddbmm()` to include NaNs in the tensor it returns, even though `beta=0`. However, with a tensor of shape [1,1,1], there should be a negligible performance difference between `torch.empty()` and `torch.zeros()` anyway, so it's better to just use `torch.zeros()` for this and avoid unnecessarily creating issues. --- modules/sub_quadratic_attention.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index 497568eb5..ae4ee4bbe 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -58,7 +58,7 @@ def _summarize_chunk( scale: float, ) -> AttnChunk: attn_weights = torch.baddbmm( - torch.empty(1, 1, 1, device=query.device, dtype=query.dtype), + torch.zeros(1, 1, 1, device=query.device, dtype=query.dtype), query, key.transpose(1,2), alpha=scale, @@ -121,7 +121,7 @@ def _get_attention_scores_no_kv_chunking( scale: float, ) -> Tensor: attn_scores = torch.baddbmm( - torch.empty(1, 1, 1, device=query.device, dtype=query.dtype), + torch.zeros(1, 1, 1, device=query.device, dtype=query.dtype), query, key.transpose(1,2), alpha=scale, From 9058620cec2788495d295f4e68ef2932d6d700e6 Mon Sep 17 00:00:00 2001 From: brkirch Date: Sat, 12 Aug 2023 04:44:16 -0400 Subject: [PATCH 49/67] `git checkout` with commit hash --- modules/launch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 4fc254a25..e77baa521 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -175,7 +175,7 @@ def git_clone(url, dir, name, commithash=None): run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False) - run_git(dir, name, 'checkout', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) + run_git(dir, name, f'checkout {commithash}', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) return From f4dbb0c820344798e3481d4104618b95594a3d10 Mon Sep 17 00:00:00 2001 From: brkirch Date: Thu, 20 Jul 2023 01:44:45 -0400 Subject: [PATCH 50/67] Change the repositories origin URLs when necessary --- modules/launch_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e77baa521..9eda7c9d3 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -173,6 +173,9 @@ def git_clone(url, dir, name, commithash=None): if current_hash == commithash: return + if run_git(dir, name, 'config --get remote.origin.url', None, f"Couldn't determine {name}'s origin URL", live=False).strip() != url: + run_git(dir, name, f'remote set-url origin "{url}"', None, f"Failed to set {name}'s origin URL", live=False) + run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False) run_git(dir, name, f'checkout {commithash}', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) From 232c931f4082ea73bbaca8f77469cfea9d5db459 Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 7 Aug 2023 10:33:43 -0400 Subject: [PATCH 51/67] Mac k-diffusion workarounds are no longer needed --- webui-macos-env.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index 6354e73ba..24bc5c426 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -12,8 +12,6 @@ fi export install_dir="$HOME" export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --no-half-vae --use-cpu interrogate" export TORCH_COMMAND="pip install torch==2.0.1 torchvision==0.15.2" -export K_DIFFUSION_REPO="https://github.com/brkirch/k-diffusion.git" -export K_DIFFUSION_COMMIT_HASH="51c9778f269cedb55a4d88c79c0246d35bdadb71" export PYTORCH_ENABLE_MPS_FALLBACK=1 #################################################################### From 5df535b7c2374c3324485faaea62fbdbffc71f71 Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 7 Aug 2023 10:20:10 -0400 Subject: [PATCH 52/67] Remove duplicate code for torchsde randn --- modules/mac_specific.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index bce527ccc..89256c5b0 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -52,9 +52,6 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): if has_mps: - # MPS fix for randn in torchsde - CondFunc('torchsde._brownian.brownian_interval._randn', lambda _, size, dtype, device, seed: torch.randn(size, dtype=dtype, device=torch.device("cpu"), generator=torch.Generator(torch.device("cpu")).manual_seed(int(seed))).to(device), lambda _, size, dtype, device, seed: device.type == 'mps') - if platform.mac_ver()[0].startswith("13.2."): # MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124) CondFunc('torch.nn.functional.linear', lambda _, input, weight, bias: (torch.matmul(input, weight.t()) + bias) if bias is not None else torch.matmul(input, weight.t()), lambda _, input, weight, bias: input.numel() > 10485760) From 2035cbbd5d6e7678450c701fce1a5de7d8bd7084 Mon Sep 17 00:00:00 2001 From: brkirch Date: Sat, 12 Aug 2023 06:01:36 -0400 Subject: [PATCH 53/67] Fix DDIM and PLMS samplers on MPS --- modules/sd_samplers_timesteps_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index 48d7e6491..d32e35213 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -11,7 +11,7 @@ from modules.models.diffusion.uni_pc import uni_pc def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta=0.0): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' else torch.float32) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) @@ -42,7 +42,7 @@ def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta= def plms(model, x, timesteps, extra_args=None, callback=None, disable=None): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' else torch.float32) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) extra_args = {} if extra_args is None else extra_args From f093c9d39d0fe9951a8f5c570027cecc68778ef2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 17:31:10 +0300 Subject: [PATCH 54/67] fix broken XYZ plot seeds add new callback for scripts to be used before processing --- modules/processing.py | 32 ++++++++++++++++++++++++++++-- modules/processing_scripts/seed.py | 2 +- modules/scripts.py | 17 +++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index fdf493598..743666552 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -152,7 +152,9 @@ class StableDiffusionProcessing: token_merging_ratio_hr = 0 disable_extra_networks: bool = False - script_args: list = None + scripts_value: scripts.ScriptRunner = field(default=None, init=False) + script_args_value: list = field(default=None, init=False) + scripts_setup_complete: bool = field(default=False, init=False) cached_uc = [None, None] cached_c = [None, None] @@ -171,7 +173,6 @@ class StableDiffusionProcessing: step_multiplier: int = field(default=1, init=False) color_corrections: list = field(default=None, init=False) - scripts: list = field(default=None, init=False) all_prompts: list = field(default=None, init=False) all_negative_prompts: list = field(default=None, init=False) all_seeds: list = field(default=None, init=False) @@ -229,6 +230,33 @@ class StableDiffusionProcessing: def sd_model(self, value): pass + @property + def scripts(self): + return self.scripts_value + + @scripts.setter + def scripts(self, value): + self.scripts_value = value + + if self.scripts_value and self.script_args_value and not self.scripts_setup_complete: + self.setup_scripts() + + @property + def script_args(self): + return self.script_args_value + + @script_args.setter + def script_args(self, value): + self.script_args_value = value + + if self.scripts_value and self.script_args_value and not self.scripts_setup_complete: + self.setup_scripts() + + def setup_scripts(self): + self.scripts_setup_complete = True + + self.scripts.setup_scrips(self) + def comment(self, text): self.comments[text] = 1 diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index cc90775aa..96b44dfbe 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -58,7 +58,7 @@ class ScriptSeed(scripts.ScriptBuiltin): return self.seed, subseed, subseed_strength - def before_process(self, p, seed, subseed, subseed_strength): + def setup(self, p, seed, subseed, subseed_strength): p.seed = seed if subseed_strength > 0: diff --git a/modules/scripts.py b/modules/scripts.py index c6459b45d..d4a9da947 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -106,9 +106,16 @@ class Script: pass + def setup(self, p, *args): + """For AlwaysVisible scripts, this function is called when the processing object is set up, before any processing starts. + args contains all values returned by components from ui(). + """ + pass + + def before_process(self, p, *args): """ - This function is called very early before processing begins for AlwaysVisible scripts. + This function is called very early during processing begins for AlwaysVisible scripts. You can modify the processing object (p) here, inject hooks, etc. args contains all values returned by components from ui() """ @@ -706,6 +713,14 @@ class ScriptRunner: except Exception: errors.report(f"Error running before_hr: {script.filename}", exc_info=True) + def setup_scrips(self, p): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.setup(p, *script_args) + except Exception: + errors.report(f"Error running setup: {script.filename}", exc_info=True) + scripts_txt2img: ScriptRunner = None scripts_img2img: ScriptRunner = None From 09ff5b5416e9e989cf2ddb2bab9129e27ed23f14 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 14 Aug 2023 01:03:49 +0900 Subject: [PATCH 55/67] Fix typo in launch_utils.py existance -> existence --- modules/launch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index e1c9cfbec..8d2256eec 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -291,7 +291,7 @@ def prepare_environment(): blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") try: - # the existance of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution + # the existence of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution os.remove(os.path.join(script_path, "tmp", "restart")) os.environ.setdefault('SD_WEBUI_RESTARTING', '1') except OSError: From 16781ba09abe1494993f819b91ea0b88c48903b7 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 20:15:20 +0300 Subject: [PATCH 56/67] fix 2 for git code botched by previous PRs --- modules/launch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 4fc254a25..e77baa521 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -175,7 +175,7 @@ def git_clone(url, dir, name, commithash=None): run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False) - run_git(dir, name, 'checkout', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) + run_git(dir, name, f'checkout {commithash}', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True) return From 007ecfbb29771aa7cdcf0263ab1811bc75fa5446 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 13 Aug 2023 21:01:13 +0300 Subject: [PATCH 57/67] also use setup callback for the refiner instead of before_process --- modules/processing_scripts/refiner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py index 7b946d056..3c5b37d25 100644 --- a/modules/processing_scripts/refiner.py +++ b/modules/processing_scripts/refiner.py @@ -38,7 +38,7 @@ class ScriptRefiner(scripts.Script): return enable_refiner, refiner_checkpoint, refiner_switch_at - def before_process(self, p, enable_refiner, refiner_checkpoint, refiner_switch_at): + def setup(self, p, enable_refiner, refiner_checkpoint, refiner_switch_at): # the actual implementation is in sd_samplers_common.py, apply_refiner if not enable_refiner or refiner_checkpoint in (None, "", "None"): From 0ea61a74be5e9666a16de8b92b1d55ed0b678a16 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:46:36 +0800 Subject: [PATCH 58/67] add res(dpmdd 2m sde heun) and reorder the sampler list --- modules/sd_samplers_kdiffusion.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 0bacfe8d4..c140d2d0d 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -8,10 +8,6 @@ from modules.shared import opts import modules.shared as shared samplers_k_diffusion = [ - ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), - ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), - ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), @@ -20,8 +16,15 @@ samplers_k_diffusion = [ ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'discard_next_to_last_sigma': True, "uses_ensd": True}), ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {"uses_ensd": True, "second_order": True}), ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), + ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), + ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), + ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), + ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), + ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {"brownian_noise": True, "solver_type": "heun"}), + ('DPM++ 2M SDE Heun Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_ka'], {'scheduler': 'karras', "brownian_noise": True, "solver_type": "heun"}), + ('DPM++ 2M SDE Heun Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_exp'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'discard_next_to_last_sigma': True, "brownian_noise": True}), ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "brownian_noise": True}), ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), @@ -161,6 +164,9 @@ class KDiffusionSampler(sd_samplers_common.Sampler): noise_sampler = self.create_noise_sampler(x, sigmas, p) extra_params_kwargs['noise_sampler'] = noise_sampler + if self.config.options.get('solver_type', None) == 'heun': + extra_params_kwargs['solver_type'] = 'heun' + self.model_wrap_cfg.init_latent = x self.last_latent = x self.sampler_extra_args = { @@ -202,6 +208,9 @@ class KDiffusionSampler(sd_samplers_common.Sampler): noise_sampler = self.create_noise_sampler(x, sigmas, p) extra_params_kwargs['noise_sampler'] = noise_sampler + if self.config.options.get('solver_type', None) == 'heun': + extra_params_kwargs['solver_type'] = 'heun' + self.last_latent = x self.sampler_extra_args = { 'cond': conditioning, @@ -210,6 +219,7 @@ class KDiffusionSampler(sd_samplers_common.Sampler): 'cond_scale': p.cfg_scale, 's_min_uncond': self.s_min_uncond } + samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args=self.sampler_extra_args, disable=False, callback=self.callback_state, **extra_params_kwargs)) if self.model_wrap_cfg.padded_cond_uncond: From d9cc27cb29926c9cc5dce331da8fbaf996cf4973 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:32:51 +0800 Subject: [PATCH 59/67] Fix MHA updown err and support ex-bias for no-bias layer --- extensions-builtin/Lora/networks.py | 37 ++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index ba6211390..1645b8229 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -277,7 +277,15 @@ def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Li self.weight.copy_(weights_backup) if bias_backup is not None: - self.bias.copy_(bias_backup) + if isinstance(self, torch.nn.MultiheadAttention): + self.out_proj.bias.copy_(bias_backup) + else: + self.bias.copy_(bias_backup) + else: + if isinstance(self, torch.nn.MultiheadAttention): + self.out_proj.bias = None + else: + self.bias = None def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]): @@ -305,7 +313,12 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn bias_backup = getattr(self, "network_bias_backup", None) if bias_backup is None and getattr(self, 'bias', None) is not None: - bias_backup = self.bias.to(devices.cpu, copy=True) + if isinstance(self, torch.nn.MultiheadAttention) and self.out_proj.bias is not None: + bias_backup = self.out_proj.bias.to(devices.cpu, copy=True) + elif getattr(self, 'bias', None) is not None: + bias_backup = self.bias.to(devices.cpu, copy=True) + else: + bias_backup = None self.network_bias_backup = bias_backup if current_names != wanted_names: @@ -322,8 +335,11 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) self.weight += updown - if ex_bias is not None and getattr(self, 'bias', None) is not None: - self.bias += ex_bias + if ex_bias is not None and hasattr(self, 'bias'): + if self.bias is None: + self.bias = torch.nn.Parameter(ex_bias) + else: + self.bias += ex_bias continue module_q = net.modules.get(network_layer_name + "_q_proj", None) @@ -333,14 +349,19 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out: with torch.no_grad(): - updown_q = module_q.calc_updown(self.in_proj_weight) - updown_k = module_k.calc_updown(self.in_proj_weight) - updown_v = module_v.calc_updown(self.in_proj_weight) + updown_q, _ = module_q.calc_updown(self.in_proj_weight) + updown_k, _ = module_k.calc_updown(self.in_proj_weight) + updown_v, _ = module_v.calc_updown(self.in_proj_weight) updown_qkv = torch.vstack([updown_q, updown_k, updown_v]) - updown_out = module_out.calc_updown(self.out_proj.weight) + updown_out, ex_bias = module_out.calc_updown(self.out_proj.weight) self.in_proj_weight += updown_qkv self.out_proj.weight += updown_out + if ex_bias is not None: + if self.out_proj.bias is None: + self.out_proj.bias = torch.nn.Parameter(ex_bias) + else: + self.out_proj.bias += ex_bias continue if module is None: From aeb76ef174bc8a1904b25ca0b0b5009395f07d96 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 08:49:02 +0300 Subject: [PATCH 60/67] repair DDIM/PLMS/UniPC batches --- modules/sd_samplers_timesteps.py | 5 ++--- modules/sd_samplers_timesteps_impl.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/sd_samplers_timesteps.py b/modules/sd_samplers_timesteps.py index 16572c7e0..6aed29742 100644 --- a/modules/sd_samplers_timesteps.py +++ b/modules/sd_samplers_timesteps.py @@ -51,10 +51,9 @@ class CFGDenoiserTimesteps(CFGDenoiser): self.alphas = shared.sd_model.alphas_cumprod def get_pred_x0(self, x_in, x_out, sigma): - ts = int(sigma.item()) + ts = sigma.to(dtype=int) - s_in = x_in.new_ones([x_in.shape[0]]) - a_t = self.alphas[ts].item() * s_in + a_t = self.alphas[ts][:, None, None, None] sqrt_one_minus_at = (1 - a_t).sqrt() pred_x0 = (x_in - sqrt_one_minus_at * x_out) / a_t.sqrt() diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index d32e35213..a72daafd4 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -16,16 +16,17 @@ def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta= sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) extra_args = {} if extra_args is None else extra_args - s_in = x.new_ones([x.shape[0]]) + s_in = x.new_ones((x.shape[0])) + s_x = x.new_ones((x.shape[0], 1, 1, 1)) for i in tqdm.trange(len(timesteps) - 1, disable=disable): index = len(timesteps) - 1 - i e_t = model(x, timesteps[index].item() * s_in, **extra_args) - a_t = alphas[index].item() * s_in - a_prev = alphas_prev[index].item() * s_in - sigma_t = sigmas[index].item() * s_in - sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_in + a_t = alphas[index].item() * s_x + a_prev = alphas_prev[index].item() * s_x + sigma_t = sigmas[index].item() * s_x + sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_x pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() dir_xt = (1. - a_prev - sigma_t ** 2).sqrt() * e_t @@ -47,13 +48,14 @@ def plms(model, x, timesteps, extra_args=None, callback=None, disable=None): extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) + s_x = x.new_ones((x.shape[0], 1, 1, 1)) old_eps = [] def get_x_prev_and_pred_x0(e_t, index): # select parameters corresponding to the currently considered timestep - a_t = alphas[index].item() * s_in - a_prev = alphas_prev[index].item() * s_in - sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_in + a_t = alphas[index].item() * s_x + a_prev = alphas_prev[index].item() * s_x + sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_x # current prediction for x_0 pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() From cda2f0a1620c3b49bb3408c30796160ed29bc87d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 08:49:39 +0300 Subject: [PATCH 61/67] make on_before_component/on_after_component possible earlier --- modules/scripts.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index d4a9da947..cbdac2b51 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -239,6 +239,8 @@ class Script: """ Calls callback before a component is created. The callback function is called with a single argument of type OnComponent. + May be called in show() or ui() - but it may be too late in latter as some components may already be created. + This function is an alternative to before_component in that it also cllows to run before a component is created, but it doesn't require to be called for every created component - just for the one you need. """ @@ -445,6 +447,28 @@ class ScriptRunner: self.scripts.append(script) self.selectable_scripts.append(script) + self.apply_on_before_component_callbacks() + + def apply_on_before_component_callbacks(self): + for script in self.scripts: + on_before = script.on_before_component_elem_id or [] + on_after = script.on_after_component_elem_id or [] + + for elem_id, callback in on_before: + if elem_id not in self.on_before_component_elem_id: + self.on_before_component_elem_id[elem_id] = [] + + self.on_before_component_elem_id[elem_id].append((callback, script)) + + for elem_id, callback in on_after: + if elem_id not in self.on_after_component_elem_id: + self.on_after_component_elem_id[elem_id] = [] + + self.on_after_component_elem_id[elem_id].append((callback, script)) + + on_before.clear() + on_after.clear() + def create_script_ui(self, script): import modules.api.models as api_models @@ -555,16 +579,7 @@ class ScriptRunner: self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None')))) self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) - for script in self.scripts: - for elem_id, callback in script.on_before_component_elem_id or []: - items = self.on_before_component_elem_id.get(elem_id, []) - items.append((callback, script)) - self.on_before_component_elem_id[elem_id] = items - - for elem_id, callback in script.on_after_component_elem_id or []: - items = self.on_after_component_elem_id.get(elem_id, []) - items.append((callback, script)) - self.on_after_component_elem_id[elem_id] = items + self.apply_on_before_component_callbacks() return self.inputs From aa26f8eb40c2f99adb6752668b1fad1b5ae0158f Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:50:53 +0800 Subject: [PATCH 62/67] Put frequently used sampler back --- modules/sd_samplers_kdiffusion.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index c140d2d0d..67853ff1b 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -8,6 +8,10 @@ from modules.shared import opts import modules.shared as shared samplers_k_diffusion = [ + ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), + ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), + ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), + ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), @@ -16,12 +20,8 @@ samplers_k_diffusion = [ ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'discard_next_to_last_sigma': True, "uses_ensd": True}), ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {"uses_ensd": True, "second_order": True}), ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), - ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), - ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), - ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), - ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {"brownian_noise": True, "solver_type": "heun"}), ('DPM++ 2M SDE Heun Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_ka'], {'scheduler': 'karras', "brownian_noise": True, "solver_type": "heun"}), ('DPM++ 2M SDE Heun Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_exp'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), From f70ded89365f71d42b6a60a561e8fccfdd25c159 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:53:40 +0800 Subject: [PATCH 63/67] remove "if bias exist" check --- extensions-builtin/Lora/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 96d14344d..22fdff4a0 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -312,7 +312,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn self.network_weights_backup = weights_backup bias_backup = getattr(self, "network_bias_backup", None) - if bias_backup is None and getattr(self, 'bias', None) is not None: + if bias_backup is None: if isinstance(self, torch.nn.MultiheadAttention) and self.out_proj.bias is not None: bias_backup = self.out_proj.bias.to(devices.cpu, copy=True) elif getattr(self, 'bias', None) is not None: From c1a31ec9f75c8dfe4ddcb0061f06e2704db98359 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 08:59:15 +0300 Subject: [PATCH 64/67] revert to applying mask before denoising for k-diffusion, like it was before --- modules/sd_samplers_cfg_denoiser.py | 6 +++++- modules/sd_samplers_timesteps.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index 113425b2a..bc9b97e45 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -56,6 +56,7 @@ class CFGDenoiser(torch.nn.Module): self.sampler = sampler self.model_wrap = None self.p = None + self.mask_before_denoising = False @property def inner_model(self): @@ -104,7 +105,7 @@ class CFGDenoiser(torch.nn.Module): assert not is_edit_model or all(len(conds) == 1 for conds in conds_list), "AND is not supported for InstructPix2Pix checkpoint (unless using Image CFG scale = 1.0)" - if self.mask is not None: + if self.mask_before_denoising and self.mask is not None: x = self.init_latent * self.mask + self.nmask * x batch_size = len(conds_list) @@ -206,6 +207,9 @@ class CFGDenoiser(torch.nn.Module): else: denoised = self.combine_denoised(x_out, conds_list, uncond, cond_scale) + if not self.mask_before_denoising and self.mask is not None: + denoised = self.init_latent * self.mask + self.nmask * denoised + self.sampler.last_latent = self.get_pred_x0(torch.cat([x_in[i:i + 1] for i in denoised_image_indexes]), torch.cat([x_out[i:i + 1] for i in denoised_image_indexes]), sigma) if opts.live_preview_content == "Prompt": diff --git a/modules/sd_samplers_timesteps.py b/modules/sd_samplers_timesteps.py index 6aed29742..c1f534edf 100644 --- a/modules/sd_samplers_timesteps.py +++ b/modules/sd_samplers_timesteps.py @@ -49,6 +49,7 @@ class CFGDenoiserTimesteps(CFGDenoiser): super().__init__(sampler) self.alphas = shared.sd_model.alphas_cumprod + self.mask_before_denoising = True def get_pred_x0(self, x_in, x_out, sigma): ts = sigma.to(dtype=int) From c7c16f805c9ea0da42d1d993f2ea7bda48beba76 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 09:48:40 +0300 Subject: [PATCH 65/67] repair /docs page --- modules/api/models.py | 9 +++++---- modules/processing.py | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/api/models.py b/modules/api/models.py index 800c9b93f..6a574771c 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -50,10 +50,12 @@ class PydanticModelGenerator: additional_fields = None, ): def field_type_generator(k, v): - # field_type = str if not overrides.get(k) else overrides[k]["type"] - # print(k, v.annotation, v.default) field_type = v.annotation + if field_type == 'Image': + # images are sent as base64 strings via API + field_type = 'str' + return Optional[field_type] def merge_class_params(class_): @@ -63,7 +65,6 @@ class PydanticModelGenerator: parameters = {**parameters, **inspect.signature(classes.__init__).parameters} return parameters - self._model_name = model_name self._class_data = merge_class_params(class_instance) @@ -72,7 +73,7 @@ class PydanticModelGenerator: field=underscore(k), field_alias=k, field_type=field_type_generator(k, v), - field_value=v.default + field_value=None if isinstance(v.default, property) else v.default ) for (k,v) in self._class_data.items() if k not in API_NOT_ALLOWED ] diff --git a/modules/processing.py b/modules/processing.py index 743666552..69d365b89 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -114,7 +114,7 @@ class StableDiffusionProcessing: prompt: str = "" prompt_for_display: str = None negative_prompt: str = "" - styles: list[str] = field(default_factory=list) + styles: list[str] = None seed: int = -1 subseed: int = -1 subseed_strength: float = 0 @@ -199,6 +199,7 @@ class StableDiffusionProcessing: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) self.comments = {} + self.styles = [] self.sampler_noise_scheduler_override = None self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond From abbecb3e7363e422d6840fbb5746c74fd453ead5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 10:15:10 +0300 Subject: [PATCH 66/67] further repair the /docs page to not break styles with the attempted fix --- modules/processing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 69d365b89..f34ba48ad 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -199,7 +199,9 @@ class StableDiffusionProcessing: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) self.comments = {} - self.styles = [] + + if self.styles is None: + self.styles = [] self.sampler_noise_scheduler_override = None self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond From f3b96d4998d8ca376d33efa7a4454e8c28e24255 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 10:22:52 +0300 Subject: [PATCH 67/67] return seed controls UI to how it was before --- modules/processing_scripts/seed.py | 47 ++++++++++++++++++------------ style.css | 15 ++++++++++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 96b44dfbe..6ce3b2fc2 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -24,47 +24,58 @@ class ScriptSeed(scripts.ScriptBuiltin): def ui(self, is_img2img): with gr.Row(elem_id=self.elem_id("seed_row")): - with gr.Column(scale=1, min_width=205): - with gr.Row(): - if cmd_opts.use_textbox_seed: - self.seed = gr.Textbox(label='Seed', value="", elem_id=self.elem_id("seed"), min_width=100) - else: - self.seed = gr.Number(label='Seed', value=-1, elem_id=self.elem_id("seed"), min_width=100, precision=0) + if cmd_opts.use_textbox_seed: + self.seed = gr.Textbox(label='Seed', value="", elem_id=self.elem_id("seed"), min_width=100) + else: + self.seed = gr.Number(label='Seed', value=-1, elem_id=self.elem_id("seed"), min_width=100, precision=0) - random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), label='Random seed') - reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), label='Reuse seed') + random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), label='Random seed') + reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), label='Reuse seed') - with gr.Column(scale=1, min_width=205): - with gr.Row(): - subseed = gr.Number(label='Variation seed', value=-1, elem_id=self.elem_id("subseed"), min_width=100, precision=0) + seed_checkbox = gr.Checkbox(label='Extra', elem_id=self.elem_id("subseed_show"), value=False) - random_subseed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_subseed")) - reuse_subseed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_subseed")) - - with gr.Column(scale=2, min_width=100): + with gr.Group(visible=False, elem_id=self.elem_id("seed_extras")) as seed_extras: + with gr.Row(elem_id=self.elem_id("subseed_row")): + subseed = gr.Number(label='Variation seed', value=-1, elem_id=self.elem_id("subseed"), precision=0) + random_subseed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_subseed")) + reuse_subseed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_subseed")) subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=self.elem_id("subseed_strength")) + with gr.Row(elem_id=self.elem_id("seed_resize_from_row")): + seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=self.elem_id("seed_resize_from_w")) + seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=self.elem_id("seed_resize_from_h")) + random_seed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("seed") + "')}", show_progress=False, inputs=[], outputs=[]) random_subseed.click(fn=None, _js="function(){setRandomSeed('" + self.elem_id("subseed") + "')}", show_progress=False, inputs=[], outputs=[]) + seed_checkbox.change(lambda x: gr.update(visible=x), show_progress=False, inputs=[seed_checkbox], outputs=[seed_extras]) + self.infotext_fields = [ (self.seed, "Seed"), + (seed_checkbox, lambda d: "Variation seed" in d or "Seed resize from-1" in d), (subseed, "Variation seed"), (subseed_strength, "Variation seed strength"), + (seed_resize_from_w, "Seed resize from-1"), + (seed_resize_from_h, "Seed resize from-2"), ] self.on_after_component(lambda x: connect_reuse_seed(self.seed, reuse_seed, x.component, False), elem_id=f'generation_info_{self.tabname}') self.on_after_component(lambda x: connect_reuse_seed(subseed, reuse_subseed, x.component, True), elem_id=f'generation_info_{self.tabname}') - return self.seed, subseed, subseed_strength + return self.seed, seed_checkbox, subseed, subseed_strength, seed_resize_from_w, seed_resize_from_h - def setup(self, p, seed, subseed, subseed_strength): + def setup(self, p, seed, seed_checkbox, subseed, subseed_strength, seed_resize_from_w, seed_resize_from_h): p.seed = seed - if subseed_strength > 0: + if seed_checkbox and subseed_strength > 0: p.subseed = subseed p.subseed_strength = subseed_strength + if seed_checkbox and seed_resize_from_w > 0 and seed_resize_from_h > 0: + p.seed_resize_from_w = seed_resize_from_w + p.seed_resize_from_h = seed_resize_from_h + + def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): """ Connects a 'reuse (sub)seed' button's click event so that it copies last used diff --git a/style.css b/style.css index dc528422f..bdf0635a0 100644 --- a/style.css +++ b/style.css @@ -222,6 +222,21 @@ div.block.gradio-accordion { padding: 0.1em 0.75em; } +[id$=_subseed_show]{ + min-width: auto !important; + flex-grow: 0 !important; + display: flex; +} + +[id$=_subseed_show] label{ + margin-bottom: 0.65em; + align-self: end; +} + +[id$=_seed_extras] > div{ + gap: 0.5em; +} + .html-log .comments{ padding-top: 0.5em; }