From 6efdfe3234d0510a6fd522c31dc62366d363ae73 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 7 Apr 2024 22:58:12 +0900 Subject: [PATCH 01/37] if use use_main_prompt index = 0 --- modules/processing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 2baca4f5f4e..c1e689c3727 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -704,7 +704,9 @@ def program_version(): def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None, all_hr_prompts=None, all_hr_negative_prompts=None): - if index is None: + if use_main_prompt: + index = 0 + elif index is None: index = position_in_batch + iteration * p.batch_size if all_negative_prompts is None: From 47ed9b2d398287f14a6dcab6fa4fe8b78bccf1c8 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 01:39:31 +0900 Subject: [PATCH 02/37] allow list or callables in generation_params --- modules/processing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index c1e689c3727..50570c49bb7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -756,6 +756,16 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "User": p.user if opts.add_user_name_to_info else None, } + for key, value in generation_params.items(): + try: + if isinstance(value, list): + generation_params[key] = value[index] + elif callable(value): + generation_params[key] = value(**locals()) + except Exception: + errors.report(f'Error creating infotext for key "{key}"', exc_info=True) + generation_params[key] = None + if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): From 219e64489c9c2a712dc31fe254d1a32ce3caf7c2 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 01:41:52 +0900 Subject: [PATCH 03/37] re-work extra_generation_params for Hires prompt --- modules/processing.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 50570c49bb7..97739fcb3f6 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -703,7 +703,7 @@ def program_version(): return res -def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None, all_hr_prompts=None, all_hr_negative_prompts=None): +def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None): if use_main_prompt: index = 0 elif index is None: @@ -717,6 +717,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter token_merging_ratio = p.get_token_merging_ratio() token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) + prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] + negative_prompt = p.main_negative_prompt if use_main_prompt else all_negative_prompts[index] + uses_ensd = opts.eta_noise_seed_delta != 0 if uses_ensd: uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) @@ -749,8 +752,6 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, "Tiling": "True" if p.tiling else None, - "Hires prompt": None, # This is set later, insert here to keep order - "Hires negative prompt": None, # This is set later, insert here to keep order **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, "User": p.user if opts.add_user_name_to_info else None, @@ -766,15 +767,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter errors.report(f'Error creating infotext for key "{key}"', exc_info=True) generation_params[key] = None - if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None): - generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None - if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None): - generation_params['Hires negative prompt'] = all_hr_negative_prompts[index] if all_hr_negative_prompts[index] != all_negative_prompts[index] else None - generation_params_text = ", ".join([k if k == v else f'{k}: {infotext_utils.quote(v)}' for k, v in generation_params.items() if v is not None]) - prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] - negative_prompt_text = f"\nNegative prompt: {p.main_negative_prompt if use_main_prompt else all_negative_prompts[index]}" if all_negative_prompts[index] else "" + negative_prompt_text = f"\nNegative prompt: {negative_prompt}" if negative_prompt else "" return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip() @@ -1216,6 +1211,17 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_sampler_name is not None and self.hr_sampler_name != self.sampler_name: self.extra_generation_params["Hires sampler"] = self.hr_sampler_name + def get_hr_prompt(p, index, prompt_text, **kwargs): + hr_prompt = p.all_hr_prompts[index] + return hr_prompt if hr_prompt != prompt_text else None + + def get_hr_negative_prompt(p, index, negative_prompt, **kwargs): + hr_negative_prompt = p.all_hr_negative_prompts[index] + return hr_negative_prompt if hr_negative_prompt != negative_prompt else None + + self.extra_generation_params["Hires prompt"] = get_hr_prompt + self.extra_generation_params["Hires negative prompt"] = get_hr_negative_prompt + self.extra_generation_params["Hires schedule type"] = None # to be set in sd_samplers_kdiffusion.py if self.hr_scheduler is None: From 1e1176b6eb4b0054ad44b29787cb8e9217da5daf Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:18:33 +0900 Subject: [PATCH 04/37] non-serializable as None --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 97739fcb3f6..bae00d74a55 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -608,7 +608,7 @@ def js(self): "version": self.version, } - return json.dumps(obj) + return json.dumps(obj, default=lambda o: None) def infotext(self, p: StableDiffusionProcessing, index): return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size) From e3aabe6959c2088f6b6e917b4f84185ae95a3af6 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:48:38 +0900 Subject: [PATCH 05/37] add documentation for create_infotext --- modules/processing.py | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index bae00d74a55..d8ba5ca4d11 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -704,6 +704,50 @@ def program_version(): def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None): + """ + this function is used to generate the infotext that is stored in the generated images, it's contains the parameters that are required to generate the imagee + Args: + p: StableDiffusionProcessing + all_prompts: list[str] + all_seeds: list[int] + all_subseeds: list[int] + comments: list[str] + iteration: int + position_in_batch: int + use_main_prompt: bool + index: int + all_negative_prompts: list[str] + + Returns: str + + Extra generation params + p.extra_generation_params dictionary allows for additional parameters to be added to the infotext + this can be use by the base webui or extensions. + To add a new entry, add a new key value pair, the dictionary key will be used as the key of the parameter in the infotext + the value generation_params can be defined as: + - str | None + - List[str|None] + - callable func(**kwargs) -> str | None + + When defined as a string, it will be used as without extra processing; this is this most common use case. + + Defining as a list allows for parameter that changes across images in the job, for example, the 'Seed' parameter. + The list should have the same length as the total number of images in the entire job. + + Defining as a callable function allows parameter cannot be generated earlier or when extra logic is required. + For example 'Hires prompt', due to reasons the hr_prompt might be changed by process in the pipeline or extensions + and may vary across different images, defining as a static string or list would not work. + + The function takes locals() as **kwargs, as such will have access to variables like 'p' and 'index'. + the base signature of the function should be: + func(**kwargs) -> str | None + optionally it can have additional arguments that will be used in the function: + func(p, index, **kwargs) -> str | None + note: for better future compatibility even though this function will have access to all variables in the locals(), + it is recommended to only use the arguments present in the function signature of create_infotext. + For actual implementation examples, see StableDiffusionProcessingTxt2Img.init > get_hr_prompt. + """ + if use_main_prompt: index = 0 elif index is None: From d9708c92b444894bce8070e4dcfaa093f8eb8d43 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 8 Apr 2024 16:15:25 +0300 Subject: [PATCH 06/37] fix limited file write (thanks, Sylwia) --- modules/ui_extensions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 913e1444e95..d822c0b8920 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -58,8 +58,9 @@ def apply_and_restart(disable_list, update_list, disable_all): def save_config_state(name): current_config_state = config_states.get_config() - if not name: - name = "Config" + + name = os.path.basename(name or "Config") + current_config_state["name"] = name timestamp = datetime.now().strftime('%Y_%m_%d-%H_%M_%S') filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") From 2580235c72b0cde49519678756c26417248348f5 Mon Sep 17 00:00:00 2001 From: Jorden Tse Date: Tue, 9 Apr 2024 11:13:47 +0800 Subject: [PATCH 07/37] Fix extra-single-image API not doing upscale failed --- modules/postprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index ab3274df3d8..812cbccae9a 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -136,6 +136,7 @@ def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_ args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { + "upscale_enabled": True, "upscale_mode": resize_mode, "upscale_by": upscaling_resize, "max_side_length": max_side_length, From 600f339c4cf1829c4a52bf83a71544ba7fe7bb5b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:59:04 +0900 Subject: [PATCH 08/37] Warning when Script is not found --- modules/scripts.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/scripts.py b/modules/scripts.py index 264503ca3c4..70ccfbe46b1 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -739,12 +739,17 @@ def init_field(title): def onload_script_visibility(params): title = params.get('Script', None) if title: - title_index = self.titles.index(title) - visibility = title_index == self.script_load_ctr - self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) - return gr.update(visible=visibility) - else: - return gr.update(visible=False) + try: + title_index = self.titles.index(title) + visibility = title_index == self.script_load_ctr + self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) + return gr.update(visible=visibility) + except ValueError: + params['Script'] = None + massage = f'Cannot find Script: "{title}"' + print(massage) + gr.Warning(massage) + return gr.update(visible=False) 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]) From ef83f6831fd747fea4d0fa638fe5ffb71aaa5b0f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:28:44 +0900 Subject: [PATCH 09/37] catch exception for all paste_fields callable --- modules/infotext_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 1c91d076df6..f1e8f54ba5e 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import gradio as gr from modules.paths import data_path -from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser +from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser, errors from PIL import Image sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name @@ -488,7 +488,11 @@ def paste_func(prompt): for output, key in paste_fields: if callable(key): - v = key(params) + try: + v = key(params) + except Exception: + errors.report(f"Error executing {key}", exc_info=True) + v = None else: v = params.get(key, None) From 4068429ac72cd83e03abe779148bbe1d6a141a09 Mon Sep 17 00:00:00 2001 From: storyicon Date: Wed, 10 Apr 2024 10:53:25 +0000 Subject: [PATCH 10/37] fix: Coordinate 'right' is less than 'left' Signed-off-by: storyicon --- modules/masking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 29a3945278e..9f5b0cd0332 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -9,8 +9,8 @@ def get_crop_region(mask, pad=0): if box: x1, y1, x2, y2 = box else: # when no box is found - x1, y1 = mask_img.size - x2 = y2 = 0 + x1 = y1 = 0 + x2, y2 = mask_img.size return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1]) From 592e40ebe937cea6325412fe3650f4b693e0ab95 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:51:29 +0900 Subject: [PATCH 11/37] update restricted_opts --- modules/shared_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/shared_options.py b/modules/shared_options.py index a2b595ff3e0..326a317e030 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -19,7 +19,9 @@ "outdir_grids", "outdir_txt2img_grids", "outdir_save", - "outdir_init_images" + "outdir_init_images", + "temp_dir", + "clean_temp_dir_at_start", } categories.register_category("saving", "Saving images") From 8e1c3561beb33fa58e39c6fb2594ece02aca188a Mon Sep 17 00:00:00 2001 From: thatfuckingbird <67429906+thatfuckingbird@users.noreply.github.com> Date: Mon, 15 Apr 2024 21:17:24 +0200 Subject: [PATCH 12/37] fix typo in function call (eror -> error) --- javascript/extraNetworks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 358ecd36c90..c5cced97399 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -568,7 +568,7 @@ function extraNetworksShowMetadata(text) { return; } } catch (error) { - console.eror(error); + console.error(error); } var elem = document.createElement('pre'); From 0f82948e4f46ca27acbf3ffb817cabec402c6438 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Mon, 15 Apr 2024 22:14:19 -0400 Subject: [PATCH 13/37] Fix cls.__module__ --- .../hypertile/scripts/hypertile_xyz.py | 2 +- modules/script_loading.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/extensions-builtin/hypertile/scripts/hypertile_xyz.py b/extensions-builtin/hypertile/scripts/hypertile_xyz.py index 9e96ae3c527..386c6b2d669 100644 --- a/extensions-builtin/hypertile/scripts/hypertile_xyz.py +++ b/extensions-builtin/hypertile/scripts/hypertile_xyz.py @@ -1,7 +1,7 @@ from modules import scripts from modules.shared import opts -xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module +xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "scripts.xyz_grid"][0].module def int_applier(value_name:str, min_range:int = -1, max_range:int = -1): """ diff --git a/modules/script_loading.py b/modules/script_loading.py index 17f658b1544..c505c0b84ad 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -9,15 +9,13 @@ def load_module(path): - module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) + module_name, _ = os.path.splitext(os.path.basename(path)) + full_module_name = "scripts." + module_name + module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - - loaded_scripts[path] = module - - module_name, _ = os.path.splitext(os.path.basename(path)) - sys.modules["scripts." + module_name] = module - + loaded_scripts[full_module_name] = module + sys.modules[full_module_name] = module return module From a95326bec434da5d0a2aeb943d35cfded75e3afa Mon Sep 17 00:00:00 2001 From: huchenlei Date: Mon, 15 Apr 2024 22:34:01 -0400 Subject: [PATCH 14/37] nit --- modules/script_loading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index c505c0b84ad..20c7998acec 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -14,7 +14,7 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) - loaded_scripts[full_module_name] = module + loaded_scripts[path] = module sys.modules[full_module_name] = module return module From bba306d414f1e81c979ae53271c022df08ef7388 Mon Sep 17 00:00:00 2001 From: Travis Geiselbrecht Date: Mon, 15 Apr 2024 21:10:11 -0700 Subject: [PATCH 15/37] fix: remove callbacks properly in remove_callbacks_for_function() Like remove_current_script_callback just before, also remove from the ordered_callbacks_map to keep the callback map and ordered callback map in sync. --- modules/script_callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 74f41f09d74..9059d4d9385 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -448,6 +448,9 @@ def remove_callbacks_for_function(callback_func): for callback_list in callback_map.values(): for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]: callback_list.remove(callback_to_remove) + for ordered_callback_list in ordered_callbacks_map.values(): + for callback_to_remove in [cb for cb in ordered_callback_list if cb.callback == callback_func]: + ordered_callback_list.remove(callback_to_remove) def on_app_started(callback, *, name=None): From 0980fdfe8c85bb4ff915bab100a383674a6a0171 Mon Sep 17 00:00:00 2001 From: storyicon Date: Tue, 16 Apr 2024 07:35:33 +0000 Subject: [PATCH 16/37] fix: images do not match Signed-off-by: storyicon --- modules/processing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 411c7c3f4e4..c14b6896526 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1537,6 +1537,9 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.mask_blur_x > 0 or self.mask_blur_y > 0: self.extra_generation_params["Mask blur"] = self.mask_blur + if image_mask.size != (self.width, self.height): + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) + if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') @@ -1551,7 +1554,6 @@ def init(self, all_prompts, all_seeds, all_subseeds): self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding else: - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) From 50190ca669fde082cd45a5030127813617a7f777 Mon Sep 17 00:00:00 2001 From: "Alessandro de Oliveira Faria (A.K.A. CABELO)" Date: Wed, 17 Apr 2024 00:01:56 -0300 Subject: [PATCH 17/37] Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index d28c7c19b93..fee5e1182e3 100755 --- a/webui.sh +++ b/webui.sh @@ -243,7 +243,7 @@ prepare_tcmalloc() { for lib in "${TCMALLOC_LIBS[@]}" do # Determine which type of tcmalloc library the library supports - TCMALLOC="$(PATH=/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" + TCMALLOC="$(PATH=/sbin:/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" TC_INFO=(${TCMALLOC//=>/}) if [[ ! -z "${TC_INFO}" ]]; then echo "Check TCMalloc: ${TC_INFO}" From 63fd38a04f91ed97bf0f51c9b63f134a2ed81d59 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:44:49 +0900 Subject: [PATCH 18/37] numpy DeprecationWarning product -> prod --- modules/textual_inversion/image_embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index ea4b88333ac..0898d8b7714 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -43,7 +43,7 @@ def lcg(m=2**32, a=1664525, c=1013904223, seed=0): def xor_block(block): g = lcg() - randblock = np.array([next(g) for _ in range(np.product(block.shape))]).astype(np.uint8).reshape(block.shape) + randblock = np.array([next(g) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape) return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F) From 9d4fdc45d3c4b9431551fd53de64a67726dcbd64 Mon Sep 17 00:00:00 2001 From: Andray Date: Thu, 18 Apr 2024 01:53:23 +0400 Subject: [PATCH 19/37] fix x1 upscalers --- modules/upscaler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/upscaler.py b/modules/upscaler.py index 59f8fbbf584..28c60cdcdfe 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -57,7 +57,7 @@ def upscale(self, img: PIL.Image, scale, selected_model: str = None): dest_h = int((img.height * scale) // 8 * 8) for _ in range(3): - if img.width >= dest_w and img.height >= dest_h: + if img.width >= dest_w and img.height >= dest_h and scale != 1: break if shared.state.interrupted: From 909c3dfe83ac8c55edebb8c23e265dbfe5532081 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 17 Apr 2024 21:20:03 -0600 Subject: [PATCH 20/37] Remove API upscaling factor limits --- modules/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index 16edf11cf83..ff377713469 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -147,7 +147,7 @@ class ExtrasBaseRequest(BaseModel): gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.") codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.") codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.") - upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.") + upscaling_resize: float = Field(default=2, title="Upscaling Factor", gt=0, description="By how much to upscale the image, only used when resize_mode=0.") upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.") upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.") upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?") From ba2a737cceced2355f10e2f645e6794762858d12 Mon Sep 17 00:00:00 2001 From: Speculative Moonstone <167392122+speculativemoonstone@users.noreply.github.com> Date: Thu, 18 Apr 2024 04:25:32 +0000 Subject: [PATCH 21/37] Allow webui.sh to be runnable from directories containing a .git file --- webui.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webui.sh b/webui.sh index d28c7c19b93..c22a68227f5 100755 --- a/webui.sh +++ b/webui.sh @@ -113,13 +113,13 @@ then exit 1 fi -if [[ -d .git ]] +if [[ -d "$SCRIPT_DIR/.git" ]] then printf "\n%s\n" "${delimiter}" printf "Repo already cloned, using it as install directory" printf "\n%s\n" "${delimiter}" - install_dir="${PWD}/../" - clone_dir="${PWD##*/}" + install_dir="${SCRIPT_DIR}/../" + clone_dir="${SCRIPT_DIR##*/}" fi # Check prerequisites From 71314e47b1e505744f5ec8336fea0f7e45e0b4fb Mon Sep 17 00:00:00 2001 From: storyicon Date: Thu, 18 Apr 2024 11:59:25 +0000 Subject: [PATCH 22/37] feat:compatible with inconsistent/empty mask Signed-off-by: storyicon --- modules/masking.py | 4 ++-- modules/processing.py | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 9f5b0cd0332..29a3945278e 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -9,8 +9,8 @@ def get_crop_region(mask, pad=0): if box: x1, y1, x2, y2 = box else: # when no box is found - x1 = y1 = 0 - x2, y2 = mask_img.size + x1, y1 = mask_img.size + x2 = y2 = 0 return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1]) diff --git a/modules/processing.py b/modules/processing.py index c14b6896526..1ee4c047798 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1537,23 +1537,24 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.mask_blur_x > 0 or self.mask_blur_y > 0: self.extra_generation_params["Mask blur"] = self.mask_blur - if image_mask.size != (self.width, self.height): - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) - if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) - crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) - x1, y1, x2, y2 = crop_region - - mask = mask.crop(crop_region) - image_mask = images.resize_image(2, mask, self.width, self.height) - self.paste_to = (x1, y1, x2-x1, y2-y1) - + if crop_region[0] >= crop_region[2] and crop_region[1] >= crop_region[3]: + crop_region = None + image_mask = None + self.mask_for_overlay = None + else: + crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) + x1, y1, x2, y2 = crop_region + mask = mask.crop(crop_region) + image_mask = images.resize_image(2, mask, self.width, self.height) + self.paste_to = (x1, y1, x2-x1, y2-y1) self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding else: + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) @@ -1579,6 +1580,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): image = images.resize_image(self.resize_mode, image, self.width, self.height) if image_mask is not None: + if self.mask_for_overlay.size != (image.width, image.height): + self.mask_for_overlay = images.resize_image(self.resize_mode, self.mask_for_overlay, image.width, image.height) image_masked = Image.new('RGBa', (image.width, image.height)) image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) From d212fb59fe897327142c9b5049460689148be7a7 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Thu, 18 Apr 2024 20:56:51 -0600 Subject: [PATCH 23/37] Hide 'No Image data blocks found.' message --- modules/textual_inversion/image_embedding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index ea4b88333ac..d6a6a260b56 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -1,12 +1,15 @@ import base64 import json import warnings +import logging import numpy as np import zlib from PIL import Image, ImageDraw import torch +logger = logging.getLogger(__name__) + class EmbeddingEncoder(json.JSONEncoder): def default(self, obj): @@ -114,7 +117,7 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - print('No Image data blocks found.') + logger.debug('No Image data blocks found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) From 5cb567c1384a019e5ba534b023b0f5d2dfff931f Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Fri, 19 Apr 2024 20:29:22 -0600 Subject: [PATCH 24/37] Add schedulers API endpoint --- modules/api/api.py | 14 +++++++++++++- modules/api/models.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 29fa0011a55..f468c385275 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -17,7 +17,7 @@ from secrets import compare_digest import modules.shared as shared -from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models +from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models, sd_schedulers from modules.api import models from modules.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images @@ -221,6 +221,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"]) self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel) self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem]) + self.add_api_route("/sdapi/v1/schedulers", self.get_schedulers, methods=["GET"], response_model=list[models.SchedulerItem]) self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem]) self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem]) self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem]) @@ -683,6 +684,17 @@ def get_cmd_flags(self): def get_samplers(self): return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers] + def get_schedulers(self): + return [ + { + "name": scheduler.name, + "label": scheduler.label, + "aliases": scheduler.aliases, + "default_rho": scheduler.default_rho, + "need_inner_model": scheduler.need_inner_model, + } + for scheduler in sd_schedulers.schedulers] + def get_upscalers(self): return [ { diff --git a/modules/api/models.py b/modules/api/models.py index 16edf11cf83..758da631265 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -235,6 +235,13 @@ class SamplerItem(BaseModel): aliases: list[str] = Field(title="Aliases") options: dict[str, str] = Field(title="Options") +class SchedulerItem(BaseModel): + name: str = Field(title="Name") + label: str = Field(title="Label") + aliases: Optional[list[str]] = Field(title="Aliases") + default_rho: Optional[float] = Field(title="Default Rho") + need_inner_model: Optional[bool] = Field(title="Needs Inner Model") + class UpscalerItem(BaseModel): name: str = Field(title="Name") model_name: Optional[str] = Field(title="Model Name") From b5b1487f6a7ccc9c80251f100a92b004f727bee7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:26:50 +0900 Subject: [PATCH 25/37] FilenameGenerator Sampler Scheduler --- modules/images.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c50b2455dee..eda02836eda 100644 --- a/modules/images.py +++ b/modules/images.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime - +import functools import pytz import io import math @@ -347,6 +347,32 @@ def sanitize_filename_part(text, replace_spaces=True): return text +@functools.cache +def get_scheduler_str(sampler_name, scheduler_name): + """Returns {Scheduler} if the scheduler is applicable to the sampler""" + if scheduler_name == 'Automatic': + config = sd_samplers.find_sampler_config(sampler_name) + scheduler_name = config.options.get('scheduler', 'Automatic') + return scheduler_name.capitalize() + + +@functools.cache +def get_sampler_scheduler_str(sampler_name, scheduler_name): + """Returns the '{Sampler} {Scheduler}' if the scheduler is applicable to the sampler""" + return f'{sampler_name} {get_scheduler_str(sampler_name, scheduler_name)}' + + +def get_sampler_scheduler(p, sampler): + """Returns '{Sampler} {Scheduler}' / '{Scheduler}' / 'NOTHING_AND_SKIP_PREVIOUS_TEXT'""" + if hasattr(p, 'scheduler') and hasattr(p, 'sampler_name'): + if sampler: + sampler_scheduler = get_sampler_scheduler_str(p.sampler_name, p.scheduler) + else: + sampler_scheduler = get_scheduler_str(p.sampler_name, p.scheduler) + return sanitize_filename_part(sampler_scheduler, replace_spaces=False) + return NOTHING_AND_SKIP_PREVIOUS_TEXT + + class FilenameGenerator: replacements = { 'seed': lambda self: self.seed if self.seed is not None else '', @@ -358,6 +384,8 @@ class FilenameGenerator: 'height': lambda self: self.image.height, 'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False), 'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False), + 'sampler_scheduler': lambda self: self.p and get_sampler_scheduler(self.p, True), + 'scheduler': lambda self: self.p and get_sampler_scheduler(self.p, False), 'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash), 'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), 'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'), From 49fee7c8dbb7f17e3903c2a238c53ddd53bc503f Mon Sep 17 00:00:00 2001 From: kaanyalova <76952012+kaanyalova@users.noreply.github.com> Date: Sat, 20 Apr 2024 23:18:54 +0300 Subject: [PATCH 26/37] Add avif support --- modules/images.py | 13 ++++++++++++- requirements.txt | 1 + requirements_versions.txt | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index c50b2455dee..09d3523e8cc 100644 --- a/modules/images.py +++ b/modules/images.py @@ -13,6 +13,8 @@ import piexif import piexif.helper from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps +# pillow_avif needs to be imported somewhere in code for it to work +import pillow_avif # noqa: F401 import string import json import hashlib @@ -569,6 +571,16 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p }) piexif.insert(exif_bytes, filename) + elif extension.lower() == '.avif': + if opts.enable_pnginfo and geninfo is not None: + exif_bytes = piexif.dump({ + "Exif": { + piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or "", encoding="unicode") + }, + }) + + + image.save(filename,format=image_format, exif=exif_bytes) elif extension.lower() == ".gif": image.save(filename, format=image_format, comment=geninfo) else: @@ -747,7 +759,6 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: exif_comment = exif_comment.decode('utf8', errors="ignore") if exif_comment: - items['exif comment'] = exif_comment geninfo = exif_comment elif "comment" in items: # for gif geninfo = items["comment"].decode('utf8', errors="ignore") diff --git a/requirements.txt b/requirements.txt index 8699c02be36..9e2ecfe4d67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ torch torchdiffeq torchsde transformers==4.30.2 +pillow-avif-plugin==1.4.3 \ No newline at end of file diff --git a/requirements_versions.txt b/requirements_versions.txt index 87aae9136e9..3df74f3d6a6 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -29,3 +29,4 @@ torchdiffeq==0.2.3 torchsde==0.2.6 transformers==4.30.2 httpx==0.24.1 +pillow-avif-plugin==1.4.3 From 6f4f6bff6b4af4e65264618a7aebe8a6435d1350 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 21 Apr 2024 07:18:58 +0300 Subject: [PATCH 27/37] add more info to the error message for #15567 --- modules/textual_inversion/image_embedding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index d6a6a260b56..644e1152e34 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -1,5 +1,6 @@ import base64 import json +import os.path import warnings import logging @@ -117,7 +118,7 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - logger.debug('No Image data blocks found.') + logger.debug(f'{os.path.basename(getattr(image, "filename", "unknown image file"))}: no embedded information found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) From 9bcfb92a00df2ff217359be68ea2b21b3260f341 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 21 Apr 2024 07:41:28 +0300 Subject: [PATCH 28/37] rename logging from textual inversion to not confuse it with global logging module --- modules/hypernetworks/hypernetwork.py | 4 ++-- modules/textual_inversion/{logging.py => saving_settings.py} | 0 modules/textual_inversion/textual_inversion.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename modules/textual_inversion/{logging.py => saving_settings.py} (100%) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 6082d9cb3e0..17454665f28 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -11,7 +11,7 @@ from einops import rearrange, repeat from ldm.util import default from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors -from modules.textual_inversion import textual_inversion, logging +from modules.textual_inversion import textual_inversion, saving_settings from modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ @@ -533,7 +533,7 @@ def train_hypernetwork(id_task, hypernetwork_name: str, learn_rate: float, batch model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds), **{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]} ) - logging.save_settings_to_file(log_directory, {**saved_params, **locals()}) + saving_settings.save_settings_to_file(log_directory, {**saved_params, **locals()}) latent_sampling_method = ds.latent_sampling_method diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/saving_settings.py similarity index 100% rename from modules/textual_inversion/logging.py rename to modules/textual_inversion/saving_settings.py diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c206ef5fd01..253f219c4ec 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -17,7 +17,7 @@ from modules.textual_inversion.learn_schedule import LearnRateScheduler from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay -from modules.textual_inversion.logging import save_settings_to_file +from modules.textual_inversion.saving_settings import save_settings_to_file TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"]) From db263df5d5c1ba6d56b277a155186b80d24ac5bd Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:34:11 +0900 Subject: [PATCH 29/37] get_crop_region_v2 --- modules/masking.py | 42 ++++++++++++++++++++++++++++++++---------- modules/processing.py | 20 ++++++++++++-------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index 29a3945278e..8e869d1b1e0 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -1,17 +1,39 @@ from PIL import Image, ImageFilter, ImageOps -def get_crop_region(mask, pad=0): - """finds a rectangular region that contains all masked ares in an image. Returns (x1, y1, x2, y2) coordinates of the rectangle. - For example, if a user has painted the top-right part of a 512x512 image, the result may be (256, 0, 512, 256)""" - mask_img = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) - box = mask_img.getbbox() - if box: +def get_crop_region_v2(mask, pad=0): + """ + Finds a rectangular region that contains all masked ares in a mask. + Returns None if mask is completely black mask (all 0) + + Parameters: + mask: PIL.Image.Image L mode or numpy 1d array + pad: int number of pixels that the region will be extended on all sides + Returns: (x1, y1, x2, y2) | None + + Introduced post 1.9.0 + """ + mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) + if box := mask.getbbox(): x1, y1, x2, y2 = box - else: # when no box is found - x1, y1 = mask_img.size - x2 = y2 = 0 - return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1]) + return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1]) if pad else box + + +def get_crop_region(mask, pad=0): + """ + Same function as get_crop_region_v2 but handles completely black mask (all 0) differently + when mask all black still return coordinates but the coordinates may be invalid ie x2>x1 or y2>y1 + Notes: it is possible for the coordinates to be "valid" again if pad size is sufficiently large + (mask_size.x-pad, mask_size.y-pad, pad, pad) + + Extension developer should use get_crop_region_v2 instead unless for compatibility considerations. + """ + mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) + if box := get_crop_region_v2(mask, pad): + return box + x1, y1 = mask.size + x2 = y2 = 0 + return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1]) def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height): diff --git a/modules/processing.py b/modules/processing.py index b5a04634ae3..f77123b9f85 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1611,19 +1611,23 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') - crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) - if crop_region[0] >= crop_region[2] and crop_region[1] >= crop_region[3]: - crop_region = None - image_mask = None - self.mask_for_overlay = None - else: + crop_region = masking.get_crop_region_v2(mask, self.inpaint_full_res_padding) + if crop_region: crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) x1, y1, x2, y2 = crop_region mask = mask.crop(crop_region) image_mask = images.resize_image(2, mask, self.width, self.height) + self.inpaint_full_res = False self.paste_to = (x1, y1, x2-x1, y2-y1) - self.extra_generation_params["Inpaint area"] = "Only masked" - self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + self.extra_generation_params["Inpaint area"] = "Only masked" + self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + else: + crop_region = None + image_mask = None + self.mask_for_overlay = None + massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.' + model_hijack.comments.append(massage) + logging.info(massage) else: image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) From 6c7c176dc9b2808fa897a8f52f445b48312b61bd Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 00:10:49 +0900 Subject: [PATCH 30/37] fix mistake in #15583 --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f77123b9f85..76557dd7f5e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1617,7 +1617,6 @@ def init(self, all_prompts, all_seeds, all_subseeds): x1, y1, x2, y2 = crop_region mask = mask.crop(crop_region) image_mask = images.resize_image(2, mask, self.width, self.height) - self.inpaint_full_res = False self.paste_to = (x1, y1, x2-x1, y2-y1) self.extra_generation_params["Inpaint area"] = "Only masked" self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding @@ -1625,6 +1624,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): crop_region = None image_mask = None self.mask_for_overlay = None + self.inpaint_full_res = False massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.' model_hijack.comments.append(massage) logging.info(massage) From a183ea4ba773da5144e9f6b99fa48bff13078d2a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 11:49:55 +0300 Subject: [PATCH 31/37] undo adding scripts to sys.modules --- modules/script_loading.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 20c7998acec..7cbba71deb8 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -2,7 +2,6 @@ import importlib.util from modules import errors -import sys loaded_scripts = {} @@ -14,8 +13,8 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(full_module_name, path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) + loaded_scripts[path] = module - sys.modules[full_module_name] = module return module From e84703b2539c21d68881852f7e9738d76e7de26f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 11:59:54 +0300 Subject: [PATCH 32/37] update changelog --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb816cc8fd..f0137f38f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## 1.9.1 + +### Minor: +* Add avif support ([#15582](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15582)) +* Add filename patterns: `[sampler_scheduler]` and `[scheduler]` ([#15581](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15581)) + +### Extensions and API: +* undo adding scripts to sys.modules +* Add schedulers API endpoint ([#15577](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15577)) +* Remove API upscaling factor limits ([#15560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15560)) + +### Bug Fixes: +* Fix images do not match / Coordinate 'right' is less than 'left' ([#15534](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15534)) +* fix: remove_callbacks_for_function should also remove from the ordered map ([#15533](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15533)) +* fix x1 upscalers ([#15555](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15555)) +* Fix cls.__module__ value in extension script ([#15532](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15532)) +* fix typo in function call (eror -> error) ([#15531](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15531)) + +### Other: +* Hide 'No Image data blocks found.' message ([#15567](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15567)) +* Allow webui.sh to be runnable from arbitrary directories containing a .git file ([#15561](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15561)) +* Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ ([#15544](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15544)) +* numpy DeprecationWarning product -> prod ([#15547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15547)) +* get_crop_region_v2 ([#15583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15583), [#15587](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15587)) + + ## 1.9.0 ### Features: @@ -85,7 +111,6 @@ * Fix extra-single-image API not doing upscale failed ([#15465](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15465)) * error handling paste_field callables ([#15470](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15470)) - ### Hardware: * Add training support and change lspci for Ascend NPU ([#14981](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14981)) * Update to ROCm5.7 and PyTorch ([#14820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14820)) From 61f6479ea9a85fd12b830be2dfe71a51db34b121 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 12:19:30 +0300 Subject: [PATCH 33/37] restore 1.8.0-style naming of scripts --- modules/script_loading.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/script_loading.py b/modules/script_loading.py index 7cbba71deb8..cccb309665f 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -8,9 +8,7 @@ def load_module(path): - module_name, _ = os.path.splitext(os.path.basename(path)) - full_module_name = "scripts." + module_name - module_spec = importlib.util.spec_from_file_location(full_module_name, path) + module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) From e9809de6512160501e34c54e9c8c5e5e493e306f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:21:48 +0900 Subject: [PATCH 34/37] restore 1.8.0-style naming of scripts --- extensions-builtin/hypertile/scripts/hypertile_xyz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/hypertile/scripts/hypertile_xyz.py b/extensions-builtin/hypertile/scripts/hypertile_xyz.py index 386c6b2d669..9e96ae3c527 100644 --- a/extensions-builtin/hypertile/scripts/hypertile_xyz.py +++ b/extensions-builtin/hypertile/scripts/hypertile_xyz.py @@ -1,7 +1,7 @@ from modules import scripts from modules.shared import opts -xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "scripts.xyz_grid"][0].module +xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module def int_applier(value_name:str, min_range:int = -1, max_range:int = -1): """ From e837124f4b1b9dcf3caccd8d2fa5730cf3df099a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 12:26:05 +0300 Subject: [PATCH 35/37] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0137f38f8a..d7bcef0bd23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.9.2 + +### Extensions and API: +* restore 1.8.0-style naming of scripts + ## 1.9.1 ### Minor: From 821adc3041b504b47b6e55bb8e8b451a90ee1833 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 22 Apr 2024 23:03:27 +0900 Subject: [PATCH 36/37] fix get_crop_region_v2 Co-Authored-By: Dowon --- modules/masking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/masking.py b/modules/masking.py index 8e869d1b1e0..2fc83031953 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -16,7 +16,7 @@ def get_crop_region_v2(mask, pad=0): mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) if box := mask.getbbox(): x1, y1, x2, y2 = box - return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1]) if pad else box + return (max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])) if pad else box def get_crop_region(mask, pad=0): From 7dfe959f4b9fbc1662def8a086e48932dff6c1b1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Apr 2024 18:00:23 +0300 Subject: [PATCH 37/37] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bcef0bd23..295d26c8c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.9.3 + +### Bug Fixes: +* fix get_crop_region_v2 ([#15594](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15594)) + ## 1.9.2 ### Extensions and API: