From 3bd898b6ce94f34bcc7685672f0e4318d2d86b33 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sat, 21 Jan 2023 23:14:59 +0300 Subject: [PATCH 0001/2418] First test of different sampler for hi-res fix --- modules/processing.py | 7 ++++++- modules/txt2img.py | 4 +++- modules/ui.py | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 6e6371a1fd2..b3206f80dfc 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -710,7 +710,7 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): sampler = 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, **kwargs): + 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_sampler: int = 0, **kwargs): super().__init__(**kwargs) self.enable_hr = enable_hr self.denoising_strength = denoising_strength @@ -721,6 +721,7 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi 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_sampler = hr_sampler if firstphase_width != 0 or firstphase_height != 0: self.hr_upscale_to_x = self.width @@ -862,6 +863,10 @@ def save_intermediate(image, index): shared.state.nextjob() img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM + + if self.hr_sampler != 0 and sd_samplers.samplers[self.hr_sampler].name != 'PLMS': + img2img_sampler_name = sd_samplers.samplers[self.hr_sampler].name + self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] diff --git a/modules/txt2img.py b/modules/txt2img.py index e945fd698d7..21f48d12df1 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -8,7 +8,7 @@ from modules.ui import plaintext_to_html -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, *args): +def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_sampler_index: int, *args): p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, @@ -38,6 +38,8 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, + hr_sampler=sd_samplers.samplers[hr_sampler_index].name + if hr_sampler_index != 0 else sd_samplers.samplers[sampler_index].name ) p.scripts = modules.scripts.scripts_txt2img diff --git a/modules/ui.py b/modules/ui.py index b3105901278..6bbbc0f6920 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -672,6 +672,9 @@ def create_ui(): 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"): + hr_sampler_index = gr.Dropdown(label='Hires sampling method', elem_id=f"hr_sampler", choices=["---"] + [x.name for x in samplers], value="---", type="index") + elif category == "batch": if not opts.dimensions_and_batch_together: with FormRow(elem_id="txt2img_column_batch"): @@ -730,6 +733,7 @@ def create_ui(): hr_second_pass_steps, hr_resize_x, hr_resize_y, + hr_sampler_index, ] + custom_inputs, outputs=[ @@ -785,6 +789,7 @@ def create_ui(): (hr_second_pass_steps, "Hires steps"), (hr_resize_x, "Hires resize-1"), (hr_resize_y, "Hires resize-2"), + (hr_sampler_index, "Hires sampling method"), *modules.scripts.scripts_txt2img.infotext_fields ] parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields) From 6c0566f937ba212e3faf1d30c337850543e85a12 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sat, 21 Jan 2023 23:25:36 +0300 Subject: [PATCH 0002/2418] Type mismatch fix --- modules/processing.py | 4 ++-- modules/txt2img.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index b3206f80dfc..65673a9d6c4 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -864,8 +864,8 @@ def save_intermediate(image, index): img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM - if self.hr_sampler != 0 and sd_samplers.samplers[self.hr_sampler].name != 'PLMS': - img2img_sampler_name = sd_samplers.samplers[self.hr_sampler].name + if self.hr_sampler != 0 and self.hr_sampler != 'PLMS': + img2img_sampler_name = self.hr_sampler self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) diff --git a/modules/txt2img.py b/modules/txt2img.py index 21f48d12df1..3d16ac5beb8 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -39,7 +39,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, hr_sampler=sd_samplers.samplers[hr_sampler_index].name - if hr_sampler_index != 0 else sd_samplers.samplers[sampler_index].name + if hr_sampler_index != 0 else 0 ) p.scripts = modules.scripts.scripts_txt2img From 8bec3a2aa10d3c8ab15cca51b3b498e6ba350105 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sat, 21 Jan 2023 23:31:36 +0300 Subject: [PATCH 0003/2418] Index fix --- modules/txt2img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/txt2img.py b/modules/txt2img.py index 3d16ac5beb8..8560b74f7b0 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -38,7 +38,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, - hr_sampler=sd_samplers.samplers[hr_sampler_index].name + hr_sampler=sd_samplers.samplers[hr_sampler_index - 1].name if hr_sampler_index != 0 else 0 ) From 9e1f49c4e5b169eecbf075c49a304774dfdc6035 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 00:03:16 +0300 Subject: [PATCH 0004/2418] PLMS edge-case handling fix --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 65673a9d6c4..6e04b0647ff 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -864,7 +864,7 @@ def save_intermediate(image, index): img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM - if self.hr_sampler != 0 and self.hr_sampler != 'PLMS': + if self.hr_sampler != 0 and sd_samplers.samplers[self.hr_sampler].name != 'PLMS': img2img_sampler_name = self.hr_sampler self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) From 3ffe2e768b542c837927cfdf0310a2e0fdd48683 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 00:07:46 +0300 Subject: [PATCH 0005/2418] PLMS edge-case handling fix 2 --- modules/processing.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 6e04b0647ff..83c65948f00 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -710,7 +710,7 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): sampler = 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_sampler: int = 0, **kwargs): + 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_sampler: int | str = 0, **kwargs): super().__init__(**kwargs) self.enable_hr = enable_hr self.denoising_strength = denoising_strength @@ -864,7 +864,11 @@ def save_intermediate(image, index): img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM - if self.hr_sampler != 0 and sd_samplers.samplers[self.hr_sampler].name != 'PLMS': + if self.hr_sampler == 0: + pass + elif self.hr_sampler == 'PLMS': + img2img_sampler_name = 'DDIM' + else: img2img_sampler_name = self.hr_sampler self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model) From 6cd7bf9f860dff3b61a50ec2d41915536cbcf448 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 00:08:58 +0300 Subject: [PATCH 0006/2418] PLMS edge-case handling fix 3 --- modules/processing.py | 4 ++-- modules/txt2img.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 83c65948f00..6f6efe06c34 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -710,7 +710,7 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): sampler = 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_sampler: int | str = 0, **kwargs): + 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_sampler: str = '---', **kwargs): super().__init__(**kwargs) self.enable_hr = enable_hr self.denoising_strength = denoising_strength @@ -864,7 +864,7 @@ def save_intermediate(image, index): img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM' # PLMS does not support img2img so we just silently switch ot DDIM - if self.hr_sampler == 0: + if self.hr_sampler == '---': pass elif self.hr_sampler == 'PLMS': img2img_sampler_name = 'DDIM' diff --git a/modules/txt2img.py b/modules/txt2img.py index 8560b74f7b0..9c8ec621fbe 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -39,7 +39,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, hr_sampler=sd_samplers.samplers[hr_sampler_index - 1].name - if hr_sampler_index != 0 else 0 + if hr_sampler_index != 0 else '---' ) p.scripts = modules.scripts.scripts_txt2img From 0f6862ef3041f3ee18e8236765b1c1958c84385b Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 00:11:05 +0300 Subject: [PATCH 0007/2418] PLMS edge-case handling fix 5 --- modules/processing.py | 2 -- modules/txt2img.py | 2 +- modules/ui.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 6f6efe06c34..a873498b605 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -866,8 +866,6 @@ def save_intermediate(image, index): if self.hr_sampler == '---': pass - elif self.hr_sampler == 'PLMS': - img2img_sampler_name = 'DDIM' else: img2img_sampler_name = self.hr_sampler diff --git a/modules/txt2img.py b/modules/txt2img.py index 9c8ec621fbe..c6ea11c27e1 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -38,7 +38,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, - hr_sampler=sd_samplers.samplers[hr_sampler_index - 1].name + hr_sampler=sd_samplers.samplers_for_img2img[hr_sampler_index - 1].name if hr_sampler_index != 0 else '---' ) diff --git a/modules/ui.py b/modules/ui.py index 6bbbc0f6920..b408379ffc7 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -673,7 +673,7 @@ def create_ui(): 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"): - hr_sampler_index = gr.Dropdown(label='Hires sampling method', elem_id=f"hr_sampler", choices=["---"] + [x.name for x in samplers], value="---", type="index") + hr_sampler_index = gr.Dropdown(label='Hires sampling method', elem_id=f"hr_sampler", choices=["---"] + [x.name for x in samplers_for_img2img], value="---", type="index") elif category == "batch": if not opts.dimensions_and_batch_together: From f7b38c484151a50a75788094d9cd357dd4841966 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 00:18:26 +0300 Subject: [PATCH 0008/2418] Style fix --- modules/txt2img.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/txt2img.py b/modules/txt2img.py index c6ea11c27e1..2eb41f3d5b6 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -38,8 +38,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, - hr_sampler=sd_samplers.samplers_for_img2img[hr_sampler_index - 1].name - if hr_sampler_index != 0 else '---' + hr_sampler=sd_samplers.samplers_for_img2img[hr_sampler_index - 1].name if hr_sampler_index != 0 else '---' ) p.scripts = modules.scripts.scripts_txt2img From 35b4104dafa97de913f563a8658a23b18a20859e Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:32:48 +0300 Subject: [PATCH 0009/2418] Change to run workflow --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c0cd1ef7b4..7c3375cf312 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Stable Diffusion web UI +# Stable Diffusion web U A browser interface based on Gradio library for Stable Diffusion. ![](screenshot.png) From cd14e7e8fd6321658c132de5748c8bc9a34840af Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:33:21 +0300 Subject: [PATCH 0010/2418] Revert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c3375cf312..9c0cd1ef7b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Stable Diffusion web U +# Stable Diffusion web UI A browser interface based on Gradio library for Stable Diffusion. ![](screenshot.png) From 8114959e7e937361b719cd85bec246ffd258dca6 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 14:28:53 +0300 Subject: [PATCH 0011/2418] Hr separate prompt test --- modules/processing.py | 23 ++++++++++++++++++++++- modules/txt2img.py | 6 ++++-- modules/ui.py | 10 ++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index a873498b605..f407e175a4a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -516,6 +516,23 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] + if type(p) == StableDiffusionProcessingTxt2Img: + if p.hr_enabled and p.is_hr_pass: + if p.hr_prompt: + if type(p.prompt) == list: + p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] + else: + p.all_prompts = p.batch_size * p.n_iter * [ + shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] + + if p.hr_negative_prompt: + if type(p.negative_prompt) == list: + p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in + p.hr_negative_prompt] + else: + p.all_negative_prompts = p.batch_size * p.n_iter * [ + shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] + if type(seed) == list: p.all_seeds = seed else: @@ -710,7 +727,7 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): sampler = 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_sampler: str = '---', **kwargs): + 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_sampler: str = '---', hr_prompt: str = '', hr_negative_prompt: str = '', **kwargs): super().__init__(**kwargs) self.enable_hr = enable_hr self.denoising_strength = denoising_strength @@ -722,6 +739,9 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi self.hr_upscale_to_x = hr_resize_x self.hr_upscale_to_y = hr_resize_y self.hr_sampler = hr_sampler + self.hr_prompt = hr_prompt if hr_prompt != '' else self.prompt + self.hr_negative_prompt = hr_negative_prompt if hr_negative_prompt != '' else self.negative_prompt + self.is_hr_pass = False if firstphase_width != 0 or firstphase_height != 0: self.hr_upscale_to_x = self.width @@ -808,6 +828,7 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs if not self.enable_hr: return samples + self.is_hr_pass = True target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y diff --git a/modules/txt2img.py b/modules/txt2img.py index 2eb41f3d5b6..c06f9f9da09 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -8,7 +8,7 @@ from modules.ui import plaintext_to_html -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_sampler_index: int, *args): +def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_sampler_index: int, hr_prompt: str, hr_negative_prompt, *args): p = StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, @@ -38,7 +38,9 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, - hr_sampler=sd_samplers.samplers_for_img2img[hr_sampler_index - 1].name if hr_sampler_index != 0 else '---' + hr_sampler=sd_samplers.samplers_for_img2img[hr_sampler_index - 1].name if hr_sampler_index != 0 else '---', + hr_prompt=hr_prompt, + hr_negative_prompt=hr_negative_prompt ) p.scripts = modules.scripts.scripts_txt2img diff --git a/modules/ui.py b/modules/ui.py index b408379ffc7..e0a9e40b7ac 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -675,6 +675,14 @@ def create_ui(): with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"): hr_sampler_index = gr.Dropdown(label='Hires sampling method', elem_id=f"hr_sampler", choices=["---"] + [x.name for x in samplers_for_img2img], value="---", type="index") + with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact"): + with gr.Column(scale=80): + with gr.Row(): + hr_prompt = gr.Textbox(label="Prompt", elem_id=f"hires_prompt", show_label=False, lines=3, placeholder="Prompt that will be used for hires fix pass") + with gr.Column(scale=80): + with gr.Row(): + hr_negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt that will be used for hires fix pass") + elif category == "batch": if not opts.dimensions_and_batch_together: with FormRow(elem_id="txt2img_column_batch"): @@ -734,6 +742,8 @@ def create_ui(): hr_resize_x, hr_resize_y, hr_sampler_index, + hr_prompt, + hr_negative_prompt, ] + custom_inputs, outputs=[ From b331ca784ad831de088ad3915b45da39c2a76fa8 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 14:35:34 +0300 Subject: [PATCH 0012/2418] Fix --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f407e175a4a..161999d57d5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -517,7 +517,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] if type(p) == StableDiffusionProcessingTxt2Img: - if p.hr_enabled and p.is_hr_pass: + if p.enable_hr and p.is_hr_pass: if p.hr_prompt: if type(p.prompt) == list: p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] From 81e0723d6559729f5b207ae04bc615318af5a11e Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 14:41:41 +0300 Subject: [PATCH 0013/2418] Logging for debugging --- modules/processing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 161999d57d5..eeab4b0c8d9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -518,12 +518,14 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr and p.is_hr_pass: + logging.info("Running hr pass with custom prompt") if p.hr_prompt: if type(p.prompt) == list: p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] else: p.all_prompts = p.batch_size * p.n_iter * [ shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] + logging.info(p.all_prompts) if p.hr_negative_prompt: if type(p.negative_prompt) == list: @@ -532,6 +534,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: p.all_negative_prompts = p.batch_size * p.n_iter * [ shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] + logging.info(p.all_negative_prompts) if type(seed) == list: p.all_seeds = seed From f774a8d24ec57cf0b795fedb0c54f0304b43b4d9 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 14:52:01 +0300 Subject: [PATCH 0014/2418] Hr-fix separate prompt experimentation --- modules/processing.py | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index eeab4b0c8d9..1133619f778 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -516,25 +516,25 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] - if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.is_hr_pass: - logging.info("Running hr pass with custom prompt") - if p.hr_prompt: - if type(p.prompt) == list: - p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] - else: - p.all_prompts = p.batch_size * p.n_iter * [ - shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] - logging.info(p.all_prompts) - - if p.hr_negative_prompt: - if type(p.negative_prompt) == list: - p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in - p.hr_negative_prompt] - else: - p.all_negative_prompts = p.batch_size * p.n_iter * [ - shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] - logging.info(p.all_negative_prompts) + # if type(p) == StableDiffusionProcessingTxt2Img: + # if p.enable_hr and p.is_hr_pass: + # logging.info("Running hr pass with custom prompt") + # if p.hr_prompt: + # if type(p.prompt) == list: + # p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] + # else: + # p.all_prompts = p.batch_size * p.n_iter * [ + # shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] + # logging.info(p.all_prompts) + # + # if p.hr_negative_prompt: + # if type(p.negative_prompt) == list: + # p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in + # p.hr_negative_prompt] + # else: + # p.all_negative_prompts = p.batch_size * p.n_iter * [ + # shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] + # logging.info(p.all_negative_prompts) if type(seed) == list: p.all_seeds = seed @@ -744,7 +744,6 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi self.hr_sampler = hr_sampler self.hr_prompt = hr_prompt if hr_prompt != '' else self.prompt self.hr_negative_prompt = hr_negative_prompt if hr_negative_prompt != '' else self.negative_prompt - self.is_hr_pass = False if firstphase_width != 0 or firstphase_height != 0: self.hr_upscale_to_x = self.width @@ -831,7 +830,9 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs if not self.enable_hr: return samples - self.is_hr_pass = True + self.prompt = self.hr_prompt + self.negative_prompt = self.hr_negative_prompt + target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y From a9f0e7d53611cf11331e2befd34f0351b47795ee Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:12:00 +0300 Subject: [PATCH 0015/2418] hr conditioning --- modules/processing.py | 72 +++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 1133619f778..21886bb51bc 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -235,7 +235,7 @@ def img2img_image_conditioning(self, source_image, latent_image, image_mask=None def init(self, all_prompts, all_seeds, all_subseeds): pass - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_uconditional_conditioning, seeds, subseeds, subseed_strength, prompts): raise NotImplementedError() def close(self): @@ -516,25 +516,25 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] - # if type(p) == StableDiffusionProcessingTxt2Img: - # if p.enable_hr and p.is_hr_pass: - # logging.info("Running hr pass with custom prompt") - # if p.hr_prompt: - # if type(p.prompt) == list: - # p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] - # else: - # p.all_prompts = p.batch_size * p.n_iter * [ - # shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] - # logging.info(p.all_prompts) - # - # if p.hr_negative_prompt: - # if type(p.negative_prompt) == list: - # p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in - # p.hr_negative_prompt] - # else: - # p.all_negative_prompts = p.batch_size * p.n_iter * [ - # shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] - # logging.info(p.all_negative_prompts) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr and p.is_hr_pass: + logging.info("Running hr pass with custom prompt") + if p.hr_prompt: + if type(p.prompt) == list: + p.all_hr_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] + else: + p.all_hr_prompts = p.batch_size * p.n_iter * [ + shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] + logging.info(p.all_prompts) + + if p.hr_negative_prompt: + if type(p.negative_prompt) == list: + p.all_hr_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in + p.hr_negative_prompt] + else: + p.all_hr_negative_prompts = p.batch_size * p.n_iter * [ + shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] + logging.info(p.all_negative_prompts) if type(seed) == list: p.all_seeds = seed @@ -607,6 +607,12 @@ def get_conds_with_caching(function, required_prompts, steps, cache): prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size] negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] + + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr: + hr_prompts = p.all_hr_prompts[n * p.batch_size:(n + 1) * p.batch_size] + hr_negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] + seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] @@ -620,6 +626,12 @@ def get_conds_with_caching(function, required_prompts, steps, cache): uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr: + hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, + cached_uc) + hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, + cached_c) if len(model_hijack.comments) > 0: for comment in model_hijack.comments: @@ -629,7 +641,16 @@ def get_conds_with_caching(function, required_prompts, steps, cache): shared.state.job = f"Batch {n+1} out of {p.n_iter}" with devices.autocast(): - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr: + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_uconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, + subseeds=subseeds, + subseed_strength=p.subseed_strength, prompts=prompts) + else: + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, + subseeds=subseeds, + subseed_strength=p.subseed_strength, prompts=prompts) x_samples_ddim = [decode_first_stage(p.sd_model, samples_ddim[i:i+1].to(dtype=devices.dtype_vae))[0].cpu() for i in range(samples_ddim.size(0))] for x in x_samples_ddim: @@ -744,6 +765,8 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi self.hr_sampler = hr_sampler self.hr_prompt = hr_prompt if hr_prompt != '' else self.prompt self.hr_negative_prompt = hr_negative_prompt if hr_negative_prompt != '' else self.negative_prompt + self.all_hr_prompts = None + self.all_hr_negative_prompts = None if firstphase_width != 0 or firstphase_height != 0: self.hr_upscale_to_x = self.width @@ -817,7 +840,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_upscaler is not None: self.extra_generation_params["Hires upscaler"] = self.hr_upscaler - def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_uconditional_conditioning, seeds, subseeds, subseed_strength, prompts): self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") @@ -830,9 +853,6 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs if not self.enable_hr: return samples - self.prompt = self.hr_prompt - self.negative_prompt = self.hr_negative_prompt - target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y @@ -904,7 +924,7 @@ def save_intermediate(image, index): x = None devices.torch_gc() - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) + samples = self.sampler.sample_img2img(self, samples, noise, hr_conditioning, hr_unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) return samples From 4e0cf7d4eda3d590df3e4b28654037d2dfb7ab94 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:15:08 +0300 Subject: [PATCH 0016/2418] hr conditioning --- modules/processing.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 21886bb51bc..6b49b4e378c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -517,24 +517,16 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.is_hr_pass: - logging.info("Running hr pass with custom prompt") - if p.hr_prompt: - if type(p.prompt) == list: - p.all_hr_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] - else: - p.all_hr_prompts = p.batch_size * p.n_iter * [ - shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] - logging.info(p.all_prompts) - - if p.hr_negative_prompt: - if type(p.negative_prompt) == list: - p.all_hr_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in - p.hr_negative_prompt] - else: - p.all_hr_negative_prompts = p.batch_size * p.n_iter * [ - shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] - logging.info(p.all_negative_prompts) + if p.enable_hr: + if type(p.prompt) == list: + p.all_hr_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] + else: + p.all_hr_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_styles_to_prompt(p.hr_prompt, p.styles)] + + if type(p.negative_prompt) == list: + p.all_hr_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in p.hr_negative_prompt] + else: + p.all_hr_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.hr_negative_prompt, p.styles)] if type(seed) == list: p.all_seeds = seed @@ -628,9 +620,9 @@ def get_conds_with_caching(function, required_prompts, steps, cache): c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: - hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, + hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, p.steps, cached_uc) - hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, + hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, p.steps, cached_c) if len(model_hijack.comments) > 0: @@ -840,7 +832,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_upscaler is not None: self.extra_generation_params["Hires upscaler"] = self.hr_upscaler - def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_uconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") From c5d4c87c02ce4c503de149db5ee3e87af4402cf0 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:17:43 +0300 Subject: [PATCH 0017/2418] hr conditioning --- modules/processing.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 6b49b4e378c..a81ace6f012 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -635,10 +635,11 @@ def get_conds_with_caching(function, required_prompts, steps, cache): with devices.autocast(): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_uconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, - subseeds=subseeds, - subseed_strength=p.subseed_strength, prompts=prompts) + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + else: + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, + subseed_strength=p.subseed_strength, prompts=prompts) + else: samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, From 2ab2bce74d7b7affb80b55da9d1ffb219129c1ef Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:28:38 +0300 Subject: [PATCH 0018/2418] hr conditioning --- modules/processing.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index a81ace6f012..bab1c101005 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -235,7 +235,7 @@ def img2img_image_conditioning(self, source_image, latent_image, image_mask=None def init(self, all_prompts, all_seeds, all_subseeds): pass - def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_uconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, hr_conditioning=None, hr_unconditional_conditioning=None, seeds, subseeds, subseed_strength, prompts): raise NotImplementedError() def close(self): @@ -517,7 +517,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr: + if p.enable_hr and p.hr_prompt != '': if type(p.prompt) == list: p.all_hr_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] else: @@ -601,7 +601,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr: + if p.enable_hr and p.hr_prompt != '': hr_prompts = p.all_hr_prompts[n * p.batch_size:(n + 1) * p.batch_size] hr_negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] @@ -619,7 +619,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr: + if p.enable_hr and p.hr_prompt != '': hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, p.steps, cached_uc) hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, p.steps, @@ -635,7 +635,12 @@ def get_conds_with_caching(function, required_prompts, steps, cache): with devices.autocast(): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + if p.hr_prompts != '': + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + else: + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=c, + hr_unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, + subseed_strength=p.subseed_strength, prompts=prompts) else: samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) @@ -756,8 +761,8 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi self.hr_upscale_to_x = hr_resize_x self.hr_upscale_to_y = hr_resize_y self.hr_sampler = hr_sampler - self.hr_prompt = hr_prompt if hr_prompt != '' else self.prompt - self.hr_negative_prompt = hr_negative_prompt if hr_negative_prompt != '' else self.negative_prompt + self.hr_prompt = hr_prompt if hr_prompt != '' else '' + self.hr_negative_prompt = hr_negative_prompt if hr_negative_prompt != '' else '' self.all_hr_prompts = None self.all_hr_negative_prompts = None From 125d5c8d96eb374a7ba214dba51da9cf30da6564 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:31:11 +0300 Subject: [PATCH 0019/2418] hr conditioning --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index bab1c101005..f0073d1a7c9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -235,7 +235,7 @@ def img2img_image_conditioning(self, source_image, latent_image, image_mask=None def init(self, all_prompts, all_seeds, all_subseeds): pass - def sample(self, conditioning, unconditional_conditioning, hr_conditioning=None, hr_unconditional_conditioning=None, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts, hr_conditioning=None, hr_unconditional_conditioning=None): raise NotImplementedError() def close(self): @@ -838,7 +838,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_upscaler is not None: self.extra_generation_params["Hires upscaler"] = self.hr_upscaler - def sample(self, conditioning, unconditional_conditioning, hr_conditioning, hr_unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts, hr_conditioning=None, hr_unconditional_conditioning=None): self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") From 34f6d667429ec3d4e0f09c6148fd37e66c460cb7 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:32:47 +0300 Subject: [PATCH 0020/2418] hr conditioning --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f0073d1a7c9..b2de8f92b34 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -635,7 +635,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): with devices.autocast(): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: - if p.hr_prompts != '': + if p.hr_prompt != '': samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) else: samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=c, From b0ae92d605dfb65314b610589f84b643d535261e Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:43:12 +0300 Subject: [PATCH 0021/2418] UI improvements --- modules/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index e0a9e40b7ac..1656e76da3e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -678,10 +678,10 @@ def create_ui(): with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact"): with gr.Column(scale=80): with gr.Row(): - hr_prompt = gr.Textbox(label="Prompt", elem_id=f"hires_prompt", show_label=False, lines=3, placeholder="Prompt that will be used for hires fix pass") + hr_prompt = gr.Textbox(label="Prompt", elem_id=f"hires_prompt", show_label=False, lines=3, placeholder="Prompt that will be used for hires fix pass (leave it blank to use the same prompt as in initial txt2img gen)") with gr.Column(scale=80): with gr.Row(): - hr_negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt that will be used for hires fix pass") + hr_negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt that will be used for hires fix pass (leave it blank to use the same prompt as in initial txt2img gen)") elif category == "batch": if not opts.dimensions_and_batch_together: From bbb1e35ea29f8cfc6a6db3b28b10bfbc99033c70 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:44:59 +0300 Subject: [PATCH 0022/2418] UI and PNG info improvements --- modules/processing.py | 3 +++ modules/ui.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index b2de8f92b34..0cf4771e49d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -789,6 +789,9 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale + self.extra_generation_params["Hires sampler"] = self.hr_sampler + self.extra_generation_params["Hires prompt"] = self.hr_prompt + self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt self.hr_upscale_to_x = int(self.width * self.hr_scale) self.hr_upscale_to_y = int(self.height * self.hr_scale) else: diff --git a/modules/ui.py b/modules/ui.py index 1656e76da3e..6418088990e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -800,6 +800,8 @@ def create_ui(): (hr_resize_x, "Hires resize-1"), (hr_resize_y, "Hires resize-2"), (hr_sampler_index, "Hires sampling method"), + (hr_prompt, "Hires prompt"), + (hr_negative_prompt, "Hires negative prompt"), *modules.scripts.scripts_txt2img.infotext_fields ] parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields) From a5c2b5ed8991e0e1ffea7b595ab13f93715dfa5c Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 15:50:20 +0300 Subject: [PATCH 0023/2418] UI and PNG info improvements --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 0cf4771e49d..8b4b2399ab1 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -790,8 +790,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale self.extra_generation_params["Hires sampler"] = self.hr_sampler - self.extra_generation_params["Hires prompt"] = self.hr_prompt - self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt + self.extra_generation_params["Hires prompt"] = f"({self.hr_prompt})" + self.extra_generation_params["Hires negative prompt"] = f"({self.hr_negative_prompt})" self.hr_upscale_to_x = int(self.width * self.hr_scale) self.hr_upscale_to_y = int(self.height * self.hr_scale) else: From 2aaee73633ef7a6d12561859ca2d58d0e10cf297 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:00:35 +0300 Subject: [PATCH 0024/2418] Gen params paste improvement --- modules/generation_parameters_copypaste.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 46e12dc6c22..b8212abd974 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -253,6 +253,9 @@ def parse_generation_parameters(x: str): done_with_prompt = True line = line[16:].strip() + if line.startswith("Hires prompt:"): + res["Hires prompt"] = line[1:][:-1] + if done_with_prompt: negative_prompt += ("" if negative_prompt == "" else "\n") + line else: From 1fa777c1d77556d116d39144c47715f6b1325538 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:03:42 +0300 Subject: [PATCH 0025/2418] Gen params paste improvement --- modules/generation_parameters_copypaste.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index b8212abd974..67e76e30dc4 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -256,6 +256,9 @@ def parse_generation_parameters(x: str): if line.startswith("Hires prompt:"): res["Hires prompt"] = line[1:][:-1] + if line.startswith("Hires negative prompt:"): + res["Hires negative prompt"] = line[1:][:-1] + if done_with_prompt: negative_prompt += ("" if negative_prompt == "" else "\n") + line else: From d261bec1ec5a185b6e57f73e8589b07d5d5bc4e8 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:14:28 +0300 Subject: [PATCH 0026/2418] Gen params paste improvement --- modules/generation_parameters_copypaste.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 67e76e30dc4..155ab350aaa 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -252,13 +252,6 @@ def parse_generation_parameters(x: str): if line.startswith("Negative prompt:"): done_with_prompt = True line = line[16:].strip() - - if line.startswith("Hires prompt:"): - res["Hires prompt"] = line[1:][:-1] - - if line.startswith("Hires negative prompt:"): - res["Hires negative prompt"] = line[1:][:-1] - if done_with_prompt: negative_prompt += ("" if negative_prompt == "" else "\n") + line else: @@ -274,6 +267,10 @@ def parse_generation_parameters(x: str): res[k+"-2"] = m.group(2) else: res[k] = v + if k.startswith("Hires prompt:"): + res["Hires prompt"] = v[1:][:-1] + elif k.startswith("Hires negative prompt:"): + res["Hires negative prompt"] = v[1:][:-1] # Missing CLIP skip means it was set to 1 (the default) if "Clip skip" not in res: From fccc39834a61509a2fb60196229cbea20539aabb Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:17:55 +0300 Subject: [PATCH 0027/2418] Gen params paste improvement --- modules/generation_parameters_copypaste.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 155ab350aaa..44c8273f4bd 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -267,9 +267,10 @@ def parse_generation_parameters(x: str): res[k+"-2"] = m.group(2) else: res[k] = v - if k.startswith("Hires prompt:"): + + if k.startswith("Hires prompt"): res["Hires prompt"] = v[1:][:-1] - elif k.startswith("Hires negative prompt:"): + elif k.startswith("Hires negative prompt"): res["Hires negative prompt"] = v[1:][:-1] # Missing CLIP skip means it was set to 1 (the default) From 7f62300f7d496501a730d28f3aaaee885513998b Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:29:08 +0300 Subject: [PATCH 0028/2418] Gen params paste improvement --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 8b4b2399ab1..c56717f60b7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -790,8 +790,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale self.extra_generation_params["Hires sampler"] = self.hr_sampler - self.extra_generation_params["Hires prompt"] = f"({self.hr_prompt})" - self.extra_generation_params["Hires negative prompt"] = f"({self.hr_negative_prompt})" + self.extra_generation_params["Hires prompt"] = f'"{self.hr_prompt}"' + self.extra_generation_params["Hires negative prompt"] = f'"{self.hr_negative_prompt}"' self.hr_upscale_to_x = int(self.width * self.hr_scale) self.hr_upscale_to_y = int(self.height * self.hr_scale) else: From 3bc8ee998db5f461b8011a72e6f167012ccb8bc1 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 22 Jan 2023 16:35:42 +0300 Subject: [PATCH 0029/2418] Gen params paste improvement --- modules/generation_parameters_copypaste.py | 4 ++-- modules/processing.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 44c8273f4bd..98098cc80f3 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -269,9 +269,9 @@ def parse_generation_parameters(x: str): res[k] = v if k.startswith("Hires prompt"): - res["Hires prompt"] = v[1:][:-1] + res["Hires prompt"] = v[1:][:-1].replace(';', ',') elif k.startswith("Hires negative prompt"): - res["Hires negative prompt"] = v[1:][:-1] + res["Hires negative prompt"] = v[1:][:-1].replace(';', ',') # Missing CLIP skip means it was set to 1 (the default) if "Clip skip" not in res: diff --git a/modules/processing.py b/modules/processing.py index c56717f60b7..01c1b53c9e3 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -790,8 +790,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale self.extra_generation_params["Hires sampler"] = self.hr_sampler - self.extra_generation_params["Hires prompt"] = f'"{self.hr_prompt}"' - self.extra_generation_params["Hires negative prompt"] = f'"{self.hr_negative_prompt}"' + self.extra_generation_params["Hires prompt"] = f'({self.hr_prompt.replace(",", ";")})' + self.extra_generation_params["Hires negative prompt"] = f'({self.hr_negative_prompt.replace(",", ";")})' self.hr_upscale_to_x = int(self.width * self.hr_scale) self.hr_upscale_to_y = int(self.height * self.hr_scale) else: From c92ec3a9255a8364f4212d5e7434da1785752737 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 29 Jan 2023 19:07:00 +0300 Subject: [PATCH 0030/2418] Extra networks loading fix --- modules/processing.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 605c541ed3d..da890fb31ac 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -555,6 +555,10 @@ def infotext(iteration=0, position_in_batch=0): model_hijack.embedding_db.load_textual_inversion_embeddings() _, extra_network_data = extra_networks.parse_prompts(p.all_prompts[0:1]) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr and p.hr_prompt != '': + _, hr_extra_network_data = extra_networks.parse_prompts(p.all_hr_prompts[0:1]) + if p.scripts is not None: p.scripts.process(p) @@ -594,7 +598,11 @@ def get_conds_with_caching(function, required_prompts, steps, cache): sd_vae_approx.model() if not p.disable_extra_networks: - extra_networks.activate(p, extra_network_data) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr and p.hr_prompt != '': + extra_networks.activate(p, extra_network_data + hr_extra_network_data) + else: + extra_networks.activate(p, extra_network_data) with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: processed = Processed(p, [], p.seed, "") @@ -745,7 +753,11 @@ def get_conds_with_caching(function, required_prompts, steps, cache): images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) if not p.disable_extra_networks: - extra_networks.deactivate(p, extra_network_data) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr and p.hr_prompt != '': + extra_networks.deactivate(p, extra_network_data + hr_extra_network_data) + else: + extra_networks.deactivate(p, extra_network_data) devices.torch_gc() From 6127d2ff1bc5c3bc81d876e6dbc9a60f79aec898 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 29 Jan 2023 19:13:27 +0300 Subject: [PATCH 0031/2418] Extra networks loading fix --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index da890fb31ac..a56915c5072 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -600,7 +600,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if not p.disable_extra_networks: if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr and p.hr_prompt != '': - extra_networks.activate(p, extra_network_data + hr_extra_network_data) + extra_networks.activate(p, extra_network_data | hr_extra_network_data) else: extra_networks.activate(p, extra_network_data) @@ -755,7 +755,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if not p.disable_extra_networks: if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr and p.hr_prompt != '': - extra_networks.deactivate(p, extra_network_data + hr_extra_network_data) + extra_networks.deactivate(p, extra_network_data | hr_extra_network_data) else: extra_networks.deactivate(p, extra_network_data) From 9beeef6267ecbcb16f8b24e0092194f0f00142a6 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 29 Jan 2023 19:16:17 +0300 Subject: [PATCH 0032/2418] Extra networks loading fix --- modules/processing.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index a56915c5072..98e83c6928f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -558,6 +558,7 @@ def infotext(iteration=0, position_in_batch=0): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr and p.hr_prompt != '': _, hr_extra_network_data = extra_networks.parse_prompts(p.all_hr_prompts[0:1]) + extra_network_data.update(hr_extra_network_data) if p.scripts is not None: @@ -598,11 +599,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): sd_vae_approx.model() if not p.disable_extra_networks: - if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': - extra_networks.activate(p, extra_network_data | hr_extra_network_data) - else: - extra_networks.activate(p, extra_network_data) + extra_networks.activate(p, extra_network_data) with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: processed = Processed(p, [], p.seed, "") @@ -753,11 +750,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True) if not p.disable_extra_networks: - if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': - extra_networks.deactivate(p, extra_network_data | hr_extra_network_data) - else: - extra_networks.deactivate(p, extra_network_data) + extra_networks.deactivate(p, extra_network_data) devices.torch_gc() From 425eab34641e8e378db610d6a2b23f3ababe1058 Mon Sep 17 00:00:00 2001 From: invincibledude <> Date: Sun, 29 Jan 2023 19:26:31 +0300 Subject: [PATCH 0033/2418] Extra network in hr abomination fix --- modules/processing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 98e83c6928f..a2a91a5b1ae 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -558,7 +558,8 @@ def infotext(iteration=0, position_in_batch=0): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr and p.hr_prompt != '': _, hr_extra_network_data = extra_networks.parse_prompts(p.all_hr_prompts[0:1]) - extra_network_data.update(hr_extra_network_data) + if p.all_hr_prompts != p.all_prompts: + extra_network_data.update(hr_extra_network_data) if p.scripts is not None: From c3bd113a0b969732a324aff415c49ea9ad434707 Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:24:41 +0000 Subject: [PATCH 0034/2418] Image info fix --- modules/processing.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 61e6dfcd3c2..43fac21c1b4 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -808,6 +808,13 @@ def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, fi def init(self, all_prompts, all_seeds, all_subseeds): if self.enable_hr: + if self.hr_sampler != '---': + self.extra_generation_params["Hires sampler"] = self.hr_sampler + + if self.hr_prompt != '': + self.extra_generation_params["Hires prompt"] = f'({self.hr_prompt.replace(",", ";")})' + self.extra_generation_params["Hires negative prompt"] = f'({self.hr_negative_prompt.replace(",", ";")})' + if opts.use_old_hires_fix_width_height and self.applied_old_hires_behavior_to != (self.width, self.height): self.hr_resize_x = self.width self.hr_resize_y = self.height @@ -819,9 +826,6 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_resize_x == 0 and self.hr_resize_y == 0: self.extra_generation_params["Hires upscale"] = self.hr_scale - self.extra_generation_params["Hires sampler"] = self.hr_sampler - self.extra_generation_params["Hires prompt"] = f'({self.hr_prompt.replace(",", ";")})' - self.extra_generation_params["Hires negative prompt"] = f'({self.hr_negative_prompt.replace(",", ";")})' self.hr_upscale_to_x = int(self.width * self.hr_scale) self.hr_upscale_to_y = int(self.height * self.hr_scale) else: From 51f81efb02876d24c9e6d844e8c0cbd2384f6514 Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:30:20 +0300 Subject: [PATCH 0035/2418] Image processing changes Image processing changes --- modules/processing.py | 76 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 43fac21c1b4..72dd3f6f577 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -528,7 +528,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)] if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': + if p.enable_hr and p.hr_prompt == '': + p.all_hr_prompts, p.all_hr_negative_prompts = p.all_prompts, p.all_negative_prompts + elif p.enable_hr and p.hr_prompt != '': if type(p.prompt) == list: p.all_hr_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.hr_prompt] else: @@ -555,14 +557,6 @@ def infotext(iteration=0, position_in_batch=0): if os.path.exists(cmd_opts.embeddings_dir) and not p.do_not_reload_embeddings: model_hijack.embedding_db.load_textual_inversion_embeddings() - _, extra_network_data = extra_networks.parse_prompts(p.all_prompts[0:1]) - if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': - _, hr_extra_network_data = extra_networks.parse_prompts(p.all_hr_prompts[0:1]) - if p.all_hr_prompts != p.all_prompts: - extra_network_data.update(hr_extra_network_data) - - if p.scripts is not None: p.scripts.process(p) @@ -600,13 +594,6 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN": sd_vae_approx.model() - if not p.disable_extra_networks: - extra_networks.activate(p, extra_network_data) - - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - processed = Processed(p, [], p.seed, "") - file.write(processed.infotext(p, 0)) - if state.job_count == -1: state.job_count = p.n_iter @@ -623,9 +610,12 @@ def get_conds_with_caching(function, required_prompts, steps, cache): negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': - hr_prompts = p.all_hr_prompts[n * p.batch_size:(n + 1) * p.batch_size] - hr_negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] + if p.enable_hr: + if p.hr_prompt == '': + hr_prompts, hr_negative_prompts = prompts, negative_prompts + else: + hr_prompts = p.all_hr_prompts[n * p.batch_size:(n + 1) * p.batch_size] + hr_negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] @@ -633,19 +623,40 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if len(prompts) == 0: break - prompts, _ = extra_networks.parse_prompts(prompts) + prompts, extra_network_data = extra_networks.parse_prompts(prompts) + if type(p) == StableDiffusionProcessingTxt2Img: + if p.enable_hr and hr_prompts != prompts: + _, hr_extra_network_data = extra_networks.parse_prompts(hr_prompts) + extra_network_data.update(hr_extra_network_data) + + + + if not p.disable_extra_networks: + with devices.autocast(): + extra_networks.activate(p, extra_network_data) if p.scripts is not None: p.scripts.process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds) + # params.txt should be saved after scripts.process_batch, since the + # infotext could be modified by that callback + # Example: a wildcard processed by process_batch sets an extra model + # strength, which is saved as "Model Strength: 1.0" in the infotext + if n == 0: + with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: + processed = Processed(p, [], p.seed, "") + file.write(processed.infotext(p, 0)) + uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c) + if type(p) == StableDiffusionProcessingTxt2Img: - if p.enable_hr and p.hr_prompt != '': - hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, p.steps, - cached_uc) - hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, p.steps, - cached_c) + if p.enable_hr: + if prompts != hr_prompts: + hr_uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, hr_negative_prompts, p.steps, cached_uc) + hr_c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, hr_prompts, p.steps, cached_c) + else: + hr_uc, hr_c = uc, c if len(model_hijack.comments) > 0: for comment in model_hijack.comments: @@ -658,20 +669,9 @@ def get_conds_with_caching(function, required_prompts, steps, cache): with devices.without_autocast() if devices.unet_needs_upcast else devices.autocast(): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: - if p.hr_prompt != '': - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) - else: - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=c, - hr_unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, - subseed_strength=p.subseed_strength, prompts=prompts) - else: - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, - subseed_strength=p.subseed_strength, prompts=prompts) - + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) else: - samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, - subseeds=subseeds, - subseed_strength=p.subseed_strength, prompts=prompts) + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) x_samples_ddim = [decode_first_stage(p.sd_model, samples_ddim[i:i+1].to(dtype=devices.dtype_vae))[0].cpu() for i in range(samples_ddim.size(0))] for x in x_samples_ddim: From b9fdb9f701e576ce48458fdf756187c2d1725649 Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Sat, 4 Mar 2023 18:09:05 +0000 Subject: [PATCH 0036/2418] Fix crash when hr is disabled --- modules/processing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 25479c0643a..f5cc24339b6 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -670,6 +670,8 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if type(p) == StableDiffusionProcessingTxt2Img: if p.enable_hr: samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, hr_conditioning=hr_c, hr_unconditional_conditioning=hr_uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) + else: + samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) else: samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts) From f6e27378404631d951656388fc178b784fe1495b Mon Sep 17 00:00:00 2001 From: InvincibleDude <81354513+InvincibleDude@users.noreply.github.com> Date: Fri, 10 Mar 2023 12:13:55 +0000 Subject: [PATCH 0037/2418] Negative prompt fix --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index f5cc24339b6..694e2637e41 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -615,7 +615,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): hr_prompts, hr_negative_prompts = prompts, negative_prompts else: hr_prompts = p.all_hr_prompts[n * p.batch_size:(n + 1) * p.batch_size] - hr_negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] + hr_negative_prompts = p.all_hr_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] From f6374934db001617e2b4dda438fc4017d065e33b Mon Sep 17 00:00:00 2001 From: Vespinian Date: Wed, 15 Mar 2023 17:53:32 -0400 Subject: [PATCH 0038/2418] Changed img2img scriptrunner for gui request from scripts_txt2img to scripts_img2img --- modules/img2img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/img2img.py b/modules/img2img.py index c973b7708dd..bf8f40125c4 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -151,7 +151,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s override_settings=override_settings, ) - p.scripts = modules.scripts.scripts_txt2img + p.scripts = modules.scripts.scripts_img2img p.script_args = args if shared.cmd_opts.enable_console_prompts: From 1d096ed1456c9b9b662477839853621848705e68 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Tue, 21 Mar 2023 16:07:24 -0600 Subject: [PATCH 0039/2418] Lazy load extra network images --- html/extra-networks-card.html | 6 +++--- modules/ui_extra_networks.py | 2 +- style.css | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 1bf3fc30d24..8348135b21c 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,6 +1,6 @@ -
- {metadata_button} - +
+ {preview_html} + {metadata_button}
    diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index cdfd6f2a0c4..1963f6a6110 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -131,7 +131,7 @@ def create_html_for_item(self, item, tabname): metadata_button = f"" args = { - "preview_html": "style='background-image: url(\"" + html.escape(preview) + "\")'" if preview else '', + "preview_html": f'' if preview else '', "prompt": item.get("prompt", None), "tabname": json.dumps(tabname), "local_preview": json.dumps(item["local_preview"]), diff --git a/style.css b/style.css index 3eac2b176e4..710e9a75911 100644 --- a/style.css +++ b/style.css @@ -1026,6 +1026,13 @@ footer { color: red; } +.extra-network-cards .card .preview{ + position: absolute; + object-fit: cover; + width: 100%; + height:100%; +} + [id*='_prompt_container'] > div { margin: 0!important; } From 771ea212de13711b494b082d8e94e79b17ac9d08 Mon Sep 17 00:00:00 2001 From: pieresimakp <69743585+pieresimakp@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:41:17 +0800 Subject: [PATCH 0040/2418] added button to grab the width and height from the loaded image in img2img --- javascript/hints.js | 1 + modules/ui.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 7f4101b2398..5bbc27a5db1 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -9,6 +9,7 @@ titles = { "UniPC": "Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models", "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", + "\u{1F4D0}": "Auto detect size from img2img", "Batch count": "How many batches of images to create (has no impact on generation performance or VRAM usage)", "Batch size": "How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)", "CFG Scale": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", diff --git a/modules/ui.py b/modules/ui.py index 7e603332796..6c6230028c2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -92,7 +92,7 @@ def gr_show(visible=True): clear_prompt_symbol = '\U0001F5D1' # 🗑️ extra_networks_symbol = '\U0001F3B4' # 🎴 switch_values_symbol = '\U000021C5' # ⇅ - +detect_image_size_symbol = '\U0001F4D0' # 📐 def plaintext_to_html(text): return ui_common.plaintext_to_html(text) @@ -756,8 +756,10 @@ def copy_image(img): with gr.Column(elem_id="img2img_column_size", scale=4): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - + + detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn") res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + if opts.dimensions_and_batch_together: with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") @@ -904,6 +906,7 @@ def select_img2img_tab(tab): img2img_prompt.submit(**img2img_args) submit.click(**img2img_args) + detect_image_size_btn.click(lambda i, w, h : i.size if i is not None else (w, h), inputs=[init_img, width, height], outputs=[width, height]) res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height]) img2img_interrogate.click( From fb72066ef6a2fed799468517932a76a39789cca6 Mon Sep 17 00:00:00 2001 From: pieresimakp <69743585+pieresimakp@users.noreply.github.com> Date: Sat, 25 Mar 2023 23:03:22 +0800 Subject: [PATCH 0041/2418] fixed button position --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index 9b6e3241595..464e4d8c902 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -758,8 +758,8 @@ def copy_image(img): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") - detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn") with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): + detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn") res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") if opts.dimensions_and_batch_together: From cb8c447f0d37b5ee9df9a7ee3e9d32b7966dabee Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sun, 26 Mar 2023 21:47:48 -0600 Subject: [PATCH 0042/2418] Update extra-networks-card.html --- html/extra-networks-card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index fe90b028417..7ea467c1bc7 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,4 +1,4 @@ -
    +
    {background_image} {metadata_button}
    From 0d2cf9ac186fc87cc56fce2a4e1136aca2386252 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Wed, 29 Mar 2023 16:35:37 +0700 Subject: [PATCH 0043/2418] feat: use existing virtualenv if already active --- webui.sh | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/webui.sh b/webui.sh index 8cdad22d310..0d25be2e566 100755 --- a/webui.sh +++ b/webui.sh @@ -152,24 +152,31 @@ else cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } fi -printf "\n%s\n" "${delimiter}" -printf "Create and activate python venv" -printf "\n%s\n" "${delimiter}" -cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } -if [[ ! -d "${venv_dir}" ]] -then - "${python_cmd}" -m venv "${venv_dir}" - first_launch=1 -fi -# shellcheck source=/dev/null -if [[ -f "${venv_dir}"/bin/activate ]] +if [[ -z "${VIRTUAL_ENV}" ]]; then - source "${venv_dir}"/bin/activate + printf "\n%s\n" "${delimiter}" + printf "Create and activate python venv" + printf "\n%s\n" "${delimiter}" + cd "${install_dir}"/"${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } + if [[ ! -d "${venv_dir}" ]] + then + "${python_cmd}" -m venv "${venv_dir}" + first_launch=1 + fi + # shellcheck source=/dev/null + if [[ -f "${venv_dir}"/bin/activate ]] + then + source "${venv_dir}"/bin/activate + else + printf "\n%s\n" "${delimiter}" + printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m" + printf "\n%s\n" "${delimiter}" + exit 1 + fi else printf "\n%s\n" "${delimiter}" - printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m" + printf "python venv already activate: ${VIRTUAL_ENV}" printf "\n%s\n" "${delimiter}" - exit 1 fi if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v accelerate)" ] From 56680cd84ab68a283772cf697f8a72408a3f4167 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 02:07:08 -0500 Subject: [PATCH 0044/2418] first --- launch.py | 6 ++++++ modules/cmd_args.py | 4 ++++ modules/sd_models.py | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/launch.py b/launch.py index 68e08114de5..94bba5ca0ff 100644 --- a/launch.py +++ b/launch.py @@ -238,12 +238,14 @@ def prepare_environment(): k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git') + tomesd_repo = os.environ.get('TOMESD_REPO', 'https://github.com/dbolya/tomesd.git') stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf") taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6") k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "5b3af030dd83e0297272d861c19477735d0317ec") codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") + tomesd_commit_hash = os.environ.get('TOMESD_COMMIT_HASH', "4f936c257e10848e0399fc6d0484a1761812092a") if not args.skip_python_version_check: check_python_version() @@ -280,6 +282,10 @@ def prepare_environment(): elif platform.system() == "Linux": run_pip(f"install {xformers_package}", "xformers") + if (not is_installed("tomesd") or args.reinstall_tomesd) and args.token_merging: + git_clone(tomesd_repo, repo_dir('tomesd'), "tomesd", tomesd_commit_hash) + run_pip(f"install {repo_dir('tomesd')}") + if not is_installed("pyngrok") and args.ngrok: run_pip("install pyngrok", "ngrok") diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 81c0b82a307..4314f97b112 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -101,3 +101,7 @@ parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) + +# token merging / tomesd +parser.add_argument("--token-merging", action='store_true', help="Provides generation speedup by merging redundant tokens. (compatible with --xformers)", default=False) +parser.add_argument("--token-merging-ratio", type=float, help="Adjusts ratio of merged to untouched tokens. Range: (0.0-1.0], Defaults to 0.5", default=0.5) diff --git a/modules/sd_models.py b/modules/sd_models.py index 6ea874dfcdc..0b74aa0f066 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -9,6 +9,7 @@ from os import mkdir from urllib import request import ldm.modules.midas as midas +import tomesd from ldm.util import instantiate_from_config @@ -430,6 +431,13 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, time_taken_ try: with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): sd_model = instantiate_from_config(sd_config.model) + + if shared.cmd_opts.token_merging: + ratio = shared.cmd_opts.token_merging_ratio + + tomesd.apply_patch(sd_model, ratio=ratio) + print(f"Model accelerated using {(ratio * 100)}% token merging via tomesd.") + timer.record("token merging") except Exception as e: pass From ef8c0440512f2fae9ceeb977b73f881c0afbb90a Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 03:21:23 -0500 Subject: [PATCH 0045/2418] forgot to add reinstall arg back earlier since args moved out of shared --- modules/cmd_args.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 4314f97b112..80df946582e 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -105,3 +105,4 @@ # token merging / tomesd parser.add_argument("--token-merging", action='store_true', help="Provides generation speedup by merging redundant tokens. (compatible with --xformers)", default=False) parser.add_argument("--token-merging-ratio", type=float, help="Adjusts ratio of merged to untouched tokens. Range: (0.0-1.0], Defaults to 0.5", default=0.5) +parser.add_argument("--reinstall-tomesd", action='store_true', help="Reinstalls tomesd", default=False) From 26ab018253cb078630fcdde47dbaee85f2466145 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 03:31:22 -0500 Subject: [PATCH 0046/2418] delay import --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 0b74aa0f066..2c05ec17b57 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -9,7 +9,6 @@ from os import mkdir from urllib import request import ldm.modules.midas as midas -import tomesd from ldm.util import instantiate_from_config @@ -433,6 +432,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, time_taken_ sd_model = instantiate_from_config(sd_config.model) if shared.cmd_opts.token_merging: + import tomesd ratio = shared.cmd_opts.token_merging_ratio tomesd.apply_patch(sd_model, ratio=ratio) From 8c88bf40060c86ba508646c6d7ddc21e389be846 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 14:12:12 -0500 Subject: [PATCH 0047/2418] use pypi package for tomesd intead of manually cloning repo --- launch.py | 7 ++----- modules/cmd_args.py | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/launch.py b/launch.py index 94bba5ca0ff..846c4c20058 100644 --- a/launch.py +++ b/launch.py @@ -238,14 +238,12 @@ def prepare_environment(): k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git') - tomesd_repo = os.environ.get('TOMESD_REPO', 'https://github.com/dbolya/tomesd.git') stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf") taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6") k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "5b3af030dd83e0297272d861c19477735d0317ec") codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") - tomesd_commit_hash = os.environ.get('TOMESD_COMMIT_HASH', "4f936c257e10848e0399fc6d0484a1761812092a") if not args.skip_python_version_check: check_python_version() @@ -282,9 +280,8 @@ def prepare_environment(): elif platform.system() == "Linux": run_pip(f"install {xformers_package}", "xformers") - if (not is_installed("tomesd") or args.reinstall_tomesd) and args.token_merging: - git_clone(tomesd_repo, repo_dir('tomesd'), "tomesd", tomesd_commit_hash) - run_pip(f"install {repo_dir('tomesd')}") + if not is_installed("tomesd") and args.token_merging: + run_pip(f"install tomesd") if not is_installed("pyngrok") and args.ngrok: run_pip("install pyngrok", "ngrok") diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 80df946582e..4314f97b112 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -105,4 +105,3 @@ # token merging / tomesd parser.add_argument("--token-merging", action='store_true', help="Provides generation speedup by merging redundant tokens. (compatible with --xformers)", default=False) parser.add_argument("--token-merging-ratio", type=float, help="Adjusts ratio of merged to untouched tokens. Range: (0.0-1.0], Defaults to 0.5", default=0.5) -parser.add_argument("--reinstall-tomesd", action='store_true', help="Reinstalls tomesd", default=False) From a609bd56b4206460d1df3c3022025fc78b66718f Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 22:18:35 -0500 Subject: [PATCH 0048/2418] Transition to using settings through UI instead of cmd line args. Added feature to only apply to hr-fix. Install package using requirements_versions.txt --- launch.py | 3 --- modules/processing.py | 35 +++++++++++++++++++++++++++++++ modules/sd_models.py | 7 ------- modules/shared.py | 44 +++++++++++++++++++++++++++++++++++++++ requirements_versions.txt | 1 + 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/launch.py b/launch.py index 846c4c20058..68e08114de5 100644 --- a/launch.py +++ b/launch.py @@ -280,9 +280,6 @@ def prepare_environment(): elif platform.system() == "Linux": run_pip(f"install {xformers_package}", "xformers") - if not is_installed("tomesd") and args.token_merging: - run_pip(f"install tomesd") - if not is_installed("pyngrok") and args.ngrok: run_pip("install pyngrok", "ngrok") diff --git a/modules/processing.py b/modules/processing.py index 6d9c6a8de29..e115aadd135 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -29,6 +29,7 @@ from einops import repeat, rearrange from blendmodes.blend import blendLayers, BlendType +import tomesd # some of those options should not be changed at all because they would break the model, so I removed them from options. opt_C = 4 @@ -500,9 +501,28 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() + if opts.token_merging: + + if p.hr_second_pass_steps < 1 and not opts.token_merging_hr_only: + tomesd.apply_patch( + p.sd_model, + ratio=opts.token_merging_ratio, + max_downsample=opts.token_merging_maximum_down_sampling, + sx=opts.token_merging_stride_x, + sy=opts.token_merging_stride_y, + use_rand=opts.token_merging_random, + merge_attn=opts.token_merging_merge_attention, + merge_crossattn=opts.token_merging_merge_cross_attention, + merge_mlp=opts.token_merging_merge_mlp + ) + res = process_images_inner(p) finally: + # undo model optimizations made by tomesd + if opts.token_merging: + tomesd.remove_patch(p.sd_model) + # restore opts to original state if p.override_settings_restore_afterwards: for k, v in stored_opts.items(): @@ -938,6 +958,21 @@ def save_intermediate(image, index): x = None devices.torch_gc() + # apply token merging optimizations from tomesd for high-res pass + # check if hr_only so we don't redundantly apply patch + if opts.token_merging and opts.token_merging_hr_only: + tomesd.apply_patch( + self.sd_model, + ratio=opts.token_merging_ratio, + max_downsample=opts.token_merging_maximum_down_sampling, + sx=opts.token_merging_stride_x, + sy=opts.token_merging_stride_y, + use_rand=opts.token_merging_random, + merge_attn=opts.token_merging_merge_attention, + merge_crossattn=opts.token_merging_merge_cross_attention, + merge_mlp=opts.token_merging_merge_mlp + ) + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) return samples diff --git a/modules/sd_models.py b/modules/sd_models.py index 2c05ec17b57..87c49b83c36 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -431,13 +431,6 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, time_taken_ with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): sd_model = instantiate_from_config(sd_config.model) - if shared.cmd_opts.token_merging: - import tomesd - ratio = shared.cmd_opts.token_merging_ratio - - tomesd.apply_patch(sd_model, ratio=ratio) - print(f"Model accelerated using {(ratio * 100)}% token merging via tomesd.") - timer.record("token merging") except Exception as e: pass diff --git a/modules/shared.py b/modules/shared.py index 5fd0eecbd1f..d7379e24fef 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -427,6 +427,50 @@ def list_samplers(): "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), })) +options_templates.update(options_section(('token_merging', 'Token Merging'), { + "token_merging": OptionInfo( + False, "Enable redundant token merging via tomesd. (currently incompatible with controlnet extension)", + gr.Checkbox + ), + "token_merging_ratio": OptionInfo( + 0.5, "Merging Ratio", + gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} + ), + "token_merging_hr_only": OptionInfo( + True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions.", + gr.Checkbox + ), + # More advanced/niche settings: + "token_merging_random": OptionInfo( + True, "Use random perturbations - Disabling might help with certain samplers", + gr.Checkbox + ), + "token_merging_merge_attention": OptionInfo( + True, "Merge attention", + gr.Checkbox + ), + "token_merging_merge_cross_attention": OptionInfo( + False, "Merge cross attention", + gr.Checkbox + ), + "token_merging_merge_mlp": OptionInfo( + False, "Merge mlp", + gr.Checkbox + ), + "token_merging_maximum_down_sampling": OptionInfo( + 1, "Maximum down sampling", + gr.Dropdown, lambda: {"choices": ["1", "2", "4", "8"]} + ), + "token_merging_stride_x": OptionInfo( + 2, "Stride - X", + gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} + ), + "token_merging_stride_y": OptionInfo( + 2, "Stride - Y", + gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} + ) +})) + options_templates.update() diff --git a/requirements_versions.txt b/requirements_versions.txt index df65431a3a5..045230ab93b 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -28,3 +28,4 @@ torchsde==0.2.5 safetensors==0.3.0 httpcore<=0.15 fastapi==0.94.0 +tomesd>=0.1 \ No newline at end of file From c707b7df95a61b66a05be94e805e1be9a432e294 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 23:47:10 -0500 Subject: [PATCH 0049/2418] remove excess condition --- modules/processing.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index e115aadd135..55735572587 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -501,26 +501,26 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - if opts.token_merging: - - if p.hr_second_pass_steps < 1 and not opts.token_merging_hr_only: - tomesd.apply_patch( - p.sd_model, - ratio=opts.token_merging_ratio, - max_downsample=opts.token_merging_maximum_down_sampling, - sx=opts.token_merging_stride_x, - sy=opts.token_merging_stride_y, - use_rand=opts.token_merging_random, - merge_attn=opts.token_merging_merge_attention, - merge_crossattn=opts.token_merging_merge_cross_attention, - merge_mlp=opts.token_merging_merge_mlp - ) + if opts.token_merging and not opts.token_merging_hr_only: + print("applying token merging to all passes") + tomesd.apply_patch( + p.sd_model, + ratio=opts.token_merging_ratio, + max_downsample=opts.token_merging_maximum_down_sampling, + sx=opts.token_merging_stride_x, + sy=opts.token_merging_stride_y, + use_rand=opts.token_merging_random, + merge_attn=opts.token_merging_merge_attention, + merge_crossattn=opts.token_merging_merge_cross_attention, + merge_mlp=opts.token_merging_merge_mlp + ) res = process_images_inner(p) finally: # undo model optimizations made by tomesd if opts.token_merging: + print('removing token merging model optimizations') tomesd.remove_patch(p.sd_model) # restore opts to original state @@ -961,6 +961,7 @@ def save_intermediate(image, index): # apply token merging optimizations from tomesd for high-res pass # check if hr_only so we don't redundantly apply patch if opts.token_merging and opts.token_merging_hr_only: + print("applying token merging for high-res pass") tomesd.apply_patch( self.sd_model, ratio=opts.token_merging_ratio, From 7201d940a4fe664beb9662fadbeade4ee1d788f7 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:27:48 -0500 Subject: [PATCH 0050/2418] Improve frontend responsiveness for some buttons --- javascript/ui.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ modules/ui.py | 10 ++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index 4a440193b81..5311e7bc3ce 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -361,3 +361,51 @@ function selectCheckpoint(name){ desiredCheckpointName = name; gradioApp().getElementById('change_checkpoint').click() } + +function setRandomSeed(target_interface) { + let seed = gradioApp().querySelector(`#${target_interface}_seed input`); + if (!seed) { + return []; + } + seed.value = "-1"; + seed.dispatchEvent(new Event("input")); + return []; +} + +function setRandomSubseed(target_interface) { + let subseed = gradioApp().querySelector(`#${target_interface}_subseed input`); + if (!subseed) { + return []; + } + subseed.value = "-1"; + subseed.dispatchEvent(new Event("input")); + return []; +} + +function switchWidthHeightTxt2Img() { + let width = gradioApp().querySelector("#txt2img_width input[type=number]"); + let height = gradioApp().querySelector("#txt2img_height input[type=number]"); + if (!width || !height) { + return []; + } + let tmp = width.value; + width.value = height.value; + height.value = tmp; + width.dispatchEvent(new Event("input")); + height.dispatchEvent(new Event("input")); + return []; +} + +function switchWidthHeightImg2Img() { + let width = gradioApp().querySelector("#img2img_width input[type=number]"); + let height = gradioApp().querySelector("#img2img_height input[type=number]"); + if (!width || !height) { + return []; + } + let tmp = width.value; + width.value = height.value; + height.value = tmp; + width.dispatchEvent(new Event("input")); + height.dispatchEvent(new Event("input")); + return []; +} diff --git a/modules/ui.py b/modules/ui.py index 627fbe0b5f3..5c693b7a71c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -192,8 +192,9 @@ def create_seed_inputs(target_interface): seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=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=target_interface + '_seed_resize_from_h') - random_seed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[seed]) - random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) + target_interface_state = gr.Textbox(target_interface, visible=False) + random_seed.click(fn=None, _js="setRandomSeed", show_progress=False, inputs=[target_interface_state], outputs=[]) + random_subseed.click(fn=None, _js="setRandomSubseed", show_progress=False, inputs=[target_interface_state], outputs=[]) def change_visibility(show): return {comp: gr_show(show) for comp in seed_extras} @@ -576,7 +577,7 @@ def create_ui(): txt2img_prompt.submit(**txt2img_args) submit.click(**txt2img_args) - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) + res_switch_btn.click(fn=None, _js="switchWidthHeightTxt2Img", inputs=None, outputs=None, show_progress=False) txt_prompt_img.change( fn=modules.images.image_data, @@ -896,7 +897,8 @@ def select_img2img_tab(tab): img2img_prompt.submit(**img2img_args) submit.click(**img2img_args) - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) + + res_switch_btn.click(fn=None, _js="switchWidthHeightImg2Img", inputs=None, outputs=None, show_progress=False) img2img_interrogate.click( fn=lambda *args: process_interrogate(interrogate, *args), From 5c8e53d5e98da0eabf384318955c57842d612c07 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Tue, 4 Apr 2023 02:26:44 -0500 Subject: [PATCH 0051/2418] Allow different merge ratios to be used for each pass. Make toggle cmd flag work again. Remove ratio flag. Remove warning about controlnet being incompatible --- modules/cmd_args.py | 3 +-- modules/processing.py | 44 +++++++++++++++---------------------------- modules/sd_models.py | 29 +++++++++++++++++++++++++++- modules/shared.py | 6 +++++- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 4314f97b112..8e5a7fabfa4 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -103,5 +103,4 @@ parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) # token merging / tomesd -parser.add_argument("--token-merging", action='store_true', help="Provides generation speedup by merging redundant tokens. (compatible with --xformers)", default=False) -parser.add_argument("--token-merging-ratio", type=float, help="Adjusts ratio of merged to untouched tokens. Range: (0.0-1.0], Defaults to 0.5", default=0.5) +parser.add_argument("--token-merging", action='store_true', help="Provides speed and memory improvements by merging redundant tokens. This has a more pronounced effect on higher resolutions.", default=False) diff --git a/modules/processing.py b/modules/processing.py index 55735572587..670a7a28a80 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -501,26 +501,16 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - if opts.token_merging and not opts.token_merging_hr_only: - print("applying token merging to all passes") - tomesd.apply_patch( - p.sd_model, - ratio=opts.token_merging_ratio, - max_downsample=opts.token_merging_maximum_down_sampling, - sx=opts.token_merging_stride_x, - sy=opts.token_merging_stride_y, - use_rand=opts.token_merging_random, - merge_attn=opts.token_merging_merge_attention, - merge_crossattn=opts.token_merging_merge_cross_attention, - merge_mlp=opts.token_merging_merge_mlp - ) + if (opts.token_merging or cmd_opts.token_merging) and not opts.token_merging_hr_only: + print("\nApplying token merging\n") + sd_models.apply_token_merging(sd_model=p.sd_model, hr=False) res = process_images_inner(p) finally: # undo model optimizations made by tomesd - if opts.token_merging: - print('removing token merging model optimizations') + if opts.token_merging or cmd_opts.token_merging: + print('\nRemoving token merging model optimizations\n') tomesd.remove_patch(p.sd_model) # restore opts to original state @@ -959,20 +949,16 @@ def save_intermediate(image, index): devices.torch_gc() # apply token merging optimizations from tomesd for high-res pass - # check if hr_only so we don't redundantly apply patch - if opts.token_merging and opts.token_merging_hr_only: - print("applying token merging for high-res pass") - tomesd.apply_patch( - self.sd_model, - ratio=opts.token_merging_ratio, - max_downsample=opts.token_merging_maximum_down_sampling, - sx=opts.token_merging_stride_x, - sy=opts.token_merging_stride_y, - use_rand=opts.token_merging_random, - merge_attn=opts.token_merging_merge_attention, - merge_crossattn=opts.token_merging_merge_cross_attention, - merge_mlp=opts.token_merging_merge_mlp - ) + # check if hr_only so we are not redundantly patching + if (cmd_opts.token_merging or opts.token_merging) and (opts.token_merging_hr_only or opts.token_merging_ratio_hr != opts.token_merging_ratio): + # case where user wants to use separate merge ratios + if not opts.token_merging_hr_only: + # clean patch done by first pass. (clobbering the first patch might be fine? this might be excessive) + print('Temporarily reverting token merging optimizations in preparation for next pass') + tomesd.remove_patch(self.sd_model) + + print("\nApplying token merging for high-res pass\n") + sd_models.apply_token_merging(sd_model=self.sd_model, hr=True) samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) diff --git a/modules/sd_models.py b/modules/sd_models.py index 87c49b83c36..696a2333daf 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -16,6 +16,7 @@ from modules.paths import models_path from modules.sd_hijack_inpainting import do_inpainting_hijack from modules.timer import Timer +import tomesd model_dir = "Stable-diffusion" model_path = os.path.abspath(os.path.join(paths.models_path, model_dir)) @@ -545,4 +546,30 @@ def unload_model_weights(sd_model=None, info=None): print(f"Unloaded weights {timer.summary()}.") - return sd_model \ No newline at end of file + return sd_model + + +def apply_token_merging(sd_model, hr: bool): + """ + Applies speed and memory optimizations from tomesd. + + Args: + hr (bool): True if called in the context of a high-res pass + """ + + ratio = shared.opts.token_merging_ratio + if hr: + ratio = shared.opts.token_merging_ratio_hr + print("effective hr pass merge ratio is "+str(ratio)) + + tomesd.apply_patch( + sd_model, + ratio=ratio, + max_downsample=shared.opts.token_merging_maximum_down_sampling, + sx=shared.opts.token_merging_stride_x, + sy=shared.opts.token_merging_stride_y, + use_rand=shared.opts.token_merging_random, + merge_attn=shared.opts.token_merging_merge_attention, + merge_crossattn=shared.opts.token_merging_merge_cross_attention, + merge_mlp=shared.opts.token_merging_merge_mlp + ) diff --git a/modules/shared.py b/modules/shared.py index d7379e24fef..c7572e9801a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -429,7 +429,7 @@ def list_samplers(): options_templates.update(options_section(('token_merging', 'Token Merging'), { "token_merging": OptionInfo( - False, "Enable redundant token merging via tomesd. (currently incompatible with controlnet extension)", + 0.5, "Enable redundant token merging via tomesd. This can provide significant speed and memory improvements.", gr.Checkbox ), "token_merging_ratio": OptionInfo( @@ -440,6 +440,10 @@ def list_samplers(): True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions.", gr.Checkbox ), + "token_merging_ratio_hr": OptionInfo( + 0.5, "Merging Ratio (high-res pass) - If 'Apply only to high-res' is enabled, this will always be the ratio used.", + gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} + ), # More advanced/niche settings: "token_merging_random": OptionInfo( True, "Use random perturbations - Disabling might help with certain samplers", From ab195ab0da78aa9aa6304948ae2c76e45dab04eb Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Tue, 4 Apr 2023 02:31:57 -0500 Subject: [PATCH 0052/2418] bump tomesd package version --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 045230ab93b..03522715220 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -28,4 +28,4 @@ torchsde==0.2.5 safetensors==0.3.0 httpcore<=0.15 fastapi==0.94.0 -tomesd>=0.1 \ No newline at end of file +tomesd>=0.1.1 \ No newline at end of file From cf5a5773bfd1ca6a3c35232881661ec1ff4b67d7 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Tue, 4 Apr 2023 02:39:13 -0500 Subject: [PATCH 0053/2418] :p --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index c7572e9801a..568acdc4c4f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -429,7 +429,7 @@ def list_samplers(): options_templates.update(options_section(('token_merging', 'Token Merging'), { "token_merging": OptionInfo( - 0.5, "Enable redundant token merging via tomesd. This can provide significant speed and memory improvements.", + False, "Enable redundant token merging via tomesd. This can provide significant speed and memory improvements.", gr.Checkbox ), "token_merging_ratio": OptionInfo( From 1c1106260300ca3956d9619875e28278b148adab Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Mon, 10 Apr 2023 03:37:15 -0500 Subject: [PATCH 0054/2418] add token merging options to infotext when necessary. Bump tomesd version --- modules/generation_parameters_copypaste.py | 37 ++++++++++++++++++++++ modules/processing.py | 22 ++++++++++--- modules/shared.py | 2 +- requirements_versions.txt | 2 +- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 6df76858f51..a7ede534419 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -282,6 +282,32 @@ def parse_generation_parameters(x: str): res["Hires resize-1"] = 0 res["Hires resize-2"] = 0 + # Infer additional override settings for token merging + print("inferring settings for tomesd") + token_merging_ratio = res.get("Token merging ratio", None) + token_merging_ratio_hr = res.get("Token merging ratio hr", None) + + if token_merging_ratio is not None or token_merging_ratio_hr is not None: + res["Token merging"] = 'True' + + if token_merging_ratio is None: + res["Token merging hr only"] = 'True' + else: + res["Token merging hr only"] = 'False' + + if res.get("Token merging random", None) is None: + res["Token merging random"] = 'False' + if res.get("Token merging merge attention", None) is None: + res["Token merging merge attention"] = 'True' + if res.get("Token merging merge cross attention", None) is None: + res["Token merging merge cross attention"] = 'False' + if res.get("Token merging merge mlp", None) is None: + res["Token merging merge mlp"] = 'False' + if res.get("Token merging stride x", None) is None: + res["Token merging stride x"] = '2' + if res.get("Token merging stride y", None) is None: + res["Token merging stride y"] = '2' + restore_old_hires_fix_params(res) return res @@ -304,6 +330,17 @@ def parse_generation_parameters(x: str): ('UniPC skip type', 'uni_pc_skip_type'), ('UniPC order', 'uni_pc_order'), ('UniPC lower order final', 'uni_pc_lower_order_final'), + ('Token merging', 'token_merging'), + ('Token merging ratio', 'token_merging_ratio'), + ('Token merging hr only', 'token_merging_hr_only'), + ('Token merging ratio hr', 'token_merging_ratio_hr'), + ('Token merging random', 'token_merging_random'), + ('Token merging merge attention', 'token_merging_merge_attention'), + ('Token merging merge cross attention', 'token_merging_merge_cross_attention'), + ('Token merging merge mlp', 'token_merging_merge_mlp'), + ('Token merging maximum downsampling', 'token_merging_maximum_downsampling'), + ('Token merging stride x', 'token_merging_stride_x'), + ('Token merging stride y', 'token_merging_stride_y') ] diff --git a/modules/processing.py b/modules/processing.py index 670a7a28a80..95058d0b7ed 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -31,6 +31,12 @@ from blendmodes.blend import blendLayers, BlendType import tomesd +# add a logger for the processing module +logger = logging.getLogger(__name__) +# manually set output level here since there is no option to do so yet through launch options +# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') + + # some of those options should not be changed at all because they would break the model, so I removed them from options. opt_C = 4 opt_f = 8 @@ -477,6 +483,14 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, + "Token merging ratio": None if not (opts.token_merging or cmd_opts.token_merging) or opts.token_merging_hr_only else opts.token_merging_ratio, + "Token merging ratio hr": None if not (opts.token_merging or cmd_opts.token_merging) else opts.token_merging_ratio_hr, + "Token merging random": None if opts.token_merging_random is False else opts.token_merging_random, + "Token merging merge attention": None if opts.token_merging_merge_attention is True else opts.token_merging_merge_attention, + "Token merging merge cross attention": None if opts.token_merging_merge_cross_attention is False else opts.token_merging_merge_cross_attention, + "Token merging merge mlp": None if opts.token_merging_merge_mlp is False else opts.token_merging_merge_mlp, + "Token merging stride x": None if opts.token_merging_stride_x == 2 else opts.token_merging_stride_x, + "Token merging stride y": None if opts.token_merging_stride_y == 2 else opts.token_merging_stride_y } generation_params.update(p.extra_generation_params) @@ -502,16 +516,16 @@ def process_images(p: StableDiffusionProcessing) -> Processed: sd_vae.reload_vae_weights() if (opts.token_merging or cmd_opts.token_merging) and not opts.token_merging_hr_only: - print("\nApplying token merging\n") sd_models.apply_token_merging(sd_model=p.sd_model, hr=False) + logger.debug('Token merging applied') res = process_images_inner(p) finally: # undo model optimizations made by tomesd if opts.token_merging or cmd_opts.token_merging: - print('\nRemoving token merging model optimizations\n') tomesd.remove_patch(p.sd_model) + logger.debug('Token merging model optimizations removed') # restore opts to original state if p.override_settings_restore_afterwards: @@ -954,11 +968,11 @@ def save_intermediate(image, index): # case where user wants to use separate merge ratios if not opts.token_merging_hr_only: # clean patch done by first pass. (clobbering the first patch might be fine? this might be excessive) - print('Temporarily reverting token merging optimizations in preparation for next pass') tomesd.remove_patch(self.sd_model) + logger.debug('Temporarily removed token merging optimizations in preparation for next pass') - print("\nApplying token merging for high-res pass\n") sd_models.apply_token_merging(sd_model=self.sd_model, hr=True) + logger.debug('Applied token merging for high-res pass') samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) diff --git a/modules/shared.py b/modules/shared.py index 568acdc4c4f..d9db7317a8b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -446,7 +446,7 @@ def list_samplers(): ), # More advanced/niche settings: "token_merging_random": OptionInfo( - True, "Use random perturbations - Disabling might help with certain samplers", + False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visaul artifacting.", gr.Checkbox ), "token_merging_merge_attention": OptionInfo( diff --git a/requirements_versions.txt b/requirements_versions.txt index 03522715220..f972f975ffe 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -28,4 +28,4 @@ torchsde==0.2.5 safetensors==0.3.0 httpcore<=0.15 fastapi==0.94.0 -tomesd>=0.1.1 \ No newline at end of file +tomesd>=0.1.2 \ No newline at end of file From c510cfd24b995f8267df3391cc961ac398922ba4 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Mon, 10 Apr 2023 03:43:56 -0500 Subject: [PATCH 0055/2418] Update shared.py fix typo --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index d9db7317a8b..edf11e43568 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -446,7 +446,7 @@ def list_samplers(): ), # More advanced/niche settings: "token_merging_random": OptionInfo( - False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visaul artifacting.", + False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting.", gr.Checkbox ), "token_merging_merge_attention": OptionInfo( From a9902ca33119d6fae0a3623424bfc7ab86f2095a Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:03:01 -0500 Subject: [PATCH 0056/2418] Update generation_parameters_copypaste.py --- modules/generation_parameters_copypaste.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index a7ede534419..ba2ca5ed7b1 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -283,7 +283,6 @@ def parse_generation_parameters(x: str): res["Hires resize-2"] = 0 # Infer additional override settings for token merging - print("inferring settings for tomesd") token_merging_ratio = res.get("Token merging ratio", None) token_merging_ratio_hr = res.get("Token merging ratio hr", None) From dff60e2e74964a8b02b75ecd8cf8007ef67a9712 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:10:50 -0500 Subject: [PATCH 0057/2418] Update sd_models.py --- modules/sd_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 696a2333daf..efcf730d673 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -560,7 +560,6 @@ def apply_token_merging(sd_model, hr: bool): ratio = shared.opts.token_merging_ratio if hr: ratio = shared.opts.token_merging_ratio_hr - print("effective hr pass merge ratio is "+str(ratio)) tomesd.apply_patch( sd_model, From 57a3d146e3e193904c1c5e148f7244b9d045f157 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Fri, 14 Apr 2023 01:23:54 +0800 Subject: [PATCH 0058/2418] Tooltip localization support --- javascript/hints.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index f48a0eb69c9..a836d755cd8 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -117,16 +117,16 @@ titles = { onUiUpdate(function(){ gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){ - tooltip = titles[span.textContent]; + tooltip = localization[titles[span.textContent]] || titles[span.textContent]; if(!tooltip){ - tooltip = titles[span.value]; + tooltip = localization[titles[span.value]] || titles[span.value]; } if(!tooltip){ for (const c of span.classList) { if (c in titles) { - tooltip = titles[c]; + tooltip = localization[titles[c]] || titles[c]; break; } } @@ -141,7 +141,7 @@ onUiUpdate(function(){ if (select.onchange != null) return; select.onchange = function(){ - select.title = titles[select.value] || ""; + select.title = localization[titles[select.value]] || titles[select.value] || ""; } }) }) From 2c24e09dfc431ef7617134a807bf741f0b0c9fa3 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:53:04 +0800 Subject: [PATCH 0059/2418] Fix gallery not being refreshed correctly --- modules/txt2img.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/txt2img.py b/modules/txt2img.py index 16841d0f2f3..6ebca656a9a 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -7,6 +7,7 @@ import modules.shared as shared import modules.processing as processing from modules.ui import plaintext_to_html +from time import time def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, override_settings_texts, *args): @@ -63,6 +64,9 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step if opts.samples_log_stdout: print(generation_info_js) + for img in processed.images: + img.already_saved_as += f'?{int(time())}' + if opts.do_not_show_images: processed.images = [] From 988dd02632bf38e64bc0cf394ece2a5f7a64e15b Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 20 Apr 2023 15:08:31 +0800 Subject: [PATCH 0060/2418] Add img2img refreshed correctly --- modules/img2img.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/img2img.py b/modules/img2img.py index 953ac5d2d12..6da974e4519 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -15,6 +15,7 @@ from modules.ui import plaintext_to_html import modules.images as images import modules.scripts +from time import time def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): @@ -179,6 +180,9 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if opts.samples_log_stdout: print(generation_info_js) + for img in processed.images: + img.already_saved_as += f'?{int(time())}' + if opts.do_not_show_images: processed.images = [] From eff00413ae76e01b9a24b13dee6a0dda98f643f2 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 20 Apr 2023 21:23:56 +0800 Subject: [PATCH 0061/2418] Refresh bug fix --- modules/img2img.py | 3 ++- modules/txt2img.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 6da974e4519..fea51667bfb 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -181,7 +181,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s print(generation_info_js) for img in processed.images: - img.already_saved_as += f'?{int(time())}' + if hasattr(img, 'already_saved_as'): + img.already_saved_as += f'?{int(time())}' if opts.do_not_show_images: processed.images = [] diff --git a/modules/txt2img.py b/modules/txt2img.py index 6ebca656a9a..57278bdc320 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -65,7 +65,8 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step print(generation_info_js) for img in processed.images: - img.already_saved_as += f'?{int(time())}' + if hasattr(img, 'already_saved_as'): + img.already_saved_as += f'?{int(time())}' if opts.do_not_show_images: processed.images = [] From b2f6e0704e178f64881f507f3a48ff36e63e1a62 Mon Sep 17 00:00:00 2001 From: catalpaaa Date: Tue, 25 Apr 2023 07:27:24 -0700 Subject: [PATCH 0062/2418] add subpath support --- modules/cmd_args.py | 1 + webui.py | 5 +++++ webui.sh | 0 3 files changed, 6 insertions(+) mode change 100755 => 100644 webui.sh diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 81c0b82a307..bdf106bfa69 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -101,3 +101,4 @@ parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) +parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') \ No newline at end of file diff --git a/webui.py b/webui.py index b570895fb2c..d8997819630 100644 --- a/webui.py +++ b/webui.py @@ -290,6 +290,11 @@ def webui(): print(f"Startup time: {startup_timer.summary()}.") + if cmd_opts.subpath: + redirector = FastAPI() + redirector.get("/") + mounted_app = gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}") + wait_on_server(shared.demo) print('Restarting UI...') diff --git a/webui.sh b/webui.sh old mode 100755 new mode 100644 From ecdc6471e7d694ef9ecec96e8c3128237efe069a Mon Sep 17 00:00:00 2001 From: catalpaaa Date: Fri, 28 Apr 2023 12:23:53 -0700 Subject: [PATCH 0063/2418] bump gradio to 3.28 --- requirements.txt | 2 +- requirements_versions.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c72b2927ec1..213375224c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ basicsr fonts font-roboto gfpgan -gradio==3.23 +gradio==3.28 invisible-watermark numpy omegaconf diff --git a/requirements_versions.txt b/requirements_versions.txt index df65431a3a5..0fe018015a3 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.12.0 basicsr==1.4.2 gfpgan==1.3.8 -gradio==3.23 +gradio==3.28 numpy==1.23.3 Pillow==9.4.0 realesrgan==0.3.0 From 5e4a0e3d2413e49ad57972e1fc4b54b3109e310c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 29 Apr 2023 23:02:23 +0300 Subject: [PATCH 0064/2418] attempt to fix broken github CI --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index e043e156ca2..1dc12daeed2 100644 --- a/launch.py +++ b/launch.py @@ -222,7 +222,7 @@ def run_extensions_installers(settings_file): def prepare_environment(): - torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.0 torchvision==0.15.1 --index-url https://download.pytorch.org/whl/cu118") + torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.0 torchvision==0.15.1 --extra-index-url https://download.pytorch.org/whl/cu118") requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') From 13d8d65ef98c1f1f52fa6e60f21025319556a6ae Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 14:40:45 +0300 Subject: [PATCH 0065/2418] hints: don't process elements that already have a title --- javascript/hints.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/javascript/hints.js b/javascript/hints.js index e7d17d36a0e..8d1967a7658 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -118,7 +118,9 @@ titles = { onUiUpdate(function(){ gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){ - tooltip = titles[span.textContent]; + if (span.title) return; // already has a title + + let tooltip = titles[span.textContent]; if(!tooltip){ tooltip = titles[span.value]; From ee973dcf1d1de44a248dc3a1b7043c9b8ebdc25a Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 14:42:11 +0300 Subject: [PATCH 0066/2418] imageMaskFix.js: fix event listeners to not use anonymous trampoline --- javascript/imageMaskFix.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js index 9fe7a60309c..6a82928e4e5 100644 --- a/javascript/imageMaskFix.js +++ b/javascript/imageMaskFix.js @@ -2,7 +2,6 @@ * temporary fix for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/668 * @see https://github.com/gradio-app/gradio/issues/1721 */ -window.addEventListener( 'resize', () => imageMaskResize()); function imageMaskResize() { const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas'); if ( ! canvases.length ) { @@ -15,7 +14,7 @@ function imageMaskResize() { const previewImage = wrapper.previousElementSibling; if ( ! previewImage.complete ) { - previewImage.addEventListener( 'load', () => imageMaskResize()); + previewImage.addEventListener( 'load', imageMaskResize); return; } @@ -40,6 +39,7 @@ function imageMaskResize() { c.style.maxHeight = '100%'; c.style.objectFit = 'contain'; }); - } - - onUiUpdate(() => imageMaskResize()); +} + +onUiUpdate(imageMaskResize); +window.addEventListener( 'resize', imageMaskResize); From 34a6ad80d5a3d6670a9e89089558e724d8c9f8bd Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 14:45:56 +0300 Subject: [PATCH 0067/2418] Use classList.toggle wherever possible --- javascript/hires_fix.js | 10 +++------- javascript/imageviewer.js | 6 +----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/javascript/hires_fix.js b/javascript/hires_fix.js index 0629475f894..ba175301968 100644 --- a/javascript/hires_fix.js +++ b/javascript/hires_fix.js @@ -1,13 +1,9 @@ -function setInactive(elem, inactive){ - if(inactive){ - elem.classList.add('inactive') - } else{ - elem.classList.remove('inactive') +function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y){ + function setInactive(elem, inactive){ + elem.classList.toggle('inactive', !!inactive) } -} -function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y){ hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale') hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x') hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y') diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 3deffa9bee1..f364a2a10a6 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -144,11 +144,7 @@ function setupImageForLightbox(e) { } function modalZoomSet(modalImage, enable) { - if (enable) { - modalImage.classList.add('modalImageFullscreen'); - } else { - modalImage.classList.remove('modalImageFullscreen'); - } + if(modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable); } function modalZoomToggle(event) { From 8ccc27127bd5abcba05f30f8a72fc37025b588ac Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 22:08:52 +0300 Subject: [PATCH 0068/2418] Fix a whole bunch of implicit globals --- javascript/aspectRatioOverlay.js | 32 ++++++++++++++++---------------- javascript/contextMenus.js | 5 ++--- javascript/edit-attention.js | 8 ++++---- javascript/extensions.js | 2 +- javascript/extraNetworks.js | 14 +++++++------- javascript/generationParams.js | 2 +- javascript/hires_fix.js | 6 +++--- javascript/imageMaskFix.js | 2 +- javascript/imageviewer.js | 6 +++--- javascript/localization.js | 10 +++++----- javascript/notification.js | 2 +- javascript/progressbar.js | 3 +-- javascript/ui.js | 30 +++++++++++++++--------------- 13 files changed, 60 insertions(+), 62 deletions(-) diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index a8278cca209..10ac81c7034 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -45,27 +45,27 @@ function dimensionChange(e, is_width, is_height){ var viewportOffset = targetElement.getBoundingClientRect(); - viewportscale = Math.min( targetElement.clientWidth/targetElement.naturalWidth, targetElement.clientHeight/targetElement.naturalHeight ) + var viewportscale = Math.min( targetElement.clientWidth/targetElement.naturalWidth, targetElement.clientHeight/targetElement.naturalHeight ) - scaledx = targetElement.naturalWidth*viewportscale - scaledy = targetElement.naturalHeight*viewportscale + var scaledx = targetElement.naturalWidth*viewportscale + var scaledy = targetElement.naturalHeight*viewportscale - cleintRectTop = (viewportOffset.top+window.scrollY) - cleintRectLeft = (viewportOffset.left+window.scrollX) - cleintRectCentreY = cleintRectTop + (targetElement.clientHeight/2) - cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth/2) + var cleintRectTop = (viewportOffset.top+window.scrollY) + var cleintRectLeft = (viewportOffset.left+window.scrollX) + var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight/2) + var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth/2) - viewRectTop = cleintRectCentreY-(scaledy/2) - viewRectLeft = cleintRectCentreX-(scaledx/2) - arRectWidth = scaledx - arRectHeight = scaledy + var viewRectTop = cleintRectCentreY-(scaledy/2) // TODO: unused? + var viewRectLeft = cleintRectCentreX-(scaledx/2) // TODO: unused? + var arRectWidth = scaledx + var arRectHeight = scaledy - arscale = Math.min( arRectWidth/currentWidth, arRectHeight/currentHeight ) - arscaledx = currentWidth*arscale - arscaledy = currentHeight*arscale + var arscale = Math.min( arRectWidth/currentWidth, arRectHeight/currentHeight ) + var arscaledx = currentWidth*arscale + var arscaledy = currentHeight*arscale - arRectTop = cleintRectCentreY-(arscaledy/2) - arRectLeft = cleintRectCentreX-(arscaledx/2) + var arRectTop = cleintRectCentreY-(arscaledy/2) + var arRectLeft = cleintRectCentreX-(arscaledx/2) arRectWidth = arscaledx arRectHeight = arscaledy diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index 9468c10722b..5107e524ca2 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -16,8 +16,7 @@ contextMenuInit = function(){ oldMenu.remove() } - let tabButton = uiCurrentTab - let baseStyle = window.getComputedStyle(tabButton) + let baseStyle = window.getComputedStyle(uiCurrentTab) const contextMenu = document.createElement('nav') contextMenu.id = "context-menu" @@ -63,7 +62,7 @@ contextMenuInit = function(){ function appendContextMenuOption(targetElementSelector,entryName,entryFunction){ - currentItems = menuSpecs.get(targetElementSelector) + var currentItems = menuSpecs.get(targetElementSelector) if(!currentItems){ currentItems = [] diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 588c7b773e8..d2c2f19050e 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -69,8 +69,8 @@ function keyupEditAttention(event){ event.preventDefault(); - closeCharacter = ')' - delta = opts.keyedit_precision_attention + var closeCharacter = ')' + var delta = opts.keyedit_precision_attention if (selectionStart > 0 && text[selectionStart - 1] == '<'){ closeCharacter = '>' @@ -91,8 +91,8 @@ function keyupEditAttention(event){ selectionEnd += 1; } - end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1; - weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end)); + var end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1; + var weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end)); if (isNaN(weight)) return; weight += isPlus ? delta : -delta; diff --git a/javascript/extensions.js b/javascript/extensions.js index 3c2f995aa35..872259afcf1 100644 --- a/javascript/extensions.js +++ b/javascript/extensions.js @@ -41,7 +41,7 @@ function install_extension_from_index(button, url){ button.disabled = "disabled" button.value = "Installing..." - textarea = gradioApp().querySelector('#extension_to_install textarea') + var textarea = gradioApp().querySelector('#extension_to_install textarea') textarea.value = url updateInput(textarea) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 25322138941..963d5b02501 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -11,10 +11,10 @@ function setupExtraNetworksForTab(tabname){ tabs.appendChild(refresh) search.addEventListener("input", function(evt){ - searchTerm = search.value.toLowerCase() + var searchTerm = search.value.toLowerCase() gradioApp().querySelectorAll('#'+tabname+'_extra_tabs div.card').forEach(function(elem){ - text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase() + var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase() elem.style.display = text.indexOf(searchTerm) == -1 ? "none" : "" }) }); @@ -96,9 +96,9 @@ function saveCardPreview(event, tabname, filename){ } function extraNetworksSearchButton(tabs_id, event){ - searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > div > textarea') - button = event.target - text = button.classList.contains("search-all") ? "" : button.textContent.trim() + var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > div > textarea') + var button = event.target + var text = button.classList.contains("search-all") ? "" : button.textContent.trim() searchTextarea.value = text updateInput(searchTextarea) @@ -133,7 +133,7 @@ function popup(contents){ } function extraNetworksShowMetadata(text){ - elem = document.createElement('pre') + var elem = document.createElement('pre') elem.classList.add('popup-metadata'); elem.textContent = text; @@ -165,7 +165,7 @@ function requestGet(url, data, handler, errorHandler){ } function extraNetworksRequestMetadata(event, extraPage, cardName){ - showError = function(){ extraNetworksShowMetadata("there was an error getting metadata"); } + var showError = function(){ extraNetworksShowMetadata("there was an error getting metadata"); } requestGet("./sd_extra_networks/metadata", {"page": extraPage, "item": cardName}, function(data){ if(data && data.metadata){ diff --git a/javascript/generationParams.js b/javascript/generationParams.js index 1266a266cf7..ef64ee2e536 100644 --- a/javascript/generationParams.js +++ b/javascript/generationParams.js @@ -23,7 +23,7 @@ let modalObserver = new MutationObserver(function(mutations) { }); function attachGalleryListeners(tab_name) { - gallery = gradioApp().querySelector('#'+tab_name+'_gallery') + var gallery = gradioApp().querySelector('#'+tab_name+'_gallery') gallery?.addEventListener('click', () => gradioApp().getElementById(tab_name+"_generation_info_button").click()); gallery?.addEventListener('keydown', (e) => { if (e.keyCode == 37 || e.keyCode == 39) // left or right arrow diff --git a/javascript/hires_fix.js b/javascript/hires_fix.js index ba175301968..48196be40f7 100644 --- a/javascript/hires_fix.js +++ b/javascript/hires_fix.js @@ -4,9 +4,9 @@ function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_ elem.classList.toggle('inactive', !!inactive) } - hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale') - hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x') - hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y') + var hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale') + var hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x') + var hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y') gradioApp().getElementById('txt2img_hires_fix_row2').style.display = opts.use_old_hires_fix_width_height ? "none" : "" diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js index 6a82928e4e5..bd0be627d9d 100644 --- a/javascript/imageMaskFix.js +++ b/javascript/imageMaskFix.js @@ -5,7 +5,7 @@ function imageMaskResize() { const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas'); if ( ! canvases.length ) { - canvases_fixed = false; + canvases_fixed = false; // TODO: this is unused..? window.removeEventListener( 'resize', imageMaskResize ); return; } diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index f364a2a10a6..32066ab8ffa 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -57,7 +57,7 @@ function modalImageSwitch(offset) { }) if (result != -1) { - nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)] + var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)] nextButton.click() const modalImage = gradioApp().getElementById("modalImage"); const modal = gradioApp().getElementById("lightboxModal"); @@ -148,7 +148,7 @@ function modalZoomSet(modalImage, enable) { } function modalZoomToggle(event) { - modalImage = gradioApp().getElementById("modalImage"); + var modalImage = gradioApp().getElementById("modalImage"); modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen')) event.stopPropagation() } @@ -175,7 +175,7 @@ function galleryImageHandler(e) { } onUiUpdate(function() { - fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img') + var fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img') if (fullImg_preview != null) { fullImg_preview.forEach(setupImageForLightbox); } diff --git a/javascript/localization.js b/javascript/localization.js index 1a5a1dbb699..e1ffa271d9a 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -35,11 +35,11 @@ function canBeTranslated(node, text){ if(! text) return false; if(! node.parentElement) return false; - parentType = node.parentElement.nodeName + var parentType = node.parentElement.nodeName if(parentType=='SCRIPT' || parentType=='STYLE' || parentType=='TEXTAREA') return false; if (parentType=='OPTION' || parentType=='SPAN'){ - pnode = node + var pnode = node for(var level=0; level<4; level++){ pnode = pnode.parentElement if(! pnode) break; @@ -69,7 +69,7 @@ function getTranslation(text){ } function processTextNode(node){ - text = node.textContent.trim() + var text = node.textContent.trim() if(! canBeTranslated(node, text)) return @@ -105,7 +105,7 @@ function processNode(node){ } function dumpTranslations(){ - dumped = {} + var dumped = {} if (localization.rtl) { dumped.rtl = true } @@ -151,7 +151,7 @@ document.addEventListener("DOMContentLoaded", function() { }) function download_localization() { - text = JSON.stringify(dumpTranslations(), null, 4) + var text = JSON.stringify(dumpTranslations(), null, 4) var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); diff --git a/javascript/notification.js b/javascript/notification.js index 8ddd4c5d919..a40de7c30b0 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -2,7 +2,7 @@ let lastHeadImg = null; -notificationButton = null +let notificationButton = null; onUiUpdate(function(){ if(notificationButton == null){ diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 23bbf298453..7b853a40f6e 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -10,7 +10,6 @@ function getGallerySelectedIndex(id_gallery){ function request(url, data, handler, errorHandler){ var xhr = new XMLHttpRequest(); - var url = url; xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function () { @@ -107,7 +106,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre divProgress.style.width = rect.width + "px"; } - progressText = "" + let progressText = "" divInner.style.width = ((res.progress || 0) * 100.0) + '%' divInner.style.background = res.progress ? "" : "transparent" diff --git a/javascript/ui.js b/javascript/ui.js index e14b33f5be5..fed96f987b4 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -1,7 +1,7 @@ // various functions for interaction with ui.py not large enough to warrant putting them in separate files function set_theme(theme){ - gradioURL = window.location.href + var gradioURL = window.location.href if (!gradioURL.includes('?__theme=')) { window.location.replace(gradioURL + '?__theme=' + theme); } @@ -47,7 +47,7 @@ function extract_image_from_gallery(gallery){ return [gallery[0]]; } - index = selected_gallery_index() + var index = selected_gallery_index() if (index < 0 || index >= gallery.length){ // Use the first image in the gallery as the default @@ -58,7 +58,7 @@ function extract_image_from_gallery(gallery){ } function args_to_array(args){ - res = [] + var res = [] for(var i=0;i Date: Sun, 30 Apr 2023 22:12:24 +0300 Subject: [PATCH 0069/2418] Fix unused variables --- javascript/aspectRatioOverlay.js | 11 +++-------- javascript/contextMenus.js | 4 ++-- javascript/extensions.js | 4 ++-- javascript/extraNetworks.js | 4 ++-- javascript/imageMaskFix.js | 1 - javascript/imageParams.js | 1 - javascript/notification.js | 2 +- javascript/progressbar.js | 4 ++-- javascript/ui.js | 4 ++-- 9 files changed, 14 insertions(+), 21 deletions(-) diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index 10ac81c7034..5160081d282 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -55,19 +55,14 @@ function dimensionChange(e, is_width, is_height){ var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight/2) var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth/2) - var viewRectTop = cleintRectCentreY-(scaledy/2) // TODO: unused? - var viewRectLeft = cleintRectCentreX-(scaledx/2) // TODO: unused? - var arRectWidth = scaledx - var arRectHeight = scaledy - - var arscale = Math.min( arRectWidth/currentWidth, arRectHeight/currentHeight ) + var arscale = Math.min( scaledx/currentWidth, scaledy/currentHeight ) var arscaledx = currentWidth*arscale var arscaledy = currentHeight*arscale var arRectTop = cleintRectCentreY-(arscaledy/2) var arRectLeft = cleintRectCentreX-(arscaledx/2) - arRectWidth = arscaledx - arRectHeight = arscaledy + var arRectWidth = arscaledx + var arRectHeight = arscaledy arPreviewRect.style.top = arRectTop+'px'; arPreviewRect.style.left = arRectLeft+'px'; diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index 5107e524ca2..ec7960e1954 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -35,7 +35,7 @@ contextMenuInit = function(){ menuEntries.forEach(function(entry){ let contextMenuEntry = document.createElement('a') contextMenuEntry.innerHTML = entry['name'] - contextMenuEntry.addEventListener("click", function(e) { + contextMenuEntry.addEventListener("click", function() { entry['func'](); }) contextMenuList.append(contextMenuEntry); @@ -78,7 +78,7 @@ contextMenuInit = function(){ } function removeContextMenuOption(uid){ - menuSpecs.forEach(function(v,k) { + menuSpecs.forEach(function(v) { let index = -1 v.forEach(function(e,ei){if(e['id']==uid){index=ei}}) if(index>=0){ diff --git a/javascript/extensions.js b/javascript/extensions.js index 872259afcf1..cb68344b3bc 100644 --- a/javascript/extensions.js +++ b/javascript/extensions.js @@ -1,5 +1,5 @@ -function extensions_apply(_, _, disable_all){ +function extensions_apply(_disabled_list, _update_list, disable_all){ var disable = [] var update = [] @@ -16,7 +16,7 @@ function extensions_apply(_, _, disable_all){ return [JSON.stringify(disable), JSON.stringify(update), disable_all] } -function extensions_check(_, _){ +function extensions_check(){ var disable = [] gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 963d5b02501..c8f6b3861f4 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -10,7 +10,7 @@ function setupExtraNetworksForTab(tabname){ tabs.appendChild(search) tabs.appendChild(refresh) - search.addEventListener("input", function(evt){ + search.addEventListener("input", function(){ var searchTerm = search.value.toLowerCase() gradioApp().querySelectorAll('#'+tabname+'_extra_tabs div.card').forEach(function(elem){ @@ -55,7 +55,7 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text){ var partToSearch = m[1] var replaced = false - var newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found, index){ + var newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found){ m = found.match(re_extranet); if(m[1] == partToSearch){ replaced = true; diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js index bd0be627d9d..a612705d270 100644 --- a/javascript/imageMaskFix.js +++ b/javascript/imageMaskFix.js @@ -23,7 +23,6 @@ function imageMaskResize() { const nw = previewImage.naturalWidth; const nh = previewImage.naturalHeight; const portrait = nh > nw; - const factor = portrait; const wW = Math.min(w, portrait ? h/nh*nw : w/nw*nw); const wH = Math.min(h, portrait ? h/nh*nh : w/nw*nh); diff --git a/javascript/imageParams.js b/javascript/imageParams.js index 67404a89ba6..64aee93b7c1 100644 --- a/javascript/imageParams.js +++ b/javascript/imageParams.js @@ -1,7 +1,6 @@ window.onload = (function(){ window.addEventListener('drop', e => { const target = e.composedPath()[0]; - const idx = selected_gallery_index(); if (target.placeholder.indexOf("Prompt") == -1) return; let prompt_target = get_tab_index('tabs') == 1 ? "img2img_prompt_image" : "txt2img_prompt_image"; diff --git a/javascript/notification.js b/javascript/notification.js index a40de7c30b0..32913bac83b 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -9,7 +9,7 @@ onUiUpdate(function(){ notificationButton = gradioApp().getElementById('request_notifications') if(notificationButton != null){ - notificationButton.addEventListener('click', function (evt) { + notificationButton.addEventListener('click', () => { Notification.requestPermission(); },true); } diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 7b853a40f6e..8d2c3492403 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -1,10 +1,10 @@ // code related to showing and updating progressbar shown as the image is being made -function rememberGallerySelection(id_gallery){ +function rememberGallerySelection(){ } -function getGallerySelectedIndex(id_gallery){ +function getGallerySelectedIndex(){ } diff --git a/javascript/ui.js b/javascript/ui.js index fed96f987b4..aa440bf640c 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -207,7 +207,7 @@ function submit_img2img(){ return res } -function restoreProgressTxt2img(x){ +function restoreProgressTxt2img(){ var id = localStorage.getItem("txt2img_task_id") if(id) { @@ -218,7 +218,7 @@ function restoreProgressTxt2img(x){ return [id] } -function restoreProgressImg2img(x){ +function restoreProgressImg2img(){ var id = localStorage.getItem("img2img_task_id") if(id) { From b7269f781c0c673c09beff6ec5fb33ea6b4779a6 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 22:14:51 +0300 Subject: [PATCH 0070/2418] Mark Notification.requestPermission's retval as purposely ignored --- javascript/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/notification.js b/javascript/notification.js index 32913bac83b..83fce1f86d1 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -10,7 +10,7 @@ onUiUpdate(function(){ if(notificationButton != null){ notificationButton.addEventListener('click', () => { - Notification.requestPermission(); + void Notification.requestPermission(); },true); } } From 4bb441bb08aa3692bdd7d347016b4d70e9cdf365 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 22:15:11 +0300 Subject: [PATCH 0071/2418] Remove redundant return --- javascript/contextMenus.js | 1 - 1 file changed, 1 deletion(-) diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index ec7960e1954..1f682c74615 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -111,7 +111,6 @@ contextMenuInit = function(){ if(e.composedPath()[0].matches(k)){ showContextMenu(e,e.composedPath()[0],v) e.preventDefault() - return } }) }); From c714300265919e325ae1340459c4866541940687 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 22:15:59 +0300 Subject: [PATCH 0072/2418] Use substring instead of deprecated substr --- javascript/contextMenus.js | 2 +- javascript/extensions.js | 6 +++--- javascript/ui.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index 1f682c74615..42f301ab7dd 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -4,7 +4,7 @@ contextMenuInit = function(){ let menuSpecs = new Map(); const uid = function(){ - return Date.now().toString(36) + Math.random().toString(36).substr(2); + return Date.now().toString(36) + Math.random().toString(36).substring(2); } function showContextMenu(event,element,menuEntries){ diff --git a/javascript/extensions.js b/javascript/extensions.js index cb68344b3bc..2a2d2f8e79e 100644 --- a/javascript/extensions.js +++ b/javascript/extensions.js @@ -5,10 +5,10 @@ function extensions_apply(_disabled_list, _update_list, disable_all){ gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substr(7)) + disable.push(x.name.substring(7)) if(x.name.startsWith("update_") && x.checked) - update.push(x.name.substr(7)) + update.push(x.name.substring(7)) }) restart_reload() @@ -21,7 +21,7 @@ function extensions_check(){ gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substr(7)) + disable.push(x.name.substring(7)) }) gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x){ diff --git a/javascript/ui.js b/javascript/ui.js index aa440bf640c..e2b9bfe4a09 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -351,7 +351,7 @@ onUiUpdate(function(){ onOptionsChanged(function(){ var elem = gradioApp().getElementById('sd_checkpoint_hash') var sd_checkpoint_hash = opts.sd_checkpoint_hash || "" - var shorthash = sd_checkpoint_hash.substr(0,10) + var shorthash = sd_checkpoint_hash.substring(0,10) if(elem && elem.textContent != shorthash){ elem.textContent = shorthash From b1717c0a4804f8ed3bb8cc2f3aea5d095778b447 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 2 May 2023 09:08:00 +0300 Subject: [PATCH 0073/2418] do not load wait for shared.sd_model to load at startup --- modules/sd_models.py | 54 ++++++++++++++++++++++++++++++++------------ modules/shared.py | 31 +++++++++++++++++++++---- modules/ui.py | 10 ++++---- webui.py | 16 ++++--------- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 4f7613a140c..59adc7ccf40 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -2,6 +2,8 @@ import os.path import sys import gc +import threading + import torch import re import safetensors.torch @@ -404,13 +406,39 @@ def repair_config(sd_config): sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' -def load_model(checkpoint_info=None, already_loaded_state_dict=None, time_taken_to_load_state_dict=None): + +class SdModelData: + def __init__(self): + self.sd_model = None + self.lock = threading.Lock() + + def get_sd_model(self): + if self.sd_model is None: + with self.lock: + try: + load_model() + except Exception as e: + errors.display(e, "loading stable diffusion model") + print("", file=sys.stderr) + print("Stable diffusion model failed to load", file=sys.stderr) + self.sd_model = None + + return self.sd_model + + def set_sd_model(self, v): + self.sd_model = v + + +model_data = SdModelData() + + +def load_model(checkpoint_info=None, already_loaded_state_dict=None): from modules import lowvram, sd_hijack checkpoint_info = checkpoint_info or select_checkpoint() - if shared.sd_model: - sd_hijack.model_hijack.undo_hijack(shared.sd_model) - shared.sd_model = None + if model_data.sd_model: + sd_hijack.model_hijack.undo_hijack(model_data.sd_model) + model_data.sd_model = None gc.collect() devices.torch_gc() @@ -464,7 +492,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, time_taken_ timer.record("hijack") sd_model.eval() - shared.sd_model = sd_model + model_data.sd_model = sd_model sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) # Reload embeddings after model load as they may or may not fit the model @@ -484,7 +512,7 @@ def reload_model_weights(sd_model=None, info=None): checkpoint_info = info or select_checkpoint() if not sd_model: - sd_model = shared.sd_model + sd_model = model_data.sd_model if sd_model is None: # previous model load failed current_checkpoint_info = None @@ -512,7 +540,7 @@ def reload_model_weights(sd_model=None, info=None): del sd_model checkpoints_loaded.clear() load_model(checkpoint_info, already_loaded_state_dict=state_dict) - return shared.sd_model + return model_data.sd_model try: load_model_weights(sd_model, checkpoint_info, state_dict, timer) @@ -535,17 +563,15 @@ def reload_model_weights(sd_model=None, info=None): return sd_model + def unload_model_weights(sd_model=None, info=None): from modules import lowvram, devices, sd_hijack timer = Timer() - if shared.sd_model: - - # shared.sd_model.cond_stage_model.to(devices.cpu) - # shared.sd_model.first_stage_model.to(devices.cpu) - shared.sd_model.to(devices.cpu) - sd_hijack.model_hijack.undo_hijack(shared.sd_model) - shared.sd_model = None + if model_data.sd_model: + model_data.sd_model.to(devices.cpu) + sd_hijack.model_hijack.undo_hijack(model_data.sd_model) + model_data.sd_model = None sd_model = None gc.collect() devices.torch_gc() diff --git a/modules/shared.py b/modules/shared.py index 6a2b3c2bd1d..151bab9e4ba 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -16,6 +16,7 @@ import modules.devices as devices from modules import localization, script_loading, errors, ui_components, shared_items, cmd_args from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir +from ldm.models.diffusion.ddpm import LatentDiffusion demo = None @@ -600,13 +601,37 @@ def cast_value(self, key, value): return value - opts = Options() if os.path.exists(config_filename): opts.load(config_filename) + +class Shared(sys.modules[__name__].__class__): + """ + this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than + at program startup. + """ + + sd_model_val = None + + @property + def sd_model(self): + import modules.sd_models + + return modules.sd_models.model_data.get_sd_model() + + @sd_model.setter + def sd_model(self, value): + import modules.sd_models + + modules.sd_models.model_data.set_sd_model(value) + + +sd_model: LatentDiffusion = None # this var is here just for IDE's type checking; it cannot be accessed because the class field above will be accessed instead +sys.modules[__name__].__class__ = Shared + settings_components = None -"""assinged from ui.py, a mapping on setting anmes to gradio components repsponsible for those settings""" +"""assinged from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" latent_upscale_default_mode = "Latent" latent_upscale_modes = { @@ -620,8 +645,6 @@ def cast_value(self, key, value): sd_upscalers = [] -sd_model = None - clip_model = None progress_print_out = sys.stdout diff --git a/modules/ui.py b/modules/ui.py index 7b45f1319f7..16c46515673 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -828,7 +828,7 @@ def copy_image(img): 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=shared.sd_model and shared.sd_model.cond_stage_key == "edit") + 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 == "seed": @@ -1693,11 +1693,9 @@ def request_restart(): show_progress=info.refresh is not None, ) - text_settings.change( - fn=lambda: gr.update(visible=shared.sd_model and shared.sd_model.cond_stage_key == "edit"), - inputs=[], - outputs=[image_cfg_scale], - ) + update_image_cfg_scale_visibility = lambda: gr.update(visible=shared.sd_model and shared.sd_model.cond_stage_key == "edit") + text_settings.change(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale]) + demo.load(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale]) button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False) button_set_checkpoint.click( diff --git a/webui.py b/webui.py index 357bf4c1979..0873a26cffd 100644 --- a/webui.py +++ b/webui.py @@ -6,6 +6,8 @@ import re import warnings import json +from threading import Thread + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -191,18 +193,10 @@ def initialize(): modules.textual_inversion.textual_inversion.list_textual_inversion_templates() startup_timer.record("refresh textual inversion templates") - try: - modules.sd_models.load_model() - except Exception as e: - errors.display(e, "loading stable diffusion model") - print("", file=sys.stderr) - print("Stable diffusion model failed to load, exiting", file=sys.stderr) - exit(1) - startup_timer.record("load SD checkpoint") - - shared.opts.data["sd_model_checkpoint"] = shared.sd_model.sd_checkpoint_info.title + # load model in parallel to other startup stuff + Thread(target=lambda: shared.sd_model).start() - shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights())) + shared.opts.onchange("sd_model_checkpoint", wrap_queued_call(lambda: modules.sd_models.reload_model_weights()), call=False) shared.opts.onchange("sd_vae", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("sd_vae_as_default", wrap_queued_call(lambda: modules.sd_vae.reload_vae_weights()), call=False) shared.opts.onchange("temp_dir", ui_tempdir.on_tmpdir_changed) From 14b70aa97ba356691254055a67b9eb2ec3440b3e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 2 May 2023 11:03:11 +0300 Subject: [PATCH 0074/2418] revert unwanted change from #9865 --- webui.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 webui.sh diff --git a/webui.sh b/webui.sh old mode 100644 new mode 100755 From efe98ca0900bdf1098a5a957f576b86e4ddebbea Mon Sep 17 00:00:00 2001 From: Acncagua Slt Date: Wed, 3 May 2023 00:44:16 +0900 Subject: [PATCH 0075/2418] Initialize the upscalers Add modelloader.load_upscalers to def initialize() --- webui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webui.py b/webui.py index 357bf4c1979..dd385a38275 100644 --- a/webui.py +++ b/webui.py @@ -185,6 +185,9 @@ def initialize(): modules.scripts.load_scripts() startup_timer.record("load scripts") + modelloader.load_upscalers() + #startup_timer.record("load upscalers") #Is this necessary? I don't know. + modules.sd_vae.refresh_vae_list() startup_timer.record("refresh VAE") From 14e55a330146bda01f883a79e3900314a79eb22c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 3 May 2023 14:28:59 +0900 Subject: [PATCH 0076/2418] print PIL.UnidentifiedImageError --- modules/img2img.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/img2img.py b/modules/img2img.py index 56c846d62ec..9fc3a698e88 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -48,7 +48,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): try: img = Image.open(image) - except UnidentifiedImageError: + except UnidentifiedImageError as e: + print(e) continue # Use the EXIF orientation of photos taken by smartphones. img = ImageOps.exif_transpose(img) From e960781511eb175943be09b314ac2be46b6fc684 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Wed, 3 May 2023 13:12:43 -0500 Subject: [PATCH 0077/2418] fix maximum downsampling option --- modules/generation_parameters_copypaste.py | 4 +++- modules/processing.py | 1 + modules/shared.py | 5 +---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 34c1b8608ed..83382e93254 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -306,6 +306,8 @@ def parse_generation_parameters(x: str): res["Token merging stride x"] = '2' if res.get("Token merging stride y", None) is None: res["Token merging stride y"] = '2' + if res.get("Token merging maximum down sampling", None) is None: + res["Token merging maximum down sampling"] = '1' restore_old_hires_fix_params(res) @@ -341,7 +343,7 @@ def parse_generation_parameters(x: str): ('Token merging merge attention', 'token_merging_merge_attention'), ('Token merging merge cross attention', 'token_merging_merge_cross_attention'), ('Token merging merge mlp', 'token_merging_merge_mlp'), - ('Token merging maximum downsampling', 'token_merging_maximum_downsampling'), + ('Token merging maximum down sampling', 'token_merging_maximum_down_sampling'), ('Token merging stride x', 'token_merging_stride_x'), ('Token merging stride y', 'token_merging_stride_y'), ('RNG', 'randn_source'), diff --git a/modules/processing.py b/modules/processing.py index d5d1da5afb9..6807a301e2b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -495,6 +495,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Token merging merge mlp": None if opts.token_merging_merge_mlp is False else opts.token_merging_merge_mlp, "Token merging stride x": None if opts.token_merging_stride_x == 2 else opts.token_merging_stride_x, "Token merging stride y": None if opts.token_merging_stride_y == 2 else opts.token_merging_stride_y, + "Token merging maximum down sampling": None if opts.token_merging_maximum_down_sampling == 1 else opts.token_merging_maximum_down_sampling, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, diff --git a/modules/shared.py b/modules/shared.py index 7b81ffc96ff..a7a72dd57a9 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -488,10 +488,7 @@ def list_samplers(): False, "Merge mlp", gr.Checkbox ), - "token_merging_maximum_down_sampling": OptionInfo( - 1, "Maximum down sampling", - gr.Dropdown, lambda: {"choices": ["1", "2", "4", "8"]} - ), + "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": ['1', '2', '4', '8']}), "token_merging_stride_x": OptionInfo( 2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} From 251be61a80e9363fabe1d1881e32a8c09366e8d6 Mon Sep 17 00:00:00 2001 From: Weiming Dong Date: Thu, 4 May 2023 07:59:52 +0800 Subject: [PATCH 0078/2418] Add extra `None` option for VAE --- scripts/xyz_grid.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index cfc7737b49b..01d97791e11 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -222,7 +222,7 @@ def __init__(self, *args, **kwargs): AxisOption("Denoising", float, apply_field("denoising_strength")), AxisOptionTxt2Img("Hires upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")), - AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: list(sd_vae.vae_dict)), + AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['None'] + list(sd_vae.vae_dict)), AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)), AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5), AxisOption("Face restore", str, apply_face_restore, format_value=format_value), @@ -346,7 +346,7 @@ def __enter__(self): self.CLIP_stop_at_last_layers = opts.CLIP_stop_at_last_layers self.vae = opts.sd_vae self.uni_pc_order = opts.uni_pc_order - + def __exit__(self, exc_type, exc_value, tb): opts.data["sd_vae"] = self.vae opts.data["uni_pc_order"] = self.uni_pc_order @@ -399,7 +399,7 @@ def ui(self, is_img2img): include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) 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.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") swap_yz_axes_button = gr.Button(value="Swap Y/Z axes", elem_id="yz_grid_swap_axes_button") @@ -490,7 +490,7 @@ def process_axis(opt, vals, vals_dropdown): start = int(mc.group(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: valslist_ext.append(val) @@ -512,7 +512,7 @@ def process_axis(opt, vals, vals_dropdown): start = float(mc.group(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: valslist_ext.append(val) From f0efc8c211fc2d2c2f8caf6e2f92501922d18c99 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Wed, 3 May 2023 21:10:31 -0500 Subject: [PATCH 0079/2418] not being cast properly every time, swap to ints --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index a7a72dd57a9..eb06909cccc 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -488,7 +488,7 @@ def list_samplers(): False, "Merge mlp", gr.Checkbox ), - "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": ['1', '2', '4', '8']}), + "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), "token_merging_stride_x": OptionInfo( 2, "Stride - X", gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} From 1bebb50da977dfb93d78ae161e0f28d332f408ea Mon Sep 17 00:00:00 2001 From: Acncagua Slt Date: Thu, 4 May 2023 11:59:22 +0900 Subject: [PATCH 0080/2418] No double calls will be made Do not call load_upscalers in list_builtin_upscalers --- modules/modelloader.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index 522affc6e5b..2bfdc1dfa8e 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -133,12 +133,9 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None): def list_builtin_upscalers(): - load_upscalers() - builtin_upscaler_classes.clear() builtin_upscaler_classes.extend(Upscaler.__subclasses__()) - def forbid_loaded_nonbuiltin_upscalers(): for cls in Upscaler.__subclasses__(): if cls not in builtin_upscaler_classes: From 29e13867bff5a00388e5eb1dd04c16c6e8f33e4f Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 4 May 2023 14:08:20 +0800 Subject: [PATCH 0081/2418] Revert "Refresh bug fix" This reverts commit eff00413ae76e01b9a24b13dee6a0dda98f643f2. --- modules/img2img.py | 3 +-- modules/txt2img.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index fea51667bfb..6da974e4519 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -181,8 +181,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s print(generation_info_js) for img in processed.images: - if hasattr(img, 'already_saved_as'): - img.already_saved_as += f'?{int(time())}' + img.already_saved_as += f'?{int(time())}' if opts.do_not_show_images: processed.images = [] diff --git a/modules/txt2img.py b/modules/txt2img.py index 57278bdc320..6ebca656a9a 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -65,8 +65,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step print(generation_info_js) for img in processed.images: - if hasattr(img, 'already_saved_as'): - img.already_saved_as += f'?{int(time())}' + img.already_saved_as += f'?{int(time())}' if opts.do_not_show_images: processed.images = [] From 35e5916af9ccf1661c6b262b768d4b241ab9eee7 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 4 May 2023 14:08:21 +0800 Subject: [PATCH 0082/2418] Revert "Add img2img refreshed correctly" This reverts commit 988dd02632bf38e64bc0cf394ece2a5f7a64e15b. --- modules/img2img.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 6da974e4519..953ac5d2d12 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -15,7 +15,6 @@ from modules.ui import plaintext_to_html import modules.images as images import modules.scripts -from time import time def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): @@ -180,9 +179,6 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if opts.samples_log_stdout: print(generation_info_js) - for img in processed.images: - img.already_saved_as += f'?{int(time())}' - if opts.do_not_show_images: processed.images = [] From 5c66fedb641841958020c0f8693fc69dee7a96a1 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 4 May 2023 14:08:22 +0800 Subject: [PATCH 0083/2418] Revert "Fix gallery not being refreshed correctly" This reverts commit 2c24e09dfc431ef7617134a807bf741f0b0c9fa3. --- modules/txt2img.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/txt2img.py b/modules/txt2img.py index 6ebca656a9a..16841d0f2f3 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -7,7 +7,6 @@ import modules.shared as shared import modules.processing as processing from modules.ui import plaintext_to_html -from time import time def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, override_settings_texts, *args): @@ -64,9 +63,6 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step if opts.samples_log_stdout: print(generation_info_js) - for img in processed.images: - img.already_saved_as += f'?{int(time())}' - if opts.do_not_show_images: processed.images = [] From 91a15dca80f44e32461f0ab7219de632781a2622 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 4 May 2023 14:38:15 +0800 Subject: [PATCH 0084/2418] Use a new way to solve webpage refresh --- modules/ui_tempdir.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 21945235ef3..82b9c4e48cd 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -2,6 +2,7 @@ import tempfile from collections import namedtuple from pathlib import Path +from time import time import gradio as gr @@ -34,7 +35,7 @@ def check_tmp_file(gradio, filename): def save_pil_to_file(pil_image, dir=None): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): - register_tmp_file(shared.demo, already_saved_as) + register_tmp_file(shared.demo, f'{already_saved_as}?{int(time())}') file_obj = Savedfile(already_saved_as) return file_obj From 8bc4a3a2a8d6ad9b439cb90d52600cc50e9b4f42 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 4 May 2023 15:55:57 +0800 Subject: [PATCH 0085/2418] Refresh fix --- modules/ui_tempdir.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 82b9c4e48cd..42a85d3b82e 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -35,7 +35,8 @@ def check_tmp_file(gradio, filename): def save_pil_to_file(pil_image, dir=None): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): - register_tmp_file(shared.demo, f'{already_saved_as}?{int(time())}') + already_saved_as += f'?{int(time())}' + register_tmp_file(shared.demo, already_saved_as) file_obj = Savedfile(already_saved_as) return file_obj From c3eced22fc7b9da4fbb2f55f2d53a7e5e511cfbd Mon Sep 17 00:00:00 2001 From: Leo Mozoloa Date: Thu, 4 May 2023 16:14:33 +0200 Subject: [PATCH 0086/2418] Fix some Lora's not working --- extensions-builtin/Lora/lora.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 6f246921ecf..bcf36d77ea8 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -165,8 +165,10 @@ def load_lora(name, filename): module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) elif type(sd_module) == torch.nn.MultiheadAttention: module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(sd_module) == torch.nn.Conv2d: + elif type(sd_module) == torch.nn.Conv2d and weight.shape[2:] == (1, 1): module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) + elif type(sd_module) == torch.nn.Conv2d and weight.shape[2:] == (3, 3): + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (3, 3), bias=False) else: print(f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}') continue @@ -232,6 +234,8 @@ def lora_calc_updown(lora, module, target): if up.shape[2:] == (1, 1) and down.shape[2:] == (1, 1): updown = (up.squeeze(2).squeeze(2) @ down.squeeze(2).squeeze(2)).unsqueeze(2).unsqueeze(3) + elif up.shape[2:] == (3, 3) or down.shape[2:] == (3, 3): + updown = torch.nn.functional.conv2d(down.permute(1, 0, 2, 3), up).permute(1, 0, 2, 3) else: updown = up @ down From 16f0739db022de6b49b6572d565f1c72e72dc3a7 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 30 Apr 2023 23:01:39 +0300 Subject: [PATCH 0087/2418] Make localization.js do nothing if there's no localization to do --- javascript/localization.js | 68 +++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/javascript/localization.js b/javascript/localization.js index e1ffa271d9a..0123b877bdb 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -25,6 +25,10 @@ re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u original_lines = {} translated_lines = {} +function hasLocalization() { + return window.localization && Object.keys(window.localization).length > 0; +} + function textNodesUnder(el){ var n, a=[], walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false); while(n=walk.nextNode()) a.push(n); @@ -119,37 +123,6 @@ function dumpTranslations(){ return dumped } -onUiUpdate(function(m){ - m.forEach(function(mutation){ - mutation.addedNodes.forEach(function(node){ - processNode(node) - }) - }); -}) - - -document.addEventListener("DOMContentLoaded", function() { - processNode(gradioApp()) - - if (localization.rtl) { // if the language is from right to left, - (new MutationObserver((mutations, observer) => { // wait for the style to load - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.tagName === 'STYLE') { - observer.disconnect(); - - for (const x of node.sheet.rules) { // find all rtl media rules - if (Array.from(x.media || []).includes('rtl')) { - x.media.appendMedium('all'); // enable them - } - } - } - }) - }); - })).observe(gradioApp(), { childList: true }); - } -}) - function download_localization() { var text = JSON.stringify(dumpTranslations(), null, 4) @@ -163,3 +136,36 @@ function download_localization() { document.body.removeChild(element); } + +if(hasLocalization()) { + onUiUpdate(function (m) { + m.forEach(function (mutation) { + mutation.addedNodes.forEach(function (node) { + processNode(node) + }) + }); + }) + + + document.addEventListener("DOMContentLoaded", function () { + processNode(gradioApp()) + + if (localization.rtl) { // if the language is from right to left, + (new MutationObserver((mutations, observer) => { // wait for the style to load + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.tagName === 'STYLE') { + observer.disconnect(); + + for (const x of node.sheet.rules) { // find all rtl media rules + if (Array.from(x.media || []).includes('rtl')) { + x.media.appendMedium('all'); // enable them + } + } + } + }) + }); + })).observe(gradioApp(), { childList: true }); + } + }) +} From a3cdf9aaf85399d6ddfb5bc3245d8f154802fe58 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Fri, 5 May 2023 15:51:01 +0800 Subject: [PATCH 0088/2418] Reopen image fix --- modules/generation_parameters_copypaste.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 6df76858f51..ae855627d61 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -59,6 +59,7 @@ def image_from_url_text(filedata): is_in_right_dir = ui_tempdir.check_tmp_file(shared.demo, filename) assert is_in_right_dir, 'trying to open image file outside of allowed directories' + filename = filename.rsplit('?', 1)[0] return Image.open(filename) if type(filedata) == list: From 79a6c5a666ba2261068afad969e0bda25bbbf6a9 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Fri, 5 May 2023 03:51:51 -0600 Subject: [PATCH 0089/2418] Fix stretched thumbnails on extras tab --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 3f56087a040..88bf6ad80db 100644 --- a/style.css +++ b/style.css @@ -246,7 +246,7 @@ button.custom-button{ } } -#txt2img_gallery img, #img2img_gallery img{ +#txt2img_gallery img, #img2img_gallery img, #extras_gallery img{ object-fit: scale-down; } #txt2img_actions_column, #img2img_actions_column { From cde0d642f34b2e008159eaeafb870d5efd1a3315 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 6 May 2023 02:20:33 +0900 Subject: [PATCH 0090/2418] add denoising strength filename pattern --- modules/images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/images.py b/modules/images.py index fd173829930..6ceb7c7c791 100644 --- a/modules/images.py +++ b/modules/images.py @@ -357,6 +357,7 @@ class FilenameGenerator: 'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.n_iter == 1 and self.p.batch_size == 1 else self.p.iteration * self.p.batch_size + self.p.batch_index + 1, 'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt..] 'clip_skip': lambda self: opts.data["CLIP_stop_at_last_layers"], + 'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT, } default_time_format = '%Y%m%d%H%M%S' From 381674739eff32156bdd88239ab7538e553b1b3d Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 6 May 2023 02:24:33 +0900 Subject: [PATCH 0091/2418] add denoising strength filename pattern --- javascript/hints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 8d1967a7658..c55fedfb3cb 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -66,8 +66,8 @@ titles = { "Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.", - "Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime], [datetime
    {name} {description} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index c8f6b3861f4..c85bc79aafe 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -1,4 +1,3 @@ - function setupExtraNetworksForTab(tabname){ gradioApp().querySelector('#'+tabname+'_extra_tabs').classList.add('extra-networks') @@ -10,16 +9,34 @@ function setupExtraNetworksForTab(tabname){ tabs.appendChild(search) tabs.appendChild(refresh) - search.addEventListener("input", function(){ + var applyFilter = function(){ var searchTerm = search.value.toLowerCase() gradioApp().querySelectorAll('#'+tabname+'_extra_tabs div.card').forEach(function(elem){ + var searchOnly = elem.querySelector('.search_only') var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase() - elem.style.display = text.indexOf(searchTerm) == -1 ? "none" : "" + + var visible = text.indexOf(searchTerm) != -1 + + if(searchOnly && searchTerm.length < 4){ + visible = false + } + + elem.style.display = visible ? "" : "none" }) - }); + } + + search.addEventListener("input", applyFilter); + applyFilter(); + + extraNetworksApplyFilter[tabname] = applyFilter; +} + +function applyExtraNetworkFilter(tabname){ + setTimeout(extraNetworksApplyFilter[tabname], 1); } +var extraNetworksApplyFilter = {} var activePromptTextarea = {}; function setupExtraNetworks(){ diff --git a/modules/modelloader.py b/modules/modelloader.py index 522affc6e5b..f22744882b3 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -22,9 +22,6 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None """ output = [] - if ext_filter is None: - ext_filter = [] - try: places = [] @@ -39,22 +36,14 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None places.append(model_path) for place in places: - if os.path.exists(place): - for file in glob.iglob(place + '**/**', recursive=True): - full_path = file - if os.path.isdir(full_path): - continue - if os.path.islink(full_path) and not os.path.exists(full_path): - print(f"Skipping broken symlink: {full_path}") - continue - if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]): - continue - if len(ext_filter) != 0: - model_name, extension = os.path.splitext(file) - if extension not in ext_filter: - continue - if file not in output: - output.append(full_path) + for full_path in shared.walk_files(place, allowed_extensions=ext_filter): + if os.path.islink(full_path) and not os.path.exists(full_path): + print(f"Skipping broken symlink: {full_path}") + continue + if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]): + continue + if full_path not in output: + output.append(full_path) if model_url is not None and len(output) == 0: if download_name is not None: diff --git a/modules/shared.py b/modules/shared.py index 91aac1a340d..dd374713256 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -726,3 +726,20 @@ def html(filename): return file.read() return "" + + +def walk_files(path, allowed_extensions=None): + if not os.path.exists(path): + return + + if allowed_extensions is not None: + allowed_extensions = set(allowed_extensions) + + for root, dirs, files in os.walk(path): + for filename in files: + if allowed_extensions is not None: + _, ext = os.path.splitext(filename) + if ext not in allowed_extensions: + continue + + yield os.path.join(root, filename) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index aa2f5d1bc1a..86c05a55140 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -89,19 +89,22 @@ def create_html(self, tabname): subdirs = {} for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: - for x in glob.glob(os.path.join(parentdir, '**/*'), recursive=True): - if not os.path.isdir(x): - continue + for root, dirs, files in os.walk(parentdir): + for dirname in dirs: + x = os.path.join(root, dirname) - subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/") - while subdir.startswith("/"): - subdir = subdir[1:] + if not os.path.isdir(x): + continue - is_empty = len(os.listdir(x)) == 0 - if not is_empty and not subdir.endswith("/"): - subdir = subdir + "/" + subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/") + while subdir.startswith("/"): + subdir = subdir[1:] - subdirs[subdir] = 1 + is_empty = len(os.listdir(x)) == 0 + if not is_empty and not subdir.endswith("/"): + subdir = subdir + "/" + + subdirs[subdir] = 1 if subdirs: subdirs = {"": 1, **subdirs} @@ -157,8 +160,20 @@ def create_html_for_item(self, item, tabname): if metadata: metadata_button = f"" + local_path = "" + filename = item.get("filename", "") + for reldir in self.allowed_directories_for_previews(): + absdir = os.path.abspath(reldir) + + if filename.startswith(absdir): + local_path = filename[len(absdir):] + + # if this is true, the item must not be show in the default view, and must instead only be + # shown when searching for it + serach_only = "/." in local_path or "\\." in local_path + args = { - "style": f"'{height}{width}{background_image}'", + "style": f"'display: none; {height}{width}{background_image}'", "prompt": item.get("prompt", None), "tabname": json.dumps(tabname), "local_preview": json.dumps(item["local_preview"]), @@ -168,6 +183,7 @@ def create_html_for_item(self, item, tabname): "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), "metadata_button": metadata_button, + "serach_only": " search_only" if serach_only else "", } return self.card_page.format(**args) @@ -209,6 +225,11 @@ def intialize(): class ExtraNetworksUi: def __init__(self): self.pages = None + """gradio HTML components related to extra networks' pages""" + + self.page_contents = None + """HTML content of the above; empty initially, filled when extra pages have to be shown""" + self.stored_extra_pages = None self.button_save_preview = None @@ -236,17 +257,22 @@ def tab_name_score(name): def create_ui(container, button, tabname): ui = ExtraNetworksUi() ui.pages = [] + ui.pages_contents = [] ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy()) ui.tabname = tabname with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs: for page in ui.stored_extra_pages: - with gr.Tab(page.title, id=page.title.lower().replace(" ", "_")): + page_id = page.title.lower().replace(" ", "_") - page_elem = gr.HTML(page.create_html(ui.tabname)) + with gr.Tab(page.title, id=page_id): + elem_id = f"{tabname}_{page_id}_cards_html" + page_elem = gr.HTML('', elem_id=elem_id) ui.pages.append(page_elem) - filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) + page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + json.dumps(tabname) + '); return []}', inputs=[], outputs=[]) + + gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh") ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) @@ -254,19 +280,22 @@ def create_ui(container, button, tabname): def toggle_visibility(is_visible): is_visible = not is_visible - return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) + + if is_visible and not ui.pages_contents: + refresh() + + return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")), *ui.pages_contents state_visible = gr.State(value=False) - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) + button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button, *ui.pages]) def refresh(): - res = [] - for pg in ui.stored_extra_pages: pg.refresh() - res.append(pg.create_html(ui.tabname)) - return res + ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] + + return ui.pages_contents button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages) From ec0da07236d286f37c86f9cd92642e24381dd6a5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 12:07:43 +0300 Subject: [PATCH 0105/2418] Lora: add an option to use old method of applying loras --- extensions-builtin/Lora/lora.py | 56 +++++++++++++++++-- .../Lora/scripts/lora_script.py | 5 ++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 83933639e4b..d488b5ae4eb 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -245,6 +245,19 @@ def lora_calc_updown(lora, module, target): return updown +def lora_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): + weights_backup = getattr(self, "lora_weights_backup", None) + + if weights_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) + + def lora_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): """ Applies the currently selected set of Loras to the weights of torch layer self. @@ -269,12 +282,7 @@ def lora_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.Mu self.lora_weights_backup = weights_backup if current_names != wanted_names: - 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) + lora_restore_weights_from_backup(self) for lora in loaded_loras: module = lora.modules.get(lora_layer_name, None) @@ -305,12 +313,45 @@ def lora_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.Mu setattr(self, "lora_current_names", wanted_names) +def lora_forward(module, input, original_forward): + """ + Old way of applying Lora by executing operations during layer's forward. + Stacking many loras this way results in big performance degradation. + """ + + if len(loaded_loras) == 0: + return original_forward(module, input) + + input = devices.cond_cast_unet(input) + + lora_restore_weights_from_backup(module) + lora_reset_cached_weight(module) + + res = original_forward(module, input) + + lora_layer_name = getattr(module, 'lora_layer_name', None) + for lora in loaded_loras: + module = lora.modules.get(lora_layer_name, None) + if module is None: + continue + + module.up.to(device=devices.device) + module.down.to(device=devices.device) + + res = res + module.up(module.down(input)) * lora.multiplier * (module.alpha / module.up.weight.shape[1] if module.alpha else 1.0) + + return res + + def lora_reset_cached_weight(self: Union[torch.nn.Conv2d, torch.nn.Linear]): setattr(self, "lora_current_names", ()) setattr(self, "lora_weights_backup", None) def lora_Linear_forward(self, input): + if shared.opts.lora_functional: + return lora_forward(self, input, torch.nn.Linear_forward_before_lora) + lora_apply_weights(self) return torch.nn.Linear_forward_before_lora(self, input) @@ -323,6 +364,9 @@ def lora_Linear_load_state_dict(self, *args, **kwargs): def lora_Conv2d_forward(self, input): + if shared.opts.lora_functional: + return lora_forward(self, input, torch.nn.Conv2d_forward_before_lora) + lora_apply_weights(self) return torch.nn.Conv2d_forward_before_lora(self, input) diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 2f2267a2fdc..a67b8a690d3 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -55,3 +55,8 @@ def before_ui(): shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), { "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in lora.available_loras]}, refresh=lora.list_available_loras), })) + + +shared.options_templates.update(shared.options_section(('compatibility', "Compatibility"), { + "lora_functional": shared.OptionInfo(False, "Lora: use old method that takes longer when you have multiple Loras active and produces same results as kohya-ss/sd-webui-additional-networks extension"), +})) From 18fb2162a44ae06eaff302845abb880f27bcc975 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 12:17:36 +0300 Subject: [PATCH 0106/2418] disable useless progress display when pasting infotext using the blur button --- modules/generation_parameters_copypaste.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 99f1a0d3fbd..6cc8d13b72a 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -129,6 +129,7 @@ def connect_paste_params_buttons(): _js=jsfunc, inputs=[binding.source_image_component], outputs=[destination_image_component, destination_width_component, destination_height_component] if destination_width_component else [destination_image_component], + show_progress=False, ) if binding.source_text_component is not None and fields is not None: @@ -140,6 +141,7 @@ def connect_paste_params_buttons(): fn=lambda *x: x, inputs=[field for field, name in paste_fields[binding.source_tabname]["fields"] if name in paste_field_names], outputs=[field for field, name in fields if name in paste_field_names], + show_progress=False, ) binding.paste_button.click( @@ -147,6 +149,7 @@ def connect_paste_params_buttons(): _js=f"switch_to_{binding.tabname}", inputs=None, outputs=None, + show_progress=False, ) @@ -409,12 +412,14 @@ def paste_settings(params): fn=paste_func, inputs=[input_comp], outputs=[x[0] for x in paste_fields], + show_progress=False, ) button.click( fn=None, _js=f"recalculate_prompts_{tabname}", inputs=[], outputs=[], + show_progress=False, ) From f62540b2d2a2692838eb6dfc831c6cf9da12531d Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 12:18:22 +0300 Subject: [PATCH 0107/2418] Revert "add mtime to served images in gallery to prevent cache from showing old images" This reverts commit 669b518cbd2b9e8f5d7852a89627200f87c649d2. --- modules/ui_tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 9cb4954a0c8..21945235ef3 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -36,7 +36,7 @@ def save_pil_to_file(pil_image, dir=None): if already_saved_as and os.path.isfile(already_saved_as): register_tmp_file(shared.demo, already_saved_as) - file_obj = Savedfile(f"{already_saved_as}?{os.path.getmtime(already_saved_as)}") + file_obj = Savedfile(already_saved_as) return file_obj if shared.opts.temp_dir != "": From 6a5901a3fd5bab8d5d5933f56d1f0f991535793d Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 12:45:22 +0300 Subject: [PATCH 0108/2418] update changelog --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d2f96e5dcd..1ae81232ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +## Upcoming 1.2.0 + +### Features: + * do not load wait for stable diffusion model to load at startup + * add filename patterns: [denoising] + * directory hiding for extra networks: dirs starting with . will hide their cards on extra network tabs unless specifically searched for + * Lora: for the `<...>` text in prompt, use name of Lora that is in the metdata of the file, if present, instead of filename (both can be used to activate lora) + * Lora: read infotext params from kohya-ss's extension parameters if they are present and if his extension is not active + * Lora: Fix some Loras not working (ones that have 3x3 convolution layer) + * Lora: add an option to use old method of applying loras (producing same results as with kohya-ss) + +### Minor: + * --subpath option for gradio for use with reverse proxy + * linux/OSX: use existing virtualenv if already active (the VIRTUAL_ENV environment variable) + * possible frontend optimization: do not apply localizations if there are none + * Add extra `None` option for VAE in XYZ plot + * print error to console when batch processing in img2img fails + * create HTML for extra network pages only on demand + * allow directories starting with . to still list their models for lora, checkpoints, etc + +### Extensions: + * Tooltip localization support + +### Bug Fixes: + * re-add /docs endpoint + * fix gamepad navigation + * make the lightbox fullscreen image function properly + * fix squished thumbnails in extras tab + * keep "search" filter for extra networks when user refreshes the tab (previously it showed everthing after you refreshed) + + ## 1.1.1 ### Bug Fixes: * fix an error that prevents running webui on torch<2.0 without --disable-safe-unpickle From 34a82a345abe89faafbd43fa34f40dd110559071 Mon Sep 17 00:00:00 2001 From: Sayo Date: Mon, 8 May 2023 19:55:05 +0800 Subject: [PATCH 0109/2418] Add api method to get LoRA models --- extensions-builtin/Lora/lora.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index d488b5ae4eb..8fc1ddcac57 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -2,7 +2,9 @@ import os import re import torch -from typing import Union +from typing import Union, List, Optional +from fastapi import FastAPI +import gradio as gr from modules import shared, devices, sd_models, errors, scripts @@ -443,9 +445,19 @@ def infotext_pasted(infotext, params): if added: params["Prompt"] += "\n" + "".join(added) +def api(_: gr.Blocks, app: FastAPI): + @app.get("/sdapi/v1/loras") + async def getloras(): + return [{"name": name, "path": available_loras[name].filename, "prompt": ""} for name in available_loras] + available_loras = {} available_lora_aliases = {} loaded_loras = [] list_available_loras() +try: + import modules.script_callbacks as script_callbacks + script_callbacks.on_app_started(api) +except: + pass \ No newline at end of file From 505a10ad928eb11849828c88850ce3e5e3566fe4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 15:09:20 +0300 Subject: [PATCH 0110/2418] use file modification time instead of current time for #9760 --- CHANGELOG.md | 1 + modules/ui_tempdir.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ae81232ce7..c56d3a0edcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * make the lightbox fullscreen image function properly * fix squished thumbnails in extras tab * keep "search" filter for extra networks when user refreshes the tab (previously it showed everthing after you refreshed) + * fix webui showing the same image if you configure the generation to always save results into same file ## 1.1.1 diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 42a85d3b82e..67bfd1ec886 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -35,7 +35,8 @@ def check_tmp_file(gradio, filename): def save_pil_to_file(pil_image, dir=None): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): - already_saved_as += f'?{int(time())}' + already_saved_as += f'?{os.path.getmtime(already_saved_as)}' + register_tmp_file(shared.demo, already_saved_as) file_obj = Savedfile(already_saved_as) From 7aab389d6fc8ad08729071b1ed9d4de64c4e44db Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 14 Apr 2023 02:22:48 -0400 Subject: [PATCH 0111/2418] Fix for Unet NaNs --- modules/sd_hijack_optimizations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 372555ffaf4..f10865cd1e7 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -256,6 +256,9 @@ def sub_quad_attention_forward(self, x, context=None, mask=None): k = k.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) v = v.unflatten(-1, (h, -1)).transpose(1,2).flatten(end_dim=1) + if q.device.type == 'mps': + q, k, v = q.contiguous(), k.contiguous(), v.contiguous() + dtype = q.dtype if shared.opts.upcast_attn: q, k = q.float(), k.float() From ab4ab4e595e89d1a9a39db70539d5944fdbe47fa Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 15:23:49 +0300 Subject: [PATCH 0112/2418] add version to infotext, footer and console output when starting --- launch.py | 17 +++++++++++++++++ modules/processing.py | 11 +++++++++++ modules/shared.py | 1 + modules/ui.py | 6 +++--- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/launch.py b/launch.py index 1dc12daeed2..2a33adc8e4f 100644 --- a/launch.py +++ b/launch.py @@ -19,6 +19,7 @@ git = os.environ.get('GIT', "git") index_url = os.environ.get('INDEX_URL', "") stored_commit_hash = None +stored_git_tag = None dir_repos = "repositories" if 'GRADIO_ANALYTICS_ENABLED' not in os.environ: @@ -70,6 +71,20 @@ def commit_hash(): return stored_commit_hash +def git_tag(): + global stored_git_tag + + if stored_git_tag is not None: + return stored_git_tag + + try: + stored_git_tag = run(f"{git} describe --tags").strip() + except Exception: + stored_git_tag = "" + + return stored_git_tag + + def run(command, desc=None, errdesc=None, custom_env=None, live=False): if desc is not None: print(desc) @@ -246,8 +261,10 @@ def prepare_environment(): check_python_version() commit = commit_hash() + tag = git_tag() print(f"Python {sys.version}") + print(f"Version: {tag}") print(f"Commit hash: {commit}") if args.reinstall_torch or not is_installed("torch") or not is_installed("torchvision"): diff --git a/modules/processing.py b/modules/processing.py index e8808beb6fc..e786791abbc 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -458,6 +458,16 @@ def fix_seed(p): p.subseed = get_fixed_seed(p.subseed) +def program_version(): + import launch + + res = launch.git_tag() + if res == "": + res = None + + return res + + def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0): index = position_in_batch + iteration * p.batch_size @@ -483,6 +493,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, + "Version": program_version() if opts.add_version_to_infotext else None, } generation_params.update(p.extra_generation_params) diff --git a/modules/shared.py b/modules/shared.py index dd374713256..f40faa79a7e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -394,6 +394,7 @@ def list_samplers(): "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), "add_model_name_to_info": OptionInfo(True, "Add model name to generation information"), + "add_version_to_infotext": OptionInfo(True, "Add program version to generation information"), "disable_weights_auto_swap": OptionInfo(True, "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint."), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), diff --git a/modules/ui.py b/modules/ui.py index 16c46515673..b2916e9c926 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1923,7 +1923,7 @@ def versions_html(): python_version = ".".join([str(x) for x in sys.version_info[0:3]]) commit = launch.commit_hash() - short_commit = commit[0:8] + tag = launch.git_tag() if shared.xformers_available: import xformers @@ -1932,6 +1932,8 @@ def versions_html(): xformers_version = "N/A" return f""" +version: {tag} + •  python: {python_version}  •  torch: {getattr(torch, '__long_version__',torch.__version__)} @@ -1940,7 +1942,5 @@ def versions_html():  •  gradio: {gr.__version__}  •  -commit: {short_commit} - •  checkpoint: N/A """ From eabea24eb8ba5068c97ff5655bbe01dc032af4e9 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 15:26:23 +0300 Subject: [PATCH 0113/2418] put infotext options into their own category in settings tab --- modules/shared.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index f40faa79a7e..e1c3e5c48ad 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -392,10 +392,6 @@ def list_samplers(): "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), - "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), - "add_model_name_to_info": OptionInfo(True, "Add model name to generation information"), - "add_version_to_infotext": OptionInfo(True, "Add program version to generation information"), - "disable_weights_auto_swap": OptionInfo(True, "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint."), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), "font": OptionInfo("", "Font for image grids that have text"), @@ -417,6 +413,13 @@ def list_samplers(): "gradio_theme": OptionInfo("Default", "Gradio theme (requires restart)", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + gradio_hf_hub_themes}) })) +options_templates.update(options_section(('infotext', "Infotext"), { + "add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"), + "add_model_name_to_info": OptionInfo(True, "Add model name to generation information"), + "add_version_to_infotext": OptionInfo(True, "Add program version to generation information"), + "disable_weights_auto_swap": OptionInfo(True, "When reading generation parameters from text into UI (from PNG info or pasted text), do not change the selected model/checkpoint."), +})) + options_templates.update(options_section(('ui', "Live previews"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), From fc966c029943ce37aaecd620645c770478395afc Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 15:30:32 +0300 Subject: [PATCH 0114/2418] do not show licenses page when user selects Show all pages in settings --- javascript/ui.js | 3 +++ modules/ui.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/javascript/ui.js b/javascript/ui.js index b63b84b2193..611b70d1059 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -348,6 +348,9 @@ onUiUpdate(function(){ settings_tabs.appendChild(show_all_pages) show_all_pages.onclick = function(){ gradioApp().querySelectorAll('#settings > div').forEach(function(elem){ + if(elem.id == "settings_tab_licenses") + return; + elem.style.display = "block"; }) } diff --git a/modules/ui.py b/modules/ui.py index b2916e9c926..39efd57656c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1566,7 +1566,7 @@ def run_settings_single(value, key): current_row.__exit__() current_tab.__exit__() - with gr.TabItem("Actions", id="actions"): + with gr.TabItem("Actions", id="actions", elem_id="settings_tab_actions"): request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications") download_localization = gr.Button(value='Download localization template', elem_id="download_localization") reload_script_bodies = gr.Button(value='Reload custom script bodies (No ui updates, No restart)', variant='secondary', elem_id="settings_reload_script_bodies") @@ -1574,7 +1574,7 @@ def run_settings_single(value, key): unload_sd_model = gr.Button(value='Unload SD checkpoint to free VRAM', elem_id="sett_unload_sd_model") reload_sd_model = gr.Button(value='Reload the last SD checkpoint back into VRAM', elem_id="sett_reload_sd_model") - with gr.TabItem("Licenses", id="licenses"): + with gr.TabItem("Licenses", id="licenses", elem_id="settings_tab_licenses"): gr.HTML(shared.html("licenses.html"), elem_id="licenses") gr.Button(value="Show all pages", elem_id="settings_show_all_pages") From f9abe4cddcdc6704be02633d9d5ed9640d6b9008 Mon Sep 17 00:00:00 2001 From: Sayo Date: Mon, 8 May 2023 20:38:10 +0800 Subject: [PATCH 0115/2418] Add api method to get LoRA models with prompt --- extensions-builtin/Lora/lora.py | 13 +++-------- extensions-builtin/Lora/scripts/api.py | 31 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 extensions-builtin/Lora/scripts/api.py diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 8fc1ddcac57..05162e41b53 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -2,9 +2,8 @@ import os import re import torch -from typing import Union, List, Optional -from fastapi import FastAPI -import gradio as gr +from typing import Union +import scripts.api as api from modules import shared, devices, sd_models, errors, scripts @@ -445,12 +444,6 @@ def infotext_pasted(infotext, params): if added: params["Prompt"] += "\n" + "".join(added) -def api(_: gr.Blocks, app: FastAPI): - @app.get("/sdapi/v1/loras") - async def getloras(): - return [{"name": name, "path": available_loras[name].filename, "prompt": ""} for name in available_loras] - - available_loras = {} available_lora_aliases = {} loaded_loras = [] @@ -458,6 +451,6 @@ async def getloras(): list_available_loras() try: import modules.script_callbacks as script_callbacks - script_callbacks.on_app_started(api) + script_callbacks.on_app_started(api.api) except: pass \ No newline at end of file diff --git a/extensions-builtin/Lora/scripts/api.py b/extensions-builtin/Lora/scripts/api.py new file mode 100644 index 00000000000..f1f2e2fc1a5 --- /dev/null +++ b/extensions-builtin/Lora/scripts/api.py @@ -0,0 +1,31 @@ +from fastapi import FastAPI +import gradio as gr +import json +import os +import lora + +def get_lora_prompts(path): + directory, filename = os.path.split(path) + name_without_ext = os.path.splitext(filename)[0] + new_filename = name_without_ext + '.civitai.info' + try: + new_path = os.path.join(directory, new_filename) + if os.path.exists(new_path): + with open(new_path, 'r') as f: + data = json.load(f) + trained_words = data.get('trainedWords', []) + if len(trained_words) > 0: + result = ','.join(trained_words) + return result + else: + return '' + else: + return '' + except Exception as e: + return '' + +def api(_: gr.Blocks, app: FastAPI): + @app.get("/sdapi/v1/loras") + async def get_loras(): + return [{"name": name, "path": lora.available_loras[name].filename, "prompt": get_lora_prompts(lora.available_loras[name].filename)} for name in lora.available_loras] + From 5edb0acfeb424f71954b111910d2e08c410b0c43 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 15:38:25 +0300 Subject: [PATCH 0116/2418] use multiselect for quicksettings (this also resets the existing setting) --- modules/shared.py | 4 ++-- modules/ui.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index e1c3e5c48ad..a8154580a60 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -404,8 +404,8 @@ def list_samplers(): "dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row"), "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), - "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), - "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), + "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), + "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), diff --git a/modules/ui.py b/modules/ui.py index 39efd57656c..883d37e7565 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1525,7 +1525,7 @@ def run_settings_single(value, key): result = gr.HTML(elem_id="settings_result") - quicksettings_names = [x.strip() for x in opts.quicksettings.split(",")] + quicksettings_names = opts.quicksettings_list quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'} quicksettings_list = [] From 2b96a7b694d3392f76940dfe5df895a2833400fb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 8 May 2023 16:46:35 +0300 Subject: [PATCH 0117/2418] add links to wiki for filename pattern settings add extended info for quicksettings setting --- javascript/ui_settings_hints.js | 41 +++++++++++++++++++++++++++++++++ modules/ui.py | 14 +++++++++++ style.css | 16 +++++++++++++ webui.py | 1 + 4 files changed, 72 insertions(+) create mode 100644 javascript/ui_settings_hints.js diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js new file mode 100644 index 00000000000..87a289d318c --- /dev/null +++ b/javascript/ui_settings_hints.js @@ -0,0 +1,41 @@ +// various hints and extra info for the settings tab + +onUiLoaded(function(){ + createLink = function(elem_id, text, href){ + var a = document.createElement('A') + a.textContent = text + a.target = '_blank'; + + elem = gradioApp().querySelector('#'+elem_id) + elem.insertBefore(a, elem.querySelector('label')) + + return a + } + + createLink("setting_samples_filename_pattern", "[wiki] ").href = "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory" + createLink("setting_directories_filename_pattern", "[wiki] ").href = "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory" + + createLink("setting_quicksettings_list", "[info] ").addEventListener("click", function(event){ + requestGet("./internal/quicksettings-hint", {}, function(data){ + var table = document.createElement('table') + table.className = 'settings-value-table' + + data.forEach(function(obj){ + var tr = document.createElement('tr') + var td = document.createElement('td') + td.textContent = obj.name + tr.appendChild(td) + + var td = document.createElement('td') + td.textContent = obj.label + tr.appendChild(td) + + table.appendChild(tr) + }) + + popup(table); + }) + }); +}) + + diff --git a/modules/ui.py b/modules/ui.py index 883d37e7565..842c57f7471 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1944,3 +1944,17 @@ def versions_html():  •  checkpoint: N/A """ + + +def setup_ui_api(app): + from pydantic import BaseModel, Field + from typing import List + + class QuicksettingsHint(BaseModel): + name: str = Field(title="Name of the quicksettings field") + label: str = Field(title="Label of the quicksettings field") + + def quicksettings_hint(): + return [QuicksettingsHint(name=k, label=v.label) for k, v in opts.data_labels.items()] + + app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint]) diff --git a/style.css b/style.css index 57ddba0ed24..b823c7ddbc5 100644 --- a/style.css +++ b/style.css @@ -125,6 +125,10 @@ div.gradio-html.min{ text-decoration: none; } +a{ + font-weight: bold; + cursor: pointer; +} /* general styled components */ @@ -397,6 +401,18 @@ div#extras_scale_to_tab div.form{ margin: 0 1.2em; } +table.settings-value-table{ + background: white; + border-collapse: collapse; + margin: 1em; + border: 4px solid white; +} + +table.settings-value-table td{ + padding: 0.4em; + border: 1px solid #ccc; + max-width: 36em; +} /* live preview */ .progressDiv{ diff --git a/webui.py b/webui.py index f770db547d2..727ebd31d10 100644 --- a/webui.py +++ b/webui.py @@ -345,6 +345,7 @@ def fastapi_setup(self): setup_middleware(app) modules.progress.setup_progress_api(app) + modules.ui.setup_ui_api(app) if launch_api: create_api(app) From 9efb809f7c2f6754367cafcce02926bf954815d5 Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 8 May 2023 15:49:43 -0400 Subject: [PATCH 0118/2418] Remove PyTorch 2.0 check Apparently the commit in the main branch of pytorch/pytorch that fixes this issue didn't make it into PyTorch 2.0.1, and since it is unclear exactly which release will have it we'll just always apply the workaround so a crash doesn't occur regardless. --- modules/mac_specific.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 6fe8dea0726..68bffec680d 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -54,6 +54,6 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): CondFunc('torch.cumsum', cumsum_fix_func, None) CondFunc('torch.Tensor.cumsum', cumsum_fix_func, None) CondFunc('torch.narrow', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).clone(), None) - if version.parse(torch.__version__) == version.parse("2.0"): + # MPS workaround for https://github.com/pytorch/pytorch/issues/96113 - CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda *args, **kwargs: len(args) == 6) + CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps') From de401d8ffb46515a7cb4749f564d6a23085b4a5e Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 8 May 2023 16:32:40 -0400 Subject: [PATCH 0119/2418] Fix generation with k-diffusion/UniPC on x64 Macs --- modules/mac_specific.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 68bffec680d..40ce2101764 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -57,3 +57,8 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): # MPS workaround for https://github.com/pytorch/pytorch/issues/96113 CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps') + + # MPS workaround for https://github.com/pytorch/pytorch/issues/92311 + if platform.processor() == 'i386': + for funcName in ['torch.argmax', 'torch.Tensor.argmax']: + CondFunc(funcName, lambda _, input, *args, **kwargs: torch.max(input.float() if input.dtype == torch.int64 else input, *args, **kwargs)[1], lambda _, input, *args, **kwargs: input.device.type == 'mps') \ No newline at end of file From 11ae5399f667aec3fa00d99a0e5eeeeb3bafeb43 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 9 May 2023 10:52:02 +0300 Subject: [PATCH 0120/2418] make it so that custom context menu from contextMenu.js only disappears after user's click, ignoring non-user click events --- javascript/contextMenus.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index 42f301ab7dd..b2bdf0532cb 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -92,8 +92,7 @@ contextMenuInit = function(){ return; } gradioApp().addEventListener("click", function(e) { - let source = e.composedPath()[0] - if(source.id && source.id.indexOf('check_progress')>-1){ + if(! e.isTrusted){ return } From eb95809501068a38f2b6bdb01b6ae5b86ff7ae87 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 9 May 2023 11:25:46 +0300 Subject: [PATCH 0121/2418] rework loras api --- extensions-builtin/Lora/lora.py | 6 ---- extensions-builtin/Lora/scripts/api.py | 31 ------------------- .../Lora/scripts/lora_script.py | 21 ++++++++++++- 3 files changed, 20 insertions(+), 38 deletions(-) delete mode 100644 extensions-builtin/Lora/scripts/api.py diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 05162e41b53..ba1293dfc88 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -3,7 +3,6 @@ import re import torch from typing import Union -import scripts.api as api from modules import shared, devices, sd_models, errors, scripts @@ -449,8 +448,3 @@ def infotext_pasted(infotext, params): loaded_loras = [] list_available_loras() -try: - import modules.script_callbacks as script_callbacks - script_callbacks.on_app_started(api.api) -except: - pass \ No newline at end of file diff --git a/extensions-builtin/Lora/scripts/api.py b/extensions-builtin/Lora/scripts/api.py deleted file mode 100644 index f1f2e2fc1a5..00000000000 --- a/extensions-builtin/Lora/scripts/api.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import FastAPI -import gradio as gr -import json -import os -import lora - -def get_lora_prompts(path): - directory, filename = os.path.split(path) - name_without_ext = os.path.splitext(filename)[0] - new_filename = name_without_ext + '.civitai.info' - try: - new_path = os.path.join(directory, new_filename) - if os.path.exists(new_path): - with open(new_path, 'r') as f: - data = json.load(f) - trained_words = data.get('trainedWords', []) - if len(trained_words) > 0: - result = ','.join(trained_words) - return result - else: - return '' - else: - return '' - except Exception as e: - return '' - -def api(_: gr.Blocks, app: FastAPI): - @app.get("/sdapi/v1/loras") - async def get_loras(): - return [{"name": name, "path": lora.available_loras[name].filename, "prompt": get_lora_prompts(lora.available_loras[name].filename)} for name in lora.available_loras] - diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index a67b8a690d3..7db971fd59b 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -1,12 +1,12 @@ import torch import gradio as gr +from fastapi import FastAPI import lora import extra_networks_lora import ui_extra_networks_lora from modules import script_callbacks, ui_extra_networks, extra_networks, shared - def unload(): torch.nn.Linear.forward = torch.nn.Linear_forward_before_lora torch.nn.Linear._load_from_state_dict = torch.nn.Linear_load_state_dict_before_lora @@ -60,3 +60,22 @@ def before_ui(): shared.options_templates.update(shared.options_section(('compatibility', "Compatibility"), { "lora_functional": shared.OptionInfo(False, "Lora: use old method that takes longer when you have multiple Loras active and produces same results as kohya-ss/sd-webui-additional-networks extension"), })) + + +def create_lora_json(obj: lora.LoraOnDisk): + return { + "name": obj.name, + "alias": obj.alias, + "path": obj.filename, + "metadata": obj.metadata, + } + + +def api_loras(_: gr.Blocks, app: FastAPI): + @app.get("/sdapi/v1/loras") + async def get_loras(): + return [create_lora_json(obj) for obj in lora.available_loras.values()] + + +script_callbacks.on_app_started(api_loras) + From ad6ec0226118b80e79446f16747976a1dd1fabcd Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 9 May 2023 11:42:47 +0300 Subject: [PATCH 0122/2418] prevent Reload UI button/link from reloading the page when it's not yet ready --- javascript/ui.js | 11 ++++++++++- modules/ui.py | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/javascript/ui.js b/javascript/ui.js index 611b70d1059..ed9673d64e1 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -395,7 +395,16 @@ function update_token_counter(button_id) { function restart_reload(){ document.body.innerHTML='

    Reloading...

    '; - setTimeout(function(){location.reload()},2000) + + var requestPing = function(){ + requestGet("./internal/ping", {}, function(data){ + location.reload(); + }, function(){ + setTimeout(requestPing, 500); + }) + } + + setTimeout(requestPing, 2000); return [] } diff --git a/modules/ui.py b/modules/ui.py index 842c57f7471..34b2aaff13a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1958,3 +1958,5 @@ def quicksettings_hint(): return [QuicksettingsHint(name=k, label=v.label) for k, v in opts.data_labels.items()] app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint]) + + app.add_api_route("/internal/ping", lambda: {}, methods=["GET"]) From d1ff57e1cb602a4ebac80a25b8e3ce2424278f94 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 9 May 2023 18:14:12 +0900 Subject: [PATCH 0123/2418] 1.1.1 quicksettings list migration --- modules/shared.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/shared.py b/modules/shared.py index a8154580a60..090707cacbc 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -549,6 +549,10 @@ def load(self, filename): with open(filename, "r", encoding="utf8") as file: self.data = json.load(file) + # 1.1.1 quicksettings list migration + if self.data.get('quicksettings') is not None and self.data.get('quicksettings_list') is None: + self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] + bad_settings = 0 for k, v in self.data.items(): info = self.data_labels.get(k, None) From e7dbefc3408cc01bda613018a6c1e1364c80b63e Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Tue, 9 May 2023 19:06:00 +0800 Subject: [PATCH 0124/2418] refresh fix --- modules/ui_tempdir.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 67bfd1ec886..46fa9cb0545 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -2,7 +2,6 @@ import tempfile from collections import namedtuple from pathlib import Path -from time import time import gradio as gr @@ -35,11 +34,9 @@ def check_tmp_file(gradio, filename): def save_pil_to_file(pil_image, dir=None): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): - already_saved_as += f'?{os.path.getmtime(already_saved_as)}' - register_tmp_file(shared.demo, already_saved_as) - file_obj = Savedfile(already_saved_as) + file_obj = Savedfile(f'{already_saved_as}?{os.path.getmtime(already_saved_as)}') return file_obj if shared.opts.temp_dir != "": From 7fd3a4e6d7b1c70461eed0c8a7dc4f2412cdaf1c Mon Sep 17 00:00:00 2001 From: Micky Brunetti Date: Tue, 9 May 2023 15:35:57 +0200 Subject: [PATCH 0125/2418] files in vae folder with same name as a checkpoint can be found too --- modules/sd_vae.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 9b00f76e9c6..4d2026e1c9e 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -88,10 +88,13 @@ def refresh_vae_list(): def find_vae_near_checkpoint(checkpoint_file): - checkpoint_path = os.path.splitext(checkpoint_file)[0] - for vae_location in [checkpoint_path + ".vae.pt", checkpoint_path + ".vae.ckpt", checkpoint_path + ".vae.safetensors"]: - if os.path.isfile(vae_location): - return vae_location + checkpoint_path = os.path.basename(checkpoint_file).split('.', 1)[0] + print(f"checkpoint: {checkpoint_path}") + for vae_file in vae_dict.values(): + vae_path = os.path.basename(vae_file).split('.', 1)[0] + print(f"vae: {vae_path}") + if vae_path == checkpoint_path: + return vae_file return None From 749a93295e5259fbba2e2a849cde5a37c67aa69f Mon Sep 17 00:00:00 2001 From: Micky Brunetti Date: Tue, 9 May 2023 15:43:58 +0200 Subject: [PATCH 0126/2418] remove logs --- modules/sd_vae.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 4d2026e1c9e..17d1f7026f4 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -89,10 +89,8 @@ def refresh_vae_list(): def find_vae_near_checkpoint(checkpoint_file): checkpoint_path = os.path.basename(checkpoint_file).split('.', 1)[0] - print(f"checkpoint: {checkpoint_path}") for vae_file in vae_dict.values(): vae_path = os.path.basename(vae_file).split('.', 1)[0] - print(f"vae: {vae_path}") if vae_path == checkpoint_path: return vae_file From 81bbe31d9ff1bc53093f45a836952106798c97a3 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 10 May 2023 00:04:36 +0900 Subject: [PATCH 0127/2418] add documentation for simple installation method using release package --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 67a1a83a31d..dec6ed01626 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,12 @@ Alternatively, use online services (like Google Colab): - [List of Online Services](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Online-Services) +### Installation on Windows 10/11 with NVidia-GPUs using release package +1. Download `sd.webui.zip` from [v1.0.0-pre](https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre) and extract it's contents. +2. Run `update.bat`. +3. Run `run.bat`. +> For more details see [Install-and-Run-on-NVidia-GPUs](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) + ### Automatic Installation on Windows 1. Install [Python 3.10.6](https://www.python.org/downloads/release/python-3106/) (Newer version of Python does not support torch), checking "Add Python to PATH". 2. Install [git](https://git-scm.com/download/win). From 3ba6c3c83c0983a025c7bddc08bb7f49481b3cbb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 9 May 2023 22:17:58 +0300 Subject: [PATCH 0128/2418] Fix up string formatting/concatenation to f-strings where feasible --- modules/api/api.py | 22 ++++----- modules/call_queue.py | 5 +- modules/esrgan_model.py | 11 +++-- modules/esrgan_model_arch.py | 16 +++---- modules/extra_networks_hypernet.py | 3 +- modules/generation_parameters_copypaste.py | 4 +- modules/hashes.py | 4 +- modules/images.py | 8 ++-- modules/interrogate.py | 4 +- modules/models/diffusion/ddpm_edit.py | 4 +- modules/models/diffusion/uni_pc/uni_pc.py | 4 +- modules/ngrok.py | 4 +- modules/paths.py | 2 +- modules/processing.py | 13 +++++- modules/progress.py | 3 +- modules/realesrgan_model.py | 8 ++-- modules/scripts.py | 5 +- modules/sd_hijack_clip_old.py | 3 +- modules/sd_hijack_unet.py | 2 +- modules/sd_models.py | 4 +- modules/sd_models_config.py | 2 +- modules/sd_samplers_kdiffusion.py | 2 +- modules/sd_vae.py | 2 +- modules/styles.py | 2 +- modules/textual_inversion/autocrop.py | 6 +-- modules/textual_inversion/dataset.py | 2 +- modules/textual_inversion/preprocess.py | 6 +-- .../textual_inversion/textual_inversion.py | 12 ++--- modules/ui.py | 46 +++++++++---------- modules/ui_extensions.py | 3 +- modules/ui_extra_networks.py | 4 +- scripts/custom_code.py | 2 +- scripts/loopback.py | 2 +- scripts/xyz_grid.py | 2 +- 34 files changed, 121 insertions(+), 101 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index cdbdce32f17..9bb95dfd1a4 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -570,20 +570,20 @@ def create_embedding(self, args: dict): filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used shared.state.end() - return CreateResponse(info = "create embedding filename: {filename}".format(filename = filename)) + return CreateResponse(info=f"create embedding filename: {filename}") except AssertionError as e: shared.state.end() - return TrainResponse(info = "create embedding error: {error}".format(error = e)) + return TrainResponse(info=f"create embedding error: {e}") def create_hypernetwork(self, args: dict): try: shared.state.begin() filename = create_hypernetwork(**args) # create empty embedding shared.state.end() - return CreateResponse(info = "create hypernetwork filename: {filename}".format(filename = filename)) + return CreateResponse(info=f"create hypernetwork filename: {filename}") except AssertionError as e: shared.state.end() - return TrainResponse(info = "create hypernetwork error: {error}".format(error = e)) + return TrainResponse(info=f"create hypernetwork error: {e}") def preprocess(self, args: dict): try: @@ -593,13 +593,13 @@ def preprocess(self, args: dict): return PreprocessResponse(info = 'preprocess complete') except KeyError as e: shared.state.end() - return PreprocessResponse(info = "preprocess error: invalid token: {error}".format(error = e)) + return PreprocessResponse(info=f"preprocess error: invalid token: {e}") except AssertionError as e: shared.state.end() - return PreprocessResponse(info = "preprocess error: {error}".format(error = e)) + return PreprocessResponse(info=f"preprocess error: {e}") except FileNotFoundError as e: shared.state.end() - return PreprocessResponse(info = 'preprocess error: {error}'.format(error = e)) + return PreprocessResponse(info=f'preprocess error: {e}') def train_embedding(self, args: dict): try: @@ -617,10 +617,10 @@ def train_embedding(self, args: dict): if not apply_optimizations: sd_hijack.apply_optimizations() shared.state.end() - return TrainResponse(info = "train embedding complete: filename: {filename} error: {error}".format(filename = filename, error = error)) + return TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") except AssertionError as msg: shared.state.end() - return TrainResponse(info = "train embedding error: {msg}".format(msg = msg)) + return TrainResponse(info=f"train embedding error: {msg}") def train_hypernetwork(self, args: dict): try: @@ -641,10 +641,10 @@ def train_hypernetwork(self, args: dict): if not apply_optimizations: sd_hijack.apply_optimizations() shared.state.end() - return TrainResponse(info="train embedding complete: filename: {filename} error: {error}".format(filename=filename, error=error)) + return TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") except AssertionError as msg: shared.state.end() - return TrainResponse(info="train embedding error: {error}".format(error=error)) + return TrainResponse(info=f"train embedding error: {error}") def get_memory(self): try: diff --git a/modules/call_queue.py b/modules/call_queue.py index 1829f3a63bf..447bb7644d6 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -60,7 +60,7 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): max_debug_str_len = 131072 # (1024*1024)/8 print("Error completing request", file=sys.stderr) - argStr = f"Arguments: {str(args)} {str(kwargs)}" + argStr = f"Arguments: {args} {kwargs}" print(argStr[:max_debug_str_len], file=sys.stderr) if len(argStr) > max_debug_str_len: print(f"(Argument list truncated at {max_debug_str_len}/{len(argStr)} characters)", file=sys.stderr) @@ -73,7 +73,8 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): if extra_outputs_array is None: extra_outputs_array = [None, ''] - res = extra_outputs_array + [f"
    {html.escape(type(e).__name__+': '+str(e))}
    "] + error_message = f'{type(e).__name__}: {e}' + res = extra_outputs_array + [f"
    {html.escape(error_message)}
    "] shared.state.skipped = False shared.state.interrupted = False diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index 9a9c38f1f64..f4369257c20 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -156,13 +156,16 @@ def do_upscale(self, img, selected_model): def load_model(self, path: str): if "http" in path: - filename = load_file_from_url(url=self.model_url, model_dir=self.model_path, - file_name="%s.pth" % self.model_name, - progress=True) + filename = load_file_from_url( + url=self.model_url, + model_dir=self.model_path, + file_name=f"{self.model_name}.pth", + progress=True, + ) else: filename = path if not os.path.exists(filename) or filename is None: - print("Unable to load %s from %s" % (self.model_path, filename)) + print(f"Unable to load {self.model_path} from {filename}") return None state_dict = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None) diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py index 1b52b0f5e96..6071fea75bc 100644 --- a/modules/esrgan_model_arch.py +++ b/modules/esrgan_model_arch.py @@ -38,7 +38,7 @@ def __init__(self, in_nc, out_nc, nf, nb, nr=3, gc=32, upscale=4, norm_type=None elif upsample_mode == 'pixelshuffle': upsample_block = pixelshuffle_block else: - raise NotImplementedError('upsample mode [{:s}] is not found'.format(upsample_mode)) + raise NotImplementedError(f'upsample mode [{upsample_mode}] is not found') if upscale == 3: upsampler = upsample_block(nf, nf, 3, act_type=act_type, convtype=convtype) else: @@ -261,10 +261,10 @@ def forward(self, x): def extra_repr(self): if self.scale_factor is not None: - info = 'scale_factor=' + str(self.scale_factor) + info = f'scale_factor={self.scale_factor}' else: - info = 'size=' + str(self.size) - info += ', mode=' + self.mode + info = f'size={self.size}' + info += f', mode={self.mode}' return info @@ -350,7 +350,7 @@ def act(act_type, inplace=True, neg_slope=0.2, n_prelu=1, beta=1.0): elif act_type == 'sigmoid': # [0, 1] range output layer = nn.Sigmoid() else: - raise NotImplementedError('activation layer [{:s}] is not found'.format(act_type)) + raise NotImplementedError(f'activation layer [{act_type}] is not found') return layer @@ -372,7 +372,7 @@ def norm(norm_type, nc): elif norm_type == 'none': def norm_layer(x): return Identity() else: - raise NotImplementedError('normalization layer [{:s}] is not found'.format(norm_type)) + raise NotImplementedError(f'normalization layer [{norm_type}] is not found') return layer @@ -388,7 +388,7 @@ def pad(pad_type, padding): elif pad_type == 'zero': layer = nn.ZeroPad2d(padding) else: - raise NotImplementedError('padding layer [{:s}] is not implemented'.format(pad_type)) + raise NotImplementedError(f'padding layer [{pad_type}] is not implemented') return layer @@ -432,7 +432,7 @@ def conv_block(in_nc, out_nc, kernel_size, stride=1, dilation=1, groups=1, bias= pad_type='zero', norm_type=None, act_type='relu', mode='CNA', convtype='Conv2D', spectral_norm=False): """ Conv layer with padding, normalization, activation """ - assert mode in ['CNA', 'NAC', 'CNAC'], 'Wrong conv mode [{:s}]'.format(mode) + assert mode in ['CNA', 'NAC', 'CNAC'], f'Wrong conv mode [{mode}]' padding = get_valid_padding(kernel_size, dilation) p = pad(pad_type, padding) if pad_type and pad_type != 'zero' else None padding = padding if pad_type == 'zero' else 0 diff --git a/modules/extra_networks_hypernet.py b/modules/extra_networks_hypernet.py index 33d100dd950..04f27c9f752 100644 --- a/modules/extra_networks_hypernet.py +++ b/modules/extra_networks_hypernet.py @@ -10,7 +10,8 @@ def activate(self, p, params_list): additional = shared.opts.sd_hypernetwork if additional != "None" and additional in shared.hypernetworks and len([x for x in params_list if x.items[0] == additional]) == 0: - p.all_prompts = [x + f"" for x in p.all_prompts] + hypernet_prompt_text = f"" + p.all_prompts = [f"{prompt}{hypernet_prompt_text}" for prompt in p.all_prompts] params_list.append(extra_networks.ExtraNetworkParams(items=[additional, shared.opts.extra_networks_default_multiplier])) names = [] diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 78248ed2aa7..fe8b18b207b 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -269,8 +269,8 @@ def parse_generation_parameters(x: str): v = v[1:-1] if v[0] == '"' and v[-1] == '"' else v m = re_imagesize.match(v) if m is not None: - res[k+"-1"] = m.group(1) - res[k+"-2"] = m.group(2) + res[f"{k}-1"] = m.group(1) + res[f"{k}-2"] = m.group(2) else: res[k] = v diff --git a/modules/hashes.py b/modules/hashes.py index 83272a0787d..032120f4344 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -13,7 +13,7 @@ def dump_cache(): - with filelock.FileLock(cache_filename+".lock"): + with filelock.FileLock(f"{cache_filename}.lock"): with open(cache_filename, "w", encoding="utf8") as file: json.dump(cache_data, file, indent=4) @@ -22,7 +22,7 @@ def cache(subsection): global cache_data if cache_data is None: - with filelock.FileLock(cache_filename+".lock"): + with filelock.FileLock(f"{cache_filename}.lock"): if not os.path.isfile(cache_filename): cache_data = {} else: diff --git a/modules/images.py b/modules/images.py index 6ceb7c7c791..a41965ab6f5 100644 --- a/modules/images.py +++ b/modules/images.py @@ -467,7 +467,7 @@ def get_next_sequence_number(path, basename): """ result = -1 if basename != '': - basename = basename + "-" + basename = f"{basename}-" prefix_length = len(basename) for p in os.listdir(path): @@ -536,7 +536,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i add_number = opts.save_images_add_number or file_decoration == '' if file_decoration != "" and add_number: - file_decoration = "-" + file_decoration + file_decoration = f"-{file_decoration}" file_decoration = namegen.apply(file_decoration) + suffix @@ -566,7 +566,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i def _atomically_save_image(image_to_save, filename_without_extension, extension): # save image with .tmp extension to avoid race condition when another process detects new image in the directory - temp_file_path = filename_without_extension + ".tmp" + temp_file_path = f"{filename_without_extension}.tmp" image_format = Image.registered_extensions()[extension] if extension.lower() == '.png': @@ -626,7 +626,7 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) if opts.save_txt and info is not None: txt_fullfn = f"{fullfn_without_extension}.txt" with open(txt_fullfn, "w", encoding="utf8") as file: - file.write(info + "\n") + file.write(f"{info}\n") else: txt_fullfn = None diff --git a/modules/interrogate.py b/modules/interrogate.py index e1665708cb0..9f7d657fd20 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -28,7 +28,7 @@ def category_types(): def download_default_clip_interrogate_categories(content_dir): print("Downloading CLIP categories...") - tmpdir = content_dir + "_tmp" + tmpdir = f"{content_dir}_tmp" category_types = ["artists", "flavors", "mediums", "movements"] try: @@ -214,7 +214,7 @@ def interrogate(self, pil_image): if shared.opts.interrogate_return_ranks: res += f", ({match}:{score/100:.3f})" else: - res += ", " + match + res += f", {match}" except Exception: print("Error interrogating", file=sys.stderr) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index f3d49c44caf..f880bc3c785 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -223,7 +223,7 @@ def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): for k in keys: for ik in ignore_keys: if k.startswith(ik): - print("Deleting key {} from state_dict.".format(k)) + print(f"Deleting key {k} from state_dict.") del sd[k] missing, unexpected = self.load_state_dict(sd, strict=False) if not only_model else self.model.load_state_dict( sd, strict=False) @@ -386,7 +386,7 @@ def validation_step(self, batch, batch_idx): _, loss_dict_no_ema = self.shared_step(batch) with self.ema_scope(): _, loss_dict_ema = self.shared_step(batch) - loss_dict_ema = {key + '_ema': loss_dict_ema[key] for key in loss_dict_ema} + loss_dict_ema = {f"{key}_ema": loss_dict_ema[key] for key in loss_dict_ema} self.log_dict(loss_dict_no_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) self.log_dict(loss_dict_ema, prog_bar=False, logger=True, on_step=False, on_epoch=True) diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index eb5f4e76289..11b330bcf3c 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -94,7 +94,7 @@ def __init__( """ if schedule not in ['discrete', 'linear', 'cosine']: - raise ValueError("Unsupported noise schedule {}. The schedule needs to be 'discrete' or 'linear' or 'cosine'".format(schedule)) + raise ValueError(f"Unsupported noise schedule {schedule}. The schedule needs to be 'discrete' or 'linear' or 'cosine'") self.schedule = schedule if schedule == 'discrete': @@ -469,7 +469,7 @@ def get_time_steps(self, skip_type, t_T, t_0, N, device): t = torch.linspace(t_T**(1. / t_order), t_0**(1. / t_order), N + 1).pow(t_order).to(device) return t else: - raise ValueError("Unsupported skip_type {}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'".format(skip_type)) + raise ValueError(f"Unsupported skip_type {skip_type}, need to be 'logSNR' or 'time_uniform' or 'time_quadratic'") def get_orders_and_timesteps_for_singlestep_solver(self, steps, order, skip_type, t_T, t_0, device): """ diff --git a/modules/ngrok.py b/modules/ngrok.py index 1ad7989bb53..7a7b4b26c9d 100644 --- a/modules/ngrok.py +++ b/modules/ngrok.py @@ -7,8 +7,8 @@ def connect(token, port, region): else: if ':' in token: # token = authtoken:username:password - account = token.split(':')[1] + ':' + token.split(':')[-1] - token = token.split(':')[0] + token, username, password = token.split(':', 2) + account = f"{username}:{password}" config = conf.PyngrokConfig( auth_token=token, region=region diff --git a/modules/paths.py b/modules/paths.py index 0e1e00e729a..acf1894bafa 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -16,7 +16,7 @@ sd_path = os.path.abspath(possible_sd_path) break -assert sd_path is not None, "Couldn't find Stable Diffusion in any of: " + str(possible_sd_paths) +assert sd_path is not None, f"Couldn't find Stable Diffusion in any of: {possible_sd_paths}" path_dirs = [ (sd_path, 'ldm', 'Stable Diffusion', []), diff --git a/modules/processing.py b/modules/processing.py index e786791abbc..1a76e552922 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -500,7 +500,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter generation_params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in generation_params.items() if v is not None]) - negative_prompt_text = "\nNegative prompt: " + p.all_negative_prompts[index] if p.all_negative_prompts[index] else "" + negative_prompt_text = f"\nNegative prompt: {p.all_negative_prompts[index]}" if p.all_negative_prompts[index] else "" return f"{all_prompts[index]}{negative_prompt_text}\n{generation_params_text}".strip() @@ -780,7 +780,16 @@ def get_conds_with_caching(function, required_prompts, steps, cache): devices.torch_gc() - res = Processed(p, output_images, p.all_seeds[0], infotext(), comments="".join(["\n\n" + x for x in comments]), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, infotexts=infotexts) + res = Processed( + p, + images_list=output_images, + seed=p.all_seeds[0], + info=infotext(), + comments="".join(f"\n\n{comment}" for comment in comments), + subseed=p.all_subseeds[0], + index_of_first_image=index_of_first_image, + infotexts=infotexts, + ) if p.scripts is not None: p.scripts.postprocess(p, res) diff --git a/modules/progress.py b/modules/progress.py index 5655346bfaa..948e6f008f7 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -96,7 +96,8 @@ def progressapi(req: ProgressRequest): if image is not None: buffered = io.BytesIO() image.save(buffered, format="png") - live_preview = 'data:image/png;base64,' + base64.b64encode(buffered.getvalue()).decode("ascii") + base64_image = base64.b64encode(buffered.getvalue()).decode('ascii') + live_preview = f"data:image/png;base64,{base64_image}" id_live_preview = shared.state.id_live_preview else: live_preview = None diff --git a/modules/realesrgan_model.py b/modules/realesrgan_model.py index d6079433077..efd7fca5e50 100644 --- a/modules/realesrgan_model.py +++ b/modules/realesrgan_model.py @@ -28,9 +28,9 @@ def __init__(self, path): for scaler in scalers: if scaler.local_data_path.startswith("http"): filename = modelloader.friendly_name(scaler.local_data_path) - local = next(iter([local_model for local_model in local_model_paths if local_model.endswith(filename + '.pth')]), None) - if local: - scaler.local_data_path = local + local_model_candidates = [local_model for local_model in local_model_paths if local_model.endswith(f"{filename}.pth")] + if local_model_candidates: + scaler.local_data_path = local_model_candidates[0] if scaler.name in opts.realesrgan_enabled_models: self.scalers.append(scaler) @@ -47,7 +47,7 @@ def do_upscale(self, img, path): info = self.load_model(path) if not os.path.exists(info.local_data_path): - print("Unable to load RealESRGAN model: %s" % info.name) + print(f"Unable to load RealESRGAN model: {info.name}") return img upsampler = RealESRGANer( diff --git a/modules/scripts.py b/modules/scripts.py index 4d0bbd665b0..d945b89fbde 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -163,7 +163,8 @@ def elem_id(self, item_id): """helper function to generate id for a HTML element, constructs final id out of script name, 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 "" + tabkind = 'img2img' if self.is_img2img else 'txt2txt' + tabname = f"{tabkind}_" if need_tabname else "" title = re.sub(r'[^a-z_0-9]', '', re.sub(r'\s', '_', self.title().lower())) return f'script_{tabname}{title}_{item_id}' @@ -526,7 +527,7 @@ def add_classes_to_gradio_component(comp): this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others """ - comp.elem_classes = ["gradio-" + comp.get_block_name(), *(comp.elem_classes or [])] + comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(comp.elem_classes or [])] if getattr(comp, 'multiselect', False): comp.elem_classes.append('multiselect') diff --git a/modules/sd_hijack_clip_old.py b/modules/sd_hijack_clip_old.py index 6d9fbbe6ca1..a3476e95633 100644 --- a/modules/sd_hijack_clip_old.py +++ b/modules/sd_hijack_clip_old.py @@ -75,7 +75,8 @@ def forward_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, text self.hijack.comments += hijack_comments if len(used_custom_terms) > 0: - self.hijack.comments.append("Used embeddings: " + ", ".join([f'{word} [{checksum}]' for word, checksum in used_custom_terms])) + embedding_names = ", ".join(f"{word} [{checksum}]" for word, checksum in used_custom_terms) + self.hijack.comments.append(f"Used embeddings: {embedding_names}") self.hijack.fixes = hijack_fixes return self.process_tokens(remade_batch_tokens, batch_multipliers) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 15858263213..ca1daf45f3c 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -18,7 +18,7 @@ def __getattr__(self, item): if hasattr(torch, item): return getattr(torch, item) - raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, item)) + raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'") def cat(self, tensors, *args, **kwargs): if len(tensors) == 2: diff --git a/modules/sd_models.py b/modules/sd_models.py index 59adc7ccf40..36f643e100d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -47,7 +47,7 @@ def __init__(self, filename): self.model_name = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0] self.hash = model_hash(filename) - self.sha256 = hashes.sha256_from_cache(self.filename, "checkpoint/" + name) + self.sha256 = hashes.sha256_from_cache(self.filename, f"checkpoint/{name}") self.shorthash = self.sha256[0:10] if self.sha256 else None self.title = name if self.shorthash is None else f'{name} [{self.shorthash}]' @@ -69,7 +69,7 @@ def register(self): checkpoint_alisases[id] = self def calculate_shorthash(self): - self.sha256 = hashes.sha256(self.filename, "checkpoint/" + self.name) + self.sha256 = hashes.sha256(self.filename, f"checkpoint/{self.name}") if self.sha256 is None: return diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 9398f52846b..7a79925a3fa 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -111,7 +111,7 @@ def find_checkpoint_config_near_filename(info): if info is None: return None - config = os.path.splitext(info.filename)[0] + ".yaml" + config = f"{os.path.splitext(info.filename)[0]}.yaml" if os.path.exists(config): return config diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index eb98e599212..0fc9f456646 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -198,7 +198,7 @@ def __getattr__(self, item): if hasattr(torch, item): return getattr(torch, item) - raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, item)) + raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'") def randn_like(self, x): if self.sampler_noises: diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 9b00f76e9c6..521e485ad76 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -89,7 +89,7 @@ def refresh_vae_list(): def find_vae_near_checkpoint(checkpoint_file): checkpoint_path = os.path.splitext(checkpoint_file)[0] - for vae_location in [checkpoint_path + ".vae.pt", checkpoint_path + ".vae.ckpt", checkpoint_path + ".vae.safetensors"]: + for vae_location in [f"{checkpoint_path}.vae.pt", f"{checkpoint_path}.vae.ckpt", f"{checkpoint_path}.vae.safetensors"]: if os.path.isfile(vae_location): return vae_location diff --git a/modules/styles.py b/modules/styles.py index 9ed8599119e..11642075ff3 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -74,7 +74,7 @@ def apply_negative_styles_to_prompt(self, prompt, styles): def save_styles(self, path: str) -> None: # Always keep a backup file around if os.path.exists(path): - shutil.copy(path, path + ".bak") + shutil.copy(path, f"{path}.bak") fd = os.open(path, os.O_RDWR|os.O_CREAT) with os.fdopen(fd, "w", encoding="utf-8-sig", newline='') as file: diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index 68e1103c514..ba1bdcd4a57 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -111,7 +111,7 @@ def focal_point(im, settings): if corner_centroid is not None: color = BLUE box = corner_centroid.bounding(max_size * corner_centroid.weight) - d.text((box[0], box[1]-15), "Edge: %.02f" % corner_centroid.weight, fill=color) + d.text((box[0], box[1]-15), f"Edge: {corner_centroid.weight:.02f}", fill=color) d.ellipse(box, outline=color) if len(corner_points) > 1: for f in corner_points: @@ -119,7 +119,7 @@ def focal_point(im, settings): if entropy_centroid is not None: color = "#ff0" box = entropy_centroid.bounding(max_size * entropy_centroid.weight) - d.text((box[0], box[1]-15), "Entropy: %.02f" % entropy_centroid.weight, fill=color) + d.text((box[0], box[1]-15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color) d.ellipse(box, outline=color) if len(entropy_points) > 1: for f in entropy_points: @@ -127,7 +127,7 @@ def focal_point(im, settings): if face_centroid is not None: color = RED box = face_centroid.bounding(max_size * face_centroid.weight) - d.text((box[0], box[1]-15), "Face: %.02f" % face_centroid.weight, fill=color) + d.text((box[0], box[1]-15), f"Face: {face_centroid.weight:.02f}", fill=color) d.ellipse(box, outline=color) if len(face_points) > 1: for f in face_points: diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index af9fbcf288c..41610e03a66 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -72,7 +72,7 @@ def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_to except Exception: continue - text_filename = os.path.splitext(path)[0] + ".txt" + text_filename = f"{os.path.splitext(path)[0]}.txt" filename = os.path.basename(path) if os.path.exists(text_filename): diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 4a29151dfa4..da0bcb26ba3 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -63,9 +63,9 @@ def save_pic_with_caption(image, index, params: PreprocessParams, existing_capti image.save(os.path.join(params.dstdir, f"{basename}.png")) if params.preprocess_txt_action == 'prepend' and existing_caption: - caption = existing_caption + ' ' + caption + caption = f"{existing_caption} {caption}" elif params.preprocess_txt_action == 'append' and existing_caption: - caption = caption + ' ' + existing_caption + caption = f"{caption} {existing_caption}" elif params.preprocess_txt_action == 'copy' and existing_caption: caption = existing_caption @@ -174,7 +174,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre params.src = filename existing_caption = None - existing_caption_filename = os.path.splitext(filename)[0] + '.txt' + existing_caption_filename = f"{os.path.splitext(filename)[0]}.txt" if os.path.exists(existing_caption_filename): with open(existing_caption_filename, 'r', encoding="utf8") as file: existing_caption = file.read() diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 379df24304d..4368eb63d61 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -69,7 +69,7 @@ def save(self, filename): 'hash': self.checksum(), 'optimizer_state_dict': self.optimizer_state_dict, } - torch.save(optimizer_saved_dict, filename + '.optim') + torch.save(optimizer_saved_dict, f"{filename}.optim") def checksum(self): if self.cached_checksum is not None: @@ -437,8 +437,8 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st optimizer = torch.optim.AdamW([embedding.vec], lr=scheduler.learn_rate, weight_decay=0.0) if shared.opts.save_optimizer_state: optimizer_state_dict = None - if os.path.exists(filename + '.optim'): - optimizer_saved_dict = torch.load(filename + '.optim', map_location='cpu') + if os.path.exists(f"{filename}.optim"): + optimizer_saved_dict = torch.load(f"{filename}.optim", map_location='cpu') if embedding.checksum() == optimizer_saved_dict.get('hash', None): optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None) @@ -599,7 +599,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st data = torch.load(last_saved_file) info.add_text("sd-ti-embedding", embedding_to_b64(data)) - title = "<{}>".format(data.get('name', '???')) + title = f"<{data.get('name', '???')}>" try: vectorSize = list(data['string_to_param'].values())[0].shape[0] @@ -608,8 +608,8 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st checkpoint = sd_models.select_checkpoint() footer_left = checkpoint.model_name - footer_mid = '[{}]'.format(checkpoint.shorthash) - footer_right = '{}v {}s'.format(vectorSize, steps_done) + footer_mid = f'[{checkpoint.shorthash}]' + footer_right = f'{vectorSize}v {steps_done}s' captioned_image = caption_image_overlay(image, title, footer_left, footer_mid, footer_right) captioned_image = insert_image_data_embed(captioned_image, data) diff --git a/modules/ui.py b/modules/ui.py index 34b2aaff13a..d02f6e82c3a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -101,7 +101,7 @@ def visit(x, func, path=""): for c in x.children: visit(c, func, path) elif x.label is not None: - func(path + "/" + str(x.label), x) + func(f"{path}/{x.label}", x) def add_style(name: str, prompt: str, negative_prompt: str): @@ -166,7 +166,7 @@ def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_di img = Image.open(image) filename = os.path.basename(image) left, _ = os.path.splitext(filename) - print(interrogation_function(img), file=open(os.path.join(ii_output_dir, left + ".txt"), 'a')) + print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a')) return [gr.update(), None] @@ -182,29 +182,29 @@ def interrogate_deepbooru(image): def create_seed_inputs(target_interface): - with FormRow(elem_id=target_interface + '_seed_row', variant="compact"): - seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1, elem_id=target_interface + '_seed') + with FormRow(elem_id=f"{target_interface}_seed_row", variant="compact"): + seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1, elem_id=f"{target_interface}_seed") seed.style(container=False) - random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed', label='Random seed') - reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed', label='Reuse seed') + 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=target_interface + '_subseed_show', value=False) + 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=target_interface + '_subseed_row') as seed_extra_row_1: + 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=target_interface + '_subseed') + subseed = gr.Number(label='Variation seed', value=-1, elem_id=f"{target_interface}_subseed") subseed.style(container=False) - random_subseed = ToolButton(random_symbol, elem_id=target_interface + '_random_subseed') - reuse_subseed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_subseed') - subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=target_interface + '_subseed_strength') + 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=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=target_interface + '_seed_resize_from_h') + 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=lambda: -1, show_progress=False, inputs=[], outputs=[seed]) random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) @@ -765,7 +765,7 @@ def copy_image(img): ) button.click( fn=lambda: None, - _js="switch_to_"+name.replace(" ", "_"), + _js=f"switch_to_{name.replace(' ', '_')}", inputs=[], outputs=[], ) @@ -1462,18 +1462,18 @@ def fun(): elif t == bool: comp = gr.Checkbox else: - raise Exception(f'bad options item type: {str(t)} for key {key}') + raise Exception(f'bad options item type: {t} for key {key}') - elem_id = "setting_"+key + elem_id = f"setting_{key}" if info.refresh is not None: if is_quicksettings: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) - create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) + create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") else: with FormRow(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) - create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) + create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") else: res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {})) @@ -1545,7 +1545,7 @@ def run_settings_single(value, key): current_tab.__exit__() gr.Group() - current_tab = gr.TabItem(elem_id="settings_{}".format(elem_id), label=text) + current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text) current_tab.__enter__() current_row = gr.Column(variant='compact') current_row.__enter__() @@ -1664,7 +1664,7 @@ def request_restart(): for interface, label, ifid in interfaces: if label in shared.opts.hidden_tabs: continue - with gr.TabItem(label, id=ifid, elem_id='tab_' + ifid): + with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"): interface.render() if os.path.exists(os.path.join(script_path, "notification.mp3")): @@ -1771,10 +1771,10 @@ def modelmerger(*args): def loadsave(path, x): def apply_field(obj, field, condition=None, init_field=None): - key = path + "/" + field + key = f"{path}/{field}" if getattr(obj, 'custom_script_source', None) is not None: - key = 'customscript/' + obj.custom_script_source + '/' + key + key = f"customscript/{obj.custom_script_source}/{key}" if getattr(obj, 'do_not_save_to_config', False): return diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 99ac8756854..d9faf85a314 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -61,7 +61,8 @@ def save_config_state(name): if not name: name = "Config" current_config_state["name"] = name - filename = os.path.join(config_states_dir, datetime.now().strftime("%Y_%m_%d-%H_%M_%S") + "_" + name + ".json") + timestamp = datetime.now().strftime('%Y_%m_%d-%H_%M_%S') + filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") print(f"Saving backup of webui/extension state to {filename}.") with open(filename, "w", encoding="utf-8") as f: json.dump(current_config_state, f) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 86c05a55140..8c3dea5643e 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -69,7 +69,9 @@ def refresh(self): pass def link_preview(self, filename): - return "./sd_extra_networks/thumb?filename=" + urllib.parse.quote(filename.replace('\\', '/')) + "&mtime=" + str(os.path.getmtime(filename)) + quoted_filename = urllib.parse.quote(filename.replace('\\', '/')) + mtime = os.path.getmtime(filename) + return f"./sd_extra_networks/thumb?filename={quoted_filename}&mtime={mtime}" def search_terms_from_path(self, filename, possible_directories=None): abspath = os.path.abspath(filename) diff --git a/scripts/custom_code.py b/scripts/custom_code.py index 4071d86d814..f36a36754b0 100644 --- a/scripts/custom_code.py +++ b/scripts/custom_code.py @@ -77,7 +77,7 @@ def display(imgs, s=display_result_data[1], i=display_result_data[2]): module.display = display indent = " " * indent_level - indented = code.replace('\n', '\n' + indent) + indented = code.replace('\n', f"\n{indent}") body = f"""def __webuitemp__(): {indent}{indented} __webuitemp__()""" diff --git a/scripts/loopback.py b/scripts/loopback.py index d3065fe6b52..ad6609be943 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -84,7 +84,7 @@ def calculate_denoising_strength(loop): p.color_corrections = initial_color_corrections if append_interrogation != "None": - p.prompt = original_prompt + ", " if original_prompt != "" else "" + p.prompt = f"{original_prompt}, " if original_prompt else "" if append_interrogation == "CLIP": p.prompt += shared.interrogator.interrogate(p.init_images[0]) elif append_interrogation == "DeepBooru": diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 01d97791e11..a725d74a3b3 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -439,7 +439,7 @@ def select_axis(axis_type,axis_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 = axis + " Values" + val_key = f"{axis} Values" 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) From 31397986e70d20e392d9c3ec70d3aef8ecc2c1ff Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 9 May 2023 22:42:02 +0300 Subject: [PATCH 0129/2418] changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c56d3a0edcb..95234a9895e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * Lora: read infotext params from kohya-ss's extension parameters if they are present and if his extension is not active * Lora: Fix some Loras not working (ones that have 3x3 convolution layer) * Lora: add an option to use old method of applying loras (producing same results as with kohya-ss) + * add version to infotext, footer and console output when starting + * add links to wiki for filename pattern settings + * add extended info for quicksettings setting and use multiselect input instead of a text field ### Minor: * --subpath option for gradio for use with reverse proxy @@ -17,9 +20,13 @@ * print error to console when batch processing in img2img fails * create HTML for extra network pages only on demand * allow directories starting with . to still list their models for lora, checkpoints, etc + * put infotext options into their own category in settings tab + * do not show licenses page when user selects Show all pages in settings + * ### Extensions: * Tooltip localization support + * Add api method to get LoRA models with prompt ### Bug Fixes: * re-add /docs endpoint @@ -28,6 +35,10 @@ * fix squished thumbnails in extras tab * keep "search" filter for extra networks when user refreshes the tab (previously it showed everthing after you refreshed) * fix webui showing the same image if you configure the generation to always save results into same file + * fix bug with upscalers not working properly + * Fix MPS on PyTorch 2.0.1, Intel Macs + * make it so that custom context menu from contextMenu.js only disappears after user's click, ignoring non-user click events + * prevent Reload UI button/link from reloading the page when it's not yet ready ## 1.1.1 From 990ca80cb64532368f88d2037bba31167314263d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 9 May 2023 23:02:44 +0300 Subject: [PATCH 0130/2418] Replace pylint CI with ruff --- .github/workflows/on_pull_request.yaml | 43 +++++++++++++++----------- ruff.toml | 10 ++++++ 2 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 ruff.toml diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index a168be5b8be..d42965b15c9 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -18,22 +18,29 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v4 + - uses: actions/setup-python@v4 with: - python-version: 3.10.6 - cache: pip - cache-dependency-path: | - **/requirements*txt - - name: Install PyLint - run: | - python -m pip install --upgrade pip - pip install pylint - # This lets PyLint check to see if it can resolve imports - - name: Install dependencies - run: | - export COMMANDLINE_ARGS="--skip-torch-cuda-test --exit" - python launch.py - - name: Analysing the code with pylint - run: | - pylint $(git ls-files '*.py') + python-version: 3.11 + # NB: there's no cache: pip here since we're not installing anything + # from the requirements.txt file(s) in the repository; it's faster + # not to have GHA download an (at the time of writing) 4 GB cache + # of PyTorch and other dependencies. + - name: Install Ruff + run: pip install ruff==0.0.265 + - name: Run Ruff + run: ruff . + +# The rest are currently disabled pending fixing of e.g. installing the torch dependency. + +# - name: Install PyLint +# run: | +# python -m pip install --upgrade pip +# pip install pylint +# # This lets PyLint check to see if it can resolve imports +# - name: Install dependencies +# run: | +# export COMMANDLINE_ARGS="--skip-torch-cuda-test --exit" +# python launch.py +# - name: Analysing the code with pylint +# run: | +# pylint $(git ls-files '*.py') diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000000..5bb28a74822 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,10 @@ +target-version = "py310" + +select = [ + "E999", # syntax error +] + +extend-exclude = [ + "extensions", + "extensions-disabled", +] From f07af8db6489544ce593bef2265146a4c829e67b Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 06:52:51 +0300 Subject: [PATCH 0131/2418] bump gradio version for all suffering musicians --- CHANGELOG.md | 1 + requirements.txt | 2 +- requirements_versions.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95234a9895e..63a6093d915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * add extended info for quicksettings setting and use multiselect input instead of a text field ### Minor: + * gradio bumped to 3.29.0 * --subpath option for gradio for use with reverse proxy * linux/OSX: use existing virtualenv if already active (the VIRTUAL_ENV environment variable) * possible frontend optimization: do not apply localizations if there are none diff --git a/requirements.txt b/requirements.txt index c08c82d7eab..35987a13595 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ basicsr fonts font-roboto gfpgan -gradio==3.28.1 +gradio==3.29.0 numpy omegaconf opencv-contrib-python diff --git a/requirements_versions.txt b/requirements_versions.txt index cfdce216073..7bce02e5f10 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.18.0 basicsr==1.4.2 gfpgan==1.3.8 -gradio==3.28.1 +gradio==3.29.0 numpy==1.23.5 Pillow==9.4.0 realesrgan==0.3.0 From d50b95b5a32b917ded123891dce3c8a018fca064 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 07:14:13 +0300 Subject: [PATCH 0132/2418] fix an issue preventing the program from starting if the user specifies a bad gradio theme --- modules/shared.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 090707cacbc..4631965b4c0 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -672,8 +672,8 @@ def reload_gradio_theme(theme_name=None): else: try: gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) - except requests.exceptions.ConnectionError: - print("Can't access HuggingFace Hub, falling back to default Gradio theme") + except Exception as e: + errors.display(e, "changing gradio theme") gradio_theme = gr.themes.Default() From f5ea1e9d928e0d45b3ebcd8ddd1cacbc6a96e184 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 07:26:31 +0300 Subject: [PATCH 0133/2418] bump torch version --- CHANGELOG.md | 2 +- launch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a6093d915..cf3fef3d0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Minor: * gradio bumped to 3.29.0 + * torch bumped to 2.0.1 * --subpath option for gradio for use with reverse proxy * linux/OSX: use existing virtualenv if already active (the VIRTUAL_ENV environment variable) * possible frontend optimization: do not apply localizations if there are none @@ -23,7 +24,6 @@ * allow directories starting with . to still list their models for lora, checkpoints, etc * put infotext options into their own category in settings tab * do not show licenses page when user selects Show all pages in settings - * ### Extensions: * Tooltip localization support diff --git a/launch.py b/launch.py index 2a33adc8e4f..cfc0cffa26a 100644 --- a/launch.py +++ b/launch.py @@ -237,7 +237,7 @@ def run_extensions_installers(settings_file): def prepare_environment(): - torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.0 torchvision==0.15.1 --extra-index-url https://download.pytorch.org/whl/cu118") + torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url https://download.pytorch.org/whl/cu118") requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') From a617d6488275a58da0627b3fed5f53593b2eb8b2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 07:43:55 +0300 Subject: [PATCH 0134/2418] add ruff config --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..9e9662ad45a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.ruff] + +ignore = [ + "E501", + "E731" +] + +exclude = ["extensions"] From 762265eab58cdb8f2d6398769bab43d8b8db0075 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 07:52:45 +0300 Subject: [PATCH 0135/2418] autofixes from ruff --- extensions-builtin/LDSR/ldsr_model_arch.py | 1 - extensions-builtin/LDSR/sd_hijack_autoencoder.py | 2 +- modules/api/api.py | 14 +++++++------- modules/extras.py | 4 ++-- modules/images.py | 4 ++-- modules/img2img.py | 2 +- modules/prompt_parser.py | 2 +- modules/realesrgan_model.py | 2 +- modules/sd_disable_initialization.py | 2 +- modules/sd_hijack.py | 4 ++-- modules/sd_hijack_ip2p.py | 2 +- modules/sd_hijack_optimizations.py | 1 - modules/sd_models.py | 6 +++--- modules/textual_inversion/textual_inversion.py | 2 +- modules/ui.py | 13 ++++++------- modules/ui_extensions.py | 2 +- modules/ui_extra_networks.py | 2 +- pyproject.toml | 4 +++- scripts/outpainting_mk_2.py | 2 +- scripts/postprocessing_upscale.py | 6 +++--- scripts/xyz_grid.py | 2 +- webui.py | 2 +- 22 files changed, 40 insertions(+), 41 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index bc11cc6e482..2339de7f44a 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -110,7 +110,6 @@ def super_resolution(self, image, steps=100, target_scale=2, half_attention=Fals diffusion_steps = int(steps) eta = 1.0 - down_sample_method = 'Lanczos' gc.collect() if torch.cuda.is_available: diff --git a/extensions-builtin/LDSR/sd_hijack_autoencoder.py b/extensions-builtin/LDSR/sd_hijack_autoencoder.py index 8e03c7f8989..db2231dd312 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -165,7 +165,7 @@ def training_step(self, batch, batch_idx, optimizer_idx): def validation_step(self, batch, batch_idx): log_dict = self._validation_step(batch, batch_idx) with self.ema_scope(): - log_dict_ema = self._validation_step(batch, batch_idx, suffix="_ema") + self._validation_step(batch, batch_idx, suffix="_ema") return log_dict def _validation_step(self, batch, batch_idx, suffix=""): diff --git a/modules/api/api.py b/modules/api/api.py index 9bb95dfd1a4..d47c39fccda 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -60,7 +60,7 @@ def decode_base64_to_image(encoding): try: image = Image.open(BytesIO(base64.b64decode(encoding))) return image - except Exception as err: + except Exception: raise HTTPException(status_code=500, detail="Invalid encoded image") def encode_pil_to_base64(image): @@ -264,11 +264,11 @@ def init_script_args(self, request, default_script_args, selectable_scripts, sel if request.alwayson_scripts and (len(request.alwayson_scripts) > 0): for alwayson_script_name in request.alwayson_scripts.keys(): alwayson_script = self.get_script(alwayson_script_name, script_runner) - if alwayson_script == None: + if alwayson_script is None: raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") # Selectable script in always on script param check - if alwayson_script.alwayson == False: - raise HTTPException(status_code=422, detail=f"Cannot have a selectable script in the always on scripts params") + if alwayson_script.alwayson is False: + raise HTTPException(status_code=422, detail="Cannot have a selectable script in the always on scripts params") # always on script with no arg should always run so you don't really need to add them to the requests if "args" in request.alwayson_scripts[alwayson_script_name]: # min between arg length in scriptrunner and arg length in the request @@ -310,7 +310,7 @@ def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): p.outpath_samples = opts.outdir_txt2img_samples shared.state.begin() - if selectable_scripts != None: + if selectable_scripts is not None: p.script_args = script_args processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here else: @@ -367,7 +367,7 @@ def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): p.outpath_samples = opts.outdir_img2img_samples shared.state.begin() - if selectable_scripts != None: + if selectable_scripts is not None: p.script_args = script_args processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here else: @@ -642,7 +642,7 @@ def train_hypernetwork(self, args: dict): sd_hijack.apply_optimizations() shared.state.end() return TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") - except AssertionError as msg: + except AssertionError: shared.state.end() return TrainResponse(info=f"train embedding error: {error}") diff --git a/modules/extras.py b/modules/extras.py index ff4e9c4e57a..eb4f0b420bd 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -136,14 +136,14 @@ def filename_nothing(): result_is_instruct_pix2pix_model = False if theta_func2: - shared.state.textinfo = f"Loading B" + shared.state.textinfo = "Loading B" print(f"Loading {secondary_model_info.filename}...") theta_1 = sd_models.read_state_dict(secondary_model_info.filename, map_location='cpu') else: theta_1 = None if theta_func1: - shared.state.textinfo = f"Loading C" + shared.state.textinfo = "Loading C" print(f"Loading {tertiary_model_info.filename}...") theta_2 = sd_models.read_state_dict(tertiary_model_info.filename, map_location='cpu') diff --git a/modules/images.py b/modules/images.py index a41965ab6f5..3d5d76ccc18 100644 --- a/modules/images.py +++ b/modules/images.py @@ -409,13 +409,13 @@ def datetime(self, *args): time_format = args[0] if len(args) > 0 and args[0] != "" else self.default_time_format try: time_zone = pytz.timezone(args[1]) if len(args) > 1 else None - except pytz.exceptions.UnknownTimeZoneError as _: + except pytz.exceptions.UnknownTimeZoneError: time_zone = None time_zone_time = time_datetime.astimezone(time_zone) try: formatted_time = time_zone_time.strftime(time_format) - except (ValueError, TypeError) as _: + except (ValueError, TypeError): formatted_time = time_zone_time.strftime(self.default_time_format) return sanitize_filename_part(formatted_time, replace_spaces=False) diff --git a/modules/img2img.py b/modules/img2img.py index 9fc3a698e88..cdae301ae7b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -59,7 +59,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): # try to find corresponding mask for an image using simple filename matching mask_image_path = os.path.join(inpaint_mask_dir, os.path.basename(image)) # if not found use first one ("same mask for all images" use-case) - if not mask_image_path in inpaint_masks: + if mask_image_path not in inpaint_masks: mask_image_path = inpaint_masks[0] mask_image = Image.open(mask_image_path) p.image_mask = mask_image diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 696653725fb..e084e948b0f 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -92,7 +92,7 @@ def __default__(self, data, children, meta): def get_schedule(prompt): try: tree = schedule_parser.parse(prompt) - except lark.exceptions.LarkError as e: + except lark.exceptions.LarkError: if 0: import traceback traceback.print_exc() diff --git a/modules/realesrgan_model.py b/modules/realesrgan_model.py index efd7fca5e50..9ec1adf2a1a 100644 --- a/modules/realesrgan_model.py +++ b/modules/realesrgan_model.py @@ -134,6 +134,6 @@ def get_realesrgan_models(scaler): ), ] return models - except Exception as e: + except Exception: print("Error making Real-ESRGAN models list:", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) diff --git a/modules/sd_disable_initialization.py b/modules/sd_disable_initialization.py index c4a09d15da1..9fc89dc6a75 100644 --- a/modules/sd_disable_initialization.py +++ b/modules/sd_disable_initialization.py @@ -61,7 +61,7 @@ def transformers_utils_hub_get_file_from_cache(original, url, *args, **kwargs): if res is None: res = original(url, *args, local_files_only=False, **kwargs) return res - except Exception as e: + except Exception: return original(url, *args, local_files_only=False, **kwargs) def transformers_utils_hub_get_from_cache(url, *args, local_files_only=False, **kwargs): diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index f4bb0266fa6..d8135211688 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -118,7 +118,7 @@ def weighted_forward(sd_model, x, c, w, *args, **kwargs): try: #Delete temporary weights if appended del sd_model._custom_loss_weight - except AttributeError as e: + except AttributeError: pass #If we have an old loss function, reset the loss function to the original one @@ -133,7 +133,7 @@ def apply_weighted_forward(sd_model): def undo_weighted_forward(sd_model): try: del sd_model.weighted_forward - except AttributeError as e: + except AttributeError: pass diff --git a/modules/sd_hijack_ip2p.py b/modules/sd_hijack_ip2p.py index 3c727d3b753..41ed54a296c 100644 --- a/modules/sd_hijack_ip2p.py +++ b/modules/sd_hijack_ip2p.py @@ -10,4 +10,4 @@ def should_hijack_ip2p(checkpoint_info): ckpt_basename = os.path.basename(checkpoint_info.filename).lower() cfg_basename = os.path.basename(sd_models_config.find_checkpoint_config_near_filename(checkpoint_info)).lower() - return "pix2pix" in ckpt_basename and not "pix2pix" in cfg_basename + return "pix2pix" in ckpt_basename and "pix2pix" not in cfg_basename diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index f10865cd1e7..b623d53d146 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -296,7 +296,6 @@ def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_ if chunk_threshold_bytes is not None and qk_matmul_size_bytes <= chunk_threshold_bytes: # the big matmul fits into our memory limit; do everything in 1 chunk, # i.e. send it down the unchunked fast-path - query_chunk_size = q_tokens kv_chunk_size = k_tokens with devices.without_autocast(disable=q.dtype == v.dtype): diff --git a/modules/sd_models.py b/modules/sd_models.py index 36f643e100d..11c1a344e4c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -239,7 +239,7 @@ def read_metadata_from_safetensors(filename): if isinstance(v, str) and v[0:1] == '{': try: res[k] = json.loads(v) - except Exception as e: + except Exception: pass return res @@ -467,7 +467,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): try: with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): sd_model = instantiate_from_config(sd_config.model) - except Exception as e: + except Exception: pass if sd_model is None: @@ -544,7 +544,7 @@ def reload_model_weights(sd_model=None, info=None): try: load_model_weights(sd_model, checkpoint_info, state_dict, timer) - except Exception as e: + except Exception: print("Failed to load checkpoint, restoring previous") load_model_weights(sd_model, current_checkpoint_info, None, timer) raise diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 4368eb63d61..f753b75f74b 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -603,7 +603,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st try: vectorSize = list(data['string_to_param'].values())[0].shape[0] - except Exception as e: + except Exception: vectorSize = '?' checkpoint = sd_models.select_checkpoint() diff --git a/modules/ui.py b/modules/ui.py index d02f6e82c3a..2171f3aaf72 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -246,7 +246,7 @@ def copy_seed(gen_info_string: str, index): all_seeds = gen_info.get('all_seeds', [-1]) res = all_seeds[index if 0 <= index < len(all_seeds) else 0] - except json.decoder.JSONDecodeError as e: + except json.decoder.JSONDecodeError: if gen_info_string != '': print("Error parsing JSON generation info:", file=sys.stderr) print(gen_info_string, file=sys.stderr) @@ -736,8 +736,8 @@ def update_orig(image, state): with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: hidden = '
    Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' gr.HTML( - f"

    Process images in a directory on the same machine where the server is running." + - f"
    Use an empty output directory to save pictures normally instead of writing to the output directory." + + "

    Process images in a directory on the same machine where the server is running." + + "
    Use an empty output directory to save pictures normally instead of writing to the output directory." + f"
    Add inpaint batch mask directory to enable inpaint batch processing." f"{hidden}

    " ) @@ -746,7 +746,6 @@ def update_orig(image, state): img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch] - img2img_image_inputs = [init_img, sketch, init_img_with_mask, inpaint_color_sketch] for i, tab in enumerate(img2img_tabs): tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab]) @@ -1290,8 +1289,8 @@ def get_textual_inversion_template_names(): with gr.Column(elem_id='ti_gallery_container'): ti_output = gr.Text(elem_id="ti_output", value="", show_label=False) - ti_gallery = gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(columns=4) - ti_progress = gr.HTML(elem_id="ti_progress", value="") + gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(columns=4) + gr.HTML(elem_id="ti_progress", value="") ti_outcome = gr.HTML(elem_id="ti_error", value="") create_embedding.click( @@ -1668,7 +1667,7 @@ def request_restart(): interface.render() if os.path.exists(os.path.join(script_path, "notification.mp3")): - audio_notification = gr.Audio(interactive=False, value=os.path.join(script_path, "notification.mp3"), elem_id="audio_notification", visible=False) + gr.Audio(interactive=False, value=os.path.join(script_path, "notification.mp3"), elem_id="audio_notification", visible=False) footer = shared.html("footer.html") footer = footer.format(versions=versions_html()) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index d9faf85a314..ed70abe5868 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -490,7 +490,7 @@ def create_ui(): config_states.list_config_states() with gr.Blocks(analytics_enabled=False) as ui: - with gr.Tabs(elem_id="tabs_extensions") as tabs: + with gr.Tabs(elem_id="tabs_extensions"): with gr.TabItem("Installed", id="installed"): with gr.Row(elem_id="extensions_installed_top"): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 8c3dea5643e..49e06289814 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -263,7 +263,7 @@ def create_ui(container, button, tabname): ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy()) ui.tabname = tabname - with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs: + with gr.Tabs(elem_id=tabname+"_extra_tabs"): for page in ui.stored_extra_pages: page_id = page.title.lower().replace(" ", "_") diff --git a/pyproject.toml b/pyproject.toml index 9e9662ad45a..1e164abc206 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,9 @@ ignore = [ "E501", - "E731" + "E731", + "E402", # Module level import not at top of file + "F401" # Module imported but unused ] exclude = ["extensions"] diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 670bb8ace35..b10fed6cf3c 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -72,7 +72,7 @@ def _get_masked_window_rgb(np_mask_grey, hardness=1.): height = _np_src_image.shape[1] num_channels = _np_src_image.shape[2] - np_src_image = _np_src_image[:] * (1. - np_mask_rgb) + _np_src_image[:] * (1. - np_mask_rgb) np_mask_grey = (np.sum(np_mask_rgb, axis=2) / 3.) img_mask = np_mask_grey > 1e-6 ref_mask = np_mask_grey < 1e-3 diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index ef1186ac620..edb70ac01ca 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -98,13 +98,13 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}' upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) - pp.info[f"Postprocess upscaler"] = upscaler1.name + pp.info["Postprocess upscaler"] = upscaler1.name if upscaler2 and upscaler_2_visibility > 0: second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) - pp.info[f"Postprocess upscaler 2"] = upscaler2.name + pp.info["Postprocess upscaler 2"] = upscaler2.name pp.image = upscaled_image @@ -134,4 +134,4 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_by=2.0, assert upscaler1, f'could not find upscaler named {upscaler_name}' pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, False) - pp.info[f"Postprocess upscaler"] = upscaler1.name + pp.info["Postprocess upscaler"] = upscaler1.name diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index a725d74a3b3..2ff42ef8217 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -316,7 +316,7 @@ def index(ix, iy, iz): return Processed(p, []) z_count = len(zs) - sub_grids = [None] * z_count + for i in range(z_count): start_index = (i * len(xs) * len(ys)) + i end_index = start_index + len(xs) * len(ys) diff --git a/webui.py b/webui.py index 727ebd31d10..ec3d2aba152 100644 --- a/webui.py +++ b/webui.py @@ -360,7 +360,7 @@ def fastapi_setup(self): if cmd_opts.subpath: redirector = FastAPI() redirector.get("/") - mounted_app = gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}") + gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}") wait_on_server(shared.demo) print('Restarting UI...') From 96d6ca4199e7c5eee8d451618de5161cea317c40 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 08:25:25 +0300 Subject: [PATCH 0136/2418] manual fixes for ruff --- extensions-builtin/LDSR/ldsr_model_arch.py | 2 +- extensions-builtin/LDSR/scripts/ldsr_model.py | 3 +- .../LDSR/sd_hijack_autoencoder.py | 10 +- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 26 ++-- .../ScuNET/scunet_model_arch.py | 9 +- .../SwinIR/scripts/swinir_model.py | 2 +- modules/api/api.py | 129 +++++++++--------- modules/api/models.py | 5 +- modules/codeformer/codeformer_arch.py | 2 +- modules/esrgan_model_arch.py | 2 + modules/extra_networks_hypernet.py | 2 +- modules/images.py | 4 +- modules/img2img.py | 1 - modules/interrogate.py | 1 - modules/modelloader.py | 6 +- modules/models/diffusion/ddpm_edit.py | 26 ++-- modules/models/diffusion/uni_pc/sampler.py | 3 +- modules/processing.py | 2 +- modules/prompt_parser.py | 11 +- modules/textual_inversion/autocrop.py | 2 +- modules/ui.py | 8 +- modules/upscaler.py | 2 +- 22 files changed, 129 insertions(+), 129 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index 2339de7f44a..a5fb8907d61 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -243,7 +243,7 @@ def make_convolutional_sample(batch, model, custom_steps=None, eta=1.0, quantize x_sample_noquant = model.decode_first_stage(sample, force_not_quantize=True) log["sample_noquant"] = x_sample_noquant log["sample_diff"] = torch.abs(x_sample_noquant - x_sample) - except: + except Exception: pass log["sample"] = x_sample diff --git a/extensions-builtin/LDSR/scripts/ldsr_model.py b/extensions-builtin/LDSR/scripts/ldsr_model.py index da19cff124c..e8dc083c58c 100644 --- a/extensions-builtin/LDSR/scripts/ldsr_model.py +++ b/extensions-builtin/LDSR/scripts/ldsr_model.py @@ -7,7 +7,8 @@ from modules.upscaler import Upscaler, UpscalerData from ldsr_model_arch import LDSR from modules import shared, script_callbacks -import sd_hijack_autoencoder, sd_hijack_ddpm_v1 +import sd_hijack_autoencoder +import sd_hijack_ddpm_v1 class UpscalerLDSR(Upscaler): diff --git a/extensions-builtin/LDSR/sd_hijack_autoencoder.py b/extensions-builtin/LDSR/sd_hijack_autoencoder.py index db2231dd312..6303fed545d 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -1,16 +1,21 @@ # The content of this file comes from the ldm/models/autoencoder.py file of the compvis/stable-diffusion repo # The VQModel & VQModelInterface were subsequently removed from ldm/models/autoencoder.py when we moved to the stability-ai/stablediffusion repo # As the LDSR upscaler relies on VQModel & VQModelInterface, the hijack aims to put them back into the ldm.models.autoencoder - +import numpy as np import torch import pytorch_lightning as pl import torch.nn.functional as F from contextlib import contextmanager + +from torch.optim.lr_scheduler import LambdaLR + +from ldm.modules.ema import LitEma from taming.modules.vqvae.quantize import VectorQuantizer2 as VectorQuantizer from ldm.modules.diffusionmodules.model import Encoder, Decoder from ldm.util import instantiate_from_config import ldm.models.autoencoder +from packaging import version class VQModel(pl.LightningModule): def __init__(self, @@ -249,7 +254,8 @@ def log_images(self, batch, only_inputs=False, plot_ema=False, **kwargs): if plot_ema: with self.ema_scope(): xrec_ema, _ = self(x) - if x.shape[1] > 3: xrec_ema = self.to_rgb(xrec_ema) + if x.shape[1] > 3: + xrec_ema = self.to_rgb(xrec_ema) log["reconstructions_ema"] = xrec_ema return log diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 5c0488e5f6f..4d3f6c56ada 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -450,7 +450,7 @@ def __init__(self, self.cond_stage_key = cond_stage_key try: self.num_downs = len(first_stage_config.params.ddconfig.ch_mult) - 1 - except: + except Exception: self.num_downs = 0 if not scale_by_std: self.scale_factor = scale_factor @@ -877,16 +877,6 @@ def forward(self, x, c, *args, **kwargs): c = self.q_sample(x_start=c, t=tc, noise=torch.randn_like(c.float())) return self.p_losses(x, c, t, *args, **kwargs) - def _rescale_annotations(self, bboxes, crop_coordinates): # TODO: move to dataset - def rescale_bbox(bbox): - x0 = clamp((bbox[0] - crop_coordinates[0]) / crop_coordinates[2]) - y0 = clamp((bbox[1] - crop_coordinates[1]) / crop_coordinates[3]) - w = min(bbox[2] / crop_coordinates[2], 1 - x0) - h = min(bbox[3] / crop_coordinates[3], 1 - y0) - return x0, y0, w, h - - return [rescale_bbox(b) for b in bboxes] - def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): @@ -1157,8 +1147,10 @@ def progressive_denoising(self, cond, shape, verbose=True, callback=None, quanti if i % log_every_t == 0 or i == timesteps - 1: intermediates.append(x0_partial) - if callback: callback(i) - if img_callback: img_callback(img, i) + if callback: + callback(i) + if img_callback: + img_callback(img, i) return img, intermediates @torch.no_grad() @@ -1205,8 +1197,10 @@ def p_sample_loop(self, cond, shape, return_intermediates=False, if i % log_every_t == 0 or i == timesteps - 1: intermediates.append(img) - if callback: callback(i) - if img_callback: img_callback(img, i) + if callback: + callback(i) + if img_callback: + img_callback(img, i) if return_intermediates: return img, intermediates @@ -1322,7 +1316,7 @@ def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=200, ddim_eta= if inpaint: # make a simple center square - b, h, w = z.shape[0], z.shape[2], z.shape[3] + h, w = z.shape[2], z.shape[3] mask = torch.ones(N, h, w).to(self.device) # zeros will be filled in mask[:, h // 4:3 * h // 4, w // 4:3 * w // 4] = 0. diff --git a/extensions-builtin/ScuNET/scunet_model_arch.py b/extensions-builtin/ScuNET/scunet_model_arch.py index 43ca8d36fe5..8028918afdb 100644 --- a/extensions-builtin/ScuNET/scunet_model_arch.py +++ b/extensions-builtin/ScuNET/scunet_model_arch.py @@ -61,7 +61,9 @@ def forward(self, x): Returns: output: tensor shape [b h w c] """ - if self.type != 'W': x = torch.roll(x, shifts=(-(self.window_size // 2), -(self.window_size // 2)), dims=(1, 2)) + if self.type != 'W': + x = torch.roll(x, shifts=(-(self.window_size // 2), -(self.window_size // 2)), dims=(1, 2)) + x = rearrange(x, 'b (w1 p1) (w2 p2) c -> b w1 w2 p1 p2 c', p1=self.window_size, p2=self.window_size) h_windows = x.size(1) w_windows = x.size(2) @@ -85,8 +87,9 @@ def forward(self, x): output = self.linear(output) output = rearrange(output, 'b (w1 w2) (p1 p2) c -> b (w1 p1) (w2 p2) c', w1=h_windows, p1=self.window_size) - if self.type != 'W': output = torch.roll(output, shifts=(self.window_size // 2, self.window_size // 2), - dims=(1, 2)) + if self.type != 'W': + output = torch.roll(output, shifts=(self.window_size // 2, self.window_size // 2), dims=(1, 2)) + return output def relative_embedding(self): diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index e8783bca153..d77c3a923b5 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -45,7 +45,7 @@ def do_upscale(self, img, model_file): img = upscale(img, model) try: torch.cuda.empty_cache() - except: + except Exception: pass return img diff --git a/modules/api/api.py b/modules/api/api.py index d47c39fccda..f52d371b750 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -15,7 +15,8 @@ import modules.shared as shared from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing -from modules.api.models import * +from modules.api import models +from modules.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.textual_inversion.textual_inversion import create_embedding, train_embedding from modules.textual_inversion.preprocess import preprocess @@ -25,20 +26,21 @@ from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices -from typing import List +from typing import Dict, List, Any import piexif import piexif.helper + def upscaler_to_index(name: str): try: return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) - except: - raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in sd_upscalers])}") + except Exception: + raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in shared.sd_upscalers])}") def script_name_to_index(name, scripts): try: return [script.title().lower() for script in scripts].index(name.lower()) - except: + except Exception: raise HTTPException(status_code=422, detail=f"Script '{name}' not found") def validate_sampler_name(name): @@ -99,7 +101,7 @@ def api_middleware(app: FastAPI): import starlette # importing just so it can be placed on silent list from rich.console import Console console = Console() - except: + except Exception: import traceback rich_available = False @@ -166,36 +168,36 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.app = app self.queue_lock = queue_lock api_middleware(self.app) - self.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) - self.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse) - self.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) - self.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) - self.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=PNGInfoResponse) - self.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=ProgressResponse) + self.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=models.TextToImageResponse) + self.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=models.ImageToImageResponse) + self.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=models.ExtrasSingleImageResponse) + self.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=models.ExtrasBatchImagesResponse) + self.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=models.PNGInfoResponse) + self.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=models.ProgressResponse) self.add_api_route("/sdapi/v1/interrogate", self.interrogateapi, methods=["POST"]) self.add_api_route("/sdapi/v1/interrupt", self.interruptapi, methods=["POST"]) self.add_api_route("/sdapi/v1/skip", self.skip, methods=["POST"]) - self.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=OptionsModel) + self.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=models.OptionsModel) 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=FlagsModel) - self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=List[SamplerItem]) - self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=List[UpscalerItem]) - self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=List[SDModelItem]) - self.add_api_route("/sdapi/v1/hypernetworks", self.get_hypernetworks, methods=["GET"], response_model=List[HypernetworkItem]) - self.add_api_route("/sdapi/v1/face-restorers", self.get_face_restorers, methods=["GET"], response_model=List[FaceRestorerItem]) - self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=List[RealesrganItem]) - self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[PromptStyleItem]) - self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=EmbeddingsResponse) + 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/upscalers", self.get_upscalers, methods=["GET"], response_model=List[models.UpscalerItem]) + self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=List[models.SDModelItem]) + self.add_api_route("/sdapi/v1/hypernetworks", self.get_hypernetworks, methods=["GET"], response_model=List[models.HypernetworkItem]) + self.add_api_route("/sdapi/v1/face-restorers", self.get_face_restorers, methods=["GET"], response_model=List[models.FaceRestorerItem]) + self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=List[models.RealesrganItem]) + self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.PromptStyleItem]) + self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse) self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) - self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=CreateResponse) - self.add_api_route("/sdapi/v1/create/hypernetwork", self.create_hypernetwork, methods=["POST"], response_model=CreateResponse) - self.add_api_route("/sdapi/v1/preprocess", self.preprocess, methods=["POST"], response_model=PreprocessResponse) - self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=TrainResponse) - self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=TrainResponse) - self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=MemoryResponse) + self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse) + self.add_api_route("/sdapi/v1/create/hypernetwork", self.create_hypernetwork, methods=["POST"], response_model=models.CreateResponse) + self.add_api_route("/sdapi/v1/preprocess", self.preprocess, methods=["POST"], response_model=models.PreprocessResponse) + self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=models.TrainResponse) + self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=models.TrainResponse) + self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=models.MemoryResponse) self.add_api_route("/sdapi/v1/unload-checkpoint", self.unloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) - self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=ScriptsList) + self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) self.default_script_arg_txt2img = [] self.default_script_arg_img2img = [] @@ -224,7 +226,7 @@ def get_scripts_list(self): t2ilist = [str(title.lower()) for title in scripts.scripts_txt2img.titles] i2ilist = [str(title.lower()) for title in scripts.scripts_img2img.titles] - return ScriptsList(txt2img = t2ilist, img2img = i2ilist) + return models.ScriptsList(txt2img=t2ilist, img2img=i2ilist) def get_script(self, script_name, script_runner): if script_name is None or script_name == "": @@ -276,7 +278,7 @@ def init_script_args(self, request, default_script_args, selectable_scripts, sel script_args[alwayson_script.args_from + idx] = request.alwayson_scripts[alwayson_script_name]["args"][idx] return script_args - def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): + def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): script_runner = scripts.scripts_txt2img if not script_runner.scripts: script_runner.initialize_scripts(False) @@ -320,9 +322,9 @@ def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] - return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) + return models.TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) - def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): + def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): init_images = img2imgreq.init_images if init_images is None: raise HTTPException(status_code=404, detail="Init image not found") @@ -381,9 +383,9 @@ def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): img2imgreq.init_images = None img2imgreq.mask = None - return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.js()) + return models.ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.js()) - def extras_single_image_api(self, req: ExtrasSingleImageRequest): + def extras_single_image_api(self, req: models.ExtrasSingleImageRequest): reqDict = setUpscalers(req) reqDict['image'] = decode_base64_to_image(reqDict['image']) @@ -391,9 +393,9 @@ def extras_single_image_api(self, req: ExtrasSingleImageRequest): with self.queue_lock: result = postprocessing.run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", save_output=False, **reqDict) - return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1]) + return models.ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1]) - def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): + def extras_batch_images_api(self, req: models.ExtrasBatchImagesRequest): reqDict = setUpscalers(req) image_list = reqDict.pop('imageList', []) @@ -402,15 +404,15 @@ def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): with self.queue_lock: result = postprocessing.run_extras(extras_mode=1, image_folder=image_folder, image="", input_dir="", output_dir="", save_output=False, **reqDict) - return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) + return models.ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) - def pnginfoapi(self, req: PNGInfoRequest): + def pnginfoapi(self, req: models.PNGInfoRequest): if(not req.image.strip()): - return PNGInfoResponse(info="") + return models.PNGInfoResponse(info="") image = decode_base64_to_image(req.image.strip()) if image is None: - return PNGInfoResponse(info="") + return models.PNGInfoResponse(info="") geninfo, items = images.read_info_from_image(image) if geninfo is None: @@ -418,13 +420,13 @@ def pnginfoapi(self, req: PNGInfoRequest): items = {**{'parameters': geninfo}, **items} - return PNGInfoResponse(info=geninfo, items=items) + return models.PNGInfoResponse(info=geninfo, items=items) - def progressapi(self, req: ProgressRequest = Depends()): + def progressapi(self, req: models.ProgressRequest = Depends()): # copy from check_progress_call of ui.py if shared.state.job_count == 0: - return ProgressResponse(progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo) + return models.ProgressResponse(progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo) # avoid dividing zero progress = 0.01 @@ -446,9 +448,9 @@ def progressapi(self, req: ProgressRequest = Depends()): if shared.state.current_image and not req.skip_current_image: current_image = encode_pil_to_base64(shared.state.current_image) - return ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) + return models.ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) - def interrogateapi(self, interrogatereq: InterrogateRequest): + def interrogateapi(self, interrogatereq: models.InterrogateRequest): image_b64 = interrogatereq.image if image_b64 is None: raise HTTPException(status_code=404, detail="Image not found") @@ -465,7 +467,7 @@ def interrogateapi(self, interrogatereq: InterrogateRequest): else: raise HTTPException(status_code=404, detail="Model not found") - return InterrogateResponse(caption=processed) + return models.InterrogateResponse(caption=processed) def interruptapi(self): shared.state.interrupt() @@ -570,36 +572,36 @@ def create_embedding(self, args: dict): filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used shared.state.end() - return CreateResponse(info=f"create embedding filename: {filename}") + return models.CreateResponse(info=f"create embedding filename: {filename}") except AssertionError as e: shared.state.end() - return TrainResponse(info=f"create embedding error: {e}") + return models.TrainResponse(info=f"create embedding error: {e}") def create_hypernetwork(self, args: dict): try: shared.state.begin() filename = create_hypernetwork(**args) # create empty embedding shared.state.end() - return CreateResponse(info=f"create hypernetwork filename: {filename}") + return models.CreateResponse(info=f"create hypernetwork filename: {filename}") except AssertionError as e: shared.state.end() - return TrainResponse(info=f"create hypernetwork error: {e}") + return models.TrainResponse(info=f"create hypernetwork error: {e}") def preprocess(self, args: dict): try: shared.state.begin() preprocess(**args) # quick operation unless blip/booru interrogation is enabled shared.state.end() - return PreprocessResponse(info = 'preprocess complete') + return models.PreprocessResponse(info = 'preprocess complete') except KeyError as e: shared.state.end() - return PreprocessResponse(info=f"preprocess error: invalid token: {e}") + return models.PreprocessResponse(info=f"preprocess error: invalid token: {e}") except AssertionError as e: shared.state.end() - return PreprocessResponse(info=f"preprocess error: {e}") + return models.PreprocessResponse(info=f"preprocess error: {e}") except FileNotFoundError as e: shared.state.end() - return PreprocessResponse(info=f'preprocess error: {e}') + return models.PreprocessResponse(info=f'preprocess error: {e}') def train_embedding(self, args: dict): try: @@ -617,10 +619,10 @@ def train_embedding(self, args: dict): if not apply_optimizations: sd_hijack.apply_optimizations() shared.state.end() - return TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") + return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") except AssertionError as msg: shared.state.end() - return TrainResponse(info=f"train embedding error: {msg}") + return models.TrainResponse(info=f"train embedding error: {msg}") def train_hypernetwork(self, args: dict): try: @@ -641,14 +643,15 @@ def train_hypernetwork(self, args: dict): if not apply_optimizations: sd_hijack.apply_optimizations() shared.state.end() - return TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") + return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") except AssertionError: shared.state.end() - return TrainResponse(info=f"train embedding error: {error}") + return models.TrainResponse(info=f"train embedding error: {error}") def get_memory(self): try: - import os, psutil + import os + import psutil process = psutil.Process(os.getpid()) res = process.memory_info() # only rss is cross-platform guaranteed so we dont rely on other values ram_total = 100 * res.rss / process.memory_percent() # and total memory is calculated as actual value is not cross-platform safe @@ -675,10 +678,10 @@ def get_memory(self): 'events': warnings, } else: - cuda = { 'error': 'unavailable' } + cuda = {'error': 'unavailable'} except Exception as err: - cuda = { 'error': f'{err}' } - return MemoryResponse(ram = ram, cuda = cuda) + cuda = {'error': f'{err}'} + return models.MemoryResponse(ram=ram, cuda=cuda) def launch(self, server_name, port): self.app.include_router(self.router) diff --git a/modules/api/models.py b/modules/api/models.py index 4a70f440c8f..4d291076536 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -223,8 +223,9 @@ class PreprocessResponse(BaseModel): if(_options[key].dest != 'help'): flag = _options[key] _type = str - if _options[key].default is not None: _type = type(_options[key].default) - flags.update({flag.dest: (_type,Field(default=flag.default, description=flag.help))}) + if _options[key].default is not None: + _type = type(_options[key].default) + flags.update({flag.dest: (_type, Field(default=flag.default, description=flag.help))}) FlagsModel = create_model("Flags", **flags) diff --git a/modules/codeformer/codeformer_arch.py b/modules/codeformer/codeformer_arch.py index 11dcc3ee765..f1a7cf09f0d 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -7,7 +7,7 @@ import torch.nn.functional as F from typing import Optional, List -from modules.codeformer.vqgan_arch import * +from modules.codeformer.vqgan_arch import VQAutoEncoder, ResBlock from basicsr.utils import get_root_logger from basicsr.utils.registry import ARCH_REGISTRY diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py index 6071fea75bc..7f8bc7c0593 100644 --- a/modules/esrgan_model_arch.py +++ b/modules/esrgan_model_arch.py @@ -438,9 +438,11 @@ def conv_block(in_nc, out_nc, kernel_size, stride=1, dilation=1, groups=1, bias= padding = padding if pad_type == 'zero' else 0 if convtype=='PartialConv2D': + from torchvision.ops import PartialConv2d # this is definitely not going to work, but PartialConv2d doesn't work anyway and this shuts up static analyzer c = PartialConv2d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, groups=groups) elif convtype=='DeformConv2D': + from torchvision.ops import DeformConv2d # not tested c = DeformConv2d(in_nc, out_nc, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, groups=groups) elif convtype=='Conv3D': diff --git a/modules/extra_networks_hypernet.py b/modules/extra_networks_hypernet.py index 04f27c9f752..aa2a14efd03 100644 --- a/modules/extra_networks_hypernet.py +++ b/modules/extra_networks_hypernet.py @@ -1,4 +1,4 @@ -from modules import extra_networks, shared, extra_networks +from modules import extra_networks, shared from modules.hypernetworks import hypernetwork diff --git a/modules/images.py b/modules/images.py index 3d5d76ccc18..5eb6d8556e2 100644 --- a/modules/images.py +++ b/modules/images.py @@ -472,9 +472,9 @@ def get_next_sequence_number(path, basename): prefix_length = len(basename) for p in os.listdir(path): if p.startswith(basename): - l = os.path.splitext(p[prefix_length:])[0].split('-') # splits the filename (removing the basename first if one is defined, so the sequence number is always the first element) + parts = os.path.splitext(p[prefix_length:])[0].split('-') # splits the filename (removing the basename first if one is defined, so the sequence number is always the first element) try: - result = max(int(l[0]), result) + result = max(int(parts[0]), result) except ValueError: pass diff --git a/modules/img2img.py b/modules/img2img.py index cdae301ae7b..32b1ecd6405 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -13,7 +13,6 @@ import modules.shared as shared import modules.processing as processing from modules.ui import plaintext_to_html -import modules.images as images import modules.scripts diff --git a/modules/interrogate.py b/modules/interrogate.py index 9f7d657fd20..22df9216ffb 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -11,7 +11,6 @@ from torchvision import transforms from torchvision.transforms.functional import InterpolationMode -import modules.shared as shared from modules import devices, paths, shared, lowvram, modelloader, errors blip_image_eval_size = 384 diff --git a/modules/modelloader.py b/modules/modelloader.py index cb85ac4ffc8..cf685000401 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -108,12 +108,12 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None): print(f"Moving {file} from {src_path} to {dest_path}.") try: shutil.move(fullpath, dest_path) - except: + except Exception: pass if len(os.listdir(src_path)) == 0: print(f"Removing empty folder: {src_path}") shutil.rmtree(src_path, True) - except: + except Exception: pass @@ -141,7 +141,7 @@ def load_upscalers(): full_model = f"modules.{model_name}_model" try: importlib.import_module(full_model) - except: + except Exception: pass datas = [] diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index f880bc3c785..611c2b699e3 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -479,7 +479,7 @@ def __init__(self, self.cond_stage_key = cond_stage_key try: self.num_downs = len(first_stage_config.params.ddconfig.ch_mult) - 1 - except: + except Exception: self.num_downs = 0 if not scale_by_std: self.scale_factor = scale_factor @@ -891,16 +891,6 @@ def forward(self, x, c, *args, **kwargs): c = self.q_sample(x_start=c, t=tc, noise=torch.randn_like(c.float())) return self.p_losses(x, c, t, *args, **kwargs) - def _rescale_annotations(self, bboxes, crop_coordinates): # TODO: move to dataset - def rescale_bbox(bbox): - x0 = clamp((bbox[0] - crop_coordinates[0]) / crop_coordinates[2]) - y0 = clamp((bbox[1] - crop_coordinates[1]) / crop_coordinates[3]) - w = min(bbox[2] / crop_coordinates[2], 1 - x0) - h = min(bbox[3] / crop_coordinates[3], 1 - y0) - return x0, y0, w, h - - return [rescale_bbox(b) for b in bboxes] - def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): @@ -1171,8 +1161,10 @@ def progressive_denoising(self, cond, shape, verbose=True, callback=None, quanti if i % log_every_t == 0 or i == timesteps - 1: intermediates.append(x0_partial) - if callback: callback(i) - if img_callback: img_callback(img, i) + if callback: + callback(i) + if img_callback: + img_callback(img, i) return img, intermediates @torch.no_grad() @@ -1219,8 +1211,10 @@ def p_sample_loop(self, cond, shape, return_intermediates=False, if i % log_every_t == 0 or i == timesteps - 1: intermediates.append(img) - if callback: callback(i) - if img_callback: img_callback(img, i) + if callback: + callback(i) + if img_callback: + img_callback(img, i) if return_intermediates: return img, intermediates @@ -1337,7 +1331,7 @@ def log_images(self, batch, N=4, n_row=4, sample=True, ddim_steps=200, ddim_eta= if inpaint: # make a simple center square - b, h, w = z.shape[0], z.shape[2], z.shape[3] + h, w = z.shape[2], z.shape[3] mask = torch.ones(N, h, w).to(self.device) # zeros will be filled in mask[:, h // 4:3 * h // 4, w // 4:3 * w // 4] = 0. diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index a241c8a7c4b..0a9defa108b 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -54,7 +54,8 @@ def sample(self, if conditioning is not None: if isinstance(conditioning, dict): ctmp = conditioning[list(conditioning.keys())[0]] - while isinstance(ctmp, list): ctmp = ctmp[0] + while isinstance(ctmp, list): + ctmp = ctmp[0] cbs = ctmp.shape[0] if cbs != batch_size: print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") diff --git a/modules/processing.py b/modules/processing.py index 1a76e552922..6f5233c139c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -664,7 +664,7 @@ def get_conds_with_caching(function, required_prompts, steps, cache): if not shared.opts.dont_fix_second_order_samplers_schedule: try: step_multiplier = 2 if sd_samplers.all_samplers_map.get(p.sampler_name).aliases[0] in ['k_dpmpp_2s_a', 'k_dpmpp_2s_a_ka', 'k_dpmpp_sde', 'k_dpmpp_sde_ka', 'k_dpm_2', 'k_dpm_2_a', 'k_heun'] else 1 - except: + except Exception: pass uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps * step_multiplier, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps * step_multiplier, cached_c) diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index e084e948b0f..3a720721f6a 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -54,18 +54,21 @@ def get_learned_conditioning_prompt_schedules(prompts, steps): """ def collect_steps(steps, tree): - l = [steps] + res = [steps] + class CollectSteps(lark.Visitor): def scheduled(self, tree): tree.children[-1] = float(tree.children[-1]) if tree.children[-1] < 1: tree.children[-1] *= steps tree.children[-1] = min(steps, int(tree.children[-1])) - l.append(tree.children[-1]) + res.append(tree.children[-1]) + def alternate(self, tree): - l.extend(range(1, steps+1)) + res.extend(range(1, steps+1)) + CollectSteps().visit(tree) - return sorted(set(l)) + return sorted(set(res)) def at_step(step, tree): class AtStep(lark.Transformer): diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index ba1bdcd4a57..d7d8d2e31e8 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -185,7 +185,7 @@ def image_face_points(im, settings): try: faces = classifier.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) - except: + except Exception: continue if len(faces) > 0: diff --git a/modules/ui.py b/modules/ui.py index 2171f3aaf72..6beda76f315 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1,15 +1,9 @@ -import html import json -import math import mimetypes import os -import platform -import random import sys -import tempfile -import time import traceback -from functools import partial, reduce +from functools import reduce import warnings import gradio as gr diff --git a/modules/upscaler.py b/modules/upscaler.py index e2eaa7308af..0ad4fe99975 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -45,7 +45,7 @@ def __init__(self, create_dirs=False): try: import cv2 self.can_tile = True - except: + except Exception: pass @abstractmethod From f741a98baccae100fcfb40c017b5c35c5cba1b0c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 08:43:42 +0300 Subject: [PATCH 0137/2418] imports cleanup for ruff --- extensions-builtin/Lora/lora.py | 1 - extensions-builtin/ScuNET/scripts/scunet_model.py | 1 - extensions-builtin/SwinIR/scripts/swinir_model.py | 3 +-- modules/codeformer/codeformer_arch.py | 4 +--- modules/codeformer/vqgan_arch.py | 2 -- modules/codeformer_model.py | 4 +--- modules/config_states.py | 2 +- modules/esrgan_model.py | 2 +- modules/esrgan_model_arch.py | 1 - modules/extensions.py | 1 - modules/generation_parameters_copypaste.py | 4 ---- modules/hypernetworks/hypernetwork.py | 3 +-- modules/hypernetworks/ui.py | 2 -- modules/images.py | 2 +- modules/img2img.py | 5 +---- modules/mac_specific.py | 1 - modules/modelloader.py | 1 - modules/models/diffusion/uni_pc/uni_pc.py | 1 - modules/processing.py | 5 ++--- modules/sd_hijack.py | 2 +- modules/sd_hijack_inpainting.py | 6 ------ modules/sd_hijack_ip2p.py | 5 +---- modules/sd_hijack_xlmr.py | 2 -- modules/sd_models.py | 2 +- modules/sd_models_config.py | 1 - modules/sd_samplers_kdiffusion.py | 1 - modules/sd_vae.py | 3 --- modules/shared.py | 3 --- modules/styles.py | 9 --------- modules/textual_inversion/autocrop.py | 4 +--- modules/textual_inversion/image_embedding.py | 2 +- modules/textual_inversion/preprocess.py | 4 ---- modules/textual_inversion/textual_inversion.py | 1 - modules/txt2img.py | 9 +++------ modules/ui.py | 5 ++--- modules/ui_extra_networks.py | 1 - modules/ui_postprocessing.py | 2 +- modules/upscaler.py | 2 -- modules/xlmr.py | 2 +- pyproject.toml | 11 +++++++---- scripts/custom_code.py | 2 +- scripts/outpainting_mk_2.py | 4 ++-- scripts/poor_mans_outpainting.py | 4 ++-- scripts/prompt_matrix.py | 7 ++----- scripts/prompts_from_file.py | 5 +---- scripts/sd_upscale.py | 4 ++-- scripts/xyz_grid.py | 6 ++---- webui.py | 2 +- 48 files changed, 42 insertions(+), 114 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index ba1293dfc88..0ab4322902b 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -1,4 +1,3 @@ -import glob import os import re import torch diff --git a/extensions-builtin/ScuNET/scripts/scunet_model.py b/extensions-builtin/ScuNET/scripts/scunet_model.py index c7fd5739b25..aa2fdb3a953 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -13,7 +13,6 @@ from modules import devices, modelloader from scunet_model_arch import SCUNet as net from modules.shared import opts -from modules import images class UpscalerScuNET(modules.upscaler.Upscaler): diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index d77c3a923b5..55dd94ab8e6 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -1,4 +1,3 @@ -import contextlib import os import numpy as np @@ -8,7 +7,7 @@ from tqdm import tqdm from modules import modelloader, devices, script_callbacks, shared -from modules.shared import cmd_opts, opts, state +from modules.shared import opts, state from swinir_model_arch import SwinIR as net from swinir_model_arch_v2 import Swin2SR as net2 from modules.upscaler import Upscaler, UpscalerData diff --git a/modules/codeformer/codeformer_arch.py b/modules/codeformer/codeformer_arch.py index f1a7cf09f0d..00c407dee3a 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -1,14 +1,12 @@ # this file is copied from CodeFormer repository. Please see comment in modules/codeformer_model.py import math -import numpy as np import torch from torch import nn, Tensor import torch.nn.functional as F -from typing import Optional, List +from typing import Optional from modules.codeformer.vqgan_arch import VQAutoEncoder, ResBlock -from basicsr.utils import get_root_logger from basicsr.utils.registry import ARCH_REGISTRY def calc_mean_std(feat, eps=1e-5): diff --git a/modules/codeformer/vqgan_arch.py b/modules/codeformer/vqgan_arch.py index e729368383a..820e6b12720 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -5,11 +5,9 @@ https://github.com/samb-t/unleashing-transformers/blob/master/models/vqgan.py ''' -import numpy as np import torch import torch.nn as nn import torch.nn.functional as F -import copy from basicsr.utils import get_root_logger from basicsr.utils.registry import ARCH_REGISTRY diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index 8d84bbc9029..8e56cb8921a 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -33,11 +33,9 @@ def setup_model(dirname): try: from torchvision.transforms.functional import normalize from modules.codeformer.codeformer_arch import CodeFormer - from basicsr.utils.download_util import load_file_from_url - from basicsr.utils import imwrite, img2tensor, tensor2img + from basicsr.utils import img2tensor, tensor2img from facelib.utils.face_restoration_helper import FaceRestoreHelper from facelib.detection.retinaface import retinaface - from modules.shared import cmd_opts net_class = CodeFormer diff --git a/modules/config_states.py b/modules/config_states.py index 2ea00929cfd..8f1ff42889d 100644 --- a/modules/config_states.py +++ b/modules/config_states.py @@ -14,7 +14,7 @@ import git from modules import shared, extensions -from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path, config_states_dir +from modules.paths_internal import script_path, config_states_dir all_config_states = OrderedDict() diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index f4369257c20..85aa6934947 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -6,7 +6,7 @@ from basicsr.utils.download_util import load_file_from_url import modules.esrgan_model_arch as arch -from modules import shared, modelloader, images, devices +from modules import modelloader, images, devices from modules.upscaler import Upscaler, UpscalerData from modules.shared import opts diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py index 7f8bc7c0593..4de9dd8dadc 100644 --- a/modules/esrgan_model_arch.py +++ b/modules/esrgan_model_arch.py @@ -2,7 +2,6 @@ from collections import OrderedDict import math -import functools import torch import torch.nn as nn import torch.nn.functional as F diff --git a/modules/extensions.py b/modules/extensions.py index 34d9d6544cb..829f8cd9711 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -3,7 +3,6 @@ import traceback import time -from datetime import datetime import git from modules import shared diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index fe8b18b207b..f1c59c4664f 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -1,15 +1,11 @@ import base64 -import html import io -import math import os import re -from pathlib import Path import gradio as gr from modules.paths import data_path from modules import shared, ui_tempdir, script_callbacks -import tempfile from PIL import Image re_param_code = r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)' diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 1fc49537c61..9fe749b7a4a 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -1,4 +1,3 @@ -import csv import datetime import glob import html @@ -18,7 +17,7 @@ from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ -from collections import defaultdict, deque +from collections import deque from statistics import stdev, mean diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 76599f5adec..be168736eee 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -1,6 +1,4 @@ import html -import os -import re import gradio as gr import modules.hypernetworks.hypernetwork diff --git a/modules/images.py b/modules/images.py index 5eb6d8556e2..7392cb8bf73 100644 --- a/modules/images.py +++ b/modules/images.py @@ -19,7 +19,7 @@ import hashlib from modules import sd_samplers, shared, script_callbacks, errors -from modules.shared import opts, cmd_opts +from modules.shared import opts LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) diff --git a/modules/img2img.py b/modules/img2img.py index 32b1ecd6405..d704bf9006d 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -1,12 +1,9 @@ -import math import os -import sys -import traceback import numpy as np from PIL import Image, ImageOps, ImageFilter, ImageEnhance, ImageChops, UnidentifiedImageError -from modules import devices, sd_samplers +from modules import sd_samplers from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, state diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 40ce2101764..5c2f92a1541 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -1,6 +1,5 @@ import torch import platform -from modules import paths from modules.sd_hijack_utils import CondFunc from packaging import version diff --git a/modules/modelloader.py b/modules/modelloader.py index cf685000401..92ada6941ca 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -1,4 +1,3 @@ -import glob import os import shutil import importlib diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 11b330bcf3c..a4c4ef4e42b 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -1,5 +1,4 @@ import torch -import torch.nn.functional as F import math from tqdm.auto import trange diff --git a/modules/processing.py b/modules/processing.py index 6f5233c139c..c3932d6ba71 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -2,7 +2,6 @@ import math import os import sys -import warnings import hashlib import torch @@ -11,10 +10,10 @@ import random import cv2 from skimage import exposure -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List import modules.sd_hijack -from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, script_callbacks, extra_networks, sd_vae_approx, scripts +from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, extra_networks, sd_vae_approx, scripts from modules.sd_hijack import model_hijack from modules.shared import opts, cmd_opts, state import modules.shared as shared diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index d8135211688..81573b7817f 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -3,7 +3,7 @@ from types import MethodType import modules.textual_inversion.textual_inversion -from modules import devices, sd_hijack_optimizations, shared, sd_hijack_checkpoint +from modules import devices, sd_hijack_optimizations, shared from modules.hypernetworks import hypernetwork from modules.shared import cmd_opts from modules import sd_hijack_clip, sd_hijack_open_clip, sd_hijack_unet, sd_hijack_xlmr, xlmr diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 55a2ce4d192..344d75c8d60 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -1,15 +1,9 @@ -import os import torch -from einops import repeat -from omegaconf import ListConfig - import ldm.models.diffusion.ddpm import ldm.models.diffusion.ddim import ldm.models.diffusion.plms -from ldm.models.diffusion.ddpm import LatentDiffusion -from ldm.models.diffusion.plms import PLMSSampler from ldm.models.diffusion.ddim import DDIMSampler, noise_like from ldm.models.diffusion.sampling_util import norm_thresholding diff --git a/modules/sd_hijack_ip2p.py b/modules/sd_hijack_ip2p.py index 41ed54a296c..6fe6b6ffec6 100644 --- a/modules/sd_hijack_ip2p.py +++ b/modules/sd_hijack_ip2p.py @@ -1,8 +1,5 @@ -import collections import os.path -import sys -import gc -import time + def should_hijack_ip2p(checkpoint_info): from modules import sd_models_config diff --git a/modules/sd_hijack_xlmr.py b/modules/sd_hijack_xlmr.py index 4ac51c386fd..28528329b60 100644 --- a/modules/sd_hijack_xlmr.py +++ b/modules/sd_hijack_xlmr.py @@ -1,8 +1,6 @@ -import open_clip.tokenizer import torch from modules import sd_hijack_clip, devices -from modules.shared import opts class FrozenXLMREmbedderWithCustomWords(sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords): diff --git a/modules/sd_models.py b/modules/sd_models.py index 11c1a344e4c..1c09c709cd9 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -565,7 +565,7 @@ def reload_model_weights(sd_model=None, info=None): def unload_model_weights(sd_model=None, info=None): - from modules import lowvram, devices, sd_hijack + from modules import devices, sd_hijack timer = Timer() if model_data.sd_model: diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 7a79925a3fa..9bfe1237d1d 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -1,4 +1,3 @@ -import re import os import torch diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 0fc9f456646..3b8e9622296 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,7 +1,6 @@ from collections import deque import torch import inspect -import einops import k_diffusion.sampling from modules import prompt_parser, devices, sd_samplers_common diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 521e485ad76..b7176125c94 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -1,8 +1,5 @@ -import torch -import safetensors.torch import os import collections -from collections import namedtuple from modules import paths, shared, devices, script_callbacks, sd_models import glob from copy import deepcopy diff --git a/modules/shared.py b/modules/shared.py index 4631965b4c0..44cd2c0c5ec 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,12 +1,9 @@ -import argparse import datetime import json import os import sys import time -import requests -from PIL import Image import gradio as gr import tqdm diff --git a/modules/styles.py b/modules/styles.py index 11642075ff3..c22769cf7f2 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -1,18 +1,9 @@ -# We need this so Python doesn't complain about the unknown StableDiffusionProcessing-typehint at runtime -from __future__ import annotations - import csv import os import os.path import typing -import collections.abc as abc -import tempfile import shutil -if typing.TYPE_CHECKING: - # Only import this when code is being type-checked, it doesn't have any effect at runtime - from .processing import StableDiffusionProcessing - class PromptStyle(typing.NamedTuple): name: str diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index d7d8d2e31e8..7770d22f5c2 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -1,10 +1,8 @@ import cv2 import requests import os -from collections import defaultdict -from math import log, sqrt import numpy as np -from PIL import Image, ImageDraw +from PIL import ImageDraw GREEN = "#0F0" BLUE = "#00F" diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index 5593f88c799..ee0e850acbc 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -2,7 +2,7 @@ import json import numpy as np import zlib -from PIL import Image, PngImagePlugin, ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageFont from fonts.ttf import Roboto import torch from modules.shared import opts diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index da0bcb26ba3..d0cad09eb0c 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -1,13 +1,9 @@ import os from PIL import Image, ImageOps import math -import platform -import sys import tqdm -import time from modules import paths, shared, images, deepbooru -from modules.shared import opts, cmd_opts from modules.textual_inversion import autocrop diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index f753b75f74b..9ed9ba459cb 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -1,7 +1,6 @@ import os import sys import traceback -import inspect from collections import namedtuple import torch diff --git a/modules/txt2img.py b/modules/txt2img.py index 16841d0f2f3..f022381ced4 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -1,18 +1,15 @@ import modules.scripts -from modules import sd_samplers +from modules import sd_samplers, processing from modules.generation_parameters_copypaste import create_override_settings_dict -from modules.processing import StableDiffusionProcessing, Processed, StableDiffusionProcessingTxt2Img, \ - StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, cmd_opts import modules.shared as shared -import modules.processing as processing from modules.ui import plaintext_to_html def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, override_settings_texts, *args): override_settings = create_override_settings_dict(override_settings_texts) - p = StableDiffusionProcessingTxt2Img( + p = processing.StableDiffusionProcessingTxt2Img( sd_model=shared.sd_model, outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, outpath_grids=opts.outdir_grids or opts.outdir_txt2img_grids, @@ -53,7 +50,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step processed = modules.scripts.scripts_txt2img.run(p, *args) if processed is None: - processed = process_images(p) + processed = processing.process_images(p) p.close() diff --git a/modules/ui.py b/modules/ui.py index 6beda76f315..f7e5759351e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -14,10 +14,10 @@ from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, postprocessing, ui_components, ui_common, ui_postprocessing, progress -from modules.ui_components import FormRow, FormColumn, FormGroup, ToolButton, FormHTML +from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path -from modules.shared import opts, cmd_opts, restricted_opts +from modules.shared import opts, cmd_opts import modules.codeformer_model import modules.generation_parameters_copypaste as parameters_copypaste @@ -28,7 +28,6 @@ import modules.styles import modules.textual_inversion.ui from modules import prompt_parser -from modules.images import save_image from modules.sd_hijack import model_hijack from modules.sd_samplers import samplers, samplers_for_img2img from modules.textual_inversion import textual_inversion diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 49e06289814..800e467a13f 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,4 +1,3 @@ -import glob import os.path import urllib.parse from pathlib import Path diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index f25639e5745..c7dc1154047 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -1,5 +1,5 @@ import gradio as gr -from modules import scripts_postprocessing, scripts, shared, gfpgan_model, codeformer_model, ui_common, postprocessing, call_queue +from modules import scripts, shared, ui_common, postprocessing, call_queue import modules.generation_parameters_copypaste as parameters_copypaste diff --git a/modules/upscaler.py b/modules/upscaler.py index 0ad4fe99975..777593b00ef 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -2,8 +2,6 @@ from abc import abstractmethod import PIL -import numpy as np -import torch from PIL import Image import modules.shared diff --git a/modules/xlmr.py b/modules/xlmr.py index beab3fdf55e..e056c3f6a25 100644 --- a/modules/xlmr.py +++ b/modules/xlmr.py @@ -1,4 +1,4 @@ -from transformers import BertPreTrainedModel,BertModel,BertConfig +from transformers import BertPreTrainedModel, BertConfig import torch.nn as nn import torch from transformers.models.xlm_roberta.configuration_xlm_roberta import XLMRobertaConfig diff --git a/pyproject.toml b/pyproject.toml index 1e164abc206..9caa9ba29ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,13 @@ [tool.ruff] +exclude = ["extensions"] + ignore = [ "E501", - "E731", - "E402", # Module level import not at top of file - "F401" # Module imported but unused + + "F401", # Module imported but unused ] -exclude = ["extensions"] + +[tool.ruff.per-file-ignores] +"webui.py" = ["E402"] # Module level import not at top of file \ No newline at end of file diff --git a/scripts/custom_code.py b/scripts/custom_code.py index f36a36754b0..cc6f0d4908e 100644 --- a/scripts/custom_code.py +++ b/scripts/custom_code.py @@ -4,7 +4,7 @@ import copy from modules.processing import Processed -from modules.shared import opts, cmd_opts, state +from modules.shared import cmd_opts def convertExpr2Expression(expr): diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index b10fed6cf3c..665dbe89706 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -7,9 +7,9 @@ import gradio as gr from PIL import Image, ImageDraw -from modules import images, processing, devices +from modules import images from modules.processing import Processed, process_images -from modules.shared import opts, cmd_opts, state +from modules.shared import opts, state # this function is taken from https://github.com/parlance-zz/g-diffuser-bot diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py index ddcbd2d3a86..c0bbecc1291 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -4,9 +4,9 @@ import gradio as gr from PIL import Image, ImageDraw -from modules import images, processing, devices +from modules import images, devices from modules.processing import Processed, process_images -from modules.shared import opts, cmd_opts, state +from modules.shared import opts, state class Script(scripts.Script): diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index e9b11517022..fb06beab4e1 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -1,14 +1,11 @@ import math -from collections import namedtuple -from copy import copy -import random import modules.scripts as scripts import gradio as gr from modules import images -from modules.processing import process_images, Processed -from modules.shared import opts, cmd_opts, state +from modules.processing import process_images +from modules.shared import opts, state import modules.sd_samplers diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 76dc5778b27..149bc85fb41 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -1,6 +1,4 @@ import copy -import math -import os import random import sys import traceback @@ -11,8 +9,7 @@ from modules import sd_samplers from modules.processing import Processed, process_images -from PIL import Image -from modules.shared import opts, cmd_opts, state +from modules.shared import state def process_string_tag(tag): diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 332d76d918e..d873a09c4af 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -4,9 +4,9 @@ import gradio as gr from PIL import Image -from modules import processing, shared, sd_samplers, images, devices +from modules import processing, shared, images, devices from modules.processing import Processed -from modules.shared import opts, cmd_opts, state +from modules.shared import opts, state class Script(scripts.Script): diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 2ff42ef8217..332e0ecde00 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -10,15 +10,13 @@ import modules.scripts as scripts import gradio as gr -from modules import images, paths, sd_samplers, processing, sd_models, sd_vae +from modules import images, sd_samplers, processing, sd_models, sd_vae from modules.processing import process_images, Processed, StableDiffusionProcessingTxt2Img -from modules.shared import opts, cmd_opts, state +from modules.shared import opts, state import modules.shared as shared import modules.sd_samplers import modules.sd_models import modules.sd_vae -import glob -import os import re from modules.ui_components import ToolButton diff --git a/webui.py b/webui.py index ec3d2aba152..48277075410 100644 --- a/webui.py +++ b/webui.py @@ -43,7 +43,7 @@ torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) -from modules import shared, devices, sd_samplers, upscaler, extensions, localization, ui_tempdir, ui_extra_networks, config_states +from modules import shared, sd_samplers, upscaler, extensions, localization, ui_tempdir, ui_extra_networks, config_states import modules.codeformer_model as codeformer import modules.face_restoration import modules.gfpgan_model as gfpgan From 4b854806d98cf5ccd48e5cd99c172613da7937f0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 09:02:23 +0300 Subject: [PATCH 0138/2418] F401 fixes for ruff --- extensions-builtin/LDSR/scripts/ldsr_model.py | 4 ++-- modules/cmd_args.py | 2 +- modules/deepbooru.py | 1 - modules/extensions.py | 2 +- modules/gfpgan_model.py | 2 +- modules/models/diffusion/uni_pc/__init__.py | 2 +- modules/paths.py | 4 ++-- modules/realesrgan_model.py | 6 +++--- modules/script_loading.py | 1 - modules/sd_hijack_inpainting.py | 2 +- modules/sd_models.py | 4 +--- modules/sd_samplers.py | 2 +- modules/shared.py | 2 +- modules/ui.py | 4 ++-- modules/upscaler.py | 2 +- pyproject.toml | 9 +++++---- webui.py | 8 ++++---- 17 files changed, 27 insertions(+), 30 deletions(-) diff --git a/extensions-builtin/LDSR/scripts/ldsr_model.py b/extensions-builtin/LDSR/scripts/ldsr_model.py index e8dc083c58c..fbbe90050d3 100644 --- a/extensions-builtin/LDSR/scripts/ldsr_model.py +++ b/extensions-builtin/LDSR/scripts/ldsr_model.py @@ -7,8 +7,8 @@ from modules.upscaler import Upscaler, UpscalerData from ldsr_model_arch import LDSR from modules import shared, script_callbacks -import sd_hijack_autoencoder -import sd_hijack_ddpm_v1 +import sd_hijack_autoencoder # noqa: F401 +import sd_hijack_ddpm_v1 # noqa: F401 class UpscalerLDSR(Upscaler): diff --git a/modules/cmd_args.py b/modules/cmd_args.py index d906a5717ee..e01ca65512b 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -1,6 +1,6 @@ import argparse import os -from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir, sd_default_config, sd_model_file +from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir, sd_default_config, sd_model_file # noqa: F401 parser = argparse.ArgumentParser() diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 122fce7f569..1c4554a209a 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -2,7 +2,6 @@ import re import torch -from PIL import Image import numpy as np from modules import modelloader, paths, deepbooru_model, devices, images, shared diff --git a/modules/extensions.py b/modules/extensions.py index 829f8cd9711..bc2c0450f7f 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -6,7 +6,7 @@ import git from modules import shared -from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path +from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401 extensions = [] diff --git a/modules/gfpgan_model.py b/modules/gfpgan_model.py index fbe6215a649..0131dea4265 100644 --- a/modules/gfpgan_model.py +++ b/modules/gfpgan_model.py @@ -78,7 +78,7 @@ def setup_model(dirname): try: from gfpgan import GFPGANer - from facexlib import detection, parsing + from facexlib import detection, parsing # noqa: F401 global user_path global have_gfpgan global gfpgan_constructor diff --git a/modules/models/diffusion/uni_pc/__init__.py b/modules/models/diffusion/uni_pc/__init__.py index e1265e3fec3..dbb35964ce0 100644 --- a/modules/models/diffusion/uni_pc/__init__.py +++ b/modules/models/diffusion/uni_pc/__init__.py @@ -1 +1 @@ -from .sampler import UniPCSampler +from .sampler import UniPCSampler # noqa: F401 diff --git a/modules/paths.py b/modules/paths.py index acf1894bafa..5f6474c032a 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -1,8 +1,8 @@ import os import sys -from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir +from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir # noqa: F401 -import modules.safe +import modules.safe # noqa: F401 # data_path = cmd_opts_pre.data diff --git a/modules/realesrgan_model.py b/modules/realesrgan_model.py index 9ec1adf2a1a..c24d8dbb524 100644 --- a/modules/realesrgan_model.py +++ b/modules/realesrgan_model.py @@ -17,9 +17,9 @@ def __init__(self, path): self.user_path = path super().__init__() try: - from basicsr.archs.rrdbnet_arch import RRDBNet - from realesrgan import RealESRGANer - from realesrgan.archs.srvgg_arch import SRVGGNetCompact + from basicsr.archs.rrdbnet_arch import RRDBNet # noqa: F401 + from realesrgan import RealESRGANer # noqa: F401 + from realesrgan.archs.srvgg_arch import SRVGGNetCompact # noqa: F401 self.enable = True self.scalers = [] scalers = self.load_models(path) diff --git a/modules/script_loading.py b/modules/script_loading.py index a7d2203fc0b..57b15862466 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -2,7 +2,6 @@ import sys import traceback import importlib.util -from types import ModuleType def load_module(path): diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 344d75c8d60..058575b792f 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -4,7 +4,7 @@ import ldm.models.diffusion.ddim import ldm.models.diffusion.plms -from ldm.models.diffusion.ddim import DDIMSampler, noise_like +from ldm.models.diffusion.ddim import noise_like from ldm.models.diffusion.sampling_util import norm_thresholding diff --git a/modules/sd_models.py b/modules/sd_models.py index 1c09c709cd9..d1e946a5b8c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -15,7 +15,6 @@ from ldm.util import instantiate_from_config from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config -from modules.paths import models_path from modules.sd_hijack_inpainting import do_inpainting_hijack from modules.timer import Timer @@ -87,8 +86,7 @@ def calculate_shorthash(self): try: # this silences the annoying "Some weights of the model checkpoint were not used when initializing..." message at start. - - from transformers import logging, CLIPModel + from transformers import logging, CLIPModel # noqa: F401 logging.set_verbosity_error() except Exception: diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index ff361f22b24..4f1bf21d89a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,7 +1,7 @@ from modules import sd_samplers_compvis, sd_samplers_kdiffusion, shared # imports for functions that previously were here and are used by other modules -from modules.sd_samplers_common import samples_to_image_grid, sample_to_image +from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # noqa: F401 all_samplers = [ *sd_samplers_kdiffusion.samplers_data_k_diffusion, diff --git a/modules/shared.py b/modules/shared.py index 44cd2c0c5ec..7d70f041a33 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -12,7 +12,7 @@ import modules.styles import modules.devices as devices from modules import localization, script_loading, errors, ui_components, shared_items, cmd_args -from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir +from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401 from ldm.models.diffusion.ddpm import LatentDiffusion demo = None diff --git a/modules/ui.py b/modules/ui.py index f7e5759351e..782b569da7d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -10,10 +10,10 @@ import gradio.routes import gradio.utils import numpy as np -from PIL import Image, PngImagePlugin +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 sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, postprocessing, ui_components, ui_common, ui_postprocessing, progress +from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path diff --git a/modules/upscaler.py b/modules/upscaler.py index 777593b00ef..e145be30eaf 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -41,7 +41,7 @@ def __init__(self, create_dirs=False): os.makedirs(self.model_path, exist_ok=True) try: - import cv2 + import cv2 # noqa: F401 self.can_tile = True except Exception: pass diff --git a/pyproject.toml b/pyproject.toml index 9caa9ba29ba..0883c127450 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,14 @@ [tool.ruff] +target-version = "py310" + exclude = ["extensions"] ignore = [ - "E501", - - "F401", # Module imported but unused + "E501", # Line too long + "E731", # Do not assign a `lambda` expression, use a `def` ] [tool.ruff.per-file-ignores] -"webui.py" = ["E402"] # Module level import not at top of file \ No newline at end of file +"webui.py" = ["E402"] # Module level import not at top of file diff --git a/webui.py b/webui.py index 48277075410..5d5e80b58e0 100644 --- a/webui.py +++ b/webui.py @@ -16,12 +16,12 @@ import logging logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) -from modules import paths, timer, import_hook, errors +from modules import paths, timer, import_hook, errors # noqa: F401 startup_timer = timer.Timer() import torch -import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them +import pytorch_lightning # noqa: F401 # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them warnings.filterwarnings(action="ignore", category=DeprecationWarning, module="pytorch_lightning") warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvision") @@ -31,12 +31,12 @@ import gradio startup_timer.record("import gradio") -import ldm.modules.encoders.modules +import ldm.modules.encoders.modules # noqa: F401 startup_timer.record("import ldm") from modules import extra_networks, ui_extra_networks_checkpoints from modules import extra_networks_hypernet, ui_extra_networks_hypernets, ui_extra_networks_textual_inversion -from modules.call_queue import wrap_queued_call, queue_lock, wrap_gradio_gpu_call +from modules.call_queue import wrap_queued_call, queue_lock # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: From 57ef617251a5cb118fb5a49c6e7cd2b609323ab0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 09:09:41 +0300 Subject: [PATCH 0139/2418] integrate the PR's config --- pyproject.toml | 5 ++++- ruff.toml | 10 ---------- 2 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 ruff.toml diff --git a/pyproject.toml b/pyproject.toml index 0883c127450..24f5b1aed3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,10 @@ target-version = "py310" -exclude = ["extensions"] +exclude = [ + "extensions", + "extensions-disabled", +] ignore = [ "E501", # Line too long diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index 5bb28a74822..00000000000 --- a/ruff.toml +++ /dev/null @@ -1,10 +0,0 @@ -target-version = "py310" - -select = [ - "E999", # syntax error -] - -extend-exclude = [ - "extensions", - "extensions-disabled", -] From e42de4b8a2356c6d286adb07292442d75e5595d3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 11:00:07 +0300 Subject: [PATCH 0140/2418] update ruff config with more stuff --- pyproject.toml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 24f5b1aed3d..2f65fd6c773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,12 @@ [tool.ruff] -target-version = "py310" +target-version = "py39" + +extend-select = [ + "B", + "C", + "I", +] exclude = [ "extensions", @@ -10,6 +16,12 @@ exclude = [ ignore = [ "E501", # Line too long "E731", # Do not assign a `lambda` expression, use a `def` + + "I001", # Import block is un-sorted or un-formatted + "C901", # Function is too complex + "C408", # Rewrite as a literal + "B007", # Loop control variable not used within loop body + ] From 028d3f6425d85f122027c127fba8bcbf4f66ee75 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 11:05:02 +0300 Subject: [PATCH 0141/2418] ruff auto fixes --- extensions-builtin/LDSR/sd_hijack_autoencoder.py | 4 ++-- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 12 ++++++------ extensions-builtin/Lora/lora.py | 12 ++++++------ extensions-builtin/Lora/scripts/lora_script.py | 2 +- modules/config_states.py | 2 +- modules/deepbooru.py | 2 +- modules/devices.py | 2 +- modules/hypernetworks/hypernetwork.py | 2 +- modules/hypernetworks/ui.py | 4 ++-- modules/interrogate.py | 2 +- modules/modelloader.py | 2 +- modules/models/diffusion/ddpm_edit.py | 4 ++-- modules/scripts_auto_postprocessing.py | 2 +- modules/sd_hijack.py | 2 +- modules/sd_hijack_optimizations.py | 14 +++++++------- modules/sd_samplers_compvis.py | 2 +- modules/sd_samplers_kdiffusion.py | 2 +- modules/shared.py | 6 +++--- modules/textual_inversion/textual_inversion.py | 2 +- modules/ui.py | 8 ++++---- modules/ui_extra_networks.py | 4 ++-- modules/ui_tempdir.py | 2 +- 22 files changed, 47 insertions(+), 47 deletions(-) diff --git a/extensions-builtin/LDSR/sd_hijack_autoencoder.py b/extensions-builtin/LDSR/sd_hijack_autoencoder.py index 6303fed545d..f457ca9388f 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -288,5 +288,5 @@ def decode(self, h, force_not_quantize=False): dec = self.decoder(quant) return dec -setattr(ldm.models.autoencoder, "VQModel", VQModel) -setattr(ldm.models.autoencoder, "VQModelInterface", VQModelInterface) +ldm.models.autoencoder.VQModel = VQModel +ldm.models.autoencoder.VQModelInterface = VQModelInterface diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 4d3f6c56ada..d8fc30e36b6 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -1116,7 +1116,7 @@ def progressive_denoising(self, cond, shape, verbose=True, callback=None, quanti if cond is not None: if isinstance(cond, dict): cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else - list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + [x[:batch_size] for x in cond[key]] for key in cond} else: cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] @@ -1215,7 +1215,7 @@ def sample(self, cond, batch_size=16, return_intermediates=False, x_T=None, if cond is not None: if isinstance(cond, dict): cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else - list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + [x[:batch_size] for x in cond[key]] for key in cond} else: cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] return self.p_sample_loop(cond, @@ -1437,7 +1437,7 @@ def log_images(self, batch, N=8, *args, **kwargs): logs['bbox_image'] = cond_img return logs -setattr(ldm.models.diffusion.ddpm, "DDPMV1", DDPMV1) -setattr(ldm.models.diffusion.ddpm, "LatentDiffusionV1", LatentDiffusionV1) -setattr(ldm.models.diffusion.ddpm, "DiffusionWrapperV1", DiffusionWrapperV1) -setattr(ldm.models.diffusion.ddpm, "Layout2ImgDiffusionV1", Layout2ImgDiffusionV1) +ldm.models.diffusion.ddpm.DDPMV1 = DDPMV1 +ldm.models.diffusion.ddpm.LatentDiffusionV1 = LatentDiffusionV1 +ldm.models.diffusion.ddpm.DiffusionWrapperV1 = DiffusionWrapperV1 +ldm.models.diffusion.ddpm.Layout2ImgDiffusionV1 = Layout2ImgDiffusionV1 diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 0ab4322902b..9795540ff71 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -172,7 +172,7 @@ def load_lora(name, filename): else: print(f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}') continue - assert False, f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}' + raise AssertionError(f"Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}") with torch.no_grad(): module.weight.copy_(weight) @@ -184,7 +184,7 @@ def load_lora(name, filename): elif lora_key == "lora_down.weight": lora_module.down = module else: - assert False, f'Bad Lora layer name: {key_diffusers} - must end in lora_up.weight, lora_down.weight or alpha' + raise AssertionError(f"Bad Lora layer name: {key_diffusers} - must end in lora_up.weight, lora_down.weight or alpha") if len(keys_failed_to_match) > 0: print(f"Failed to match keys when loading Lora {filename}: {keys_failed_to_match}") @@ -202,7 +202,7 @@ def load_loras(names, multipliers=None): loaded_loras.clear() loras_on_disk = [available_lora_aliases.get(name, None) for name in names] - if any([x is None for x in loras_on_disk]): + if any(x is None for x in loras_on_disk): list_available_loras() loras_on_disk = [available_lora_aliases.get(name, None) for name in names] @@ -309,7 +309,7 @@ def lora_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.Mu print(f'failed to calculate lora weights for layer {lora_layer_name}') - setattr(self, "lora_current_names", wanted_names) + self.lora_current_names = wanted_names def lora_forward(module, input, original_forward): @@ -343,8 +343,8 @@ def lora_forward(module, input, original_forward): def lora_reset_cached_weight(self: Union[torch.nn.Conv2d, torch.nn.Linear]): - setattr(self, "lora_current_names", ()) - setattr(self, "lora_weights_backup", None) + self.lora_current_names = () + self.lora_weights_backup = None def lora_Linear_forward(self, input): diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 7db971fd59b..b70e2de733c 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -53,7 +53,7 @@ def before_ui(): shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), { - "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in lora.available_loras]}, refresh=lora.list_available_loras), + "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None"] + list(lora.available_loras)}, refresh=lora.list_available_loras), })) diff --git a/modules/config_states.py b/modules/config_states.py index 8f1ff42889d..75da862ab99 100644 --- a/modules/config_states.py +++ b/modules/config_states.py @@ -35,7 +35,7 @@ def list_config_states(): j["filepath"] = path config_states.append(j) - config_states = list(sorted(config_states, key=lambda cs: cs["created_at"], reverse=True)) + config_states = sorted(config_states, key=lambda cs: cs["created_at"], reverse=True) for cs in config_states: timestamp = time.asctime(time.gmtime(cs["created_at"])) diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 1c4554a209a..547e1b4c67a 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -78,7 +78,7 @@ def tag_multi(self, pil_image, force_disable_ranks=False): res = [] - filtertags = set([x.strip().replace(' ', '_') for x in shared.opts.deepbooru_filter_tags.split(",")]) + filtertags = {x.strip().replace(' ', '_') for x in shared.opts.deepbooru_filter_tags.split(",")} for tag in [x for x in tags if x not in filtertags]: probability = probability_dict[tag] diff --git a/modules/devices.py b/modules/devices.py index c705a3cb676..d8a34a0fdde 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -65,7 +65,7 @@ def enable_tf32(): # enabling benchmark option seems to enable a range of cards to do fp16 when they otherwise can't # see https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/4407 - if any([torch.cuda.get_device_capability(devid) == (7, 5) for devid in range(0, torch.cuda.device_count())]): + if any(torch.cuda.get_device_capability(devid) == (7, 5) for devid in range(0, torch.cuda.device_count())): torch.backends.cudnn.benchmark = True torch.backends.cuda.matmul.allow_tf32 = True diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 9fe749b7a4a..6ef0bfdfab9 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -403,7 +403,7 @@ def attention_CrossAttention_forward(self, x, context=None, mask=None): k = self.to_k(context_k) v = self.to_v(context_v) - q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v)) sim = einsum('b i d, b j d -> b i j', q, k) * self.scale diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index be168736eee..e3f9eb13d24 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -5,13 +5,13 @@ from modules import devices, sd_hijack, shared not_available = ["hardswish", "multiheadattention"] -keys = list(x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available) +keys = [x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available] def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False, dropout_structure=None): filename = modules.hypernetworks.hypernetwork.create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, dropout_structure) - return gr.Dropdown.update(choices=sorted([x for x in shared.hypernetworks.keys()])), f"Created: {filename}", "" + return gr.Dropdown.update(choices=sorted(shared.hypernetworks.keys())), f"Created: {filename}", "" def train_hypernetwork(*args): diff --git a/modules/interrogate.py b/modules/interrogate.py index 22df9216ffb..a1c8e537137 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -159,7 +159,7 @@ def rank(self, image_features, text_array, top_count=1): text_array = text_array[0:int(shared.opts.interrogate_clip_dict_limit)] top_count = min(top_count, len(text_array)) - text_tokens = clip.tokenize([text for text in text_array], truncate=True).to(devices.device_interrogate) + text_tokens = clip.tokenize(list(text_array), truncate=True).to(devices.device_interrogate) text_features = self.clip_model.encode_text(text_tokens).type(self.dtype) text_features /= text_features.norm(dim=-1, keepdim=True) diff --git a/modules/modelloader.py b/modules/modelloader.py index 92ada6941ca..25612bf81e0 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -39,7 +39,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None if os.path.islink(full_path) and not os.path.exists(full_path): print(f"Skipping broken symlink: {full_path}") continue - if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]): + if ext_blacklist is not None and any(full_path.endswith(x) for x in ext_blacklist): continue if full_path not in output: output.append(full_path) diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 611c2b699e3..094321170b0 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -1130,7 +1130,7 @@ def progressive_denoising(self, cond, shape, verbose=True, callback=None, quanti if cond is not None: if isinstance(cond, dict): cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else - list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + [x[:batch_size] for x in cond[key]] for key in cond} else: cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] @@ -1229,7 +1229,7 @@ def sample(self, cond, batch_size=16, return_intermediates=False, x_T=None, if cond is not None: if isinstance(cond, dict): cond = {key: cond[key][:batch_size] if not isinstance(cond[key], list) else - list(map(lambda x: x[:batch_size], cond[key])) for key in cond} + [x[:batch_size] for x in cond[key]] for key in cond} else: cond = [c[:batch_size] for c in cond] if isinstance(cond, list) else cond[:batch_size] return self.p_sample_loop(cond, diff --git a/modules/scripts_auto_postprocessing.py b/modules/scripts_auto_postprocessing.py index 30d6d6586f0..d63078de50e 100644 --- a/modules/scripts_auto_postprocessing.py +++ b/modules/scripts_auto_postprocessing.py @@ -17,7 +17,7 @@ def ui(self, is_img2img): return self.postprocessing_controls.values() def postprocess_image(self, p, script_pp, *args): - args_dict = {k: v for k, v in zip(self.postprocessing_controls, args)} + args_dict = dict(zip(self.postprocessing_controls, args)) pp = scripts_postprocessing.PostprocessedImage(script_pp.image) pp.info = {} diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 81573b7817f..e374aeb888d 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -37,7 +37,7 @@ def apply_optimizations(): optimization_method = None - can_use_sdp = hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(getattr(torch.nn.functional, "scaled_dot_product_attention")) # not everyone has torch 2.x to use sdp + can_use_sdp = hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(torch.nn.functional.scaled_dot_product_attention) # not everyone has torch 2.x to use sdp if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0)): print("Applying xformers cross attention optimization.") diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index b623d53d146..a174bbe1041 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -49,7 +49,7 @@ def split_cross_attention_forward_v1(self, x, context=None, mask=None): v_in = self.to_v(context_v) del context, context_k, context_v, x - q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q_in, k_in, v_in)) + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in)) del q_in, k_in, v_in dtype = q.dtype @@ -98,7 +98,7 @@ def split_cross_attention_forward(self, x, context=None, mask=None): del context, x - q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q_in, k_in, v_in)) + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in)) del q_in, k_in, v_in r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) @@ -229,7 +229,7 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): with devices.without_autocast(disable=not shared.opts.upcast_attn): k = k * self.scale - q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v)) r = einsum_op(q, k, v) r = r.to(dtype) return self.to_out(rearrange(r, '(b h) n d -> b n (h d)', h=h)) @@ -334,7 +334,7 @@ def xformers_attention_forward(self, x, context=None, mask=None): k_in = self.to_k(context_k) v_in = self.to_v(context_v) - q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b n h d', h=h), (q_in, k_in, v_in)) + q, k, v = (rearrange(t, 'b n (h d) -> b n h d', h=h) for t in (q_in, k_in, v_in)) del q_in, k_in, v_in dtype = q.dtype @@ -460,7 +460,7 @@ def xformers_attnblock_forward(self, x): k = self.k(h_) v = self.v(h_) b, c, h, w = q.shape - q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v)) dtype = q.dtype if shared.opts.upcast_attn: q, k = q.float(), k.float() @@ -482,7 +482,7 @@ def sdp_attnblock_forward(self, x): k = self.k(h_) v = self.v(h_) b, c, h, w = q.shape - q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v)) dtype = q.dtype if shared.opts.upcast_attn: q, k = q.float(), k.float() @@ -506,7 +506,7 @@ def sub_quad_attnblock_forward(self, x): k = self.k(h_) v = self.v(h_) b, c, h, w = q.shape - q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + q, k, v = (rearrange(t, 'b c h w -> b (h w) c') for t in (q, k, v)) q = q.contiguous() k = k.contiguous() v = v.contiguous() diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index bfcc557494f..7427648fba0 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -83,7 +83,7 @@ def before_sample(self, x, ts, cond, unconditional_conditioning): conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) unconditional_conditioning = prompt_parser.reconstruct_cond_batch(unconditional_conditioning, self.step) - assert all([len(conds) == 1 for conds in conds_list]), 'composition via AND is not supported for DDIM/PLMS samplers' + assert all(len(conds) == 1 for conds in conds_list), 'composition via AND is not supported for DDIM/PLMS samplers' cond = tensor # for DDIM, shapes must match, we can't just process cond and uncond independently; diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 3b8e9622296..2f733cf5c28 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -86,7 +86,7 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): conds_list, tensor = prompt_parser.reconstruct_multicond_batch(cond, self.step) uncond = prompt_parser.reconstruct_cond_batch(uncond, self.step) - 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)" + 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)" batch_size = len(conds_list) repeats = [len(conds_list[i]) for i in range(batch_size)] diff --git a/modules/shared.py b/modules/shared.py index 7d70f041a33..e269158588f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ def list_samplers(): "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (px)"), "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (px)"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt"), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in hypernetworks.keys()]}, refresh=reload_hypernetworks), + "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + list(hypernetworks.keys())}, refresh=reload_hypernetworks), })) options_templates.update(options_section(('ui', "User interface"), { @@ -403,7 +403,7 @@ def list_samplers(): "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), - "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), + "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), @@ -583,7 +583,7 @@ def reorder(self): if item.section not in section_ids: section_ids[item.section] = len(section_ids) - self.data_labels = {k: v for k, v in sorted(settings_items, key=lambda x: section_ids[x[1].section])} + self.data_labels = dict(sorted(settings_items, key=lambda x: section_ids[x[1].section])) def cast_value(self, key, value): """casts an arbitrary to the same type as this setting's value with key diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 9ed9ba459cb..c37bb2ad8f2 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -167,7 +167,7 @@ def load_from_file(self, path, filename): if 'string_to_param' in data: param_dict = data['string_to_param'] if hasattr(param_dict, '_parameters'): - param_dict = getattr(param_dict, '_parameters') # fix for torch 1.12.1 loading saved file from torch 1.11 + param_dict = param_dict._parameters # fix for torch 1.12.1 loading saved file from torch 1.11 assert len(param_dict) == 1, 'embedding file has multiple terms in it' emb = next(iter(param_dict.items()))[1] # diffuser concepts diff --git a/modules/ui.py b/modules/ui.py index 782b569da7d..84d661b29af 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1222,7 +1222,7 @@ def update_interp_description(value): ) def get_textual_inversion_template_names(): - return sorted([x for x in textual_inversion.textual_inversion_templates]) + return sorted(textual_inversion.textual_inversion_templates) with gr.Tab(label="Train", id="train"): gr.HTML(value="

    Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images [wiki]

    ") @@ -1230,8 +1230,8 @@ def get_textual_inversion_template_names(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") - train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=[x for x in shared.hypernetworks.keys()]) - create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted([x for x in shared.hypernetworks.keys()])}, "refresh_train_hypernetwork_name") + train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=list(shared.hypernetworks.keys())) + create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted(shared.hypernetworks.keys())}, "refresh_train_hypernetwork_name") with FormRow(): embedding_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005", elem_id="train_embedding_learn_rate") @@ -1808,7 +1808,7 @@ def apply_field(obj, field, condition=None, init_field=None): if type(x) == gr.Dropdown: def check_dropdown(val): if getattr(x, 'multiselect', False): - return all([value in x.choices for value in val]) + return all(value in x.choices for value in val) else: return val in x.choices diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 800e467a13f..ab585917639 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -26,7 +26,7 @@ def register_page(page): def fetch_file(filename: str = ""): from starlette.responses import FileResponse - if not any([Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs]): + if not any(Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs): raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.") ext = os.path.splitext(filename)[1].lower() @@ -326,7 +326,7 @@ def save_preview(index, images, filename): is_allowed = False for extra_page in ui.stored_extra_pages: - if any([path_is_parent(x, filename) for x in extra_page.allowed_directories_for_previews()]): + if any(path_is_parent(x, filename) for x in extra_page.allowed_directories_for_previews()): is_allowed = True break diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 46fa9cb0545..cac73c511d6 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -23,7 +23,7 @@ def register_tmp_file(gradio, filename): def check_tmp_file(gradio, filename): if hasattr(gradio, 'temp_file_sets'): - return any([filename in fileset for fileset in gradio.temp_file_sets]) + return any(filename in fileset for fileset in gradio.temp_file_sets) if hasattr(gradio, 'temp_dirs'): return any(Path(temp_dir).resolve() in Path(filename).resolve().parents for temp_dir in gradio.temp_dirs) From 550256db1ce18778a9d56ff343d844c61b9f9b83 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 11:19:16 +0300 Subject: [PATCH 0142/2418] ruff manual fixes --- .../LDSR/sd_hijack_autoencoder.py | 10 +++++----- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 14 +++++++------- extensions-builtin/SwinIR/swinir_model_arch.py | 6 +++++- .../SwinIR/swinir_model_arch_v2.py | 11 +++++++++-- modules/api/api.py | 18 ++++++++++++------ modules/codeformer/codeformer_arch.py | 7 +++++-- modules/codeformer/vqgan_arch.py | 4 ++-- modules/generation_parameters_copypaste.py | 4 ++-- modules/models/diffusion/ddpm_edit.py | 14 ++++++++------ modules/models/diffusion/uni_pc/uni_pc.py | 7 +++++-- modules/safe.py | 2 +- modules/sd_samplers_compvis.py | 2 +- modules/textual_inversion/image_embedding.py | 2 +- modules/textual_inversion/learn_schedule.py | 4 ++-- pyproject.toml | 5 ++++- 15 files changed, 69 insertions(+), 41 deletions(-) diff --git a/extensions-builtin/LDSR/sd_hijack_autoencoder.py b/extensions-builtin/LDSR/sd_hijack_autoencoder.py index f457ca9388f..8cc82d54316 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -24,7 +24,7 @@ def __init__(self, n_embed, embed_dim, ckpt_path=None, - ignore_keys=[], + ignore_keys=None, image_key="image", colorize_nlabels=None, monitor=None, @@ -62,7 +62,7 @@ def __init__(self, print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") if ckpt_path is not None: - self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys) + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys or []) self.scheduler_config = scheduler_config self.lr_g_factor = lr_g_factor @@ -81,11 +81,11 @@ def ema_scope(self, context=None): if context is not None: print(f"{context}: Restored training weights") - def init_from_ckpt(self, path, ignore_keys=list()): + def init_from_ckpt(self, path, ignore_keys=None): sd = torch.load(path, map_location="cpu")["state_dict"] keys = list(sd.keys()) for k in keys: - for ik in ignore_keys: + for ik in ignore_keys or []: if k.startswith(ik): print("Deleting key {} from state_dict.".format(k)) del sd[k] @@ -270,7 +270,7 @@ def to_rgb(self, x): class VQModelInterface(VQModel): def __init__(self, embed_dim, *args, **kwargs): - super().__init__(embed_dim=embed_dim, *args, **kwargs) + super().__init__(*args, embed_dim=embed_dim, **kwargs) self.embed_dim = embed_dim def encode(self, x): diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index d8fc30e36b6..f16d6504379 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -48,7 +48,7 @@ def __init__(self, beta_schedule="linear", loss_type="l2", ckpt_path=None, - ignore_keys=[], + ignore_keys=None, load_only_unet=False, monitor="val/loss", use_ema=True, @@ -100,7 +100,7 @@ def __init__(self, if monitor is not None: self.monitor = monitor if ckpt_path is not None: - self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys, only_model=load_only_unet) + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys or [], only_model=load_only_unet) self.register_schedule(given_betas=given_betas, beta_schedule=beta_schedule, timesteps=timesteps, linear_start=linear_start, linear_end=linear_end, cosine_s=cosine_s) @@ -182,13 +182,13 @@ def ema_scope(self, context=None): if context is not None: print(f"{context}: Restored training weights") - def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): + def init_from_ckpt(self, path, ignore_keys=None, only_model=False): sd = torch.load(path, map_location="cpu") if "state_dict" in list(sd.keys()): sd = sd["state_dict"] keys = list(sd.keys()) for k in keys: - for ik in ignore_keys: + for ik in ignore_keys or []: if k.startswith(ik): print("Deleting key {} from state_dict.".format(k)) del sd[k] @@ -444,7 +444,7 @@ def __init__(self, conditioning_key = None ckpt_path = kwargs.pop("ckpt_path", None) ignore_keys = kwargs.pop("ignore_keys", []) - super().__init__(conditioning_key=conditioning_key, *args, **kwargs) + super().__init__(*args, conditioning_key=conditioning_key, **kwargs) self.concat_mode = concat_mode self.cond_stage_trainable = cond_stage_trainable self.cond_stage_key = cond_stage_key @@ -1418,10 +1418,10 @@ class Layout2ImgDiffusionV1(LatentDiffusionV1): # TODO: move all layout-specific hacks to this class def __init__(self, cond_stage_key, *args, **kwargs): assert cond_stage_key == 'coordinates_bbox', 'Layout2ImgDiffusion only for cond_stage_key="coordinates_bbox"' - super().__init__(cond_stage_key=cond_stage_key, *args, **kwargs) + super().__init__(*args, cond_stage_key=cond_stage_key, **kwargs) def log_images(self, batch, N=8, *args, **kwargs): - logs = super().log_images(batch=batch, N=N, *args, **kwargs) + logs = super().log_images(*args, batch=batch, N=N, **kwargs) key = 'train' if self.training else 'validation' dset = self.trainer.datamodule.datasets[key] diff --git a/extensions-builtin/SwinIR/swinir_model_arch.py b/extensions-builtin/SwinIR/swinir_model_arch.py index 863f42db6f5..75f7bedc794 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -644,13 +644,17 @@ class SwinIR(nn.Module): """ def __init__(self, img_size=64, patch_size=1, in_chans=3, - embed_dim=96, depths=[6, 6, 6, 6], num_heads=[6, 6, 6, 6], + embed_dim=96, depths=None, num_heads=None, window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, norm_layer=nn.LayerNorm, ape=False, patch_norm=True, use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv', **kwargs): super(SwinIR, self).__init__() + + depths = depths or [6, 6, 6, 6] + num_heads = num_heads or [6, 6, 6, 6] + num_in_ch = in_chans num_out_ch = in_chans num_feat = 64 diff --git a/extensions-builtin/SwinIR/swinir_model_arch_v2.py b/extensions-builtin/SwinIR/swinir_model_arch_v2.py index 0e28ae6eefa..d4c0b0da3c4 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -74,9 +74,12 @@ class WindowAttention(nn.Module): """ def __init__(self, dim, window_size, num_heads, qkv_bias=True, attn_drop=0., proj_drop=0., - pretrained_window_size=[0, 0]): + pretrained_window_size=None): super().__init__() + + pretrained_window_size = pretrained_window_size or [0, 0] + self.dim = dim self.window_size = window_size # Wh, Ww self.pretrained_window_size = pretrained_window_size @@ -698,13 +701,17 @@ class Swin2SR(nn.Module): """ def __init__(self, img_size=64, patch_size=1, in_chans=3, - embed_dim=96, depths=[6, 6, 6, 6], num_heads=[6, 6, 6, 6], + embed_dim=96, depths=None, num_heads=None, window_size=7, mlp_ratio=4., qkv_bias=True, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, norm_layer=nn.LayerNorm, ape=False, patch_norm=True, use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv', **kwargs): super(Swin2SR, self).__init__() + + depths = depths or [6, 6, 6, 6] + num_heads = num_heads or [6, 6, 6, 6] + num_in_ch = in_chans num_out_ch = in_chans num_feat = 64 diff --git a/modules/api/api.py b/modules/api/api.py index f52d371b750..9efb558e6b7 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -34,14 +34,16 @@ def upscaler_to_index(name: str): try: return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) - except Exception: - raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in shared.sd_upscalers])}") + except Exception as e: + raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in shared.sd_upscalers])}") from e + def script_name_to_index(name, scripts): try: return [script.title().lower() for script in scripts].index(name.lower()) - except Exception: - raise HTTPException(status_code=422, detail=f"Script '{name}' not found") + except Exception as e: + raise HTTPException(status_code=422, detail=f"Script '{name}' not found") from e + def validate_sampler_name(name): config = sd_samplers.all_samplers_map.get(name, None) @@ -50,20 +52,23 @@ def validate_sampler_name(name): return name + def setUpscalers(req: dict): reqDict = vars(req) reqDict['extras_upscaler_1'] = reqDict.pop('upscaler_1', None) reqDict['extras_upscaler_2'] = reqDict.pop('upscaler_2', None) return reqDict + def decode_base64_to_image(encoding): if encoding.startswith("data:image/"): encoding = encoding.split(";")[1].split(",")[1] try: image = Image.open(BytesIO(base64.b64decode(encoding))) return image - except Exception: - raise HTTPException(status_code=500, detail="Invalid encoded image") + except Exception as e: + raise HTTPException(status_code=500, detail="Invalid encoded image") from e + def encode_pil_to_base64(image): with io.BytesIO() as output_bytes: @@ -94,6 +99,7 @@ def encode_pil_to_base64(image): return base64.b64encode(bytes_data) + def api_middleware(app: FastAPI): rich_available = True try: diff --git a/modules/codeformer/codeformer_arch.py b/modules/codeformer/codeformer_arch.py index 00c407dee3a..ff1c0b4b832 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -161,10 +161,13 @@ def forward(self, enc_feat, dec_feat, w=1): class CodeFormer(VQAutoEncoder): def __init__(self, dim_embd=512, n_head=8, n_layers=9, codebook_size=1024, latent_size=256, - connect_list=['32', '64', '128', '256'], - fix_modules=['quantize','generator']): + connect_list=None, + fix_modules=None): super(CodeFormer, self).__init__(512, 64, [1, 2, 2, 4, 4, 8], 'nearest',2, [16], codebook_size) + connect_list = connect_list or ['32', '64', '128', '256'] + fix_modules = fix_modules or ['quantize', 'generator'] + if fix_modules is not None: for module in fix_modules: for param in getattr(self, module).parameters(): diff --git a/modules/codeformer/vqgan_arch.py b/modules/codeformer/vqgan_arch.py index 820e6b12720..b24a0394cd7 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -326,7 +326,7 @@ def forward(self, x): @ARCH_REGISTRY.register() class VQAutoEncoder(nn.Module): - def __init__(self, img_size, nf, ch_mult, quantizer="nearest", res_blocks=2, attn_resolutions=[16], codebook_size=1024, emb_dim=256, + def __init__(self, img_size, nf, ch_mult, quantizer="nearest", res_blocks=2, attn_resolutions=None, codebook_size=1024, emb_dim=256, beta=0.25, gumbel_straight_through=False, gumbel_kl_weight=1e-8, model_path=None): super().__init__() logger = get_root_logger() @@ -337,7 +337,7 @@ def __init__(self, img_size, nf, ch_mult, quantizer="nearest", res_blocks=2, att self.embed_dim = emb_dim self.ch_mult = ch_mult self.resolution = img_size - self.attn_resolutions = attn_resolutions + self.attn_resolutions = attn_resolutions or [16] self.quantizer_type = quantizer self.encoder = Encoder( self.in_channels, diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index f1c59c4664f..7fbbe7071de 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -19,14 +19,14 @@ class ParamBinding: - def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=[]): + def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None): self.paste_button = paste_button self.tabname = tabname self.source_text_component = source_text_component self.source_image_component = source_image_component self.source_tabname = source_tabname self.override_settings_component = override_settings_component - self.paste_field_names = paste_field_names + self.paste_field_names = paste_field_names or [] def reset(): diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 094321170b0..af4dea15e13 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -52,7 +52,7 @@ def __init__(self, beta_schedule="linear", loss_type="l2", ckpt_path=None, - ignore_keys=[], + ignore_keys=None, load_only_unet=False, monitor="val/loss", use_ema=True, @@ -107,7 +107,7 @@ def __init__(self, print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.") if ckpt_path is not None: - self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys, only_model=load_only_unet) + self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys or [], only_model=load_only_unet) # If initialing from EMA-only checkpoint, create EMA model after loading. if self.use_ema and not load_ema: @@ -194,7 +194,9 @@ def ema_scope(self, context=None): if context is not None: print(f"{context}: Restored training weights") - def init_from_ckpt(self, path, ignore_keys=list(), only_model=False): + def init_from_ckpt(self, path, ignore_keys=None, only_model=False): + ignore_keys = ignore_keys or [] + sd = torch.load(path, map_location="cpu") if "state_dict" in list(sd.keys()): sd = sd["state_dict"] @@ -473,7 +475,7 @@ def __init__(self, conditioning_key = None ckpt_path = kwargs.pop("ckpt_path", None) ignore_keys = kwargs.pop("ignore_keys", []) - super().__init__(conditioning_key=conditioning_key, *args, load_ema=load_ema, **kwargs) + super().__init__(*args, conditioning_key=conditioning_key, load_ema=load_ema, **kwargs) self.concat_mode = concat_mode self.cond_stage_trainable = cond_stage_trainable self.cond_stage_key = cond_stage_key @@ -1433,10 +1435,10 @@ class Layout2ImgDiffusion(LatentDiffusion): # TODO: move all layout-specific hacks to this class def __init__(self, cond_stage_key, *args, **kwargs): assert cond_stage_key == 'coordinates_bbox', 'Layout2ImgDiffusion only for cond_stage_key="coordinates_bbox"' - super().__init__(cond_stage_key=cond_stage_key, *args, **kwargs) + super().__init__(*args, cond_stage_key=cond_stage_key, **kwargs) def log_images(self, batch, N=8, *args, **kwargs): - logs = super().log_images(batch=batch, N=N, *args, **kwargs) + logs = super().log_images(*args, batch=batch, N=N, **kwargs) key = 'train' if self.training else 'validation' dset = self.trainer.datamodule.datasets[key] diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index a4c4ef4e42b..6f8ad6315b1 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -178,13 +178,13 @@ def model_wrapper( model, noise_schedule, model_type="noise", - model_kwargs={}, + model_kwargs=None, guidance_type="uncond", #condition=None, #unconditional_condition=None, guidance_scale=1., classifier_fn=None, - classifier_kwargs={}, + classifier_kwargs=None, ): """Create a wrapper function for the noise prediction model. @@ -275,6 +275,9 @@ def model_fn(x, t_continuous) -> noise: A noise prediction model that accepts the noised data and the continuous time as the inputs. """ + model_kwargs = model_kwargs or [] + classifier_kwargs = classifier_kwargs or [] + def get_model_input_time(t_continuous): """ Convert the continuous-time `t_continuous` (in [epsilon, T]) to the model input time. diff --git a/modules/safe.py b/modules/safe.py index e6c2f2c0b7b..2d5b972fa22 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -104,7 +104,7 @@ def check_pt(filename, extra_handler): def load(filename, *args, **kwargs): - return load_with_extra(filename, extra_handler=global_extra_handler, *args, **kwargs) + return load_with_extra(filename, *args, extra_handler=global_extra_handler, **kwargs) def load_with_extra(filename, extra_handler=None, *args, **kwargs): diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 7427648fba0..b1ee3be75e9 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -55,7 +55,7 @@ def launch_sampling(self, steps, func): def p_sample_ddim_hook(self, x_dec, cond, ts, unconditional_conditioning, *args, **kwargs): x_dec, ts, cond, unconditional_conditioning = self.before_sample(x_dec, ts, cond, unconditional_conditioning) - res = self.orig_p_sample_ddim(x_dec, cond, ts, unconditional_conditioning=unconditional_conditioning, *args, **kwargs) + res = self.orig_p_sample_ddim(x_dec, cond, ts, *args, unconditional_conditioning=unconditional_conditioning, **kwargs) x_dec, ts, cond, unconditional_conditioning, res = self.after_sample(x_dec, ts, cond, unconditional_conditioning, res) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index ee0e850acbc..d85a4888650 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -17,7 +17,7 @@ def default(self, obj): class EmbeddingDecoder(json.JSONDecoder): def __init__(self, *args, **kwargs): - json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) + json.JSONDecoder.__init__(self, *args, object_hook=self.object_hook, **kwargs) def object_hook(self, d): if 'TORCHTENSOR' in d: diff --git a/modules/textual_inversion/learn_schedule.py b/modules/textual_inversion/learn_schedule.py index f63fc72ff8d..fda5889845c 100644 --- a/modules/textual_inversion/learn_schedule.py +++ b/modules/textual_inversion/learn_schedule.py @@ -32,8 +32,8 @@ def __init__(self, learn_rate, max_steps, cur_step=0): self.maxit += 1 return assert self.rates - except (ValueError, AssertionError): - raise Exception('Invalid learning rate schedule. It should be a number or, for example, like "0.001:100, 0.00001:1000, 1e-5:10000" to have lr of 0.001 until step 100, 0.00001 until 1000, and 1e-5 until 10000.') + except (ValueError, AssertionError) as e: + raise Exception('Invalid learning rate schedule. It should be a number or, for example, like "0.001:100, 0.00001:1000, 1e-5:10000" to have lr of 0.001 until step 100, 0.00001 until 1000, and 1e-5 until 10000.') from e def __iter__(self): diff --git a/pyproject.toml b/pyproject.toml index 2f65fd6c773..346a0cde830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,9 @@ ignore = [ ] - [tool.ruff.per-file-ignores] "webui.py" = ["E402"] # Module level import not at top of file + +[tool.ruff.flake8-bugbear] +# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. +extend-immutable-calls = ["fastapi.Depends", "fastapi.security.HTTPBasic"] \ No newline at end of file From a5121e7a0623db328a9462d340d389ed6737374a Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 11:37:18 +0300 Subject: [PATCH 0143/2418] fixes for B007 --- extensions-builtin/LDSR/ldsr_model_arch.py | 2 +- extensions-builtin/Lora/lora.py | 2 +- extensions-builtin/ScuNET/scripts/scunet_model.py | 2 +- extensions-builtin/SwinIR/swinir_model_arch.py | 2 +- extensions-builtin/SwinIR/swinir_model_arch_v2.py | 2 +- modules/codeformer_model.py | 2 +- modules/esrgan_model.py | 8 ++------ modules/extra_networks.py | 2 +- modules/generation_parameters_copypaste.py | 2 +- modules/hypernetworks/hypernetwork.py | 12 ++++++------ modules/images.py | 2 +- modules/interrogate.py | 4 ++-- modules/prompt_parser.py | 14 +++++++------- modules/safe.py | 4 ++-- modules/scripts.py | 10 +++++----- modules/scripts_postprocessing.py | 8 ++++---- modules/sd_hijack_clip.py | 2 +- modules/shared.py | 6 +++--- modules/textual_inversion/learn_schedule.py | 2 +- modules/textual_inversion/textual_inversion.py | 10 +++++----- modules/ui.py | 6 +++--- modules/ui_extra_networks.py | 2 +- modules/ui_tempdir.py | 2 +- modules/upscaler.py | 2 +- pyproject.toml | 1 - scripts/prompts_from_file.py | 2 +- scripts/sd_upscale.py | 4 ++-- scripts/xyz_grid.py | 2 +- 28 files changed, 57 insertions(+), 62 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index a5fb8907d61..27e385496e7 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -88,7 +88,7 @@ def run(model, selected_path, custom_steps, eta): x_t = None logs = None - for n in range(n_runs): + for _ in range(n_runs): if custom_shape is not None: x_t = torch.randn(1, custom_shape[1], custom_shape[2], custom_shape[3]).to(model.device) x_t = repeat(x_t, '1 c h w -> b c h w', b=custom_shape[0]) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 9795540ff71..7b56136ffbc 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -418,7 +418,7 @@ def infotext_pasted(infotext, params): added = [] - for k, v in params.items(): + for k in params: if not k.startswith("AddNet Model "): continue diff --git a/extensions-builtin/ScuNET/scripts/scunet_model.py b/extensions-builtin/ScuNET/scripts/scunet_model.py index aa2fdb3a953..1f5ea0d3d82 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -132,7 +132,7 @@ def load_model(self, path: str): model = net(in_nc=3, config=[4, 4, 4, 4, 4, 4, 4], dim=64) model.load_state_dict(torch.load(filename), strict=True) model.eval() - for k, v in model.named_parameters(): + for _, v in model.named_parameters(): v.requires_grad = False model = model.to(device) diff --git a/extensions-builtin/SwinIR/swinir_model_arch.py b/extensions-builtin/SwinIR/swinir_model_arch.py index 75f7bedc794..de195d9b03a 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -848,7 +848,7 @@ def flops(self): H, W = self.patches_resolution flops += H * W * 3 * self.embed_dim * 9 flops += self.patch_embed.flops() - for i, layer in enumerate(self.layers): + for layer in self.layers: flops += layer.flops() flops += H * W * 3 * self.embed_dim * self.embed_dim flops += self.upsample.flops() diff --git a/extensions-builtin/SwinIR/swinir_model_arch_v2.py b/extensions-builtin/SwinIR/swinir_model_arch_v2.py index d4c0b0da3c4..15777af9da5 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -1001,7 +1001,7 @@ def flops(self): H, W = self.patches_resolution flops += H * W * 3 * self.embed_dim * 9 flops += self.patch_embed.flops() - for i, layer in enumerate(self.layers): + for layer in self.layers: flops += layer.flops() flops += H * W * 3 * self.embed_dim * self.embed_dim flops += self.upsample.flops() diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index 8e56cb8921a..ececdbae4c4 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -94,7 +94,7 @@ def restore(self, np_image, w=None): self.face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5) self.face_helper.align_warp_face() - for idx, cropped_face in enumerate(self.face_helper.cropped_faces): + for cropped_face in self.face_helper.cropped_faces: cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True) normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer) diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index 85aa6934947..a009eb42700 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -16,9 +16,7 @@ def mod2normal(state_dict): # this code is copied from https://github.com/victorca25/iNNfer if 'conv_first.weight' in state_dict: crt_net = {} - items = [] - for k, v in state_dict.items(): - items.append(k) + items = list(state_dict) crt_net['model.0.weight'] = state_dict['conv_first.weight'] crt_net['model.0.bias'] = state_dict['conv_first.bias'] @@ -52,9 +50,7 @@ def resrgan2normal(state_dict, nb=23): if "conv_first.weight" in state_dict and "body.0.rdb1.conv1.weight" in state_dict: re8x = 0 crt_net = {} - items = [] - for k, v in state_dict.items(): - items.append(k) + items = list(state_dict) crt_net['model.0.weight'] = state_dict['conv_first.weight'] crt_net['model.0.bias'] = state_dict['conv_first.bias'] diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 1978673d7d9..f9db41bc088 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -91,7 +91,7 @@ def deactivate(p, extra_network_data): """call deactivate for extra networks in extra_network_data in specified order, then call deactivate for all remaining registered networks""" - for extra_network_name, extra_network_args in extra_network_data.items(): + for extra_network_name in extra_network_data: extra_network = extra_network_registry.get(extra_network_name, None) if extra_network is None: continue diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 7fbbe7071de..b0e945a12dc 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -247,7 +247,7 @@ def parse_generation_parameters(x: str): lines.append(lastline) lastline = '' - for i, line in enumerate(lines): + for line in lines: line = line.strip() if line.startswith("Negative prompt:"): done_with_prompt = True diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 6ef0bfdfab9..38ef074f1c6 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -177,34 +177,34 @@ def __init__(self, name=None, enable_sizes=None, layer_structure=None, activatio def weights(self): res = [] - for k, layers in self.layers.items(): + for layers in self.layers.values(): for layer in layers: res += layer.parameters() return res def train(self, mode=True): - for k, layers in self.layers.items(): + for layers in self.layers.values(): for layer in layers: layer.train(mode=mode) for param in layer.parameters(): param.requires_grad = mode def to(self, device): - for k, layers in self.layers.items(): + for layers in self.layers.values(): for layer in layers: layer.to(device) return self def set_multiplier(self, multiplier): - for k, layers in self.layers.items(): + for layers in self.layers.values(): for layer in layers: layer.multiplier = multiplier return self def eval(self): - for k, layers in self.layers.items(): + for layers in self.layers.values(): for layer in layers: layer.eval() for param in layer.parameters(): @@ -619,7 +619,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi try: sd_hijack_checkpoint.add() - for i in range((steps-initial_step) * gradient_step): + for _ in range((steps-initial_step) * gradient_step): if scheduler.finished: break if shared.state.interrupted: diff --git a/modules/images.py b/modules/images.py index 7392cb8bf73..c4e98c757de 100644 --- a/modules/images.py +++ b/modules/images.py @@ -149,7 +149,7 @@ def get_font(fontsize): return ImageFont.truetype(Roboto, fontsize) def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): - for i, line in enumerate(lines): + for line in lines: fnt = initial_fnt fontsize = initial_fontsize while drawing.multiline_textsize(line.text, font=fnt)[0] > line.allowed_width and fontsize > 0: diff --git a/modules/interrogate.py b/modules/interrogate.py index a1c8e537137..111b1322c39 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -207,8 +207,8 @@ def interrogate(self, pil_image): image_features /= image_features.norm(dim=-1, keepdim=True) - for name, topn, items in self.categories(): - matches = self.rank(image_features, items, top_count=topn) + for cat in self.categories(): + matches = self.rank(image_features, cat.items, top_count=cat.topn) for match, score in matches: if shared.opts.interrogate_return_ranks: res += f", ({match}:{score/100:.3f})" diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 3a720721f6a..b4aff704af5 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -143,7 +143,7 @@ def get_learned_conditioning(model, prompts, steps): conds = model.get_learned_conditioning(texts) cond_schedule = [] - for i, (end_at_step, text) in enumerate(prompt_schedule): + for i, (end_at_step, _) in enumerate(prompt_schedule): cond_schedule.append(ScheduledPromptConditioning(end_at_step, conds[i])) cache[prompt] = cond_schedule @@ -219,8 +219,8 @@ def reconstruct_cond_batch(c: List[List[ScheduledPromptConditioning]], current_s res = torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype) for i, cond_schedule in enumerate(c): target_index = 0 - for current, (end_at, cond) in enumerate(cond_schedule): - if current_step <= end_at: + for current, entry in enumerate(cond_schedule): + if current_step <= entry.end_at_step: target_index = current break res[i] = cond_schedule[target_index].cond @@ -234,13 +234,13 @@ def reconstruct_multicond_batch(c: MulticondLearnedConditioning, current_step): tensors = [] conds_list = [] - for batch_no, composable_prompts in enumerate(c.batch): + for composable_prompts in c.batch: conds_for_batch = [] - for cond_index, composable_prompt in enumerate(composable_prompts): + for composable_prompt in composable_prompts: target_index = 0 - for current, (end_at, cond) in enumerate(composable_prompt.schedules): - if current_step <= end_at: + for current, entry in enumerate(composable_prompt.schedules): + if current_step <= entry.end_at_step: target_index = current break diff --git a/modules/safe.py b/modules/safe.py index 2d5b972fa22..1e791c5bdc5 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -95,11 +95,11 @@ def check_pt(filename, extra_handler): except zipfile.BadZipfile: - # if it's not a zip file, it's an olf pytorch format, with five objects written to pickle + # if it's not a zip file, it's an old pytorch format, with five objects written to pickle with open(filename, "rb") as file: unpickler = RestrictedUnpickler(file) unpickler.extra_handler = extra_handler - for i in range(5): + for _ in range(5): unpickler.load() diff --git a/modules/scripts.py b/modules/scripts.py index d945b89fbde..0c12ebd5461 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -231,7 +231,7 @@ def load_scripts(): syspath = sys.path def register_scripts_from_module(module): - for key, script_class in module.__dict__.items(): + for script_class in module.__dict__.values(): if type(script_class) != type: continue @@ -295,9 +295,9 @@ def initialize_scripts(self, is_img2img): auto_processing_scripts = scripts_auto_postprocessing.create_auto_preprocessing_script_data() - for script_class, path, basedir, script_module in auto_processing_scripts + scripts_data: - script = script_class() - script.filename = path + for script_data in auto_processing_scripts + scripts_data: + script = script_data.script_class() + script.filename = script_data.path script.is_txt2img = not is_img2img script.is_img2img = is_img2img @@ -492,7 +492,7 @@ def reload_sources(self, cache): module = script_loading.load_module(script.filename) cache[filename] = module - for key, script_class in module.__dict__.items(): + for script_class in module.__dict__.values(): if type(script_class) == type and issubclass(script_class, Script): self.scripts[si] = script_class() self.scripts[si].filename = filename diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index b11568c0521..6751406cb09 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -66,9 +66,9 @@ def __init__(self): def initialize_scripts(self, scripts_data): self.scripts = [] - for script_class, path, basedir, script_module in scripts_data: - script: ScriptPostprocessing = script_class() - script.filename = path + for script_data in scripts_data: + script: ScriptPostprocessing = script_data.script_class() + script.filename = script_data.path if script.name == "Simple Upscale": continue @@ -124,7 +124,7 @@ def run(self, pp: PostprocessedImage, args): script_args = args[script.args_from:script.args_to] process_args = {} - for (name, component), value in zip(script.controls.items(), script_args): + for (name, component), value in zip(script.controls.items(), script_args): # noqa B007 process_args[name] = value script.process(pp, **process_args) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 9fa5c5c50cd..c0c350f675b 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -223,7 +223,7 @@ def forward(self, texts): self.hijack.fixes = [x.fixes for x in batch_chunk] for fixes in self.hijack.fixes: - for position, embedding in fixes: + for position, embedding in fixes: # noqa: B007 used_embeddings[embedding.name] = embedding z = self.process_tokens(tokens, multipliers) diff --git a/modules/shared.py b/modules/shared.py index e269158588f..913c9e631c9 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -211,7 +211,7 @@ def __init__(self, default=None, label="", component=None, component_args=None, def options_section(section_identifier, options_dict): - for k, v in options_dict.items(): + for v in options_dict.values(): v.section = section_identifier return options_dict @@ -579,7 +579,7 @@ def reorder(self): section_ids = {} settings_items = self.data_labels.items() - for k, item in settings_items: + for _, item in settings_items: if item.section not in section_ids: section_ids[item.section] = len(section_ids) @@ -740,7 +740,7 @@ def walk_files(path, allowed_extensions=None): if allowed_extensions is not None: allowed_extensions = set(allowed_extensions) - for root, dirs, files in os.walk(path): + for root, _, files in os.walk(path): for filename in files: if allowed_extensions is not None: _, ext = os.path.splitext(filename) diff --git a/modules/textual_inversion/learn_schedule.py b/modules/textual_inversion/learn_schedule.py index fda5889845c..c56bea45eb5 100644 --- a/modules/textual_inversion/learn_schedule.py +++ b/modules/textual_inversion/learn_schedule.py @@ -12,7 +12,7 @@ def __init__(self, learn_rate, max_steps, cur_step=0): self.it = 0 self.maxit = 0 try: - for i, pair in enumerate(pairs): + for pair in pairs: if not pair.strip(): continue tmp = pair.split(':') diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c37bb2ad8f2..47035332576 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -29,7 +29,7 @@ def list_textual_inversion_templates(): textual_inversion_templates.clear() - for root, dirs, fns in os.walk(shared.cmd_opts.textual_inversion_templates_dir): + for root, _, fns in os.walk(shared.cmd_opts.textual_inversion_templates_dir): for fn in fns: path = os.path.join(root, fn) @@ -198,7 +198,7 @@ def load_from_dir(self, embdir): if not os.path.isdir(embdir.path): return - for root, dirs, fns in os.walk(embdir.path, followlinks=True): + for root, _, fns in os.walk(embdir.path, followlinks=True): for fn in fns: try: fullfn = os.path.join(root, fn) @@ -215,7 +215,7 @@ def load_from_dir(self, embdir): def load_textual_inversion_embeddings(self, force_reload=False): if not force_reload: need_reload = False - for path, embdir in self.embedding_dirs.items(): + for embdir in self.embedding_dirs.values(): if embdir.has_changed(): need_reload = True break @@ -228,7 +228,7 @@ def load_textual_inversion_embeddings(self, force_reload=False): self.skipped_embeddings.clear() self.expected_shape = self.get_expected_shape() - for path, embdir in self.embedding_dirs.items(): + for embdir in self.embedding_dirs.values(): self.load_from_dir(embdir) embdir.update() @@ -469,7 +469,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st try: sd_hijack_checkpoint.add() - for i in range((steps-initial_step) * gradient_step): + for _ in range((steps-initial_step) * gradient_step): if scheduler.finished: break if shared.state.interrupted: diff --git a/modules/ui.py b/modules/ui.py index 84d661b29af..83bfb7d8c04 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -416,7 +416,7 @@ def create_sampler_and_steps_selection(choices, tabname): def ordered_ui_categories(): user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder.split(","))} - for i, category in sorted(enumerate(shared.ui_reorder_categories), key=lambda x: user_order.get(x[1], x[0] * 2 + 0)): + for _, category in sorted(enumerate(shared.ui_reorder_categories), key=lambda x: user_order.get(x[1], x[0] * 2 + 0)): yield category @@ -1646,7 +1646,7 @@ def request_restart(): with gr.Blocks(theme=shared.gradio_theme, analytics_enabled=False, title="Stable Diffusion") as demo: with gr.Row(elem_id="quicksettings", variant="compact"): - for i, k, item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): + for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): component = create_setting_component(k, is_quicksettings=True) component_dict[k] = component @@ -1673,7 +1673,7 @@ def request_restart(): outputs=[text_settings, result], ) - for i, k, item in quicksettings_list: + for _i, k, _item in quicksettings_list: component = component_dict[k] info = opts.data_labels[k] diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index ab585917639..2fd82e8ea22 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -90,7 +90,7 @@ def create_html(self, tabname): subdirs = {} for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: - for root, dirs, files in os.walk(parentdir): + for root, dirs, _ in os.walk(parentdir): for dirname in dirs: x = os.path.join(root, dirname) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index cac73c511d6..f05049e1fb6 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -72,7 +72,7 @@ def cleanup_tmpdr(): if temp_dir == "" or not os.path.isdir(temp_dir): return - for root, dirs, files in os.walk(temp_dir, topdown=False): + for root, _, files in os.walk(temp_dir, topdown=False): for name in files: _, extension = os.path.splitext(name) if extension != ".png": diff --git a/modules/upscaler.py b/modules/upscaler.py index e145be30eaf..8acb6e96f4f 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -55,7 +55,7 @@ def upscale(self, img: PIL.Image, scale, selected_model: str = None): dest_w = int(img.width * scale) dest_h = int(img.height * scale) - for i in range(3): + for _ in range(3): shape = (img.width, img.height) img = self.do_upscale(img, selected_model) diff --git a/pyproject.toml b/pyproject.toml index 346a0cde830..c88907be842 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ ignore = [ "I001", # Import block is un-sorted or un-formatted "C901", # Function is too complex "C408", # Rewrite as a literal - "B007", # Loop control variable not used within loop body ] diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 149bc85fb41..27af5ff6cea 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -156,7 +156,7 @@ def run(self, p, checkbox_iterate, checkbox_iterate_batch, prompt_txt: str): images = [] all_prompts = [] infotexts = [] - for n, args in enumerate(jobs): + for args in jobs: state.job = f"{state.job_no + 1} out of {state.job_count}" copy_p = copy.copy(p) diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index d873a09c4af..0b1d3096818 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -56,7 +56,7 @@ def run(self, p, _, overlap, upscaler_index, scale_factor): work = [] - for y, h, row in grid.tiles: + for _y, _h, row in grid.tiles: for tiledata in row: work.append(tiledata[2]) @@ -85,7 +85,7 @@ def run(self, p, _, overlap, upscaler_index, scale_factor): work_results += processed.images image_index = 0 - for y, h, row in grid.tiles: + for _y, _h, row in grid.tiles: for tiledata in row: tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height)) image_index += 1 diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 332e0ecde00..38a20381f62 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -704,7 +704,7 @@ def cell(x, y, z, ix, iy, iz): if not include_sub_grids: # Done with sub-grids, drop all related information: - for sg in range(z_count): + for _ in range(z_count): del processed.images[1] del processed.all_prompts[1] del processed.all_seeds[1] From d25219b7e889cf34bccae9cb88497708796efda2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 11:55:09 +0300 Subject: [PATCH 0144/2418] manual fixes for some C408 --- extensions-builtin/LDSR/ldsr_model_arch.py | 4 ++-- extensions-builtin/LDSR/sd_hijack_autoencoder.py | 2 +- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 8 ++++---- modules/api/api.py | 2 +- modules/models/diffusion/ddpm_edit.py | 8 ++++---- modules/models/diffusion/uni_pc/uni_pc.py | 4 ++-- modules/sd_hijack_inpainting.py | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index 27e385496e7..2173de79254 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -157,7 +157,7 @@ def super_resolution(self, image, steps=100, target_scale=2, half_attention=Fals def get_cond(selected_path): - example = dict() + example = {} up_f = 4 c = selected_path.convert('RGB') c = torch.unsqueeze(torchvision.transforms.ToTensor()(c), 0) @@ -195,7 +195,7 @@ def convsample_ddim(model, cond, steps, shape, eta=1.0, callback=None, normals_s @torch.no_grad() def make_convolutional_sample(batch, model, custom_steps=None, eta=1.0, quantize_x0=False, custom_shape=None, temperature=1., noise_dropout=0., corrector=None, corrector_kwargs=None, x_T=None, ddim_use_x0_pred=False): - log = dict() + log = {} z, c, x, xrec, xc = model.get_input(batch, model.first_stage_key, return_first_stage_outputs=True, diff --git a/extensions-builtin/LDSR/sd_hijack_autoencoder.py b/extensions-builtin/LDSR/sd_hijack_autoencoder.py index 8cc82d54316..81c5101b7d7 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -237,7 +237,7 @@ def get_last_layer(self): return self.decoder.conv_out.weight def log_images(self, batch, only_inputs=False, plot_ema=False, **kwargs): - log = dict() + log = {} x = self.get_input(batch, self.image_key) x = x.to(self.device) if only_inputs: diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index f16d6504379..57c02d12656 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -375,7 +375,7 @@ def _get_rows_from_list(self, samples): @torch.no_grad() def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwargs): - log = dict() + log = {} x = self.get_input(batch, self.first_stage_key) N = min(x.shape[0], N) n_row = min(x.shape[0], n_row) @@ -383,7 +383,7 @@ def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwarg log["inputs"] = x # get diffusion row - diffusion_row = list() + diffusion_row = [] x_start = x[:n_row] for t in range(self.num_timesteps): @@ -1247,7 +1247,7 @@ def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=200, ddim_eta= use_ddim = ddim_steps is not None - log = dict() + log = {} z, c, x, xrec, xc = self.get_input(batch, self.first_stage_key, return_first_stage_outputs=True, force_c_encode=True, @@ -1274,7 +1274,7 @@ def log_images(self, batch, N=8, n_row=4, sample=True, ddim_steps=200, ddim_eta= if plot_diffusion_rows: # get diffusion row - diffusion_row = list() + diffusion_row = [] z_start = z[:n_row] for t in range(self.num_timesteps): if t % self.log_every_t == 0 or t == self.num_timesteps - 1: diff --git a/modules/api/api.py b/modules/api/api.py index 9efb558e6b7..594fa655d77 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -165,7 +165,7 @@ async def http_exception_handler(request: Request, e: HTTPException): class Api: def __init__(self, app: FastAPI, queue_lock: Lock): if shared.cmd_opts.api_auth: - self.credentials = dict() + self.credentials = {} for auth in shared.cmd_opts.api_auth.split(","): user, password = auth.split(":") self.credentials[user] = password diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index af4dea15e13..3fb76b651e1 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -405,7 +405,7 @@ def _get_rows_from_list(self, samples): @torch.no_grad() def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwargs): - log = dict() + log = {} x = self.get_input(batch, self.first_stage_key) N = min(x.shape[0], N) n_row = min(x.shape[0], n_row) @@ -413,7 +413,7 @@ def log_images(self, batch, N=8, n_row=2, sample=True, return_keys=None, **kwarg log["inputs"] = x # get diffusion row - diffusion_row = list() + diffusion_row = [] x_start = x[:n_row] for t in range(self.num_timesteps): @@ -1263,7 +1263,7 @@ def log_images(self, batch, N=4, n_row=4, sample=True, ddim_steps=200, ddim_eta= use_ddim = False - log = dict() + log = {} z, c, x, xrec, xc = self.get_input(batch, self.first_stage_key, return_first_stage_outputs=True, force_c_encode=True, @@ -1291,7 +1291,7 @@ def log_images(self, batch, N=4, n_row=4, sample=True, ddim_steps=200, ddim_eta= if plot_diffusion_rows: # get diffusion row - diffusion_row = list() + diffusion_row = [] z_start = z[:n_row] for t in range(self.num_timesteps): if t % self.log_every_t == 0 or t == self.num_timesteps - 1: diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 6f8ad6315b1..f6c49f87482 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -344,7 +344,7 @@ def model_fn(x, t_continuous, condition, unconditional_condition): t_in = torch.cat([t_continuous] * 2) if isinstance(condition, dict): assert isinstance(unconditional_condition, dict) - c_in = dict() + c_in = {} for k in condition: if isinstance(condition[k], list): c_in[k] = [torch.cat([ @@ -355,7 +355,7 @@ def model_fn(x, t_continuous, condition, unconditional_condition): unconditional_condition[k], condition[k]]) elif isinstance(condition, list): - c_in = list() + c_in = [] assert isinstance(unconditional_condition, list) for i in range(len(condition)): c_in.append(torch.cat([unconditional_condition[i], condition[i]])) diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 058575b792f..c1977b19419 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -23,7 +23,7 @@ def get_model_output(x, t): if isinstance(c, dict): assert isinstance(unconditional_conditioning, dict) - c_in = dict() + c_in = {} for k in c: if isinstance(c[k], list): c_in[k] = [ From 3ec7b705c78b7aca9569c92a419837352c7a4ec6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 21:21:32 +0300 Subject: [PATCH 0145/2418] suggestions and fixes from the PR --- extensions-builtin/Lora/scripts/lora_script.py | 2 +- extensions-builtin/SwinIR/swinir_model_arch.py | 6 +----- extensions-builtin/SwinIR/swinir_model_arch_v2.py | 11 ++--------- modules/codeformer/codeformer_arch.py | 7 ++----- modules/hypernetworks/ui.py | 4 ++-- modules/models/diffusion/uni_pc/uni_pc.py | 4 ++-- modules/scripts_postprocessing.py | 2 +- modules/sd_hijack_clip.py | 2 +- modules/shared.py | 2 +- modules/textual_inversion/textual_inversion.py | 3 +-- modules/ui.py | 4 ++-- 11 files changed, 16 insertions(+), 31 deletions(-) diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index b70e2de733c..13d297d7dc4 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -53,7 +53,7 @@ def before_ui(): shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), { - "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None"] + list(lora.available_loras)}, refresh=lora.list_available_loras), + "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None", *lora.available_loras]}, refresh=lora.list_available_loras), })) diff --git a/extensions-builtin/SwinIR/swinir_model_arch.py b/extensions-builtin/SwinIR/swinir_model_arch.py index de195d9b03a..73e37cfad54 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -644,17 +644,13 @@ class SwinIR(nn.Module): """ def __init__(self, img_size=64, patch_size=1, in_chans=3, - embed_dim=96, depths=None, num_heads=None, + embed_dim=96, depths=(6, 6, 6, 6), num_heads=(6, 6, 6, 6), window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, norm_layer=nn.LayerNorm, ape=False, patch_norm=True, use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv', **kwargs): super(SwinIR, self).__init__() - - depths = depths or [6, 6, 6, 6] - num_heads = num_heads or [6, 6, 6, 6] - num_in_ch = in_chans num_out_ch = in_chans num_feat = 64 diff --git a/extensions-builtin/SwinIR/swinir_model_arch_v2.py b/extensions-builtin/SwinIR/swinir_model_arch_v2.py index 15777af9da5..3ca9be78247 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -74,12 +74,9 @@ class WindowAttention(nn.Module): """ def __init__(self, dim, window_size, num_heads, qkv_bias=True, attn_drop=0., proj_drop=0., - pretrained_window_size=None): + pretrained_window_size=(0, 0)): super().__init__() - - pretrained_window_size = pretrained_window_size or [0, 0] - self.dim = dim self.window_size = window_size # Wh, Ww self.pretrained_window_size = pretrained_window_size @@ -701,17 +698,13 @@ class Swin2SR(nn.Module): """ def __init__(self, img_size=64, patch_size=1, in_chans=3, - embed_dim=96, depths=None, num_heads=None, + embed_dim=96, depths=(6, 6, 6, 6), num_heads=(6, 6, 6, 6), window_size=7, mlp_ratio=4., qkv_bias=True, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, norm_layer=nn.LayerNorm, ape=False, patch_norm=True, use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv', **kwargs): super(Swin2SR, self).__init__() - - depths = depths or [6, 6, 6, 6] - num_heads = num_heads or [6, 6, 6, 6] - num_in_ch = in_chans num_out_ch = in_chans num_feat = 64 diff --git a/modules/codeformer/codeformer_arch.py b/modules/codeformer/codeformer_arch.py index ff1c0b4b832..45c70f84f71 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -161,13 +161,10 @@ def forward(self, enc_feat, dec_feat, w=1): class CodeFormer(VQAutoEncoder): def __init__(self, dim_embd=512, n_head=8, n_layers=9, codebook_size=1024, latent_size=256, - connect_list=None, - fix_modules=None): + connect_list=('32', '64', '128', '256'), + fix_modules=('quantize', 'generator')): super(CodeFormer, self).__init__(512, 64, [1, 2, 2, 4, 4, 8], 'nearest',2, [16], codebook_size) - connect_list = connect_list or ['32', '64', '128', '256'] - fix_modules = fix_modules or ['quantize', 'generator'] - if fix_modules is not None: for module in fix_modules: for param in getattr(self, module).parameters(): diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index e3f9eb13d24..8b6255e2b67 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -5,13 +5,13 @@ from modules import devices, sd_hijack, shared not_available = ["hardswish", "multiheadattention"] -keys = [x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available] +keys = [x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict if x not in not_available] def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False, dropout_structure=None): filename = modules.hypernetworks.hypernetwork.create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, dropout_structure) - return gr.Dropdown.update(choices=sorted(shared.hypernetworks.keys())), f"Created: {filename}", "" + return gr.Dropdown.update(choices=sorted(shared.hypernetworks)), f"Created: {filename}", "" def train_hypernetwork(*args): diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index f6c49f87482..a227b947812 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -275,8 +275,8 @@ def model_fn(x, t_continuous) -> noise: A noise prediction model that accepts the noised data and the continuous time as the inputs. """ - model_kwargs = model_kwargs or [] - classifier_kwargs = classifier_kwargs or [] + model_kwargs = model_kwargs or {} + classifier_kwargs = classifier_kwargs or {} def get_model_input_time(t_continuous): """ diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index 6751406cb09..bac1335dc35 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -124,7 +124,7 @@ def run(self, pp: PostprocessedImage, args): script_args = args[script.args_from:script.args_to] process_args = {} - for (name, component), value in zip(script.controls.items(), script_args): # noqa B007 + for (name, _component), value in zip(script.controls.items(), script_args): process_args[name] = value script.process(pp, **process_args) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index c0c350f675b..cc6e8c21ec6 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -223,7 +223,7 @@ def forward(self, texts): self.hijack.fixes = [x.fixes for x in batch_chunk] for fixes in self.hijack.fixes: - for position, embedding in fixes: # noqa: B007 + for _position, embedding in fixes: used_embeddings[embedding.name] = embedding z = self.process_tokens(tokens, multipliers) diff --git a/modules/shared.py b/modules/shared.py index 913c9e631c9..ac67adc0253 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ def list_samplers(): "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (px)"), "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (px)"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt"), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + list(hypernetworks.keys())}, refresh=reload_hypernetworks), + "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", hypernetworks]}, refresh=reload_hypernetworks), })) options_templates.update(options_section(('ui', "User interface"), { diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 47035332576..9e1b2b9a848 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -166,8 +166,7 @@ def load_from_file(self, path, filename): # textual inversion embeddings if 'string_to_param' in data: param_dict = data['string_to_param'] - if hasattr(param_dict, '_parameters'): - param_dict = param_dict._parameters # fix for torch 1.12.1 loading saved file from torch 1.11 + param_dict = getattr(param_dict, '_parameters', param_dict) # fix for torch 1.12.1 loading saved file from torch 1.11 assert len(param_dict) == 1, 'embedding file has multiple terms in it' emb = next(iter(param_dict.items()))[1] # diffuser concepts diff --git a/modules/ui.py b/modules/ui.py index 83bfb7d8c04..7ee99473a3c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1230,8 +1230,8 @@ def get_textual_inversion_template_names(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") - train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=list(shared.hypernetworks.keys())) - create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted(shared.hypernetworks.keys())}, "refresh_train_hypernetwork_name") + train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=sorted(shared.hypernetworks)) + create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted(shared.hypernetworks)}, "refresh_train_hypernetwork_name") with FormRow(): embedding_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005", elem_id="train_embedding_learn_rate") From 8aa87c564a79965013715d56a5f90d2a34d5d6ee Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 10 May 2023 23:41:08 +0300 Subject: [PATCH 0146/2418] add UI to edit defaults allow setting defaults for elements in extensions' tabs fix a problem with ESRGAN upscalers disappearing after UI reload implicit change: HTML element id for train tab from tab_ti to tab_train (will this break things?) --- modules/modelloader.py | 27 ++---- modules/ui.py | 122 ++++-------------------- modules/ui_loadsave.py | 208 +++++++++++++++++++++++++++++++++++++++++ style.css | 4 + webui.py | 6 +- 5 files changed, 242 insertions(+), 125 deletions(-) create mode 100644 modules/ui_loadsave.py diff --git a/modules/modelloader.py b/modules/modelloader.py index 25612bf81e0..2a479bcb56d 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -116,20 +116,6 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None): pass -builtin_upscaler_classes = [] -forbidden_upscaler_classes = set() - - -def list_builtin_upscalers(): - builtin_upscaler_classes.clear() - builtin_upscaler_classes.extend(Upscaler.__subclasses__()) - -def forbid_loaded_nonbuiltin_upscalers(): - for cls in Upscaler.__subclasses__(): - if cls not in builtin_upscaler_classes: - forbidden_upscaler_classes.add(cls) - - def load_upscalers(): # We can only do this 'magic' method to dynamically load upscalers if they are referenced, # so we'll try to import any _model.py files before looking in __subclasses__ @@ -145,10 +131,17 @@ def load_upscalers(): datas = [] commandline_options = vars(shared.cmd_opts) - for cls in Upscaler.__subclasses__(): - if cls in forbidden_upscaler_classes: - continue + # some of upscaler classes will not go away after reloading their modules, and we'll end + # up with two copies of those classes. The newest copy will always be the last in the list, + # so we go from end to beginning and ignore duplicates + used_classes = {} + for cls in reversed(Upscaler.__subclasses__()): + classname = str(cls) + if classname not in used_classes: + used_classes[classname] = cls + + for cls in reversed(used_classes.values()): name = cls.__name__ cmd_name = f"{name.lower().replace('upscaler', '')}_models_path" scaler = cls(commandline_options.get(cmd_name, None)) diff --git a/modules/ui.py b/modules/ui.py index 7ee99473a3c..1efb656a471 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -13,7 +13,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 sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress +from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path @@ -86,16 +86,6 @@ def send_gradio_gallery_to_image(x): return None return image_from_url_text(x[0]) -def visit(x, func, path=""): - if hasattr(x, 'children'): - if isinstance(x, gr.Tabs) and x.elem_id is not None: - # Tabs element can't have a label, have to use elem_id instead - func(f"{path}/Tabs@{x.elem_id}", x) - for c in x.children: - visit(c, func, path) - elif x.label is not None: - func(f"{path}/{x.label}", x) - def add_style(name: str, prompt: str, negative_prompt: str): if name is None: @@ -1471,6 +1461,8 @@ def fun(): return res + loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config_file) + components = [] component_dict = {} shared.settings_components = component_dict @@ -1558,6 +1550,9 @@ def run_settings_single(value, key): current_row.__exit__() current_tab.__exit__() + with gr.TabItem("Defaults", id="defaults", elem_id="settings_tab_defaults"): + loadsave.create_ui() + with gr.TabItem("Actions", id="actions", elem_id="settings_tab_actions"): request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications") download_localization = gr.Button(value='Download localization template', elem_id="download_localization") @@ -1631,7 +1626,7 @@ def request_restart(): (extras_interface, "Extras", "extras"), (pnginfo_interface, "PNG Info", "pnginfo"), (modelmerger_interface, "Checkpoint Merger", "modelmerger"), - (train_interface, "Train", "ti"), + (train_interface, "Train", "train"), ] interfaces += script_callbacks.ui_tabs_callback() @@ -1659,6 +1654,16 @@ def request_restart(): with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"): interface.render() + for interface, _label, ifid in interfaces: + if ifid in ["extensions", "settings"]: + continue + + loadsave.add_block(interface, ifid) + + loadsave.add_component(f"webui/Tabs@{tabs.elem_id}", tabs) + + loadsave.setup_ui() + if os.path.exists(os.path.join(script_path, "notification.mp3")): gr.Audio(interactive=False, value=os.path.join(script_path, "notification.mp3"), elem_id="audio_notification", visible=False) @@ -1747,97 +1752,8 @@ def modelmerger(*args): ] ) - ui_config_file = cmd_opts.ui_config_file - ui_settings = {} - settings_count = len(ui_settings) - error_loading = False - - try: - if os.path.exists(ui_config_file): - with open(ui_config_file, "r", encoding="utf8") as file: - ui_settings = json.load(file) - except Exception: - error_loading = True - print("Error loading settings:", file=sys.stderr) - print(traceback.format_exc(), file=sys.stderr) - - def loadsave(path, x): - def apply_field(obj, field, condition=None, init_field=None): - key = f"{path}/{field}" - - if getattr(obj, 'custom_script_source', None) is not None: - key = f"customscript/{obj.custom_script_source}/{key}" - - if getattr(obj, 'do_not_save_to_config', False): - return - - saved_value = ui_settings.get(key, None) - if saved_value is None: - ui_settings[key] = getattr(obj, field) - elif condition and not condition(saved_value): - pass - - # this warning is generally not useful; - # print(f'Warning: Bad ui setting value: {key}: {saved_value}; Default value "{getattr(obj, field)}" will be used instead.') - else: - setattr(obj, field, saved_value) - if init_field is not None: - init_field(saved_value) - - if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number, gr.Dropdown, ToolButton] and x.visible: - apply_field(x, 'visible') - - if type(x) == gr.Slider: - apply_field(x, 'value') - apply_field(x, 'minimum') - apply_field(x, 'maximum') - apply_field(x, 'step') - - if type(x) == gr.Radio: - apply_field(x, 'value', lambda val: val in x.choices) - - if type(x) == gr.Checkbox: - apply_field(x, 'value') - - if type(x) == gr.Textbox: - apply_field(x, 'value') - - if type(x) == gr.Number: - apply_field(x, 'value') - - if type(x) == gr.Dropdown: - def check_dropdown(val): - if getattr(x, 'multiselect', False): - return all(value in x.choices for value in val) - else: - return val in x.choices - - apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None)) - - def check_tab_id(tab_id): - tab_items = list(filter(lambda e: isinstance(e, gr.TabItem), x.children)) - if type(tab_id) == str: - tab_ids = [t.id for t in tab_items] - return tab_id in tab_ids - elif type(tab_id) == int: - return tab_id >= 0 and tab_id < len(tab_items) - else: - return False - - if type(x) == gr.Tabs: - apply_field(x, 'selected', check_tab_id) - - visit(txt2img_interface, loadsave, "txt2img") - visit(img2img_interface, loadsave, "img2img") - visit(extras_interface, loadsave, "extras") - visit(modelmerger_interface, loadsave, "modelmerger") - visit(train_interface, loadsave, "train") - - loadsave(f"webui/Tabs@{tabs.elem_id}", tabs) - - if not error_loading and (not os.path.exists(ui_config_file) or settings_count != len(ui_settings)): - with open(ui_config_file, "w", encoding="utf8") as file: - json.dump(ui_settings, file, indent=4) + loadsave.dump_defaults() + demo.ui_loadsave = loadsave # Required as a workaround for change() event not triggering when loading values from ui-config.json interp_description.value = update_interp_description(interp_method.value) diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py new file mode 100644 index 00000000000..728fec9eaad --- /dev/null +++ b/modules/ui_loadsave.py @@ -0,0 +1,208 @@ +import json +import os + +import gradio as gr + +from modules import errors +from modules.ui_components import ToolButton + + +class UiLoadsave: + """allows saving and restorig default values for gradio components""" + + def __init__(self, filename): + self.filename = filename + self.ui_settings = {} + self.component_mapping = {} + self.error_loading = False + self.finalized_ui = False + + self.ui_defaults_view = None + self.ui_defaults_apply = None + self.ui_defaults_review = None + + try: + if os.path.exists(self.filename): + self.ui_settings = self.read_from_file() + except Exception as e: + self.error_loading = True + errors.display(e, "loading settings") + + def add_component(self, path, x): + """adds component to the registry of tracked components""" + + assert not self.finalized_ui + + def apply_field(obj, field, condition=None, init_field=None): + key = f"{path}/{field}" + + if getattr(obj, 'custom_script_source', None) is not None: + key = f"customscript/{obj.custom_script_source}/{key}" + + if getattr(obj, 'do_not_save_to_config', False): + return + + saved_value = self.ui_settings.get(key, None) + if saved_value is None: + self.ui_settings[key] = getattr(obj, field) + elif condition and not condition(saved_value): + pass + else: + setattr(obj, field, saved_value) + if init_field is not None: + init_field(saved_value) + + if field == 'value' and key not in self.component_mapping: + self.component_mapping[key] = x + + if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number, gr.Dropdown, ToolButton] and x.visible: + apply_field(x, 'visible') + + if type(x) == gr.Slider: + apply_field(x, 'value') + apply_field(x, 'minimum') + apply_field(x, 'maximum') + apply_field(x, 'step') + + if type(x) == gr.Radio: + apply_field(x, 'value', lambda val: val in x.choices) + + if type(x) == gr.Checkbox: + apply_field(x, 'value') + + if type(x) == gr.Textbox: + apply_field(x, 'value') + + if type(x) == gr.Number: + apply_field(x, 'value') + + if type(x) == gr.Dropdown: + def check_dropdown(val): + if getattr(x, 'multiselect', False): + return all(value in x.choices for value in val) + else: + return val in x.choices + + apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None)) + + def check_tab_id(tab_id): + tab_items = list(filter(lambda e: isinstance(e, gr.TabItem), x.children)) + if type(tab_id) == str: + tab_ids = [t.id for t in tab_items] + return tab_id in tab_ids + elif type(tab_id) == int: + return 0 <= tab_id < len(tab_items) + else: + return False + + if type(x) == gr.Tabs: + apply_field(x, 'selected', check_tab_id) + + def add_block(self, x, path=""): + """adds all components inside a gradio block x to the registry of tracked components""" + + if hasattr(x, 'children'): + if isinstance(x, gr.Tabs) and x.elem_id is not None: + # Tabs element can't have a label, have to use elem_id instead + self.add_component(f"{path}/Tabs@{x.elem_id}", x) + for c in x.children: + self.add_block(c, path) + elif x.label is not None: + self.add_component(f"{path}/{x.label}", x) + + def read_from_file(self): + with open(self.filename, "r", encoding="utf8") as file: + return json.load(file) + + def write_to_file(self, current_ui_settings): + with open(self.filename, "w", encoding="utf8") as file: + json.dump(current_ui_settings, file, indent=4) + + def dump_defaults(self): + """saves default values to a file unless tjhe file is present and there was an error loading default values at start""" + + if self.error_loading and os.path.exists(self.filename): + return + + self.write_to_file(self.ui_settings) + + def iter_changes(self, current_ui_settings, values): + """ + given a dictionary with defaults from a file and current values from gradio elements, returns + an iterator over tuples of values that are not the same between the file and the current; + tuple contents are: path, old value, new value + """ + + for (path, component), new_value in zip(self.component_mapping.items(), values): + old_value = current_ui_settings.get(path) + + choices = getattr(component, 'choices', None) + if isinstance(new_value, int) and choices: + if new_value >= len(choices): + continue + + new_value = choices[new_value] + + if new_value == old_value: + continue + + if old_value is None and new_value == '' or new_value == []: + continue + + yield path, old_value, new_value + + def ui_view(self, *values): + text = [""] + + for path, old_value, new_value in self.iter_changes(self.read_from_file(), values): + if old_value is None: + old_value = "None" + + text.append(f"") + + if len(text) == 1: + text.append("") + + text.append("") + return "".join(text) + + def ui_apply(self, *values): + num_changed = 0 + + current_ui_settings = self.read_from_file() + + for path, _, new_value in self.iter_changes(current_ui_settings.copy(), values): + num_changed += 1 + current_ui_settings[path] = new_value + + if num_changed == 0: + return "No changes." + + self.write_to_file(current_ui_settings) + + return f"Wrote {num_changed} changes." + + def create_ui(self): + """creates ui elements for editing defaults UI, without adding any logic to them""" + + gr.HTML( + f"This page allows you to change default values in UI elements on other tabs.
    " + f"Make your changes, press 'View changes' to review the changed default values,
    " + f"then press 'Apply' to write them to {self.filename}.
    " + f"New defaults will apply after you restart the UI.
    " + ) + + with gr.Row(): + self.ui_defaults_view = gr.Button(value='View changes', elem_id="ui_defaults_view", variant="secondary") + self.ui_defaults_apply = gr.Button(value='Apply', elem_id="ui_defaults_apply", variant="primary") + + self.ui_defaults_review = gr.HTML("") + + def setup_ui(self): + """adds logic to elements created with create_ui; all add_block class must be made before this""" + + assert not self.finalized_ui + self.finalized_ui = True + + self.ui_defaults_view.click(fn=self.ui_view, inputs=list(self.component_mapping.values()), outputs=[self.ui_defaults_review]) + self.ui_defaults_apply.click(fn=self.ui_apply, inputs=list(self.component_mapping.values()), outputs=[self.ui_defaults_review]) diff --git a/style.css b/style.css index b823c7ddbc5..4ac919b5e8c 100644 --- a/style.css +++ b/style.css @@ -414,6 +414,10 @@ table.settings-value-table td{ max-width: 36em; } +.ui-defaults-none{ + color: #aaa !important; +} + /* live preview */ .progressDiv{ position: relative; diff --git a/webui.py b/webui.py index 5d5e80b58e0..2eecfaa087e 100644 --- a/webui.py +++ b/webui.py @@ -181,14 +181,11 @@ def initialize(): gfpgan.setup_model(cmd_opts.gfpgan_models_path) startup_timer.record("setup gfpgan") - modelloader.list_builtin_upscalers() - startup_timer.record("list builtin upscalers") - modules.scripts.load_scripts() startup_timer.record("load scripts") modelloader.load_upscalers() - #startup_timer.record("load upscalers") #Is this necessary? I don't know. + startup_timer.record("load upscalers") modules.sd_vae.refresh_vae_list() startup_timer.record("refresh VAE") @@ -388,7 +385,6 @@ def fastapi_setup(self): localization.list_localizations(cmd_opts.localizations_dir) - modelloader.forbid_loaded_nonbuiltin_upscalers() modules.scripts.reload_scripts() startup_timer.record("load scripts") From c8732dfa6f763332962d97ff040af156e24a9e62 Mon Sep 17 00:00:00 2001 From: Louis Del Valle <92354925+nero-dv@users.noreply.github.com> Date: Wed, 10 May 2023 22:05:18 -0500 Subject: [PATCH 0147/2418] Update sub_quadratic_attention.py 1. Determine the number of query chunks. 2. Calculate the final shape of the res tensor. 3. Initialize the tensor with the calculated shape and dtype, (same dtype as the input tensors, usually) Can initialize the tensor as a zero-filled tensor with the correct shape and dtype, then compute the attention scores for each query chunk and fill the corresponding slice of tensor. --- modules/sub_quadratic_attention.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index 055953236e5..f80c1600a53 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -202,13 +202,22 @@ def get_query_chunk(chunk_idx: int) -> Tensor: value=value, ) - # TODO: maybe we should use torch.empty_like(query) to allocate storage in-advance, - # and pass slices to be mutated, instead of torch.cat()ing the returned slices - res = torch.cat([ - compute_query_chunk_attn( + # slices of res tensor are mutable, modifications made + # to the slices will affect the original tensor. + # if output of compute_query_chunk_attn function has same number of + # dimensions as input query tensor, we initialize tensor like this: + num_query_chunks = int(np.ceil(q_tokens / query_chunk_size)) + query_shape = get_query_chunk(0).shape + res_shape = (query_shape[0], query_shape[1] * num_query_chunks, *query_shape[2:]) + res_dtype = get_query_chunk(0).dtype + res = torch.zeros(res_shape, dtype=res_dtype) + + for i in range(num_query_chunks): + attn_scores = compute_query_chunk_attn( query=get_query_chunk(i * query_chunk_size), key=key, value=value, - ) for i in range(math.ceil(q_tokens / query_chunk_size)) - ], dim=1) + ) + res[:, i * query_chunk_size:(i + 1) * query_chunk_size, :] = attn_scores + return res From ae17e97898af8dd776b20e104ba9a81fe699e4df Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 11 May 2023 12:26:04 +0800 Subject: [PATCH 0148/2418] UniPC progress bar adjustment --- modules/models/diffusion/uni_pc/uni_pc.py | 70 ++++++++++++----------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index eb5f4e76289..1d1b07bd4eb 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -1,7 +1,7 @@ import torch import torch.nn.functional as F import math -from tqdm.auto import trange +import tqdm class NoiseScheduleVP: @@ -757,40 +757,44 @@ def sample(self, x, steps=20, t_start=None, t_end=None, order=3, skip_type='time vec_t = timesteps[0].expand((x.shape[0])) model_prev_list = [self.model_fn(x, vec_t)] t_prev_list = [vec_t] - # Init the first `order` values by lower order multistep DPM-Solver. - for init_order in range(1, order): - vec_t = timesteps[init_order].expand(x.shape[0]) - x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, init_order, use_corrector=True) - if model_x is None: - model_x = self.model_fn(x, vec_t) - if self.after_update is not None: - self.after_update(x, model_x) - model_prev_list.append(model_x) - t_prev_list.append(vec_t) - for step in trange(order, steps + 1): - vec_t = timesteps[step].expand(x.shape[0]) - if lower_order_final: - step_order = min(order, steps + 1 - step) - else: - step_order = order - #print('this step order:', step_order) - if step == steps: - #print('do not run corrector at the last step') - use_corrector = False - else: - use_corrector = True - x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, step_order, use_corrector=use_corrector) - if self.after_update is not None: - self.after_update(x, model_x) - for i in range(order - 1): - t_prev_list[i] = t_prev_list[i + 1] - model_prev_list[i] = model_prev_list[i + 1] - t_prev_list[-1] = vec_t - # We do not need to evaluate the final model value. - if step < steps: + with tqdm.tqdm(total=steps) as pbar: + # Init the first `order` values by lower order multistep DPM-Solver. + for init_order in range(1, order): + vec_t = timesteps[init_order].expand(x.shape[0]) + x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, init_order, use_corrector=True) if model_x is None: model_x = self.model_fn(x, vec_t) - model_prev_list[-1] = model_x + if self.after_update is not None: + self.after_update(x, model_x) + model_prev_list.append(model_x) + t_prev_list.append(vec_t) + pbar.update() + + for step in range(order, steps + 1): + vec_t = timesteps[step].expand(x.shape[0]) + if lower_order_final: + step_order = min(order, steps + 1 - step) + else: + step_order = order + #print('this step order:', step_order) + if step == steps: + #print('do not run corrector at the last step') + use_corrector = False + else: + use_corrector = True + x, model_x = self.multistep_uni_pc_update(x, model_prev_list, t_prev_list, vec_t, step_order, use_corrector=use_corrector) + if self.after_update is not None: + self.after_update(x, model_x) + for i in range(order - 1): + t_prev_list[i] = t_prev_list[i + 1] + model_prev_list[i] = model_prev_list[i + 1] + t_prev_list[-1] = vec_t + # We do not need to evaluate the final model value. + if step < steps: + if model_x is None: + model_x = self.model_fn(x, vec_t) + model_prev_list[-1] = model_x + pbar.update() else: raise NotImplementedError() if denoise_to_zero: From e334758ec281eaf7723c806713721d12bb568e24 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 07:45:05 +0300 Subject: [PATCH 0149/2418] repair #10266 --- modules/sub_quadratic_attention.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index f80c1600a53..cc38debdc15 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -201,23 +201,15 @@ def get_query_chunk(chunk_idx: int) -> Tensor: key=key, value=value, ) - - # slices of res tensor are mutable, modifications made - # to the slices will affect the original tensor. - # if output of compute_query_chunk_attn function has same number of - # dimensions as input query tensor, we initialize tensor like this: - num_query_chunks = int(np.ceil(q_tokens / query_chunk_size)) - query_shape = get_query_chunk(0).shape - res_shape = (query_shape[0], query_shape[1] * num_query_chunks, *query_shape[2:]) - res_dtype = get_query_chunk(0).dtype - res = torch.zeros(res_shape, dtype=res_dtype) - - for i in range(num_query_chunks): + + res = torch.zeros_like(query) + for i in range(math.ceil(q_tokens / query_chunk_size)): attn_scores = compute_query_chunk_attn( query=get_query_chunk(i * query_chunk_size), key=key, value=value, ) - res[:, i * query_chunk_size:(i + 1) * query_chunk_size, :] = attn_scores + + res[:, i * query_chunk_size:i * query_chunk_size + attn_scores.shape[1], :] = attn_scores return res From b7e160a87d07b2fd1c12812c43786e141cc86bd5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 08:14:45 +0300 Subject: [PATCH 0150/2418] change live preview format to jpeg to prevent unreasonably slow previews for large images, and add an option to let user select the format --- modules/progress.py | 4 ++-- modules/shared.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/progress.py b/modules/progress.py index 948e6f008f7..289dd311d6c 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -95,9 +95,9 @@ def progressapi(req: ProgressRequest): image = shared.state.current_image if image is not None: buffered = io.BytesIO() - image.save(buffered, format="png") + image.save(buffered, format=opts.live_previews_format) base64_image = base64.b64encode(buffered.getvalue()).decode('ascii') - live_preview = f"data:image/png;base64,{base64_image}" + live_preview = f"data:image/{opts.live_previews_format};base64,{base64_image}" id_live_preview = shared.state.id_live_preview else: live_preview = None diff --git a/modules/shared.py b/modules/shared.py index ac67adc0253..fc39161ea9a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -420,6 +420,7 @@ def list_samplers(): options_templates.update(options_section(('ui', "Live previews"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), + "live_previews_format": OptionInfo("jpeg", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), From ef11c197b329a446de55206abfb8d013b65cdb76 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 08:48:08 +0300 Subject: [PATCH 0151/2418] Update clean-fid to loosen transitive dependency pins Diff: https://github.com/GaParmar/clean-fid/compare/bd92e684ff06819058083c5a9fddc6f712045d46...c8ffa420a3923e8fd87c1e75170de2cf59d2644b --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 7bce02e5f10..476029044c1 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -17,7 +17,7 @@ timm==0.6.7 piexif==1.1.3 einops==0.4.1 jsonmerge==1.8.0 -clean-fid==0.1.29 +clean-fid==0.1.35 resize-right==0.0.2 torchdiffeq==0.2.3 kornia==0.6.7 From 1dcd6723242c3d691610f9ed937951baea49c2d1 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Thu, 11 May 2023 14:29:52 +0800 Subject: [PATCH 0152/2418] Update sd_vae.py There is no need to use split. --- modules/sd_vae.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 17d1f7026f4..95262ca3108 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -88,10 +88,9 @@ def refresh_vae_list(): def find_vae_near_checkpoint(checkpoint_file): - checkpoint_path = os.path.basename(checkpoint_file).split('.', 1)[0] + checkpoint_path = os.path.basename(checkpoint_file).rsplit('.', 1)[0] for vae_file in vae_dict.values(): - vae_path = os.path.basename(vae_file).split('.', 1)[0] - if vae_path == checkpoint_path: + if os.path.basename(vae_file).startswith(checkpoint_path): return vae_file return None From 16e4d791224125cef2b91f7cf39893ceffd8bd74 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:05:39 +0300 Subject: [PATCH 0153/2418] paths_internal: deduplicate modules_path --- modules/paths_internal.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 6765bafe016..a3d3e1f8889 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -3,7 +3,8 @@ import argparse import os -script_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +modules_path = os.path.dirname(os.path.realpath(__file__)) +script_path = os.path.dirname(modules_path) sd_configs_path = os.path.join(script_path, "configs") sd_default_config = os.path.join(sd_configs_path, "v1-inference.yaml") @@ -12,7 +13,7 @@ # Parse the --data-dir flag first so we can use it as a base for our other argument default values parser_pre = argparse.ArgumentParser(add_help=False) -parser_pre.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored",) +parser_pre.add_argument("--data-dir", type=str, default=os.path.dirname(modules_path), help="base path where all user data is stored", ) cmd_opts_pre = parser_pre.parse_known_args()[0] data_path = cmd_opts_pre.data_dir From df7070eca22278b25c921ef72d3f97a221d66242 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:06:19 +0300 Subject: [PATCH 0154/2418] Deduplicate get_font code --- modules/images.py | 13 +++++++------ modules/textual_inversion/image_embedding.py | 9 ++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/images.py b/modules/images.py index c4e98c757de..d8527179d52 100644 --- a/modules/images.py +++ b/modules/images.py @@ -24,6 +24,13 @@ LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) +def get_font(fontsize: int): + try: + return ImageFont.truetype(opts.font or Roboto, fontsize) + except Exception: + return ImageFont.truetype(Roboto, fontsize) + + def image_grid(imgs, batch_size=1, rows=None): if rows is None: if opts.n_rows > 0: @@ -142,12 +149,6 @@ def wrap(drawing, text, font, line_length): lines.append(word) return lines - def get_font(fontsize): - try: - return ImageFont.truetype(opts.font or Roboto, fontsize) - except Exception: - return ImageFont.truetype(Roboto, fontsize) - def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): for line in lines: fnt = initial_fnt diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index d85a4888650..5858a55f57c 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -3,9 +3,7 @@ import numpy as np import zlib from PIL import Image, ImageDraw, ImageFont -from fonts.ttf import Roboto import torch -from modules.shared import opts class EmbeddingEncoder(json.JSONEncoder): @@ -136,11 +134,8 @@ def caption_image_overlay(srcimage, title, footerLeft, footerMid, footerRight, t image = srcimage.copy() fontsize = 32 if textfont is None: - try: - textfont = ImageFont.truetype(opts.font or Roboto, fontsize) - textfont = opts.font or Roboto - except Exception: - textfont = Roboto + from modules.images import get_font + textfont = get_font(fontsize) factor = 1.5 gradient = Image.new('RGBA', (1, image.size[1]), color=(0, 0, 0, 0)) From 1332c46b71b169b889d7df420f3285d9022da5cc Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:07:01 +0300 Subject: [PATCH 0155/2418] Drop fonts + font-roboto deps since we only use the single regular cut of Roboto --- modules/Roboto-Regular.ttf | Bin 0 -> 305608 bytes modules/images.py | 6 +++--- modules/paths_internal.py | 2 ++ requirements.txt | 2 -- requirements_versions.txt | 2 -- 5 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 modules/Roboto-Regular.ttf diff --git a/modules/Roboto-Regular.ttf b/modules/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..500b1045b0c94d83d2e6798aaf1faa55a2dab6fc GIT binary patch literal 305608 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
    ^|w#p-*faeRa`IJ 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 z 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�an+ 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 N)wRzvMS%?|2g*+{|xg(;Jq)Q%*B^H@|u6jyEl2 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�W5+L{Oa7%kiR+a_t0aeF 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 Date: Thu, 11 May 2023 10:09:29 +0300 Subject: [PATCH 0156/2418] return wrap_gradio_gpu_call to webui.py for extensions --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 2eecfaa087e..293a16ccda2 100644 --- a/webui.py +++ b/webui.py @@ -36,7 +36,7 @@ from modules import extra_networks, ui_extra_networks_checkpoints from modules import extra_networks_hypernet, ui_extra_networks_hypernets, ui_extra_networks_textual_inversion -from modules.call_queue import wrap_queued_call, queue_lock +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, queue_lock # noqa: F401 # Truncate version number of nightly/local build of PyTorch to not cause exceptions with CodeFormer or Safetensors if ".dev" in torch.__version__ or "+git" in torch.__version__: From 49db24ce273ebccb598fc13de946d1ac3cad38f8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:30:29 +0300 Subject: [PATCH 0157/2418] launch.py: Add debugging envvar to see install output --- launch.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/launch.py b/launch.py index cfc0cffa26a..136e8855ca9 100644 --- a/launch.py +++ b/launch.py @@ -22,6 +22,9 @@ stored_git_tag = None dir_repos = "repositories" +# Whether to default to printing command output +default_command_live = (os.environ.get('WEBUI_LAUNCH_LIVE_OUTPUT') == "1") + if 'GRADIO_ANALYTICS_ENABLED' not in os.environ: os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False' @@ -85,7 +88,7 @@ def git_tag(): return stored_git_tag -def run(command, desc=None, errdesc=None, custom_env=None, live=False): +def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live): if desc is not None: print(desc) @@ -135,7 +138,7 @@ def run_python(code, desc=None, errdesc=None): return run(f'"{python}" -c "{code}"', desc, errdesc) -def run_pip(command, desc=None, live=False): +def run_pip(command, desc=None, live=default_command_live): if args.skip_install: return From 875bc270093b4f89b9d0d20a93a5e8ea514b3f6c Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:34:19 +0300 Subject: [PATCH 0158/2418] launch.py: Simplify run() --- launch.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/launch.py b/launch.py index 136e8855ca9..ae75f6fda45 100644 --- a/launch.py +++ b/launch.py @@ -88,32 +88,36 @@ def git_tag(): return stored_git_tag -def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live): +def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str: if desc is not None: print(desc) - if live: - result = subprocess.run(command, shell=True, env=os.environ if custom_env is None else custom_env) - if result.returncode != 0: - raise RuntimeError(f"""{errdesc or 'Error running command'}. -Command: {command} -Error code: {result.returncode}""") + run_kwargs = { + "args": command, + "shell": True, + "env": os.environ if custom_env is None else custom_env, + "encoding": 'utf8', + "errors": 'ignore', + } - return "" + if not live: + run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE - result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ if custom_env is None else custom_env) + result = subprocess.run(**run_kwargs) if result.returncode != 0: - - message = f"""{errdesc or 'Error running command'}. -Command: {command} -Error code: {result.returncode} -stdout: {result.stdout.decode(encoding="utf8", errors="ignore") if len(result.stdout)>0 else ''} -stderr: {result.stderr.decode(encoding="utf8", errors="ignore") if len(result.stderr)>0 else ''} -""" - raise RuntimeError(message) - - return result.stdout.decode(encoding="utf8", errors="ignore") + error_bits = [ + f"{errdesc or 'Error running command'}.", + f"Command: {command}", + f"Error code: {result.returncode}", + ] + if result.stdout: + error_bits.append(f"stdout: {result.stdout}") + if result.stderr: + error_bits.append(f"stderr: {result.stderr}") + raise RuntimeError("\n".join(error_bits)) + + return (result.stdout or "") def check_run(command): From a09e1e6e18e800191817493253dfc481a07b1868 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:48:48 +0300 Subject: [PATCH 0159/2418] launch.py: Use GitHub archive URLs for gfpgan, clip, openclip instead of git clones --- launch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launch.py b/launch.py index ae75f6fda45..fed14d93a52 100644 --- a/launch.py +++ b/launch.py @@ -248,9 +248,9 @@ def prepare_environment(): requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') - gfpgan_package = os.environ.get('GFPGAN_PACKAGE', "git+https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379") - clip_package = os.environ.get('CLIP_PACKAGE', "git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1") - openclip_package = os.environ.get('OPENCLIP_PACKAGE', "git+https://github.com/mlfoundations/open_clip.git@bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b") + gfpgan_package = os.environ.get('GFPGAN_PACKAGE', "https://github.com/TencentARC/GFPGAN/archive/8d2447a2d918f8eba5a4a01463fd48e45126a379.zip") + clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip") + openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip") stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git") taming_transformers_repo = os.environ.get('TAMING_TRANSFORMERS_REPO', "https://github.com/CompVis/taming-transformers.git") From dd3ca9adf7077cb7209b31fd16a40deffc4948ca Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:35:22 +0300 Subject: [PATCH 0160/2418] launch.py: make torch_index_url an envvar --- launch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launch.py b/launch.py index fed14d93a52..670af87c13e 100644 --- a/launch.py +++ b/launch.py @@ -244,7 +244,8 @@ def run_extensions_installers(settings_file): def prepare_environment(): - torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url https://download.pytorch.org/whl/cu118") + torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118") + torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}") requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') From c702010e575d68fff0b3e640e10b6e750513b5a8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:36:10 +0300 Subject: [PATCH 0161/2418] CI: use CPU wheel repo for PyTorch --- .github/workflows/run_tests.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 9a0b8d22b07..58bc4770c77 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -19,6 +19,11 @@ jobs: **/requirements*txt - name: Run tests run: python launch.py --tests test --no-half --disable-opt-split-attention --use-cpu all --skip-torch-cuda-test + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_PROGRESS_BAR: "off" + TORCH_INDEX_URL: https://download.pytorch.org/whl/cpu + WEBUI_LAUNCH_LIVE_OUTPUT: "1" - name: Upload main app stdout-stderr uses: actions/upload-artifact@v3 if: always() From 5b592669f9917cf885b0f597ac427ba91c847801 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 10:51:52 +0300 Subject: [PATCH 0162/2418] CI: use launch.py for dependencies too --- .github/workflows/run_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 58bc4770c77..0708398b95a 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -17,6 +17,7 @@ jobs: cache: pip cache-dependency-path: | **/requirements*txt + launch.py - name: Run tests run: python launch.py --tests test --no-half --disable-opt-split-attention --use-cpu all --skip-torch-cuda-test env: From 0bfaf613a84613f41946da02571e0e467e88d273 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 13:30:33 +0300 Subject: [PATCH 0163/2418] put the star where it belongs --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index fc39161ea9a..f387b5ae218 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ def list_samplers(): "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (px)"), "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (px)"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt"), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", hypernetworks]}, refresh=reload_hypernetworks), + "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *hypernetworks]}, refresh=reload_hypernetworks), })) options_templates.update(options_section(('ui', "User interface"), { From 483545252f865334a6da84339126cefd59c3d885 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 14:24:22 +0300 Subject: [PATCH 0164/2418] fix broken prompts from file --- scripts/prompts_from_file.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 27af5ff6cea..9607077a7d1 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -97,11 +97,12 @@ def cmdargs(line): def load_prompt_file(file): if file is None: - lines = [] + return None, gr.update(), gr.update(lines=7) else: lines = [x.strip() for x in file.decode('utf8', errors='ignore').split("\n")] + return None, "\n".join(lines), gr.update(lines=7) + - return None, "\n".join(lines), gr.update(lines=7) class Script(scripts.Script): @@ -115,12 +116,12 @@ def ui(self, is_img2img): prompt_txt = gr.Textbox(label="List of prompt inputs", lines=1, elem_id=self.elem_id("prompt_txt")) file = gr.File(label="Upload prompt inputs", type='binary', elem_id=self.elem_id("file")) - file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt]) + file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt], show_progress=False) # We start at one line. When the text changes, we jump to seven lines, or two lines if no \n. # We don't shrink back to 1, because that causes the control to ignore [enter], and it may # be unclear to the user that shift-enter is needed. - prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt]) + prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt], show_progress=False) return [checkbox_iterate, checkbox_iterate_batch, prompt_txt] def run(self, p, checkbox_iterate, checkbox_iterate_batch, prompt_txt: str): From 8ca50f8240fc1b4400ba59be50ad7a75b99cfbcb Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 14:49:14 +0300 Subject: [PATCH 0165/2418] fix broken prompts from file --- CHANGELOG.md | 1 + scripts/prompts_from_file.py | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3fef3d0e6..1a0f7ae521c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ * Fix MPS on PyTorch 2.0.1, Intel Macs * make it so that custom context menu from contextMenu.js only disappears after user's click, ignoring non-user click events * prevent Reload UI button/link from reloading the page when it's not yet ready + * fix prompts from file script failing to read contents from a drag/drop file ## 1.1.1 diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 76dc5778b27..f168389c45a 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -100,30 +100,29 @@ def cmdargs(line): def load_prompt_file(file): if file is None: - lines = [] + return None, gr.update(), gr.update(lines=7) else: lines = [x.strip() for x in file.decode('utf8', errors='ignore').split("\n")] - - return None, "\n".join(lines), gr.update(lines=7) + return None, "\n".join(lines), gr.update(lines=7) class Script(scripts.Script): def title(self): return "Prompts from file or textbox" - def ui(self, is_img2img): + def ui(self, is_img2img): checkbox_iterate = gr.Checkbox(label="Iterate seed every line", value=False, elem_id=self.elem_id("checkbox_iterate")) checkbox_iterate_batch = gr.Checkbox(label="Use same random seed for all lines", value=False, elem_id=self.elem_id("checkbox_iterate_batch")) prompt_txt = gr.Textbox(label="List of prompt inputs", lines=1, elem_id=self.elem_id("prompt_txt")) file = gr.File(label="Upload prompt inputs", type='binary', elem_id=self.elem_id("file")) - file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt]) + file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt], show_progress=False) # We start at one line. When the text changes, we jump to seven lines, or two lines if no \n. # We don't shrink back to 1, because that causes the control to ignore [enter], and it may # be unclear to the user that shift-enter is needed. - prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt]) + prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt], show_progress=False) return [checkbox_iterate, checkbox_iterate_batch, prompt_txt] def run(self, p, checkbox_iterate, checkbox_iterate_batch, prompt_txt: str): From 098d2fda5250f9f418fc641bd0f185cb461ee6d9 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 18:13:35 +0300 Subject: [PATCH 0166/2418] Reindent autocrop with 4 spaces --- modules/textual_inversion/autocrop.py | 202 +++++++++++++------------- 1 file changed, 102 insertions(+), 100 deletions(-) diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index 7770d22f5c2..8e667a4d3ee 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -10,63 +10,64 @@ def crop_image(im, settings): - """ Intelligently crop an image to the subject matter """ - - scale_by = 1 - if is_landscape(im.width, im.height): - scale_by = settings.crop_height / im.height - elif is_portrait(im.width, im.height): - scale_by = settings.crop_width / im.width - elif is_square(im.width, im.height): - if is_square(settings.crop_width, settings.crop_height): - scale_by = settings.crop_width / im.width - elif is_landscape(settings.crop_width, settings.crop_height): - scale_by = settings.crop_width / im.width - elif is_portrait(settings.crop_width, settings.crop_height): - scale_by = settings.crop_height / im.height - - im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) - im_debug = im.copy() - - focus = focal_point(im_debug, settings) - - # take the focal point and turn it into crop coordinates that try to center over the focal - # point but then get adjusted back into the frame - y_half = int(settings.crop_height / 2) - x_half = int(settings.crop_width / 2) - - x1 = focus.x - x_half - if x1 < 0: - x1 = 0 - elif x1 + settings.crop_width > im.width: - x1 = im.width - settings.crop_width - - y1 = focus.y - y_half - if y1 < 0: - y1 = 0 - elif y1 + settings.crop_height > im.height: - y1 = im.height - settings.crop_height - - x2 = x1 + settings.crop_width - y2 = y1 + settings.crop_height - - crop = [x1, y1, x2, y2] - - results = [] - - results.append(im.crop(tuple(crop))) - - if settings.annotate_image: - d = ImageDraw.Draw(im_debug) - rect = list(crop) - rect[2] -= 1 - rect[3] -= 1 - d.rectangle(rect, outline=GREEN) - results.append(im_debug) - if settings.destop_view_image: - im_debug.show() - - return results + """ Intelligently crop an image to the subject matter """ + + scale_by = 1 + if is_landscape(im.width, im.height): + scale_by = settings.crop_height / im.height + elif is_portrait(im.width, im.height): + scale_by = settings.crop_width / im.width + elif is_square(im.width, im.height): + if is_square(settings.crop_width, settings.crop_height): + scale_by = settings.crop_width / im.width + elif is_landscape(settings.crop_width, settings.crop_height): + scale_by = settings.crop_width / im.width + elif is_portrait(settings.crop_width, settings.crop_height): + scale_by = settings.crop_height / im.height + + + im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) + im_debug = im.copy() + + focus = focal_point(im_debug, settings) + + # take the focal point and turn it into crop coordinates that try to center over the focal + # point but then get adjusted back into the frame + y_half = int(settings.crop_height / 2) + x_half = int(settings.crop_width / 2) + + x1 = focus.x - x_half + if x1 < 0: + x1 = 0 + elif x1 + settings.crop_width > im.width: + x1 = im.width - settings.crop_width + + y1 = focus.y - y_half + if y1 < 0: + y1 = 0 + elif y1 + settings.crop_height > im.height: + y1 = im.height - settings.crop_height + + x2 = x1 + settings.crop_width + y2 = y1 + settings.crop_height + + crop = [x1, y1, x2, y2] + + results = [] + + results.append(im.crop(tuple(crop))) + + if settings.annotate_image: + d = ImageDraw.Draw(im_debug) + rect = list(crop) + rect[2] -= 1 + rect[3] -= 1 + d.rectangle(rect, outline=GREEN) + results.append(im_debug) + if settings.destop_view_image: + im_debug.show() + + return results def focal_point(im, settings): corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else [] @@ -86,7 +87,7 @@ def focal_point(im, settings): corner_centroid = None if len(corner_points) > 0: corner_centroid = centroid(corner_points) - corner_centroid.weight = settings.corner_points_weight / weight_pref_total + corner_centroid.weight = settings.corner_points_weight / weight_pref_total pois.append(corner_centroid) entropy_centroid = None @@ -98,7 +99,7 @@ def focal_point(im, settings): face_centroid = None if len(face_points) > 0: face_centroid = centroid(face_points) - face_centroid.weight = settings.face_points_weight / weight_pref_total + face_centroid.weight = settings.face_points_weight / weight_pref_total pois.append(face_centroid) average_point = poi_average(pois, settings) @@ -132,7 +133,7 @@ def focal_point(im, settings): d.rectangle(f.bounding(4), outline=color) d.ellipse(average_point.bounding(max_size), outline=GREEN) - + return average_point @@ -260,10 +261,11 @@ def image_entropy(im): hist = hist[hist > 0] return -np.log2(hist / hist.sum()).sum() + def centroid(pois): - x = [poi.x for poi in pois] - y = [poi.y for poi in pois] - return PointOfInterest(sum(x)/len(pois), sum(y)/len(pois)) + x = [poi.x for poi in pois] + y = [poi.y for poi in pois] + return PointOfInterest(sum(x) / len(pois), sum(y) / len(pois)) def poi_average(pois, settings): @@ -281,59 +283,59 @@ def poi_average(pois, settings): def is_landscape(w, h): - return w > h + return w > h def is_portrait(w, h): - return h > w + return h > w def is_square(w, h): - return w == h + return w == h def download_and_cache_models(dirname): - download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true' - model_file_name = 'face_detection_yunet.onnx' + download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true' + model_file_name = 'face_detection_yunet.onnx' - if not os.path.exists(dirname): - os.makedirs(dirname) + if not os.path.exists(dirname): + os.makedirs(dirname) - cache_file = os.path.join(dirname, model_file_name) - if not os.path.exists(cache_file): - print(f"downloading face detection model from '{download_url}' to '{cache_file}'") - response = requests.get(download_url) - with open(cache_file, "wb") as f: - f.write(response.content) + cache_file = os.path.join(dirname, model_file_name) + if not os.path.exists(cache_file): + print(f"downloading face detection model from '{download_url}' to '{cache_file}'") + response = requests.get(download_url) + with open(cache_file, "wb") as f: + f.write(response.content) - if os.path.exists(cache_file): - return cache_file - return None + if os.path.exists(cache_file): + return cache_file + return None class PointOfInterest: - def __init__(self, x, y, weight=1.0, size=10): - self.x = x - self.y = y - self.weight = weight - self.size = size + def __init__(self, x, y, weight=1.0, size=10): + self.x = x + self.y = y + self.weight = weight + self.size = size - def bounding(self, size): - return [ - self.x - size//2, - self.y - size//2, - self.x + size//2, - self.y + size//2 - ] + def bounding(self, size): + return [ + self.x - size // 2, + self.y - size // 2, + self.x + size // 2, + self.y + size // 2 + ] class Settings: - def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False, dnn_model_path=None): - self.crop_width = crop_width - self.crop_height = crop_height - self.corner_points_weight = corner_points_weight - self.entropy_points_weight = entropy_points_weight - self.face_points_weight = face_points_weight - self.annotate_image = annotate_image - self.destop_view_image = False - self.dnn_model_path = dnn_model_path + def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False, dnn_model_path=None): + self.crop_width = crop_width + self.crop_height = crop_height + self.corner_points_weight = corner_points_weight + self.entropy_points_weight = entropy_points_weight + self.face_points_weight = face_points_weight + self.annotate_image = annotate_image + self.destop_view_image = False + self.dnn_model_path = dnn_model_path From 431bc5a297ff7c17231b92b6c8f8152b2fab8553 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 18:13:54 +0300 Subject: [PATCH 0167/2418] Reindent utils_test with 4 spaces --- test/basic_features/utils_test.py | 86 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/test/basic_features/utils_test.py b/test/basic_features/utils_test.py index 0bfc28a0d30..d9e46b5e438 100644 --- a/test/basic_features/utils_test.py +++ b/test/basic_features/utils_test.py @@ -1,62 +1,64 @@ import unittest import requests + class UtilsTests(unittest.TestCase): - def setUp(self): - self.url_options = "http://localhost:7860/sdapi/v1/options" - self.url_cmd_flags = "http://localhost:7860/sdapi/v1/cmd-flags" - self.url_samplers = "http://localhost:7860/sdapi/v1/samplers" - self.url_upscalers = "http://localhost:7860/sdapi/v1/upscalers" - self.url_sd_models = "http://localhost:7860/sdapi/v1/sd-models" - self.url_hypernetworks = "http://localhost:7860/sdapi/v1/hypernetworks" - self.url_face_restorers = "http://localhost:7860/sdapi/v1/face-restorers" - self.url_realesrgan_models = "http://localhost:7860/sdapi/v1/realesrgan-models" - self.url_prompt_styles = "http://localhost:7860/sdapi/v1/prompt-styles" - self.url_embeddings = "http://localhost:7860/sdapi/v1/embeddings" + def setUp(self): + self.url_options = "http://localhost:7860/sdapi/v1/options" + self.url_cmd_flags = "http://localhost:7860/sdapi/v1/cmd-flags" + self.url_samplers = "http://localhost:7860/sdapi/v1/samplers" + self.url_upscalers = "http://localhost:7860/sdapi/v1/upscalers" + self.url_sd_models = "http://localhost:7860/sdapi/v1/sd-models" + self.url_hypernetworks = "http://localhost:7860/sdapi/v1/hypernetworks" + self.url_face_restorers = "http://localhost:7860/sdapi/v1/face-restorers" + self.url_realesrgan_models = "http://localhost:7860/sdapi/v1/realesrgan-models" + self.url_prompt_styles = "http://localhost:7860/sdapi/v1/prompt-styles" + self.url_embeddings = "http://localhost:7860/sdapi/v1/embeddings" + + def test_options_get(self): + self.assertEqual(requests.get(self.url_options).status_code, 200) + + def test_options_write(self): + response = requests.get(self.url_options) + self.assertEqual(response.status_code, 200) + + pre_value = response.json()["send_seed"] - def test_options_get(self): - self.assertEqual(requests.get(self.url_options).status_code, 200) + self.assertEqual(requests.post(self.url_options, json={"send_seed": not pre_value}).status_code, 200) - def test_options_write(self): - response = requests.get(self.url_options) - self.assertEqual(response.status_code, 200) + response = requests.get(self.url_options) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["send_seed"], not pre_value) - pre_value = response.json()["send_seed"] + requests.post(self.url_options, json={"send_seed": pre_value}) - self.assertEqual(requests.post(self.url_options, json={"send_seed":not pre_value}).status_code, 200) + def test_cmd_flags(self): + self.assertEqual(requests.get(self.url_cmd_flags).status_code, 200) - response = requests.get(self.url_options) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["send_seed"], not pre_value) + def test_samplers(self): + self.assertEqual(requests.get(self.url_samplers).status_code, 200) - requests.post(self.url_options, json={"send_seed": pre_value}) + def test_upscalers(self): + self.assertEqual(requests.get(self.url_upscalers).status_code, 200) - def test_cmd_flags(self): - self.assertEqual(requests.get(self.url_cmd_flags).status_code, 200) + def test_sd_models(self): + self.assertEqual(requests.get(self.url_sd_models).status_code, 200) - def test_samplers(self): - self.assertEqual(requests.get(self.url_samplers).status_code, 200) + def test_hypernetworks(self): + self.assertEqual(requests.get(self.url_hypernetworks).status_code, 200) - def test_upscalers(self): - self.assertEqual(requests.get(self.url_upscalers).status_code, 200) + def test_face_restorers(self): + self.assertEqual(requests.get(self.url_face_restorers).status_code, 200) - def test_sd_models(self): - self.assertEqual(requests.get(self.url_sd_models).status_code, 200) + def test_realesrgan_models(self): + self.assertEqual(requests.get(self.url_realesrgan_models).status_code, 200) - def test_hypernetworks(self): - self.assertEqual(requests.get(self.url_hypernetworks).status_code, 200) + def test_prompt_styles(self): + self.assertEqual(requests.get(self.url_prompt_styles).status_code, 200) - def test_face_restorers(self): - self.assertEqual(requests.get(self.url_face_restorers).status_code, 200) - - def test_realesrgan_models(self): - self.assertEqual(requests.get(self.url_realesrgan_models).status_code, 200) - - def test_prompt_styles(self): - self.assertEqual(requests.get(self.url_prompt_styles).status_code, 200) + def test_embeddings(self): + self.assertEqual(requests.get(self.url_embeddings).status_code, 200) - def test_embeddings(self): - self.assertEqual(requests.get(self.url_embeddings).status_code, 200) if __name__ == "__main__": unittest.main() From cb3f8ff59fe8f142c3ca074b8cbaaf83357f9dc1 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Thu, 11 May 2023 15:55:43 +0000 Subject: [PATCH 0168/2418] Fix symlink scanning --- modules/shared.py | 2 +- modules/ui_extra_networks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index f387b5ae218..210424ac808 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -741,7 +741,7 @@ def walk_files(path, allowed_extensions=None): if allowed_extensions is not None: allowed_extensions = set(allowed_extensions) - for root, _, files in os.walk(path): + for root, _, files in os.walk(path, followlinks=True): for filename in files: if allowed_extensions is not None: _, ext = os.path.splitext(filename) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 2fd82e8ea22..e35d0bfee45 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -90,7 +90,7 @@ def create_html(self, tabname): subdirs = {} for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: - for root, dirs, _ in os.walk(parentdir): + for root, dirs, _ in os.walk(parentdir, followlinks=True): for dirname in dirs: x = os.path.join(root, dirname) From 49a55b410b66b7dd9be9335d8a2e3a71e4f8b15c Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 18:28:15 +0300 Subject: [PATCH 0169/2418] Autofix Ruff W (not W605) (mostly whitespace) --- extensions-builtin/LDSR/ldsr_model_arch.py | 4 +- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 6 +-- .../ScuNET/scunet_model_arch.py | 2 +- .../SwinIR/scripts/swinir_model.py | 2 +- .../SwinIR/swinir_model_arch.py | 2 +- .../SwinIR/swinir_model_arch_v2.py | 52 +++++++++---------- launch.py | 2 +- modules/api/api.py | 4 +- modules/api/models.py | 2 +- modules/cmd_args.py | 2 +- modules/codeformer/codeformer_arch.py | 14 ++--- modules/codeformer/vqgan_arch.py | 38 +++++++------- modules/esrgan_model_arch.py | 4 +- modules/extras.py | 2 +- modules/hypernetworks/hypernetwork.py | 12 ++--- modules/images.py | 2 +- modules/mac_specific.py | 4 +- modules/masking.py | 2 +- modules/ngrok.py | 4 +- modules/processing.py | 2 +- modules/script_callbacks.py | 14 ++--- modules/sd_hijack.py | 12 ++--- modules/sd_hijack_optimizations.py | 32 ++++++------ modules/sd_models.py | 4 +- modules/sd_samplers_kdiffusion.py | 18 +++---- modules/sub_quadratic_attention.py | 2 +- modules/textual_inversion/dataset.py | 4 +- modules/textual_inversion/preprocess.py | 2 +- .../textual_inversion/textual_inversion.py | 16 +++--- modules/ui.py | 18 +++---- modules/ui_extensions.py | 6 +-- modules/xlmr.py | 6 +-- pyproject.toml | 5 +- scripts/img2imgalt.py | 14 ++--- scripts/loopback.py | 8 +-- scripts/poor_mans_outpainting.py | 2 +- scripts/prompt_matrix.py | 2 +- scripts/prompts_from_file.py | 4 +- scripts/sd_upscale.py | 2 +- 39 files changed, 167 insertions(+), 166 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index 2173de79254..7f450086ff9 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -130,11 +130,11 @@ def super_resolution(self, image, steps=100, target_scale=2, half_attention=Fals im_og = im_og.resize((width_downsampled_pre, height_downsampled_pre), Image.LANCZOS) else: print(f"Down sample rate is 1 from {target_scale} / 4 (Not downsampling)") - + # pad width and height to multiples of 64, pads with the edge values of image to avoid artifacts pad_w, pad_h = np.max(((2, 2), np.ceil(np.array(im_og.size) / 64).astype(int)), axis=0) * 64 - im_og.size im_padded = Image.fromarray(np.pad(np.array(im_og), ((0, pad_h), (0, pad_w), (0, 0)), mode='edge')) - + logs = self.run(model["model"], im_padded, diffusion_steps, eta) sample = logs["sample"] diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 57c02d12656..631a08ef090 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -460,7 +460,7 @@ def __init__(self, self.instantiate_cond_stage(cond_stage_config) self.cond_stage_forward = cond_stage_forward self.clip_denoised = False - self.bbox_tokenizer = None + self.bbox_tokenizer = None self.restarted_from_ckpt = False if ckpt_path is not None: @@ -792,7 +792,7 @@ def differentiable_decode_first_stage(self, z, predict_cids=False, force_not_qua z = z.view((z.shape[0], -1, ks[0], ks[1], z.shape[-1])) # (bn, nc, ks[0], ks[1], L ) # 2. apply model loop over last dim - if isinstance(self.first_stage_model, VQModelInterface): + if isinstance(self.first_stage_model, VQModelInterface): output_list = [self.first_stage_model.decode(z[:, :, :, :, i], force_not_quantize=predict_cids or force_not_quantize) for i in range(z.shape[-1])] @@ -890,7 +890,7 @@ def apply_model(self, x_noisy, t, cond, return_ids=False): if hasattr(self, "split_input_params"): assert len(cond) == 1 # todo can only deal with one conditioning atm - assert not return_ids + assert not return_ids ks = self.split_input_params["ks"] # eg. (128, 128) stride = self.split_input_params["stride"] # eg. (64, 64) diff --git a/extensions-builtin/ScuNET/scunet_model_arch.py b/extensions-builtin/ScuNET/scunet_model_arch.py index 8028918afdb..b51a880629b 100644 --- a/extensions-builtin/ScuNET/scunet_model_arch.py +++ b/extensions-builtin/ScuNET/scunet_model_arch.py @@ -265,4 +265,4 @@ def _init_weights(self, m): nn.init.constant_(m.bias, 0) elif isinstance(m, nn.LayerNorm): nn.init.constant_(m.bias, 0) - nn.init.constant_(m.weight, 1.0) \ No newline at end of file + nn.init.constant_(m.weight, 1.0) diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index 55dd94ab8e6..0ba50487494 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -150,7 +150,7 @@ def inference(img, model, tile, tile_overlap, window_size, scale): for w_idx in w_idx_list: if state.interrupted or state.skipped: break - + in_patch = img[..., h_idx: h_idx + tile, w_idx: w_idx + tile] out_patch = model(in_patch) out_patch_mask = torch.ones_like(out_patch) diff --git a/extensions-builtin/SwinIR/swinir_model_arch.py b/extensions-builtin/SwinIR/swinir_model_arch.py index 73e37cfad54..93b9327473a 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -805,7 +805,7 @@ def forward_features(self, x): def forward(self, x): H, W = x.shape[2:] x = self.check_image_size(x) - + self.mean = self.mean.type_as(x) x = (x - self.mean) * self.img_range diff --git a/extensions-builtin/SwinIR/swinir_model_arch_v2.py b/extensions-builtin/SwinIR/swinir_model_arch_v2.py index 3ca9be78247..dad22cca29e 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -241,7 +241,7 @@ def __init__(self, dim, input_resolution, num_heads, window_size=7, shift_size=0 attn_mask = None self.register_buffer("attn_mask", attn_mask) - + def calculate_mask(self, x_size): # calculate attention mask for SW-MSA H, W = x_size @@ -263,7 +263,7 @@ def calculate_mask(self, x_size): attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0)) - return attn_mask + return attn_mask def forward(self, x, x_size): H, W = x_size @@ -288,7 +288,7 @@ def forward(self, x, x_size): attn_windows = self.attn(x_windows, mask=self.attn_mask) # nW*B, window_size*window_size, C else: attn_windows = self.attn(x_windows, mask=self.calculate_mask(x_size).to(x.device)) - + # merge windows attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C) shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C @@ -369,7 +369,7 @@ def flops(self): H, W = self.input_resolution flops = (H // 2) * (W // 2) * 4 * self.dim * 2 * self.dim flops += H * W * self.dim // 2 - return flops + return flops class BasicLayer(nn.Module): """ A basic Swin Transformer layer for one stage. @@ -447,7 +447,7 @@ def _init_respostnorm(self): nn.init.constant_(blk.norm1.weight, 0) nn.init.constant_(blk.norm2.bias, 0) nn.init.constant_(blk.norm2.weight, 0) - + class PatchEmbed(nn.Module): r""" Image to Patch Embedding Args: @@ -492,7 +492,7 @@ def flops(self): flops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1]) if self.norm is not None: flops += Ho * Wo * self.embed_dim - return flops + return flops class RSTB(nn.Module): """Residual Swin Transformer Block (RSTB). @@ -531,7 +531,7 @@ def __init__(self, dim, input_resolution, depth, num_heads, window_size, num_heads=num_heads, window_size=window_size, mlp_ratio=mlp_ratio, - qkv_bias=qkv_bias, + qkv_bias=qkv_bias, drop=drop, attn_drop=attn_drop, drop_path=drop_path, norm_layer=norm_layer, @@ -622,7 +622,7 @@ def __init__(self, scale, num_feat): else: raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.') super(Upsample, self).__init__(*m) - + class Upsample_hf(nn.Sequential): """Upsample module. @@ -642,7 +642,7 @@ def __init__(self, scale, num_feat): m.append(nn.PixelShuffle(3)) else: raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.') - super(Upsample_hf, self).__init__(*m) + super(Upsample_hf, self).__init__(*m) class UpsampleOneStep(nn.Sequential): @@ -667,8 +667,8 @@ def flops(self): H, W = self.input_resolution flops = H * W * self.num_feat * 3 * 9 return flops - - + + class Swin2SR(nn.Module): r""" Swin2SR @@ -699,7 +699,7 @@ class Swin2SR(nn.Module): def __init__(self, img_size=64, patch_size=1, in_chans=3, embed_dim=96, depths=(6, 6, 6, 6), num_heads=(6, 6, 6, 6), - window_size=7, mlp_ratio=4., qkv_bias=True, + window_size=7, mlp_ratio=4., qkv_bias=True, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, norm_layer=nn.LayerNorm, ape=False, patch_norm=True, use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv', @@ -764,7 +764,7 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, num_heads=num_heads[i_layer], window_size=window_size, mlp_ratio=self.mlp_ratio, - qkv_bias=qkv_bias, + qkv_bias=qkv_bias, drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])], # no impact on SR results norm_layer=norm_layer, @@ -776,7 +776,7 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, ) self.layers.append(layer) - + if self.upsampler == 'pixelshuffle_hf': self.layers_hf = nn.ModuleList() for i_layer in range(self.num_layers): @@ -787,7 +787,7 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, num_heads=num_heads[i_layer], window_size=window_size, mlp_ratio=self.mlp_ratio, - qkv_bias=qkv_bias, + qkv_bias=qkv_bias, drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])], # no impact on SR results norm_layer=norm_layer, @@ -799,7 +799,7 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, ) self.layers_hf.append(layer) - + self.norm = norm_layer(self.num_features) # build the last conv layer in deep feature extraction @@ -829,10 +829,10 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, self.conv_aux = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) self.conv_after_aux = nn.Sequential( nn.Conv2d(3, num_feat, 3, 1, 1), - nn.LeakyReLU(inplace=True)) + nn.LeakyReLU(inplace=True)) self.upsample = Upsample(upscale, num_feat) self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) - + elif self.upsampler == 'pixelshuffle_hf': self.conv_before_upsample = nn.Sequential(nn.Conv2d(embed_dim, num_feat, 3, 1, 1), nn.LeakyReLU(inplace=True)) @@ -846,7 +846,7 @@ def __init__(self, img_size=64, patch_size=1, in_chans=3, nn.Conv2d(embed_dim, num_feat, 3, 1, 1), nn.LeakyReLU(inplace=True)) self.conv_last_hf = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) - + elif self.upsampler == 'pixelshuffledirect': # for lightweight SR (to save parameters) self.upsample = UpsampleOneStep(upscale, embed_dim, num_out_ch, @@ -905,7 +905,7 @@ def forward_features(self, x): x = self.patch_unembed(x, x_size) return x - + def forward_features_hf(self, x): x_size = (x.shape[2], x.shape[3]) x = self.patch_embed(x) @@ -919,7 +919,7 @@ def forward_features_hf(self, x): x = self.norm(x) # B L C x = self.patch_unembed(x, x_size) - return x + return x def forward(self, x): H, W = x.shape[2:] @@ -951,7 +951,7 @@ def forward(self, x): x = self.conv_after_body(self.forward_features(x)) + x x_before = self.conv_before_upsample(x) x_out = self.conv_last(self.upsample(x_before)) - + x_hf = self.conv_first_hf(x_before) x_hf = self.conv_after_body_hf(self.forward_features_hf(x_hf)) + x_hf x_hf = self.conv_before_upsample_hf(x_hf) @@ -977,15 +977,15 @@ def forward(self, x): x_first = self.conv_first(x) res = self.conv_after_body(self.forward_features(x_first)) + x_first x = x + self.conv_last(res) - + x = x / self.img_range + self.mean if self.upsampler == "pixelshuffle_aux": return x[:, :, :H*self.upscale, :W*self.upscale], aux - + elif self.upsampler == "pixelshuffle_hf": x_out = x_out / self.img_range + self.mean return x_out[:, :, :H*self.upscale, :W*self.upscale], x[:, :, :H*self.upscale, :W*self.upscale], x_hf[:, :, :H*self.upscale, :W*self.upscale] - + else: return x[:, :, :H*self.upscale, :W*self.upscale] @@ -1014,4 +1014,4 @@ def flops(self): x = torch.randn((1, 3, height, width)) x = model(x) - print(x.shape) \ No newline at end of file + print(x.shape) diff --git a/launch.py b/launch.py index 670af87c13e..62b33f1497a 100644 --- a/launch.py +++ b/launch.py @@ -327,7 +327,7 @@ def prepare_environment(): if args.update_all_extensions: git_pull_recursive(extensions_dir) - + if "--exit" in sys.argv: print("Exiting because of --exit argument") exit(0) diff --git a/modules/api/api.py b/modules/api/api.py index 594fa655d77..165985c3504 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -227,7 +227,7 @@ def get_selectable_script(self, script_name, script_runner): script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) script = script_runner.selectable_scripts[script_idx] return script, script_idx - + def get_scripts_list(self): t2ilist = [str(title.lower()) for title in scripts.scripts_txt2img.titles] i2ilist = [str(title.lower()) for title in scripts.scripts_img2img.titles] @@ -237,7 +237,7 @@ def get_scripts_list(self): def get_script(self, script_name, script_runner): if script_name is None or script_name == "": return None, None - + script_idx = script_name_to_index(script_name, script_runner.scripts) return script_runner.scripts[script_idx] diff --git a/modules/api/models.py b/modules/api/models.py index 4d291076536..006ccdb750f 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -289,4 +289,4 @@ class MemoryResponse(BaseModel): class ScriptsList(BaseModel): txt2img: list = Field(default=None,title="Txt2img", description="Titles of scripts (txt2img)") - img2img: list = Field(default=None,title="Img2img", description="Titles of scripts (img2img)") \ No newline at end of file + img2img: list = Field(default=None,title="Img2img", description="Titles of scripts (img2img)") diff --git a/modules/cmd_args.py b/modules/cmd_args.py index e01ca65512b..f4a4ab36f49 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -102,4 +102,4 @@ parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) -parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') \ No newline at end of file +parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') diff --git a/modules/codeformer/codeformer_arch.py b/modules/codeformer/codeformer_arch.py index 45c70f84f71..12db6814268 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -119,7 +119,7 @@ def forward(self, tgt, tgt_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, query_pos: Optional[Tensor] = None): - + # self attention tgt2 = self.norm1(tgt) q = k = self.with_pos_embed(tgt2, query_pos) @@ -159,7 +159,7 @@ def forward(self, enc_feat, dec_feat, w=1): @ARCH_REGISTRY.register() class CodeFormer(VQAutoEncoder): - def __init__(self, dim_embd=512, n_head=8, n_layers=9, + def __init__(self, dim_embd=512, n_head=8, n_layers=9, codebook_size=1024, latent_size=256, connect_list=('32', '64', '128', '256'), fix_modules=('quantize', 'generator')): @@ -179,14 +179,14 @@ def __init__(self, dim_embd=512, n_head=8, n_layers=9, self.feat_emb = nn.Linear(256, self.dim_embd) # transformer - self.ft_layers = nn.Sequential(*[TransformerSALayer(embed_dim=dim_embd, nhead=n_head, dim_mlp=self.dim_mlp, dropout=0.0) + self.ft_layers = nn.Sequential(*[TransformerSALayer(embed_dim=dim_embd, nhead=n_head, dim_mlp=self.dim_mlp, dropout=0.0) for _ in range(self.n_layers)]) # logits_predict head self.idx_pred_layer = nn.Sequential( nn.LayerNorm(dim_embd), nn.Linear(dim_embd, codebook_size, bias=False)) - + self.channels = { '16': 512, '32': 256, @@ -221,7 +221,7 @@ def forward(self, x, w=0, detach_16=True, code_only=False, adain=False): enc_feat_dict = {} out_list = [self.fuse_encoder_block[f_size] for f_size in self.connect_list] for i, block in enumerate(self.encoder.blocks): - x = block(x) + x = block(x) if i in out_list: enc_feat_dict[str(x.shape[-1])] = x.clone() @@ -266,11 +266,11 @@ def forward(self, x, w=0, detach_16=True, code_only=False, adain=False): fuse_list = [self.fuse_generator_block[f_size] for f_size in self.connect_list] for i, block in enumerate(self.generator.blocks): - x = block(x) + x = block(x) if i in fuse_list: # fuse after i-th block f_size = str(x.shape[-1]) if w>0: x = self.fuse_convs_dict[f_size](enc_feat_dict[f_size].detach(), x, w) out = x # logits doesn't need softmax before cross_entropy loss - return out, logits, lq_feat \ No newline at end of file + return out, logits, lq_feat diff --git a/modules/codeformer/vqgan_arch.py b/modules/codeformer/vqgan_arch.py index b24a0394cd7..09ee6660dc5 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -13,7 +13,7 @@ def normalize(in_channels): return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True) - + @torch.jit.script def swish(x): @@ -210,15 +210,15 @@ def forward(self, x): # compute attention b, c, h, w = q.shape q = q.reshape(b, c, h*w) - q = q.permute(0, 2, 1) + q = q.permute(0, 2, 1) k = k.reshape(b, c, h*w) - w_ = torch.bmm(q, k) + w_ = torch.bmm(q, k) w_ = w_ * (int(c)**(-0.5)) w_ = F.softmax(w_, dim=2) # attend to values v = v.reshape(b, c, h*w) - w_ = w_.permute(0, 2, 1) + w_ = w_.permute(0, 2, 1) h_ = torch.bmm(v, w_) h_ = h_.reshape(b, c, h, w) @@ -270,18 +270,18 @@ def __init__(self, in_channels, nf, emb_dim, ch_mult, num_res_blocks, resolution def forward(self, x): for block in self.blocks: x = block(x) - + return x class Generator(nn.Module): def __init__(self, nf, emb_dim, ch_mult, res_blocks, img_size, attn_resolutions): super().__init__() - self.nf = nf - self.ch_mult = ch_mult + self.nf = nf + self.ch_mult = ch_mult self.num_resolutions = len(self.ch_mult) self.num_res_blocks = res_blocks - self.resolution = img_size + self.resolution = img_size self.attn_resolutions = attn_resolutions self.in_channels = emb_dim self.out_channels = 3 @@ -315,24 +315,24 @@ def __init__(self, nf, emb_dim, ch_mult, res_blocks, img_size, attn_resolutions) blocks.append(nn.Conv2d(block_in_ch, self.out_channels, kernel_size=3, stride=1, padding=1)) self.blocks = nn.ModuleList(blocks) - + def forward(self, x): for block in self.blocks: x = block(x) - + return x - + @ARCH_REGISTRY.register() class VQAutoEncoder(nn.Module): def __init__(self, img_size, nf, ch_mult, quantizer="nearest", res_blocks=2, attn_resolutions=None, codebook_size=1024, emb_dim=256, beta=0.25, gumbel_straight_through=False, gumbel_kl_weight=1e-8, model_path=None): super().__init__() logger = get_root_logger() - self.in_channels = 3 - self.nf = nf - self.n_blocks = res_blocks + self.in_channels = 3 + self.nf = nf + self.n_blocks = res_blocks self.codebook_size = codebook_size self.embed_dim = emb_dim self.ch_mult = ch_mult @@ -363,11 +363,11 @@ def __init__(self, img_size, nf, ch_mult, quantizer="nearest", res_blocks=2, att self.kl_weight ) self.generator = Generator( - self.nf, + self.nf, self.embed_dim, - self.ch_mult, - self.n_blocks, - self.resolution, + self.ch_mult, + self.n_blocks, + self.resolution, self.attn_resolutions ) @@ -432,4 +432,4 @@ def __init__(self, nc=3, ndf=64, n_layers=4, model_path=None): raise ValueError('Wrong params!') def forward(self, x): - return self.main(x) \ No newline at end of file + return self.main(x) diff --git a/modules/esrgan_model_arch.py b/modules/esrgan_model_arch.py index 4de9dd8dadc..2b9888bafbc 100644 --- a/modules/esrgan_model_arch.py +++ b/modules/esrgan_model_arch.py @@ -105,7 +105,7 @@ class ResidualDenseBlock_5C(nn.Module): Modified options that can be used: - "Partial Convolution based Padding" arXiv:1811.11718 - "Spectral normalization" arXiv:1802.05957 - - "ICASSP 2020 - ESRGAN+ : Further Improving ESRGAN" N. C. + - "ICASSP 2020 - ESRGAN+ : Further Improving ESRGAN" N. C. {Rakotonirina} and A. {Rasoanaivo} """ @@ -170,7 +170,7 @@ def forward(self, x): scale = self.sigma * x.detach() if self.is_relative_detach else self.sigma * x sampled_noise = self.noise.repeat(*x.size()).normal_() * scale x = x + sampled_noise - return x + return x def conv1x1(in_planes, out_planes, stride=1): return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) diff --git a/modules/extras.py b/modules/extras.py index eb4f0b420bd..bdf9b3b7a26 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -199,7 +199,7 @@ def filename_nothing(): result_is_inpainting_model = True else: theta_0[key] = theta_func2(a, b, multiplier) - + theta_0[key] = to_half(theta_0[key], save_as_half) shared.state.sampling_step += 1 diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 38ef074f1c6..570b5603d1a 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -540,7 +540,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi return hypernetwork, filename scheduler = LearnRateScheduler(learn_rate, steps, initial_step) - + clip_grad = torch.nn.utils.clip_grad_value_ if clip_grad_mode == "value" else torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else None if clip_grad: clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, initial_step, verbose=False) @@ -593,7 +593,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi print(e) scaler = torch.cuda.amp.GradScaler() - + batch_size = ds.batch_size gradient_step = ds.gradient_step # n steps = batch_size * gradient_step * n image processed @@ -636,7 +636,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi if clip_grad: clip_grad_sched.step(hypernetwork.step) - + with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) if use_weight: @@ -657,14 +657,14 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi _loss_step += loss.item() scaler.scale(loss).backward() - + # go back until we reach gradient accumulation steps if (j + 1) % gradient_step != 0: continue loss_logging.append(_loss_step) if clip_grad: clip_grad(weights, clip_grad_sched.learn_rate) - + scaler.step(optimizer) scaler.update() hypernetwork.step += 1 @@ -674,7 +674,7 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi _loss_step = 0 steps_done = hypernetwork.step + 1 - + epoch_num = hypernetwork.step // steps_per_epoch epoch_step = hypernetwork.step % steps_per_epoch diff --git a/modules/images.py b/modules/images.py index 3b8b62d93ce..b2de3662f29 100644 --- a/modules/images.py +++ b/modules/images.py @@ -367,7 +367,7 @@ def __init__(self, p, seed, prompt, image): self.seed = seed self.prompt = prompt self.image = image - + def hasprompt(self, *args): lower = self.prompt.lower() if self.p is None or self.prompt is None: diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 5c2f92a1541..d74c6b95513 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -42,7 +42,7 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): # MPS workaround for https://github.com/pytorch/pytorch/issues/79383 CondFunc('torch.Tensor.to', lambda orig_func, self, *args, **kwargs: orig_func(self.contiguous(), *args, **kwargs), lambda _, self, *args, **kwargs: self.device.type != 'mps' and (args and isinstance(args[0], torch.device) and args[0].type == 'mps' or isinstance(kwargs.get('device'), torch.device) and kwargs['device'].type == 'mps')) - # MPS workaround for https://github.com/pytorch/pytorch/issues/80800 + # MPS workaround for https://github.com/pytorch/pytorch/issues/80800 CondFunc('torch.nn.functional.layer_norm', lambda orig_func, *args, **kwargs: orig_func(*([args[0].contiguous()] + list(args[1:])), **kwargs), lambda _, *args, **kwargs: args and isinstance(args[0], torch.Tensor) and args[0].device.type == 'mps') # MPS workaround for https://github.com/pytorch/pytorch/issues/90532 @@ -60,4 +60,4 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): # MPS workaround for https://github.com/pytorch/pytorch/issues/92311 if platform.processor() == 'i386': for funcName in ['torch.argmax', 'torch.Tensor.argmax']: - CondFunc(funcName, lambda _, input, *args, **kwargs: torch.max(input.float() if input.dtype == torch.int64 else input, *args, **kwargs)[1], lambda _, input, *args, **kwargs: input.device.type == 'mps') \ No newline at end of file + CondFunc(funcName, lambda _, input, *args, **kwargs: torch.max(input.float() if input.dtype == torch.int64 else input, *args, **kwargs)[1], lambda _, input, *args, **kwargs: input.device.type == 'mps') diff --git a/modules/masking.py b/modules/masking.py index a5c4d2da521..be9f84c76f6 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -4,7 +4,7 @@ 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)""" - + h, w = mask.shape crop_left = 0 diff --git a/modules/ngrok.py b/modules/ngrok.py index 7a7b4b26c9d..67a74e8533d 100644 --- a/modules/ngrok.py +++ b/modules/ngrok.py @@ -13,7 +13,7 @@ def connect(token, port, region): config = conf.PyngrokConfig( auth_token=token, region=region ) - + # Guard for existing tunnels existing = ngrok.get_tunnels(pyngrok_config=config) if existing: @@ -24,7 +24,7 @@ def connect(token, port, region): print(f'ngrok has already been connected to localhost:{port}! URL: {public_url}\n' 'You can use this link after the launch is complete.') return - + try: if account is None: public_url = ngrok.connect(port, pyngrok_config=config, bind_tls=True).public_url diff --git a/modules/processing.py b/modules/processing.py index c3932d6ba71..f902b9df969 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -164,7 +164,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.all_subseeds = None self.iteration = 0 self.is_hr_pass = False - + @property def sd_model(self): diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 171097320ac..7d9dd736121 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -32,22 +32,22 @@ class CFGDenoiserParams: def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps, text_cond, text_uncond): self.x = x """Latent image representation in the process of being denoised""" - + self.image_cond = image_cond """Conditioning image""" - + self.sigma = sigma """Current sigma noise step value""" - + self.sampling_step = sampling_step """Current Sampling step number""" - + self.total_sampling_steps = total_sampling_steps """Total number of sampling steps planned""" - + self.text_cond = text_cond """ Encoder hidden states of text conditioning from prompt""" - + self.text_uncond = text_uncond """ Encoder hidden states of text conditioning from negative prompt""" @@ -240,7 +240,7 @@ def add_callback(callbacks, fun): callbacks.append(ScriptCallback(filename, fun)) - + def remove_current_script_callbacks(): stack = [x for x in inspect.stack() if x.filename != __file__] filename = stack[0].filename if len(stack) > 0 else 'unknown file' diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index e374aeb888d..7e50f1abcf6 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -34,7 +34,7 @@ def apply_optimizations(): ldm.modules.diffusionmodules.model.nonlinearity = silu ldm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th - + optimization_method = None can_use_sdp = hasattr(torch.nn.functional, "scaled_dot_product_attention") and callable(torch.nn.functional.scaled_dot_product_attention) # not everyone has torch 2.x to use sdp @@ -92,12 +92,12 @@ def fix_checkpoint(): def weighted_loss(sd_model, pred, target, mean=True): #Calculate the weight normally, but ignore the mean loss = sd_model._old_get_loss(pred, target, mean=False) - + #Check if we have weights available weight = getattr(sd_model, '_custom_loss_weight', None) if weight is not None: loss *= weight - + #Return the loss, as mean if specified return loss.mean() if mean else loss @@ -105,7 +105,7 @@ def weighted_forward(sd_model, x, c, w, *args, **kwargs): try: #Temporarily append weights to a place accessible during loss calc sd_model._custom_loss_weight = w - + #Replace 'get_loss' with a weight-aware one. Otherwise we need to reimplement 'forward' completely #Keep 'get_loss', but don't overwrite the previous old_get_loss if it's already set if not hasattr(sd_model, '_old_get_loss'): @@ -120,7 +120,7 @@ def weighted_forward(sd_model, x, c, w, *args, **kwargs): del sd_model._custom_loss_weight except AttributeError: pass - + #If we have an old loss function, reset the loss function to the original one if hasattr(sd_model, '_old_get_loss'): sd_model.get_loss = sd_model._old_get_loss @@ -184,7 +184,7 @@ def flatten(el): def undo_hijack(self, m): if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: - m.cond_stage_model = m.cond_stage_model.wrapped + m.cond_stage_model = m.cond_stage_model.wrapped elif type(m.cond_stage_model) == sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords: m.cond_stage_model = m.cond_stage_model.wrapped diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index a174bbe1041..f00fe55cb01 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -62,10 +62,10 @@ def split_cross_attention_forward_v1(self, x, context=None, mask=None): end = i + 2 s1 = einsum('b i d, b j d -> b i j', q[i:end], k[i:end]) s1 *= self.scale - + s2 = s1.softmax(dim=-1) del s1 - + r1[i:end] = einsum('b i j, b j d -> b i d', s2, v[i:end]) del s2 del q, k, v @@ -95,43 +95,43 @@ def split_cross_attention_forward(self, x, context=None, mask=None): with devices.without_autocast(disable=not shared.opts.upcast_attn): k_in = k_in * self.scale - + del context, x - + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in)) del q_in, k_in, v_in - + r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) - + mem_free_total = get_available_vram() - + gb = 1024 ** 3 tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() modifier = 3 if q.element_size() == 2 else 2.5 mem_required = tensor_size * modifier steps = 1 - + if mem_required > mem_free_total: steps = 2 ** (math.ceil(math.log(mem_required / mem_free_total, 2))) # print(f"Expected tensor size:{tensor_size/gb:0.1f}GB, cuda free:{mem_free_cuda/gb:0.1f}GB " # f"torch free:{mem_free_torch/gb:0.1f} total:{mem_free_total/gb:0.1f} steps:{steps}") - + if steps > 64: max_res = math.floor(math.sqrt(math.sqrt(mem_free_total / 2.5)) / 8) * 64 raise RuntimeError(f'Not enough memory, use lower resolution (max approx. {max_res}x{max_res}). ' f'Need: {mem_required / 64 / gb:0.1f}GB free, Have:{mem_free_total / gb:0.1f}GB free') - + slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1] for i in range(0, q.shape[1], slice_size): end = i + slice_size s1 = einsum('b i d, b j d -> b i j', q[:, i:end], k) - + s2 = s1.softmax(dim=-1, dtype=q.dtype) del s1 - + r1[:, i:end] = einsum('b i j, b j d -> b i d', s2, v) del s2 - + del q, k, v r1 = r1.to(dtype) @@ -228,7 +228,7 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): with devices.without_autocast(disable=not shared.opts.upcast_attn): k = k * self.scale - + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v)) r = einsum_op(q, k, v) r = r.to(dtype) @@ -369,7 +369,7 @@ def scaled_dot_product_attention_forward(self, x, context=None, mask=None): q = q_in.view(batch_size, -1, h, head_dim).transpose(1, 2) k = k_in.view(batch_size, -1, h, head_dim).transpose(1, 2) v = v_in.view(batch_size, -1, h, head_dim).transpose(1, 2) - + del q_in, k_in, v_in dtype = q.dtype @@ -451,7 +451,7 @@ def cross_attention_attnblock_forward(self, x): h3 += x return h3 - + def xformers_attnblock_forward(self, x): try: h_ = x diff --git a/modules/sd_models.py b/modules/sd_models.py index d1e946a5b8c..3316d021184 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -165,7 +165,7 @@ def model_hash(filename): def select_checkpoint(): model_checkpoint = shared.opts.sd_model_checkpoint - + checkpoint_info = checkpoint_alisases.get(model_checkpoint, None) if checkpoint_info is not None: return checkpoint_info @@ -372,7 +372,7 @@ def load_model_wrapper(model_type): if not os.path.exists(path): if not os.path.exists(midas_path): mkdir(midas_path) - + print(f"Downloading midas model weights for {model_type} to {path}") request.urlretrieve(midas_urls[model_type], path) print(f"{model_type} downloaded") diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 2f733cf5c28..e9e41818c81 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -93,10 +93,10 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): if shared.sd_model.model.conditioning_key == "crossattn-adm": image_uncond = torch.zeros_like(image_cond) - make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": c_crossattn, "c_adm": c_adm} + make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": c_crossattn, "c_adm": c_adm} else: image_uncond = image_cond - make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": c_crossattn, "c_concat": [c_concat]} + make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": c_crossattn, "c_concat": [c_concat]} if not is_edit_model: x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x]) @@ -316,7 +316,7 @@ def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, sigma_sched = sigmas[steps - t_enc - 1:] xi = x + noise * sigma_sched[0] - + extra_params_kwargs = self.initialize(p) parameters = inspect.signature(self.func).parameters @@ -339,9 +339,9 @@ def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, self.model_wrap_cfg.init_latent = x self.last_latent = x extra_args={ - 'cond': conditioning, - 'image_cond': image_conditioning, - 'uncond': unconditional_conditioning, + 'cond': conditioning, + 'image_cond': image_conditioning, + 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale, 's_min_uncond': self.s_min_uncond } @@ -374,9 +374,9 @@ def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, ima self.last_latent = x samples = self.launch_sampling(steps, lambda: self.func(self.model_wrap_cfg, x, extra_args={ - 'cond': conditioning, - 'image_cond': image_conditioning, - 'uncond': unconditional_conditioning, + 'cond': conditioning, + 'image_cond': image_conditioning, + 'uncond': unconditional_conditioning, 'cond_scale': p.cfg_scale, 's_min_uncond': self.s_min_uncond }, disable=False, callback=self.callback_state, **extra_params_kwargs)) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index cc38debdc15..497568eb51b 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -179,7 +179,7 @@ def get_query_chunk(chunk_idx: int) -> Tensor: chunk_idx, min(query_chunk_size, q_tokens) ) - + summarize_chunk: SummarizeChunk = partial(_summarize_chunk, scale=scale) summarize_chunk: SummarizeChunk = partial(checkpoint, summarize_chunk) if use_checkpoint else summarize_chunk compute_query_chunk_attn: ComputeQueryChunkAttn = partial( diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 41610e03a66..b9621fc9529 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -118,7 +118,7 @@ def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_to weight = torch.ones(latent_sample.shape) else: weight = None - + if latent_sampling_method == "random": entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight) else: @@ -243,4 +243,4 @@ def pin_memory(self): return self def collate_wrapper_random(batch): - return BatchLoaderRandom(batch) \ No newline at end of file + return BatchLoaderRandom(batch) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index d0cad09eb0c..a009d8e8006 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -125,7 +125,7 @@ def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, thr default=None ) return wh and center_crop(image, *wh) - + def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None): width = process_width diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 9e1b2b9a848..d489ed1e0c9 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -323,16 +323,16 @@ def tensorboard_add(tensorboard_writer, loss, global_step, step, learn_rate, epo tensorboard_add_scaler(tensorboard_writer, f"Learn rate/train/epoch-{epoch_num}", learn_rate, step) def tensorboard_add_scaler(tensorboard_writer, tag, value, step): - tensorboard_writer.add_scalar(tag=tag, + tensorboard_writer.add_scalar(tag=tag, scalar_value=value, global_step=step) def tensorboard_add_image(tensorboard_writer, tag, pil_image, step): # Convert a pil image to a torch tensor img_tensor = torch.as_tensor(np.array(pil_image, copy=True)) - img_tensor = img_tensor.view(pil_image.size[1], pil_image.size[0], + img_tensor = img_tensor.view(pil_image.size[1], pil_image.size[0], len(pil_image.getbands())) img_tensor = img_tensor.permute((2, 0, 1)) - + tensorboard_writer.add_image(tag, img_tensor, global_step=step) def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, data_root, template_file, template_filename, steps, save_model_every, create_image_every, log_directory, name="embedding"): @@ -402,7 +402,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st if initial_step >= steps: shared.state.textinfo = "Model has already been trained beyond specified max steps" return embedding, filename - + scheduler = LearnRateScheduler(learn_rate, steps, initial_step) clip_grad = torch.nn.utils.clip_grad_value_ if clip_grad_mode == "value" else \ torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else \ @@ -412,7 +412,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st # dataset loading may take a while, so input validations and early returns should be done before this shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." old_parallel_processing_allowed = shared.parallel_processing_allowed - + if shared.opts.training_enable_tensorboard: tensorboard_writer = tensorboard_setup(log_directory) @@ -439,7 +439,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st optimizer_saved_dict = torch.load(f"{filename}.optim", map_location='cpu') if embedding.checksum() == optimizer_saved_dict.get('hash', None): optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None) - + if optimizer_state_dict is not None: optimizer.load_state_dict(optimizer_state_dict) print("Loaded existing optimizer from checkpoint") @@ -485,7 +485,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st if clip_grad: clip_grad_sched.step(embedding.step) - + with devices.autocast(): x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) if use_weight: @@ -513,7 +513,7 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st # go back until we reach gradient accumulation steps if (j + 1) % gradient_step != 0: continue - + if clip_grad: clip_grad(embedding.vec, clip_grad_sched.learn_rate) diff --git a/modules/ui.py b/modules/ui.py index 1efb656a471..ff82fff6c84 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1171,7 +1171,7 @@ def update_interp_description(value): process_focal_crop_entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_entropy_weight") process_focal_crop_edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_edges_weight") process_focal_crop_debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug") - + with gr.Column(visible=False) as process_multicrop_col: gr.Markdown('Each image is center-cropped with an automatically chosen width and height.') with gr.Row(): @@ -1183,7 +1183,7 @@ def update_interp_description(value): with gr.Row(): process_multicrop_objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="train_process_multicrop_objective") process_multicrop_threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="train_process_multicrop_threshold") - + with gr.Row(): with gr.Column(scale=3): gr.HTML(value="") @@ -1226,7 +1226,7 @@ def get_textual_inversion_template_names(): with FormRow(): embedding_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005", elem_id="train_embedding_learn_rate") hypernetwork_learn_rate = gr.Textbox(label='Hypernetwork Learning rate', placeholder="Hypernetwork Learning rate", value="0.00001", elem_id="train_hypernetwork_learn_rate") - + with FormRow(): clip_grad_mode = gr.Dropdown(value="disabled", label="Gradient Clipping", choices=["disabled", "value", "norm"]) clip_grad_value = gr.Textbox(placeholder="Gradient clip value", value="0.1", show_label=False) @@ -1565,7 +1565,7 @@ def run_settings_single(value, key): gr.HTML(shared.html("licenses.html"), elem_id="licenses") gr.Button(value="Show all pages", elem_id="settings_show_all_pages") - + def unload_sd_weights(): modules.sd_models.unload_model_weights() @@ -1841,15 +1841,15 @@ def versions_html(): return f""" version:
    {tag} - •  + • python: {python_version} - •  + • torch: {getattr(torch, '__long_version__',torch.__version__)} - •  + • xformers: {xformers_version} - •  + • gradio: {gr.__version__} - •  + • checkpoint: N/A """ diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index ed70abe5868..af49773326f 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -467,7 +467,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text="
    - + """ for tag in [x for x in extension_tags if x not in tags]: @@ -535,9 +535,9 @@ def create_ui(): hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order", ], type="index") - with gr.Row(): + with gr.Row(): search_extensions_text = gr.Text(label="Search").style(container=False) - + install_result = gr.HTML() available_extensions_table = gr.HTML() diff --git a/modules/xlmr.py b/modules/xlmr.py index e056c3f6a25..a407a3cade8 100644 --- a/modules/xlmr.py +++ b/modules/xlmr.py @@ -28,7 +28,7 @@ class BertSeriesModelWithTransformation(BertPreTrainedModel): config_class = BertSeriesConfig def __init__(self, config=None, **kargs): - # modify initialization for autoloading + # modify initialization for autoloading if config is None: config = XLMRobertaConfig() config.attention_probs_dropout_prob= 0.1 @@ -74,7 +74,7 @@ def encode(self,c): text["attention_mask"] = torch.tensor( text['attention_mask']).to(device) features = self(**text) - return features['projection_state'] + return features['projection_state'] def forward( self, @@ -134,4 +134,4 @@ def forward( class RobertaSeriesModelWithTransformation(BertSeriesModelWithTransformation): base_model_prefix = 'roberta' - config_class= RobertaSeriesConfig \ No newline at end of file + config_class= RobertaSeriesConfig diff --git a/pyproject.toml b/pyproject.toml index c88907be842..d4a1bbf4e11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ extend-select = [ "B", "C", "I", + "W", ] exclude = [ @@ -20,7 +21,7 @@ ignore = [ "I001", # Import block is un-sorted or un-formatted "C901", # Function is too complex "C408", # Rewrite as a literal - + "W605", # invalid escape sequence, messes with some docstrings ] [tool.ruff.per-file-ignores] @@ -28,4 +29,4 @@ ignore = [ [tool.ruff.flake8-bugbear] # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. -extend-immutable-calls = ["fastapi.Depends", "fastapi.security.HTTPBasic"] \ No newline at end of file +extend-immutable-calls = ["fastapi.Depends", "fastapi.security.HTTPBasic"] diff --git a/scripts/img2imgalt.py b/scripts/img2imgalt.py index bb00fb3f1f9..1e833fa898f 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -149,9 +149,9 @@ def ui(self, is_img2img): sigma_adjustment = gr.Checkbox(label="Sigma adjustment for finding noise for image", value=False, elem_id=self.elem_id("sigma_adjustment")) return [ - info, + info, override_sampler, - override_prompt, original_prompt, original_negative_prompt, + override_prompt, original_prompt, original_negative_prompt, override_steps, st, override_strength, cfg, randomness, sigma_adjustment, @@ -191,17 +191,17 @@ def sample_extra(conditioning, unconditional_conditioning, seeds, subseeds, subs self.cache = Cached(rec_noise, cfg, st, lat, original_prompt, original_negative_prompt, sigma_adjustment) rand_noise = processing.create_random_tensors(p.init_latent.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w, p=p) - + combined_noise = ((1 - randomness) * rec_noise + randomness * rand_noise) / ((randomness**2 + (1-randomness)**2) ** 0.5) - + sampler = sd_samplers.create_sampler(p.sampler_name, p.sd_model) sigmas = sampler.model_wrap.get_sigmas(p.steps) - + noise_dt = combined_noise - (p.init_latent / sigmas[0]) - + p.seed = p.seed + 1 - + return sampler.sample_img2img(p, p.init_latent, noise_dt, conditioning, unconditional_conditioning, image_conditioning=p.image_conditioning) p.sample = sample_extra diff --git a/scripts/loopback.py b/scripts/loopback.py index ad6609be943..2d5feaf9b26 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -14,7 +14,7 @@ def title(self): def show(self, is_img2img): return is_img2img - def ui(self, is_img2img): + def ui(self, is_img2img): loops = gr.Slider(minimum=1, maximum=32, step=1, label='Loops', value=4, elem_id=self.elem_id("loops")) final_denoising_strength = gr.Slider(minimum=0, maximum=1, step=0.01, label='Final denoising strength', value=0.5, elem_id=self.elem_id("final_denoising_strength")) denoising_curve = gr.Dropdown(label="Denoising strength curve", choices=["Aggressive", "Linear", "Lazy"], value="Linear") @@ -104,7 +104,7 @@ def calculate_denoising_strength(loop): p.seed = processed.seed + 1 p.denoising_strength = calculate_denoising_strength(i + 1) - + if state.skipped: break @@ -121,7 +121,7 @@ def calculate_denoising_strength(loop): all_images.append(last_image) p.inpainting_fill = original_inpainting_fill - + if state.interrupted: break @@ -132,7 +132,7 @@ def calculate_denoising_strength(loop): if opts.return_grid: grids.append(grid) - + all_images = grids + all_images processed = Processed(p, all_images, initial_seed, initial_info) diff --git a/scripts/poor_mans_outpainting.py b/scripts/poor_mans_outpainting.py index c0bbecc1291..ea0632b68c1 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -19,7 +19,7 @@ def show(self, is_img2img): def ui(self, is_img2img): if not is_img2img: return None - + pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128, elem_id=self.elem_id("pixels")) mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id=self.elem_id("mask_blur")) inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", elem_id=self.elem_id("inpainting_fill")) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index fb06beab4e1..88324fe6a74 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -96,7 +96,7 @@ def run(self, p, put_at_start, different_seeds, prompt_type, variations_delimite p.prompt_for_display = positive_prompt processed = process_images(p) - grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) + grid = images.image_grid(processed.images, p.batch_size, rows=1 << ((len(prompt_matrix_parts) - 1) // 2)) grid = images.draw_prompt_matrix(grid, processed.images[0].width, processed.images[0].height, prompt_matrix_parts, margin_size) processed.images.insert(0, grid) processed.index_of_first_image = 1 diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 9607077a7d1..2378816f97e 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -109,7 +109,7 @@ class Script(scripts.Script): def title(self): return "Prompts from file or textbox" - def ui(self, is_img2img): + def ui(self, is_img2img): checkbox_iterate = gr.Checkbox(label="Iterate seed every line", value=False, elem_id=self.elem_id("checkbox_iterate")) checkbox_iterate_batch = gr.Checkbox(label="Use same random seed for all lines", value=False, elem_id=self.elem_id("checkbox_iterate_batch")) @@ -166,7 +166,7 @@ def run(self, p, checkbox_iterate, checkbox_iterate_batch, prompt_txt: str): proc = process_images(copy_p) images += proc.images - + if checkbox_iterate: p.seed = p.seed + (p.batch_size * p.n_iter) all_prompts += proc.all_prompts diff --git a/scripts/sd_upscale.py b/scripts/sd_upscale.py index 0b1d3096818..e614c23b987 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -16,7 +16,7 @@ def title(self): def show(self, is_img2img): return is_img2img - def ui(self, is_img2img): + def ui(self, is_img2img): info = gr.HTML("

    Will upscale the image by the selected scale factor; use width and height sliders to set tile size

    ") overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64, elem_id=self.elem_id("overlap")) scale_factor = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label='Scale Factor', value=2.0, elem_id=self.elem_id("scale_factor")) From da10de022f69e7847bcc64a7914d56246d852e20 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 20:52:30 +0300 Subject: [PATCH 0170/2418] Make live previews use JPEG only when the image is lorge enough --- modules/progress.py | 12 ++++++++++-- modules/shared.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/progress.py b/modules/progress.py index 289dd311d6c..c2e37834f01 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -95,9 +95,17 @@ def progressapi(req: ProgressRequest): image = shared.state.current_image if image is not None: buffered = io.BytesIO() - image.save(buffered, format=opts.live_previews_format) + format = opts.live_previews_format + save_kwargs = {} + if format == "auto": + if max(*image.size) > 256: + format = "jpeg" + else: + format = "png" + save_kwargs = {"optimize": True} + image.save(buffered, format=format, **save_kwargs) base64_image = base64.b64encode(buffered.getvalue()).decode('ascii') - live_preview = f"data:image/{opts.live_previews_format};base64,{base64_image}" + live_preview = f"data:image/{format};base64,{base64_image}" id_live_preview = shared.state.id_live_preview else: live_preview = None diff --git a/modules/shared.py b/modules/shared.py index f387b5ae218..22b456184aa 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -420,7 +420,7 @@ def list_samplers(): options_templates.update(options_section(('ui', "Live previews"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), - "live_previews_format": OptionInfo("jpeg", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}), + "live_previews_format": OptionInfo("auto", "Live preview file format", gr.Radio, {"choices": ["auto", "jpeg", "png", "webp"]}), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), From d4bd67bd678d6dd523fed0490bdd586fe0dd72ef Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 23:12:43 +0300 Subject: [PATCH 0171/2418] Bump versions to avoid downgrading them --- requirements_versions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index df8c6861bf8..0a276b0b4b8 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -5,12 +5,12 @@ basicsr==1.4.2 gfpgan==1.3.8 gradio==3.29.0 numpy==1.23.5 -Pillow==9.4.0 +Pillow==9.5.0 realesrgan==0.3.0 torch omegaconf==2.2.3 pytorch_lightning==1.9.4 -scikit-image==0.19.2 +scikit-image==0.20.0 timm==0.6.7 piexif==1.1.3 einops==0.4.1 From 681c16dd1e911bdf831b031b0f31aaba41c280f8 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 12 May 2023 22:33:21 +0900 Subject: [PATCH 0172/2418] fix --data-dir for COMMANDLINE_ARGS move reading of COMMANDLINE_ARGS into paths_internal.py so --data-dir can be properly read --- launch.py | 4 ---- modules/paths_internal.py | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/launch.py b/launch.py index 62b33f1497a..516746accd0 100644 --- a/launch.py +++ b/launch.py @@ -3,16 +3,12 @@ import os import sys import importlib.util -import shlex import platform import json from modules import cmd_args from modules.paths_internal import script_path, extensions_dir -commandline_args = os.environ.get('COMMANDLINE_ARGS', "") -sys.argv += shlex.split(commandline_args) - args, _ = cmd_args.parser.parse_known_args() python = sys.executable diff --git a/modules/paths_internal.py b/modules/paths_internal.py index a23f6d70682..005a9b0aa75 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -2,6 +2,11 @@ import argparse import os +import sys +import shlex + +commandline_args = os.environ.get('COMMANDLINE_ARGS', "") +sys.argv += shlex.split(commandline_args) modules_path = os.path.dirname(os.path.realpath(__file__)) script_path = os.path.dirname(modules_path) From 0cab07b2f1078df29c7f3b52a3ae6b27d9ed0764 Mon Sep 17 00:00:00 2001 From: brkirch Date: Fri, 12 May 2023 11:15:43 -0400 Subject: [PATCH 0173/2418] Set PyTorch version to 2.0.1 for macOS --- webui-macos-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui-macos-env.sh b/webui-macos-env.sh index 10ab81c9eb9..6354e73ba72 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -11,7 +11,7 @@ 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 torchvision" +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 55d222a9f4fb51eeb4c0b0fe4e703d45a39ae7a0 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 23:00:53 +0300 Subject: [PATCH 0174/2418] launch.py: make git_tag() and commit_hash() work even when WEBUI_LAUNCH_LIVE_OUTPUT --- launch.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/launch.py b/launch.py index 516746accd0..ae67fe5c5e9 100644 --- a/launch.py +++ b/launch.py @@ -5,6 +5,7 @@ import importlib.util import platform import json +from functools import lru_cache from modules import cmd_args from modules.paths_internal import script_path, extensions_dir @@ -14,8 +15,6 @@ python = sys.executable git = os.environ.get('GIT', "git") index_url = os.environ.get('INDEX_URL', "") -stored_commit_hash = None -stored_git_tag = None dir_repos = "repositories" # Whether to default to printing command output @@ -56,32 +55,20 @@ def check_python_version(): """) +@lru_cache() def commit_hash(): - global stored_commit_hash - - if stored_commit_hash is not None: - return stored_commit_hash - try: - stored_commit_hash = run(f"{git} rev-parse HEAD").strip() + return subprocess.check_output(f"{git} rev-parse HEAD", encoding='utf8').strip() except Exception: - stored_commit_hash = "" - - return stored_commit_hash + return "" +@lru_cache() def git_tag(): - global stored_git_tag - - if stored_git_tag is not None: - return stored_git_tag - try: - stored_git_tag = run(f"{git} describe --tags").strip() + return subprocess.check_output(f"{git} describe --tags", encoding='utf8').strip() except Exception: - stored_git_tag = "" - - return stored_git_tag + return "" def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str: From 451d255b5859580c4adf99d67760330d58d76446 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 23:29:34 +0300 Subject: [PATCH 0175/2418] Get rid of check_run + run_python --- launch.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/launch.py b/launch.py index ae67fe5c5e9..578af22906c 100644 --- a/launch.py +++ b/launch.py @@ -103,11 +103,6 @@ def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_ return (result.stdout or "") -def check_run(command): - result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - return result.returncode == 0 - - def is_installed(package): try: spec = importlib.util.find_spec(package) @@ -121,10 +116,6 @@ def repo_dir(name): return os.path.join(script_path, dir_repos, name) -def run_python(code, desc=None, errdesc=None): - return run(f'"{python}" -c "{code}"', desc, errdesc) - - def run_pip(command, desc=None, live=default_command_live): if args.skip_install: return @@ -133,8 +124,9 @@ def run_pip(command, desc=None, live=default_command_live): return run(f'"{python}" -m pip {command} --prefer-binary{index_url_line}', desc=f"Installing {desc}", errdesc=f"Couldn't install {desc}", live=live) -def check_run_python(code): - return check_run(f'"{python}" -c "{code}"') +def check_run_python(code: str) -> bool: + result = subprocess.run([python, "-c", code], capture_output=True, shell=True) + return result.returncode == 0 def git_clone(url, dir, name, commithash=None): @@ -261,8 +253,11 @@ def prepare_environment(): if args.reinstall_torch or not is_installed("torch") or not is_installed("torchvision"): run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch", live=True) - if not args.skip_torch_cuda_test: - run_python("import torch; assert torch.cuda.is_available(), 'Torch is not able to use GPU; add --skip-torch-cuda-test to COMMANDLINE_ARGS variable to disable this check'") + if not args.skip_torch_cuda_test and not check_run_python("import torch; assert torch.cuda.is_available()"): + raise RuntimeError( + 'Torch is not able to use GPU; ' + 'add --skip-torch-cuda-test to COMMANDLINE_ARGS variable to disable this check' + ) if not is_installed("gfpgan"): run_pip(f"install {gfpgan_package}", "gfpgan") From b14e23529f52f926a8912b2c3d10b9a90dae3967 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Fri, 12 May 2023 18:06:13 +0000 Subject: [PATCH 0176/2418] Redirect Gradio phone home request This request is sent regardless of Gradio analytics being enabled or not via the env var. Idea from text-generation-webui. --- webui.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/webui.py b/webui.py index 293a16ccda2..cdb96740565 100644 --- a/webui.py +++ b/webui.py @@ -28,7 +28,16 @@ startup_timer.record("import torch") +import requests +def gradio_get(url, **kwargs): + kwargs.setdefault('allow_redirects', True) + return requests.api.request('get', 'http://127.0.0.1/', **kwargs) + +original_get = requests.get +requests.get = gradio_get import gradio +requests.get = original_get + startup_timer.record("import gradio") import ldm.modules.encoders.modules # noqa: F401 From 867be74244dc725fcf2685018b97501e83a16235 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Fri, 12 May 2023 18:08:34 +0000 Subject: [PATCH 0177/2418] Define default fonts for Gradio theme Allows web UI to (almost) be ran fully offline. The web UI will hang on load if offline when these fonts are not manually defined, as it will attempt (and fail) to pull from Google Fonts. --- modules/shared.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 1df1dd45d06..b09b384e70f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -666,13 +666,19 @@ def reload_gradio_theme(theme_name=None): theme_name = opts.gradio_theme if theme_name == "Default": - gradio_theme = gr.themes.Default() + gradio_theme = gr.themes.Default( + font=['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], + ) else: try: gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) except Exception as e: errors.display(e, "changing gradio theme") - gradio_theme = gr.themes.Default() + gradio_theme = gr.themes.Default( + font=['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], + ) From 231562ea13e4f697953bdbabd6b76b22a88c587b Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 08:16:20 +0300 Subject: [PATCH 0178/2418] update changelog for release --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0f7ae521c..d1727864dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ -## Upcoming 1.2.0 +## 1.2.0 ### Features: - * do not load wait for stable diffusion model to load at startup + * do not wait for stable diffusion model to load at startup * add filename patterns: [denoising] * directory hiding for extra networks: dirs starting with . will hide their cards on extra network tabs unless specifically searched for * Lora: for the `<...>` text in prompt, use name of Lora that is in the metdata of the file, if present, instead of filename (both can be used to activate lora) From d274b8297e8588ce1ea08200935e46c100288de3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 14:49:14 +0300 Subject: [PATCH 0179/2418] fix broken prompts from file --- CHANGELOG.md | 1 + scripts/prompts_from_file.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3fef3d0e6..1a0f7ae521c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ * Fix MPS on PyTorch 2.0.1, Intel Macs * make it so that custom context menu from contextMenu.js only disappears after user's click, ignoring non-user click events * prevent Reload UI button/link from reloading the page when it's not yet ready + * fix prompts from file script failing to read contents from a drag/drop file ## 1.1.1 diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 2378816f97e..b918a764e9b 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -103,8 +103,6 @@ def load_prompt_file(file): return None, "\n".join(lines), gr.update(lines=7) - - class Script(scripts.Script): def title(self): return "Prompts from file or textbox" From 41359378767e3ae44b68b3eee468e57964c21272 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 08:16:20 +0300 Subject: [PATCH 0180/2418] update changelog for release --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0f7ae521c..d1727864dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ -## Upcoming 1.2.0 +## 1.2.0 ### Features: - * do not load wait for stable diffusion model to load at startup + * do not wait for stable diffusion model to load at startup * add filename patterns: [denoising] * directory hiding for extra networks: dirs starting with . will hide their cards on extra network tabs unless specifically searched for * Lora: for the `<...>` text in prompt, use name of Lora that is in the metdata of the file, if present, instead of filename (both can be used to activate lora) From 999a03e4a770217395b5eb8f8165ed0d8ebe5656 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sat, 13 May 2023 15:10:35 +0300 Subject: [PATCH 0181/2418] Wait for DOMContentLoaded until checking whether localization should be disabled Refs https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9955#issuecomment-1546587143 --- javascript/localization.js | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/javascript/localization.js b/javascript/localization.js index 0123b877bdb..d0bdfc16e19 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -137,7 +137,11 @@ function download_localization() { document.body.removeChild(element); } -if(hasLocalization()) { +document.addEventListener("DOMContentLoaded", function () { + if (!hasLocalization()) { + return; + } + onUiUpdate(function (m) { m.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { @@ -146,26 +150,23 @@ if(hasLocalization()) { }); }) + processNode(gradioApp()) - document.addEventListener("DOMContentLoaded", function () { - processNode(gradioApp()) + if (localization.rtl) { // if the language is from right to left, + (new MutationObserver((mutations, observer) => { // wait for the style to load + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.tagName === 'STYLE') { + observer.disconnect(); - if (localization.rtl) { // if the language is from right to left, - (new MutationObserver((mutations, observer) => { // wait for the style to load - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.tagName === 'STYLE') { - observer.disconnect(); - - for (const x of node.sheet.rules) { // find all rtl media rules - if (Array.from(x.media || []).includes('rtl')) { - x.media.appendMedium('all'); // enable them - } + for (const x of node.sheet.rules) { // find all rtl media rules + if (Array.from(x.media || []).includes('rtl')) { + x.media.appendMedium('all'); // enable them } } - }) - }); - })).observe(gradioApp(), { childList: true }); - } - }) -} + } + }) + }); + })).observe(gradioApp(), { childList: true }); + } +}) From 5afc44aab14819afea87b2f36c2f76dc43d3e83c Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sat, 13 May 2023 12:57:32 +0000 Subject: [PATCH 0182/2418] Requested changes --- modules/shared.py | 15 +++++++-------- style.css | 3 +++ webui.py | 9 +-------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index b09b384e70f..96a20a6bd9d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -665,20 +665,19 @@ def reload_gradio_theme(theme_name=None): if not theme_name: theme_name = opts.gradio_theme + default_theme_args = dict( + font=["Source Sans Pro", 'ui-sans-serif', 'system-ui', 'sans-serif'], + font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], + ) + if theme_name == "Default": - gradio_theme = gr.themes.Default( - font=['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], - ) + gradio_theme = gr.themes.Default(**default_theme_args) else: try: gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) except Exception as e: errors.display(e, "changing gradio theme") - gradio_theme = gr.themes.Default( - font=['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], - ) + gradio_theme = gr.themes.Default(**default_theme_args) diff --git a/style.css b/style.css index 4ac919b5e8c..8c7be275cec 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,6 @@ +/* temporary fix to load default gradio font in frontend instead of backend */ + +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600&display=swap'); /* general gradio fixes */ diff --git a/webui.py b/webui.py index cdb96740565..d3aafe6d06c 100644 --- a/webui.py +++ b/webui.py @@ -28,15 +28,8 @@ startup_timer.record("import torch") -import requests -def gradio_get(url, **kwargs): - kwargs.setdefault('allow_redirects', True) - return requests.api.request('get', 'http://127.0.0.1/', **kwargs) - -original_get = requests.get -requests.get = gradio_get import gradio -requests.get = original_get +startup_timer.record("import gradio") startup_timer.record("import gradio") From 867c8a1083e892f06d78c41c5e0a05138c2ae337 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sat, 13 May 2023 12:59:00 +0000 Subject: [PATCH 0183/2418] minor fix --- webui.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/webui.py b/webui.py index d3aafe6d06c..293a16ccda2 100644 --- a/webui.py +++ b/webui.py @@ -31,8 +31,6 @@ import gradio startup_timer.record("import gradio") -startup_timer.record("import gradio") - import ldm.modules.encoders.modules # noqa: F401 startup_timer.record("import ldm") From 55e52c878ab669d5b11b001a4152ee1a3b8d4880 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Sat, 13 May 2023 09:24:56 -0500 Subject: [PATCH 0184/2418] remove command line option --- modules/cmd_args.py | 1 - modules/processing.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 46043e33348..f4a4ab36f49 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -102,5 +102,4 @@ parser.add_argument("--skip-version-check", action='store_true', help="Do not check versions of torch and xformers") parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) -parser.add_argument("--token-merging", action='store_true', help="Provides speed and memory improvements by merging redundant tokens. This has a more pronounced effect on higher resolutions.", default=False) parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') diff --git a/modules/processing.py b/modules/processing.py index 8ba3a96b53f..6828e898160 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -496,8 +496,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, - "Token merging ratio": None if not (opts.token_merging or cmd_opts.token_merging) or opts.token_merging_hr_only else opts.token_merging_ratio, - "Token merging ratio hr": None if not (opts.token_merging or cmd_opts.token_merging) else opts.token_merging_ratio_hr, + "Token merging ratio": None if not opts.token_merging or opts.token_merging_hr_only else opts.token_merging_ratio, + "Token merging ratio hr": None if not opts.token_merging else opts.token_merging_ratio_hr, "Token merging random": None if opts.token_merging_random is False else opts.token_merging_random, "Token merging merge attention": None if opts.token_merging_merge_attention is True else opts.token_merging_merge_attention, "Token merging merge cross attention": None if opts.token_merging_merge_cross_attention is False else opts.token_merging_merge_cross_attention, @@ -538,7 +538,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - if (opts.token_merging or cmd_opts.token_merging) and not opts.token_merging_hr_only: + if opts.token_merging and not opts.token_merging_hr_only: sd_models.apply_token_merging(sd_model=p.sd_model, hr=False) logger.debug('Token merging applied') @@ -546,7 +546,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: finally: # undo model optimizations made by tomesd - if opts.token_merging or cmd_opts.token_merging: + if opts.token_merging: tomesd.remove_patch(p.sd_model) logger.debug('Token merging model optimizations removed') @@ -1004,7 +1004,7 @@ def save_intermediate(image, index): # apply token merging optimizations from tomesd for high-res pass # check if hr_only so we are not redundantly patching - if (cmd_opts.token_merging or opts.token_merging) and (opts.token_merging_hr_only or opts.token_merging_ratio_hr != opts.token_merging_ratio): + if opts.token_merging and (opts.token_merging_hr_only or opts.token_merging_ratio_hr != opts.token_merging_ratio): # case where user wants to use separate merge ratios if not opts.token_merging_hr_only: # clean patch done by first pass. (clobbering the first patch might be fine? this might be excessive) From cb5f61281a95be72fc812b7d350b6ec23e2f9bdd Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sat, 13 May 2023 11:04:26 -0400 Subject: [PATCH 0185/2418] Allow bf16 in safe unpickler --- modules/safe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/safe.py b/modules/safe.py index 1e791c5bdc5..e8f50774374 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -40,7 +40,7 @@ def find_class(self, module, name): return getattr(collections, name) if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter', '_rebuild_device_tensor_from_numpy']: return getattr(torch._utils, name) - if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32']: + if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32', 'BFloat16Storage']: return getattr(torch, name) if module == 'torch.nn.modules.container' and name in ['ParameterDict']: return getattr(torch.nn.modules.container, name) From ac83627a31daac06f4d48b0e7db223ef807fe8e5 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Sat, 13 May 2023 10:23:42 -0500 Subject: [PATCH 0186/2418] heavily simplify --- modules/generation_parameters_copypaste.py | 36 ------------------- modules/processing.py | 35 ++++++++---------- modules/sd_models.py | 11 +++--- modules/shared.py | 42 +++------------------- 4 files changed, 23 insertions(+), 101 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index fb56254fc2b..a0a98bbcb03 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -282,33 +282,6 @@ def parse_generation_parameters(x: str): res["Hires resize-1"] = 0 res["Hires resize-2"] = 0 - # Infer additional override settings for token merging - token_merging_ratio = res.get("Token merging ratio", None) - token_merging_ratio_hr = res.get("Token merging ratio hr", None) - - if token_merging_ratio is not None or token_merging_ratio_hr is not None: - res["Token merging"] = 'True' - - if token_merging_ratio is None: - res["Token merging hr only"] = 'True' - else: - res["Token merging hr only"] = 'False' - - if res.get("Token merging random", None) is None: - res["Token merging random"] = 'False' - if res.get("Token merging merge attention", None) is None: - res["Token merging merge attention"] = 'True' - if res.get("Token merging merge cross attention", None) is None: - res["Token merging merge cross attention"] = 'False' - if res.get("Token merging merge mlp", None) is None: - res["Token merging merge mlp"] = 'False' - if res.get("Token merging stride x", None) is None: - res["Token merging stride x"] = '2' - if res.get("Token merging stride y", None) is None: - res["Token merging stride y"] = '2' - if res.get("Token merging maximum down sampling", None) is None: - res["Token merging maximum down sampling"] = '1' - restore_old_hires_fix_params(res) # Missing RNG means the default was set, which is GPU RNG @@ -335,17 +308,8 @@ def parse_generation_parameters(x: str): ('UniPC skip type', 'uni_pc_skip_type'), ('UniPC order', 'uni_pc_order'), ('UniPC lower order final', 'uni_pc_lower_order_final'), - ('Token merging', 'token_merging'), ('Token merging ratio', 'token_merging_ratio'), - ('Token merging hr only', 'token_merging_hr_only'), ('Token merging ratio hr', 'token_merging_ratio_hr'), - ('Token merging random', 'token_merging_random'), - ('Token merging merge attention', 'token_merging_merge_attention'), - ('Token merging merge cross attention', 'token_merging_merge_cross_attention'), - ('Token merging merge mlp', 'token_merging_merge_mlp'), - ('Token merging maximum down sampling', 'token_merging_maximum_down_sampling'), - ('Token merging stride x', 'token_merging_stride_x'), - ('Token merging stride y', 'token_merging_stride_y'), ('RNG', 'randn_source'), ('NGMS', 's_min_uncond') ] diff --git a/modules/processing.py b/modules/processing.py index 6828e898160..32ff61e9294 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -34,7 +34,7 @@ # add a logger for the processing module logger = logging.getLogger(__name__) # manually set output level here since there is no option to do so yet through launch options -# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') # some of those options should not be changed at all because they would break the model, so I removed them from options. @@ -496,15 +496,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, - "Token merging ratio": None if not opts.token_merging or opts.token_merging_hr_only else opts.token_merging_ratio, - "Token merging ratio hr": None if not opts.token_merging else opts.token_merging_ratio_hr, - "Token merging random": None if opts.token_merging_random is False else opts.token_merging_random, - "Token merging merge attention": None if opts.token_merging_merge_attention is True else opts.token_merging_merge_attention, - "Token merging merge cross attention": None if opts.token_merging_merge_cross_attention is False else opts.token_merging_merge_cross_attention, - "Token merging merge mlp": None if opts.token_merging_merge_mlp is False else opts.token_merging_merge_mlp, - "Token merging stride x": None if opts.token_merging_stride_x == 2 else opts.token_merging_stride_x, - "Token merging stride y": None if opts.token_merging_stride_y == 2 else opts.token_merging_stride_y, - "Token merging maximum down sampling": None if opts.token_merging_maximum_down_sampling == 1 else opts.token_merging_maximum_down_sampling, + "Token merging ratio": None if opts.token_merging_ratio == 0 else opts.token_merging_ratio, + "Token merging ratio hr": None if not p.enable_hr or opts.token_merging_ratio_hr == 0 else opts.token_merging_ratio_hr, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, @@ -538,15 +531,15 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - if opts.token_merging and not opts.token_merging_hr_only: + if opts.token_merging_ratio > 0: sd_models.apply_token_merging(sd_model=p.sd_model, hr=False) - logger.debug('Token merging applied') + logger.debug(f"Token merging applied to first pass. Ratio: '{opts.token_merging_ratio}'") res = process_images_inner(p) finally: # undo model optimizations made by tomesd - if opts.token_merging: + if opts.token_merging_ratio > 0: tomesd.remove_patch(p.sd_model) logger.debug('Token merging model optimizations removed') @@ -1003,19 +996,21 @@ def save_intermediate(image, index): devices.torch_gc() # apply token merging optimizations from tomesd for high-res pass - # check if hr_only so we are not redundantly patching - if opts.token_merging and (opts.token_merging_hr_only or opts.token_merging_ratio_hr != opts.token_merging_ratio): - # case where user wants to use separate merge ratios - if not opts.token_merging_hr_only: - # clean patch done by first pass. (clobbering the first patch might be fine? this might be excessive) + if opts.token_merging_ratio_hr > 0: + # in case the user has used separate merge ratios + if opts.token_merging_ratio > 0: tomesd.remove_patch(self.sd_model) - logger.debug('Temporarily removed token merging optimizations in preparation for next pass') + logger.debug('Adjusting token merging ratio for high-res pass') sd_models.apply_token_merging(sd_model=self.sd_model, hr=True) - logger.debug('Applied token merging for high-res pass') + logger.debug(f"Applied token merging for high-res pass. Ratio: '{opts.token_merging_ratio_hr}'") samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) + if opts.token_merging_ratio_hr > 0 or opts.token_merging_ratio > 0: + tomesd.remove_patch(self.sd_model) + logger.debug('Removed token merging optimizations from model') + self.is_hr_pass = False return samples diff --git a/modules/sd_models.py b/modules/sd_models.py index 4787193c0a5..4c9a0a1fb70 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -596,11 +596,8 @@ def apply_token_merging(sd_model, hr: bool): tomesd.apply_patch( sd_model, ratio=ratio, - max_downsample=shared.opts.token_merging_maximum_down_sampling, - sx=shared.opts.token_merging_stride_x, - sy=shared.opts.token_merging_stride_y, - use_rand=shared.opts.token_merging_random, - merge_attn=shared.opts.token_merging_merge_attention, - merge_crossattn=shared.opts.token_merging_merge_cross_attention, - merge_mlp=shared.opts.token_merging_merge_mlp + use_rand=False, # can cause issues with some samplers + merge_attn=True, + merge_crossattn=False, + merge_mlp=False ) diff --git a/modules/shared.py b/modules/shared.py index 4b3465855a2..0d96c14cb37 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -459,47 +459,13 @@ def list_samplers(): })) options_templates.update(options_section(('token_merging', 'Token Merging'), { - "token_merging": OptionInfo( - False, "Enable redundant token merging via tomesd. This can provide significant speed and memory improvements.", - gr.Checkbox - ), - "token_merging_ratio": OptionInfo( - 0.5, "Merging Ratio", - gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} - ), - "token_merging_hr_only": OptionInfo( - True, "Apply only to high-res fix pass. Disabling can yield a ~20-35% speedup on contemporary resolutions.", - gr.Checkbox - ), "token_merging_ratio_hr": OptionInfo( - 0.5, "Merging Ratio (high-res pass) - If 'Apply only to high-res' is enabled, this will always be the ratio used.", + 0, "Merging Ratio (high-res pass)", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} ), - # More advanced/niche settings: - "token_merging_random": OptionInfo( - False, "Use random perturbations - Can improve outputs for certain samplers. For others, it may cause visual artifacting.", - gr.Checkbox - ), - "token_merging_merge_attention": OptionInfo( - True, "Merge attention", - gr.Checkbox - ), - "token_merging_merge_cross_attention": OptionInfo( - False, "Merge cross attention", - gr.Checkbox - ), - "token_merging_merge_mlp": OptionInfo( - False, "Merge mlp", - gr.Checkbox - ), - "token_merging_maximum_down_sampling": OptionInfo(1, "Maximum down sampling", gr.Radio, lambda: {"choices": [1, 2, 4, 8]}), - "token_merging_stride_x": OptionInfo( - 2, "Stride - X", - gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} - ), - "token_merging_stride_y": OptionInfo( - 2, "Stride - Y", - gr.Slider, {"minimum": 2, "maximum": 8, "step": 2} + "token_merging_ratio": OptionInfo( + 0, "Merging Ratio", + gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} ) })) From 917faa5325371e51d68f7d6f7b15ea4466bd5adf Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Sat, 13 May 2023 10:26:09 -0500 Subject: [PATCH 0187/2418] move to stable-diffusion tab --- modules/shared.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 0d96c14cb37..e49e9b74c54 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -350,6 +350,8 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source. Changes seeds drastically. Use CPU to produce the same picture across different vidocard vendors.", gr.Radio, {"choices": ["GPU", "CPU"]}), + "token_merging_ratio_hr": OptionInfo(0, "Merging Ratio (high-res pass)", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), + "token_merging_ratio": OptionInfo(0, "Merging Ratio", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}) })) options_templates.update(options_section(('compatibility', "Compatibility"), { @@ -458,16 +460,6 @@ def list_samplers(): "sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"), })) -options_templates.update(options_section(('token_merging', 'Token Merging'), { - "token_merging_ratio_hr": OptionInfo( - 0, "Merging Ratio (high-res pass)", - gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} - ), - "token_merging_ratio": OptionInfo( - 0, "Merging Ratio", - gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} - ) -})) options_templates.update() From c2fdb44880e07f43aee2f7edc1dc36a9516501e8 Mon Sep 17 00:00:00 2001 From: papuSpartan <30642826+papuSpartan@users.noreply.github.com> Date: Sat, 13 May 2023 11:11:02 -0500 Subject: [PATCH 0188/2418] fix for img2img --- modules/processing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 32ff61e9294..94fe2625cd7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -34,7 +34,7 @@ # add a logger for the processing module logger = logging.getLogger(__name__) # manually set output level here since there is no option to do so yet through launch options -logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') +# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') # some of those options should not be changed at all because they would break the model, so I removed them from options. @@ -478,6 +478,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter index = position_in_batch + iteration * p.batch_size clip_skip = getattr(p, 'clip_skip', opts.CLIP_stop_at_last_layers) + enable_hr = getattr(p, 'enable_hr', False) generation_params = { "Steps": p.steps, @@ -497,7 +498,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Clip skip": None if clip_skip <= 1 else clip_skip, "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, "Token merging ratio": None if opts.token_merging_ratio == 0 else opts.token_merging_ratio, - "Token merging ratio hr": None if not p.enable_hr or opts.token_merging_ratio_hr == 0 else opts.token_merging_ratio_hr, + "Token merging ratio hr": None if not enable_hr or opts.token_merging_ratio_hr == 0 else opts.token_merging_ratio_hr, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, From 1f57b948b78df872c5a8a1c6e6c7e3c35e06f969 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sat, 13 May 2023 19:14:10 +0300 Subject: [PATCH 0189/2418] Move localization to its own script block and load it first --- modules/localization.py | 4 ++-- modules/ui.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/localization.py b/modules/localization.py index f6a6f2fbdf2..ee9c65e7dce 100644 --- a/modules/localization.py +++ b/modules/localization.py @@ -23,7 +23,7 @@ def list_localizations(dirname): localizations[fn] = file.path -def localization_js(current_localization_name): +def localization_js(current_localization_name: str) -> str: fn = localizations.get(current_localization_name, None) data = {} if fn is not None: @@ -34,4 +34,4 @@ def localization_js(current_localization_name): print(f"Error loading localization from {fn}:", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) - return f"var localization = {json.dumps(data)}\n" + return f"window.localization = {json.dumps(data)}" diff --git a/modules/ui.py b/modules/ui.py index ff82fff6c84..ff25c4ce987 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1771,12 +1771,11 @@ def webpath(fn): def javascript_html(): - script_js = os.path.join(script_path, "script.js") - head = f'\n' + # Ensure localization is in `window` before scripts + head = f'\n' - inline = f"{localization.localization_js(shared.opts.localization)};" - if cmd_opts.theme is not None: - inline += f"set_theme('{cmd_opts.theme}');" + script_js = os.path.join(script_path, "script.js") + head += f'\n' for script in modules.scripts.list_scripts("javascript", ".js"): head += f'\n' @@ -1784,7 +1783,8 @@ def javascript_html(): for script in modules.scripts.list_scripts("javascript", ".mjs"): head += f'\n' - head += f'\n' + if cmd_opts.theme: + head += f'\n' return head From cd6990c243e926672ff84e7db1ca34ae60015486 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sat, 13 May 2023 19:22:39 +0300 Subject: [PATCH 0190/2418] Make dump translations work again --- javascript/localization.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/javascript/localization.js b/javascript/localization.js index d0bdfc16e19..86e5ca67cfb 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -109,18 +109,23 @@ function processNode(node){ } function dumpTranslations(){ + if(!hasLocalization()) { + // If we don't have any localization, + // we will not have traversed the app to find + // original_lines, so do that now. + processNode(gradioApp()); + } var dumped = {} if (localization.rtl) { - dumped.rtl = true + dumped.rtl = true; } - Object.keys(original_lines).forEach(function(text){ - if(dumped[text] !== undefined) return - - dumped[text] = localization[text] || text - }) + for (const text in original_lines) { + if(dumped[text] !== undefined) continue; + dumped[text] = localization[text] || text; + } - return dumped + return dumped; } function download_localization() { From 477199357f4f5f02d62857a0cf432a3ed19e6418 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 20:15:37 +0300 Subject: [PATCH 0191/2418] add an option to always refer to lora by filenames never refer to lora by an alias if multiple loras have same alias or the alias is called none --- extensions-builtin/Lora/lora.py | 6 ++++++ extensions-builtin/Lora/scripts/lora_script.py | 1 + extensions-builtin/Lora/ui_extra_networks_lora.py | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index ba1293dfc88..6fa80006897 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -393,6 +393,8 @@ def lora_MultiheadAttention_load_state_dict(self, *args, **kwargs): def list_available_loras(): available_loras.clear() available_lora_aliases.clear() + forbidden_lora_aliases.clear() + forbidden_lora_aliases.update({"none": 1}) os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) @@ -406,6 +408,9 @@ def list_available_loras(): available_loras[name] = entry + if entry.alias in available_lora_aliases: + forbidden_lora_aliases[entry.alias.lower()] = 1 + available_lora_aliases[name] = entry available_lora_aliases[entry.alias] = entry @@ -445,6 +450,7 @@ def infotext_pasted(infotext, params): available_loras = {} available_lora_aliases = {} +forbidden_lora_aliases = {} loaded_loras = [] list_available_loras() diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 7db971fd59b..060bda0594c 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -54,6 +54,7 @@ def before_ui(): shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), { "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None"] + [x for x in lora.available_loras]}, refresh=lora.list_available_loras), + "lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}), })) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index a0edbc1ebf6..2050e3faabd 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -15,13 +15,19 @@ def refresh(self): def list_items(self): for name, lora_on_disk in lora.available_loras.items(): path, ext = os.path.splitext(lora_on_disk.filename) + + if shared.opts.lora_preferred_name == "Filename" or lora_on_disk.alias.lower() in lora.forbidden_lora_aliases: + alias = name + else: + alias = lora_on_disk.alias + yield { "name": name, "filename": path, "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), - "prompt": json.dumps(f""), + "prompt": json.dumps(f""), "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, } From 7e3539df6f4e3979e080ed5d76faa3649c10f76f Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 20:21:11 +0300 Subject: [PATCH 0192/2418] fix upscalers disappearing after the user reloads UI --- modules/modelloader.py | 27 ++++++++++----------------- webui.py | 6 +----- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index cb85ac4ffc8..a70aa0e37f0 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -117,20 +117,6 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None): pass -builtin_upscaler_classes = [] -forbidden_upscaler_classes = set() - - -def list_builtin_upscalers(): - builtin_upscaler_classes.clear() - builtin_upscaler_classes.extend(Upscaler.__subclasses__()) - -def forbid_loaded_nonbuiltin_upscalers(): - for cls in Upscaler.__subclasses__(): - if cls not in builtin_upscaler_classes: - forbidden_upscaler_classes.add(cls) - - def load_upscalers(): # We can only do this 'magic' method to dynamically load upscalers if they are referenced, # so we'll try to import any _model.py files before looking in __subclasses__ @@ -146,10 +132,17 @@ def load_upscalers(): datas = [] commandline_options = vars(shared.cmd_opts) - for cls in Upscaler.__subclasses__(): - if cls in forbidden_upscaler_classes: - continue + # some of upscaler classes will not go away after reloading their modules, and we'll end + # up with two copies of those classes. The newest copy will always be the last in the list, + # so we go from end to beginning and ignore duplicates + used_classes = {} + for cls in reversed(Upscaler.__subclasses__()): + classname = str(cls) + if classname not in used_classes: + used_classes[classname] = cls + + for cls in reversed(used_classes.values()): name = cls.__name__ cmd_name = f"{name.lower().replace('upscaler', '')}_models_path" scaler = cls(commandline_options.get(cmd_name, None)) diff --git a/webui.py b/webui.py index 727ebd31d10..e8f0a63dd9f 100644 --- a/webui.py +++ b/webui.py @@ -181,14 +181,11 @@ def initialize(): gfpgan.setup_model(cmd_opts.gfpgan_models_path) startup_timer.record("setup gfpgan") - modelloader.list_builtin_upscalers() - startup_timer.record("list builtin upscalers") - modules.scripts.load_scripts() startup_timer.record("load scripts") modelloader.load_upscalers() - #startup_timer.record("load upscalers") #Is this necessary? I don't know. + startup_timer.record("load upscalers") #Is this necessary? I don't know. modules.sd_vae.refresh_vae_list() startup_timer.record("refresh VAE") @@ -388,7 +385,6 @@ def fastapi_setup(self): localization.list_localizations(cmd_opts.localizations_dir) - modelloader.forbid_loaded_nonbuiltin_upscalers() modules.scripts.reload_scripts() startup_timer.record("load scripts") From 063848798c4d4df6d3e108f4cc00c35ca38f5ebd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 13 May 2023 19:45:18 +0300 Subject: [PATCH 0193/2418] Merge pull request #10339 from catboxanon/bf16 Allow bf16 in safe unpickler --- modules/safe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/safe.py b/modules/safe.py index e6c2f2c0b7b..e1a67f7368a 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -40,7 +40,7 @@ def find_class(self, module, name): return getattr(collections, name) if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter', '_rebuild_device_tensor_from_numpy']: return getattr(torch._utils, name) - if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32']: + if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32', 'BFloat16Storage']: return getattr(torch, name) if module == 'torch.nn.modules.container' and name in ['ParameterDict']: return getattr(torch.nn.modules.container, name) From 12c78138dd56eef029ce74e371c8e646cb07f6fb Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 13 May 2023 19:43:15 +0300 Subject: [PATCH 0194/2418] Merge pull request #10324 from catboxanon/offline Allow web UI to be ran fully offline --- modules/shared.py | 9 +++++++-- style.css | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 4631965b4c0..b3508883a33 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -667,14 +667,19 @@ def reload_gradio_theme(theme_name=None): if not theme_name: theme_name = opts.gradio_theme + default_theme_args = dict( + font=["Source Sans Pro", 'ui-sans-serif', 'system-ui', 'sans-serif'], + font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'], + ) + if theme_name == "Default": - gradio_theme = gr.themes.Default() + gradio_theme = gr.themes.Default(**default_theme_args) else: try: gradio_theme = gr.themes.ThemeClass.from_hub(theme_name) except Exception as e: errors.display(e, "changing gradio theme") - gradio_theme = gr.themes.Default() + gradio_theme = gr.themes.Default(**default_theme_args) diff --git a/style.css b/style.css index b823c7ddbc5..31b2ed5a694 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,6 @@ +/* temporary fix to load default gradio font in frontend instead of backend */ + +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600&display=swap'); /* general gradio fixes */ From 27f7fbf35cd72d547d830f97828ee13d3d2009aa Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 20:24:48 +0300 Subject: [PATCH 0195/2418] update readme --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1727864dd5..b586b271cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## Upcoming 1.2.1 + +### Features: + * add an option to always refer to lora by filenames + +### Bug Fixes: + * never refer to lora by an alias if multiple loras have same alias or the alias is called none + * fix upscalers disappearing after the user reloads UI + * allow bf16 in safe unpickler (resolves problems with loading some loras) + * allow web UI to be ran fully offline + ## 1.2.0 ### Features: From 86ff43b930077aa41439c570fe41ad5de910455d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 13 May 2023 19:44:55 +0300 Subject: [PATCH 0196/2418] Merge pull request #10335 from akx/l10n-dis-take-2 Localization fixes --- javascript/localization.js | 60 +++++++++++++++++++++----------------- modules/localization.py | 4 +-- modules/ui.py | 12 ++++---- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/javascript/localization.js b/javascript/localization.js index 0123b877bdb..86e5ca67cfb 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -109,18 +109,23 @@ function processNode(node){ } function dumpTranslations(){ + if(!hasLocalization()) { + // If we don't have any localization, + // we will not have traversed the app to find + // original_lines, so do that now. + processNode(gradioApp()); + } var dumped = {} if (localization.rtl) { - dumped.rtl = true + dumped.rtl = true; } - Object.keys(original_lines).forEach(function(text){ - if(dumped[text] !== undefined) return - - dumped[text] = localization[text] || text - }) + for (const text in original_lines) { + if(dumped[text] !== undefined) continue; + dumped[text] = localization[text] || text; + } - return dumped + return dumped; } function download_localization() { @@ -137,7 +142,11 @@ function download_localization() { document.body.removeChild(element); } -if(hasLocalization()) { +document.addEventListener("DOMContentLoaded", function () { + if (!hasLocalization()) { + return; + } + onUiUpdate(function (m) { m.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { @@ -146,26 +155,23 @@ if(hasLocalization()) { }); }) + processNode(gradioApp()) - document.addEventListener("DOMContentLoaded", function () { - processNode(gradioApp()) + if (localization.rtl) { // if the language is from right to left, + (new MutationObserver((mutations, observer) => { // wait for the style to load + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.tagName === 'STYLE') { + observer.disconnect(); - if (localization.rtl) { // if the language is from right to left, - (new MutationObserver((mutations, observer) => { // wait for the style to load - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.tagName === 'STYLE') { - observer.disconnect(); - - for (const x of node.sheet.rules) { // find all rtl media rules - if (Array.from(x.media || []).includes('rtl')) { - x.media.appendMedium('all'); // enable them - } + for (const x of node.sheet.rules) { // find all rtl media rules + if (Array.from(x.media || []).includes('rtl')) { + x.media.appendMedium('all'); // enable them } } - }) - }); - })).observe(gradioApp(), { childList: true }); - } - }) -} + } + }) + }); + })).observe(gradioApp(), { childList: true }); + } +}) diff --git a/modules/localization.py b/modules/localization.py index f6a6f2fbdf2..ee9c65e7dce 100644 --- a/modules/localization.py +++ b/modules/localization.py @@ -23,7 +23,7 @@ def list_localizations(dirname): localizations[fn] = file.path -def localization_js(current_localization_name): +def localization_js(current_localization_name: str) -> str: fn = localizations.get(current_localization_name, None) data = {} if fn is not None: @@ -34,4 +34,4 @@ def localization_js(current_localization_name): print(f"Error loading localization from {fn}:", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) - return f"var localization = {json.dumps(data)}\n" + return f"window.localization = {json.dumps(data)}" diff --git a/modules/ui.py b/modules/ui.py index d02f6e82c3a..f07bcc41e43 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1863,12 +1863,11 @@ def webpath(fn): def javascript_html(): - script_js = os.path.join(script_path, "script.js") - head = f'\n' + # Ensure localization is in `window` before scripts + head = f'\n' - inline = f"{localization.localization_js(shared.opts.localization)};" - if cmd_opts.theme is not None: - inline += f"set_theme('{cmd_opts.theme}');" + script_js = os.path.join(script_path, "script.js") + head += f'\n' for script in modules.scripts.list_scripts("javascript", ".js"): head += f'\n' @@ -1876,7 +1875,8 @@ def javascript_html(): for script in modules.scripts.list_scripts("javascript", ".mjs"): head += f'\n' - head += f'\n' + if cmd_opts.theme: + head += f'\n' return head From d7e9ac2aff6bfd3c03f9d50a98e31468fd34a77f Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sat, 13 May 2023 20:47:32 +0300 Subject: [PATCH 0197/2418] update readme --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b586b271cdf..a7bf4c2feaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * fix upscalers disappearing after the user reloads UI * allow bf16 in safe unpickler (resolves problems with loading some loras) * allow web UI to be ran fully offline + * fix localizations not working ## 1.2.0 From 3078001439d25b66ef5627c9e3d431aa23bbed73 Mon Sep 17 00:00:00 2001 From: catboxanon <122327233+catboxanon@users.noreply.github.com> Date: Sun, 14 May 2023 01:49:41 +0000 Subject: [PATCH 0198/2418] Add/modify CFG callbacks Required by self-attn guidance extension https://github.com/ashen-sensored/sd_webui_SAG --- modules/script_callbacks.py | 35 +++++++++++++++++++++++++++++++ modules/sd_samplers_kdiffusion.py | 8 ++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 7d9dd736121..e83c6ecf539 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -53,6 +53,21 @@ def __init__(self, x, image_cond, sigma, sampling_step, total_sampling_steps, te class CFGDenoisedParams: + def __init__(self, x, sampling_step, total_sampling_steps, inner_model): + self.x = x + """Latent image representation in the process of being denoised""" + + self.sampling_step = sampling_step + """Current Sampling step number""" + + self.total_sampling_steps = total_sampling_steps + """Total number of sampling steps planned""" + + self.inner_model = inner_model + """Inner model reference that is being used for denoising""" + + +class AfterCFGCallbackParams: def __init__(self, x, sampling_step, total_sampling_steps): self.x = x """Latent image representation in the process of being denoised""" @@ -63,6 +78,9 @@ def __init__(self, x, sampling_step, total_sampling_steps): self.total_sampling_steps = total_sampling_steps """Total number of sampling steps planned""" + self.output_altered = False + """A flag for CFGDenoiser that indicates whether the output has been altered by the callback""" + class UiTrainTabParams: def __init__(self, txt2img_preview_params): @@ -87,6 +105,7 @@ def __init__(self, imgs, cols, rows): callbacks_image_saved=[], callbacks_cfg_denoiser=[], callbacks_cfg_denoised=[], + callbacks_cfg_after_cfg=[], callbacks_before_component=[], callbacks_after_component=[], callbacks_image_grid=[], @@ -186,6 +205,14 @@ def cfg_denoised_callback(params: CFGDenoisedParams): report_exception(c, 'cfg_denoised_callback') +def cfg_after_cfg_callback(params: AfterCFGCallbackParams): + for c in callback_map['callbacks_cfg_after_cfg']: + try: + c.callback(params) + except Exception: + report_exception(c, 'cfg_after_cfg_callback') + + def before_component_callback(component, **kwargs): for c in callback_map['callbacks_before_component']: try: @@ -332,6 +359,14 @@ def on_cfg_denoised(callback): add_callback(callback_map['callbacks_cfg_denoised'], callback) +def on_cfg_after_cfg(callback): + """register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations has completed. + The callback is called with one argument: + - params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details. + """ + add_callback(callback_map['callbacks_cfg_after_cfg'], callback) + + def on_before_component(callback): """register a function to be called before a component is created. The callback is called with arguments: diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index e9e41818c81..55f0d3a3e25 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -8,6 +8,7 @@ import modules.shared as shared from modules.script_callbacks import CFGDenoiserParams, cfg_denoiser_callback from modules.script_callbacks import CFGDenoisedParams, cfg_denoised_callback +from modules.script_callbacks import AfterCFGCallbackParams, cfg_after_cfg_callback samplers_k_diffusion = [ ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {}), @@ -160,7 +161,7 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): fake_uncond = torch.cat([x_out[i:i+1] for i in denoised_image_indexes]) x_out = torch.cat([x_out, fake_uncond]) # we skipped uncond denoising, so we put cond-denoised image to where the uncond-denoised image should be - denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps) + denoised_params = CFGDenoisedParams(x_out, state.sampling_step, state.sampling_steps, self.inner_model) cfg_denoised_callback(denoised_params) devices.test_for_nans(x_out, "unet") @@ -180,6 +181,11 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): if self.mask is not None: denoised = self.init_latent * self.mask + self.nmask * denoised + after_cfg_callback_params = AfterCFGCallbackParams(denoised, state.sampling_step, state.sampling_steps) + cfg_after_cfg_callback(after_cfg_callback_params) + if after_cfg_callback_params.output_altered: + denoised = after_cfg_callback_params.x + self.step += 1 return denoised From 8abfc95013d247c8a863d048574bc1f9d1eb0443 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 12:56:34 +0800 Subject: [PATCH 0199/2418] Update script_callbacks.py --- modules/script_callbacks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index e83c6ecf539..57dfd457f22 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -64,7 +64,7 @@ def __init__(self, x, sampling_step, total_sampling_steps, inner_model): """Total number of sampling steps planned""" self.inner_model = inner_model - """Inner model reference that is being used for denoising""" + """Inner model reference used for denoising""" class AfterCFGCallbackParams: @@ -79,7 +79,7 @@ def __init__(self, x, sampling_step, total_sampling_steps): """Total number of sampling steps planned""" self.output_altered = False - """A flag for CFGDenoiser that indicates whether the output has been altered by the callback""" + """A flag for CFGDenoiser indicating whether the output has been altered by the callback""" class UiTrainTabParams: @@ -360,9 +360,9 @@ def on_cfg_denoised(callback): def on_cfg_after_cfg(callback): - """register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations has completed. + """register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations are completed. The callback is called with one argument: - - params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details. + - params: AfterCFGCallbackParams - parameters to be passed to the script for post-processing after cfg calculation. """ add_callback(callback_map['callbacks_cfg_after_cfg'], callback) From 005849331e82cded96f6f3e5ff828037c672c38d Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 08:15:22 +0300 Subject: [PATCH 0200/2418] remove output_altered flag from AfterCFGCallbackParams --- modules/script_callbacks.py | 3 --- modules/sd_samplers_kdiffusion.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 57dfd457f22..3c21a362884 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -78,9 +78,6 @@ def __init__(self, x, sampling_step, total_sampling_steps): self.total_sampling_steps = total_sampling_steps """Total number of sampling steps planned""" - self.output_altered = False - """A flag for CFGDenoiser indicating whether the output has been altered by the callback""" - class UiTrainTabParams: def __init__(self, txt2img_preview_params): diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 55f0d3a3e25..61f23ad7fe3 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -183,8 +183,7 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): after_cfg_callback_params = AfterCFGCallbackParams(denoised, state.sampling_step, state.sampling_steps) cfg_after_cfg_callback(after_cfg_callback_params) - if after_cfg_callback_params.output_altered: - denoised = after_cfg_callback_params.x + denoised = after_cfg_callback_params.x self.step += 1 return denoised From 2cfaffb239bb2b99aab06352f8c101e48e48dec9 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 08:30:37 +0300 Subject: [PATCH 0201/2418] updates for #9256 --- modules/generation_parameters_copypaste.py | 2 +- modules/shared.py | 4 ++-- requirements.txt | 1 + requirements_versions.txt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index a0a98bbcb03..f1a2204c3a9 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -311,7 +311,7 @@ def parse_generation_parameters(x: str): ('Token merging ratio', 'token_merging_ratio'), ('Token merging ratio hr', 'token_merging_ratio_hr'), ('RNG', 'randn_source'), - ('NGMS', 's_min_uncond') + ('NGMS', 's_min_uncond'), ] diff --git a/modules/shared.py b/modules/shared.py index a5e8d0bd019..7ec9967e384 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -350,8 +350,8 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source. Changes seeds drastically. Use CPU to produce the same picture across different vidocard vendors.", gr.Radio, {"choices": ["GPU", "CPU"]}), - "token_merging_ratio_hr": OptionInfo(0, "Merging Ratio (high-res pass)", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}), - "token_merging_ratio": OptionInfo(0, "Merging Ratio", gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1}) + "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), + "token_merging_ratio_hr": OptionInfo(0.0, "Togen merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), })) options_templates.update(options_section(('compatibility', "Compatibility"), { diff --git a/requirements.txt b/requirements.txt index 2423bfd2291..302b3dabd08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ torchsde safetensors psutil rich +tomesd diff --git a/requirements_versions.txt b/requirements_versions.txt index 0e03deedc0c..17ae9484cbe 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -26,4 +26,4 @@ torchsde==0.2.5 safetensors==0.3.1 httpcore<=0.15 fastapi==0.94.0 -tomesd>=0.1.2 \ No newline at end of file +tomesd==0.1.2 From e14b586d0494d6c5cc3cbc45b5fa00c03d052443 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 12:42:44 +0800 Subject: [PATCH 0202/2418] Add Tiny AE live preview --- modules/sd_samplers_common.py | 21 ++++++---- modules/sd_vae_taesd.py | 76 +++++++++++++++++++++++++++++++++++ modules/shared.py | 2 +- webui.py | 11 +++++ 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 modules/sd_vae_taesd.py diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index bc074238141..d3dc130c549 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -2,7 +2,7 @@ import numpy as np import torch from PIL import Image -from modules import devices, processing, images, sd_vae_approx +from modules import devices, processing, images, sd_vae_approx, sd_vae_taesd from modules.shared import opts, state import modules.shared as shared @@ -22,21 +22,26 @@ def setup_img2img_steps(p, steps=None): return steps, t_enc -approximation_indexes = {"Full": 0, "Approx NN": 1, "Approx cheap": 2} +approximation_indexes = {"Full": 0, "Tiny AE": 1, "Approx NN": 2, "Approx cheap": 3} def single_sample_to_image(sample, approximation=None): if approximation is None: approximation = approximation_indexes.get(opts.show_progress_type, 0) - if approximation == 2: - x_sample = sd_vae_approx.cheap_approximation(sample) - elif approximation == 1: - x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + if approximation == 1: + x_sample = sd_vae_taesd.decode()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + x_sample = sd_vae_taesd.TAESD.unscale_latents(x_sample) + x_sample = torch.clamp((x_sample * 0.25) + 0.5, 0, 1) else: - x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] + if approximation == 3: + x_sample = sd_vae_approx.cheap_approximation(sample) + elif approximation == 2: + x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + else: + x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] + x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) - x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) return Image.fromarray(x_sample) diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py new file mode 100644 index 00000000000..ccc979593ba --- /dev/null +++ b/modules/sd_vae_taesd.py @@ -0,0 +1,76 @@ +""" +Tiny AutoEncoder for Stable Diffusion +(DNN for encoding / decoding SD's latent space) + +https://github.com/madebyollin/taesd +""" +import os +import torch +import torch.nn as nn + +from modules import devices, paths_internal + +sd_vae_taesd = None + + +def conv(n_in, n_out, **kwargs): + return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs) + + +class Clamp(nn.Module): + @staticmethod + def forward(x): + return torch.tanh(x / 3) * 3 + + +class Block(nn.Module): + def __init__(self, n_in, n_out): + super().__init__() + self.conv = nn.Sequential(conv(n_in, n_out), nn.ReLU(), conv(n_out, n_out), nn.ReLU(), conv(n_out, n_out)) + self.skip = nn.Conv2d(n_in, n_out, 1, bias=False) if n_in != n_out else nn.Identity() + self.fuse = nn.ReLU() + + def forward(self, x): + return self.fuse(self.conv(x) + self.skip(x)) + + +def decoder(): + return nn.Sequential( + Clamp(), conv(4, 64), nn.ReLU(), + Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), + Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), + Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), + Block(64, 64), conv(64, 3), + ) + + +class TAESD(nn.Module): + latent_magnitude = 2 + latent_shift = 0.5 + + def __init__(self, decoder_path="taesd_decoder.pth"): + """Initialize pretrained TAESD on the given device from the given checkpoints.""" + super().__init__() + self.decoder = decoder() + self.decoder.load_state_dict( + torch.load(decoder_path, map_location='cpu' if devices.device.type != 'cuda' else None)) + + @staticmethod + def unscale_latents(x): + """[0, 1] -> raw latents""" + return x.sub(TAESD.latent_shift).mul(2 * TAESD.latent_magnitude) + + +def decode(): + global sd_vae_taesd + + if sd_vae_taesd is None: + model_path = os.path.join(paths_internal.models_path, "VAE-approx", "taesd_decoder.pth") + if os.path.exists(model_path): + sd_vae_taesd = TAESD(model_path) + sd_vae_taesd.eval() + sd_vae_taesd.to(devices.device, devices.dtype) + else: + raise FileNotFoundError('Tiny AE mdoel not found') + + return sd_vae_taesd.decoder diff --git a/modules/shared.py b/modules/shared.py index 4631965b4c0..6760a9005e2 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -425,7 +425,7 @@ def list_samplers(): "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), + "show_progress_type": OptionInfo("Tiny AE", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Tiny AE", "Approx NN", "Approx cheap"]}), "live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), "live_preview_refresh_period": OptionInfo(1000, "Progressbar/preview update period, in milliseconds") })) diff --git a/webui.py b/webui.py index 727ebd31d10..0a928434877 100644 --- a/webui.py +++ b/webui.py @@ -144,10 +144,21 @@ def check_versions(): """.strip()) +def check_taesd(): + from modules.paths_internal import models_path + + model_url = 'https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth' + model_path = os.path.join(models_path, "VAE-approx", "taesd_decoder.pth") + if not os.path.exists(model_path): + print('download taesd model') + torch.hub.download_url_to_file(model_url, os.path.dirname(model_path)) + + def initialize(): fix_asyncio_event_loop_policy() check_versions() + check_taesd() extensions.list_extensions() localization.list_localizations(cmd_opts.localizations_dir) From bd9b9d425a355e151b43047a5df5fcead2fcdc52 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 13:19:11 +0800 Subject: [PATCH 0203/2418] Add live preview mode check --- modules/sd_samplers_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index d3dc130c549..b1e8a780602 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -26,8 +26,8 @@ def setup_img2img_steps(p, steps=None): def single_sample_to_image(sample, approximation=None): - if approximation is None: - approximation = approximation_indexes.get(opts.show_progress_type, 0) + if approximation is None or approximation not in approximation_indexes.keys(): + approximation = approximation_indexes.get(opts.show_progress_type, 1) if approximation == 1: x_sample = sd_vae_taesd.decode()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() From ce515b81c57a2028ea515bd8f6f7984ba0f08963 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 10:02:51 +0300 Subject: [PATCH 0204/2418] set up a system to provide extra info for settings elements in python rather than js add a bit of spacing/styling to settings elements add link info for token merging --- javascript/ui_settings_hints.js | 92 ++++++++++++++++++++------------- modules/shared.py | 33 +++++++++--- style.css | 20 +++++++ 3 files changed, 103 insertions(+), 42 deletions(-) diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js index 87a289d318c..9251fd71131 100644 --- a/javascript/ui_settings_hints.js +++ b/javascript/ui_settings_hints.js @@ -1,41 +1,61 @@ // various hints and extra info for the settings tab -onUiLoaded(function(){ - createLink = function(elem_id, text, href){ - var a = document.createElement('A') - a.textContent = text - a.target = '_blank'; - - elem = gradioApp().querySelector('#'+elem_id) - elem.insertBefore(a, elem.querySelector('label')) - - return a - } - - createLink("setting_samples_filename_pattern", "[wiki] ").href = "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory" - createLink("setting_directories_filename_pattern", "[wiki] ").href = "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory" - - createLink("setting_quicksettings_list", "[info] ").addEventListener("click", function(event){ - requestGet("./internal/quicksettings-hint", {}, function(data){ - var table = document.createElement('table') - table.className = 'settings-value-table' - - data.forEach(function(obj){ - var tr = document.createElement('tr') - var td = document.createElement('td') - td.textContent = obj.name - tr.appendChild(td) - - var td = document.createElement('td') - td.textContent = obj.label - tr.appendChild(td) - - table.appendChild(tr) - }) - - popup(table); - }) - }); +settingsHintsSetup = false + +onOptionsChanged(function(){ + if(settingsHintsSetup) return + settingsHintsSetup = true + + gradioApp().querySelectorAll('#settings [id^=setting_]').forEach(function(div){ + var name = div.id.substr(8) + var commentBefore = opts._comments_before[name] + var commentAfter = opts._comments_after[name] + + if(! commentBefore && !commentAfter) return + + var span = null + if(div.classList.contains('gradio-checkbox')) span = div.querySelector('label span') + else if(div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span') + else span = div.querySelector('label span').firstChild + + if(!span) return + + if(commentBefore){ + var comment = document.createElement('DIV') + comment.className = 'settings-comment' + comment.innerHTML = commentBefore + span.parentElement.insertBefore(document.createTextNode('\xa0'), span) + span.parentElement.insertBefore(comment, span) + span.parentElement.insertBefore(document.createTextNode('\xa0'), span) + } + if(commentAfter){ + var comment = document.createElement('DIV') + comment.className = 'settings-comment' + comment.innerHTML = commentAfter + span.parentElement.insertBefore(comment, span.nextSibling) + span.parentElement.insertBefore(document.createTextNode('\xa0'), span.nextSibling) + } + }) }) +function settingsHintsShowQuicksettings(){ + requestGet("./internal/quicksettings-hint", {}, function(data){ + var table = document.createElement('table') + table.className = 'settings-value-table' + data.forEach(function(obj){ + var tr = document.createElement('tr') + var td = document.createElement('td') + td.textContent = obj.name + tr.appendChild(td) + + var td = document.createElement('td') + td.textContent = obj.label + tr.appendChild(td) + + table.appendChild(tr) + }) + + popup(table); + }) +} diff --git a/modules/shared.py b/modules/shared.py index 7ec9967e384..24fdcd59390 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -199,8 +199,9 @@ def assign_current_image(self, image): face_restorers = [] + class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None): + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after=''): self.default = default self.label = label self.component = component @@ -209,6 +210,24 @@ def __init__(self, default=None, label="", component=None, component_args=None, self.section = section self.refresh = refresh + self.comment_before = comment_before + """HTML text that will be added after label in UI""" + + self.comment_after = comment_after + """HTML text that will be added before label in UI""" + + def link(self, label, url): + self.comment_before += f"[{label}]" + return self + + def js(self, label, js_func): + self.comment_before += f"[{label}]" + return self + + def info(self, info): + self.comment_after += f"({info})" + return self + def options_section(section_identifier, options_dict): for v in options_dict.values(): @@ -240,7 +259,7 @@ def list_samplers(): options_templates.update(options_section(('saving-images', "Saving images/grids"), { "samples_save": OptionInfo(True, "Always save all generated images"), "samples_format": OptionInfo('png', 'File format for images'), - "samples_filename_pattern": OptionInfo("", "Images filename pattern", component_args=hide_dirs), + "samples_filename_pattern": OptionInfo("", "Images filename pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"), "save_images_add_number": OptionInfo(True, "Add number to filename when saving", component_args=hide_dirs), "grid_save": OptionInfo(True, "Always save all generated image grids"), @@ -290,7 +309,7 @@ def list_samplers(): "save_to_dirs": OptionInfo(True, "Save images to a subdirectory"), "grid_save_to_dirs": OptionInfo(True, "Save grids to a subdirectory"), "use_save_to_dirs_for_ui": OptionInfo(False, "When using \"Save\" button, save images to a subdirectory"), - "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), + "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"), "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1, **hide_dirs}), })) @@ -350,7 +369,7 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source. Changes seeds drastically. Use CPU to produce the same picture across different vidocard vendors.", gr.Radio, {"choices": ["GPU", "CPU"]}), - "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), + "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_hr": OptionInfo(0.0, "Togen merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), })) @@ -404,7 +423,7 @@ def list_samplers(): "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), - "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}), + "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings"), "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), @@ -572,7 +591,9 @@ def onchange(self, key, func, call=True): func() def dumpjson(self): - d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()} + d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()} + d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None} + d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None} return json.dumps(d) def add_option(self, key, info): diff --git a/style.css b/style.css index 8c7be275cec..1e978592892 100644 --- a/style.css +++ b/style.css @@ -421,6 +421,26 @@ table.settings-value-table td{ color: #aaa !important; } +#settings span{ + color: var(--body-text-color); +} + +#settings .gradio-textbox, #settings .gradio-slider, #settings .gradio-number, #settings .gradio-dropdown, #settings .gradio-checkboxgroup{ + margin-top: 0.75em; +} + +.gradio-textbox .settings-comment, .gradio-slider .settings-comment, .gradio-number .settings-comment, .gradio-dropdown .settings-comment, .gradio-checkboxgroup .settings-comment { + display: inline +} + +.settings-comment a{ + text-decoration: underline; +} + +.settings-comment .info{ + opacity: 0.75; +} + /* live preview */ .progressDiv{ position: relative; From a423f23d289225a39d6f93a03bfda13eddbb42b7 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 14 May 2023 16:22:40 +0900 Subject: [PATCH 0205/2418] allow jpeg for extra network preview --- modules/ui_extra_networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index e35d0bfee45..0baccf566ad 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -30,7 +30,7 @@ def fetch_file(filename: str = ""): raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.") ext = os.path.splitext(filename)[1].lower() - if ext not in (".png", ".jpg", ".webp"): + if ext not in (".png", ".jpg", ".jpeg", ".webp"): raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg and webp.") # would profit from returning 304 @@ -194,7 +194,7 @@ def find_preview(self, path): Find a preview PNG for a given path (without extension) and call link_preview on it. """ - preview_extensions = ["png", "jpg", "webp"] + preview_extensions = ["png", "jpg", "jpeg", "webp"] if shared.opts.samples_format not in preview_extensions: preview_extensions.append(shared.opts.samples_format) From a00e42556ffbc1b757fda5ba3f85a9e11c931441 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 11:04:21 +0300 Subject: [PATCH 0206/2418] add a bunch of descriptions and reword a lot of settings (sorry, localizers) --- .../ScuNET/scripts/scunet_model.py | 13 ++- javascript/ui_settings_hints.js | 3 +- modules/shared.py | 94 ++++++++++--------- style.css | 4 +- 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/extensions-builtin/ScuNET/scripts/scunet_model.py b/extensions-builtin/ScuNET/scripts/scunet_model.py index 1f5ea0d3d82..cc2cbc6a107 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -10,7 +10,7 @@ from basicsr.utils.download_util import load_file_from_url import modules.upscaler -from modules import devices, modelloader +from modules import devices, modelloader, script_callbacks from scunet_model_arch import SCUNet as net from modules.shared import opts @@ -137,3 +137,14 @@ def load_model(self, path: str): model = model.to(device) return model + + +def on_ui_settings(): + import gradio as gr + from modules import shared + + shared.opts.add_option("SCUNET_tile", shared.OptionInfo(256, "Tile size for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, section=('upscaling', "Upscaling")).info("0 = no tiling")) + shared.opts.add_option("SCUNET_tile_overlap", shared.OptionInfo(8, "Tile overlap for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}, section=('upscaling', "Upscaling")).info("Low values = visible seam")) + + +script_callbacks.on_ui_settings(on_ui_settings) diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js index 9251fd71131..6d1933dc8fe 100644 --- a/javascript/ui_settings_hints.js +++ b/javascript/ui_settings_hints.js @@ -15,7 +15,8 @@ onOptionsChanged(function(){ var span = null if(div.classList.contains('gradio-checkbox')) span = div.querySelector('label span') - else if(div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span') + else if(div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span').firstChild + else if(div.classList.contains('gradio-radio')) span = div.querySelector('span').firstChild else span = div.querySelector('label span').firstChild if(!span) return diff --git a/modules/shared.py b/modules/shared.py index 24fdcd59390..a057764437c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -228,6 +228,12 @@ def info(self, info): self.comment_after += f"({info})" return self + def needs_restart(self): + self.comment_after += " (requires restart)" + return self + + + def options_section(section_identifier, options_dict): for v in options_dict.values(): @@ -278,10 +284,10 @@ def list_samplers(): "save_mask_composite": OptionInfo(False, "For inpainting, save a masked composite"), "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), - "export_for_4chan": OptionInfo(True, "If the saved image file size is above the limit, or its either width or height are above the limit, save a downscaled copy as JPG"), + "export_for_4chan": OptionInfo(True, "Save copy of large images as JPG").info("if the file size is above the limit, or either width or height are above the limit"), "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), "target_side_length": OptionInfo(4000, "Width/height limit for the above option, in pixels", gr.Number), - "img_max_size_mp": OptionInfo(200, "Maximum image size, in megapixels", gr.Number), + "img_max_size_mp": OptionInfo(200, "Maximum image size", gr.Number).info("in megapixels"), "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), "use_upscaler_name_as_suffix": OptionInfo(False, "Use upscaler name as filename suffix in the extras tab"), @@ -314,23 +320,21 @@ def list_samplers(): })) options_templates.update(options_section(('upscaling', "Upscaling"), { - "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), - "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI. (Requires restart)", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), + "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), + "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), + "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), - "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), - "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), })) options_templates.update(options_section(('face-restoration', "Face restoration"), { "face_restoration_model": OptionInfo("CodeFormer", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), - "code_former_weight": OptionInfo(0.5, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "code_former_weight": OptionInfo(0.5, "CodeFormer weight", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}).info("0 = maximum effect; 1 = minimum effect"), "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), })) options_templates.update(options_section(('system', "System"), { "show_warnings": OptionInfo(False, "Show warnings in console."), - "memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}), + "memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}).info("0 = disable"), "samples_log_stdout": OptionInfo(False, "Always print all generation info to standard output"), "multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job."), "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), @@ -355,20 +359,20 @@ def list_samplers(): "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), - "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), + "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list).info("choose VAE model: Automatic = use one with same filename as checkpoint; None = use VAE from checkpoint"), "sd_vae_as_default": OptionInfo(True, "Ignore selected VAE for stable diffusion checkpoints that have their own .vae.pt next to them"), "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for img2img", gr.Slider, {"minimum": 0.5, "maximum": 1.5, "step": 0.01}), "img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."), - "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."), + "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies.").info("normally you'd do less with less denoising"), "img2img_background_color": OptionInfo("#ffffff", "With img2img, fill image's transparent parts with this color.", ui_components.FormColorPicker, {}), "enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."), - "enable_emphasis": OptionInfo(True, "Emphasis: use (text) to make model pay more attention to text and [text] to make it pay less attention"), + "enable_emphasis": OptionInfo(True, "Enable emphasis").info("use (text) to make model pay more attention to text and [text] to make it pay less attention"), "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"), - "comma_padding_backtrack": OptionInfo(20, "Increase coherency by padding from the last comma within n tokens when using more than 75 tokens", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1 }), - "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}), + "comma_padding_backtrack": OptionInfo(20, "Prompt word wrap length limit", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1}).info("in tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"), + "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP nrtwork; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), - "randn_source": OptionInfo("GPU", "Random number generator source. Changes seeds drastically. Use CPU to produce the same picture across different vidocard vendors.", gr.Radio, {"choices": ["GPU", "CPU"]}), + "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different vidocard vendors"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_hr": OptionInfo(0.0, "Togen merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), })) @@ -382,30 +386,32 @@ def list_samplers(): })) options_templates.update(options_section(('interrogate', "Interrogate Options"), { - "interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"), - "interrogate_return_ranks": OptionInfo(False, "Interrogate: include ranks of model tags matches in results (Has no effect on caption-based interrogators)."), - "interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), - "interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), - "interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), - "interrogate_clip_dict_limit": OptionInfo(1500, "CLIP: maximum number of lines in text file (0 = No limit)"), + "interrogate_keep_models_in_memory": OptionInfo(False, "Keep models in VRAM"), + "interrogate_return_ranks": OptionInfo(False, "Include ranks of model tags matches in results.").info("booru only"), + "interrogate_clip_num_beams": OptionInfo(1, "BLIP: num_beams", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}), + "interrogate_clip_min_length": OptionInfo(24, "BLIP: minimum description length", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), + "interrogate_clip_max_length": OptionInfo(48, "BLIP: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), + "interrogate_clip_dict_limit": OptionInfo(1500, "CLIP: maximum number of lines in text file").info("0 = No limit"), "interrogate_clip_skip_categories": OptionInfo([], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), - "interrogate_deepbooru_score_threshold": OptionInfo(0.5, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "deepbooru_sort_alpha": OptionInfo(True, "Interrogate: deepbooru sort alphabetically"), - "deepbooru_use_spaces": OptionInfo(False, "use spaces for tags in deepbooru"), - "deepbooru_escape": OptionInfo(True, "escape (\\) brackets in deepbooru (so they are used as literal brackets and not for emphasis)"), - "deepbooru_filter_tags": OptionInfo("", "filter out those tags from deepbooru output (separated by comma)"), + "interrogate_deepbooru_score_threshold": OptionInfo(0.5, "deepbooru: score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "deepbooru_sort_alpha": OptionInfo(True, "deepbooru: sort tags alphabetically").info("if not: sort by score"), + "deepbooru_use_spaces": OptionInfo(True, "deepbooru: use spaces in tags").info("if not: use underscores"), + "deepbooru_escape": OptionInfo(True, "deepbooru: escape (\\) brackets").info("so they are used as literal brackets and not for emphasis"), + "deepbooru_filter_tags": OptionInfo("", "deepbooru: filter out those tags").info("separate by comma"), })) options_templates.update(options_section(('extra_networks', "Extra Networks"), { "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}), "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (px)"), - "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (px)"), - "extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt"), + "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"), + "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), + "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *hypernetworks]}, refresh=reload_hypernetworks), })) options_templates.update(options_section(('ui', "User interface"), { + "localization": OptionInfo("None", "Localization", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)).needs_restart(), + "gradio_theme": OptionInfo("Default", "Gradio theme", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + gradio_hf_hub_themes}).needs_restart(), "return_grid": OptionInfo(True, "Show grid in results for web"), "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results for web"), "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results for web"), @@ -418,17 +424,15 @@ def list_samplers(): "js_modal_lightbox_gamepad": OptionInfo(True, "Navigate image viewer with gamepad"), "js_modal_lightbox_gamepad_repeat": OptionInfo(250, "Gamepad repeat period, in milliseconds"), "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), - "samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group"), - "dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row"), + "samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group").needs_restart(), + "dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row").needs_restart(), "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), - "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings"), - "hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}), + "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that appear at the top of page rather than in settings tab").needs_restart(), + "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}).needs_restart(), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), - "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"), - "localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)), - "gradio_theme": OptionInfo("Default", "Gradio theme (requires restart)", ui_components.DropdownEditable, lambda: {"choices": ["Default"] + gradio_hf_hub_themes}) + "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_restart(), })) options_templates.update(options_section(('infotext', "Infotext"), { @@ -443,26 +447,26 @@ def list_samplers(): "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "live_previews_format": OptionInfo("auto", "Live preview file format", gr.Radio, {"choices": ["auto", "jpeg", "png", "webp"]}), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), - "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), + "show_progress_every_n_steps": OptionInfo(10, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}).info("in sampling steps - show new live preview image every N sampling steps; -1 = only show after completion of batch"), + "show_progress_type": OptionInfo("Approx NN", "Live preview method", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}).info("Full = slow but pretty; Approx NN = fast but low quality; Approx cheap = super fast but terrible otherwise"), "live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), - "live_preview_refresh_period": OptionInfo(1000, "Progressbar/preview update period, in milliseconds") + "live_preview_refresh_period": OptionInfo(1000, "Progressbar and preview update period").info("in milliseconds"), })) options_templates.update(options_section(('sampler-params', "Sampler parameters"), { - "hide_samplers": OptionInfo([], "Hide samplers in user interface (requires restart)", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers()]}), - "eta_ddim": OptionInfo(0.0, "eta (noise multiplier) for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "eta_ancestral": OptionInfo(1.0, "eta (noise multiplier) for ancestral samplers", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "hide_samplers": OptionInfo([], "Hide samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers()]}).needs_restart(), + "eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}).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}).info("noise multiplier; applies to Euler a and other samplers that have a in them"), "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": 1.0, "step": 0.01}), 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}), - 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma"), + 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}).info("ENSD; does not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"), + 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma").link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/6044"), 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}), 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}), - 'uni_pc_order': OptionInfo(3, "UniPC order (must be < sampling steps)", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}), + 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}).info("must be < sampling steps"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final"), })) diff --git a/style.css b/style.css index 1e978592892..0c2f453c4e4 100644 --- a/style.css +++ b/style.css @@ -425,11 +425,11 @@ table.settings-value-table td{ color: var(--body-text-color); } -#settings .gradio-textbox, #settings .gradio-slider, #settings .gradio-number, #settings .gradio-dropdown, #settings .gradio-checkboxgroup{ +#settings .gradio-textbox, #settings .gradio-slider, #settings .gradio-number, #settings .gradio-dropdown, #settings .gradio-checkboxgroup, #settings .gradio-radio{ margin-top: 0.75em; } -.gradio-textbox .settings-comment, .gradio-slider .settings-comment, .gradio-number .settings-comment, .gradio-dropdown .settings-comment, .gradio-checkboxgroup .settings-comment { +#settings span .settings-comment { display: inline } From a58ae0b7174d9903fa426def2eda842dbbfcb53c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 11:15:15 +0300 Subject: [PATCH 0207/2418] remove auto live previews format option, fix slow PNG generation --- modules/progress.py | 19 +++++++++---------- modules/shared.py | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/modules/progress.py b/modules/progress.py index c2e37834f01..269863c942e 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -95,17 +95,16 @@ def progressapi(req: ProgressRequest): image = shared.state.current_image if image is not None: buffered = io.BytesIO() - format = opts.live_previews_format - save_kwargs = {} - if format == "auto": - if max(*image.size) > 256: - format = "jpeg" - else: - format = "png" - save_kwargs = {"optimize": True} - image.save(buffered, format=format, **save_kwargs) + + if opts.live_previews_image_format == "png": + # using optimize for large images takes an enormous amount of time + save_kwargs = {"optimize": max(*image.size) > 256} + else: + save_kwargs = {} + + image.save(buffered, format=opts.live_previews_image_format, **save_kwargs) base64_image = base64.b64encode(buffered.getvalue()).decode('ascii') - live_preview = f"data:image/{format};base64,{base64_image}" + live_preview = f"data:image/{opts.live_previews_image_format};base64,{base64_image}" id_live_preview = shared.state.id_live_preview else: live_preview = None diff --git a/modules/shared.py b/modules/shared.py index a057764437c..07f18b1b7cd 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -445,7 +445,7 @@ def list_samplers(): options_templates.update(options_section(('ui', "Live previews"), { "show_progressbar": OptionInfo(True, "Show progressbar"), "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), - "live_previews_format": OptionInfo("auto", "Live preview file format", gr.Radio, {"choices": ["auto", "jpeg", "png", "webp"]}), + "live_previews_image_format": OptionInfo("png", "Live preview file format", gr.Radio, {"choices": ["jpeg", "png", "webp"]}), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}).info("in sampling steps - show new live preview image every N sampling steps; -1 = only show after completion of batch"), "show_progress_type": OptionInfo("Approx NN", "Live preview method", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}).info("Full = slow but pretty; Approx NN = fast but low quality; Approx cheap = super fast but terrible otherwise"), From 1a43524018ea3e64b93be2abc2a49b6159515442 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 13:27:50 +0300 Subject: [PATCH 0208/2418] fix model loading twice in some situations --- modules/sd_hijack.py | 3 +++ modules/sd_models.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 7e50f1abcf6..14e7f79938f 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -216,6 +216,9 @@ def clear_comments(self): self.comments = [] def get_prompt_lengths(self, text): + if self.clip is None: + return "-", "-" + _, token_count = self.clip.process_texts([text]) return token_count, self.clip.get_target_prompt_token_count(token_count) diff --git a/modules/sd_models.py b/modules/sd_models.py index 4c9a0a1fb70..dddbc6e1c9d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -414,6 +414,9 @@ def __init__(self): def get_sd_model(self): if self.sd_model is None: with self.lock: + if self.sd_model is not None: + return self.sd_model + try: load_model() except Exception as e: From b9abdb50a3684e2f7392bf68736ad1ec025045af Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 13:31:03 +0300 Subject: [PATCH 0209/2418] add a possible fix for 'LatentDiffusion' object has no attribute 'lora_layer_mapping' --- CHANGELOG.md | 1 + extensions-builtin/Lora/lora.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7bf4c2feaa..881870bb9d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * allow bf16 in safe unpickler (resolves problems with loading some loras) * allow web UI to be ran fully offline * fix localizations not working + * fix error for loras: 'LatentDiffusion' object has no attribute 'lora_layer_mapping' ## 1.2.0 diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 6fa80006897..b5d0c98f97d 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -133,6 +133,10 @@ def load_lora(name, filename): sd = sd_models.read_state_dict(filename) + # this should not be needed but is here as an emergency fix for an unknown error people are experiencing in 1.2.0 + if not hasattr(shared.sd_model, 'lora_layer_mapping'): + assign_lora_names_to_compvis_modules(shared.sd_model) + keys_failed_to_match = {} is_sd2 = 'model_transformer_resblocks' in shared.sd_model.lora_layer_mapping From dbd13dee3aa7c8e37aa43f30a8272b50ba61e7fe Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Sun, 14 May 2023 13:34:50 +0300 Subject: [PATCH 0210/2418] update readme for release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881870bb9d4..8cf444ca730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming 1.2.1 +## 1.2.1 ### Features: * add an option to always refer to lora by filenames From efe81620a09186259731af9d2d28fd87c574da97 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 22:17:36 +0800 Subject: [PATCH 0211/2418] Add GPU device Add GPU option to troubleshoot. --- .github/ISSUE_TEMPLATE/bug_report.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7d435297a69..a24e62d4225 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -59,6 +59,18 @@ body: - iOS - Android - Other/Cloud + - type: dropdown + id: device + attributes: + label: What device are you running WebUI on? + multiple: true + options: + - Nvidia GPUs (RTX 20 above) + - Nvidia GPUs (GTX 16 below) + - AMD GPUs (RX 6000 above) + - AMD GPUs (RX 5000 below) + - CPU + - Other GPUs - type: dropdown id: browsers attributes: From ef046fae39868d25be9bc1a6b7863bcd22e550c2 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 22:26:43 +0800 Subject: [PATCH 0212/2418] Downgrade Gradio --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index 7bce02e5f10..cfdce216073 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.18.0 basicsr==1.4.2 gfpgan==1.3.8 -gradio==3.29.0 +gradio==3.28.1 numpy==1.23.5 Pillow==9.4.0 realesrgan==0.3.0 From f29c41bf6dd04a721e1dc0a162b042cad269f247 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 22:29:28 +0800 Subject: [PATCH 0213/2418] Modify pytorch command --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index cfc0cffa26a..b00420d06d9 100644 --- a/launch.py +++ b/launch.py @@ -237,7 +237,7 @@ def run_extensions_installers(settings_file): def prepare_environment(): - torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url https://download.pytorch.org/whl/cu118") + torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118") requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') From b023940032b38363801f0613d67226ecddaecee4 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Sun, 14 May 2023 22:39:38 +0800 Subject: [PATCH 0214/2418] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a24e62d4225..e0ca454d371 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,17 +60,17 @@ body: - Android - Other/Cloud - type: dropdown - id: device - attributes: - label: What device are you running WebUI on? - multiple: true - options: - - Nvidia GPUs (RTX 20 above) - - Nvidia GPUs (GTX 16 below) - - AMD GPUs (RX 6000 above) - - AMD GPUs (RX 5000 below) - - CPU - - Other GPUs + id: device + attributes: + label: What device are you running WebUI on? + multiple: true + options: + - Nvidia GPUs (RTX 20 above) + - Nvidia GPUs (GTX 16 below) + - AMD GPUs (RX 6000 above) + - AMD GPUs (RX 5000 below) + - CPU + - Other GPUs - type: dropdown id: browsers attributes: From a98ae89bde8033cdf7cff5726eddc5649df20c3c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 15 May 2023 00:31:34 +0900 Subject: [PATCH 0215/2418] fix xyz checkpoint --- scripts/xyz_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index a725d74a3b3..db768fd2aa6 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -86,7 +86,7 @@ def apply_checkpoint(p, x, xs): info = modules.sd_models.get_closet_checkpoint_match(x) if info is None: raise RuntimeError(f"Unknown checkpoint: {x}") - p.override_settings['sd_model_checkpoint'] = info.hash + p.override_settings['sd_model_checkpoint'] = info.name def confirm_checkpoints(p, xs): From d9968e61082fb5ed16eedfe8fa43bf6f2e7b76e7 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 14 May 2023 20:30:02 +0300 Subject: [PATCH 0216/2418] launch.py: Don't involve shell for running Python or Git for output Fixes Linux regression in 451d255b5859580c4adf99d67760330d58d76446 --- launch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launch.py b/launch.py index 578af22906c..f83809116c8 100644 --- a/launch.py +++ b/launch.py @@ -58,7 +58,7 @@ def check_python_version(): @lru_cache() def commit_hash(): try: - return subprocess.check_output(f"{git} rev-parse HEAD", encoding='utf8').strip() + return subprocess.check_output([git, "rev-parse", "HEAD"], shell=False, encoding='utf8').strip() except Exception: return "" @@ -66,7 +66,7 @@ def commit_hash(): @lru_cache() def git_tag(): try: - return subprocess.check_output(f"{git} describe --tags", encoding='utf8').strip() + return subprocess.check_output([git, "describe", "--tags"], shell=False, encoding='utf8').strip() except Exception: return "" @@ -125,7 +125,7 @@ def run_pip(command, desc=None, live=default_command_live): def check_run_python(code: str) -> bool: - result = subprocess.run([python, "-c", code], capture_output=True, shell=True) + result = subprocess.run([python, "-c", code], capture_output=True, shell=False) return result.returncode == 0 From 38583be7af64d984c871fd7d54f5fd0a64a3e6d1 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Mon, 15 May 2023 02:37:43 +0800 Subject: [PATCH 0217/2418] Revert Gradio version --- requirements_versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index cfdce216073..7bce02e5f10 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -3,7 +3,7 @@ transformers==4.25.1 accelerate==0.18.0 basicsr==1.4.2 gfpgan==1.3.8 -gradio==3.28.1 +gradio==3.29.0 numpy==1.23.5 Pillow==9.4.0 realesrgan==0.3.0 From 9a9557ecfc824c4a005dc79466dba8f5884dbfb1 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Mon, 15 May 2023 03:00:23 +0800 Subject: [PATCH 0218/2418] Change to extra-index-url --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index b00420d06d9..034de0a9025 100644 --- a/launch.py +++ b/launch.py @@ -237,7 +237,7 @@ def run_extensions_installers(settings_file): def prepare_environment(): - torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118") + torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --extra-index-url https://download.pytorch.org/whl/cu118") requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt") xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.17') From 742da3193290f5692901c4c614c98bec291163f2 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Mon, 15 May 2023 03:04:34 +0800 Subject: [PATCH 0219/2418] Minor changes --- modules/sd_vae_taesd.py | 2 +- webui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index ccc979593ba..927a72987fe 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -71,6 +71,6 @@ def decode(): sd_vae_taesd.eval() sd_vae_taesd.to(devices.device, devices.dtype) else: - raise FileNotFoundError('Tiny AE mdoel not found') + raise FileNotFoundError('Tiny AE model not found') return sd_vae_taesd.decoder diff --git a/webui.py b/webui.py index 0a928434877..0d0816bcc86 100644 --- a/webui.py +++ b/webui.py @@ -151,7 +151,7 @@ def check_taesd(): model_path = os.path.join(models_path, "VAE-approx", "taesd_decoder.pth") if not os.path.exists(model_path): print('download taesd model') - torch.hub.download_url_to_file(model_url, os.path.dirname(model_path)) + torch.hub.download_url_to_file(model_url, model_path) def initialize(): From f517838c75014f981ae1c41f1bc776d74daf9a23 Mon Sep 17 00:00:00 2001 From: Keith <1868690+wk5ovc@users.noreply.github.com> Date: Mon, 15 May 2023 10:47:01 +0800 Subject: [PATCH 0220/2418] Fix extra networks save preview image geninfo --- modules/ui_extra_networks.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 8c3dea5643e..9e6e05313f4 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -334,9 +334,19 @@ def save_preview(index, images, filename): assert is_allowed, f'writing to {filename} is not allowed' if geninfo: - pnginfo_data = PngImagePlugin.PngInfo() - pnginfo_data.add_text('parameters', geninfo) - image.save(filename, pnginfo=pnginfo_data) + ext = os.path.splitext(filename)[1].lower() + if ext == '.png': + pnginfo_data = PngImagePlugin.PngInfo() + pnginfo_data.add_text('parameters', geninfo) + image.save(filename, pnginfo=pnginfo_data) + elif ext in ('.jpg', '.jpeg', '.webp'): + exif_bytes = piexif.dump({ + 'Exif': {piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or '', + encoding='unicode')} + }) + image.save(filename, exif=exif_bytes, quality=shared.opts.jpeg_quality) + else: + image.save(filename) else: image.save(filename) From 32af211f4c6e58ccff000bb99bfe7e69c9b82ca1 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Mon, 15 May 2023 15:42:37 +0800 Subject: [PATCH 0221/2418] Add Python version Many users still use unverified versions of Python and file version-specific issues, often without mentioning version information, making troubleshooting difficult. --- .github/ISSUE_TEMPLATE/bug_report.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7d435297a69..863aae741c0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -47,6 +47,15 @@ body: description: Which commit are you running ? (Do not write *Latest version/repo/commit*, as this means nothing and will have changed by the time we read your issue. Rather, copy the **Commit** link at the bottom of the UI, or from the cmd/terminal if you can't launch it.) validations: required: true + - type: dropdown + id: py-version + attributes: + label: What Python version are you running on ? + multiple: false + options: + - Python 3.10.x + - Python 3.11.x (above, no supported yet) + - Python 3.9.x (below, no recommended) - type: dropdown id: platforms attributes: From 9e9090753255746cec7b3cb522a2f8c12b38728a Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 May 2023 02:02:51 +0900 Subject: [PATCH 0222/2418] xyz token merging --- scripts/xyz_grid.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 5672267d58f..da820b394d4 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -144,6 +144,11 @@ def apply_face_restore(p, opt, x): p.restore_faces = is_active +def apply_override(field): + def fun(p, x, xs): + p.override_settings[field] = x + return fun + def format_value_add_label(p, opt, x): if type(x) == float: x = round(x, 8) @@ -224,6 +229,8 @@ def __init__(self, *args, **kwargs): AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)), AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5), AxisOption("Face restore", str, apply_face_restore, format_value=format_value), + AxisOption("Token merging ratio", float, apply_override('token_merging_ratio')), + AxisOption("Token merging ratio high-res", float, apply_override('token_merging_ratio_hr')), ] From 0d3a80e2692fb72da9798367b882f45312202f2e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 15 May 2023 20:33:44 +0300 Subject: [PATCH 0223/2418] Show "Loading..." for extra networks when displaying for the first time --- modules/ui_extra_networks.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 0baccf566ad..752cf2b8305 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -268,7 +268,7 @@ def create_ui(container, button, tabname): with gr.Tab(page.title, id=page_id): elem_id = f"{tabname}_{page_id}_cards_html" - page_elem = gr.HTML('', elem_id=elem_id) + page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + json.dumps(tabname) + '); return []}', inputs=[], outputs=[]) @@ -282,13 +282,24 @@ def create_ui(container, button, tabname): def toggle_visibility(is_visible): is_visible = not is_visible - if is_visible and not ui.pages_contents: + return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) + + def fill_tabs(is_empty): + """Creates HTML for extra networks' tabs when the extra networks button is clicked for the first time.""" + + if not ui.pages_contents: refresh() - return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")), *ui.pages_contents + if is_empty: + return True, *ui.pages_contents + + return True, *[gr.update() for _ in ui.pages_contents] state_visible = gr.State(value=False) - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button, *ui.pages]) + button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button], show_progress=False) + + state_empty = gr.State(value=True) + button.click(fn=fill_tabs, inputs=[state_empty], outputs=[state_empty, *ui.pages], show_progress=False) def refresh(): for pg in ui.stored_extra_pages: From 0d2a4b608c075daa3a4d1a1c9df01a763ae4793a Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 15 May 2023 20:57:11 +0300 Subject: [PATCH 0224/2418] load extensions' git metadata in parallel to loading the main program to save a ton of time during startup --- modules/config_states.py | 2 ++ modules/extensions.py | 12 +++++++++++- modules/ui_extensions.py | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/modules/config_states.py b/modules/config_states.py index 75da862ab99..db65bcdbf7a 100644 --- a/modules/config_states.py +++ b/modules/config_states.py @@ -83,6 +83,8 @@ def get_extension_config(): ext_config = {} for ext in extensions.extensions: + ext.read_info_from_repo() + entry = { "name": ext.name, "path": ext.path, diff --git a/modules/extensions.py b/modules/extensions.py index bc2c0450f7f..1053253ed4f 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -1,5 +1,6 @@ import os import sys +import threading import traceback import time @@ -24,6 +25,8 @@ def active(): class Extension: + lock = threading.Lock() + def __init__(self, name, path, enabled=True, is_builtin=False): self.name = name self.path = path @@ -42,8 +45,13 @@ def read_info_from_repo(self): if self.is_builtin or self.have_info_from_repo: return - self.have_info_from_repo = True + with self.lock: + if self.have_info_from_repo: + return + self.do_read_info_from_repo() + + def do_read_info_from_repo(self): repo = None try: if os.path.exists(os.path.join(self.path, ".git")): @@ -70,6 +78,8 @@ def read_info_from_repo(self): print(f"Failed reading extension data from Git repository ({self.name}): {ex}", file=sys.stderr) self.remote = None + self.have_info_from_repo = True + def list_files(self, subdir, extension): from modules import scripts diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index af49773326f..aaa7e571a6a 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -1,6 +1,7 @@ import json import os.path import sys +import threading import time from datetime import datetime import traceback @@ -484,11 +485,18 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" return code, list(tags) +def preload_extensions_git_metadata(): + for extension in extensions.extensions: + extension.read_info_from_repo() + + def create_ui(): import modules.ui config_states.list_config_states() + threading.Thread(target=preload_extensions_git_metadata).start() + with gr.Blocks(analytics_enabled=False) as ui: with gr.Tabs(elem_id="tabs_extensions"): with gr.TabItem("Installed", id="installed"): @@ -508,7 +516,8 @@ def create_ui(): """ info = gr.HTML(html) - extensions_table = gr.HTML(lambda: extension_table()) + extensions_table = gr.HTML('Loading...') + ui.load(fn=extension_table, inputs=[], outputs=[extensions_table]) apply.click( fn=apply_and_restart, @@ -595,7 +604,8 @@ def create_ui(): config_save_button = gr.Button(value="Save Current Config") config_states_info = gr.HTML("") - config_states_table = gr.HTML(lambda: update_config_states_table("Current")) + config_states_table = gr.HTML("Loading...") + ui.load(fn=update_config_states_table, inputs=[config_states_list], outputs=[config_states_table]) config_save_button.click(fn=save_config_state, inputs=[config_save_name], outputs=[config_states_list, config_states_info]) @@ -608,4 +618,5 @@ def create_ui(): outputs=[config_states_table], ) + return ui From a47abe1b7b667374e9df1932172230132d3fe8db Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Mon, 15 May 2023 21:22:35 +0300 Subject: [PATCH 0225/2418] update extensions table: show branch, show date in separate column, and show version from tags if available --- modules/extensions.py | 6 ++---- modules/ui_extensions.py | 7 ++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index 1053253ed4f..f16f059ec11 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -66,13 +66,11 @@ def do_read_info_from_repo(self): try: self.status = 'unknown' self.remote = next(repo.remote().urls, None) - head = repo.head.commit self.commit_date = repo.head.commit.committed_date - ts = time.asctime(time.gmtime(self.commit_date)) if repo.active_branch: self.branch = repo.active_branch.name - self.commit_hash = head.hexsha - self.version = f'{self.commit_hash[:8]} ({ts})' + self.commit_hash = repo.head.commit.hexsha + self.version = repo.git.describe("--always", "--tags") # compared to `self.commit_hash[:8]` this takes about 30% more time total but since we run it in parallel we don't care except Exception as ex: print(f"Failed reading extension data from Git repository ({self.name}): {ex}", file=sys.stderr) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index aaa7e571a6a..6ad9a97c57b 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -141,7 +141,9 @@ def extension_table():
    - + + + @@ -149,6 +151,7 @@ def extension_table(): """ for ext in extensions.extensions: + ext: extensions.Extension ext.read_info_from_repo() remote = f"""{html.escape("built-in" if ext.is_builtin else ext.remote or '')}""" @@ -170,7 +173,9 @@ def extension_table(): + + {ext_status} """ From 3d76eabbca3adb711787d1802d6b61c0971b4bc0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 16 May 2023 07:59:43 +0300 Subject: [PATCH 0226/2418] add visual progress for extension installation from URL --- modules/extensions.py | 1 - modules/ui_extensions.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/extensions.py b/modules/extensions.py index f16f059ec11..359a7aa5134 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -3,7 +3,6 @@ import threading import traceback -import time import git from modules import shared diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 6ad9a97c57b..d7a0f68563e 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -593,9 +593,9 @@ def create_ui(): install_result = gr.HTML(elem_id="extension_install_result") install_button.click( - fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]), + fn=modules.ui.wrap_gradio_call(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), inputs=[install_dirname, install_url, install_branch], - outputs=[extensions_table, install_result], + outputs=[install_url, extensions_table, install_result], ) with gr.TabItem("Backup/Restore"): From cdac5ace1456ba779d5a0171ff8757f31955bfee Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 16 May 2023 11:54:02 +0300 Subject: [PATCH 0227/2418] suppress ENSD infotext for samplers that don't use it --- modules/processing.py | 11 +++++++---- modules/sd_samplers.py | 8 +++++++- modules/sd_samplers_common.py | 21 ++++++++++++++++++++- modules/sd_samplers_compvis.py | 8 ++++++-- modules/sd_samplers_kdiffusion.py | 16 ++++++++-------- 5 files changed, 48 insertions(+), 16 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 94fe2625cd7..15806f78d0c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -13,7 +13,7 @@ from typing import Any, Dict, List import modules.sd_hijack -from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, extra_networks, sd_vae_approx, scripts +from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, extra_networks, sd_vae_approx, scripts, sd_samplers_common from modules.sd_hijack import model_hijack from modules.shared import opts, cmd_opts, state import modules.shared as shared @@ -480,6 +480,10 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter clip_skip = getattr(p, 'clip_skip', opts.CLIP_stop_at_last_layers) enable_hr = getattr(p, 'enable_hr', False) + uses_ensd = opts.eta_noise_seed_delta != 0 + if uses_ensd: + uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) + generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, @@ -496,17 +500,16 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Denoising strength": getattr(p, 'denoising_strength', None), "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, - "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta, + "ENSD": opts.eta_noise_seed_delta if uses_ensd else None, "Token merging ratio": None if opts.token_merging_ratio == 0 else opts.token_merging_ratio, "Token merging ratio hr": None if not enable_hr or opts.token_merging_ratio_hr == 0 else opts.token_merging_ratio_hr, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, + **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, } - generation_params.update(p.extra_generation_params) - generation_params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in generation_params.items() if v is not None]) negative_prompt_text = f"\nNegative prompt: {p.all_negative_prompts[index]}" if p.all_negative_prompts[index] else "" diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 4f1bf21d89a..f22aad8f216 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -14,12 +14,18 @@ samplers_map = {} -def create_sampler(name, model): +def find_sampler_config(name): if name is not None: config = all_samplers_map.get(name, None) else: config = all_samplers[0] + return config + + +def create_sampler(name, model): + config = find_sampler_config(name) + assert config is not None, f'bad sampler name: {name}' sampler = config.constructor(model) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index bc074238141..92880cafdfa 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -2,7 +2,7 @@ import numpy as np import torch from PIL import Image -from modules import devices, processing, images, sd_vae_approx +from modules import devices, processing, images, sd_vae_approx, sd_samplers from modules.shared import opts, state import modules.shared as shared @@ -58,6 +58,25 @@ def store_latent(decoded): shared.state.assign_current_image(sample_to_image(decoded)) +def is_sampler_using_eta_noise_seed_delta(p): + """returns whether sampler from config will use eta noise seed delta for image creation""" + + sampler_config = sd_samplers.find_sampler_config(p.sampler_name) + + eta = p.eta + + if eta is None and p.sampler is not None: + eta = p.sampler.eta + + if eta is None and sampler_config is not None: + eta = 0 if sampler_config.options.get("default_eta_is_0", False) else 1.0 + + if eta == 0: + return False + + return sampler_config.options.get("uses_ensd", False) + + class InterruptedException(BaseException): pass diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index b1ee3be75e9..bdae8b404b5 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -11,7 +11,7 @@ samplers_data_compvis = [ - sd_samplers_common.SamplerData('DDIM', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.ddim.DDIMSampler, model), [], {}), + sd_samplers_common.SamplerData('DDIM', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.ddim.DDIMSampler, model), [], {"default_eta_is_0": True, "uses_ensd": True}), sd_samplers_common.SamplerData('PLMS', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.plms.PLMSSampler, model), [], {}), sd_samplers_common.SamplerData('UniPC', lambda model: VanillaStableDiffusionSampler(modules.models.diffusion.uni_pc.UniPCSampler, model), [], {}), ] @@ -134,7 +134,11 @@ def unipc_after_update(self, x, model_x): self.update_step(x) def initialize(self, p): - self.eta = p.eta if p.eta is not None else shared.opts.eta_ddim + if self.is_ddim: + self.eta = p.eta if p.eta is not None else shared.opts.eta_ddim + else: + self.eta = 0.0 + if self.eta != 0.0: p.extra_generation_params["Eta DDIM"] = self.eta diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 61f23ad7fe3..5455561a00b 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -11,21 +11,21 @@ from modules.script_callbacks import AfterCFGCallbackParams, cfg_after_cfg_callback samplers_k_diffusion = [ - ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {}), + ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), ('Heun', 'sample_heun', ['k_heun'], {}), ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'discard_next_to_last_sigma': True}), - ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'discard_next_to_last_sigma': True}), - ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {}), + ('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}), ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {}), - ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {}), - ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {}), + ('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'}), - ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True}), - ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True}), - ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras'}), + ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True}), + ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True}), + ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras', "uses_ensd": True}), ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras'}), ] From a61cbef02c7652f96d333b28e01f5230e225224e Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 16 May 2023 12:36:15 +0300 Subject: [PATCH 0228/2418] add second_order field to sampler config --- modules/processing.py | 8 ++------ modules/sd_samplers_kdiffusion.py | 14 +++++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 15806f78d0c..678c4468f4b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -681,12 +681,8 @@ def get_conds_with_caching(function, required_prompts, steps, cache): processed = Processed(p, [], p.seed, "") file.write(processed.infotext(p, 0)) - step_multiplier = 1 - if not shared.opts.dont_fix_second_order_samplers_schedule: - try: - step_multiplier = 2 if sd_samplers.all_samplers_map.get(p.sampler_name).aliases[0] in ['k_dpmpp_2s_a', 'k_dpmpp_2s_a_ka', 'k_dpmpp_sde', 'k_dpmpp_sde_ka', 'k_dpm_2', 'k_dpm_2_a', 'k_heun'] else 1 - except Exception: - pass + sampler_config = sd_samplers.find_sampler_config(p.sampler_name) + step_multiplier = 2 if sampler_config and sampler_config.options.get("second_order", False) else 1 uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps * step_multiplier, cached_uc) c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps * step_multiplier, cached_c) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 5455561a00b..552c6c64f0a 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -14,20 +14,20 @@ ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), - ('Heun', 'sample_heun', ['k_heun'], {}), + ('Heun', 'sample_heun', ['k_heun'], {"second_order": True}), ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'discard_next_to_last_sigma': True}), ('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}), + ('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++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {}), + ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": 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'}), - ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True}), - ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True}), - ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras', "uses_ensd": True}), + ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), + ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), + ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras', "uses_ensd": True, "second_order": True}), ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), - ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras'}), + ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True}), ] samplers_data_k_diffusion = [ From 6302978ff8e51ad0917c62806ca127b514088a70 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 16 May 2023 15:14:44 +0300 Subject: [PATCH 0229/2418] restore nqsp in footer that was lost during linting --- modules/ui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index ff25c4ce987..8e51e7823d0 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1841,15 +1841,15 @@ def versions_html(): return f""" version: {tag} - • + •  python: {python_version} - • + •  torch: {getattr(torch, '__long_version__',torch.__version__)} - • + •  xformers: {xformers_version} - • + •  gradio: {gr.__version__} - • + •  checkpoint: N/A """ From ce38ee8f26d0b84888c72b58cdd9682ac3fd6151 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 16 May 2023 15:41:49 +0300 Subject: [PATCH 0230/2418] add info link for Negative Guidance minimum sigma --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 07f18b1b7cd..3abf71c0053 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -458,8 +458,8 @@ def list_samplers(): "eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}).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}).info("noise multiplier; applies to Euler a and other samplers that have a in them"), "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform', 'quad']}), + 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}).info("ENSD; does not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"), From 4fb2cc0f060d1f63e0e62e38d37e983745ce3fda Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Wed, 17 May 2023 00:32:32 +0800 Subject: [PATCH 0231/2418] Minor change --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 0d0816bcc86..0aa03ea8f39 100644 --- a/webui.py +++ b/webui.py @@ -150,7 +150,7 @@ def check_taesd(): model_url = 'https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth' model_path = os.path.join(models_path, "VAE-approx", "taesd_decoder.pth") if not os.path.exists(model_path): - print('download taesd model') + print('From taesd repo download decoder model') torch.hub.download_url_to_file(model_url, model_path) From bbce167305091b34795284cabca7ab2fd56469b6 Mon Sep 17 00:00:00 2001 From: lenankamp <31517075+lenankamp@users.noreply.github.com> Date: Tue, 16 May 2023 14:37:45 -0400 Subject: [PATCH 0232/2418] Recursive batch img2img.py Searches sub directories and performs img2img batch processing, also limits inputs to jpg, webp, and png. Then saves to putput directory with relative paths. --- modules/img2img.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 9fc3a698e88..ad5f2e731c5 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -20,7 +20,13 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): processing.fix_seed(p) - images = shared.listfiles(input_dir) +# recursive batch, as written limits potential inputs to common image formats, may e better to just check if isfile for general use +images = [] + for root, directories, files in os.walk(input_dir): + for filename in files: + filepath = os.path.join(root, filename) + if filepath.endswith(".jpg") or filepath.endswith(".jpeg") or filepath.endswith(".png") or filepath.endswith(".webp"): + images.append(filepath) is_inpaint_batch = False if inpaint_mask_dir: @@ -70,16 +76,17 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args): for n, processed_image in enumerate(proc.images): filename = os.path.basename(image) + relpath = os.path.dirname(os.path.relpath(image, input_dir)) if n > 0: left, right = os.path.splitext(filename) filename = f"{left}-{n}{right}" if not save_normally: - os.makedirs(output_dir, exist_ok=True) + os.makedirs(os.path.join(output_dir, relpath), exist_ok=True) if processed_image.mode == 'RGBA': processed_image = processed_image.convert("RGB") - processed_image.save(os.path.join(output_dir, filename)) + processed_image.save(os.path.join(output_dir, relpath, filename)) 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_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, 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, *args): From 0d31f20cbd556ea4ba3d8ad9254bcce71c32088c Mon Sep 17 00:00:00 2001 From: bobzilladev Date: Tue, 16 May 2023 13:15:30 -0400 Subject: [PATCH 0233/2418] Use ngrok-py library --- launch.py | 4 ++-- modules/cmd_args.py | 3 ++- modules/ngrok.py | 37 ++++++++++++++----------------------- modules/ui.py | 2 +- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/launch.py b/launch.py index cfc0cffa26a..871d3ea8ec2 100644 --- a/launch.py +++ b/launch.py @@ -294,8 +294,8 @@ def prepare_environment(): elif platform.system() == "Linux": run_pip(f"install {xformers_package}", "xformers") - if not is_installed("pyngrok") and args.ngrok: - run_pip("install pyngrok", "ngrok") + if not is_installed("ngrok") and args.ngrok: + run_pip("install ngrok", "ngrok") os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index d906a5717ee..bf18b7b7eaf 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -1,4 +1,5 @@ import argparse +import json import os from modules.paths_internal import models_path, script_path, data_path, extensions_dir, extensions_builtin_dir, sd_default_config, sd_model_file @@ -39,7 +40,7 @@ parser.add_argument("--upcast-sampling", action='store_true', help="upcast sampling. No effect with --no-half. Usually produces similar results to --no-half with better performance while using less memory.") parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site") parser.add_argument("--ngrok", type=str, help="ngrok authtoken, alternative to gradio --share", default=None) -parser.add_argument("--ngrok-region", type=str, help="The region in which ngrok should start.", default="us") +parser.add_argument("--ngrok-options", type=json.loads, help='The options to pass to ngrok in JSON format, e.g.: \'{"authtoken_from_env":true, "basic_auth":"user:password", "oauth_provider":"google", "oauth_allow_emails":"user@asdf.com"}\'', default=dict()) parser.add_argument("--enable-insecure-extension-access", action='store_true', help="enable extensions tab regardless of other options") parser.add_argument("--codeformer-models-path", type=str, help="Path to directory with codeformer model file(s).", default=os.path.join(models_path, 'Codeformer')) parser.add_argument("--gfpgan-models-path", type=str, help="Path to directory with GFPGAN model file(s).", default=os.path.join(models_path, 'GFPGAN')) diff --git a/modules/ngrok.py b/modules/ngrok.py index 7a7b4b26c9d..caa352d1a16 100644 --- a/modules/ngrok.py +++ b/modules/ngrok.py @@ -1,6 +1,7 @@ -from pyngrok import ngrok, conf, exception +import ngrok -def connect(token, port, region): +# Connect to ngrok for ingress +def connect(token, port, options): account = None if token is None: token = 'None' @@ -10,28 +11,18 @@ def connect(token, port, region): token, username, password = token.split(':', 2) account = f"{username}:{password}" - config = conf.PyngrokConfig( - auth_token=token, region=region - ) - - # Guard for existing tunnels - existing = ngrok.get_tunnels(pyngrok_config=config) - if existing: - for established in existing: - # Extra configuration in the case that the user is also using ngrok for other tunnels - if established.config['addr'][-4:] == str(port): - public_url = existing[0].public_url - print(f'ngrok has already been connected to localhost:{port}! URL: {public_url}\n' - 'You can use this link after the launch is complete.') - return - + # For all options see: https://github.com/ngrok/ngrok-py/blob/main/examples/ngrok-connect-full.py + if not options.get('authtoken_from_env'): + options['authtoken'] = token + if account: + options['basic_auth'] = account + if not options.get('session_metadata'): + options['session_metadata'] = 'stable-diffusion-webui' + try: - if account is None: - public_url = ngrok.connect(port, pyngrok_config=config, bind_tls=True).public_url - else: - public_url = ngrok.connect(port, pyngrok_config=config, bind_tls=True, auth=account).public_url - except exception.PyngrokNgrokError: - print(f'Invalid ngrok authtoken, ngrok connection aborted.\n' + public_url = ngrok.connect(f"127.0.0.1:{port}", **options).url() + except Exception as e: + print(f'Invalid ngrok authtoken? ngrok connection aborted due to: {e}\n' f'Your token: {token}, get the right one on https://dashboard.ngrok.com/get-started/your-authtoken') else: print(f'ngrok connected to localhost:{port}! URL: {public_url}\n' diff --git a/modules/ui.py b/modules/ui.py index f07bcc41e43..5f5405f06f2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -59,7 +59,7 @@ ngrok.connect( cmd_opts.ngrok, cmd_opts.port if cmd_opts.port is not None else 7860, - cmd_opts.ngrok_region + cmd_opts.ngrok_options ) From a4d5fdd3c245b2998b084e3172ccc9786980e771 Mon Sep 17 00:00:00 2001 From: grimatoma Date: Tue, 16 May 2023 13:32:32 -0700 Subject: [PATCH 0234/2418] Remove max width for model dropdown Removing the max width for the model dropdown allows the user to see the full name of a model especially when it is long. Model names are getting more complex and longer and the current width almost always cuts off model names. If a user leverages folders than it pretty much always cuts off the name... --- style.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/style.css b/style.css index 0c2f453c4e4..f8ffbd8d341 100644 --- a/style.css +++ b/style.css @@ -363,12 +363,10 @@ div#extras_scale_to_tab div.form{ /* settings */ #quicksettings { - width: fit-content; align-items: end; } #quicksettings > div, #quicksettings > fieldset{ - max-width: 24em; min-width: 24em; padding: 0; border: none; From e378590d33ae7a659601b260e2b98a1b51b6f656 Mon Sep 17 00:00:00 2001 From: Weiming Date: Wed, 17 May 2023 10:20:11 +0800 Subject: [PATCH 0235/2418] Fix remove `textual inversion` prompt --- javascript/extraNetworks.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index c85bc79aafe..4d9a522cd9d 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -68,18 +68,27 @@ var re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; function tryToRemoveExtraNetworkFromPrompt(textarea, text){ var m = text.match(re_extranet) - if(! m) return false - - var partToSearch = m[1] var replaced = false - var newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found){ - m = found.match(re_extranet); - if(m[1] == partToSearch){ - replaced = true; - return "" - } - return found; - }) + var newTextareaText + if(m) { + var partToSearch = m[1] + newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found){ + m = found.match(re_extranet); + if(m[1] == partToSearch){ + replaced = true; + return "" + } + return found; + }) + } else { + newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found){ + if(found == text) { + replaced = true; + return "" + } + return found; + }) + } if(replaced){ textarea.value = newTextareaText From 54f657ffbc3c2e297d1d81c0e2026a68ccfbd602 Mon Sep 17 00:00:00 2001 From: dennissheng Date: Wed, 17 May 2023 10:47:02 +0800 Subject: [PATCH 0236/2418] not clear checkpoints cache when config changes --- modules/sd_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 36f643e100d..e37fcb8fe9d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -538,7 +538,6 @@ def reload_model_weights(sd_model=None, info=None): if sd_model is None or checkpoint_config != sd_model.used_config: del sd_model - checkpoints_loaded.clear() load_model(checkpoint_info, already_loaded_state_dict=state_dict) return model_data.sd_model From b217ebc49000b41baab3094dbc8caaf33eaf5579 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 08:41:21 +0300 Subject: [PATCH 0237/2418] add credits --- README.md | 1 + html/licenses.html | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.md b/README.md index 67a1a83a31d..c1e193c03f2 100644 --- a/README.md +++ b/README.md @@ -158,5 +158,6 @@ Licenses for borrowed code can be found in `Settings -> Licenses` screen, and al - Instruct pix2pix - Tim Brooks (star), Aleksander Holynski (star), Alexei A. Efros (no star) - https://github.com/timothybrooks/instruct-pix2pix - Security advice - RyotaK - UniPC sampler - Wenliang Zhao - https://github.com/wl-zhao/UniPC +- TAESD - Ollin Boer Bohan - https://github.com/madebyollin/taesd - Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user. - (You) diff --git a/html/licenses.html b/html/licenses.html index bc995aa074b..ef6f2c0a42b 100644 --- a/html/licenses.html +++ b/html/licenses.html @@ -661,4 +661,30 @@

    TAESD

    +Tiny AutoEncoder for Stable Diffusion option for live previews +
    +MIT License
    +
    +Copyright (c) 2023 Ollin Boer Bohan
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
     
    \ No newline at end of file From 56a2672831751480f94a018f861f0143a8234ae8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 09:24:01 +0300 Subject: [PATCH 0238/2418] return live preview defaults to how they were only download TAESD model when it's needed return calculations in single_sample_to_image to just if/elif/elif blocks keep taesd model in its own directory --- modules/sd_samplers_common.py | 29 +++++++++++++++-------------- modules/sd_vae_taesd.py | 18 +++++++++++++++--- modules/shared.py | 2 +- webui.py | 11 ----------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index b1e8a780602..20a9af206e6 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -22,28 +22,29 @@ def setup_img2img_steps(p, steps=None): return steps, t_enc -approximation_indexes = {"Full": 0, "Tiny AE": 1, "Approx NN": 2, "Approx cheap": 3} +approximation_indexes = {"Full": 0, "Approx NN": 1, "Approx cheap": 2, "TAESD": 3} def single_sample_to_image(sample, approximation=None): - if approximation is None or approximation not in approximation_indexes.keys(): - approximation = approximation_indexes.get(opts.show_progress_type, 1) - if approximation == 1: - x_sample = sd_vae_taesd.decode()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() - x_sample = sd_vae_taesd.TAESD.unscale_latents(x_sample) - x_sample = torch.clamp((x_sample * 0.25) + 0.5, 0, 1) + if approximation is None: + approximation = approximation_indexes.get(opts.show_progress_type, 0) + + if approximation == 2: + x_sample = sd_vae_approx.cheap_approximation(sample) + elif approximation == 1: + x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + elif approximation == 3: + x_sample = sd_vae_taesd.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + x_sample = sd_vae_taesd.TAESD.unscale_latents(x_sample) # returns value in [-2, 2] + x_sample = x_sample * 0.5 else: - if approximation == 3: - x_sample = sd_vae_approx.cheap_approximation(sample) - elif approximation == 2: - x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() - else: - x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] - x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) + x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] + x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) + return Image.fromarray(x_sample) diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 927a72987fe..d23812ef143 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -61,16 +61,28 @@ def unscale_latents(x): return x.sub(TAESD.latent_shift).mul(2 * TAESD.latent_magnitude) -def decode(): +def download_model(model_path): + model_url = 'https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth' + + if not os.path.exists(model_path): + os.makedirs(os.path.dirname(model_path), exist_ok=True) + + print(f'Downloading TAESD decoder to: {model_path}') + torch.hub.download_url_to_file(model_url, model_path) + + +def model(): global sd_vae_taesd if sd_vae_taesd is None: - model_path = os.path.join(paths_internal.models_path, "VAE-approx", "taesd_decoder.pth") + model_path = os.path.join(paths_internal.models_path, "VAE-taesd", "taesd_decoder.pth") + download_model(model_path) + if os.path.exists(model_path): sd_vae_taesd = TAESD(model_path) sd_vae_taesd.eval() sd_vae_taesd.to(devices.device, devices.dtype) else: - raise FileNotFoundError('Tiny AE model not found') + raise FileNotFoundError('TAESD model not found') return sd_vae_taesd.decoder diff --git a/modules/shared.py b/modules/shared.py index 6760a9005e2..96036d38384 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -425,7 +425,7 @@ def list_samplers(): "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("Tiny AE", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Tiny AE", "Approx NN", "Approx cheap"]}), + "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap", "TAESD"]}), "live_preview_content": OptionInfo("Prompt", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), "live_preview_refresh_period": OptionInfo(1000, "Progressbar/preview update period, in milliseconds") })) diff --git a/webui.py b/webui.py index 0aa03ea8f39..727ebd31d10 100644 --- a/webui.py +++ b/webui.py @@ -144,21 +144,10 @@ def check_versions(): """.strip()) -def check_taesd(): - from modules.paths_internal import models_path - - model_url = 'https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth' - model_path = os.path.join(models_path, "VAE-approx", "taesd_decoder.pth") - if not os.path.exists(model_path): - print('From taesd repo download decoder model') - torch.hub.download_url_to_file(model_url, model_path) - - def initialize(): fix_asyncio_event_loop_policy() check_versions() - check_taesd() extensions.list_extensions() localization.list_localizations(cmd_opts.localizations_dir) From 85b4f89926f7c3aaa7846dcbb47df3fd3b483b6b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 May 2023 23:46:45 +0300 Subject: [PATCH 0239/2418] Replace state.need_restart with state.server_command + replace poll loop with signal --- modules/shared.py | 42 +++++++++++++++++++++++++++++++++++++++- modules/ui.py | 6 +----- modules/ui_extensions.py | 7 ++----- webui.py | 39 +++++++++++++++++++++++-------------- 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 3abf71c0053..648a2a19153 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -2,6 +2,7 @@ import json import os import sys +import threading import time import gradio as gr @@ -110,8 +111,47 @@ class State: id_live_preview = 0 textinfo = None time_start = None - need_restart = False server_start = None + _server_command_signal = threading.Event() + _server_command: str | None = None + + @property + def need_restart(self) -> bool: + # Compatibility getter for need_restart. + return self.server_command == "restart" + + @need_restart.setter + def need_restart(self, value: bool) -> None: + # Compatibility setter for need_restart. + if value: + self.server_command = "restart" + + @property + def server_command(self): + return self._server_command + + @server_command.setter + def server_command(self, value: str | None) -> None: + """ + Set the server command to `value` and signal that it's been set. + """ + self._server_command = value + self._server_command_signal.set() + + def wait_for_server_command(self, timeout: float | None = None) -> str | None: + """ + Wait for server command to get set; return and clear the value and signal. + """ + if self._server_command_signal.wait(timeout): + self._server_command_signal.clear() + req = self._server_command + self._server_command = None + return req + return None + + def request_restart(self) -> None: + self.interrupt() + self.server_command = True def skip(self): self.skipped = True diff --git a/modules/ui.py b/modules/ui.py index 8e51e7823d0..bed8464e667 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1609,12 +1609,8 @@ def reload_scripts(): outputs=[] ) - def request_restart(): - shared.state.interrupt() - shared.state.need_restart = True - restart_gradio.click( - fn=request_restart, + fn=shared.state.request_restart, _js='restart_reload', inputs=[], outputs=[], diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index d7a0f68563e..4ba3bdd7c4e 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -52,9 +52,7 @@ def apply_and_restart(disable_list, update_list, disable_all): shared.opts.disabled_extensions = disabled shared.opts.disable_all_extensions = disable_all shared.opts.save(shared.config_filename) - - shared.state.interrupt() - shared.state.need_restart = True + shared.state.request_restart() def save_config_state(name): @@ -92,8 +90,7 @@ def restore_config_state(confirmed, config_state_name, restore_type): if restore_type == "webui" or restore_type == "both": config_states.restore_webui_config(config_state) - shared.state.interrupt() - shared.state.need_restart = True + shared.state.request_restart() return "" diff --git a/webui.py b/webui.py index 293a16ccda2..39dec3cae27 100644 --- a/webui.py +++ b/webui.py @@ -234,7 +234,10 @@ def sigint_handler(sig, frame): print(f'Interrupted with signal {sig} in {frame}') os._exit(0) - signal.signal(signal.SIGINT, sigint_handler) + if not os.environ.get("COVERAGE_RUN"): + # Don't install the immediate-quit handler when running under coverage, + # as then the coverage report won't be generated. + signal.signal(signal.SIGINT, sigint_handler) def setup_middleware(app): @@ -255,19 +258,6 @@ def create_api(app): return api -def wait_on_server(demo=None): - while 1: - time.sleep(0.5) - if shared.state.need_restart: - shared.state.need_restart = False - time.sleep(0.5) - demo.close() - time.sleep(0.5) - - modules.script_callbacks.app_reload_callback() - break - - def api_only(): initialize() @@ -328,6 +318,7 @@ def fastapi_setup(self): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) + # after initial launch, disable --autolaunch for subsequent restarts cmd_opts.autolaunch = False @@ -359,8 +350,26 @@ def fastapi_setup(self): redirector.get("/") gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}") - wait_on_server(shared.demo) + try: + while True: + server_command = shared.state.wait_for_server_command(timeout=5) + if server_command: + if server_command in ("stop", "restart"): + break + else: + print(f"Unknown server command: {server_command}") + except KeyboardInterrupt: + server_command = "stop" + + if server_command == "stop": + # If we catch a keyboard interrupt, we want to stop the server and exit. + print('Caught KeyboardInterrupt, stopping...') + shared.demo.close() + break print('Restarting UI...') + shared.demo.close() + time.sleep(0.5) + modules.script_callbacks.app_reload_callback() startup_timer.reset() From 875990a23213c63c19b8fdd3c87345f7a8ea2ceb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 16 May 2023 20:58:35 +0300 Subject: [PATCH 0240/2418] Add option for /_stop route (for graceful shutdown) --- modules/cmd_args.py | 1 + webui.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index f4a4ab36f49..6144db5ccd6 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -103,3 +103,4 @@ parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') +parser.add_argument('--add-stop-route', action='store_true', help='add /_stop route to stop server') diff --git a/webui.py b/webui.py index 39dec3cae27..5172f049cd1 100644 --- a/webui.py +++ b/webui.py @@ -8,7 +8,7 @@ import json from threading import Thread -from fastapi import FastAPI +from fastapi import FastAPI, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from packaging import version @@ -270,6 +270,12 @@ def api_only(): print(f"Startup time: {startup_timer.summary()}.") api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861) + +def stop_route(request): + shared.state.server_command = "stop" + return Response("Stopping.") + + def webui(): launch_api = cmd_opts.api initialize() @@ -318,6 +324,8 @@ def fastapi_setup(self): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) + if cmd_opts.add_stop_route: + app.add_route("/_stop", stop_route, methods=["POST"]) # after initial launch, disable --autolaunch for subsequent restarts cmd_opts.autolaunch = False @@ -359,11 +367,12 @@ def fastapi_setup(self): else: print(f"Unknown server command: {server_command}") except KeyboardInterrupt: + print('Caught KeyboardInterrupt, stopping...') server_command = "stop" if server_command == "stop": + print("Stopping server...") # If we catch a keyboard interrupt, we want to stop the server and exit. - print('Caught KeyboardInterrupt, stopping...') shared.demo.close() break print('Restarting UI...') From 315f109427a5dbbf48b0da560640523800fe3c1d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 17 May 2023 10:26:32 +0300 Subject: [PATCH 0241/2418] Copy s_min_uncond to Processed Should fix #10416 --- modules/processing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/processing.py b/modules/processing.py index 678c4468f4b..cd63b9a604f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -316,6 +316,7 @@ def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", self.s_tmin = p.s_tmin self.s_tmax = p.s_tmax self.s_noise = p.s_noise + self.s_min_uncond = p.s_min_uncond self.sampler_noise_scheduler_override = p.sampler_noise_scheduler_override self.prompt = self.prompt if type(self.prompt) != list else self.prompt[0] self.negative_prompt = self.negative_prompt if type(self.negative_prompt) != list else self.negative_prompt[0] From b3397c2492db375b89faece3a8d5aa3a230f62c0 Mon Sep 17 00:00:00 2001 From: Baptiste Rajaut Date: Wed, 17 May 2023 11:01:33 +0200 Subject: [PATCH 0242/2418] Bump pytorch for AMD Users So apparently it works now? Before you would get "Pytorch cant use the GPU" but not anymore. --- webui.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui.sh b/webui.sh index 19cf2f78fc3..b644abf52a0 100755 --- a/webui.sh +++ b/webui.sh @@ -118,8 +118,8 @@ case "$gpu_info" in esac if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then - # AMD users will still use torch 1.13 because 2.0 does not seem to work. - export TORCH_COMMAND="pip install torch==1.13.1+rocm5.2 torchvision==0.14.1+rocm5.2 --index-url https://download.pytorch.org/whl/rocm5.2" + # Apparently now this works + export TORCH_COMMAND="pip install torch-2.0.1+rocm5.4.2 torchvision-0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" fi for preq in "${GIT}" "${python_cmd}" From 484948f5c0b755a921c02cccbcacb2684a86a814 Mon Sep 17 00:00:00 2001 From: Baptiste Rajaut Date: Wed, 17 May 2023 11:10:57 +0200 Subject: [PATCH 0243/2418] Fixing webui.sh If only i proofread what i wrote --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index b644abf52a0..113a8c1ade0 100755 --- a/webui.sh +++ b/webui.sh @@ -119,7 +119,7 @@ esac if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then # Apparently now this works - export TORCH_COMMAND="pip install torch-2.0.1+rocm5.4.2 torchvision-0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" + export TORCH_COMMAND="pip install torch==2.0.1+rocm5.4.2 torchvision==0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" fi for preq in "${GIT}" "${python_cmd}" From 7a13a3f4ba86dc44fcf7d9944b179018744862f5 Mon Sep 17 00:00:00 2001 From: Sakura-Luna <53183413+Sakura-Luna@users.noreply.github.com> Date: Wed, 17 May 2023 17:39:07 +0800 Subject: [PATCH 0244/2418] TAESD fix --- modules/sd_samplers_common.py | 9 +++++---- modules/sd_vae_taesd.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index ceda6a35799..d99c933da8f 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -35,13 +35,14 @@ def single_sample_to_image(sample, approximation=None): elif approximation == 1: x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() elif approximation == 3: - x_sample = sd_vae_taesd.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() - x_sample = sd_vae_taesd.TAESD.unscale_latents(x_sample) # returns value in [-2, 2] - x_sample = x_sample * 0.5 + x_sample = sample * 1.5 + x_sample = sd_vae_taesd.model()(x_sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() else: x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] - x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) + if approximation != 3: + x_sample = (x_sample + 1.0) / 2.0 + x_sample = torch.clamp(x_sample, min=0.0, max=1.0) x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index d23812ef143..5e8496e8739 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -45,7 +45,7 @@ def decoder(): class TAESD(nn.Module): - latent_magnitude = 2 + latent_magnitude = 3 latent_shift = 0.5 def __init__(self, decoder_path="taesd_decoder.pth"): From 1210548cba9dbd78378a710d75601922addefca2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 14:53:39 +0300 Subject: [PATCH 0245/2418] simplify single_sample_to_image --- modules/sd_samplers_common.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index d99c933da8f..763829f1ca3 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -26,22 +26,19 @@ def setup_img2img_steps(p, steps=None): def single_sample_to_image(sample, approximation=None): - if approximation is None: approximation = approximation_indexes.get(opts.show_progress_type, 0) if approximation == 2: - x_sample = sd_vae_approx.cheap_approximation(sample) + x_sample = sd_vae_approx.cheap_approximation(sample) * 0.5 + 0.5 elif approximation == 1: - x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() + x_sample = sd_vae_approx.model()(sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() * 0.5 + 0.5 elif approximation == 3: x_sample = sample * 1.5 x_sample = sd_vae_taesd.model()(x_sample.to(devices.device, devices.dtype).unsqueeze(0))[0].detach() else: - x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] + x_sample = processing.decode_first_stage(shared.sd_model, sample.unsqueeze(0))[0] * 0.5 + 0.5 - if approximation != 3: - x_sample = (x_sample + 1.0) / 2.0 x_sample = torch.clamp(x_sample, min=0.0, max=1.0) x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) From 13f4c62ba3870f172e6fdb26d4f33576f7f60f7e Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 17 May 2023 13:23:01 +0300 Subject: [PATCH 0246/2418] Add basic ESLint configuration for formatting This doesn't enable any of ESLint's actual possible-issue linting, but just style normalization based on the Prettier configuration (but without line length limits). --- .eslintignore | 4 ++++ .eslintrc.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 ++ package.json | 11 +++++++++++ 4 files changed, 66 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 package.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000000..1cfd9487674 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +extensions +extensions-disabled +repositories +venv \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000000..48f9df7d423 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,49 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + // "extends": "eslint:recommended", + parserOptions: { + ecmaVersion: "latest", + }, + rules: { + "arrow-spacing": "error", + "block-spacing": "error", + "brace-style": "error", + "comma-dangle": ["error", "only-multiline"], + "comma-spacing": "error", + "comma-style": ["error", "last"], + "curly": ["error", "multi-line", "consistent"], + "eol-last": "error", + "func-call-spacing": "error", + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "consistent"], + "indent": ["error", 4], + "key-spacing": "error", + "keyword-spacing": "error", + "linebreak-style": ["error", "unix"], + "no-extra-semi": "error", + "no-mixed-spaces-and-tabs": "error", + "no-trailing-spaces": "error", + "no-whitespace-before-property": "error", + "object-curly-newline": ["error", {consistent: true, multiline: true}], + "quote-props": ["error", "consistent-as-needed"], + "semi": ["error", "always"], + "semi-spacing": "error", + "semi-style": ["error", "last"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", "never"], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": "error", + "switch-colon-spacing": "error", + "template-curly-spacing": ["error", "never"], + "unicode-bom": "error", + // "no-multi-spaces": "error", // TODO: enable? + // "object-curly-spacing": "off", // TODO: enable? + // "object-property-newline": "off", // TODO: enable? + // "operator-linebreak": "off", // TODO: enable? + // "quotes": ["error", "double", {avoidEscape: true}], // TODO: enable? + }, +}; diff --git a/.gitignore b/.gitignore index 7328401f573..46654d83594 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ notification.mp3 /test/stderr.txt /cache.json* /config_states/ +/node_modules +/package-lock.json \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000000..c0ba406787d --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "stable-diffusion-webui", + "version": "0.0.0", + "devDependencies": { + "eslint": "^8.40.0" + }, + "scripts": { + "lint": "eslint .", + "fix": "eslint --fix ." + } +} From 4f11f285f912fd48bc85a650a0384b6044d68b86 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 17 May 2023 13:31:01 +0300 Subject: [PATCH 0247/2418] Add ESLint to CI --- .github/workflows/on_pull_request.yaml | 36 +++++++++----------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index d42965b15c9..7b7219fd6b0 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -1,19 +1,11 @@ -# See https://github.com/actions/starter-workflows/blob/1067f16ad8a1eac328834e4b0ae24f7d206f810d/ci/pylint.yml for original reference file name: Run Linting/Formatting on Pull Requests on: - push - pull_request - # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpull_requestpull_request_targetbranchesbranches-ignore for syntax docs - # if you want to filter out branches, delete the `- pull_request` and uncomment these lines : - # pull_request: - # branches: - # - master - # branches-ignore: - # - development jobs: - lint: + lint-python: runs-on: ubuntu-latest steps: - name: Checkout Code @@ -29,18 +21,14 @@ jobs: run: pip install ruff==0.0.265 - name: Run Ruff run: ruff . - -# The rest are currently disabled pending fixing of e.g. installing the torch dependency. - -# - name: Install PyLint -# run: | -# python -m pip install --upgrade pip -# pip install pylint -# # This lets PyLint check to see if it can resolve imports -# - name: Install dependencies -# run: | -# export COMMANDLINE_ARGS="--skip-torch-cuda-test --exit" -# python launch.py -# - name: Analysing the code with pylint -# run: | -# pylint $(git ls-files '*.py') + lint-js: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - run: npm i --ci + - run: npm run lint From 9c54b78d9dde5601e916f308d9a9d6953ec39430 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 17 May 2023 15:46:58 +0300 Subject: [PATCH 0248/2418] Run `eslint --fix` (and normalize tabs to spaces) --- .../javascript/prompt-bracket-checker.js | 52 +-- javascript/aspectRatioOverlay.js | 224 +++++----- javascript/contextMenus.js | 338 +++++++------- javascript/dragdrop.js | 47 +- javascript/edit-attention.js | 240 +++++----- javascript/extensions.js | 145 +++--- javascript/extraNetworks.js | 420 +++++++++--------- javascript/generationParams.js | 48 +- javascript/hints.js | 72 +-- javascript/hires_fix.js | 36 +- javascript/imageMaskFix.js | 20 +- javascript/imageParams.js | 4 +- javascript/imageviewer.js | 221 ++++----- javascript/imageviewerGamepad.js | 4 +- javascript/localization.js | 354 +++++++-------- javascript/notification.js | 12 +- javascript/progressbar.js | 182 ++++---- javascript/textualInversion.js | 34 +- javascript/ui.js | 415 ++++++++--------- javascript/ui_settings_hints.js | 124 +++--- script.js | 74 +-- 21 files changed, 1554 insertions(+), 1512 deletions(-) diff --git a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js index 5c7a836a2ee..ed9baf9d03f 100644 --- a/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js +++ b/extensions-builtin/prompt-bracket-checker/javascript/prompt-bracket-checker.js @@ -4,39 +4,39 @@ // If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong. function checkBrackets(textArea, counterElt) { - var counts = {}; - (textArea.value.match(/[(){}\[\]]/g) || []).forEach(bracket => { - counts[bracket] = (counts[bracket] || 0) + 1; - }); - var errors = []; + var counts = {}; + (textArea.value.match(/[(){}\[\]]/g) || []).forEach(bracket => { + counts[bracket] = (counts[bracket] || 0) + 1; + }); + var errors = []; - function checkPair(open, close, kind) { - if (counts[open] !== counts[close]) { - errors.push( - `${open}...${close} - Detected ${counts[open] || 0} opening and ${counts[close] || 0} closing ${kind}.` - ); + function checkPair(open, close, kind) { + if (counts[open] !== counts[close]) { + errors.push( + `${open}...${close} - Detected ${counts[open] || 0} opening and ${counts[close] || 0} closing ${kind}.` + ); + } } - } - checkPair('(', ')', 'round brackets'); - checkPair('[', ']', 'square brackets'); - checkPair('{', '}', 'curly brackets'); - counterElt.title = errors.join('\n'); - counterElt.classList.toggle('error', errors.length !== 0); + checkPair('(', ')', 'round brackets'); + checkPair('[', ']', 'square brackets'); + checkPair('{', '}', 'curly brackets'); + counterElt.title = errors.join('\n'); + counterElt.classList.toggle('error', errors.length !== 0); } function setupBracketChecking(id_prompt, id_counter) { - var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea"); - var counter = gradioApp().getElementById(id_counter) + var textarea = gradioApp().querySelector("#" + id_prompt + " > label > textarea"); + var counter = gradioApp().getElementById(id_counter); - if (textarea && counter) { - textarea.addEventListener("input", () => checkBrackets(textarea, counter)); - } + if (textarea && counter) { + textarea.addEventListener("input", () => checkBrackets(textarea, counter)); + } } -onUiLoaded(function () { - setupBracketChecking('txt2img_prompt', 'txt2img_token_counter'); - setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter'); - setupBracketChecking('img2img_prompt', 'img2img_token_counter'); - setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter'); +onUiLoaded(function() { + setupBracketChecking('txt2img_prompt', 'txt2img_token_counter'); + setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter'); + setupBracketChecking('img2img_prompt', 'img2img_token_counter'); + setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter'); }); diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index 5160081d282..059338d6906 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -1,111 +1,113 @@ - -let currentWidth = null; -let currentHeight = null; -let arFrameTimeout = setTimeout(function(){},0); - -function dimensionChange(e, is_width, is_height){ - - if(is_width){ - currentWidth = e.target.value*1.0 - } - if(is_height){ - currentHeight = e.target.value*1.0 - } - - var inImg2img = gradioApp().querySelector("#tab_img2img").style.display == "block"; - - if(!inImg2img){ - return; - } - - var targetElement = null; - - var tabIndex = get_tab_index('mode_img2img') - if(tabIndex == 0){ // img2img - targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] img'); - } else if(tabIndex == 1){ //Sketch - targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img'); - } else if(tabIndex == 2){ // Inpaint - targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img'); - } else if(tabIndex == 3){ // Inpaint sketch - targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img'); - } - - - if(targetElement){ - - var arPreviewRect = gradioApp().querySelector('#imageARPreview'); - if(!arPreviewRect){ - arPreviewRect = document.createElement('div') - arPreviewRect.id = "imageARPreview"; - gradioApp().appendChild(arPreviewRect) - } - - - - var viewportOffset = targetElement.getBoundingClientRect(); - - var viewportscale = Math.min( targetElement.clientWidth/targetElement.naturalWidth, targetElement.clientHeight/targetElement.naturalHeight ) - - var scaledx = targetElement.naturalWidth*viewportscale - var scaledy = targetElement.naturalHeight*viewportscale - - var cleintRectTop = (viewportOffset.top+window.scrollY) - var cleintRectLeft = (viewportOffset.left+window.scrollX) - var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight/2) - var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth/2) - - var arscale = Math.min( scaledx/currentWidth, scaledy/currentHeight ) - var arscaledx = currentWidth*arscale - var arscaledy = currentHeight*arscale - - var arRectTop = cleintRectCentreY-(arscaledy/2) - var arRectLeft = cleintRectCentreX-(arscaledx/2) - var arRectWidth = arscaledx - var arRectHeight = arscaledy - - arPreviewRect.style.top = arRectTop+'px'; - arPreviewRect.style.left = arRectLeft+'px'; - arPreviewRect.style.width = arRectWidth+'px'; - arPreviewRect.style.height = arRectHeight+'px'; - - clearTimeout(arFrameTimeout); - arFrameTimeout = setTimeout(function(){ - arPreviewRect.style.display = 'none'; - },2000); - - arPreviewRect.style.display = 'block'; - - } - -} - - -onUiUpdate(function(){ - var arPreviewRect = gradioApp().querySelector('#imageARPreview'); - if(arPreviewRect){ - arPreviewRect.style.display = 'none'; - } - var tabImg2img = gradioApp().querySelector("#tab_img2img"); - if (tabImg2img) { - var inImg2img = tabImg2img.style.display == "block"; - if(inImg2img){ - let inputs = gradioApp().querySelectorAll('input'); - inputs.forEach(function(e){ - var is_width = e.parentElement.id == "img2img_width" - var is_height = e.parentElement.id == "img2img_height" - - if((is_width || is_height) && !e.classList.contains('scrollwatch')){ - e.addEventListener('input', function(e){dimensionChange(e, is_width, is_height)} ) - e.classList.add('scrollwatch') - } - if(is_width){ - currentWidth = e.value*1.0 - } - if(is_height){ - currentHeight = e.value*1.0 - } - }) - } - } -}); + +let currentWidth = null; +let currentHeight = null; +let arFrameTimeout = setTimeout(function() {}, 0); + +function dimensionChange(e, is_width, is_height) { + + if (is_width) { + currentWidth = e.target.value * 1.0; + } + if (is_height) { + currentHeight = e.target.value * 1.0; + } + + var inImg2img = gradioApp().querySelector("#tab_img2img").style.display == "block"; + + if (!inImg2img) { + return; + } + + var targetElement = null; + + var tabIndex = get_tab_index('mode_img2img'); + if (tabIndex == 0) { // img2img + targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] img'); + } else if (tabIndex == 1) { //Sketch + targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img'); + } else if (tabIndex == 2) { // Inpaint + targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img'); + } else if (tabIndex == 3) { // Inpaint sketch + targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img'); + } + + + if (targetElement) { + + var arPreviewRect = gradioApp().querySelector('#imageARPreview'); + if (!arPreviewRect) { + arPreviewRect = document.createElement('div'); + arPreviewRect.id = "imageARPreview"; + gradioApp().appendChild(arPreviewRect); + } + + + + var viewportOffset = targetElement.getBoundingClientRect(); + + var viewportscale = Math.min(targetElement.clientWidth / targetElement.naturalWidth, targetElement.clientHeight / targetElement.naturalHeight); + + var scaledx = targetElement.naturalWidth * viewportscale; + var scaledy = targetElement.naturalHeight * viewportscale; + + var cleintRectTop = (viewportOffset.top + window.scrollY); + var cleintRectLeft = (viewportOffset.left + window.scrollX); + var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight / 2); + var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth / 2); + + var arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight); + var arscaledx = currentWidth * arscale; + var arscaledy = currentHeight * arscale; + + var arRectTop = cleintRectCentreY - (arscaledy / 2); + var arRectLeft = cleintRectCentreX - (arscaledx / 2); + var arRectWidth = arscaledx; + var arRectHeight = arscaledy; + + arPreviewRect.style.top = arRectTop + 'px'; + arPreviewRect.style.left = arRectLeft + 'px'; + arPreviewRect.style.width = arRectWidth + 'px'; + arPreviewRect.style.height = arRectHeight + 'px'; + + clearTimeout(arFrameTimeout); + arFrameTimeout = setTimeout(function() { + arPreviewRect.style.display = 'none'; + }, 2000); + + arPreviewRect.style.display = 'block'; + + } + +} + + +onUiUpdate(function() { + var arPreviewRect = gradioApp().querySelector('#imageARPreview'); + if (arPreviewRect) { + arPreviewRect.style.display = 'none'; + } + var tabImg2img = gradioApp().querySelector("#tab_img2img"); + if (tabImg2img) { + var inImg2img = tabImg2img.style.display == "block"; + if (inImg2img) { + let inputs = gradioApp().querySelectorAll('input'); + inputs.forEach(function(e) { + var is_width = e.parentElement.id == "img2img_width"; + var is_height = e.parentElement.id == "img2img_height"; + + if ((is_width || is_height) && !e.classList.contains('scrollwatch')) { + e.addEventListener('input', function(e) { + dimensionChange(e, is_width, is_height); + }); + e.classList.add('scrollwatch'); + } + if (is_width) { + currentWidth = e.value * 1.0; + } + if (is_height) { + currentHeight = e.value * 1.0; + } + }); + } + } +}); diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index b2bdf0532cb..f7a15cae12a 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -1,166 +1,172 @@ - -contextMenuInit = function(){ - let eventListenerApplied=false; - let menuSpecs = new Map(); - - const uid = function(){ - return Date.now().toString(36) + Math.random().toString(36).substring(2); - } - - function showContextMenu(event,element,menuEntries){ - let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - - let oldMenu = gradioApp().querySelector('#context-menu') - if(oldMenu){ - oldMenu.remove() - } - - let baseStyle = window.getComputedStyle(uiCurrentTab) - - const contextMenu = document.createElement('nav') - contextMenu.id = "context-menu" - contextMenu.style.background = baseStyle.background - contextMenu.style.color = baseStyle.color - contextMenu.style.fontFamily = baseStyle.fontFamily - contextMenu.style.top = posy+'px' - contextMenu.style.left = posx+'px' - - - - const contextMenuList = document.createElement('ul') - contextMenuList.className = 'context-menu-items'; - contextMenu.append(contextMenuList); - - menuEntries.forEach(function(entry){ - let contextMenuEntry = document.createElement('a') - contextMenuEntry.innerHTML = entry['name'] - contextMenuEntry.addEventListener("click", function() { - entry['func'](); - }) - contextMenuList.append(contextMenuEntry); - - }) - - gradioApp().appendChild(contextMenu) - - let menuWidth = contextMenu.offsetWidth + 4; - let menuHeight = contextMenu.offsetHeight + 4; - - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - - if ( (windowWidth - posx) < menuWidth ) { - contextMenu.style.left = windowWidth - menuWidth + "px"; - } - - if ( (windowHeight - posy) < menuHeight ) { - contextMenu.style.top = windowHeight - menuHeight + "px"; - } - - } - - function appendContextMenuOption(targetElementSelector,entryName,entryFunction){ - - var currentItems = menuSpecs.get(targetElementSelector) - - if(!currentItems){ - currentItems = [] - menuSpecs.set(targetElementSelector,currentItems); - } - let newItem = {'id':targetElementSelector+'_'+uid(), - 'name':entryName, - 'func':entryFunction, - 'isNew':true} - - currentItems.push(newItem) - return newItem['id'] - } - - function removeContextMenuOption(uid){ - menuSpecs.forEach(function(v) { - let index = -1 - v.forEach(function(e,ei){if(e['id']==uid){index=ei}}) - if(index>=0){ - v.splice(index, 1); - } - }) - } - - function addContextMenuEventListener(){ - if(eventListenerApplied){ - return; - } - gradioApp().addEventListener("click", function(e) { - if(! e.isTrusted){ - return - } - - let oldMenu = gradioApp().querySelector('#context-menu') - if(oldMenu){ - oldMenu.remove() - } - }); - gradioApp().addEventListener("contextmenu", function(e) { - let oldMenu = gradioApp().querySelector('#context-menu') - if(oldMenu){ - oldMenu.remove() - } - menuSpecs.forEach(function(v,k) { - if(e.composedPath()[0].matches(k)){ - showContextMenu(e,e.composedPath()[0],v) - e.preventDefault() - } - }) - }); - eventListenerApplied=true - - } - - return [appendContextMenuOption, removeContextMenuOption, addContextMenuEventListener] -} - -initResponse = contextMenuInit(); -appendContextMenuOption = initResponse[0]; -removeContextMenuOption = initResponse[1]; -addContextMenuEventListener = initResponse[2]; - -(function(){ - //Start example Context Menu Items - let generateOnRepeat = function(genbuttonid,interruptbuttonid){ - let genbutton = gradioApp().querySelector(genbuttonid); - let interruptbutton = gradioApp().querySelector(interruptbuttonid); - if(!interruptbutton.offsetParent){ - genbutton.click(); - } - clearInterval(window.generateOnRepeatInterval) - window.generateOnRepeatInterval = setInterval(function(){ - if(!interruptbutton.offsetParent){ - genbutton.click(); - } - }, - 500) - } - - appendContextMenuOption('#txt2img_generate','Generate forever',function(){ - generateOnRepeat('#txt2img_generate','#txt2img_interrupt'); - }) - appendContextMenuOption('#img2img_generate','Generate forever',function(){ - generateOnRepeat('#img2img_generate','#img2img_interrupt'); - }) - - let cancelGenerateForever = function(){ - clearInterval(window.generateOnRepeatInterval) - } - - appendContextMenuOption('#txt2img_interrupt','Cancel generate forever',cancelGenerateForever) - appendContextMenuOption('#txt2img_generate', 'Cancel generate forever',cancelGenerateForever) - appendContextMenuOption('#img2img_interrupt','Cancel generate forever',cancelGenerateForever) - appendContextMenuOption('#img2img_generate', 'Cancel generate forever',cancelGenerateForever) - -})(); -//End example Context Menu Items - -onUiUpdate(function(){ - addContextMenuEventListener() -}); + +contextMenuInit = function() { + let eventListenerApplied = false; + let menuSpecs = new Map(); + + const uid = function() { + return Date.now().toString(36) + Math.random().toString(36).substring(2); + }; + + function showContextMenu(event, element, menuEntries) { + let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + + let oldMenu = gradioApp().querySelector('#context-menu'); + if (oldMenu) { + oldMenu.remove(); + } + + let baseStyle = window.getComputedStyle(uiCurrentTab); + + const contextMenu = document.createElement('nav'); + contextMenu.id = "context-menu"; + contextMenu.style.background = baseStyle.background; + contextMenu.style.color = baseStyle.color; + contextMenu.style.fontFamily = baseStyle.fontFamily; + contextMenu.style.top = posy + 'px'; + contextMenu.style.left = posx + 'px'; + + + + const contextMenuList = document.createElement('ul'); + contextMenuList.className = 'context-menu-items'; + contextMenu.append(contextMenuList); + + menuEntries.forEach(function(entry) { + let contextMenuEntry = document.createElement('a'); + contextMenuEntry.innerHTML = entry['name']; + contextMenuEntry.addEventListener("click", function() { + entry['func'](); + }); + contextMenuList.append(contextMenuEntry); + + }); + + gradioApp().appendChild(contextMenu); + + let menuWidth = contextMenu.offsetWidth + 4; + let menuHeight = contextMenu.offsetHeight + 4; + + let windowWidth = window.innerWidth; + let windowHeight = window.innerHeight; + + if ((windowWidth - posx) < menuWidth) { + contextMenu.style.left = windowWidth - menuWidth + "px"; + } + + if ((windowHeight - posy) < menuHeight) { + contextMenu.style.top = windowHeight - menuHeight + "px"; + } + + } + + function appendContextMenuOption(targetElementSelector, entryName, entryFunction) { + + var currentItems = menuSpecs.get(targetElementSelector); + + if (!currentItems) { + currentItems = []; + menuSpecs.set(targetElementSelector, currentItems); + } + let newItem = { + id: targetElementSelector + '_' + uid(), + name: entryName, + func: entryFunction, + isNew: true + }; + + currentItems.push(newItem); + return newItem['id']; + } + + function removeContextMenuOption(uid) { + menuSpecs.forEach(function(v) { + let index = -1; + v.forEach(function(e, ei) { + if (e['id'] == uid) { + index = ei; + } + }); + if (index >= 0) { + v.splice(index, 1); + } + }); + } + + function addContextMenuEventListener() { + if (eventListenerApplied) { + return; + } + gradioApp().addEventListener("click", function(e) { + if (!e.isTrusted) { + return; + } + + let oldMenu = gradioApp().querySelector('#context-menu'); + if (oldMenu) { + oldMenu.remove(); + } + }); + gradioApp().addEventListener("contextmenu", function(e) { + let oldMenu = gradioApp().querySelector('#context-menu'); + if (oldMenu) { + oldMenu.remove(); + } + menuSpecs.forEach(function(v, k) { + if (e.composedPath()[0].matches(k)) { + showContextMenu(e, e.composedPath()[0], v); + e.preventDefault(); + } + }); + }); + eventListenerApplied = true; + + } + + return [appendContextMenuOption, removeContextMenuOption, addContextMenuEventListener]; +}; + +initResponse = contextMenuInit(); +appendContextMenuOption = initResponse[0]; +removeContextMenuOption = initResponse[1]; +addContextMenuEventListener = initResponse[2]; + +(function() { + //Start example Context Menu Items + let generateOnRepeat = function(genbuttonid, interruptbuttonid) { + let genbutton = gradioApp().querySelector(genbuttonid); + let interruptbutton = gradioApp().querySelector(interruptbuttonid); + if (!interruptbutton.offsetParent) { + genbutton.click(); + } + clearInterval(window.generateOnRepeatInterval); + window.generateOnRepeatInterval = setInterval(function() { + if (!interruptbutton.offsetParent) { + genbutton.click(); + } + }, + 500); + }; + + appendContextMenuOption('#txt2img_generate', 'Generate forever', function() { + generateOnRepeat('#txt2img_generate', '#txt2img_interrupt'); + }); + appendContextMenuOption('#img2img_generate', 'Generate forever', function() { + generateOnRepeat('#img2img_generate', '#img2img_interrupt'); + }); + + let cancelGenerateForever = function() { + clearInterval(window.generateOnRepeatInterval); + }; + + appendContextMenuOption('#txt2img_interrupt', 'Cancel generate forever', cancelGenerateForever); + appendContextMenuOption('#txt2img_generate', 'Cancel generate forever', cancelGenerateForever); + appendContextMenuOption('#img2img_interrupt', 'Cancel generate forever', cancelGenerateForever); + appendContextMenuOption('#img2img_generate', 'Cancel generate forever', cancelGenerateForever); + +})(); +//End example Context Menu Items + +onUiUpdate(function() { + addContextMenuEventListener(); +}); diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index fe00892481a..e316a365011 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -1,11 +1,11 @@ // allows drag-dropping files into gradio image elements, and also pasting images from clipboard -function isValidImageList( files ) { +function isValidImageList(files) { return files && files?.length === 1 && ['image/png', 'image/gif', 'image/jpeg'].includes(files[0].type); } -function dropReplaceImage( imgWrap, files ) { - if ( ! isValidImageList( files ) ) { +function dropReplaceImage(imgWrap, files) { + if (!isValidImageList(files)) { return; } @@ -14,44 +14,44 @@ function dropReplaceImage( imgWrap, files ) { imgWrap.querySelector('.modify-upload button + button, .touch-none + div button + button')?.click(); const callback = () => { const fileInput = imgWrap.querySelector('input[type="file"]'); - if ( fileInput ) { - if ( files.length === 0 ) { + if (fileInput) { + if (files.length === 0) { files = new DataTransfer(); files.items.add(tmpFile); fileInput.files = files.files; } else { fileInput.files = files; } - fileInput.dispatchEvent(new Event('change')); + fileInput.dispatchEvent(new Event('change')); } }; - - if ( imgWrap.closest('#pnginfo_image') ) { + + if (imgWrap.closest('#pnginfo_image')) { // special treatment for PNG Info tab, wait for fetch request to finish const oldFetch = window.fetch; - window.fetch = async (input, options) => { + window.fetch = async(input, options) => { const response = await oldFetch(input, options); - if ( 'api/predict/' === input ) { + if ('api/predict/' === input) { const content = await response.text(); window.fetch = oldFetch; - window.requestAnimationFrame( () => callback() ); + window.requestAnimationFrame(() => callback()); return new Response(content, { status: response.status, statusText: response.statusText, headers: response.headers - }) + }); } return response; - }; + }; } else { - window.requestAnimationFrame( () => callback() ); + window.requestAnimationFrame(() => callback()); } } window.document.addEventListener('dragover', e => { const target = e.composedPath()[0]; const imgWrap = target.closest('[data-testid="image"]'); - if ( !imgWrap && target.placeholder && target.placeholder.indexOf("Prompt") == -1) { + if (!imgWrap && target.placeholder && target.placeholder.indexOf("Prompt") == -1) { return; } e.stopPropagation(); @@ -65,33 +65,34 @@ window.document.addEventListener('drop', e => { return; } const imgWrap = target.closest('[data-testid="image"]'); - if ( !imgWrap ) { + if (!imgWrap) { return; } e.stopPropagation(); e.preventDefault(); const files = e.dataTransfer.files; - dropReplaceImage( imgWrap, files ); + dropReplaceImage(imgWrap, files); }); window.addEventListener('paste', e => { const files = e.clipboardData.files; - if ( ! isValidImageList( files ) ) { + if (!isValidImageList(files)) { return; } const visibleImageFields = [...gradioApp().querySelectorAll('[data-testid="image"]')] .filter(el => uiElementIsVisible(el)); - if ( ! visibleImageFields.length ) { + if (!visibleImageFields.length) { return; } - + const firstFreeImageField = visibleImageFields .filter(el => el.querySelector('input[type=file]'))?.[0]; dropReplaceImage( firstFreeImageField ? - firstFreeImageField : - visibleImageFields[visibleImageFields.length - 1] - , files ); + firstFreeImageField : + visibleImageFields[visibleImageFields.length - 1] + , files + ); }); diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index d2c2f19050e..fdf00b4d2ea 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -1,120 +1,120 @@ -function keyupEditAttention(event){ - let target = event.originalTarget || event.composedPath()[0]; - if (! target.matches("[id*='_toprow'] [id*='_prompt'] textarea")) return; - if (! (event.metaKey || event.ctrlKey)) return; - - let isPlus = event.key == "ArrowUp" - let isMinus = event.key == "ArrowDown" - if (!isPlus && !isMinus) return; - - let selectionStart = target.selectionStart; - let selectionEnd = target.selectionEnd; - let text = target.value; - - function selectCurrentParenthesisBlock(OPEN, CLOSE){ - if (selectionStart !== selectionEnd) return false; - - // Find opening parenthesis around current cursor - const before = text.substring(0, selectionStart); - let beforeParen = before.lastIndexOf(OPEN); - if (beforeParen == -1) return false; - let beforeParenClose = before.lastIndexOf(CLOSE); - while (beforeParenClose !== -1 && beforeParenClose > beforeParen) { - beforeParen = before.lastIndexOf(OPEN, beforeParen - 1); - beforeParenClose = before.lastIndexOf(CLOSE, beforeParenClose - 1); - } - - // Find closing parenthesis around current cursor - const after = text.substring(selectionStart); - let afterParen = after.indexOf(CLOSE); - if (afterParen == -1) return false; - let afterParenOpen = after.indexOf(OPEN); - while (afterParenOpen !== -1 && afterParen > afterParenOpen) { - afterParen = after.indexOf(CLOSE, afterParen + 1); - afterParenOpen = after.indexOf(OPEN, afterParenOpen + 1); - } - if (beforeParen === -1 || afterParen === -1) return false; - - // Set the selection to the text between the parenthesis - const parenContent = text.substring(beforeParen + 1, selectionStart + afterParen); - const lastColon = parenContent.lastIndexOf(":"); - selectionStart = beforeParen + 1; - selectionEnd = selectionStart + lastColon; - target.setSelectionRange(selectionStart, selectionEnd); - return true; - } - - function selectCurrentWord(){ - if (selectionStart !== selectionEnd) return false; - const delimiters = opts.keyedit_delimiters + " \r\n\t"; - - // seek backward until to find beggining - while (!delimiters.includes(text[selectionStart - 1]) && selectionStart > 0) { - selectionStart--; - } - - // seek forward to find end - while (!delimiters.includes(text[selectionEnd]) && selectionEnd < text.length) { - selectionEnd++; - } - - target.setSelectionRange(selectionStart, selectionEnd); - return true; - } - - // If the user hasn't selected anything, let's select their current parenthesis block or word - if (!selectCurrentParenthesisBlock('<', '>') && !selectCurrentParenthesisBlock('(', ')')) { - selectCurrentWord(); - } - - event.preventDefault(); - - var closeCharacter = ')' - var delta = opts.keyedit_precision_attention - - if (selectionStart > 0 && text[selectionStart - 1] == '<'){ - closeCharacter = '>' - delta = opts.keyedit_precision_extra - } else if (selectionStart == 0 || text[selectionStart - 1] != "(") { - - // do not include spaces at the end - while(selectionEnd > selectionStart && text[selectionEnd-1] == ' '){ - selectionEnd -= 1; - } - if(selectionStart == selectionEnd){ - return - } - - text = text.slice(0, selectionStart) + "(" + text.slice(selectionStart, selectionEnd) + ":1.0)" + text.slice(selectionEnd); - - selectionStart += 1; - selectionEnd += 1; - } - - var end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1; - var weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end)); - if (isNaN(weight)) return; - - weight += isPlus ? delta : -delta; - weight = parseFloat(weight.toPrecision(12)); - if(String(weight).length == 1) weight += ".0" - - if (closeCharacter == ')' && weight == 1) { - text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + 5); - selectionStart--; - selectionEnd--; - } else { - text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + 1 + end - 1); - } - - target.focus(); - target.value = text; - target.selectionStart = selectionStart; - target.selectionEnd = selectionEnd; - - updateInput(target) -} - -addEventListener('keydown', (event) => { - keyupEditAttention(event); -}); +function keyupEditAttention(event) { + let target = event.originalTarget || event.composedPath()[0]; + if (!target.matches("[id*='_toprow'] [id*='_prompt'] textarea")) return; + if (!(event.metaKey || event.ctrlKey)) return; + + let isPlus = event.key == "ArrowUp"; + let isMinus = event.key == "ArrowDown"; + if (!isPlus && !isMinus) return; + + let selectionStart = target.selectionStart; + let selectionEnd = target.selectionEnd; + let text = target.value; + + function selectCurrentParenthesisBlock(OPEN, CLOSE) { + if (selectionStart !== selectionEnd) return false; + + // Find opening parenthesis around current cursor + const before = text.substring(0, selectionStart); + let beforeParen = before.lastIndexOf(OPEN); + if (beforeParen == -1) return false; + let beforeParenClose = before.lastIndexOf(CLOSE); + while (beforeParenClose !== -1 && beforeParenClose > beforeParen) { + beforeParen = before.lastIndexOf(OPEN, beforeParen - 1); + beforeParenClose = before.lastIndexOf(CLOSE, beforeParenClose - 1); + } + + // Find closing parenthesis around current cursor + const after = text.substring(selectionStart); + let afterParen = after.indexOf(CLOSE); + if (afterParen == -1) return false; + let afterParenOpen = after.indexOf(OPEN); + while (afterParenOpen !== -1 && afterParen > afterParenOpen) { + afterParen = after.indexOf(CLOSE, afterParen + 1); + afterParenOpen = after.indexOf(OPEN, afterParenOpen + 1); + } + if (beforeParen === -1 || afterParen === -1) return false; + + // Set the selection to the text between the parenthesis + const parenContent = text.substring(beforeParen + 1, selectionStart + afterParen); + const lastColon = parenContent.lastIndexOf(":"); + selectionStart = beforeParen + 1; + selectionEnd = selectionStart + lastColon; + target.setSelectionRange(selectionStart, selectionEnd); + return true; + } + + function selectCurrentWord() { + if (selectionStart !== selectionEnd) return false; + const delimiters = opts.keyedit_delimiters + " \r\n\t"; + + // seek backward until to find beggining + while (!delimiters.includes(text[selectionStart - 1]) && selectionStart > 0) { + selectionStart--; + } + + // seek forward to find end + while (!delimiters.includes(text[selectionEnd]) && selectionEnd < text.length) { + selectionEnd++; + } + + target.setSelectionRange(selectionStart, selectionEnd); + return true; + } + + // If the user hasn't selected anything, let's select their current parenthesis block or word + if (!selectCurrentParenthesisBlock('<', '>') && !selectCurrentParenthesisBlock('(', ')')) { + selectCurrentWord(); + } + + event.preventDefault(); + + var closeCharacter = ')'; + var delta = opts.keyedit_precision_attention; + + if (selectionStart > 0 && text[selectionStart - 1] == '<') { + closeCharacter = '>'; + delta = opts.keyedit_precision_extra; + } else if (selectionStart == 0 || text[selectionStart - 1] != "(") { + + // do not include spaces at the end + while (selectionEnd > selectionStart && text[selectionEnd - 1] == ' ') { + selectionEnd -= 1; + } + if (selectionStart == selectionEnd) { + return; + } + + text = text.slice(0, selectionStart) + "(" + text.slice(selectionStart, selectionEnd) + ":1.0)" + text.slice(selectionEnd); + + selectionStart += 1; + selectionEnd += 1; + } + + var end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1; + var weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end)); + if (isNaN(weight)) return; + + weight += isPlus ? delta : -delta; + weight = parseFloat(weight.toPrecision(12)); + if (String(weight).length == 1) weight += ".0"; + + if (closeCharacter == ')' && weight == 1) { + text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + 5); + selectionStart--; + selectionEnd--; + } else { + text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + 1 + end - 1); + } + + target.focus(); + target.value = text; + target.selectionStart = selectionStart; + target.selectionEnd = selectionEnd; + + updateInput(target); +} + +addEventListener('keydown', (event) => { + keyupEditAttention(event); +}); diff --git a/javascript/extensions.js b/javascript/extensions.js index 2a2d2f8e79e..efeaf3a5b66 100644 --- a/javascript/extensions.js +++ b/javascript/extensions.js @@ -1,71 +1,74 @@ - -function extensions_apply(_disabled_list, _update_list, disable_all){ - var disable = [] - var update = [] - - gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ - if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substring(7)) - - if(x.name.startsWith("update_") && x.checked) - update.push(x.name.substring(7)) - }) - - restart_reload() - - return [JSON.stringify(disable), JSON.stringify(update), disable_all] -} - -function extensions_check(){ - var disable = [] - - gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ - if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substring(7)) - }) - - gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x){ - x.innerHTML = "Loading..." - }) - - - var id = randomId() - requestProgress(id, gradioApp().getElementById('extensions_installed_top'), null, function(){ - - }) - - return [id, JSON.stringify(disable)] -} - -function install_extension_from_index(button, url){ - button.disabled = "disabled" - button.value = "Installing..." - - var textarea = gradioApp().querySelector('#extension_to_install textarea') - textarea.value = url - updateInput(textarea) - - gradioApp().querySelector('#install_extension_button').click() -} - -function config_state_confirm_restore(_, config_state_name, config_restore_type) { - if (config_state_name == "Current") { - return [false, config_state_name, config_restore_type]; - } - let restored = ""; - if (config_restore_type == "extensions") { - restored = "all saved extension versions"; - } else if (config_restore_type == "webui") { - restored = "the webui version"; - } else { - restored = "the webui version and all saved extension versions"; - } - let confirmed = confirm("Are you sure you want to restore from this state?\nThis will reset " + restored + "."); - if (confirmed) { - restart_reload(); - gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x){ - x.innerHTML = "Loading..." - }) - } - return [confirmed, config_state_name, config_restore_type]; -} + +function extensions_apply(_disabled_list, _update_list, disable_all) { + var disable = []; + var update = []; + + gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x) { + if (x.name.startsWith("enable_") && !x.checked) { + disable.push(x.name.substring(7)); + } + + if (x.name.startsWith("update_") && x.checked) { + update.push(x.name.substring(7)); + } + }); + + restart_reload(); + + return [JSON.stringify(disable), JSON.stringify(update), disable_all]; +} + +function extensions_check() { + var disable = []; + + gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x) { + if (x.name.startsWith("enable_") && !x.checked) { + disable.push(x.name.substring(7)); + } + }); + + gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x) { + x.innerHTML = "Loading..."; + }); + + + var id = randomId(); + requestProgress(id, gradioApp().getElementById('extensions_installed_top'), null, function() { + + }); + + return [id, JSON.stringify(disable)]; +} + +function install_extension_from_index(button, url) { + button.disabled = "disabled"; + button.value = "Installing..."; + + var textarea = gradioApp().querySelector('#extension_to_install textarea'); + textarea.value = url; + updateInput(textarea); + + gradioApp().querySelector('#install_extension_button').click(); +} + +function config_state_confirm_restore(_, config_state_name, config_restore_type) { + if (config_state_name == "Current") { + return [false, config_state_name, config_restore_type]; + } + let restored = ""; + if (config_restore_type == "extensions") { + restored = "all saved extension versions"; + } else if (config_restore_type == "webui") { + restored = "the webui version"; + } else { + restored = "the webui version and all saved extension versions"; + } + let confirmed = confirm("Are you sure you want to restore from this state?\nThis will reset " + restored + "."); + if (confirmed) { + restart_reload(); + gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x) { + x.innerHTML = "Loading..."; + }); + } + return [confirmed, config_state_name, config_restore_type]; +} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 4d9a522cd9d..0c80fa74c66 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -1,205 +1,215 @@ -function setupExtraNetworksForTab(tabname){ - gradioApp().querySelector('#'+tabname+'_extra_tabs').classList.add('extra-networks') - - var tabs = gradioApp().querySelector('#'+tabname+'_extra_tabs > div') - var search = gradioApp().querySelector('#'+tabname+'_extra_search textarea') - var refresh = gradioApp().getElementById(tabname+'_extra_refresh') - - search.classList.add('search') - tabs.appendChild(search) - tabs.appendChild(refresh) - - var applyFilter = function(){ - var searchTerm = search.value.toLowerCase() - - gradioApp().querySelectorAll('#'+tabname+'_extra_tabs div.card').forEach(function(elem){ - var searchOnly = elem.querySelector('.search_only') - var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase() - - var visible = text.indexOf(searchTerm) != -1 - - if(searchOnly && searchTerm.length < 4){ - visible = false - } - - elem.style.display = visible ? "" : "none" - }) - } - - search.addEventListener("input", applyFilter); - applyFilter(); - - extraNetworksApplyFilter[tabname] = applyFilter; -} - -function applyExtraNetworkFilter(tabname){ - setTimeout(extraNetworksApplyFilter[tabname], 1); -} - -var extraNetworksApplyFilter = {} -var activePromptTextarea = {}; - -function setupExtraNetworks(){ - setupExtraNetworksForTab('txt2img') - setupExtraNetworksForTab('img2img') - - function registerPrompt(tabname, id){ - var textarea = gradioApp().querySelector("#" + id + " > label > textarea"); - - if (! activePromptTextarea[tabname]){ - activePromptTextarea[tabname] = textarea - } - - textarea.addEventListener("focus", function(){ - activePromptTextarea[tabname] = textarea; - }); - } - - registerPrompt('txt2img', 'txt2img_prompt') - registerPrompt('txt2img', 'txt2img_neg_prompt') - registerPrompt('img2img', 'img2img_prompt') - registerPrompt('img2img', 'img2img_neg_prompt') -} - -onUiLoaded(setupExtraNetworks) - -var re_extranet = /<([^:]+:[^:]+):[\d\.]+>/; -var re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; - -function tryToRemoveExtraNetworkFromPrompt(textarea, text){ - var m = text.match(re_extranet) - var replaced = false - var newTextareaText - if(m) { - var partToSearch = m[1] - newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found){ - m = found.match(re_extranet); - if(m[1] == partToSearch){ - replaced = true; - return "" - } - return found; - }) - } else { - newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found){ - if(found == text) { - replaced = true; - return "" - } - return found; - }) - } - - if(replaced){ - textarea.value = newTextareaText - return true; - } - - return false -} - -function cardClicked(tabname, textToAdd, allowNegativePrompt){ - var textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector("#" + tabname + "_prompt > label > textarea") - - if(! tryToRemoveExtraNetworkFromPrompt(textarea, textToAdd)){ - textarea.value = textarea.value + opts.extra_networks_add_text_separator + textToAdd - } - - updateInput(textarea) -} - -function saveCardPreview(event, tabname, filename){ - var textarea = gradioApp().querySelector("#" + tabname + '_preview_filename > label > textarea') - var button = gradioApp().getElementById(tabname + '_save_preview') - - textarea.value = filename - updateInput(textarea) - - button.click() - - event.stopPropagation() - event.preventDefault() -} - -function extraNetworksSearchButton(tabs_id, event){ - var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > div > textarea') - var button = event.target - var text = button.classList.contains("search-all") ? "" : button.textContent.trim() - - searchTextarea.value = text - updateInput(searchTextarea) -} - -var globalPopup = null; -var globalPopupInner = null; -function popup(contents){ - if(! globalPopup){ - globalPopup = document.createElement('div') - globalPopup.onclick = function(){ globalPopup.style.display = "none"; }; - globalPopup.classList.add('global-popup'); - - var close = document.createElement('div') - close.classList.add('global-popup-close'); - close.onclick = function(){ globalPopup.style.display = "none"; }; - close.title = "Close"; - globalPopup.appendChild(close) - - globalPopupInner = document.createElement('div') - globalPopupInner.onclick = function(event){ event.stopPropagation(); return false; }; - globalPopupInner.classList.add('global-popup-inner'); - globalPopup.appendChild(globalPopupInner) - - gradioApp().appendChild(globalPopup); - } - - globalPopupInner.innerHTML = ''; - globalPopupInner.appendChild(contents); - - globalPopup.style.display = "flex"; -} - -function extraNetworksShowMetadata(text){ - var elem = document.createElement('pre') - elem.classList.add('popup-metadata'); - elem.textContent = text; - - popup(elem); -} - -function requestGet(url, data, handler, errorHandler){ - var xhr = new XMLHttpRequest(); - var args = Object.keys(data).map(function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }).join('&') - xhr.open("GET", url + "?" + args, true); - - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - try { - var js = JSON.parse(xhr.responseText); - handler(js) - } catch (error) { - console.error(error); - errorHandler() - } - } else{ - errorHandler() - } - } - }; - var js = JSON.stringify(data); - xhr.send(js); -} - -function extraNetworksRequestMetadata(event, extraPage, cardName){ - var showError = function(){ extraNetworksShowMetadata("there was an error getting metadata"); } - - requestGet("./sd_extra_networks/metadata", {"page": extraPage, "item": cardName}, function(data){ - if(data && data.metadata){ - extraNetworksShowMetadata(data.metadata) - } else{ - showError() - } - }, showError) - - event.stopPropagation() -} +function setupExtraNetworksForTab(tabname) { + gradioApp().querySelector('#' + tabname + '_extra_tabs').classList.add('extra-networks'); + + var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div'); + var search = gradioApp().querySelector('#' + tabname + '_extra_search textarea'); + var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); + + search.classList.add('search'); + tabs.appendChild(search); + tabs.appendChild(refresh); + + var applyFilter = function() { + var searchTerm = search.value.toLowerCase(); + + gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { + var searchOnly = elem.querySelector('.search_only'); + var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase(); + + var visible = text.indexOf(searchTerm) != -1; + + if (searchOnly && searchTerm.length < 4) { + visible = false; + } + + elem.style.display = visible ? "" : "none"; + }); + }; + + search.addEventListener("input", applyFilter); + applyFilter(); + + extraNetworksApplyFilter[tabname] = applyFilter; +} + +function applyExtraNetworkFilter(tabname) { + setTimeout(extraNetworksApplyFilter[tabname], 1); +} + +var extraNetworksApplyFilter = {}; +var activePromptTextarea = {}; + +function setupExtraNetworks() { + setupExtraNetworksForTab('txt2img'); + setupExtraNetworksForTab('img2img'); + + function registerPrompt(tabname, id) { + var textarea = gradioApp().querySelector("#" + id + " > label > textarea"); + + if (!activePromptTextarea[tabname]) { + activePromptTextarea[tabname] = textarea; + } + + textarea.addEventListener("focus", function() { + activePromptTextarea[tabname] = textarea; + }); + } + + registerPrompt('txt2img', 'txt2img_prompt'); + registerPrompt('txt2img', 'txt2img_neg_prompt'); + registerPrompt('img2img', 'img2img_prompt'); + registerPrompt('img2img', 'img2img_neg_prompt'); +} + +onUiLoaded(setupExtraNetworks); + +var re_extranet = /<([^:]+:[^:]+):[\d\.]+>/; +var re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; + +function tryToRemoveExtraNetworkFromPrompt(textarea, text) { + var m = text.match(re_extranet); + var replaced = false; + var newTextareaText; + if (m) { + var partToSearch = m[1]; + newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found) { + m = found.match(re_extranet); + if (m[1] == partToSearch) { + replaced = true; + return ""; + } + return found; + }); + } else { + newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) { + if (found == text) { + replaced = true; + return ""; + } + return found; + }); + } + + if (replaced) { + textarea.value = newTextareaText; + return true; + } + + return false; +} + +function cardClicked(tabname, textToAdd, allowNegativePrompt) { + var textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector("#" + tabname + "_prompt > label > textarea"); + + if (!tryToRemoveExtraNetworkFromPrompt(textarea, textToAdd)) { + textarea.value = textarea.value + opts.extra_networks_add_text_separator + textToAdd; + } + + updateInput(textarea); +} + +function saveCardPreview(event, tabname, filename) { + var textarea = gradioApp().querySelector("#" + tabname + '_preview_filename > label > textarea'); + var button = gradioApp().getElementById(tabname + '_save_preview'); + + textarea.value = filename; + updateInput(textarea); + + button.click(); + + event.stopPropagation(); + event.preventDefault(); +} + +function extraNetworksSearchButton(tabs_id, event) { + var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > div > textarea'); + var button = event.target; + var text = button.classList.contains("search-all") ? "" : button.textContent.trim(); + + searchTextarea.value = text; + updateInput(searchTextarea); +} + +var globalPopup = null; +var globalPopupInner = null; +function popup(contents) { + if (!globalPopup) { + globalPopup = document.createElement('div'); + globalPopup.onclick = function() { + globalPopup.style.display = "none"; + }; + globalPopup.classList.add('global-popup'); + + var close = document.createElement('div'); + close.classList.add('global-popup-close'); + close.onclick = function() { + globalPopup.style.display = "none"; + }; + close.title = "Close"; + globalPopup.appendChild(close); + + globalPopupInner = document.createElement('div'); + globalPopupInner.onclick = function(event) { + event.stopPropagation(); return false; + }; + globalPopupInner.classList.add('global-popup-inner'); + globalPopup.appendChild(globalPopupInner); + + gradioApp().appendChild(globalPopup); + } + + globalPopupInner.innerHTML = ''; + globalPopupInner.appendChild(contents); + + globalPopup.style.display = "flex"; +} + +function extraNetworksShowMetadata(text) { + var elem = document.createElement('pre'); + elem.classList.add('popup-metadata'); + elem.textContent = text; + + popup(elem); +} + +function requestGet(url, data, handler, errorHandler) { + var xhr = new XMLHttpRequest(); + var args = Object.keys(data).map(function(k) { + return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]); + }).join('&'); + xhr.open("GET", url + "?" + args, true); + + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + try { + var js = JSON.parse(xhr.responseText); + handler(js); + } catch (error) { + console.error(error); + errorHandler(); + } + } else { + errorHandler(); + } + } + }; + var js = JSON.stringify(data); + xhr.send(js); +} + +function extraNetworksRequestMetadata(event, extraPage, cardName) { + var showError = function() { + extraNetworksShowMetadata("there was an error getting metadata"); + }; + + requestGet("./sd_extra_networks/metadata", {page: extraPage, item: cardName}, function(data) { + if (data && data.metadata) { + extraNetworksShowMetadata(data.metadata); + } else { + showError(); + } + }, showError); + + event.stopPropagation(); +} diff --git a/javascript/generationParams.js b/javascript/generationParams.js index ef64ee2e536..f9e84e70667 100644 --- a/javascript/generationParams.js +++ b/javascript/generationParams.js @@ -1,33 +1,35 @@ // attaches listeners to the txt2img and img2img galleries to update displayed generation param text when the image changes let txt2img_gallery, img2img_gallery, modal = undefined; -onUiUpdate(function(){ - if (!txt2img_gallery) { - txt2img_gallery = attachGalleryListeners("txt2img") - } - if (!img2img_gallery) { - img2img_gallery = attachGalleryListeners("img2img") - } - if (!modal) { - modal = gradioApp().getElementById('lightboxModal') - modalObserver.observe(modal, { attributes : true, attributeFilter : ['style'] }); - } +onUiUpdate(function() { + if (!txt2img_gallery) { + txt2img_gallery = attachGalleryListeners("txt2img"); + } + if (!img2img_gallery) { + img2img_gallery = attachGalleryListeners("img2img"); + } + if (!modal) { + modal = gradioApp().getElementById('lightboxModal'); + modalObserver.observe(modal, { attributes: true, attributeFilter: ['style'] }); + } }); let modalObserver = new MutationObserver(function(mutations) { - mutations.forEach(function(mutationRecord) { - let selectedTab = gradioApp().querySelector('#tabs div button.selected')?.innerText - if (mutationRecord.target.style.display === 'none' && (selectedTab === 'txt2img' || selectedTab === 'img2img')) - gradioApp().getElementById(selectedTab+"_generation_info_button")?.click() - }); + mutations.forEach(function(mutationRecord) { + let selectedTab = gradioApp().querySelector('#tabs div button.selected')?.innerText; + if (mutationRecord.target.style.display === 'none' && (selectedTab === 'txt2img' || selectedTab === 'img2img')) { + gradioApp().getElementById(selectedTab + "_generation_info_button")?.click(); + } + }); }); function attachGalleryListeners(tab_name) { - var gallery = gradioApp().querySelector('#'+tab_name+'_gallery') - gallery?.addEventListener('click', () => gradioApp().getElementById(tab_name+"_generation_info_button").click()); - gallery?.addEventListener('keydown', (e) => { - if (e.keyCode == 37 || e.keyCode == 39) // left or right arrow - gradioApp().getElementById(tab_name+"_generation_info_button").click() - }); - return gallery; + var gallery = gradioApp().querySelector('#' + tab_name + '_gallery'); + gallery?.addEventListener('click', () => gradioApp().getElementById(tab_name + "_generation_info_button").click()); + gallery?.addEventListener('keydown', (e) => { + if (e.keyCode == 37 || e.keyCode == 39) { // left or right arrow + gradioApp().getElementById(tab_name + "_generation_info_button").click(); + } + }); + return gallery; } diff --git a/javascript/hints.js b/javascript/hints.js index 3746df99fa7..477b7d8091a 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -3,14 +3,14 @@ titles = { "Sampling steps": "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results", "Sampling method": "Which algorithm to use to produce the image", - "GFPGAN": "Restore low quality faces using GFPGAN neural network", - "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help", - "DDIM": "Denoising Diffusion Implicit Models - best at inpainting", - "UniPC": "Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models", - "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", - - "Batch count": "How many batches of images to create (has no impact on generation performance or VRAM usage)", - "Batch size": "How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)", + "GFPGAN": "Restore low quality faces using GFPGAN neural network", + "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help", + "DDIM": "Denoising Diffusion Implicit Models - best at inpainting", + "UniPC": "Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models", + "DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution", + + "Batch count": "How many batches of images to create (has no impact on generation performance or VRAM usage)", + "Batch size": "How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)", "CFG Scale": "Classifier Free Guidance Scale - how strongly the image should conform to prompt - lower values produce more creative results", "Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result", "\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time", @@ -40,7 +40,7 @@ titles = { "Inpaint at full resolution": "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image", "Denoising strength": "Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies.", - + "Skip": "Stop processing current image and continue processing.", "Interrupt": "Stop processing images and return any results accumulated so far.", "Save": "Write image to a directory (default - log/images) and generation parameters into csv file.", @@ -96,7 +96,7 @@ titles = { "Add difference": "Result = A + (B - C) * M", "No interpolation": "Result = A", - "Initialization text": "If the number of tokens is more than the number of vectors, some may be skipped.\nLeave the textbox empty to start with zeroed out vectors", + "Initialization text": "If the number of tokens is more than the number of vectors, some may be skipped.\nLeave the textbox empty to start with zeroed out vectors", "Learning rate": "How fast should training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.", "Clip skip": "Early stopping parameter for CLIP model; 1 is stop at last layer as usual, 2 is stop at penultimate layer, etc.", @@ -113,38 +113,38 @@ titles = { "Discard weights with matching name": "Regular expression; if weights's name matches it, the weights is not written to the resulting checkpoint. Use ^model_ema to discard EMA weights.", "Extra networks tab order": "Comma-separated list of tab names; tabs listed here will appear in the extra networks UI first and in order lsited.", "Negative Guidance minimum sigma": "Skip negative prompt for steps where image is already mostly denoised; the higher this value, the more skips there will be; provides increased performance in exchange for minor quality reduction." -} +}; -onUiUpdate(function(){ - gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){ - if (span.title) return; // already has a title +onUiUpdate(function() { + gradioApp().querySelectorAll('span, button, select, p').forEach(function(span) { + if (span.title) return; // already has a title - let tooltip = localization[titles[span.textContent]] || titles[span.textContent]; + let tooltip = localization[titles[span.textContent]] || titles[span.textContent]; - if(!tooltip){ - tooltip = localization[titles[span.value]] || titles[span.value]; - } + if (!tooltip) { + tooltip = localization[titles[span.value]] || titles[span.value]; + } - if(!tooltip){ - for (const c of span.classList) { - if (c in titles) { - tooltip = localization[titles[c]] || titles[c]; - break; - } - } - } + if (!tooltip) { + for (const c of span.classList) { + if (c in titles) { + tooltip = localization[titles[c]] || titles[c]; + break; + } + } + } - if(tooltip){ - span.title = tooltip; - } - }) + if (tooltip) { + span.title = tooltip; + } + }); - gradioApp().querySelectorAll('select').forEach(function(select){ - if (select.onchange != null) return; + gradioApp().querySelectorAll('select').forEach(function(select) { + if (select.onchange != null) return; - select.onchange = function(){ + select.onchange = function() { select.title = localization[titles[select.value]] || titles[select.value] || ""; - } - }) -}) + }; + }); +}); diff --git a/javascript/hires_fix.js b/javascript/hires_fix.js index 48196be40f7..0d04ab3b424 100644 --- a/javascript/hires_fix.js +++ b/javascript/hires_fix.js @@ -1,18 +1,18 @@ - -function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y){ - function setInactive(elem, inactive){ - elem.classList.toggle('inactive', !!inactive) - } - - var hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale') - var hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x') - var hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y') - - gradioApp().getElementById('txt2img_hires_fix_row2').style.display = opts.use_old_hires_fix_width_height ? "none" : "" - - setInactive(hrUpscaleBy, opts.use_old_hires_fix_width_height || hr_resize_x > 0 || hr_resize_y > 0) - setInactive(hrResizeX, opts.use_old_hires_fix_width_height || hr_resize_x == 0) - setInactive(hrResizeY, opts.use_old_hires_fix_width_height || hr_resize_y == 0) - - return [enable, width, height, hr_scale, hr_resize_x, hr_resize_y] -} + +function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y) { + function setInactive(elem, inactive) { + elem.classList.toggle('inactive', !!inactive); + } + + var hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale'); + var hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x'); + var hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y'); + + gradioApp().getElementById('txt2img_hires_fix_row2').style.display = opts.use_old_hires_fix_width_height ? "none" : ""; + + setInactive(hrUpscaleBy, opts.use_old_hires_fix_width_height || hr_resize_x > 0 || hr_resize_y > 0); + setInactive(hrResizeX, opts.use_old_hires_fix_width_height || hr_resize_x == 0); + setInactive(hrResizeY, opts.use_old_hires_fix_width_height || hr_resize_y == 0); + + return [enable, width, height, hr_scale, hr_resize_x, hr_resize_y]; +} diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js index a612705d270..91a6377b56b 100644 --- a/javascript/imageMaskFix.js +++ b/javascript/imageMaskFix.js @@ -4,17 +4,17 @@ */ function imageMaskResize() { const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas'); - if ( ! canvases.length ) { - canvases_fixed = false; // TODO: this is unused..? - window.removeEventListener( 'resize', imageMaskResize ); - return; + if (!canvases.length) { + canvases_fixed = false; // TODO: this is unused..? + window.removeEventListener('resize', imageMaskResize); + return; } const wrapper = canvases[0].closest('.touch-none'); const previewImage = wrapper.previousElementSibling; - if ( ! previewImage.complete ) { - previewImage.addEventListener( 'load', imageMaskResize); + if (!previewImage.complete) { + previewImage.addEventListener('load', imageMaskResize); return; } @@ -24,15 +24,15 @@ function imageMaskResize() { const nh = previewImage.naturalHeight; const portrait = nh > nw; - const wW = Math.min(w, portrait ? h/nh*nw : w/nw*nw); - const wH = Math.min(h, portrait ? h/nh*nh : w/nw*nh); + const wW = Math.min(w, portrait ? h / nh * nw : w / nw * nw); + const wH = Math.min(h, portrait ? h / nh * nh : w / nw * nh); wrapper.style.width = `${wW}px`; wrapper.style.height = `${wH}px`; wrapper.style.left = `0px`; wrapper.style.top = `0px`; - canvases.forEach( c => { + canvases.forEach(c => { c.style.width = c.style.height = ''; c.style.maxWidth = '100%'; c.style.maxHeight = '100%'; @@ -41,4 +41,4 @@ function imageMaskResize() { } onUiUpdate(imageMaskResize); -window.addEventListener( 'resize', imageMaskResize); +window.addEventListener('resize', imageMaskResize); diff --git a/javascript/imageParams.js b/javascript/imageParams.js index 64aee93b7c1..057e2d3924e 100644 --- a/javascript/imageParams.js +++ b/javascript/imageParams.js @@ -1,4 +1,4 @@ -window.onload = (function(){ +window.onload = (function() { window.addEventListener('drop', e => { const target = e.composedPath()[0]; if (target.placeholder.indexOf("Prompt") == -1) return; @@ -10,7 +10,7 @@ window.onload = (function(){ const imgParent = gradioApp().getElementById(prompt_target); const files = e.dataTransfer.files; const fileInput = imgParent.querySelector('input[type="file"]'); - if ( fileInput ) { + if (fileInput) { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); } diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 32066ab8ffa..ecd12379380 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -5,24 +5,24 @@ function closeModal() { function showModal(event) { const source = event.target || event.srcElement; - const modalImage = gradioApp().getElementById("modalImage") - const lb = gradioApp().getElementById("lightboxModal") - modalImage.src = source.src + const modalImage = gradioApp().getElementById("modalImage"); + const lb = gradioApp().getElementById("lightboxModal"); + modalImage.src = source.src; if (modalImage.style.display === 'none') { lb.style.setProperty('background-image', 'url(' + source.src + ')'); } lb.style.display = "flex"; - lb.focus() + lb.focus(); - const tabTxt2Img = gradioApp().getElementById("tab_txt2img") - const tabImg2Img = gradioApp().getElementById("tab_img2img") + const tabTxt2Img = gradioApp().getElementById("tab_txt2img"); + const tabImg2Img = gradioApp().getElementById("tab_img2img"); // show the save button in modal only on txt2img or img2img tabs if (tabTxt2Img.style.display != "none" || tabImg2Img.style.display != "none") { - gradioApp().getElementById("modal_save").style.display = "inline" + gradioApp().getElementById("modal_save").style.display = "inline"; } else { - gradioApp().getElementById("modal_save").style.display = "none" + gradioApp().getElementById("modal_save").style.display = "none"; } - event.stopPropagation() + event.stopPropagation(); } function negmod(n, m) { @@ -30,14 +30,14 @@ function negmod(n, m) { } function updateOnBackgroundChange() { - const modalImage = gradioApp().getElementById("modalImage") + const modalImage = gradioApp().getElementById("modalImage"); if (modalImage && modalImage.offsetParent) { let currentButton = selected_gallery_button(); if (currentButton?.children?.length > 0 && modalImage.src != currentButton.children[0].src) { modalImage.src = currentButton.children[0].src; if (modalImage.style.display === 'none') { - modal.style.setProperty('background-image', `url(${modalImage.src})`) + modal.style.setProperty('background-image', `url(${modalImage.src})`); } } } @@ -49,108 +49,109 @@ function modalImageSwitch(offset) { if (galleryButtons.length > 1) { var currentButton = selected_gallery_button(); - var result = -1 + var result = -1; galleryButtons.forEach(function(v, i) { if (v == currentButton) { - result = i + result = i; } - }) + }); if (result != -1) { - var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)] - nextButton.click() + var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)]; + nextButton.click(); const modalImage = gradioApp().getElementById("modalImage"); const modal = gradioApp().getElementById("lightboxModal"); modalImage.src = nextButton.children[0].src; if (modalImage.style.display === 'none') { - modal.style.setProperty('background-image', `url(${modalImage.src})`) + modal.style.setProperty('background-image', `url(${modalImage.src})`); } setTimeout(function() { - modal.focus() - }, 10) + modal.focus(); + }, 10); } } } -function saveImage(){ - const tabTxt2Img = gradioApp().getElementById("tab_txt2img") - const tabImg2Img = gradioApp().getElementById("tab_img2img") - const saveTxt2Img = "save_txt2img" - const saveImg2Img = "save_img2img" +function saveImage() { + const tabTxt2Img = gradioApp().getElementById("tab_txt2img"); + const tabImg2Img = gradioApp().getElementById("tab_img2img"); + const saveTxt2Img = "save_txt2img"; + const saveImg2Img = "save_img2img"; if (tabTxt2Img.style.display != "none") { - gradioApp().getElementById(saveTxt2Img).click() + gradioApp().getElementById(saveTxt2Img).click(); } else if (tabImg2Img.style.display != "none") { - gradioApp().getElementById(saveImg2Img).click() + gradioApp().getElementById(saveImg2Img).click(); } else { - console.error("missing implementation for saving modal of this type") + console.error("missing implementation for saving modal of this type"); } } function modalSaveImage(event) { - saveImage() - event.stopPropagation() + saveImage(); + event.stopPropagation(); } function modalNextImage(event) { - modalImageSwitch(1) - event.stopPropagation() + modalImageSwitch(1); + event.stopPropagation(); } function modalPrevImage(event) { - modalImageSwitch(-1) - event.stopPropagation() + modalImageSwitch(-1); + event.stopPropagation(); } function modalKeyHandler(event) { switch (event.key) { - case "s": - saveImage() - break; - case "ArrowLeft": - modalPrevImage(event) - break; - case "ArrowRight": - modalNextImage(event) - break; - case "Escape": - closeModal(); - break; + case "s": + saveImage(); + break; + case "ArrowLeft": + modalPrevImage(event); + break; + case "ArrowRight": + modalNextImage(event); + break; + case "Escape": + closeModal(); + break; } } function setupImageForLightbox(e) { - if (e.dataset.modded) - return; + if (e.dataset.modded) { + return; + } - e.dataset.modded = true; - e.style.cursor='pointer' - e.style.userSelect='none' + e.dataset.modded = true; + e.style.cursor = 'pointer'; + e.style.userSelect = 'none'; - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 + var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; - // For Firefox, listening on click first switched to next image then shows the lightbox. - // If you know how to fix this without switching to mousedown event, please. - // For other browsers the event is click to make it possiblr to drag picture. - var event = isFirefox ? 'mousedown' : 'click' + // For Firefox, listening on click first switched to next image then shows the lightbox. + // If you know how to fix this without switching to mousedown event, please. + // For other browsers the event is click to make it possiblr to drag picture. + var event = isFirefox ? 'mousedown' : 'click'; - e.addEventListener(event, function (evt) { - if(!opts.js_modal_lightbox || evt.button != 0) return; + e.addEventListener(event, function(evt) { + if (!opts.js_modal_lightbox || evt.button != 0) return; - modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed) - evt.preventDefault() - showModal(evt) - }, true); + modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed); + evt.preventDefault(); + showModal(evt); + }, true); } function modalZoomSet(modalImage, enable) { - if(modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable); + if (modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable); } function modalZoomToggle(event) { var modalImage = gradioApp().getElementById("modalImage"); - modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen')) - event.stopPropagation() + modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen')); + event.stopPropagation(); } function modalTileImageToggle(event) { @@ -159,99 +160,99 @@ function modalTileImageToggle(event) { const isTiling = modalImage.style.display === 'none'; if (isTiling) { modalImage.style.display = 'block'; - modal.style.setProperty('background-image', 'none') + modal.style.setProperty('background-image', 'none'); } else { modalImage.style.display = 'none'; - modal.style.setProperty('background-image', `url(${modalImage.src})`) + modal.style.setProperty('background-image', `url(${modalImage.src})`); } - event.stopPropagation() + event.stopPropagation(); } function galleryImageHandler(e) { //if (e && e.parentElement.tagName == 'BUTTON') { - e.onclick = showGalleryImage; + e.onclick = showGalleryImage; //} } onUiUpdate(function() { - var fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img') + var fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img'); if (fullImg_preview != null) { fullImg_preview.forEach(setupImageForLightbox); } updateOnBackgroundChange(); -}) +}); document.addEventListener("DOMContentLoaded", function() { //const modalFragment = document.createDocumentFragment(); - const modal = document.createElement('div') + const modal = document.createElement('div'); modal.onclick = closeModal; modal.id = "lightboxModal"; - modal.tabIndex = 0 - modal.addEventListener('keydown', modalKeyHandler, true) + modal.tabIndex = 0; + modal.addEventListener('keydown', modalKeyHandler, true); - const modalControls = document.createElement('div') + const modalControls = document.createElement('div'); modalControls.className = 'modalControls gradio-container'; modal.append(modalControls); - const modalZoom = document.createElement('span') + const modalZoom = document.createElement('span'); modalZoom.className = 'modalZoom cursor'; - modalZoom.innerHTML = '⤡' - modalZoom.addEventListener('click', modalZoomToggle, true) + modalZoom.innerHTML = '⤡'; + modalZoom.addEventListener('click', modalZoomToggle, true); modalZoom.title = "Toggle zoomed view"; - modalControls.appendChild(modalZoom) + modalControls.appendChild(modalZoom); - const modalTileImage = document.createElement('span') + const modalTileImage = document.createElement('span'); modalTileImage.className = 'modalTileImage cursor'; - modalTileImage.innerHTML = '⊞' - modalTileImage.addEventListener('click', modalTileImageToggle, true) + modalTileImage.innerHTML = '⊞'; + modalTileImage.addEventListener('click', modalTileImageToggle, true); modalTileImage.title = "Preview tiling"; - modalControls.appendChild(modalTileImage) + modalControls.appendChild(modalTileImage); - const modalSave = document.createElement("span") - modalSave.className = "modalSave cursor" - modalSave.id = "modal_save" - modalSave.innerHTML = "🖫" - modalSave.addEventListener("click", modalSaveImage, true) - modalSave.title = "Save Image(s)" - modalControls.appendChild(modalSave) + const modalSave = document.createElement("span"); + modalSave.className = "modalSave cursor"; + modalSave.id = "modal_save"; + modalSave.innerHTML = "🖫"; + modalSave.addEventListener("click", modalSaveImage, true); + modalSave.title = "Save Image(s)"; + modalControls.appendChild(modalSave); - const modalClose = document.createElement('span') + const modalClose = document.createElement('span'); modalClose.className = 'modalClose cursor'; - modalClose.innerHTML = '×' + modalClose.innerHTML = '×'; modalClose.onclick = closeModal; modalClose.title = "Close image viewer"; - modalControls.appendChild(modalClose) + modalControls.appendChild(modalClose); - const modalImage = document.createElement('img') + const modalImage = document.createElement('img'); modalImage.id = 'modalImage'; modalImage.onclick = closeModal; - modalImage.tabIndex = 0 - modalImage.addEventListener('keydown', modalKeyHandler, true) - modal.appendChild(modalImage) + modalImage.tabIndex = 0; + modalImage.addEventListener('keydown', modalKeyHandler, true); + modal.appendChild(modalImage); - const modalPrev = document.createElement('a') + const modalPrev = document.createElement('a'); modalPrev.className = 'modalPrev'; - modalPrev.innerHTML = '❮' - modalPrev.tabIndex = 0 + modalPrev.innerHTML = '❮'; + modalPrev.tabIndex = 0; modalPrev.addEventListener('click', modalPrevImage, true); - modalPrev.addEventListener('keydown', modalKeyHandler, true) - modal.appendChild(modalPrev) + modalPrev.addEventListener('keydown', modalKeyHandler, true); + modal.appendChild(modalPrev); - const modalNext = document.createElement('a') + const modalNext = document.createElement('a'); modalNext.className = 'modalNext'; - modalNext.innerHTML = '❯' - modalNext.tabIndex = 0 + modalNext.innerHTML = '❯'; + modalNext.tabIndex = 0; modalNext.addEventListener('click', modalNextImage, true); - modalNext.addEventListener('keydown', modalKeyHandler, true) + modalNext.addEventListener('keydown', modalKeyHandler, true); - modal.appendChild(modalNext) + modal.appendChild(modalNext); try { - gradioApp().appendChild(modal); - } catch (e) { - gradioApp().body.appendChild(modal); - } + gradioApp().appendChild(modal); + } catch (e) { + gradioApp().body.appendChild(modal); + } document.body.appendChild(modal); diff --git a/javascript/imageviewerGamepad.js b/javascript/imageviewerGamepad.js index 6297a12bc34..31d226dee74 100644 --- a/javascript/imageviewerGamepad.js +++ b/javascript/imageviewerGamepad.js @@ -1,7 +1,7 @@ window.addEventListener('gamepadconnected', (e) => { const index = e.gamepad.index; let isWaiting = false; - setInterval(async () => { + setInterval(async() => { if (!opts.js_modal_lightbox_gamepad || isWaiting) return; const gamepad = navigator.getGamepads()[index]; const xValue = gamepad.axes[0]; @@ -14,7 +14,7 @@ window.addEventListener('gamepadconnected', (e) => { } if (isWaiting) { await sleepUntil(() => { - const xValue = navigator.getGamepads()[index].axes[0] + const xValue = navigator.getGamepads()[index].axes[0]; if (xValue < 0.3 && xValue > -0.3) { return true; } diff --git a/javascript/localization.js b/javascript/localization.js index 86e5ca67cfb..3d043a9ad51 100644 --- a/javascript/localization.js +++ b/javascript/localization.js @@ -1,177 +1,177 @@ - -// localization = {} -- the dict with translations is created by the backend - -ignore_ids_for_localization={ - setting_sd_hypernetwork: 'OPTION', - setting_sd_model_checkpoint: 'OPTION', - setting_realesrgan_enabled_models: 'OPTION', - modelmerger_primary_model_name: 'OPTION', - modelmerger_secondary_model_name: 'OPTION', - modelmerger_tertiary_model_name: 'OPTION', - train_embedding: 'OPTION', - train_hypernetwork: 'OPTION', - txt2img_styles: 'OPTION', - img2img_styles: 'OPTION', - setting_random_artist_categories: 'SPAN', - setting_face_restoration_model: 'SPAN', - setting_realesrgan_enabled_models: 'SPAN', - extras_upscaler_1: 'SPAN', - extras_upscaler_2: 'SPAN', -} - -re_num = /^[\.\d]+$/ -re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u - -original_lines = {} -translated_lines = {} - -function hasLocalization() { - return window.localization && Object.keys(window.localization).length > 0; -} - -function textNodesUnder(el){ - var n, a=[], walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false); - while(n=walk.nextNode()) a.push(n); - return a; -} - -function canBeTranslated(node, text){ - if(! text) return false; - if(! node.parentElement) return false; - - var parentType = node.parentElement.nodeName - if(parentType=='SCRIPT' || parentType=='STYLE' || parentType=='TEXTAREA') return false; - - if (parentType=='OPTION' || parentType=='SPAN'){ - var pnode = node - for(var level=0; level<4; level++){ - pnode = pnode.parentElement - if(! pnode) break; - - if(ignore_ids_for_localization[pnode.id] == parentType) return false; - } - } - - if(re_num.test(text)) return false; - if(re_emoji.test(text)) return false; - return true -} - -function getTranslation(text){ - if(! text) return undefined - - if(translated_lines[text] === undefined){ - original_lines[text] = 1 - } - - tl = localization[text] - if(tl !== undefined){ - translated_lines[tl] = 1 - } - - return tl -} - -function processTextNode(node){ - var text = node.textContent.trim() - - if(! canBeTranslated(node, text)) return - - tl = getTranslation(text) - if(tl !== undefined){ - node.textContent = tl - } -} - -function processNode(node){ - if(node.nodeType == 3){ - processTextNode(node) - return - } - - if(node.title){ - tl = getTranslation(node.title) - if(tl !== undefined){ - node.title = tl - } - } - - if(node.placeholder){ - tl = getTranslation(node.placeholder) - if(tl !== undefined){ - node.placeholder = tl - } - } - - textNodesUnder(node).forEach(function(node){ - processTextNode(node) - }) -} - -function dumpTranslations(){ - if(!hasLocalization()) { - // If we don't have any localization, - // we will not have traversed the app to find - // original_lines, so do that now. - processNode(gradioApp()); - } - var dumped = {} - if (localization.rtl) { - dumped.rtl = true; - } - - for (const text in original_lines) { - if(dumped[text] !== undefined) continue; - dumped[text] = localization[text] || text; - } - - return dumped; -} - -function download_localization() { - var text = JSON.stringify(dumpTranslations(), null, 4) - - var element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); - element.setAttribute('download', "localization.json"); - element.style.display = 'none'; - document.body.appendChild(element); - - element.click(); - - document.body.removeChild(element); -} - -document.addEventListener("DOMContentLoaded", function () { - if (!hasLocalization()) { - return; - } - - onUiUpdate(function (m) { - m.forEach(function (mutation) { - mutation.addedNodes.forEach(function (node) { - processNode(node) - }) - }); - }) - - processNode(gradioApp()) - - if (localization.rtl) { // if the language is from right to left, - (new MutationObserver((mutations, observer) => { // wait for the style to load - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.tagName === 'STYLE') { - observer.disconnect(); - - for (const x of node.sheet.rules) { // find all rtl media rules - if (Array.from(x.media || []).includes('rtl')) { - x.media.appendMedium('all'); // enable them - } - } - } - }) - }); - })).observe(gradioApp(), { childList: true }); - } -}) + +// localization = {} -- the dict with translations is created by the backend + +ignore_ids_for_localization = { + setting_sd_hypernetwork: 'OPTION', + setting_sd_model_checkpoint: 'OPTION', + setting_realesrgan_enabled_models: 'OPTION', + modelmerger_primary_model_name: 'OPTION', + modelmerger_secondary_model_name: 'OPTION', + modelmerger_tertiary_model_name: 'OPTION', + train_embedding: 'OPTION', + train_hypernetwork: 'OPTION', + txt2img_styles: 'OPTION', + img2img_styles: 'OPTION', + setting_random_artist_categories: 'SPAN', + setting_face_restoration_model: 'SPAN', + setting_realesrgan_enabled_models: 'SPAN', + extras_upscaler_1: 'SPAN', + extras_upscaler_2: 'SPAN', +}; + +re_num = /^[\.\d]+$/; +re_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u; + +original_lines = {}; +translated_lines = {}; + +function hasLocalization() { + return window.localization && Object.keys(window.localization).length > 0; +} + +function textNodesUnder(el) { + var n, a = [], walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false); + while (n = walk.nextNode()) a.push(n); + return a; +} + +function canBeTranslated(node, text) { + if (!text) return false; + if (!node.parentElement) return false; + + var parentType = node.parentElement.nodeName; + if (parentType == 'SCRIPT' || parentType == 'STYLE' || parentType == 'TEXTAREA') return false; + + if (parentType == 'OPTION' || parentType == 'SPAN') { + var pnode = node; + for (var level = 0; level < 4; level++) { + pnode = pnode.parentElement; + if (!pnode) break; + + if (ignore_ids_for_localization[pnode.id] == parentType) return false; + } + } + + if (re_num.test(text)) return false; + if (re_emoji.test(text)) return false; + return true; +} + +function getTranslation(text) { + if (!text) return undefined; + + if (translated_lines[text] === undefined) { + original_lines[text] = 1; + } + + tl = localization[text]; + if (tl !== undefined) { + translated_lines[tl] = 1; + } + + return tl; +} + +function processTextNode(node) { + var text = node.textContent.trim(); + + if (!canBeTranslated(node, text)) return; + + tl = getTranslation(text); + if (tl !== undefined) { + node.textContent = tl; + } +} + +function processNode(node) { + if (node.nodeType == 3) { + processTextNode(node); + return; + } + + if (node.title) { + tl = getTranslation(node.title); + if (tl !== undefined) { + node.title = tl; + } + } + + if (node.placeholder) { + tl = getTranslation(node.placeholder); + if (tl !== undefined) { + node.placeholder = tl; + } + } + + textNodesUnder(node).forEach(function(node) { + processTextNode(node); + }); +} + +function dumpTranslations() { + if (!hasLocalization()) { + // If we don't have any localization, + // we will not have traversed the app to find + // original_lines, so do that now. + processNode(gradioApp()); + } + var dumped = {}; + if (localization.rtl) { + dumped.rtl = true; + } + + for (const text in original_lines) { + if (dumped[text] !== undefined) continue; + dumped[text] = localization[text] || text; + } + + return dumped; +} + +function download_localization() { + var text = JSON.stringify(dumpTranslations(), null, 4); + + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', "localization.json"); + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +document.addEventListener("DOMContentLoaded", function() { + if (!hasLocalization()) { + return; + } + + onUiUpdate(function(m) { + m.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + processNode(node); + }); + }); + }); + + processNode(gradioApp()); + + if (localization.rtl) { // if the language is from right to left, + (new MutationObserver((mutations, observer) => { // wait for the style to load + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.tagName === 'STYLE') { + observer.disconnect(); + + for (const x of node.sheet.rules) { // find all rtl media rules + if (Array.from(x.media || []).includes('rtl')) { + x.media.appendMedium('all'); // enable them + } + } + } + }); + }); + })).observe(gradioApp(), { childList: true }); + } +}); diff --git a/javascript/notification.js b/javascript/notification.js index 83fce1f86d1..a68a76f2514 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -4,14 +4,14 @@ let lastHeadImg = null; let notificationButton = null; -onUiUpdate(function(){ - if(notificationButton == null){ - notificationButton = gradioApp().getElementById('request_notifications') +onUiUpdate(function() { + if (notificationButton == null) { + notificationButton = gradioApp().getElementById('request_notifications'); - if(notificationButton != null){ + if (notificationButton != null) { notificationButton.addEventListener('click', () => { void Notification.requestPermission(); - },true); + }, true); } } @@ -42,7 +42,7 @@ onUiUpdate(function(){ } ); - notification.onclick = function(_){ + notification.onclick = function(_) { parent.focus(); this.close(); }; diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 8d2c3492403..cd273e4800a 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -1,29 +1,29 @@ // code related to showing and updating progressbar shown as the image is being made -function rememberGallerySelection(){ +function rememberGallerySelection() { } -function getGallerySelectedIndex(){ +function getGallerySelectedIndex() { } -function request(url, data, handler, errorHandler){ +function request(url, data, handler, errorHandler) { var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/json"); - xhr.onreadystatechange = function () { + xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { try { var js = JSON.parse(xhr.responseText); - handler(js) + handler(js); } catch (error) { console.error(error); - errorHandler() + errorHandler(); } - } else{ - errorHandler() + } else { + errorHandler(); } } }; @@ -31,147 +31,147 @@ function request(url, data, handler, errorHandler){ xhr.send(js); } -function pad2(x){ - return x<10 ? '0'+x : x +function pad2(x) { + return x < 10 ? '0' + x : x; } -function formatTime(secs){ - if(secs > 3600){ - return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60) - } else if(secs > 60){ - return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60) - } else{ - return Math.floor(secs) + "s" +function formatTime(secs) { + if (secs > 3600) { + return pad2(Math.floor(secs / 60 / 60)) + ":" + pad2(Math.floor(secs / 60) % 60) + ":" + pad2(Math.floor(secs) % 60); + } else if (secs > 60) { + return pad2(Math.floor(secs / 60)) + ":" + pad2(Math.floor(secs) % 60); + } else { + return Math.floor(secs) + "s"; } } -function setTitle(progress){ - var title = 'Stable Diffusion' +function setTitle(progress) { + var title = 'Stable Diffusion'; - if(opts.show_progress_in_title && progress){ + if (opts.show_progress_in_title && progress) { title = '[' + progress.trim() + '] ' + title; } - if(document.title != title){ + if (document.title != title) { document.title = title; } } -function randomId(){ - return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")" +function randomId() { + return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + ")"; } // starts sending progress requests to "/internal/progress" uri, creating progressbar above progressbarContainer element and // preview inside gallery element. Cleans up all created stuff when the task is over and calls atEnd. // calls onProgress every time there is a progress update -function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgress, inactivityTimeout=40){ - var dateStart = new Date() - var wasEverActive = false - var parentProgressbar = progressbarContainer.parentNode - var parentGallery = gallery ? gallery.parentNode : null - - var divProgress = document.createElement('div') - divProgress.className='progressDiv' - divProgress.style.display = opts.show_progressbar ? "block" : "none" - var divInner = document.createElement('div') - divInner.className='progress' - - divProgress.appendChild(divInner) - parentProgressbar.insertBefore(divProgress, progressbarContainer) - - if(parentGallery){ - var livePreview = document.createElement('div') - livePreview.className='livePreview' - parentGallery.insertBefore(livePreview, gallery) +function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgress, inactivityTimeout = 40) { + var dateStart = new Date(); + var wasEverActive = false; + var parentProgressbar = progressbarContainer.parentNode; + var parentGallery = gallery ? gallery.parentNode : null; + + var divProgress = document.createElement('div'); + divProgress.className = 'progressDiv'; + divProgress.style.display = opts.show_progressbar ? "block" : "none"; + var divInner = document.createElement('div'); + divInner.className = 'progress'; + + divProgress.appendChild(divInner); + parentProgressbar.insertBefore(divProgress, progressbarContainer); + + if (parentGallery) { + var livePreview = document.createElement('div'); + livePreview.className = 'livePreview'; + parentGallery.insertBefore(livePreview, gallery); } - var removeProgressBar = function(){ - setTitle("") - parentProgressbar.removeChild(divProgress) - if(parentGallery) parentGallery.removeChild(livePreview) - atEnd() - } + var removeProgressBar = function() { + setTitle(""); + parentProgressbar.removeChild(divProgress); + if (parentGallery) parentGallery.removeChild(livePreview); + atEnd(); + }; - var fun = function(id_task, id_live_preview){ - request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){ - if(res.completed){ - removeProgressBar() - return + var fun = function(id_task, id_live_preview) { + request("./internal/progress", {id_task: id_task, id_live_preview: id_live_preview}, function(res) { + if (res.completed) { + removeProgressBar(); + return; } - var rect = progressbarContainer.getBoundingClientRect() + var rect = progressbarContainer.getBoundingClientRect(); - if(rect.width){ + if (rect.width) { divProgress.style.width = rect.width + "px"; } - let progressText = "" + let progressText = ""; - divInner.style.width = ((res.progress || 0) * 100.0) + '%' - divInner.style.background = res.progress ? "" : "transparent" + divInner.style.width = ((res.progress || 0) * 100.0) + '%'; + divInner.style.background = res.progress ? "" : "transparent"; - if(res.progress > 0){ - progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%' + if (res.progress > 0) { + progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%'; } - if(res.eta){ - progressText += " ETA: " + formatTime(res.eta) + if (res.eta) { + progressText += " ETA: " + formatTime(res.eta); } - setTitle(progressText) + setTitle(progressText); - if(res.textinfo && res.textinfo.indexOf("\n") == -1){ - progressText = res.textinfo + " " + progressText + if (res.textinfo && res.textinfo.indexOf("\n") == -1) { + progressText = res.textinfo + " " + progressText; } - divInner.textContent = progressText + divInner.textContent = progressText; - var elapsedFromStart = (new Date() - dateStart) / 1000 + var elapsedFromStart = (new Date() - dateStart) / 1000; - if(res.active) wasEverActive = true; + if (res.active) wasEverActive = true; - if(! res.active && wasEverActive){ - removeProgressBar() - return + if (!res.active && wasEverActive) { + removeProgressBar(); + return; } - if(elapsedFromStart > inactivityTimeout && !res.queued && !res.active){ - removeProgressBar() - return + if (elapsedFromStart > inactivityTimeout && !res.queued && !res.active) { + removeProgressBar(); + return; } - if(res.live_preview && gallery){ - var rect = gallery.getBoundingClientRect() - if(rect.width){ - livePreview.style.width = rect.width + "px" - livePreview.style.height = rect.height + "px" + if (res.live_preview && gallery) { + var rect = gallery.getBoundingClientRect(); + if (rect.width) { + livePreview.style.width = rect.width + "px"; + livePreview.style.height = rect.height + "px"; } var img = new Image(); img.onload = function() { - livePreview.appendChild(img) - if(livePreview.childElementCount > 2){ - livePreview.removeChild(livePreview.firstElementChild) + livePreview.appendChild(img); + if (livePreview.childElementCount > 2) { + livePreview.removeChild(livePreview.firstElementChild); } - } + }; img.src = res.live_preview; } - if(onProgress){ - onProgress(res) + if (onProgress) { + onProgress(res); } setTimeout(() => { fun(id_task, res.id_live_preview); - }, opts.live_preview_refresh_period || 500) - }, function(){ - removeProgressBar() - }) - } + }, opts.live_preview_refresh_period || 500); + }, function() { + removeProgressBar(); + }); + }; - fun(id_task, 0) + fun(id_task, 0); } diff --git a/javascript/textualInversion.js b/javascript/textualInversion.js index 0354b860ca9..37e3d0756c4 100644 --- a/javascript/textualInversion.js +++ b/javascript/textualInversion.js @@ -1,17 +1,17 @@ - - - -function start_training_textual_inversion(){ - gradioApp().querySelector('#ti_error').innerHTML='' - - var id = randomId() - requestProgress(id, gradioApp().getElementById('ti_output'), gradioApp().getElementById('ti_gallery'), function(){}, function(progress){ - gradioApp().getElementById('ti_progress').innerHTML = progress.textinfo - }) - - var res = args_to_array(arguments) - - res[0] = id - - return res -} + + + +function start_training_textual_inversion() { + gradioApp().querySelector('#ti_error').innerHTML = ''; + + var id = randomId(); + requestProgress(id, gradioApp().getElementById('ti_output'), gradioApp().getElementById('ti_gallery'), function() {}, function(progress) { + gradioApp().getElementById('ti_progress').innerHTML = progress.textinfo; + }); + + var res = args_to_array(arguments); + + res[0] = id; + + return res; +} diff --git a/javascript/ui.js b/javascript/ui.js index ed9673d64e1..f4727ca3a7e 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -1,9 +1,9 @@ // various functions for interaction with ui.py not large enough to warrant putting them in separate files -function set_theme(theme){ - var gradioURL = window.location.href +function set_theme(theme) { + var gradioURL = window.location.href; if (!gradioURL.includes('?__theme=')) { - window.location.replace(gradioURL + '?__theme=' + theme); + window.location.replace(gradioURL + '?__theme=' + theme); } } @@ -14,7 +14,7 @@ function all_gallery_buttons() { if (elem.parentElement.offsetParent) { visibleGalleryButtons.push(elem); } - }) + }); return visibleGalleryButtons; } @@ -25,31 +25,35 @@ function selected_gallery_button() { if (elem.parentElement.offsetParent) { visibleCurrentButton = elem; } - }) + }); return visibleCurrentButton; } -function selected_gallery_index(){ +function selected_gallery_index() { var buttons = all_gallery_buttons(); var button = selected_gallery_button(); - var result = -1 - buttons.forEach(function(v, i){ if(v==button) { result = i } }) + var result = -1; + buttons.forEach(function(v, i) { + if (v == button) { + result = i; + } + }); - return result + return result; } -function extract_image_from_gallery(gallery){ - if (gallery.length == 0){ +function extract_image_from_gallery(gallery) { + if (gallery.length == 0) { return [null]; } - if (gallery.length == 1){ + if (gallery.length == 1) { return [gallery[0]]; } - var index = selected_gallery_index() + var index = selected_gallery_index(); - if (index < 0 || index >= gallery.length){ + if (index < 0 || index >= gallery.length) { // Use the first image in the gallery as the default index = 0; } @@ -57,248 +61,249 @@ function extract_image_from_gallery(gallery){ return [gallery[index]]; } -function args_to_array(args){ - var res = [] - for(var i=0;i label > textarea"); - if(counter.parentElement == prompt.parentElement){ - return + if (counter.parentElement == prompt.parentElement) { + return; } - prompt.parentElement.insertBefore(counter, prompt) - prompt.parentElement.style.position = "relative" + prompt.parentElement.insertBefore(counter, prompt); + prompt.parentElement.style.position = "relative"; - promptTokecountUpdateFuncs[id] = function(){ update_token_counter(id_button); } - textarea.addEventListener("input", promptTokecountUpdateFuncs[id]); + promptTokecountUpdateFuncs[id] = function() { + update_token_counter(id_button); + }; + textarea.addEventListener("input", promptTokecountUpdateFuncs[id]); } - registerTextarea('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button') - registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button') - registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button') - registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button') - - var show_all_pages = gradioApp().getElementById('settings_show_all_pages') - var settings_tabs = gradioApp().querySelector('#settings div') - if(show_all_pages && settings_tabs){ - settings_tabs.appendChild(show_all_pages) - show_all_pages.onclick = function(){ - gradioApp().querySelectorAll('#settings > div').forEach(function(elem){ - if(elem.id == "settings_tab_licenses") + registerTextarea('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button'); + registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button'); + registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button'); + registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button'); + + var show_all_pages = gradioApp().getElementById('settings_show_all_pages'); + var settings_tabs = gradioApp().querySelector('#settings div'); + if (show_all_pages && settings_tabs) { + settings_tabs.appendChild(show_all_pages); + show_all_pages.onclick = function() { + gradioApp().querySelectorAll('#settings > div').forEach(function(elem) { + if (elem.id == "settings_tab_licenses") { return; + } elem.style.display = "block"; - }) - } + }); + }; } -}) +}); -onOptionsChanged(function(){ - var elem = gradioApp().getElementById('sd_checkpoint_hash') - var sd_checkpoint_hash = opts.sd_checkpoint_hash || "" - var shorthash = sd_checkpoint_hash.substring(0,10) +onOptionsChanged(function() { + var elem = gradioApp().getElementById('sd_checkpoint_hash'); + var sd_checkpoint_hash = opts.sd_checkpoint_hash || ""; + var shorthash = sd_checkpoint_hash.substring(0, 10); - if(elem && elem.textContent != shorthash){ - elem.textContent = shorthash - elem.title = sd_checkpoint_hash - elem.href = "https://google.com/search?q=" + sd_checkpoint_hash - } -}) + if (elem && elem.textContent != shorthash) { + elem.textContent = shorthash; + elem.title = sd_checkpoint_hash; + elem.href = "https://google.com/search?q=" + sd_checkpoint_hash; + } +}); let txt2img_textarea, img2img_textarea = undefined; -let wait_time = 800 +let wait_time = 800; let token_timeouts = {}; function update_txt2img_tokens(...args) { - update_token_counter("txt2img_token_button") - if (args.length == 2) - return args[0] - return args; + update_token_counter("txt2img_token_button"); + if (args.length == 2) { + return args[0]; + } + return args; } function update_img2img_tokens(...args) { - update_token_counter("img2img_token_button") - if (args.length == 2) - return args[0] - return args; + update_token_counter("img2img_token_button"); + if (args.length == 2) { + return args[0]; + } + return args; } function update_token_counter(button_id) { - if (token_timeouts[button_id]) - clearTimeout(token_timeouts[button_id]); - token_timeouts[button_id] = setTimeout(() => gradioApp().getElementById(button_id)?.click(), wait_time); + if (token_timeouts[button_id]) { + clearTimeout(token_timeouts[button_id]); + } + token_timeouts[button_id] = setTimeout(() => gradioApp().getElementById(button_id)?.click(), wait_time); } -function restart_reload(){ - document.body.innerHTML='

    Reloading...

    '; +function restart_reload() { + document.body.innerHTML = '

    Reloading...

    '; - var requestPing = function(){ - requestGet("./internal/ping", {}, function(data){ + var requestPing = function() { + requestGet("./internal/ping", {}, function(data) { location.reload(); - }, function(){ + }, function() { setTimeout(requestPing, 500); - }) - } + }); + }; setTimeout(requestPing, 2000); - return [] + return []; } // Simulate an `input` DOM event for Gradio Textbox component. Needed after you edit its contents in javascript, otherwise your edits // will only visible on web page and not sent to python. -function updateInput(target){ - let e = new Event("input", { bubbles: true }) - Object.defineProperty(e, "target", {value: target}) - target.dispatchEvent(e); +function updateInput(target) { + let e = new Event("input", { bubbles: true }); + Object.defineProperty(e, "target", {value: target}); + target.dispatchEvent(e); } var desiredCheckpointName = null; -function selectCheckpoint(name){ +function selectCheckpoint(name) { desiredCheckpointName = name; - gradioApp().getElementById('change_checkpoint').click() + gradioApp().getElementById('change_checkpoint').click(); } -function currentImg2imgSourceResolution(_, _, scaleBy){ - var img = gradioApp().querySelector('#mode_img2img > div[style="display: block;"] img') - return img ? [img.naturalWidth, img.naturalHeight, scaleBy] : [0, 0, scaleBy] +function currentImg2imgSourceResolution(_, _, scaleBy) { + var img = gradioApp().querySelector('#mode_img2img > div[style="display: block;"] img'); + return img ? [img.naturalWidth, img.naturalHeight, scaleBy] : [0, 0, scaleBy]; } -function updateImg2imgResizeToTextAfterChangingImage(){ +function updateImg2imgResizeToTextAfterChangingImage() { // At the time this is called from gradio, the image has no yet been replaced. // There may be a better solution, but this is simple and straightforward so I'm going with it. setTimeout(function() { - gradioApp().getElementById('img2img_update_resize_to').click() + gradioApp().getElementById('img2img_update_resize_to').click(); }, 500); - return [] + return []; } diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js index 6d1933dc8fe..0db41b116f6 100644 --- a/javascript/ui_settings_hints.js +++ b/javascript/ui_settings_hints.js @@ -1,62 +1,62 @@ -// various hints and extra info for the settings tab - -settingsHintsSetup = false - -onOptionsChanged(function(){ - if(settingsHintsSetup) return - settingsHintsSetup = true - - gradioApp().querySelectorAll('#settings [id^=setting_]').forEach(function(div){ - var name = div.id.substr(8) - var commentBefore = opts._comments_before[name] - var commentAfter = opts._comments_after[name] - - if(! commentBefore && !commentAfter) return - - var span = null - if(div.classList.contains('gradio-checkbox')) span = div.querySelector('label span') - else if(div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span').firstChild - else if(div.classList.contains('gradio-radio')) span = div.querySelector('span').firstChild - else span = div.querySelector('label span').firstChild - - if(!span) return - - if(commentBefore){ - var comment = document.createElement('DIV') - comment.className = 'settings-comment' - comment.innerHTML = commentBefore - span.parentElement.insertBefore(document.createTextNode('\xa0'), span) - span.parentElement.insertBefore(comment, span) - span.parentElement.insertBefore(document.createTextNode('\xa0'), span) - } - if(commentAfter){ - var comment = document.createElement('DIV') - comment.className = 'settings-comment' - comment.innerHTML = commentAfter - span.parentElement.insertBefore(comment, span.nextSibling) - span.parentElement.insertBefore(document.createTextNode('\xa0'), span.nextSibling) - } - }) -}) - -function settingsHintsShowQuicksettings(){ - requestGet("./internal/quicksettings-hint", {}, function(data){ - var table = document.createElement('table') - table.className = 'settings-value-table' - - data.forEach(function(obj){ - var tr = document.createElement('tr') - var td = document.createElement('td') - td.textContent = obj.name - tr.appendChild(td) - - var td = document.createElement('td') - td.textContent = obj.label - tr.appendChild(td) - - table.appendChild(tr) - }) - - popup(table); - }) -} +// various hints and extra info for the settings tab + +settingsHintsSetup = false; + +onOptionsChanged(function() { + if (settingsHintsSetup) return; + settingsHintsSetup = true; + + gradioApp().querySelectorAll('#settings [id^=setting_]').forEach(function(div) { + var name = div.id.substr(8); + var commentBefore = opts._comments_before[name]; + var commentAfter = opts._comments_after[name]; + + if (!commentBefore && !commentAfter) return; + + var span = null; + if (div.classList.contains('gradio-checkbox')) span = div.querySelector('label span'); + else if (div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span').firstChild; + else if (div.classList.contains('gradio-radio')) span = div.querySelector('span').firstChild; + else span = div.querySelector('label span').firstChild; + + if (!span) return; + + if (commentBefore) { + var comment = document.createElement('DIV'); + comment.className = 'settings-comment'; + comment.innerHTML = commentBefore; + span.parentElement.insertBefore(document.createTextNode('\xa0'), span); + span.parentElement.insertBefore(comment, span); + span.parentElement.insertBefore(document.createTextNode('\xa0'), span); + } + if (commentAfter) { + var comment = document.createElement('DIV'); + comment.className = 'settings-comment'; + comment.innerHTML = commentAfter; + span.parentElement.insertBefore(comment, span.nextSibling); + span.parentElement.insertBefore(document.createTextNode('\xa0'), span.nextSibling); + } + }); +}); + +function settingsHintsShowQuicksettings() { + requestGet("./internal/quicksettings-hint", {}, function(data) { + var table = document.createElement('table'); + table.className = 'settings-value-table'; + + data.forEach(function(obj) { + var tr = document.createElement('tr'); + var td = document.createElement('td'); + td.textContent = obj.name; + tr.appendChild(td); + + var td = document.createElement('td'); + td.textContent = obj.label; + tr.appendChild(td); + + table.appendChild(tr); + }); + + popup(table); + }); +} diff --git a/script.js b/script.js index 03afe8445b8..f6a3883af96 100644 --- a/script.js +++ b/script.js @@ -1,66 +1,72 @@ function gradioApp() { - const elems = document.getElementsByTagName('gradio-app') - const elem = elems.length == 0 ? document : elems[0] + const elems = document.getElementsByTagName('gradio-app'); + const elem = elems.length == 0 ? document : elems[0]; - if (elem !== document) elem.getElementById = function(id){ return document.getElementById(id) } - return elem.shadowRoot ? elem.shadowRoot : elem + if (elem !== document) { + elem.getElementById = function(id) { + return document.getElementById(id); + }; + } + return elem.shadowRoot ? elem.shadowRoot : elem; } function get_uiCurrentTab() { - return gradioApp().querySelector('#tabs button.selected') + return gradioApp().querySelector('#tabs button.selected'); } function get_uiCurrentTabContent() { - return gradioApp().querySelector('.tabitem[id^=tab_]:not([style*="display: none"])') + return gradioApp().querySelector('.tabitem[id^=tab_]:not([style*="display: none"])'); } -uiUpdateCallbacks = [] -uiLoadedCallbacks = [] -uiTabChangeCallbacks = [] -optionsChangedCallbacks = [] -let uiCurrentTab = null +uiUpdateCallbacks = []; +uiLoadedCallbacks = []; +uiTabChangeCallbacks = []; +optionsChangedCallbacks = []; +let uiCurrentTab = null; -function onUiUpdate(callback){ - uiUpdateCallbacks.push(callback) +function onUiUpdate(callback) { + uiUpdateCallbacks.push(callback); } -function onUiLoaded(callback){ - uiLoadedCallbacks.push(callback) +function onUiLoaded(callback) { + uiLoadedCallbacks.push(callback); } -function onUiTabChange(callback){ - uiTabChangeCallbacks.push(callback) +function onUiTabChange(callback) { + uiTabChangeCallbacks.push(callback); } -function onOptionsChanged(callback){ - optionsChangedCallbacks.push(callback) +function onOptionsChanged(callback) { + optionsChangedCallbacks.push(callback); } -function runCallback(x, m){ +function runCallback(x, m) { try { - x(m) + x(m); } catch (e) { (console.error || console.log).call(console, e.message, e); } } function executeCallbacks(queue, m) { - queue.forEach(function(x){runCallback(x, m)}) + queue.forEach(function(x) { + runCallback(x, m); + }); } var executedOnLoaded = false; document.addEventListener("DOMContentLoaded", function() { - var mutationObserver = new MutationObserver(function(m){ - if(!executedOnLoaded && gradioApp().querySelector('#txt2img_prompt')){ + var mutationObserver = new MutationObserver(function(m) { + if (!executedOnLoaded && gradioApp().querySelector('#txt2img_prompt')) { executedOnLoaded = true; executeCallbacks(uiLoadedCallbacks); } executeCallbacks(uiUpdateCallbacks, m); const newTab = get_uiCurrentTab(); - if ( newTab && ( newTab !== uiCurrentTab ) ) { + if (newTab && (newTab !== uiCurrentTab)) { uiCurrentTab = newTab; executeCallbacks(uiTabChangeCallbacks); } }); - mutationObserver.observe( gradioApp(), { childList:true, subtree:true }) + mutationObserver.observe(gradioApp(), { childList: true, subtree: true }); }); /** @@ -69,9 +75,9 @@ document.addEventListener("DOMContentLoaded", function() { document.addEventListener('keydown', function(e) { var handled = false; if (e.key !== undefined) { - if((e.key == "Enter" && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; + if ((e.key == "Enter" && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; } else if (e.keyCode !== undefined) { - if((e.keyCode == 13 && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; + if ((e.keyCode == 13 && (e.metaKey || e.ctrlKey || e.altKey))) handled = true; } if (handled) { button = get_uiCurrentTabContent().querySelector('button[id$=_generate]'); @@ -80,22 +86,22 @@ document.addEventListener('keydown', function(e) { } e.preventDefault(); } -}) +}); /** * checks that a UI element is not in another hidden element or tab content */ function uiElementIsVisible(el) { let isVisible = !el.closest('.\\!hidden'); - if ( ! isVisible ) { + if (!isVisible) { return false; } - while( isVisible = el.closest('.tabitem')?.style.display !== 'none' ) { - if ( ! isVisible ) { + while (isVisible = el.closest('.tabitem')?.style.display !== 'none') { + if (!isVisible) { return false; - } else if ( el.parentElement ) { - el = el.parentElement + } else if (el.parentElement) { + el = el.parentElement; } else { break; } From f8ca37b9035dc8cb09e15afc5ade6976b927e923 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 17:06:45 +0300 Subject: [PATCH 0249/2418] fix inability to run with --freeze-settings --- webui.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/webui.py b/webui.py index 293a16ccda2..2cd551bdba3 100644 --- a/webui.py +++ b/webui.py @@ -144,16 +144,11 @@ def check_versions(): """.strip()) -def initialize(): - fix_asyncio_event_loop_policy() - - check_versions() - - extensions.list_extensions() - localization.list_localizations(cmd_opts.localizations_dir) - startup_timer.record("list extensions") - +def restore_config_state_file(): config_state_file = shared.opts.restore_config_state_file + if config_state_file == "": + return + shared.opts.restore_config_state_file = "" shared.opts.save(shared.config_filename) @@ -166,6 +161,18 @@ def initialize(): elif config_state_file: print(f"!!! Config state backup not found: {config_state_file}") + +def initialize(): + fix_asyncio_event_loop_policy() + + check_versions() + + extensions.list_extensions() + localization.list_localizations(cmd_opts.localizations_dir) + startup_timer.record("list extensions") + + restore_config_state_file() + if cmd_opts.ui_debug_mode: shared.sd_upscalers = upscaler.UpscalerLanczos().scalers modules.scripts.load_scripts() @@ -370,18 +377,7 @@ def fastapi_setup(self): extensions.list_extensions() startup_timer.record("list extensions") - config_state_file = shared.opts.restore_config_state_file - shared.opts.restore_config_state_file = "" - shared.opts.save(shared.config_filename) - - if os.path.isfile(config_state_file): - print(f"*** About to restore extension state from file: {config_state_file}") - with open(config_state_file, "r", encoding="utf-8") as f: - config_state = json.load(f) - config_states.restore_extension_config(config_state) - startup_timer.record("restore extension config") - elif config_state_file: - print(f"!!! Config state backup not found: {config_state_file}") + restore_config_state_file() localization.list_localizations(cmd_opts.localizations_dir) From 95cb492e4106646450480ec74014c3ec9a679c1f Mon Sep 17 00:00:00 2001 From: Weiming Date: Wed, 17 May 2023 21:25:50 +0800 Subject: [PATCH 0250/2418] Fixed: #10460 --- modules/extras.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/extras.py b/modules/extras.py index bdf9b3b7a26..aabb3b2623b 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -242,9 +242,10 @@ def filename_nothing(): shared.state.textinfo = "Saving" print(f"Saving to {output_modelname}...") - metadata = {"format": "pt", "sd_merge_models": {}, "sd_merge_recipe": None} + metadata = None if save_metadata: + metadata = {"format": "pt", "sd_merge_models": {}} merge_recipe = { "type": "webui", # indicate this model was merged with webui's built-in merger "primary_model_hash": primary_model_info.sha256, From 76ebf750a463bc24434048dc60e289b0b6198598 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 17:44:07 +0300 Subject: [PATCH 0251/2418] use a local variable instead of dictionary entry for sd_merge_models in merge model metadata code --- modules/extras.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/extras.py b/modules/extras.py index aabb3b2623b..830b53aa2b6 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -245,7 +245,8 @@ def filename_nothing(): metadata = None if save_metadata: - metadata = {"format": "pt", "sd_merge_models": {}} + metadata = {"format": "pt"} + merge_recipe = { "type": "webui", # indicate this model was merged with webui's built-in merger "primary_model_hash": primary_model_info.sha256, @@ -263,15 +264,17 @@ def filename_nothing(): } metadata["sd_merge_recipe"] = json.dumps(merge_recipe) + sd_merge_models = {} + def add_model_metadata(checkpoint_info): checkpoint_info.calculate_shorthash() - metadata["sd_merge_models"][checkpoint_info.sha256] = { + sd_merge_models[checkpoint_info.sha256] = { "name": checkpoint_info.name, "legacy_hash": checkpoint_info.hash, "sd_merge_recipe": checkpoint_info.metadata.get("sd_merge_recipe", None) } - metadata["sd_merge_models"].update(checkpoint_info.metadata.get("sd_merge_models", {})) + sd_merge_models.update(checkpoint_info.metadata.get("sd_merge_models", {})) add_model_metadata(primary_model_info) if secondary_model_info: @@ -279,7 +282,7 @@ def add_model_metadata(checkpoint_info): if tertiary_model_info: add_model_metadata(tertiary_model_info) - metadata["sd_merge_models"] = json.dumps(metadata["sd_merge_models"]) + metadata["sd_merge_models"] = json.dumps(sd_merge_models) _, extension = os.path.splitext(output_modelname) if extension.lower() == ".safetensors": From 216b0fa6c904a4803a74e9af9b335142ae980dd1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 18:26:46 +0300 Subject: [PATCH 0252/2418] when adding tooltips, do not scan whole document and instead only scan added elements --- javascript/hints.js | 63 ++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index 3746df99fa7..b00b504a4e9 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -115,36 +115,53 @@ titles = { "Negative Guidance minimum sigma": "Skip negative prompt for steps where image is already mostly denoised; the higher this value, the more skips there will be; provides increased performance in exchange for minor quality reduction." } +function updateTooltipForSpan(span){ + if (span.title) return; // already has a title -onUiUpdate(function(){ - gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){ - if (span.title) return; // already has a title - - let tooltip = localization[titles[span.textContent]] || titles[span.textContent]; + let tooltip = localization[titles[span.textContent]] || titles[span.textContent]; if(!tooltip){ - tooltip = localization[titles[span.value]] || titles[span.value]; - } + tooltip = localization[titles[span.value]] || titles[span.value]; + } - if(!tooltip){ - for (const c of span.classList) { - if (c in titles) { - tooltip = localization[titles[c]] || titles[c]; - break; - } + if(!tooltip){ + for (const c of span.classList) { + if (c in titles) { + tooltip = localization[titles[c]] || titles[c]; + break; } } + } - if(tooltip){ - span.title = tooltip; - } - }) + if(tooltip){ + span.title = tooltip; + } +} + +function updateTooltipForSelect(select){ + if (select.onchange != null) return; - gradioApp().querySelectorAll('select').forEach(function(select){ - if (select.onchange != null) return; + select.onchange = function(){ + select.title = localization[titles[select.value]] || titles[select.value] || ""; + } +} - select.onchange = function(){ - select.title = localization[titles[select.value]] || titles[select.value] || ""; - } - }) +observedTooltipElements = {"SPAN": 1, "BUTTON": 1, "SELECT": 1, "P": 1} + +onUiUpdate(function(m){ + m.forEach(function(record){ + record.addedNodes.forEach(function(node){ + if(observedTooltipElements[node.tagName]){ + updateTooltipForSpan(node) + } + if(node.tagName == "SELECT"){ + updateTooltipForSelect(node) + } + + if(node.querySelectorAll){ + node.querySelectorAll('span, button, select, p').forEach(updateTooltipForSpan) + node.querySelectorAll('select').forEach(updateTooltipForSelect) + } + }) + }) }) From f5092164e8e452debc889475dbaa163e4f877fda Mon Sep 17 00:00:00 2001 From: Iheuzio <97270760+Iheuzio@users.noreply.github.com> Date: Wed, 17 May 2023 12:51:54 -0400 Subject: [PATCH 0253/2418] Fix typo in syntax --- modules/ui_extra_networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 8c3dea5643e..4f754f433b9 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -172,7 +172,7 @@ def create_html_for_item(self, item, tabname): # if this is true, the item must not be show in the default view, and must instead only be # shown when searching for it - serach_only = "/." in local_path or "\\." in local_path + search_only = "/." in local_path or "\\." in local_path args = { "style": f"'display: none; {height}{width}{background_image}'", @@ -185,7 +185,7 @@ def create_html_for_item(self, item, tabname): "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), "metadata_button": metadata_button, - "serach_only": " search_only" if serach_only else "", + "search_only": " search_only" if search_only else "", } return self.card_page.format(**args) From 9fd6c1e3430f5947add23e2e94ac816c2546481c Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 20:22:38 +0300 Subject: [PATCH 0254/2418] move some settings to the new Optimization page add slider for token merging for img2img rework StableDiffusionProcessing to have the token_merging_ratio field fix a bug with applying png optimizations for live previews when they shouldn't be applied --- modules/processing.py | 52 +++++++++++++++++++++---------------------- modules/progress.py | 6 ++++- modules/sd_models.py | 36 +++++++++++++++++------------- modules/shared.py | 8 +++++-- 4 files changed, 56 insertions(+), 46 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index cd63b9a604f..2b8dd361d7e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -29,12 +29,6 @@ from einops import repeat, rearrange from blendmodes.blend import blendLayers, BlendType -import tomesd - -# add a logger for the processing module -logger = logging.getLogger(__name__) -# manually set output level here since there is no option to do so yet through launch options -# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(message)s') # some of those options should not be changed at all because they would break the model, so I removed them from options. @@ -156,6 +150,8 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.override_settings_restore_afterwards = override_settings_restore_afterwards self.is_using_inpainting_conditioning = False self.disable_extra_networks = False + self.token_merging_ratio = 0 + self.token_merging_ratio_hr = 0 if not seed_enable_extras: self.subseed = -1 @@ -171,6 +167,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.all_subseeds = None self.iteration = 0 self.is_hr_pass = False + self.sampler = None @property @@ -280,6 +277,12 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs def close(self): self.sampler = None + def get_token_merging_ratio(self, for_hr=False): + if for_hr: + return self.token_merging_ratio_hr or opts.token_merging_ratio_hr or self.token_merging_ratio or opts.token_merging_ratio + + return self.token_merging_ratio or opts.token_merging_ratio + class Processed: def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, all_negative_prompts=None, all_seeds=None, all_subseeds=None, index_of_first_image=0, infotexts=None, comments=""): @@ -309,6 +312,8 @@ def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", self.styles = p.styles self.job_timestamp = state.job_timestamp self.clip_skip = opts.CLIP_stop_at_last_layers + self.token_merging_ratio = p.token_merging_ratio + self.token_merging_ratio_hr = p.token_merging_ratio_hr self.eta = p.eta self.ddim_discretize = p.ddim_discretize @@ -367,6 +372,9 @@ def js(self): 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) + def get_token_merging_ratio(self, for_hr=False): + return self.token_merging_ratio_hr if for_hr else self.token_merging_ratio + # from https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3 def slerp(val, low, high): @@ -480,6 +488,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter clip_skip = getattr(p, 'clip_skip', opts.CLIP_stop_at_last_layers) enable_hr = getattr(p, 'enable_hr', False) + token_merging_ratio = p.get_token_merging_ratio() + token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) uses_ensd = opts.eta_noise_seed_delta != 0 if uses_ensd: @@ -502,8 +512,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, "ENSD": opts.eta_noise_seed_delta if uses_ensd else None, - "Token merging ratio": None if opts.token_merging_ratio == 0 else opts.token_merging_ratio, - "Token merging ratio hr": None if not enable_hr or opts.token_merging_ratio_hr == 0 else opts.token_merging_ratio_hr, + "Token merging ratio": None if token_merging_ratio == 0 else token_merging_ratio, + "Token merging ratio hr": None if not enable_hr or token_merging_ratio_hr == 0 else token_merging_ratio_hr, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, @@ -536,17 +546,12 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - if opts.token_merging_ratio > 0: - sd_models.apply_token_merging(sd_model=p.sd_model, hr=False) - logger.debug(f"Token merging applied to first pass. Ratio: '{opts.token_merging_ratio}'") + sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio()) res = process_images_inner(p) finally: - # undo model optimizations made by tomesd - if opts.token_merging_ratio > 0: - tomesd.remove_patch(p.sd_model) - logger.debug('Token merging model optimizations removed') + sd_models.apply_token_merging(p.sd_model, 0) # restore opts to original state if p.override_settings_restore_afterwards: @@ -996,21 +1001,11 @@ def save_intermediate(image, index): x = None devices.torch_gc() - # apply token merging optimizations from tomesd for high-res pass - if opts.token_merging_ratio_hr > 0: - # in case the user has used separate merge ratios - if opts.token_merging_ratio > 0: - tomesd.remove_patch(self.sd_model) - logger.debug('Adjusting token merging ratio for high-res pass') - - sd_models.apply_token_merging(sd_model=self.sd_model, hr=True) - logger.debug(f"Applied token merging for high-res pass. Ratio: '{opts.token_merging_ratio_hr}'") + sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) - if opts.token_merging_ratio_hr > 0 or opts.token_merging_ratio > 0: - tomesd.remove_patch(self.sd_model) - logger.debug('Removed token merging optimizations from model') + sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) self.is_hr_pass = False @@ -1173,3 +1168,6 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs devices.torch_gc() return samples + + def get_token_merging_ratio(self, for_hr=False): + return self.token_merging_ratio or ("token_merging_ratio" in self.override_settings and opts.token_merging_ratio) or opts.token_merging_ratio_img2img or opts.token_merging_ratio diff --git a/modules/progress.py b/modules/progress.py index 269863c942e..f405f07fed2 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -98,7 +98,11 @@ def progressapi(req: ProgressRequest): if opts.live_previews_image_format == "png": # using optimize for large images takes an enormous amount of time - save_kwargs = {"optimize": max(*image.size) > 256} + if max(*image.size) <= 256: + save_kwargs = {"optimize": True} + else: + save_kwargs = {"optimize": False, "compress_level": 1} + else: save_kwargs = {} diff --git a/modules/sd_models.py b/modules/sd_models.py index e612be10da4..4bd8783e222 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -583,23 +583,27 @@ def unload_model_weights(sd_model=None, info=None): return sd_model -def apply_token_merging(sd_model, hr: bool): +def apply_token_merging(sd_model, token_merging_ratio): """ Applies speed and memory optimizations from tomesd. - - Args: - hr (bool): True if called in the context of a high-res pass """ - ratio = shared.opts.token_merging_ratio - if hr: - ratio = shared.opts.token_merging_ratio_hr - - tomesd.apply_patch( - sd_model, - ratio=ratio, - use_rand=False, # can cause issues with some samplers - merge_attn=True, - merge_crossattn=False, - merge_mlp=False - ) + current_token_merging_ratio = getattr(sd_model, 'applied_token_merged_ratio', 0) + + if current_token_merging_ratio == token_merging_ratio: + return + + if current_token_merging_ratio > 0: + tomesd.remove_patch(sd_model) + + if token_merging_ratio > 0: + tomesd.apply_patch( + sd_model, + ratio=token_merging_ratio, + use_rand=False, # can cause issues with some samplers + merge_attn=True, + merge_crossattn=False, + merge_mlp=False + ) + + sd_model.applied_token_merged_ratio = token_merging_ratio diff --git a/modules/shared.py b/modules/shared.py index 47bc6d0ea1c..76af8b9cdea 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -413,8 +413,13 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP nrtwork; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different vidocard vendors"), +})) + +options_templates.update(options_section(('optimizations', "Optimizations"), { + "s_min_uncond": OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), - "token_merging_ratio_hr": OptionInfo(0.0, "Togen merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), + "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), + "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), })) options_templates.update(options_section(('compatibility', "Compatibility"), { @@ -498,7 +503,6 @@ def list_samplers(): "eta_ddim": OptionInfo(0.0, "Eta for DDIM", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}).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}).info("noise multiplier; applies to Euler a and other samplers that have a in them"), "ddim_discretize": OptionInfo('uniform', "img2img DDIM discretize", gr.Radio, {"choices": ['uniform', 'quad']}), - 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), From f6a622bcefce987b39a135defd823ee0efee1ec5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 20:27:48 +0300 Subject: [PATCH 0255/2418] isn't there something you forgot, #10483? --- html/extra-networks-card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 1d5462172c0..6853b14f9d6 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -6,7 +6,7 @@ - + {name} {description} From a6b618d07248267de36f0e8f4a847d997285e272 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 21:03:41 +0300 Subject: [PATCH 0256/2418] use a single function for saving images with metadata both in extra networks and main mode for #10395 --- modules/images.py | 70 +++++++++++++++++++++--------------- modules/ui_extra_networks.py | 19 ++-------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/modules/images.py b/modules/images.py index b2de3662f29..4e8cd9934cf 100644 --- a/modules/images.py +++ b/modules/images.py @@ -482,6 +482,43 @@ def get_next_sequence_number(path, basename): return result + 1 +def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_pnginfo=None): + if extension is None: + extension = os.path.splitext(filename)[1] + + image_format = Image.registered_extensions()[extension] + + existing_pnginfo = existing_pnginfo or {} + if opts.enable_pnginfo: + existing_pnginfo['parameters'] = geninfo + + if extension.lower() == '.png': + pnginfo_data = PngImagePlugin.PngInfo() + for k, v in (existing_pnginfo or {}).items(): + pnginfo_data.add_text(k, str(v)) + + image.save(filename, format=image_format, quality=opts.jpeg_quality, pnginfo=pnginfo_data) + + elif extension.lower() in (".jpg", ".jpeg", ".webp"): + if image.mode == 'RGBA': + image = image.convert("RGB") + elif image.mode == 'I;16': + image = image.point(lambda p: p * 0.0038910505836576).convert("RGB" if extension.lower() == ".webp" else "L") + + image.save(filename, format=image_format, quality=opts.jpeg_quality, lossless=opts.webp_lossless) + + 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") + }, + }) + + piexif.insert(exif_bytes, filename) + else: + image.save(filename, format=image_format, quality=opts.jpeg_quality) + + def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None): """Save an image. @@ -566,38 +603,13 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i info = params.pnginfo.get(pnginfo_section_name, None) def _atomically_save_image(image_to_save, filename_without_extension, extension): - # save image with .tmp extension to avoid race condition when another process detects new image in the directory + """ + save image with .tmp extension to avoid race condition when another process detects new image in the directory + """ temp_file_path = f"{filename_without_extension}.tmp" - image_format = Image.registered_extensions()[extension] - - if extension.lower() == '.png': - pnginfo_data = PngImagePlugin.PngInfo() - if opts.enable_pnginfo: - for k, v in params.pnginfo.items(): - pnginfo_data.add_text(k, str(v)) - - image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality, pnginfo=pnginfo_data) - elif extension.lower() in (".jpg", ".jpeg", ".webp"): - if image_to_save.mode == 'RGBA': - image_to_save = image_to_save.convert("RGB") - elif image_to_save.mode == 'I;16': - image_to_save = image_to_save.point(lambda p: p * 0.0038910505836576).convert("RGB" if extension.lower() == ".webp" else "L") - - image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality, lossless=opts.webp_lossless) - - if opts.enable_pnginfo and info is not None: - exif_bytes = piexif.dump({ - "Exif": { - piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(info or "", encoding="unicode") - }, - }) - - piexif.insert(exif_bytes, temp_file_path) - else: - image_to_save.save(temp_file_path, format=image_format, quality=opts.jpeg_quality) + save_image_with_geninfo(image_to_save, info, temp_file_path, extension, params.pnginfo) - # atomically rename the file with correct extension os.replace(temp_file_path, filename_without_extension + extension) fullfn_without_extension, extension = os.path.splitext(params.filename) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 471df23b5df..c6e45fb1c50 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -4,7 +4,7 @@ from PIL import PngImagePlugin from modules import shared -from modules.images import read_info_from_image +from modules.images import read_info_from_image, save_image_with_geninfo import gradio as gr import json import html @@ -343,22 +343,7 @@ def save_preview(index, images, filename): assert is_allowed, f'writing to {filename} is not allowed' - if geninfo: - ext = os.path.splitext(filename)[1].lower() - if ext == '.png': - pnginfo_data = PngImagePlugin.PngInfo() - pnginfo_data.add_text('parameters', geninfo) - image.save(filename, pnginfo=pnginfo_data) - elif ext in ('.jpg', '.jpeg', '.webp'): - exif_bytes = piexif.dump({ - 'Exif': {piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or '', - encoding='unicode')} - }) - image.save(filename, exif=exif_bytes, quality=shared.opts.jpeg_quality) - else: - image.save(filename) - else: - image.save(filename) + save_image_with_geninfo(image, geninfo, filename) return [page.create_html(ui.tabname) for page in ui.stored_extra_pages] From 8fe9ea7f4d8fd76038db61e1fd2b1cc5927ce108 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 21:45:26 +0300 Subject: [PATCH 0257/2418] add options to show/hide hidden files and dirs, and to not list models/files in hidden directories --- modules/shared.py | 6 ++++++ modules/ui_extra_networks.py | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 76af8b9cdea..23563582e08 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -378,6 +378,7 @@ def list_samplers(): "samples_log_stdout": OptionInfo(False, "Always print all generation info to standard output"), "multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job."), "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), + "list_hidden_files": OptionInfo(True, "Load models/files in hidden directories").info("directory is hidden if its name starts with \".\""), })) options_templates.update(options_section(('training', "Training"), { @@ -446,6 +447,8 @@ def list_samplers(): })) options_templates.update(options_section(('extra_networks', "Extra Networks"), { + "extra_networks_show_hidden_directories": OptionInfo(True, "Show hidden directories").info("directory is hidden if its name starts with \".\"."), + "extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more'), "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}), "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"), @@ -825,4 +828,7 @@ def walk_files(path, allowed_extensions=None): if ext not in allowed_extensions: continue + if not opts.list_hidden_files and ("/." in root or "\\." in root): + continue + yield os.path.join(root, filename) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index c6e45fb1c50..8669cc1a99b 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -105,6 +105,9 @@ def create_html(self, tabname): if not is_empty and not subdir.endswith("/"): subdir = subdir + "/" + if ("/." in subdir or subdir.startswith(".")) and not shared.opts.extra_networks_show_hidden_directories: + continue + subdirs[subdir] = 1 if subdirs: @@ -147,6 +150,10 @@ def allowed_directories_for_previews(self): return [] def create_html_for_item(self, item, tabname): + """ + Create HTML for card item in tab tabname; can return empty string if the item is not meant to be shown. + """ + preview = item.get("preview", None) onclick = item.get("onclick", None) @@ -169,9 +176,15 @@ def create_html_for_item(self, item, tabname): if filename.startswith(absdir): local_path = filename[len(absdir):] - # if this is true, the item must not be show in the default view, and must instead only be + # if this is true, the item must not be shown in the default view, and must instead only be # shown when searching for it - search_only = "/." in local_path or "\\." in local_path + if shared.opts.extra_networks_hidden_models == "Always": + search_only = False + else: + search_only = "/." in local_path or "\\." in local_path + + if search_only and shared.opts.extra_networks_hidden_models == "Never": + return "" args = { "style": f"'display: none; {height}{width}{background_image}'", From f6fc7916c4615c4f5cac97cab5add9b0536f6efa Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 22:43:24 +0300 Subject: [PATCH 0258/2418] add /sdapi/v1/script-info api --- modules/api/api.py | 13 +++++++++++-- modules/api/models.py | 21 +++++++++++++++++++-- modules/scripts.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 165985c3504..eee99bbb29c 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -204,6 +204,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/unload-checkpoint", self.unloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) + self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo]) self.default_script_arg_txt2img = [] self.default_script_arg_img2img = [] @@ -229,11 +230,19 @@ def get_selectable_script(self, script_name, script_runner): return script, script_idx def get_scripts_list(self): - t2ilist = [str(title.lower()) for title in scripts.scripts_txt2img.titles] - i2ilist = [str(title.lower()) for title in scripts.scripts_img2img.titles] + t2ilist = [script.name for script in scripts.scripts_txt2img.scripts if script.name is not None] + i2ilist = [script.name for script in scripts.scripts_img2img.scripts if script.name is not None] return models.ScriptsList(txt2img=t2ilist, img2img=i2ilist) + def get_script_info(self): + res = [] + + for script_list in [scripts.scripts_txt2img.scripts, scripts.scripts_img2img.scripts]: + res += [script.api_info for script in script_list if script.api_info is not None] + + return res + def get_script(self, script_name, script_runner): if script_name is None or script_name == "": return None, None diff --git a/modules/api/models.py b/modules/api/models.py index 006ccdb750f..1ff2fb338ce 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -287,6 +287,23 @@ class MemoryResponse(BaseModel): ram: dict = Field(title="RAM", description="System memory stats") cuda: dict = Field(title="CUDA", description="nVidia CUDA memory stats") + class ScriptsList(BaseModel): - txt2img: list = Field(default=None,title="Txt2img", description="Titles of scripts (txt2img)") - img2img: list = Field(default=None,title="Img2img", description="Titles of scripts (img2img)") + txt2img: list = Field(default=None, title="Txt2img", description="Titles of scripts (txt2img)") + img2img: list = Field(default=None, title="Img2img", description="Titles of scripts (img2img)") + + +class ScriptArg(BaseModel): + label: str = Field(default=None, title="Label", description="Name of the argument in UI") + value: Optional[Any] = Field(default=None, title="Value", description="Default value of the argument") + minimum: Optional[Any] = Field(default=None, title="Minimum", description="Minimum allowed value for the argumentin UI") + maximum: Optional[Any] = Field(default=None, title="Minimum", description="Maximum allowed value for the argumentin UI") + step: Optional[Any] = Field(default=None, title="Minimum", description="Step for changing value of the argumentin UI") + choices: Optional[List[str]] = Field(default=None, title="Choices", description="Possible values for the argument") + + +class ScriptInfo(BaseModel): + name: str = Field(default=None, title="Name", description="Script name") + is_alwayson: bool = Field(default=None, title="IsAlwayson", description="Flag specifying whether this script is an alwayson script") + is_img2img: bool = Field(default=None, title="IsImg2img", description="Flag specifying whether this script is an img2img script") + args: List[ScriptArg] = Field(title="Arguments", description="List of script's arguments") diff --git a/modules/scripts.py b/modules/scripts.py index 0c12ebd5461..e33d8c81947 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -17,6 +17,9 @@ def __init__(self, image): class Script: + name = None + """script's internal name derived from title""" + filename = None args_from = None args_to = None @@ -25,8 +28,8 @@ class Script: is_txt2img = False is_img2img = False - """A gr.Group component that has all script's UI inside it""" group = None + """A gr.Group component that has all script's UI inside it""" infotext_fields = None """if set in ui(), this is a list of pairs of gradio component + text; the text will be used when @@ -38,6 +41,9 @@ class Script: various "Send to " buttons when clicked """ + api_info = None + """Generated value of type modules.api.models.ScriptInfo with information about the script for API""" + def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -313,6 +319,8 @@ def initialize_scripts(self, is_img2img): self.selectable_scripts.append(script) def setup_ui(self): + import modules.api.models as api_models + self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts] inputs = [None] @@ -327,9 +335,28 @@ def create_script_ui(script, inputs, inputs_alwayson): if controls is None: return + script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower() + api_args = [] + for control in controls: control.custom_script_source = os.path.basename(script.filename) + arg_info = api_models.ScriptArg(label=control.label or "") + + for field in ("value", "minimum", "maximum", "step", "choices"): + v = getattr(control, field, None) + if v is not None: + setattr(arg_info, field, v) + + api_args.append(arg_info) + + script.api_info = api_models.ScriptInfo( + name=script.name, + is_img2img=script.is_img2img, + is_alwayson=script.alwayson, + args=api_args, + ) + if script.infotext_fields is not None: self.infotext_fields += script.infotext_fields From ad3a7f2ab9bb459ba86bcfb9041c5dbbac7841ee Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 22:50:08 +0300 Subject: [PATCH 0259/2418] alternative solution to fix styles load when edited by human #9765 as suggested by akx --- modules/styles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/styles.py b/modules/styles.py index c22769cf7f2..34e1b5e15e3 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -43,7 +43,7 @@ def reload(self): return with open(self.path, "r", encoding="utf-8-sig", newline='') as file: - reader = csv.DictReader(file) + reader = csv.DictReader(file, skipinitialspace=True) for row in reader: # Support loading old CSV format with "name, text"-columns prompt = row["prompt"] if "prompt" in row else row["text"] From 30410fd355c08c639ae40ce4be08033a70463885 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Wed, 17 May 2023 22:54:32 +0300 Subject: [PATCH 0260/2418] simplify name pattern setting tooltips --- javascript/hints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index b00b504a4e9..7b6f37ad912 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -66,8 +66,8 @@ titles = { "Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.", - "Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [denoising], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime], [datetime
    PathOld valueNew value
    {path}{old_value}{new_value}
    No changes
    {html.escape(description)}

    Added: {html.escape(added)}

    {install_code}
    Extension URLVersionBranchVersionDate Update
    {html.escape(ext.name)} {remote}{ext.branch} {version_link}{time.asctime(time.gmtime(ext.commit_date))}
    - + @@ -170,7 +173,7 @@ def extension_table(): code += f""" - + From 4bd490c28dd8f17b7df943eb3963c34d725084fc Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 27 Jun 2023 06:18:43 +0300 Subject: [PATCH 0568/2418] add missing infotext entry for the pad cond/uncond option --- modules/generation_parameters_copypaste.py | 1 + modules/sd_samplers_kdiffusion.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index a638f912186..dd30a1b5f8f 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -357,6 +357,7 @@ def parse_generation_parameters(x: str): ('Token merging ratio hr', 'token_merging_ratio_hr'), ('RNG', 'randn_source'), ('NGMS', 's_min_uncond'), + ('Pad conds', 'pad_cond_uncond'), ] diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index f8a0c7ba820..71581b76359 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -69,6 +69,7 @@ def __init__(self, model): self.init_latent = None self.step = 0 self.image_cfg_scale = None + self.padded_cond_uncond = False def combine_denoised(self, x_out, conds_list, uncond, cond_scale): denoised_uncond = x_out[-uncond.shape[0]:] @@ -133,15 +134,17 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): x_in = x_in[:-batch_size] sigma_in = sigma_in[:-batch_size] - # TODO add infotext entry + self.padded_cond_uncond = False if shared.opts.pad_cond_uncond and tensor.shape[1] != uncond.shape[1]: empty = shared.sd_model.cond_stage_model_empty_prompt num_repeats = (tensor.shape[1] - uncond.shape[1]) // empty.shape[1] if num_repeats < 0: tensor = torch.cat([tensor, empty.repeat((tensor.shape[0], -num_repeats, 1))], axis=1) + self.padded_cond_uncond = True elif num_repeats > 0: uncond = torch.cat([uncond, empty.repeat((uncond.shape[0], num_repeats, 1))], axis=1) + self.padded_cond_uncond = True if tensor.shape[1] == uncond.shape[1] or skip_uncond: if is_edit_model: @@ -405,6 +408,9 @@ def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, samples = self.launch_sampling(t_enc + 1, lambda: self.func(self.model_wrap_cfg, xi, extra_args=extra_args, disable=False, callback=self.callback_state, **extra_params_kwargs)) + if self.model_wrap_cfg.padded_cond_uncond: + p.extra_generation_params["Pad conds"] = True + return samples def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): @@ -438,5 +444,8 @@ def sample(self, p, x, conditioning, unconditional_conditioning, steps=None, ima 's_min_uncond': self.s_min_uncond }, disable=False, callback=self.callback_state, **extra_params_kwargs)) + if self.model_wrap_cfg.padded_cond_uncond: + p.extra_generation_params["Pad conds"] = True + return samples From 9bb1fcfad43103778406ace17e6804c67fad9c17 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 27 Jun 2023 08:59:35 +0300 Subject: [PATCH 0569/2418] alternate fix for catch errors when retrieving extension index #11290 --- modules/ui_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index f3db76f270b..278bf5e459d 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -571,9 +571,9 @@ def create_ui(): available_extensions_table = gr.HTML() refresh_available_extensions_button.click( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]), + fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update(), gr.update()]), inputs=[available_extensions_index, hide_tags, sort_column], - outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result, search_extensions_text], + outputs=[available_extensions_index, available_extensions_table, hide_tags, search_extensions_text, install_result], ) install_extension_button.click( From 24129368f1b732be25ef486edb2cf5a6ace66737 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 27 Jun 2023 09:19:04 +0300 Subject: [PATCH 0570/2418] send tensors to the correct device when loading from safetensors file with memmap disabled for #11260 --- modules/sd_models.py | 4 +++- modules/shared.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 0391398ae72..f65f4e3639e 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -246,11 +246,13 @@ def read_metadata_from_safetensors(filename): def read_state_dict(checkpoint_file, print_global_state=False, map_location=None): _, extension = os.path.splitext(checkpoint_file) if extension.lower() == ".safetensors": + device = map_location or shared.weight_load_location or devices.get_optimal_device_name() + if not shared.opts.disable_mmap_load_safetensors: - device = map_location or shared.weight_load_location or devices.get_optimal_device_name() pl_sd = safetensors.torch.load_file(checkpoint_file, device=device) else: pl_sd = safetensors.torch.load(open(checkpoint_file, 'rb').read()) + pl_sd = {k: v.to(device) for k, v in pl_sd.items()} else: pl_sd = torch.load(checkpoint_file, map_location=map_location or shared.weight_load_location) diff --git a/modules/shared.py b/modules/shared.py index 4743a428e30..203ee1b9e56 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -376,7 +376,7 @@ def list_samplers(): "multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job."), "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), "list_hidden_files": OptionInfo(True, "Load models/files in hidden directories").info("directory is hidden if its name starts with \".\""), - "disable_mmap_load_safetensors": OptionInfo(False, "Disable memmapping for loading .safetensors files (fixes very slow loading speed in some cases)."), + "disable_mmap_load_safetensors": OptionInfo(False, "Disable memmapping for loading .safetensors files.").info("fixes very slow loading speed in some cases"), })) options_templates.update(options_section(('training', "Training"), { From d06af4e517865277d0521642c2c5513af9afd76f Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Tue, 27 Jun 2023 09:26:18 +0300 Subject: [PATCH 0571/2418] fix and rework #11113 --- modules/api/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index f96056b6a32..1d4aeff5909 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -522,9 +522,9 @@ def get_config(self): return options def set_config(self, req: Dict[str, Any]): - checkpoint_key="sd_model_checkpoint" - if checkpoint_key in req and str(req[checkpoint_key]) not in checkpoint_alisases: - raise RuntimeError(f"model {v!r} not found") + checkpoint_name = req.get("sd_model_checkpoint", None) + if checkpoint_name is not None and checkpoint_name not in checkpoint_alisases: + raise RuntimeError(f"model {checkpoint_name!r} not found") for k, v in req.items(): shared.opts.set(k, v) From da14f6a6632e67cacaeaac7441344f0848f66114 Mon Sep 17 00:00:00 2001 From: NoCrypt <57245077+NoCrypt@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:16:44 +0700 Subject: [PATCH 0572/2418] Add options to change colors in grid --- modules/images.py | 13 +++++-------- modules/shared.py | 5 ++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/images.py b/modules/images.py index 1906e2ab5ff..320008be734 100644 --- a/modules/images.py +++ b/modules/images.py @@ -10,7 +10,7 @@ import numpy as np import piexif import piexif.helper -from PIL import Image, ImageFont, ImageDraw, PngImagePlugin +from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin import string import json import hashlib @@ -156,10 +156,10 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): while drawing.multiline_textsize(line.text, font=fnt)[0] > line.allowed_width and fontsize > 0: fontsize -= 1 fnt = get_font(fontsize) - drawing.multiline_text((draw_x, draw_y + line.size[1] / 2), line.text, font=fnt, fill=color_active if line.is_active else color_inactive, anchor="mm", align="center") + drawing.multiline_text((draw_x, draw_y + line.size[1] / 2), line.text, font=fnt, fill=ImageColor.getcolor(opts.grid_text_color_active, 'RGB') if line.is_active else ImageColor.getcolor(opts.grid_text_color_inactive, 'RGB'), anchor="mm", align="center") if not line.is_active: - drawing.line((draw_x - line.size[0] // 2, draw_y + line.size[1] // 2, draw_x + line.size[0] // 2, draw_y + line.size[1] // 2), fill=color_inactive, width=4) + drawing.line((draw_x - line.size[0] // 2, draw_y + line.size[1] // 2, draw_x + line.size[0] // 2, draw_y + line.size[1] // 2), fill=ImageColor.getcolor(opts.grid_text_color_inactive, 'RGB'), width=4) draw_y += line.size[1] + line_spacing @@ -168,9 +168,6 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): fnt = get_font(fontsize) - color_active = (0, 0, 0) - color_inactive = (153, 153, 153) - pad_left = 0 if sum([sum([len(line.text) for line in lines]) for lines in ver_texts]) == 0 else width * 3 // 4 cols = im.width // width @@ -179,7 +176,7 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' assert rows == len(ver_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' - calc_img = Image.new("RGB", (1, 1), "white") + calc_img = Image.new("RGB", (1, 1), ImageColor.getcolor(opts.grid_background, 'RGB')) calc_d = ImageDraw.Draw(calc_img) for texts, allowed_width in zip(hor_texts + ver_texts, [width] * len(hor_texts) + [pad_left] * len(ver_texts)): @@ -200,7 +197,7 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): pad_top = 0 if sum(hor_text_heights) == 0 else max(hor_text_heights) + line_spacing * 2 - result = Image.new("RGB", (im.width + pad_left + margin * (cols-1), im.height + pad_top + margin * (rows-1)), "white") + result = Image.new("RGB", (im.width + pad_left + margin * (cols-1), im.height + pad_top + margin * (rows-1)), ImageColor.getcolor(opts.grid_background, 'RGB')) for row in range(rows): for col in range(cols): diff --git a/modules/shared.py b/modules/shared.py index 203ee1b9e56..4a83cca4fb7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -413,6 +413,10 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors"), + "font": OptionInfo("", "Font for image grids that have text"), + "grid_text_color_active": OptionInfo("#000000", "Text color for image grids", ui_components.FormColorPicker, {}), + "grid_text_color_inactive": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}), + "grid_background": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}), })) options_templates.update(options_section(('optimizations', "Optimizations"), { @@ -471,7 +475,6 @@ def list_samplers(): "do_not_show_images": OptionInfo(False, "Do not show any images in results for web"), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), - "font": OptionInfo("", "Font for image grids that have text"), "js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"), "js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"), "js_modal_lightbox_gamepad": OptionInfo(False, "Navigate image viewer with gamepad"), From b0ec69b360835a901a1aa57df1f7c8c9d55bf31c Mon Sep 17 00:00:00 2001 From: hako-mikan <122196982+hako-mikan@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:37:08 +0900 Subject: [PATCH 0573/2418] add 'before_hr callback' script callback --- modules/processing.py | 3 +++ modules/scripts.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index 8da738842cf..35463c37ca7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1074,6 +1074,9 @@ def save_intermediate(image, index): sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) + if self.scripts is not None: + self.scripts.before_hr(self) + samples = self.sampler.sample_img2img(self, samples, noise, self.hr_c, self.hr_uc, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) diff --git a/modules/scripts.py b/modules/scripts.py index 99bf836af7b..6485f3984ef 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -186,6 +186,11 @@ def elem_id(self, item_id): return f'script_{tabname}{title}_{item_id}' + def before_hr(self, p ,*args): + """ + This function is called before hires fix start. + """ + pass current_basedir = paths.script_path @@ -548,6 +553,15 @@ def reload_sources(self, cache): self.scripts[si].args_to = args_to + def before_hr(self, p): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.before_hr(p, *script_args) + except Exception: + errors.report(f"Error running before_hr: {script.filename}", exc_info=True) + + scripts_txt2img: ScriptRunner = None scripts_img2img: ScriptRunner = None scripts_postproc: scripts_postprocessing.ScriptPostprocessingRunner = None From 24d4475bdb623b321bc3fdf7205ae4f3221b6dd5 Mon Sep 17 00:00:00 2001 From: catalpaaa Date: Wed, 28 Jun 2023 03:15:01 -0700 Subject: [PATCH 0574/2418] fixing --subpath on newer gradio version --- webui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webui.py b/webui.py index 136d036d53e..02880b85f49 100644 --- a/webui.py +++ b/webui.py @@ -359,7 +359,11 @@ def api_only(): modules.script_callbacks.app_started_callback(None, app) print(f"Startup time: {startup_timer.summary()}.") - api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861) + api.launch( + server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", + port=cmd_opts.port if cmd_opts.port else 7861, + root_path = f"/{cmd_opts.subpath}" + ) def stop_route(request): @@ -403,6 +407,7 @@ def webui(): "docs_url": "/docs", "redoc_url": "/redoc", }, + root_path = f"/{cmd_opts.subpath}", ) if cmd_opts.add_stop_route: app.add_route("/_stop", stop_route, methods=["POST"]) @@ -436,11 +441,6 @@ def webui(): timer.startup_record = startup_timer.dump() print(f"Startup time: {startup_timer.summary()}.") - if cmd_opts.subpath: - redirector = FastAPI() - redirector.get("/") - gradio.mount_gradio_app(redirector, shared.demo, path=f"/{cmd_opts.subpath}") - try: while True: server_command = shared.state.wait_for_server_command(timeout=5) From 45ab7475d61fe42b70c37541974c03736cf73189 Mon Sep 17 00:00:00 2001 From: NoCrypt <57245077+NoCrypt@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:55:58 +0700 Subject: [PATCH 0575/2418] Revision --- modules/images.py | 13 +++++++++---- modules/shared.py | 6 +++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/images.py b/modules/images.py index 320008be734..913c3c2fcf8 100644 --- a/modules/images.py +++ b/modules/images.py @@ -139,6 +139,11 @@ def __init__(self, text='', is_active=True): def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0): + + color_active = ImageColor.getcolor(opts.grid_text_active_color, 'RGB') + color_inactive = ImageColor.getcolor(opts.grid_text_inactive_color, 'RGB') + color_background = ImageColor.getcolor(opts.grid_background_color, 'RGB') + def wrap(drawing, text, font, line_length): lines = [''] for word in text.split(): @@ -156,10 +161,10 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): while drawing.multiline_textsize(line.text, font=fnt)[0] > line.allowed_width and fontsize > 0: fontsize -= 1 fnt = get_font(fontsize) - drawing.multiline_text((draw_x, draw_y + line.size[1] / 2), line.text, font=fnt, fill=ImageColor.getcolor(opts.grid_text_color_active, 'RGB') if line.is_active else ImageColor.getcolor(opts.grid_text_color_inactive, 'RGB'), anchor="mm", align="center") + drawing.multiline_text((draw_x, draw_y + line.size[1] / 2), line.text, font=fnt, fill=color_active if line.is_active else color_inactive, anchor="mm", align="center") if not line.is_active: - drawing.line((draw_x - line.size[0] // 2, draw_y + line.size[1] // 2, draw_x + line.size[0] // 2, draw_y + line.size[1] // 2), fill=ImageColor.getcolor(opts.grid_text_color_inactive, 'RGB'), width=4) + drawing.line((draw_x - line.size[0] // 2, draw_y + line.size[1] // 2, draw_x + line.size[0] // 2, draw_y + line.size[1] // 2), fill=color_inactive, width=4) draw_y += line.size[1] + line_spacing @@ -176,7 +181,7 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): assert cols == len(hor_texts), f'bad number of horizontal texts: {len(hor_texts)}; must be {cols}' assert rows == len(ver_texts), f'bad number of vertical texts: {len(ver_texts)}; must be {rows}' - calc_img = Image.new("RGB", (1, 1), ImageColor.getcolor(opts.grid_background, 'RGB')) + calc_img = Image.new("RGB", (1, 1), color_background) calc_d = ImageDraw.Draw(calc_img) for texts, allowed_width in zip(hor_texts + ver_texts, [width] * len(hor_texts) + [pad_left] * len(ver_texts)): @@ -197,7 +202,7 @@ def draw_texts(drawing, draw_x, draw_y, lines, initial_fnt, initial_fontsize): pad_top = 0 if sum(hor_text_heights) == 0 else max(hor_text_heights) + line_spacing * 2 - result = Image.new("RGB", (im.width + pad_left + margin * (cols-1), im.height + pad_top + margin * (rows-1)), ImageColor.getcolor(opts.grid_background, 'RGB')) + result = Image.new("RGB", (im.width + pad_left + margin * (cols-1), im.height + pad_top + margin * (rows-1)), color_background) for row in range(rows): for col in range(cols): diff --git a/modules/shared.py b/modules/shared.py index 4a83cca4fb7..22e6bd0b024 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -414,9 +414,9 @@ def list_samplers(): "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors"), "font": OptionInfo("", "Font for image grids that have text"), - "grid_text_color_active": OptionInfo("#000000", "Text color for image grids", ui_components.FormColorPicker, {}), - "grid_text_color_inactive": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}), - "grid_background": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}), + "grid_text_active_color": OptionInfo("#000000", "Text color for image grids", ui_components.FormColorPicker, {}), + "grid_text_inactive_color": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}), + "grid_background_color": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}), })) options_templates.update(options_section(('optimizations', "Optimizations"), { From d22eb8a17f8d8c0e8018d9f9c71f7a96108544ee Mon Sep 17 00:00:00 2001 From: NoCrypt <57245077+NoCrypt@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:57:34 +0700 Subject: [PATCH 0576/2418] Fix lint --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 913c3c2fcf8..3e6988fce2a 100644 --- a/modules/images.py +++ b/modules/images.py @@ -139,7 +139,7 @@ def __init__(self, text='', is_active=True): def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0): - + color_active = ImageColor.getcolor(opts.grid_text_active_color, 'RGB') color_inactive = ImageColor.getcolor(opts.grid_text_inactive_color, 'RGB') color_background = ImageColor.getcolor(opts.grid_background_color, 'RGB') From f74fb5049506b85a98b02b1c2fd7361e9f751980 Mon Sep 17 00:00:00 2001 From: NoCrypt <57245077+NoCrypt@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:24:57 +0700 Subject: [PATCH 0577/2418] Move change colors options to Saving images/grids --- modules/shared.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 22e6bd0b024..76d8e221ff3 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -311,6 +311,10 @@ def list_samplers(): "grid_prevent_empty_spots": OptionInfo(False, "Prevent empty spots in grid (when set to autodetect)"), "grid_zip_filename_pattern": OptionInfo("", "Archive filename pattern", component_args=hide_dirs).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Images-Filename-Name-and-Subdirectory"), "n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), + "font": OptionInfo("", "Font for image grids that have text"), + "grid_text_active_color": OptionInfo("#000000", "Text color for image grids", ui_components.FormColorPicker, {}), + "grid_text_inactive_color": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}), + "grid_background_color": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}), "enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"), "save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."), @@ -413,10 +417,6 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors"), - "font": OptionInfo("", "Font for image grids that have text"), - "grid_text_active_color": OptionInfo("#000000", "Text color for image grids", ui_components.FormColorPicker, {}), - "grid_text_inactive_color": OptionInfo("#999999", "Inactive text color for image grids", ui_components.FormColorPicker, {}), - "grid_background_color": OptionInfo("#ffffff", "Background color for image grids", ui_components.FormColorPicker, {}), })) options_templates.update(options_section(('optimizations', "Optimizations"), { From 9c2a7f1e8bafcb59e566bf568fdefe1be95905fe Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:37:20 +0900 Subject: [PATCH 0578/2418] add callback after_extra_networks_activate --- modules/extra_networks.py | 3 +++ modules/scripts.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 1f093df2785..41799b0a984 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -103,6 +103,9 @@ def activate(p, extra_network_data): except Exception as e: errors.display(e, f"activating extra network {extra_network_name}") + if p.scripts is not None: + p.scripts.after_extra_networks_activate(p, batch_number=p.iteration, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds, extra_network_data=extra_network_data) + def deactivate(p, extra_network_data): """call deactivate for extra networks in extra_network_data in specified order, then call diff --git a/modules/scripts.py b/modules/scripts.py index 99bf836af7b..340f1480bba 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -116,6 +116,21 @@ def before_process_batch(self, p, *args, **kwargs): pass + def after_extra_networks_activate(self, p, *args, **kwargs): + """ + Calledafter extra networks activation, before conds calculation + allow modification of the network after extra networks activation been applied + won't be call if p.disable_extra_networks + + **kwargs will have those items: + - batch_number - index of current batch, from 0 to number of batches-1 + - prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things + - seeds - list of seeds for current batch + - subseeds - list of subseeds for current batch + - extra_network_data - list of ExtraNetworkParams for current stage + """ + pass + def process_batch(self, p, *args, **kwargs): """ Same as process(), but called for every batch. @@ -483,6 +498,14 @@ def before_process_batch(self, p, **kwargs): except Exception: errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True) + def after_extra_networks_activate(self, p, **kwargs): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.after_extra_networks_activate(p, *script_args, **kwargs) + except Exception: + errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True) + def process_batch(self, p, **kwargs): for script in self.alwayson_scripts: try: From 0b0767939d4cc0868a10b6c0978f7b2d963dea1a Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 28 Jun 2023 17:51:27 -0600 Subject: [PATCH 0579/2418] Correctly remove end parenthesis with ctrl+up/down --- javascript/edit-attention.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index ffa73147ff2..8906c8922e1 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -100,11 +100,12 @@ function keyupEditAttention(event) { if (String(weight).length == 1) weight += ".0"; if (closeCharacter == ')' && weight == 1) { - text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + 5); + var endParenPos = text.substring(selectionEnd).indexOf(')'); + text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + endParenPos + 1); selectionStart--; selectionEnd--; } else { - text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + 1 + end - 1); + text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + end); } target.focus(); From cc9c1719786de00d4a5bfcf83be4bf2808cf0cb5 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:21:28 +0900 Subject: [PATCH 0580/2418] rename --add-stop-route to --api-server-stop --- .github/workflows/run_tests.yaml | 2 +- modules/api/api.py | 2 +- modules/cmd_args.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 965460116e1..178c026ae61 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -42,7 +42,7 @@ jobs: --no-half --disable-opt-split-attention --use-cpu all - --add-stop-route + --api-server-stop 2>&1 | tee output.txt & - name: Run tests run: | diff --git a/modules/api/api.py b/modules/api/api.py index 279c384acb9..adc633db00e 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -202,7 +202,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo]) - if shared.cmd_opts.add_stop_route: + if shared.cmd_opts.api_server_stop: self.add_api_route("/sdapi/v1/server-kill", self.kill_webui, methods=["POST"]) self.add_api_route("/sdapi/v1/server-restart", self.restart_webui, methods=["POST"]) self.add_api_route("/sdapi/v1/server-stop", self.stop_webui, methods=["POST"]) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 624dcb4fe55..278a605ebbc 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -106,4 +106,4 @@ parser.add_argument("--no-hashing", action='store_true', help="disable sha256 hashing of checkpoints to help loading performance", default=False) parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') -parser.add_argument('--add-stop-route', action='store_true', help='enable server stop/restart/kill via api') +parser.add_argument('--api-server-stop', action='store_true', help='enable server stop/restart/kill via api') From 0bc0e652a3d8cde5533af52c4f232c213b9989f0 Mon Sep 17 00:00:00 2001 From: hunshcn Date: Thu, 29 Jun 2023 18:12:55 +0800 Subject: [PATCH 0581/2418] sync default value of process_focal_crop_entropy_weight between ui and api --- modules/textual_inversion/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py index 0d4c3f84b0b..dbd856bd859 100644 --- a/modules/textual_inversion/preprocess.py +++ b/modules/textual_inversion/preprocess.py @@ -7,7 +7,7 @@ from modules.textual_inversion import autocrop -def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None): +def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.15, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None): try: if process_caption: shared.interrogator.load() From d47324b898d057c0f854b9be891f2483a2b7001f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 29 Jun 2023 19:25:18 +0900 Subject: [PATCH 0582/2418] add stars --- modules/ui_extensions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 278bf5e459d..ac239d6487a 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -424,6 +424,7 @@ def search_extensions(filter_text, hide_tags, sort_column): (False, lambda x: x.get('name', 'z')), (True, lambda x: x.get('name', 'z')), (False, lambda x: 'z'), + (True, lambda x: x.get('stars', 0)), ] @@ -451,6 +452,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" for ext in sorted(extlist, key=sort_function, reverse=sort_reverse): name = ext.get("name", "noname") + stars = int(ext.get("stars", 0)) added = ext.get('added', 'unknown') url = ext.get("url", None) description = ext.get("description", "") @@ -478,7 +480,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" code += f""" - + @@ -562,7 +564,7 @@ def create_ui(): with gr.Row(): hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) - sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order", ], type="index") + sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order", "stars"], type="index") with gr.Row(): search_extensions_text = gr.Text(label="Search").style(container=False) From b1c6e39620dd398ae6a2cb1e9236b65a7294cf59 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 29 Jun 2023 19:25:34 +0900 Subject: [PATCH 0583/2418] starts left --- style.css | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index e1df716f135..5073f0f0963 100644 --- a/style.css +++ b/style.css @@ -704,11 +704,24 @@ table.popup-table .link{ margin: 0; } -#available_extensions .date_added{ - opacity: 0.85; +#available_extensions .info{ + margin: 0.5em 0; + display: flex; + margin-top: auto; + opacity: 0.80; font-size: 90%; } +#available_extensions .date_added{ + margin-right: auto; + display: inline-block; +} + +#available_extensions .star_count{ + margin-left: auto; + display: inline-block; +} + /* replace original footer with ours */ footer { From 0416a7bfbaecab20a4ae4cd8330faee556bb3d89 Mon Sep 17 00:00:00 2001 From: Akiba Date: Thu, 29 Jun 2023 18:46:52 +0800 Subject: [PATCH 0584/2418] fix can't get current 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 97539e68f20..0e0dbca496d 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -142,7 +142,7 @@ def git_clone(url, dir, name, commithash=None): if commithash is None: return - current_hash = run(f'"{git}" -C "{dir}" rev-parse HEAD', None, f"Couldn't determine {name}'s hash: {commithash}").strip() + current_hash = run(f'"{git}" -C "{dir}" rev-parse HEAD', None, f"Couldn't determine {name}'s hash: {commithash}", live=False).strip() if current_hash == commithash: return From 2ccc832b3333fe520961466aa1f05b24aafdd792 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:46:59 +0900 Subject: [PATCH 0585/2418] add extensions Update Created dates with sorting --- modules/ui_extensions.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index ac239d6487a..dff522ef83a 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -424,10 +424,19 @@ def search_extensions(filter_text, hide_tags, sort_column): (False, lambda x: x.get('name', 'z')), (True, lambda x: x.get('name', 'z')), (False, lambda x: 'z'), + (True, lambda x: x.get('commit_time', '')), + (True, lambda x: x.get('created_at', '')), (True, lambda x: x.get('stars', 0)), ] +def get_date(info: dict, key): + try: + return datetime.strptime(info.get(key), "%Y-%m-%dT%H:%M:%SZ").strftime("%Y-%m-%d") + except (ValueError, TypeError): + return '' + + def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""): extlist = available_extensions["extensions"] installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions} @@ -454,6 +463,8 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" name = ext.get("name", "noname") stars = int(ext.get("stars", 0)) added = ext.get('added', 'unknown') + update_time = get_date(ext, 'commit_time') + create_time = get_date(ext, 'created_at') url = ext.get("url", None) description = ext.get("description", "") extension_tags = ext.get("tags", []) @@ -480,7 +491,8 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" code += f""" - + @@ -564,7 +576,7 @@ def create_ui(): with gr.Row(): hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) - sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order", "stars"], type="index") + sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index") with gr.Row(): search_extensions_text = gr.Text(label="Search").style(container=False) From 8a07c59baa670f8ed54757f7ac7580b27ecac3dd Mon Sep 17 00:00:00 2001 From: gshawn3 <133769806+gshawn3@users.noreply.github.com> Date: Fri, 30 Jun 2023 03:49:26 -0700 Subject: [PATCH 0586/2418] fix for #11534: canvas zoom and pan extension hijacking shortcut keys --- .../canvas-zoom-and-pan/javascript/zoom.js | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 5ebd2073c1b..ed3e52bcbae 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -608,23 +608,29 @@ onUiLoaded(async() => { // Handle keydown events function handleKeyDown(event) { - const hotkeyActions = { - [hotkeysConfig.canvas_hotkey_reset]: resetZoom, - [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, - [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen - }; + // before activating shortcut, ensure user is not actively typing in an input field + if(event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { + event.preventDefault; + } else { - const action = hotkeyActions[event.code]; - if (action) { - event.preventDefault(); - action(event); - } + const hotkeyActions = { + [hotkeysConfig.canvas_hotkey_reset]: resetZoom, + [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, + [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen + }; - if ( - isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) || - isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust) - ) { - event.preventDefault(); + const action = hotkeyActions[event.code]; + if (action) { + event.preventDefault(); + action(event); + } + + if ( + isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) || + isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust) + ) { + event.preventDefault(); + } } } @@ -687,10 +693,15 @@ onUiLoaded(async() => { // Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element. function handleMoveKeyDown(e) { if (e.code === hotkeysConfig.canvas_hotkey_move) { - if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { - e.preventDefault(); - document.activeElement.blur(); - isMoving = true; + // before activating shortcut, ensure user is not actively typing in an input field + if(e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { + event.preventDefault; + } else { + if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { + e.preventDefault(); + document.activeElement.blur(); + isMoving = true; + } } } } From 7f46f81dd7b517e829395734750f0eb8360675d4 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Sat, 1 Jul 2023 17:20:56 -0600 Subject: [PATCH 0587/2418] Change default seed_resize to 0 --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 8da738842cf..9e838aada3b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -109,7 +109,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 = False, tiling: bool = False, 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 = 1.0, 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 = 0, seed_resize_from_w: int = 0, 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 = False, tiling: bool = False, 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 = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = 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) From 74d001bc68c2106aa963e3474eee70327b8f3760 Mon Sep 17 00:00:00 2001 From: ramyma Date: Sun, 2 Jul 2023 04:59:59 +0300 Subject: [PATCH 0588/2418] Hotfix: call processing close to cleanup API generation calls --- modules/api/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/api/api.py b/modules/api/api.py index 279c384acb9..f10e3fe3b9e 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -335,6 +335,7 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() + p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] @@ -392,6 +393,7 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() + p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] From 8519d52ef505204be53c68e58edc6569ca5cfb32 Mon Sep 17 00:00:00 2001 From: Danil Boldyrev Date: Sun, 2 Jul 2023 19:20:49 +0300 Subject: [PATCH 0589/2418] fixing the copy/paste function, correct code --- .../canvas-zoom-and-pan/javascript/zoom.js | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index ed3e52bcbae..29f43a3fd69 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -608,29 +608,34 @@ onUiLoaded(async() => { // Handle keydown events function handleKeyDown(event) { + // Disable key locks to make pasting from the buffer work correctly + if ((event.ctrlKey && event.code === 'KeyV') || event.code === "F5") { + return; + } + // before activating shortcut, ensure user is not actively typing in an input field - if(event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { - event.preventDefault; - } else { + if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { + return; + } - const hotkeyActions = { - [hotkeysConfig.canvas_hotkey_reset]: resetZoom, - [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, - [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen - }; - const action = hotkeyActions[event.code]; - if (action) { - event.preventDefault(); - action(event); - } + const hotkeyActions = { + [hotkeysConfig.canvas_hotkey_reset]: resetZoom, + [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, + [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen + }; - if ( - isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) || - isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust) - ) { - event.preventDefault(); - } + const action = hotkeyActions[event.code]; + if (action) { + event.preventDefault(); + action(event); + } + + if ( + isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) || + isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust) + ) { + event.preventDefault(); } } @@ -692,16 +697,22 @@ onUiLoaded(async() => { // Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element. function handleMoveKeyDown(e) { + + // Disable key locks to make pasting from the buffer work correctly + if ((e.ctrlKey && e.code === 'KeyV') || e.code === "F5") { + return; + } + + // before activating shortcut, ensure user is not actively typing in an input field + if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { + return; + } + if (e.code === hotkeysConfig.canvas_hotkey_move) { - // before activating shortcut, ensure user is not actively typing in an input field - if(e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { - event.preventDefault; - } else { - if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { - e.preventDefault(); - document.activeElement.blur(); - isMoving = true; - } + if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { + e.preventDefault(); + document.activeElement.blur(); + isMoving = true; } } } From 5a32d4fcb195f7ee5be2617d9f776c01fd0437b7 Mon Sep 17 00:00:00 2001 From: onyasumi Date: Mon, 3 Jul 2023 07:15:19 +0000 Subject: [PATCH 0590/2418] Fix launch script to be runnable from any directory --- webui.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/webui.sh b/webui.sh index 5c8d977cb2d..8a3c6f1202a 100755 --- a/webui.sh +++ b/webui.sh @@ -4,26 +4,28 @@ # change the variables in webui-user.sh instead # ################################################# +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + # If run from macOS, load defaults from webui-macos-env.sh if [[ "$OSTYPE" == "darwin"* ]]; then - if [[ -f webui-macos-env.sh ]] + if [[ -f "$SCRIPT_DIR"/webui-macos-env.sh ]] then - source ./webui-macos-env.sh + source "$SCRIPT_DIR"/webui-macos-env.sh fi fi # Read variables from webui-user.sh # shellcheck source=/dev/null -if [[ -f webui-user.sh ]] +if [[ -f "$SCRIPT_DIR"/webui-user.sh ]] then - source ./webui-user.sh + source "$SCRIPT_DIR"/webui-user.sh fi # Set defaults # Install directory without trailing slash if [[ -z "${install_dir}" ]] then - install_dir="$(pwd)" + install_dir="$(dirname "$0")" fi # Name of the subdirectory (defaults to stable-diffusion-webui) From e33e2c51753b91d836aabc52f1f8d67d7de05f86 Mon Sep 17 00:00:00 2001 From: Frank Tao <48634762+onyasumi@users.noreply.github.com> Date: Mon, 3 Jul 2023 03:17:27 -0400 Subject: [PATCH 0591/2418] Update webui.sh --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index 8a3c6f1202a..246381fc275 100755 --- a/webui.sh +++ b/webui.sh @@ -25,7 +25,7 @@ fi # Install directory without trailing slash if [[ -z "${install_dir}" ]] then - install_dir="$(dirname "$0")" + install_dir="$SCRIPT_DIR" fi # Name of the subdirectory (defaults to stable-diffusion-webui) From b70001e618d0f0015273e1313cc7ebe3002a4510 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 30 Jun 2023 13:44:58 +0300 Subject: [PATCH 0592/2418] Add SD_WEBUI_LOG_LEVEL envvar --- webui.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/webui.py b/webui.py index bad29f28441..1b44d4ad2ef 100644 --- a/webui.py +++ b/webui.py @@ -18,6 +18,17 @@ import logging +# We can't use cmd_opts for this because it will not have been initialized at this point. +log_level = os.environ.get("SD_WEBUI_LOG_LEVEL") +if log_level: + log_level = getattr(logging, log_level.upper(), None) or logging.INFO + logging.basicConfig( + level=log_level, + format='%(asctime)s %(levelname)s [%(name)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + ) + +logging.getLogger("torch.distributed.nn").setLevel(logging.ERROR) # sshh... logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) from modules import paths, timer, import_hook, errors, devices # noqa: F401 From f44feb6a10aacc6a5ff4c9275fba2546b2858935 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 30 Jun 2023 13:11:31 +0300 Subject: [PATCH 0593/2418] Add job argument to State.begin() --- modules/api/api.py | 14 +++++++------- modules/call_queue.py | 2 +- modules/extras.py | 3 +-- modules/interrogate.py | 3 +-- modules/postprocessing.py | 3 +-- modules/shared.py | 4 ++-- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 279c384acb9..3ea099ad8c4 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -327,7 +327,7 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples - shared.state.begin() + shared.state.begin(job="scripts_txt2img") if selectable_scripts is not None: p.script_args = script_args processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here @@ -384,7 +384,7 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples - shared.state.begin() + shared.state.begin(job="scripts_img2img") if selectable_scripts is not None: p.script_args = script_args processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here @@ -599,7 +599,7 @@ def refresh_checkpoints(self): def create_embedding(self, args: dict): try: - shared.state.begin() + shared.state.begin(job="create_embedding") filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used shared.state.end() @@ -610,7 +610,7 @@ def create_embedding(self, args: dict): def create_hypernetwork(self, args: dict): try: - shared.state.begin() + shared.state.begin(job="create_hypernetwork") filename = create_hypernetwork(**args) # create empty embedding shared.state.end() return models.CreateResponse(info=f"create hypernetwork filename: {filename}") @@ -620,7 +620,7 @@ def create_hypernetwork(self, args: dict): def preprocess(self, args: dict): try: - shared.state.begin() + shared.state.begin(job="preprocess") preprocess(**args) # quick operation unless blip/booru interrogation is enabled shared.state.end() return models.PreprocessResponse(info = 'preprocess complete') @@ -636,7 +636,7 @@ def preprocess(self, args: dict): def train_embedding(self, args: dict): try: - shared.state.begin() + shared.state.begin(job="train_embedding") apply_optimizations = shared.opts.training_xattention_optimizations error = None filename = '' @@ -657,7 +657,7 @@ def train_embedding(self, args: dict): def train_hypernetwork(self, args: dict): try: - shared.state.begin() + shared.state.begin(job="train_hypernetwork") shared.loaded_hypernetworks = [] apply_optimizations = shared.opts.training_xattention_optimizations error = None diff --git a/modules/call_queue.py b/modules/call_queue.py index 69bf63d2b9e..3b94f8a4c8e 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -30,7 +30,7 @@ def f(*args, **kwargs): id_task = None with queue_lock: - shared.state.begin() + shared.state.begin(job=id_task) progress.start_task(id_task) try: diff --git a/modules/extras.py b/modules/extras.py index 830b53aa2b6..e9c0263ec7d 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -73,8 +73,7 @@ def to_half(tensor, enable): def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae, discard_weights, save_metadata): - shared.state.begin() - shared.state.job = 'model-merge' + shared.state.begin(job="model-merge") def fail(message): shared.state.textinfo = message diff --git a/modules/interrogate.py b/modules/interrogate.py index 9b2c5b60efb..a3ae1dd5c4c 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -184,8 +184,7 @@ def generate_caption(self, pil_image): def interrogate(self, pil_image): res = "" - shared.state.begin() - shared.state.job = 'interrogate' + shared.state.begin(job="interrogate") try: if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: lowvram.send_everything_to_cpu() diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 736315e2d7a..544b2f7208c 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -9,8 +9,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, show_extras_results, *args, save_output: bool = True): devices.torch_gc() - shared.state.begin() - shared.state.job = 'extras' + shared.state.begin(job="extras") image_data = [] image_names = [] diff --git a/modules/shared.py b/modules/shared.py index 203ee1b9e56..7df2879cc8c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -173,7 +173,7 @@ def dict(self): return obj - def begin(self): + def begin(self, job: str = "(unknown)"): self.sampling_step = 0 self.job_count = -1 self.processing_has_refined_job_count = False @@ -187,7 +187,7 @@ def begin(self): self.interrupted = False self.textinfo = None self.time_start = time.time() - + self.job = job devices.torch_gc() def end(self): From e4303443477b9ac3c90ec4dd58a4810f7ac1eabe Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 30 Jun 2023 13:11:49 +0300 Subject: [PATCH 0594/2418] API: use finally: for state.end() --- modules/api/api.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 3ea099ad8c4..8b79495d846 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -602,37 +602,35 @@ def create_embedding(self, args: dict): shared.state.begin(job="create_embedding") filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used - shared.state.end() return models.CreateResponse(info=f"create embedding filename: {filename}") except AssertionError as e: - shared.state.end() return models.TrainResponse(info=f"create embedding error: {e}") + finally: + shared.state.end() + def create_hypernetwork(self, args: dict): try: shared.state.begin(job="create_hypernetwork") filename = create_hypernetwork(**args) # create empty embedding - shared.state.end() return models.CreateResponse(info=f"create hypernetwork filename: {filename}") except AssertionError as e: - shared.state.end() return models.TrainResponse(info=f"create hypernetwork error: {e}") + finally: + shared.state.end() def preprocess(self, args: dict): try: shared.state.begin(job="preprocess") preprocess(**args) # quick operation unless blip/booru interrogation is enabled shared.state.end() - return models.PreprocessResponse(info = 'preprocess complete') + return models.PreprocessResponse(info='preprocess complete') except KeyError as e: - shared.state.end() return models.PreprocessResponse(info=f"preprocess error: invalid token: {e}") - except AssertionError as e: - shared.state.end() + except Exception as e: return models.PreprocessResponse(info=f"preprocess error: {e}") - except FileNotFoundError as e: + finally: shared.state.end() - return models.PreprocessResponse(info=f'preprocess error: {e}') def train_embedding(self, args: dict): try: @@ -649,11 +647,11 @@ def train_embedding(self, args: dict): finally: if not apply_optimizations: sd_hijack.apply_optimizations() - shared.state.end() return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") - except AssertionError as msg: - shared.state.end() + except Exception as msg: return models.TrainResponse(info=f"train embedding error: {msg}") + finally: + shared.state.end() def train_hypernetwork(self, args: dict): try: @@ -675,9 +673,10 @@ def train_hypernetwork(self, args: dict): sd_hijack.apply_optimizations() shared.state.end() return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") - except AssertionError: + except Exception as exc: + return models.TrainResponse(info=f"train embedding error: {exc}") + finally: shared.state.end() - return models.TrainResponse(info=f"train embedding error: {error}") def get_memory(self): try: From 522a8b9f629940a205812b5b023f25c051f3c8d8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 30 Jun 2023 13:24:17 +0300 Subject: [PATCH 0595/2418] Add a status logger in modules.shared --- modules/shared.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/shared.py b/modules/shared.py index 7df2879cc8c..9ab9d98b6ef 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -4,6 +4,7 @@ import sys import threading import time +import logging import gradio as gr import torch @@ -18,6 +19,8 @@ from ldm.models.diffusion.ddpm import LatentDiffusion from typing import Optional +log = logging.getLogger(__name__) + demo = None parser = cmd_args.parser @@ -144,12 +147,15 @@ def wait_for_server_command(self, timeout: Optional[float] = None) -> Optional[s def request_restart(self) -> None: self.interrupt() self.server_command = "restart" + log.info("Received restart request") def skip(self): self.skipped = True + log.info("Received skip request") def interrupt(self): self.interrupted = True + log.info("Received interrupt request") def nextjob(self): if opts.live_previews_enable and opts.show_progress_every_n_steps == -1: @@ -189,8 +195,11 @@ def begin(self, job: str = "(unknown)"): self.time_start = time.time() self.job = job devices.torch_gc() + log.info("Starting job %s", job) def end(self): + duration = time.time() - self.time_start + log.info("Ending job %s (%.2f seconds)", self.job, duration) self.job = "" self.job_count = 0 From 08f9b705cda4277aed49ed00c405ada2925e3b50 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 3 Jul 2023 13:08:28 +0300 Subject: [PATCH 0596/2418] Use read_info_from_image in postprocessing Avoids bad keys such as `exif` ending up in the "PNG info" passed forward --- modules/postprocessing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 736315e2d7a..38544c3899f 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -54,7 +54,9 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, for image, name in zip(image_data, image_names): shared.state.textinfo = name - existing_pnginfo = image.info or {} + parameters, existing_pnginfo = images.read_info_from_image(image) + if parameters: + existing_pnginfo["parameters"] = parameters pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB")) From b2c574891f492d00e310e387a024638a7bcf2353 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 3 Jul 2023 13:09:37 +0300 Subject: [PATCH 0597/2418] read_info_from_image: add `photoshop` to ignored --- modules/images.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/images.py b/modules/images.py index 1906e2ab5ff..ac53a3c558e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -662,6 +662,13 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) return fullfn, txt_fullfn +IGNORED_INFO_KEYS = { + 'jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif', + 'loop', 'background', 'timestamp', 'duration', 'progressive', 'progression', + 'icc_profile', 'chromaticity', 'photoshop', +} + + def read_info_from_image(image): items = image.info or {} @@ -679,9 +686,7 @@ def read_info_from_image(image): items['exif comment'] = exif_comment geninfo = exif_comment - for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif', - 'loop', 'background', 'timestamp', 'duration', 'progressive', 'progression', - 'icc_profile', 'chromaticity']: + for field in IGNORED_INFO_KEYS: items.pop(field, None) if items.get("Software", None) == "NovelAI": From 96f0593c8fcfb5d31da9731d995c6d6f2ad77829 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 3 Jul 2023 13:10:20 +0300 Subject: [PATCH 0598/2418] read_info_from_image: add type --- modules/images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index ac53a3c558e..74a10a7b1bd 100644 --- a/modules/images.py +++ b/modules/images.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import pytz @@ -669,7 +671,7 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) } -def read_info_from_image(image): +def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: items = image.info or {} geninfo = items.pop('parameters', None) From 5c6a33b3e11f5aa7b2fc56753c5a724e1351ce81 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 3 Jul 2023 13:10:42 +0300 Subject: [PATCH 0599/2418] read_info_from_image: don't mutate info in passed-in image --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 74a10a7b1bd..ec421993433 100644 --- a/modules/images.py +++ b/modules/images.py @@ -672,7 +672,7 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: - items = image.info or {} + items = (image.info or {}).copy() geninfo = items.pop('parameters', None) From 32788873176e9d79e1fffd6f89f94b6d0ec8bb91 Mon Sep 17 00:00:00 2001 From: ramyma Date: Mon, 3 Jul 2023 20:02:30 +0300 Subject: [PATCH 0600/2418] Handle cleanup in case there's an exception thrown --- modules/api/api.py | 57 +++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index f10e3fe3b9e..d9278e9ef87 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -323,19 +323,21 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): with self.queue_lock: p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) - p.scripts = script_runner - p.outpath_grids = opts.outdir_txt2img_grids - p.outpath_samples = opts.outdir_txt2img_samples - - shared.state.begin() - if selectable_scripts is not None: - p.script_args = script_args - processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here - else: - p.script_args = tuple(script_args) # Need to pass args as tuple here - processed = process_images(p) - shared.state.end() - p.close() + try: + p.scripts = script_runner + p.outpath_grids = opts.outdir_txt2img_grids + p.outpath_samples = opts.outdir_txt2img_samples + + shared.state.begin() + if selectable_scripts is not None: + p.script_args = script_args + processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + shared.state.end() + finally: + p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] @@ -380,20 +382,23 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): with self.queue_lock: p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) - p.init_images = [decode_base64_to_image(x) for x in init_images] - p.scripts = script_runner - p.outpath_grids = opts.outdir_img2img_grids - p.outpath_samples = opts.outdir_img2img_samples + try: + p.init_images = [decode_base64_to_image(x) for x in init_images] + p.scripts = script_runner + p.outpath_grids = opts.outdir_img2img_grids + p.outpath_samples = opts.outdir_img2img_samples + + shared.state.begin() + if selectable_scripts is not None: + p.script_args = script_args + processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + shared.state.end() - shared.state.begin() - if selectable_scripts is not None: - p.script_args = script_args - processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here - else: - p.script_args = tuple(script_args) # Need to pass args as tuple here - processed = process_images(p) - shared.state.end() - p.close() + finally: + p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] From c1c04928596f69ddb39b8841a8435ecefb0594e9 Mon Sep 17 00:00:00 2001 From: ramyma Date: Mon, 3 Jul 2023 20:17:47 +0300 Subject: [PATCH 0601/2418] Use contextlib for closing the generation process --- modules/api/api.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index d9278e9ef87..e92c2938855 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -30,6 +30,7 @@ from typing import Dict, List, Any import piexif import piexif.helper +from contextlib import closing def script_name_to_index(name, scripts): @@ -322,8 +323,7 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): args.pop('save_images', None) with self.queue_lock: - p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) - try: + with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: p.scripts = script_runner p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples @@ -336,8 +336,6 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end() - finally: - p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] @@ -381,8 +379,7 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): args.pop('save_images', None) with self.queue_lock: - p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) - try: + with closing(StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args)) as p: p.init_images = [decode_base64_to_image(x) for x in init_images] p.scripts = script_runner p.outpath_grids = opts.outdir_img2img_grids @@ -397,8 +394,6 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): processed = process_images(p) shared.state.end() - finally: - p.close() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] From f731a728c68035ee36317ed0096ac5ecbfd50553 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Mon, 3 Jul 2023 11:41:10 -0600 Subject: [PATCH 0602/2418] Check seed_resize_from <= 0 --- modules/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 9e838aada3b..dc55212120c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -109,7 +109,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 = 0, seed_resize_from_w: int = 0, 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 = False, tiling: bool = False, 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 = 1.0, 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 = False, tiling: bool = False, 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 = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = 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) @@ -573,7 +573,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Model": (None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')), "Variation seed": (None if p.subseed_strength == 0 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}"), + "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}"), "Denoising strength": getattr(p, 'denoising_strength', None), "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None, "Clip skip": None if clip_skip <= 1 else clip_skip, From f325783abd828c3b90b4d0aa19031401c0ba4c4c Mon Sep 17 00:00:00 2001 From: Danil Boldyrev Date: Tue, 4 Jul 2023 22:26:43 +0300 Subject: [PATCH 0603/2418] made the blur function optional, added exclusion buttons --- .../canvas-zoom-and-pan/javascript/zoom.js | 20 ++++++++++++------- .../scripts/hotkey_config.py | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 29f43a3fd69..30199dcd60a 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -200,7 +200,8 @@ onUiLoaded(async() => { canvas_hotkey_move: "KeyF", canvas_hotkey_overlap: "KeyO", canvas_disabled_functions: [], - canvas_show_tooltip: true + canvas_show_tooltip: true, + canvas_blur_prompt: false }; const functionMap = { @@ -609,13 +610,15 @@ onUiLoaded(async() => { // Handle keydown events function handleKeyDown(event) { // Disable key locks to make pasting from the buffer work correctly - if ((event.ctrlKey && event.code === 'KeyV') || event.code === "F5") { + if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") { return; } // before activating shortcut, ensure user is not actively typing in an input field - if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { - return; + if (!hotkeysConfig.canvas_blur_prompt) { + if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { + return; + } } @@ -699,15 +702,18 @@ onUiLoaded(async() => { function handleMoveKeyDown(e) { // Disable key locks to make pasting from the buffer work correctly - if ((e.ctrlKey && e.code === 'KeyV') || e.code === "F5") { + if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && event.code === 'KeyC') || e.code === "F5") { return; } // before activating shortcut, ensure user is not actively typing in an input field - if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { - return; + if (!hotkeysConfig.canvas_blur_prompt) { + if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { + return; + } } + if (e.code === hotkeysConfig.canvas_hotkey_move) { if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { e.preventDefault(); diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py index 1b6683aa9c3..380176ce26c 100644 --- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py +++ b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py @@ -9,5 +9,6 @@ "canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"), "canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, neededs for testing"), "canvas_show_tooltip": shared.OptionInfo(True, "Enable tooltip on the canvas"), + "canvas_blur_prompt": shared.OptionInfo(False, "Take the focus off the prompt when working with a canvas"), "canvas_disabled_functions": shared.OptionInfo(["Overlap"], "Disable function that you don't use", gr.CheckboxGroup, {"choices": ["Zoom","Adjust brush size", "Moving canvas","Fullscreen","Reset Zoom","Overlap"]}), })) From c602471b85d270e8c36707817d9bad92b0ff991e Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 5 Jul 2023 03:19:26 -0600 Subject: [PATCH 0604/2418] Allow gif for extra network previews --- modules/ui_extra_networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index a7d3bc792f9..1efd00b0353 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -30,8 +30,8 @@ def fetch_file(filename: str = ""): raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.") ext = os.path.splitext(filename)[1].lower() - if ext not in (".png", ".jpg", ".jpeg", ".webp"): - raise ValueError(f"File cannot be fetched: {filename}. Only png and jpg and webp.") + if ext not in (".png", ".jpg", ".jpeg", ".webp", ".gif"): + raise ValueError(f"File cannot be fetched: {filename}. Only png, jpg, webp, and gif.") # would profit from returning 304 return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) From fb661e089f24a3056b9724c580e3badc214467cc Mon Sep 17 00:00:00 2001 From: semjon00 Date: Wed, 5 Jul 2023 15:39:04 +0300 Subject: [PATCH 0605/2418] Fix throwing exception when trying to resize image with I;16 mode --- modules/images.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/images.py b/modules/images.py index 1906e2ab5ff..91e3fae26e9 100644 --- a/modules/images.py +++ b/modules/images.py @@ -639,12 +639,18 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) oversize = image.width > opts.target_side_length or image.height > opts.target_side_length if opts.export_for_4chan and (oversize or os.stat(fullfn).st_size > opts.img_downscale_threshold * 1024 * 1024): ratio = image.width / image.height - + resize_to = None if oversize and ratio > 1: - image = image.resize((round(opts.target_side_length), round(image.height * opts.target_side_length / image.width)), LANCZOS) + resize_to = round(opts.target_side_length), round(image.height * opts.target_side_length / image.width) elif oversize: - image = image.resize((round(image.width * opts.target_side_length / image.height), round(opts.target_side_length)), LANCZOS) + resize_to = round(image.width * opts.target_side_length / image.height), round(opts.target_side_length) + if resize_to is not None: + try: + # Resizing image with LANCZOS could throw an exception if e.g. image mode is I;16 + image = image.resize(resize_to, LANCZOS) + except: + image = image.resize(resize_to) try: _atomically_save_image(image, fullfn_without_extension, ".jpg") except Exception as e: From daf41a273485e865c9c9ef458b2c26be4422bcb2 Mon Sep 17 00:00:00 2001 From: Hao-Wu Date: Thu, 6 Jul 2023 15:37:10 +0800 Subject: [PATCH 0606/2418] Fix warning of 'has_mps' is deprecated from PyTorch --- modules/mac_specific.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index d74c6b95513..735847f54ff 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -4,16 +4,21 @@ from packaging import version -# has_mps is only available in nightly pytorch (for now) and macOS 12.3+. -# check `getattr` and try it for compatibility +# before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+, +# use check `getattr` and try it for compatibility. +# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availabilty, +# since torch 2.0.1+ nightly build, getattr(torch, 'has_mps', False) was deprecated, see https://github.com/pytorch/pytorch/pull/103279 def check_for_mps() -> bool: - if not getattr(torch, 'has_mps', False): - return False - try: - torch.zeros(1).to(torch.device("mps")) - return True - except Exception: - return False + if version.parse(torch.__version__) <= version.parse("2.0.1"): + if not getattr(torch, 'has_mps', False): + return False + try: + torch.zeros(1).to(torch.device("mps")) + return True + except Exception: + return False + else: + return torch.backends.mps.is_available() and torch.backends.mps.is_built() has_mps = check_for_mps() From 259967b7c60cbd2aeb091e691b5f49d9fb64b872 Mon Sep 17 00:00:00 2001 From: jovijovi Date: Thu, 6 Jul 2023 18:43:17 +0800 Subject: [PATCH 0607/2418] fix(api): convert to "RGB" if image mode is "RGBA" --- modules/api/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/api/api.py b/modules/api/api.py index 2e49526e3e2..6507f641cb6 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -84,6 +84,8 @@ def encode_pil_to_base64(image): image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality) elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"): + if image.mode == "RGBA": + image = image.convert("RGB") parameters = image.info.get('parameters', None) exif_bytes = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(parameters or "", encoding="unicode") } From c258dd34a888b7c6c9e4c9bbef76732d9d7db6e7 Mon Sep 17 00:00:00 2001 From: Neil Mahseth Date: Thu, 6 Jul 2023 22:02:47 +0530 Subject: [PATCH 0608/2418] Fix UnicodeEncodeError when writing to file CLIP Interrogator Batch Mode The code snippet print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a')) raises a UnicodeEncodeError with the message "'charmap' codec can't encode character '\u016b' in position 129". This error occurs because the default encoding used by the open() function cannot handle certain Unicode characters. To fix this issue, the encoding parameter needs to be explicitly specified when opening the file. By using an appropriate encoding, such as 'utf-8', we can ensure that Unicode characters are properly encoded and written to the file. The updated code should be modified as follows: python Copy code print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8')) By making this change, the code will no longer raise the UnicodeEncodeError and will correctly handle Unicode characters during the file write operation. --- modules/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui.py b/modules/ui.py index e2e3b6da312..10e35ec3eb0 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -155,7 +155,7 @@ def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_di img = Image.open(image) filename = os.path.basename(image) left, _ = os.path.splitext(filename) - print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a')) + print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8')) return [gr.update(), None] From f4391796416a998903fcd3e3e0dc7e8cca3614f2 Mon Sep 17 00:00:00 2001 From: gitama2023 <138025603+gitama2023@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:18:01 +0800 Subject: [PATCH 0609/2418] Added a prompt for users using poor scaling Added a JavaScript file that detects browser scaling and prompts users when scale is not 100% --- javascript/badScaleChecker.js | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 javascript/badScaleChecker.js diff --git a/javascript/badScaleChecker.js b/javascript/badScaleChecker.js new file mode 100644 index 00000000000..625ad309da1 --- /dev/null +++ b/javascript/badScaleChecker.js @@ -0,0 +1,108 @@ +(function() { + var ignore = localStorage.getItem("bad-scale-ignore-it") == "ignore-it"; + + function getScale() { + var ratio = 0, + screen = window.screen, + ua = navigator.userAgent.toLowerCase(); + + if (window.devicePixelRatio !== undefined) { + ratio = window.devicePixelRatio; + } else if (~ua.indexOf('msie')) { + if (screen.deviceXDPI && screen.logicalXDPI) { + ratio = screen.deviceXDPI / screen.logicalXDPI; + } + } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) { + ratio = window.outerWidth / window.innerWidth; + } + + return ratio == 0 ? 0 : Math.round(ratio * 100); + } + + var showing = false; + + var div = document.createElement("div"); + div.style.position = "fixed"; + div.style.top = "0px"; + div.style.left = "0px"; + div.style.width = "100vw"; + div.style.backgroundColor = "firebrick"; + div.style.textAlign = "center"; + div.style.zIndex = 99; + + var b = document.createElement("b"); + b.innerHTML = 'Bad Scale: ??% '; + + div.appendChild(b); + + var note1 = document.createElement("p"); + note1.innerHTML = "Change your browser or your computer settings!"; + note1.title = 'Just make sure "computer-scale" * "browser-scale" = 100% ,\n' + + "you can keep your computer-scale and only change this page's scale,\n" + + "for example: your computer-scale is 125%, just use [\"CTRL\"+\"-\"] to make your browser-scale of this page to 80%."; + div.appendChild(note1); + + var note2 = document.createElement("p"); + note2.innerHTML = " Otherwise, it will cause this page to not function properly!"; + note2.title = "When you click \"Copy image to: [inpaint sketch]\" in some img2img's tab,\n" + + "if scale<100% the canvas will be invisible,\n" + + "else if scale>100% this page will take large amount of memory and CPU performance."; + div.appendChild(note2); + + var btn = document.createElement("button"); + btn.innerHTML = "Click here to ignore"; + + div.appendChild(btn); + + function tryShowTopBar(scale) { + if (showing) return; + + b.innerHTML = 'Bad Scale: ' + scale + '% '; + + var updateScaleTimer = setInterval(function() { + var newScale = getScale(); + b.innerHTML = 'Bad Scale: ' + newScale + '% '; + if (newScale == 100) { + var p = div.parentNode; + if (p != null) p.removeChild(div); + showing = false; + clearInterval(updateScaleTimer); + check(); + } + }, 999); + + btn.onclick = function() { + clearInterval(updateScaleTimer); + var p = div.parentNode; + if (p != null) p.removeChild(div); + ignore = true; + showing = false; + localStorage.setItem("bad-scale-ignore-it", "ignore-it"); + }; + + document.body.appendChild(div); + } + + function check() { + if (!ignore) { + var timer = setInterval(function() { + var scale = getScale(); + if (scale != 100 && !ignore) { + tryShowTopBar(scale); + clearInterval(timer); + } + if (ignore) { + clearInterval(timer); + } + }, 999); + } + } + + if (document.readyState != "complete") { + document.onreadystatechange = function() { + if (document.readyState != "complete") check(); + }; + } else { + check(); + } +})(); From a369a0cf658c4371a3b037ded40e22323b6ebce0 Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Fri, 7 Jul 2023 09:04:49 -0700 Subject: [PATCH 0610/2418] Add a link to an index-able/crawl-able wiki mirroring service of the wiki At the moment, the wiki is editable by GitHub users, so it is blocked from indexing. If you are searching for something related to this repo, Google and other search engines will not use the content for it. This link hack just sticks a link on the README so search engines may prioritize it. At the moment, 0 pages from GitHub are index and only 7 pages from my proxy service are. If you add this, the rest should get indexed. An indexable page looks like this: https://github-wiki-see.page/m/AUTOMATIC1111/stable-diffusion-webui/wiki/Command-Line-Arguments-and-Settings. It is not meant to be read, just indexed, and users are expected to click through to the GitHub copy. https://github-wiki-see.page/ has more information about the situation. I built the tool and I'm happy to answer any questions I can. Similar: https://github.com/MiSTer-devel/Main_MiSTer#main_mister-main-binary-and-wiki-repo:~:text=For%20the%20purposes%20of%20getting%20google%20to%20crawl%20the%20wiki%2C%20here%27s%20a%20link%20to%20the%20(not%20for%20humans)%20crawlable%20wiki --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 73d94960ea3..e6d8e4bd423 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,11 @@ Find the instructions [here](https://github.com/AUTOMATIC1111/stable-diffusion-w Here's how to add code to this repo: [Contributing](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Contributing) ## Documentation + The documentation was moved from this README over to the project's [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki). +For the purposes of getting Google and other search engines to crawl the wiki, here's a link to the (not for humans) [crawlable wiki](https://github-wiki-see.page/m/AUTOMATIC1111/stable-diffusion-webui/wiki). + ## Credits Licenses for borrowed code can be found in `Settings -> Licenses` screen, and also in `html/licenses.html` file. From 19772c3c97647bdda76cd7f652ae517840431e88 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 13:43:42 +0300 Subject: [PATCH 0611/2418] fix problem with extra network saving images as previews losing generation info add a description for save_image_with_geninfo --- modules/images.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 1906e2ab5ff..04f55f14908 100644 --- a/modules/images.py +++ b/modules/images.py @@ -497,13 +497,23 @@ def get_next_sequence_number(path, basename): return result + 1 -def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_pnginfo=None): +def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_pnginfo=None, pnginfo_section_name='parameters'): + """ + Saves image to filename, including geninfo as text information for generation info. + For PNG images, geninfo is added to existing pnginfo dictionary using the pnginfo_section_name argument as key. + For JPG images, there's no dictionary and geninfo just replaces the EXIF description. + """ + if extension is None: extension = os.path.splitext(filename)[1] image_format = Image.registered_extensions()[extension] if extension.lower() == '.png': + existing_pnginfo = existing_pnginfo or {} + if opts.enable_pnginfo: + existing_pnginfo[pnginfo_section_name] = geninfo + if opts.enable_pnginfo: pnginfo_data = PngImagePlugin.PngInfo() for k, v in (existing_pnginfo or {}).items(): @@ -622,7 +632,7 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) """ temp_file_path = f"{filename_without_extension}.tmp" - save_image_with_geninfo(image_to_save, info, temp_file_path, extension, params.pnginfo) + save_image_with_geninfo(image_to_save, info, temp_file_path, extension, existing_pnginfo=params.pnginfo, pnginfo_section_name=pnginfo_section_name) os.replace(temp_file_path, filename_without_extension + extension) From 7a7fa25d02d469533dab5084bbd08d96d2df45a2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 14:21:40 +0300 Subject: [PATCH 0612/2418] lint fix for #11492 --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index fdccec0927f..9a5d958526d 100644 --- a/modules/images.py +++ b/modules/images.py @@ -661,7 +661,7 @@ def _atomically_save_image(image_to_save, filename_without_extension, extension) try: # Resizing image with LANCZOS could throw an exception if e.g. image mode is I;16 image = image.resize(resize_to, LANCZOS) - except: + except Exception: image = image.resize(resize_to) try: _atomically_save_image(image, fullfn_without_extension, ".jpg") From 3602602260abaa325850e4768b7e253834e207d0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 14:44:02 +0300 Subject: [PATCH 0613/2418] whitespace for #11477 --- modules/scripts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/scripts.py b/modules/scripts.py index d96f88b0260..a07adc4254f 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -187,7 +187,7 @@ def elem_id(self, item_id): return f'script_{tabname}{title}_{item_id}' - def before_hr(self, p ,*args): + def before_hr(self, p, *args): """ This function is called before hires fix start. """ From 18256c5f0174126cb103afece2b39b6b831e034a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 14:58:33 +0300 Subject: [PATCH 0614/2418] fix for #11478 --- webui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui.py b/webui.py index b02ae7a5437..34c2fd18130 100644 --- a/webui.py +++ b/webui.py @@ -43,7 +43,7 @@ startup_timer.record("import torch") -import gradio +import gradio # noqa: F401 startup_timer.record("import gradio") import ldm.modules.encoders.modules # noqa: F401 @@ -413,7 +413,7 @@ def webui(): "docs_url": "/docs", "redoc_url": "/redoc", }, - root_path = f"/{cmd_opts.subpath}", + root_path=f"/{cmd_opts.subpath}" if cmd_opts.subpath else "", ) # after initial launch, disable --autolaunch for subsequent restarts From b88645d9ebddfa26aaf6ee25519a95c967a23138 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 15:14:14 +0300 Subject: [PATCH 0615/2418] additional changes for merge conflict for #11337 --- modules/img2img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/img2img.py b/modules/img2img.py index b8ea3a3cd7d..5e18bab9cb4 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -230,7 +230,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if is_batch: assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" - process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir) + process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_input_dir, png_info_props=img2img_batch_output_dir, png_info_dir=img2img_batch_inpaint_mask_dir) processed = Processed(p, [], p.seed, "") else: From 9043b91649f35adaa732d811184e81afb7a34b71 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 15:14:24 +0300 Subject: [PATCH 0616/2418] additional changes for merge conflict for #11337 --- modules/ui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index c752a64d03a..e83f26518c3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -934,13 +934,13 @@ def select_img2img_tab(tab): inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, - img2img_batch_use_png_info, - img2img_batch_png_info_props, - img2img_batch_png_info_dir, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, override_settings, + img2img_batch_use_png_info, + img2img_batch_png_info_props, + img2img_batch_png_info_dir, ] + custom_inputs, outputs=[ img2img_gallery, From 1d71c36de2d7bbbcd290ba4dc5afd8ba909c74f8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 15:21:29 +0300 Subject: [PATCH 0617/2418] third time's the charm --- modules/img2img.py | 2 +- modules/ui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 5e18bab9cb4..881212fc069 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -230,7 +230,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if is_batch: assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" - process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_input_dir, png_info_props=img2img_batch_output_dir, png_info_dir=img2img_batch_inpaint_mask_dir) + process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir) processed = Processed(p, [], p.seed, "") else: diff --git a/modules/ui.py b/modules/ui.py index e83f26518c3..39d226ada79 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -733,7 +733,7 @@ def update_orig(image, state): img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") - with gr.Accordion("PNG info"): + with gr.Accordion("PNG info", open=False): img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", **shared.hide_dirs, elem_id="img2img_batch_use_png_info") img2img_batch_png_info_dir = gr.Textbox(label="PNG info directory", **shared.hide_dirs, placeholder="Leave empty to use input directory", elem_id="img2img_batch_png_info_dir") img2img_batch_png_info_props = gr.CheckboxGroup(["Prompt", "Negative prompt", "Seed", "CFG scale", "Sampler", "Steps"], label="Parameters to take from png info", info="Prompts from png info will be appended to prompts set in ui.") From 274a3e21babe5fa913b4a34d49b5d7cd72c5fa89 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 15:42:00 +0300 Subject: [PATCH 0618/2418] small rework for img2img PNG info --- modules/img2img.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 881212fc069..a5f1c148a90 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -96,27 +96,16 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal info_img = Image.open(info_img_path) geninfo, _ = imgutil.read_info_from_image(info_img) parsed_parameters = parse_generation_parameters(geninfo) - if("Prompt" in png_info_props): - p.prompt = prompt + " " + parsed_parameters["Prompt"] - if("Negative prompt" in png_info_props): - p.negative_prompt = negative_prompt + " " + parsed_parameters["Negative prompt"] - if("Seed" in png_info_props): - p.seed = int(parsed_parameters["Seed"]) - if("CFG scale" in png_info_props): - p.cfg_scale = float(parsed_parameters["CFG scale"]) - if("Sampler" in png_info_props): - p.sampler_name = parsed_parameters["Sampler"] - if("Steps" in png_info_props): - p.steps = int(parsed_parameters["Steps"]) - except Exception as e: - print(f"batch png info: using ui set prompts; failed to get png info for {image}") - print(e) - p.prompt = prompt - p.negative_prompt = negative_prompt - p.seed = seed - p.cfg_scale = cfg_scale - p.sampler_name = sampler_name - p.steps = steps + parsed_parameters = {k: v for k, v in parsed_parameters.items() if k in (png_info_props or {})} + except Exception: + parsed_parameters = {} + + p.prompt = prompt + (" " + parsed_parameters["Prompt"] if "Prompt" in parsed_parameters else "") + p.negative_prompt = negative_prompt + (" " + parsed_parameters["Negative prompt"] if "Negative prompt" in parsed_parameters else "") + p.seed = int(parsed_parameters.get("Seed", seed)) + p.cfg_scale = float(parsed_parameters.get("CFG scale", cfg_scale)) + p.sampler_name = parsed_parameters.get("Sampler", sampler_name) + p.steps = int(parsed_parameters.get("Steps", steps)) proc = modules.scripts.scripts_img2img.run(p, *args) if proc is None: From 7a6abc59ea1ecd8bb311de1719b018fb5960cd80 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 16:15:28 +0300 Subject: [PATCH 0619/2418] for #10650: change key to alt+arrows, enable by default --- javascript/edit-order.js | 6 +++++- modules/shared.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/edit-order.js b/javascript/edit-order.js index 50f7fe37ecc..ad983d33c86 100644 --- a/javascript/edit-order.js +++ b/javascript/edit-order.js @@ -1,8 +1,12 @@ +/* alt+left/right moves text in prompt */ + function keyupEditOrder(event) { if (!opts.keyedit_move) return; + let target = event.originalTarget || event.composedPath()[0]; if (!target.matches("*:is([id*='_toprow'] [id*='_prompt'], .prompt) textarea")) return; - if (!event.metaKey && !event.ctrlKey) return; + if (!event.altKey) return; + event.preventDefault() let isLeft = event.key == "ArrowLeft"; let isRight = event.key == "ArrowRight"; diff --git a/modules/shared.py b/modules/shared.py index b9c53875a65..b29c33075b1 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -494,7 +494,7 @@ def list_samplers(): "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_delimiters": OptionInfo(".,\\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), - "keyedit_move": OptionInfo(False, "Ctrl+left/right moves prompt elements"), + "keyedit_move": OptionInfo(True, "Alt+left/right moves prompt elements"), "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", ui_components.DropdownMulti, lambda: {"choices": list(opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that appear at the top of page rather than in settings tab").needs_restart(), "ui_tab_order": OptionInfo([], "UI tab order", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}).needs_restart(), "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": list(tab_names)}).needs_restart(), From d7d6e8cfc8b85a99a48f82975ee213d487783c28 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 16:45:59 +0300 Subject: [PATCH 0620/2418] use natural sort for shared.walk_files and shared.listfiles, as well as for dirs in extra networks --- extensions-builtin/Lora/lora.py | 2 +- modules/shared.py | 14 +++++++++++--- modules/ui_extra_networks.py | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 34ff57dd0e7..cd46e6c7eee 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -443,7 +443,7 @@ def list_available_loras(): os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) - for filename in sorted(candidates, key=str.lower): + for filename in candidates: if os.path.isdir(filename): continue diff --git a/modules/shared.py b/modules/shared.py index b29c33075b1..48478a68b0d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,6 +1,7 @@ import datetime import json import os +import re import sys import threading import time @@ -832,8 +833,12 @@ def clear(self): mem_mon.start() +def natural_sort_key(s, regex=re.compile('([0-9]+)')): + return [int(text) if text.isdigit() else text.lower() for text in regex.split(s)] + + def listfiles(dirname): - filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=str.lower) if not x.startswith(".")] + filenames = [os.path.join(dirname, x) for x in sorted(os.listdir(dirname), key=natural_sort_key) if not x.startswith(".")] return [file for file in filenames if os.path.isfile(file)] @@ -858,8 +863,11 @@ def walk_files(path, allowed_extensions=None): if allowed_extensions is not None: allowed_extensions = set(allowed_extensions) - for root, _, files in os.walk(path, followlinks=True): - for filename in files: + items = list(os.walk(path, followlinks=True)) + items = sorted(items, key=lambda x: natural_sort_key(x[0])) + + for root, _, files in items: + for filename in sorted(files, key=natural_sort_key): if allowed_extensions is not None: _, ext = os.path.splitext(filename) if ext not in allowed_extensions: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 1efd00b0353..693cafb643c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -90,8 +90,8 @@ def create_html(self, tabname): subdirs = {} for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: - for root, dirs, _ in os.walk(parentdir, followlinks=True): - for dirname in dirs: + for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])): + for dirname in sorted(dirs, key=shared.natural_sort_key): x = os.path.join(root, dirname) if not os.path.isdir(x): From e161b5a0259c870b9d01408d02c504c3281dbdb1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 16:54:03 +0300 Subject: [PATCH 0621/2418] rework #10436 to use shared.walk_files --- modules/img2img.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 3b83814bf25..ef87eb0f951 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -18,13 +18,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None): processing.fix_seed(p) - images = [] - for root, directories, files in os.walk(input_dir): - for filename in files: - filepath = os.path.join(root, filename) - if filepath.endswith(".jpg") or filepath.endswith(".jpeg") or filepath.endswith(".png") or filepath.endswith(".webp"): - images.append(filepath) - + images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp"))) is_inpaint_batch = False if inpaint_mask_dir: From da8916f92649fc4d947cb46d9d8f8ea1621b2a59 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 17:13:18 +0300 Subject: [PATCH 0622/2418] added torch.mps.empty_cache() to torch_gc() changed a bunch of places that use torch.cuda.empty_cache() to use torch_gc() instead --- extensions-builtin/LDSR/ldsr_model_arch.py | 8 +++----- extensions-builtin/ScuNET/scripts/scunet_model.py | 4 ++-- extensions-builtin/SwinIR/scripts/swinir_model.py | 5 +---- modules/codeformer_model.py | 2 +- modules/devices.py | 3 +++ modules/sd_models.py | 1 - 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/extensions-builtin/LDSR/ldsr_model_arch.py b/extensions-builtin/LDSR/ldsr_model_arch.py index 7f450086ff9..7cac36ce55a 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -12,7 +12,7 @@ from ldm.models.diffusion.ddim import DDIMSampler from ldm.util import instantiate_from_config, ismap -from modules import shared, sd_hijack +from modules import shared, sd_hijack, devices cached_ldsr_model: torch.nn.Module = None @@ -112,8 +112,7 @@ def super_resolution(self, image, steps=100, target_scale=2, half_attention=Fals gc.collect() - if torch.cuda.is_available: - torch.cuda.empty_cache() + devices.torch_gc() im_og = image width_og, height_og = im_og.size @@ -150,8 +149,7 @@ def super_resolution(self, image, steps=100, target_scale=2, half_attention=Fals del model gc.collect() - if torch.cuda.is_available: - torch.cuda.empty_cache() + devices.torch_gc() return a diff --git a/extensions-builtin/ScuNET/scripts/scunet_model.py b/extensions-builtin/ScuNET/scripts/scunet_model.py index ffef26b2d26..167d2f64b8e 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -85,7 +85,7 @@ def tiled_inference(img, model): def do_upscale(self, img: PIL.Image.Image, selected_file): - torch.cuda.empty_cache() + devices.torch_gc() try: model = self.load_model(selected_file) @@ -110,7 +110,7 @@ def do_upscale(self, img: PIL.Image.Image, selected_file): torch_output = torch_output[:, :h * 1, :w * 1] # remove padding, if any np_output: np.ndarray = torch_output.float().cpu().clamp_(0, 1).numpy() del torch_img, torch_output - torch.cuda.empty_cache() + devices.torch_gc() output = np_output.transpose((1, 2, 0)) # CHW to HWC output = output[:, :, ::-1] # BGR to RGB diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index c6bc53a8850..c2c2a43c1a0 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -42,10 +42,7 @@ def do_upscale(self, img, model_file): return img model = model.to(device_swinir, dtype=devices.dtype) img = upscale(img, model) - try: - torch.cuda.empty_cache() - except Exception: - pass + devices.torch_gc() return img def load_model(self, path, scale=4): diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index f293acf5df1..da42b5e9932 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -99,7 +99,7 @@ def restore(self, np_image, w=None): output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0] restored_face = tensor2img(output, rgb2bgr=True, min_max=(-1, 1)) del output - torch.cuda.empty_cache() + devices.torch_gc() except Exception: errors.report('Failed inference for CodeFormer', exc_info=True) restored_face = tensor2img(cropped_face_t, rgb2bgr=True, min_max=(-1, 1)) diff --git a/modules/devices.py b/modules/devices.py index 620ed1a6326..c5ad950f621 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -49,10 +49,13 @@ def get_device_for(task): def torch_gc(): + if torch.cuda.is_available(): with torch.cuda.device(get_cuda_device_string()): torch.cuda.empty_cache() torch.cuda.ipc_collect() + elif has_mps() and hasattr(torch.mps, 'empty_cache'): + torch.mps.empty_cache() def enable_tf32(): diff --git a/modules/sd_models.py b/modules/sd_models.py index f65f4e3639e..653c4cc01bf 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -590,7 +590,6 @@ def unload_model_weights(sd_model=None, info=None): sd_model = None gc.collect() devices.torch_gc() - torch.cuda.empty_cache() print(f"Unloaded weights {timer.summary()}.") From da468a585bb631bc91c3435f349dfb7ce7fe3895 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 3 Jul 2023 12:17:20 +0300 Subject: [PATCH 0623/2418] Fix typo: checkpoint_alisases --- modules/api/api.py | 4 ++-- modules/processing.py | 2 +- modules/sd_models.py | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 224bbfc6875..5793bb44023 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -22,7 +22,7 @@ from modules.textual_inversion.preprocess import preprocess from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork from PIL import PngImagePlugin,Image -from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights, checkpoint_alisases +from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights, checkpoint_aliases from modules.sd_vae import vae_dict from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models @@ -519,7 +519,7 @@ def get_config(self): def set_config(self, req: Dict[str, Any]): checkpoint_name = req.get("sd_model_checkpoint", None) - if checkpoint_name is not None and checkpoint_name not in checkpoint_alisases: + if checkpoint_name is not None and checkpoint_name not in checkpoint_aliases: raise RuntimeError(f"model {checkpoint_name!r} not found") for k, v in req.items(): diff --git a/modules/processing.py b/modules/processing.py index 21d1492cd74..cd568a208aa 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -606,7 +606,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: try: # if no checkpoint override or the override checkpoint can't be found, remove override entry and load opts checkpoint - if sd_models.checkpoint_alisases.get(p.override_settings.get('sd_model_checkpoint')) is None: + if sd_models.checkpoint_aliases.get(p.override_settings.get('sd_model_checkpoint')) is None: p.override_settings.pop('sd_model_checkpoint', None) sd_models.reload_model_weights() diff --git a/modules/sd_models.py b/modules/sd_models.py index 653c4cc01bf..060e0007c10 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -23,7 +23,8 @@ model_path = os.path.abspath(os.path.join(paths.models_path, model_dir)) checkpoints_list = {} -checkpoint_alisases = {} +checkpoint_aliases = {} +checkpoint_alisases = checkpoint_aliases # for compatibility with old name checkpoints_loaded = collections.OrderedDict() @@ -66,7 +67,7 @@ def __init__(self, filename): def register(self): checkpoints_list[self.title] = self for id in self.ids: - checkpoint_alisases[id] = self + checkpoint_aliases[id] = self def calculate_shorthash(self): self.sha256 = hashes.sha256(self.filename, f"checkpoint/{self.name}") @@ -112,7 +113,7 @@ def alphanumeric_key(key): def list_models(): checkpoints_list.clear() - checkpoint_alisases.clear() + checkpoint_aliases.clear() cmd_ckpt = shared.cmd_opts.ckpt if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): @@ -136,7 +137,7 @@ def list_models(): def get_closet_checkpoint_match(search_string): - checkpoint_info = checkpoint_alisases.get(search_string, None) + checkpoint_info = checkpoint_aliases.get(search_string, None) if checkpoint_info is not None: return checkpoint_info @@ -166,7 +167,7 @@ def select_checkpoint(): """Raises `FileNotFoundError` if no checkpoints are found.""" model_checkpoint = shared.opts.sd_model_checkpoint - checkpoint_info = checkpoint_alisases.get(model_checkpoint, None) + checkpoint_info = checkpoint_aliases.get(model_checkpoint, None) if checkpoint_info is not None: return checkpoint_info From 4da92281f65a5d3620e61aef76dc2ec23394e706 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 17:29:28 +0300 Subject: [PATCH 0624/2418] pin version for torch for Navi3 according to comment from #11228 --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index 4b0d7cd84ea..a683d946d3e 100755 --- a/webui.sh +++ b/webui.sh @@ -134,7 +134,7 @@ case "$gpu_info" in *"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0 ;; *"Navi 3"*) [[ -z "${TORCH_COMMAND}" ]] && \ - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.5" + export TORCH_COMMAND="pip install --pre torch==2.1.0.dev-20230614+rocm5.5 torchvision==0.16.0.dev-20230614+rocm5.5 --index-url https://download.pytorch.org/whl/nightly/rocm5.5" # Navi 3 needs at least 5.5 which is only on the nightly chain ;; *"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0 From 4981c7d3704e50dd93fe1b68d299239a4ded1ec2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 17:52:03 +0300 Subject: [PATCH 0625/2418] move github proxy to settings, System page. --- modules/shared.py | 1 + modules/ui_extensions.py | 33 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index 48478a68b0d..b7518de68b7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -391,6 +391,7 @@ def list_samplers(): "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), "list_hidden_files": OptionInfo(True, "Load models/files in hidden directories").info("directory is hidden if its name starts with \".\""), "disable_mmap_load_safetensors": OptionInfo(False, "Disable memmapping for loading .safetensors files.").info("fixes very slow loading speed in some cases"), + "github_proxy": OptionInfo("None", "Github proxy", ui_components.DropdownEditable, lambda: {"choices": ["None", "ghproxy.com", "hub.yzuu.cf", "hub.njuu.cf", "hub.nuaa.cf"]}).info("for custom inputs will just replace github.com with the input"), })) options_templates.update(options_section(('training', "Training"), { diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index ac523bcf96f..a208012d884 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -325,7 +325,18 @@ def normalize_git_url(url): return url -def install_extension_from_url(dirname, proxy, url, branch_name=None): +def github_proxy(url): + proxy = shared.opts.github_proxy + + if proxy == 'None': + return url + if proxy == 'ghproxy.com': + return "https://ghproxy.com/" + url + + return url.replace('github.com', proxy) + + +def install_extension_from_url(dirname, url, branch_name=None): check_access() if isinstance(dirname, str): @@ -335,18 +346,7 @@ def install_extension_from_url(dirname, proxy, url, branch_name=None): assert url, 'No URL specified' - proxy_list = { - "none": "", - "ghproxy": "https://ghproxy.com/", - "yzuu": "hub.yzuu.cf", - "njuu": "hub.njuu.cf", - "nuaa": "hub.nuaa.cf", - } - - if proxy in ['yzuu', 'njuu', 'nuaa']: - url = url.replace('github.com', proxy_list[proxy]) - elif proxy == 'ghproxy': - url = proxy_list[proxy] + url + url = github_proxy(url) if dirname is None or dirname == "": *parts, last_part = url.split('/') @@ -628,11 +628,6 @@ def create_ui(): ) with gr.TabItem("Install from URL", id="install_from_url"): - install_proxy = gr.Radio( - label="Install Proxy", choices=["none", "ghproxy", "nuaa", "yzuu", "njuu"], value="none", - info="If you can't access github.com, you can use a proxy to install extensions from github.com" - ) - install_url = gr.Text(label="URL for extension's git repository") install_branch = gr.Text(label="Specific branch name", placeholder="Leave empty for default main branch") install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto") @@ -641,7 +636,7 @@ def create_ui(): install_button.click( fn=modules.ui.wrap_gradio_call(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), - inputs=[install_dirname, install_proxy, install_url, install_branch], + inputs=[install_dirname, install_url, install_branch], outputs=[install_url, extensions_table, install_result], ) From e3507a1be4826f5c196cb8651d932c9af84a5019 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 8 Jul 2023 17:53:17 +0300 Subject: [PATCH 0626/2418] fix for eslint --- javascript/edit-order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/edit-order.js b/javascript/edit-order.js index ad983d33c86..e6e73937637 100644 --- a/javascript/edit-order.js +++ b/javascript/edit-order.js @@ -6,7 +6,7 @@ function keyupEditOrder(event) { let target = event.originalTarget || event.composedPath()[0]; if (!target.matches("*:is([id*='_toprow'] [id*='_prompt'], .prompt) textarea")) return; if (!event.altKey) return; - event.preventDefault() + event.preventDefault(); let isLeft = event.key == "ArrowLeft"; let isRight = event.key == "ArrowRight"; From 44d66daaad3dae283a85329020d1345d08189e32 Mon Sep 17 00:00:00 2001 From: SiYu Wu Date: Sun, 9 Jul 2023 03:05:38 +0800 Subject: [PATCH 0627/2418] add option SWIN_torch_compile to accelerate SwinIR upscale using torch.compile() --- .../SwinIR/scripts/swinir_model.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/extensions-builtin/SwinIR/scripts/swinir_model.py b/extensions-builtin/SwinIR/scripts/swinir_model.py index c2c2a43c1a0..ae0d0e6a8ea 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -1,4 +1,5 @@ import sys +import platform import numpy as np import torch @@ -18,6 +19,8 @@ class UpscalerSwinIR(Upscaler): def __init__(self, dirname): + self._cached_model = None # keep the model when SWIN_torch_compile is on to prevent re-compile every runs + self._cached_model_config = None # to clear '_cached_model' when changing model (v1/v2) or settings self.name = "SwinIR" self.model_url = SWINIR_MODEL_URL self.model_name = "SwinIR 4x" @@ -35,12 +38,24 @@ def __init__(self, dirname): self.scalers = scalers def do_upscale(self, img, model_file): - try: - model = self.load_model(model_file) - except Exception as e: - print(f"Failed loading SwinIR model {model_file}: {e}", file=sys.stderr) - return img - model = model.to(device_swinir, dtype=devices.dtype) + use_compile = hasattr(opts, 'SWIN_torch_compile') and opts.SWIN_torch_compile \ + and int(torch.__version__.split('.')[0]) >= 2 and platform.system() != "Windows" + current_config = (model_file, opts.SWIN_tile) + + if use_compile and self._cached_model_config == current_config: + model = self._cached_model + else: + self._cached_model = None + try: + model = self.load_model(model_file) + except Exception as e: + print(f"Failed loading SwinIR model {model_file}: {e}", file=sys.stderr) + return img + model = model.to(device_swinir, dtype=devices.dtype) + if use_compile: + model = torch.compile(model) + self._cached_model = model + self._cached_model_config = current_config img = upscale(img, model) devices.torch_gc() return img @@ -170,6 +185,8 @@ def on_ui_settings(): shared.opts.add_option("SWIN_tile", shared.OptionInfo(192, "Tile size for all SwinIR.", gr.Slider, {"minimum": 16, "maximum": 512, "step": 16}, section=('upscaling', "Upscaling"))) shared.opts.add_option("SWIN_tile_overlap", shared.OptionInfo(8, "Tile overlap, in pixels for SwinIR. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}, section=('upscaling', "Upscaling"))) + if int(torch.__version__.split('.')[0]) >= 2 and platform.system() != "Windows": # torch.compile() require pytorch 2.0 or above, and not on Windows + shared.opts.add_option("SWIN_torch_compile", shared.OptionInfo(False, "Use torch.compile to accelerate SwinIR.", gr.Checkbox, {"interactive": True}, section=('upscaling', "Upscaling")).info("Takes longer on first run")) script_callbacks.on_ui_settings(on_ui_settings) From 75f56406cede0095eb0e5dcc4e0d5759063e89dc Mon Sep 17 00:00:00 2001 From: wfjsw Date: Sun, 9 Jul 2023 22:40:23 +0800 Subject: [PATCH 0628/2418] Revert Pull Request #11244 Revert "Add github mirror for the download extension" This reverts commit 9ec2ba2d28bb0d8f01e19e2919b7bf2e3e864773. Revert "Update code style" This reverts commit de022c4c80240a430a8099fb27a41aa505bf5b2f. Revert "Update call method" This reverts commit e9bd18c57bd83363d38c7409263fe87f3ed3a7f0. Revert "move github proxy to settings, System page." This reverts commit 4981c7d3704e50dd93fe1b68d299239a4ded1ec2. --- modules/shared.py | 1 - modules/ui_extensions.py | 17 ++--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index b7518de68b7..48478a68b0d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -391,7 +391,6 @@ def list_samplers(): "print_hypernet_extra": OptionInfo(False, "Print extra hypernetwork information to console."), "list_hidden_files": OptionInfo(True, "Load models/files in hidden directories").info("directory is hidden if its name starts with \".\""), "disable_mmap_load_safetensors": OptionInfo(False, "Disable memmapping for loading .safetensors files.").info("fixes very slow loading speed in some cases"), - "github_proxy": OptionInfo("None", "Github proxy", ui_components.DropdownEditable, lambda: {"choices": ["None", "ghproxy.com", "hub.yzuu.cf", "hub.njuu.cf", "hub.nuaa.cf"]}).info("for custom inputs will just replace github.com with the input"), })) options_templates.update(options_section(('training', "Training"), { diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index a208012d884..dff522ef83a 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -325,17 +325,6 @@ def normalize_git_url(url): return url -def github_proxy(url): - proxy = shared.opts.github_proxy - - if proxy == 'None': - return url - if proxy == 'ghproxy.com': - return "https://ghproxy.com/" + url - - return url.replace('github.com', proxy) - - def install_extension_from_url(dirname, url, branch_name=None): check_access() @@ -346,8 +335,6 @@ def install_extension_from_url(dirname, url, branch_name=None): assert url, 'No URL specified' - url = github_proxy(url) - if dirname is None or dirname == "": *parts, last_part = url.split('/') last_part = normalize_git_url(last_part) @@ -367,12 +354,12 @@ def install_extension_from_url(dirname, url, branch_name=None): shutil.rmtree(tmpdir, True) if not branch_name: # if no branch is specified, use the default branch - with git.Repo.clone_from(url, tmpdir, filter=['blob:none'], verbose=False) as repo: + with git.Repo.clone_from(url, tmpdir, filter=['blob:none']) as repo: repo.remote().fetch() for submodule in repo.submodules: submodule.update() else: - with git.Repo.clone_from(url, tmpdir, filter=['blob:none'], branch=branch_name, verbose=False) as repo: + with git.Repo.clone_from(url, tmpdir, filter=['blob:none'], branch=branch_name) as repo: repo.remote().fetch() for submodule in repo.submodules: submodule.update() From 089a0022ae9dd53c7b9b540e251fb0231459e297 Mon Sep 17 00:00:00 2001 From: tangjicheng Date: Mon, 10 Jul 2023 23:10:14 +0900 Subject: [PATCH 0629/2418] add queue lock for refresh-checkpoints --- modules/api/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 2e49526e3e2..7f7e3a9b1ab 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -593,7 +593,8 @@ def convert_embeddings(embeddings): } def refresh_checkpoints(self): - shared.refresh_checkpoints() + with self.queue_lock: + shared.refresh_checkpoints() def create_embedding(self, args: dict): try: From 44c27ebc7393ea793245aa565ace6c9bf1313980 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 10 Jul 2023 20:08:23 +0300 Subject: [PATCH 0630/2418] Use closing() with processing classes everywhere Follows up on #11569 --- modules/hypernetworks/hypernetwork.py | 6 ++++-- modules/img2img.py | 20 +++++++++---------- .../textual_inversion/textual_inversion.py | 6 ++++-- modules/txt2img.py | 11 +++++----- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 51941c11633..79670b87715 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -3,6 +3,7 @@ import html import os import inspect +from contextlib import closing import modules.textual_inversion.dataset import torch @@ -711,8 +712,9 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi preview_text = p.prompt - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images) > 0 else None + with closing(p): + processed = processing.process_images(p) + image = processed.images[0] if len(processed.images) > 0 else None if unload: shared.sd_model.cond_stage_model.to(devices.cpu) diff --git a/modules/img2img.py b/modules/img2img.py index ef87eb0f951..4d9a02ccff5 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -1,4 +1,5 @@ import os +from contextlib import closing from pathlib import Path import numpy as np @@ -217,18 +218,17 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if mask: p.extra_generation_params["Mask blur"] = mask_blur - if is_batch: - assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" + with closing(p): + if is_batch: + assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" - process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir) + process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir) - processed = Processed(p, [], p.seed, "") - else: - processed = modules.scripts.scripts_img2img.run(p, *args) - if processed is None: - processed = process_images(p) - - p.close() + processed = Processed(p, [], p.seed, "") + else: + processed = modules.scripts.scripts_img2img.run(p, *args) + if processed is None: + processed = process_images(p) shared.total_tqdm.clear() diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index bb6f211caa2..cbe975b7540 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -1,5 +1,6 @@ import os from collections import namedtuple +from contextlib import closing import torch import tqdm @@ -584,8 +585,9 @@ def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_st preview_text = p.prompt - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images) > 0 else None + with closing(p): + processed = processing.process_images(p) + image = processed.images[0] if len(processed.images) > 0 else None if unload: shared.sd_model.first_stage_model.to(devices.cpu) diff --git a/modules/txt2img.py b/modules/txt2img.py index 6aa79f23927..d0be2e73fd9 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -1,3 +1,5 @@ +from contextlib import closing + import modules.scripts from modules import sd_samplers, processing from modules.generation_parameters_copypaste import create_override_settings_dict @@ -53,12 +55,11 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step if cmd_opts.enable_console_prompts: print(f"\ntxt2img: {prompt}", file=shared.progress_print_out) - processed = modules.scripts.scripts_txt2img.run(p, *args) - - if processed is None: - processed = processing.process_images(p) + with closing(p): + processed = modules.scripts.scripts_txt2img.run(p, *args) - p.close() + if processed is None: + processed = processing.process_images(p) shared.total_tqdm.clear() From f865d3e11647dfd6c7b2cdf90dde24680e58acd8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 11 Jul 2023 06:23:52 +0300 Subject: [PATCH 0631/2418] add changelog for 1.4.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a31f35bcd3..925403a9138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.4.1 + +### Bug Fixes: + * add queue lock for refresh-checkpoints + ## 1.4.0 ### Features: From 10d4e4ace2d243c020e6a83060c938dee7d8c02d Mon Sep 17 00:00:00 2001 From: TangJicheng Date: Tue, 11 Jul 2023 17:30:57 +0900 Subject: [PATCH 0632/2418] add cmd_args: --timeout-keep-alive --- modules/cmd_args.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index de905caa14e..982d9055cdd 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -107,3 +107,4 @@ parser.add_argument("--no-download-sd-model", action='store_true', help="don't download SD1.5 model even if no model is found in --ckpt-dir", default=False) parser.add_argument('--subpath', type=str, help='customize the subpath for gradio, use with reverse proxy') parser.add_argument('--add-stop-route', action='store_true', help='add /_stop route to stop server') +parser.add_argument('--timeout-keep-alive', type=int, default=30, help='set timeout_keep_alive for uvicorn') From 14501f56aaf3c97fb2c38633350dc747b9651f43 Mon Sep 17 00:00:00 2001 From: TangJicheng Date: Tue, 11 Jul 2023 17:32:04 +0900 Subject: [PATCH 0633/2418] set timeout_keep_alive --- modules/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 7f7e3a9b1ab..4ea5d825f72 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -715,4 +715,4 @@ def get_memory(self): def launch(self, server_name, port): self.app.include_router(self.router) - uvicorn.run(self.app, host=server_name, port=port, timeout_keep_alive=0) + uvicorn.run(self.app, host=server_name, port=port, timeout_keep_alive=shared.cmd_opts.timeout_keep_alive) From b85fc7187d953828340d4e3af34af46d9fc70b9e Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 10 Jul 2023 21:18:34 +0300 Subject: [PATCH 0634/2418] Fix MPS cache cleanup Importing torch does not import torch.mps so the call failed. --- modules/devices.py | 5 +++-- modules/mac_specific.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index c5ad950f621..57e51da30e2 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -54,8 +54,9 @@ def torch_gc(): with torch.cuda.device(get_cuda_device_string()): torch.cuda.empty_cache() torch.cuda.ipc_collect() - elif has_mps() and hasattr(torch.mps, 'empty_cache'): - torch.mps.empty_cache() + + if has_mps(): + mac_specific.torch_mps_gc() def enable_tf32(): diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 735847f54ff..2c2f15ca422 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -1,8 +1,12 @@ +import logging + import torch import platform from modules.sd_hijack_utils import CondFunc from packaging import version +log = logging.getLogger() + # before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+, # use check `getattr` and try it for compatibility. @@ -19,9 +23,19 @@ def check_for_mps() -> bool: return False else: return torch.backends.mps.is_available() and torch.backends.mps.is_built() + + has_mps = check_for_mps() +def torch_mps_gc() -> None: + try: + from torch.mps import empty_cache + empty_cache() + except Exception: + log.warning("MPS garbage collection failed", exc_info=True) + + # MPS workaround for https://github.com/pytorch/pytorch/issues/89784 def cumsum_fix(input, cumsum_func, *args, **kwargs): if input.device.type == 'mps': From 3636c2c6eda6ea25db95a5e3e77fe1ac347f0081 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 11 Jul 2023 15:05:20 +0300 Subject: [PATCH 0635/2418] Allow using alt in the prompt fields again --- javascript/edit-order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/edit-order.js b/javascript/edit-order.js index e6e73937637..ed4ef9ac399 100644 --- a/javascript/edit-order.js +++ b/javascript/edit-order.js @@ -6,11 +6,11 @@ function keyupEditOrder(event) { let target = event.originalTarget || event.composedPath()[0]; if (!target.matches("*:is([id*='_toprow'] [id*='_prompt'], .prompt) textarea")) return; if (!event.altKey) return; - event.preventDefault(); let isLeft = event.key == "ArrowLeft"; let isRight = event.key == "ArrowRight"; if (!isLeft && !isRight) return; + event.preventDefault(); let selectionStart = target.selectionStart; let selectionEnd = target.selectionEnd; From af081211ee93622473ee575de30fed2fd8263c09 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 11 Jul 2023 21:16:43 +0300 Subject: [PATCH 0636/2418] getting SD2.1 to run on SDXL repo --- modules/launch_utils.py | 3 ++ modules/paths.py | 1 + modules/prompt_parser.py | 64 +++++++++++++++++++++++++------ modules/sd_hijack.py | 9 +++++ modules/sd_hijack_open_clip.py | 4 ++ modules/sd_models.py | 8 +++- modules/sd_models_config.py | 2 + modules/sd_models_xl.py | 40 +++++++++++++++++++ modules/sd_samplers_kdiffusion.py | 45 +++++++++++++++++----- 9 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 modules/sd_models_xl.py diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 0e0dbca496d..3b740dbd478 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -235,11 +235,13 @@ def prepare_environment(): openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip") stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git") + stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git") k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git') 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") codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") @@ -297,6 +299,7 @@ def prepare_environment(): os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True) git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash) + git_clone(stable_diffusion_xl_repo, repo_dir('generative-models'), "Stable Diffusion XL", stable_diffusion_xl_commit_hash) git_clone(k_diffusion_repo, repo_dir('k-diffusion'), "K-diffusion", k_diffusion_commit_hash) git_clone(codeformer_repo, repo_dir('CodeFormer'), "CodeFormer", codeformer_commit_hash) git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash) diff --git a/modules/paths.py b/modules/paths.py index bada804e6c1..f509a85f7c8 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -20,6 +20,7 @@ path_dirs = [ (sd_path, 'ldm', 'Stable Diffusion', []), + (os.path.join(sd_path, '../generative-models'), 'sgm', 'Stable Diffusion XL', []), (os.path.join(sd_path, '../CodeFormer'), 'inference_codeformer.py', 'CodeFormer', []), (os.path.join(sd_path, '../BLIP'), 'models/blip.py', 'BLIP', []), (os.path.join(sd_path, '../k-diffusion'), 'k_diffusion/sampling.py', 'k_diffusion', ["atstart"]), diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 0069d8b0e1a..d7f9e9a9cf4 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -144,7 +144,12 @@ def get_learned_conditioning(model, prompts, steps): cond_schedule = [] for i, (end_at_step, _) in enumerate(prompt_schedule): - cond_schedule.append(ScheduledPromptConditioning(end_at_step, conds[i])) + if isinstance(conds, dict): + cond = {k: v[i] for k, v in conds.items()} + else: + cond = conds[i] + + cond_schedule.append(ScheduledPromptConditioning(end_at_step, cond)) cache[prompt] = cond_schedule res.append(cond_schedule) @@ -214,20 +219,57 @@ def get_multicond_learned_conditioning(model, prompts, steps) -> MulticondLearne return MulticondLearnedConditioning(shape=(len(prompts),), batch=res) +class DictWithShape(dict): + def __init__(self, x, shape): + super().__init__() + self.update(x) + + @property + def shape(self): + return self["crossattn"].shape + + def reconstruct_cond_batch(c: List[List[ScheduledPromptConditioning]], current_step): param = c[0][0].cond - res = torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype) + is_dict = isinstance(param, dict) + + if is_dict: + dict_cond = param + res = {k: torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype) for k, param in dict_cond.items()} + res = DictWithShape(res, (len(c),) + dict_cond['crossattn'].shape) + else: + res = torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype) + for i, cond_schedule in enumerate(c): target_index = 0 for current, entry in enumerate(cond_schedule): if current_step <= entry.end_at_step: target_index = current break - res[i] = cond_schedule[target_index].cond + + if is_dict: + for k, param in cond_schedule[target_index].cond.items(): + res[k][i] = param + else: + res[i] = cond_schedule[target_index].cond return res +def stack_conds(tensors): + # if prompts have wildly different lengths above the limit we'll get tensors of different shapes + # and won't be able to torch.stack them. So this fixes that. + token_count = max([x.shape[0] for x in tensors]) + for i in range(len(tensors)): + if tensors[i].shape[0] != token_count: + last_vector = tensors[i][-1:] + last_vector_repeated = last_vector.repeat([token_count - tensors[i].shape[0], 1]) + tensors[i] = torch.vstack([tensors[i], last_vector_repeated]) + + return torch.stack(tensors) + + + def reconstruct_multicond_batch(c: MulticondLearnedConditioning, current_step): param = c.batch[0][0].schedules[0].cond @@ -249,16 +291,14 @@ def reconstruct_multicond_batch(c: MulticondLearnedConditioning, current_step): conds_list.append(conds_for_batch) - # if prompts have wildly different lengths above the limit we'll get tensors fo different shapes - # and won't be able to torch.stack them. So this fixes that. - token_count = max([x.shape[0] for x in tensors]) - for i in range(len(tensors)): - if tensors[i].shape[0] != token_count: - last_vector = tensors[i][-1:] - last_vector_repeated = last_vector.repeat([token_count - tensors[i].shape[0], 1]) - tensors[i] = torch.vstack([tensors[i], last_vector_repeated]) + if isinstance(tensors[0], dict): + keys = list(tensors[0].keys()) + stacked = {k: stack_conds([x[k] for x in tensors]) for k in keys} + stacked = DictWithShape(stacked, stacked['crossattn'].shape) + else: + stacked = stack_conds(tensors).to(device=param.device, dtype=param.dtype) - return conds_list, torch.stack(tensors).to(device=param.device, dtype=param.dtype) + return conds_list, stacked re_attention = re.compile(r""" diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 3b6f95ce2ea..c4b9211fd21 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -166,6 +166,15 @@ def apply_optimizations(self, option=None): undo_optimizations() def hijack(self, m): + conditioner = getattr(m, 'conditioner', None) + if conditioner: + for i in range(len(conditioner.embedders)): + embedder = conditioner.embedders[i] + if type(embedder).__name__ == 'FrozenOpenCLIPEmbedder': + embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) + m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(embedder, self) + conditioner.embedders[i] = m.cond_stage_model + if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: model_embeddings = m.cond_stage_model.roberta.embeddings model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.word_embeddings, self) diff --git a/modules/sd_hijack_open_clip.py b/modules/sd_hijack_open_clip.py index f733e8529fb..6ac5bda6e14 100644 --- a/modules/sd_hijack_open_clip.py +++ b/modules/sd_hijack_open_clip.py @@ -16,6 +16,10 @@ def __init__(self, wrapped, hijack): self.id_end = tokenizer.encoder[""] self.id_pad = 0 + self.is_trainable = getattr(wrapped, 'is_trainable', False) + self.input_key = getattr(wrapped, 'input_key', 'txt') + self.legacy_ucg_val = None + def tokenize(self, texts): assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip' diff --git a/modules/sd_models.py b/modules/sd_models.py index 060e0007c10..8d639583cc7 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -14,7 +14,7 @@ from ldm.util import instantiate_from_config -from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config, sd_unet +from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config, sd_unet, sd_models_xl from modules.sd_hijack_inpainting import do_inpainting_hijack from modules.timer import Timer import tomesd @@ -289,6 +289,9 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer if state_dict is None: state_dict = get_checkpoint_state_dict(checkpoint_info, timer) + if hasattr(model, 'conditioner'): + sd_models_xl.extend_sdxl(model) + model.load_state_dict(state_dict, strict=False) del state_dict timer.record("apply weights to model") @@ -334,7 +337,8 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer model.sd_checkpoint_info = checkpoint_info shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 - model.logvar = model.logvar.to(devices.device) # fix for training + if hasattr(model, 'logvar'): + model.logvar = model.logvar.to(devices.device) # fix for training sd_vae.delete_base_vae() sd_vae.clear_loaded_vae() diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 9bfe1237d1d..96501569b46 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -6,11 +6,13 @@ sd_configs_path = shared.sd_configs_path sd_repo_configs_path = os.path.join(paths.paths['Stable Diffusion'], "configs", "stable-diffusion") +sd_xl_repo_configs_path = os.path.join(paths.paths['Stable Diffusion XL'], "configs", "inference") config_default = shared.sd_default_config config_sd2 = os.path.join(sd_repo_configs_path, "v2-inference.yaml") config_sd2v = os.path.join(sd_repo_configs_path, "v2-inference-v.yaml") +config_sd2v = os.path.join(sd_xl_repo_configs_path, "sd_2_1_768.yaml") config_sd2_inpainting = os.path.join(sd_repo_configs_path, "v2-inpainting-inference.yaml") config_depth_model = os.path.join(sd_repo_configs_path, "v2-midas-inference.yaml") config_unclip = os.path.join(sd_repo_configs_path, "v2-1-stable-unclip-l-inference.yaml") diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py new file mode 100644 index 00000000000..d43b8868659 --- /dev/null +++ b/modules/sd_models_xl.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import torch + +import sgm.models.diffusion +import sgm.modules.diffusionmodules.denoiser_scaling +import sgm.modules.diffusionmodules.discretizer +from modules import devices + + +def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: list[str]): + for embedder in self.conditioner.embedders: + embedder.ucg_rate = 0.0 + + c = self.conditioner({'txt': batch}) + + return c + + +def apply_model(self: sgm.models.diffusion.DiffusionEngine, x, t, cond): + return self.model(x, t, cond) + + +def extend_sdxl(model): + dtype = next(model.model.diffusion_model.parameters()).dtype + model.model.diffusion_model.dtype = dtype + model.model.conditioning_key = 'crossattn' + + model.cond_stage_model = [x for x in model.conditioner.embedders if type(x).__name__ == 'FrozenOpenCLIPEmbedder'][0] + model.cond_stage_key = model.cond_stage_model.input_key + + model.parameterization = "v" if isinstance(model.denoiser.scaling, sgm.modules.diffusionmodules.denoiser_scaling.VScaling) else "eps" + + discretization = sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization() + model.alphas_cumprod = torch.asarray(discretization.alphas_cumprod, device=devices.device, dtype=dtype) + + +sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning +sgm.models.diffusion.DiffusionEngine.apply_model = apply_model + diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 71581b76359..73289ce47fe 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -53,6 +53,28 @@ } +def catenate_conds(conds): + if not isinstance(conds[0], dict): + return torch.cat(conds) + + return {key: torch.cat([x[key] for x in conds]) for key in conds[0].keys()} + + +def subscript_cond(cond, a, b): + if not isinstance(cond, dict): + return cond[a:b] + + return {key: vec[a:b] for key, vec in cond.items()} + + +def pad_cond(tensor, repeats, empty): + if not isinstance(tensor, dict): + return torch.cat([tensor, empty.repeat((tensor.shape[0], repeats, 1))], axis=1) + + tensor['crossattn'] = pad_cond(tensor['crossattn'], repeats, empty) + return tensor + + class CFGDenoiser(torch.nn.Module): """ Classifier free guidance denoiser. A wrapper for stable diffusion model (specifically for unet) @@ -105,10 +127,13 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): if shared.sd_model.model.conditioning_key == "crossattn-adm": image_uncond = torch.zeros_like(image_cond) - make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": c_crossattn, "c_adm": c_adm} + make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": [c_crossattn], "c_adm": c_adm} else: image_uncond = image_cond - make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": c_crossattn, "c_concat": [c_concat]} + if isinstance(uncond, dict): + make_condition_dict = lambda c_crossattn, c_concat: {**c_crossattn, "c_concat": [c_concat]} + else: + make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": [c_crossattn], "c_concat": [c_concat]} if not is_edit_model: x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x]) @@ -140,28 +165,28 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): num_repeats = (tensor.shape[1] - uncond.shape[1]) // empty.shape[1] if num_repeats < 0: - tensor = torch.cat([tensor, empty.repeat((tensor.shape[0], -num_repeats, 1))], axis=1) + tensor = pad_cond(tensor, -num_repeats, empty) self.padded_cond_uncond = True elif num_repeats > 0: - uncond = torch.cat([uncond, empty.repeat((uncond.shape[0], num_repeats, 1))], axis=1) + uncond = pad_cond(uncond, num_repeats, empty) self.padded_cond_uncond = True if tensor.shape[1] == uncond.shape[1] or skip_uncond: if is_edit_model: - cond_in = torch.cat([tensor, uncond, uncond]) + cond_in = catenate_conds([tensor, uncond, uncond]) elif skip_uncond: cond_in = tensor else: - cond_in = torch.cat([tensor, uncond]) + cond_in = catenate_conds([tensor, uncond]) if shared.batch_cond_uncond: - x_out = self.inner_model(x_in, sigma_in, cond=make_condition_dict([cond_in], image_cond_in)) + x_out = self.inner_model(x_in, sigma_in, cond=make_condition_dict(cond_in, image_cond_in)) else: x_out = torch.zeros_like(x_in) for batch_offset in range(0, x_out.shape[0], batch_size): a = batch_offset b = a + batch_size - x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=make_condition_dict([cond_in[a:b]], image_cond_in[a:b])) + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=make_condition_dict(cond_in[a:b], image_cond_in[a:b])) else: x_out = torch.zeros_like(x_in) batch_size = batch_size*2 if shared.batch_cond_uncond else batch_size @@ -170,14 +195,14 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): b = min(a + batch_size, tensor.shape[0]) if not is_edit_model: - c_crossattn = [tensor[a:b]] + c_crossattn = subscript_cond(tensor, a, b) else: c_crossattn = torch.cat([tensor[a:b]], uncond) x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=make_condition_dict(c_crossattn, image_cond_in[a:b])) if not skip_uncond: - x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond=make_condition_dict([uncond], image_cond_in[-uncond.shape[0]:])) + x_out[-uncond.shape[0]:] = self.inner_model(x_in[-uncond.shape[0]:], sigma_in[-uncond.shape[0]:], cond=make_condition_dict(uncond, image_cond_in[-uncond.shape[0]:])) denoised_image_indexes = [x[0][0] for x in conds_list] if skip_uncond: From 3fee3c34f1b01d21770ab0a226b432cdd8444792 Mon Sep 17 00:00:00 2001 From: missionfloyd Date: Wed, 12 Jul 2023 02:45:03 -0600 Subject: [PATCH 0637/2418] Save img2img batch with images.save_image() --- modules/img2img.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 2c4970207fe..15306972378 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -8,6 +8,7 @@ from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, state +from modules.images import save_image import modules.shared as shared import modules.processing as processing from modules.ui import plaintext_to_html @@ -84,17 +85,17 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal proc = process_images(p) for n, processed_image in enumerate(proc.images): - filename = image_path.name + filename = image_path.stem + infotext = proc.infotext(p, n) if n > 0: - left, right = os.path.splitext(filename) - filename = f"{left}-{n}{right}" + filename += f"-{n}" if not save_normally: os.makedirs(output_dir, exist_ok=True) if processed_image.mode == 'RGBA': processed_image = processed_image.convert("RGB") - processed_image.save(os.path.join(output_dir, filename)) + save_image(processed_image, output_dir, None, extension=opts.samples_format, info=infotext, forced_filename=filename, save_to_dirs=False) 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_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, 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, *args): From 6c0d5d1198576dbe664f55cffec27b03d0789efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=80=E5=AE=97?= Date: Wed, 12 Jul 2023 16:51:50 +0800 Subject: [PATCH 0638/2418] fix: check fill size none zero when resize (fixes #11425) --- modules/images.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/images.py b/modules/images.py index 7bbfc3e0b10..7935b122a2d 100644 --- a/modules/images.py +++ b/modules/images.py @@ -302,12 +302,14 @@ def resize(im, w, h): if ratio < src_ratio: fill_height = height // 2 - src_h // 2 - res.paste(resized.resize((width, fill_height), box=(0, 0, width, 0)), box=(0, 0)) - res.paste(resized.resize((width, fill_height), box=(0, resized.height, width, resized.height)), box=(0, fill_height + src_h)) + if fill_height > 0: + res.paste(resized.resize((width, fill_height), box=(0, 0, width, 0)), box=(0, 0)) + res.paste(resized.resize((width, fill_height), box=(0, resized.height, width, resized.height)), box=(0, fill_height + src_h)) elif ratio > src_ratio: fill_width = width // 2 - src_w // 2 - res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0)) - res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0)) + if fill_width > 0: + res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0)) + res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0)) return res From 8f6b24ce5922174d96eb9776126488cb28694ff8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 12 Jul 2023 15:16:42 +0300 Subject: [PATCH 0639/2418] Add correct logger name --- modules/mac_specific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 2c2f15ca422..328b5973ad3 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -5,7 +5,7 @@ from modules.sd_hijack_utils import CondFunc from packaging import version -log = logging.getLogger() +log = logging.getLogger(__name__) # before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+, From 3d524fd3f1bdb17946bf6fa8a3cdf7b10859c495 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 12 Jul 2023 15:17:13 +0300 Subject: [PATCH 0640/2418] Don't do MPS GC when there's a latent that could still be sampled --- modules/mac_specific.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/mac_specific.py b/modules/mac_specific.py index 328b5973ad3..9ceb43baec2 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -30,6 +30,10 @@ def check_for_mps() -> bool: def torch_mps_gc() -> None: try: + from modules.shared import state + if state.current_latent is not None: + log.debug("`current_latent` is set, skipping MPS garbage collection") + return from torch.mps import empty_cache empty_cache() except Exception: From ea49bb06125262e61c44003e42219bc04e38b10b Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:30:22 +0900 Subject: [PATCH 0641/2418] use submit blur for quick settings textbox --- modules/ui_settings.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/ui_settings.py b/modules/ui_settings.py index 0c560b30f9f..a6076bf3060 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -260,13 +260,20 @@ def add_functionality(self, demo): component = self.component_dict[k] info = opts.data_labels[k] - change_handler = component.release if hasattr(component, 'release') else component.change - change_handler( - fn=lambda value, k=k: self.run_settings_single(value, key=k), - inputs=[component], - outputs=[component, self.text_settings], - show_progress=info.refresh is not None, - ) + if isinstance(component, gr.Textbox): + methods = [component.submit, component.blur] + elif hasattr(component, 'release'): + methods = [component.release] + else: + methods = [component.change] + + for method in methods: + method( + fn=lambda value, k=k: self.run_settings_single(value, key=k), + inputs=[component], + outputs=[component, self.text_settings], + show_progress=info.refresh is not None, + ) button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False) button_set_checkpoint.click( From da464a3fb39ecc6ea7b22fe87271194480d8501c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 12 Jul 2023 23:52:43 +0300 Subject: [PATCH 0642/2418] SDXL support --- modules/launch_utils.py | 17 ++++++++++ modules/lowvram.py | 51 ++++++++++++++++++++++-------- modules/paths.py | 9 +++++- modules/processing.py | 7 ++-- modules/prompt_parser.py | 23 ++++++++++++-- modules/sd_hijack.py | 23 +++++++++++++- modules/sd_hijack_clip.py | 16 +++++++--- modules/sd_hijack_open_clip.py | 38 +++++++++++++++++++--- modules/sd_hijack_optimizations.py | 51 +++++++++++++++++++++++++----- modules/sd_models.py | 14 ++++++-- modules/sd_models_config.py | 5 ++- modules/sd_models_xl.py | 27 +++++++++++++--- modules/sd_samplers_kdiffusion.py | 2 +- modules/shared.py | 2 ++ requirements.txt | 1 + requirements_versions.txt | 1 + 16 files changed, 242 insertions(+), 45 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 3b740dbd478..aa9d1880240 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -224,6 +224,20 @@ def run_extensions_installers(settings_file): run_extension_installer(os.path.join(extensions_dir, dirname_extension)) +def mute_sdxl_imports(): + """create fake modules that SDXL wants to import but doesn't actually use for our purposes""" + + import importlib + + module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec('taming.modules.losses.lpips', None)) + module.LPIPS = None + sys.modules['taming.modules.losses.lpips'] = module + + module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec('sgm.data', None)) + module.StableDataModuleFromConfig = None + sys.modules['sgm.data'] = module + + def prepare_environment(): torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118") torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}") @@ -319,11 +333,14 @@ def prepare_environment(): if args.update_all_extensions: git_pull_recursive(extensions_dir) + mute_sdxl_imports() + if "--exit" in sys.argv: print("Exiting because of --exit argument") exit(0) + def configure_for_tests(): if "--api" not in sys.argv: sys.argv.append("--api") diff --git a/modules/lowvram.py b/modules/lowvram.py index d95bcfbf0f3..da4f33a8a02 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -53,19 +53,46 @@ def first_stage_model_decode_wrap(z): send_me_to_gpu(first_stage_model, None) return first_stage_model_decode(z) - # for SD1, cond_stage_model is CLIP and its NN is in the tranformer frield, but for SD2, it's open clip, and it's in model field - if hasattr(sd_model.cond_stage_model, 'model'): - sd_model.cond_stage_model.transformer = sd_model.cond_stage_model.model - - # remove several big modules: cond, first_stage, depth/embedder (if applicable), and unet from the model and then - # send the model to GPU. Then put modules back. the modules will be in CPU. - stored = sd_model.cond_stage_model.transformer, sd_model.first_stage_model, getattr(sd_model, 'depth_model', None), getattr(sd_model, 'embedder', None), sd_model.model - sd_model.cond_stage_model.transformer, sd_model.first_stage_model, sd_model.depth_model, sd_model.embedder, sd_model.model = None, None, None, None, None + to_remain_in_cpu = [ + (sd_model, 'first_stage_model'), + (sd_model, 'depth_model'), + (sd_model, 'embedder'), + (sd_model, 'model'), + (sd_model, 'embedder'), + ] + + is_sdxl = hasattr(sd_model, 'conditioner') + is_sd2 = not is_sdxl and hasattr(sd_model.cond_stage_model, 'model') + + if is_sdxl: + to_remain_in_cpu.append((sd_model, 'conditioner')) + elif is_sd2: + to_remain_in_cpu.append((sd_model.cond_stage_model, 'model')) + else: + to_remain_in_cpu.append((sd_model.cond_stage_model, 'transformer')) + + # remove several big modules: cond, first_stage, depth/embedder (if applicable), and unet from the model + stored = [] + for obj, field in to_remain_in_cpu: + module = getattr(obj, field, None) + stored.append(module) + setattr(obj, field, None) + + # send the model to GPU. sd_model.to(devices.device) - sd_model.cond_stage_model.transformer, sd_model.first_stage_model, sd_model.depth_model, sd_model.embedder, sd_model.model = stored + + # put modules back. the modules will be in CPU. + for (obj, field), module in zip(to_remain_in_cpu, stored): + setattr(obj, field, module) # register hooks for those the first three models - sd_model.cond_stage_model.transformer.register_forward_pre_hook(send_me_to_gpu) + if is_sdxl: + sd_model.conditioner.register_forward_pre_hook(send_me_to_gpu) + elif is_sd2: + sd_model.cond_stage_model.model.register_forward_pre_hook(send_me_to_gpu) + else: + sd_model.cond_stage_model.transformer.register_forward_pre_hook(send_me_to_gpu) + sd_model.first_stage_model.register_forward_pre_hook(send_me_to_gpu) sd_model.first_stage_model.encode = first_stage_model_encode_wrap sd_model.first_stage_model.decode = first_stage_model_decode_wrap @@ -75,10 +102,6 @@ def first_stage_model_decode_wrap(z): sd_model.embedder.register_forward_pre_hook(send_me_to_gpu) parents[sd_model.cond_stage_model.transformer] = sd_model.cond_stage_model - if hasattr(sd_model.cond_stage_model, 'model'): - sd_model.cond_stage_model.model = sd_model.cond_stage_model.transformer - del sd_model.cond_stage_model.transformer - if use_medvram: sd_model.model.register_forward_pre_hook(send_me_to_gpu) else: diff --git a/modules/paths.py b/modules/paths.py index f509a85f7c8..1100a8dc697 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -20,7 +20,7 @@ path_dirs = [ (sd_path, 'ldm', 'Stable Diffusion', []), - (os.path.join(sd_path, '../generative-models'), 'sgm', 'Stable Diffusion XL', []), + (os.path.join(sd_path, '../generative-models'), 'sgm', 'Stable Diffusion XL', ["sgm"]), (os.path.join(sd_path, '../CodeFormer'), 'inference_codeformer.py', 'CodeFormer', []), (os.path.join(sd_path, '../BLIP'), 'models/blip.py', 'BLIP', []), (os.path.join(sd_path, '../k-diffusion'), 'k_diffusion/sampling.py', 'k_diffusion', ["atstart"]), @@ -36,6 +36,13 @@ d = os.path.abspath(d) if "atstart" in options: sys.path.insert(0, d) + elif "sgm" in options: + # Stable Diffusion XL repo has scripts dir with __init__.py in it which ruins every extension's scripts dir, so we + # import sgm and remove it from sys.path so that when a script imports scripts.something, it doesbn't use sgm's scripts dir. + + sys.path.insert(0, d) + import sgm + sys.path.pop(0) else: sys.path.append(d) paths[what] = d diff --git a/modules/processing.py b/modules/processing.py index cd568a208aa..85d3542327f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -343,10 +343,13 @@ def get_conds_with_caching(self, function, required_prompts, steps, caches, extr return cache[1] def setup_conds(self): + prompts = prompt_parser.SdConditioning(self.prompts, width=self.width, height=self.height) + negative_prompts = prompt_parser.SdConditioning(self.negative_prompts, width=self.width, height=self.height) + 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, self.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, self.prompts, self.steps * self.step_multiplier, [self.cached_c], self.extra_network_data) + 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) def parse_extra_network_prompts(self): self.prompts, self.extra_network_data = extra_networks.parse_prompts(self.prompts) diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index d7f9e9a9cf4..33810669f7e 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from collections import namedtuple from typing import List @@ -109,7 +111,19 @@ def get_schedule(prompt): ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"]) -def get_learned_conditioning(model, prompts, steps): +class SdConditioning(list): + """ + A list with prompts for stable diffusion's conditioner model. + Can also specify width and height of created image - SDXL needs it. + """ + def __init__(self, prompts, width=None, height=None): + super().__init__() + self.extend(prompts) + self.width = width or getattr(prompts, 'width', None) + self.height = height or getattr(prompts, 'height', None) + + +def get_learned_conditioning(model, prompts: SdConditioning | list[str], steps): """converts a list of prompts into a list of prompt schedules - each schedule is a list of ScheduledPromptConditioning, specifying the comdition (cond), and the sampling step at which this condition is to be replaced by the next one. @@ -160,11 +174,13 @@ def get_learned_conditioning(model, prompts, steps): re_AND = re.compile(r"\bAND\b") re_weight = re.compile(r"^(.*?)(?:\s*:\s*([-+]?(?:\d+\.?|\d*\.\d+)))?\s*$") -def get_multicond_prompt_list(prompts): + +def get_multicond_prompt_list(prompts: SdConditioning | list[str]): res_indexes = [] - prompt_flat_list = [] prompt_indexes = {} + prompt_flat_list = SdConditioning(prompts) + prompt_flat_list.clear() for prompt in prompts: subprompts = re_AND.split(prompt) @@ -201,6 +217,7 @@ def __init__(self, shape, batch): self.shape: tuple = shape # the shape field is needed to send this object to DDIM/PLMS self.batch: List[List[ComposableScheduledPromptConditioning]] = batch + def get_multicond_learned_conditioning(model, prompts, steps) -> MulticondLearnedConditioning: """same as get_learned_conditioning, but returns a list of ScheduledPromptConditioning along with the weight objects for each prompt. For each prompt, the list is obtained by splitting the prompt using the AND separator. diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index c4b9211fd21..266811f955d 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -15,6 +15,11 @@ import ldm.models.diffusion.plms import ldm.modules.encoders.modules +import sgm.modules.attention +import sgm.modules.diffusionmodules.model +import sgm.modules.diffusionmodules.openaimodel +import sgm.modules.encoders.modules + attention_CrossAttention_forward = ldm.modules.attention.CrossAttention.forward diffusionmodules_model_nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity diffusionmodules_model_AttnBlock_forward = ldm.modules.diffusionmodules.model.AttnBlock.forward @@ -56,6 +61,9 @@ def apply_optimizations(option=None): ldm.modules.diffusionmodules.model.nonlinearity = silu ldm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th + sgm.modules.diffusionmodules.model.nonlinearity = silu + sgm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th + if current_optimizer is not None: current_optimizer.undo() current_optimizer = None @@ -89,6 +97,10 @@ def undo_optimizations(): ldm.modules.attention.CrossAttention.forward = hypernetwork.attention_CrossAttention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward + sgm.modules.diffusionmodules.model.nonlinearity = diffusionmodules_model_nonlinearity + sgm.modules.attention.CrossAttention.forward = hypernetwork.attention_CrossAttention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward + def fix_checkpoint(): """checkpoints are now added and removed in embedding/hypernet code, since torch doesn't want @@ -170,10 +182,19 @@ def hijack(self, m): if conditioner: for i in range(len(conditioner.embedders)): embedder = conditioner.embedders[i] - if type(embedder).__name__ == 'FrozenOpenCLIPEmbedder': + typename = type(embedder).__name__ + if typename == 'FrozenOpenCLIPEmbedder': embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(embedder, self) conditioner.embedders[i] = m.cond_stage_model + if typename == 'FrozenCLIPEmbedder': + model_embeddings = m.cond_stage_model.transformer.text_model.embeddings + model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.token_embedding, self) + m.cond_stage_model = sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords(embedder, self) + conditioner.embedders[i] = m.cond_stage_model + if typename == 'FrozenOpenCLIPEmbedder2': + embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) + conditioner.embedders[i] = sd_hijack_open_clip.FrozenOpenCLIPEmbedder2WithCustomWords(embedder, self) if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: model_embeddings = m.cond_stage_model.roberta.embeddings diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 3b5a7666717..6c17a81d26b 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -42,6 +42,10 @@ def __init__(self, wrapped, hijack): self.hijack: sd_hijack.StableDiffusionModelHijack = hijack self.chunk_length = 75 + self.is_trainable = getattr(wrapped, 'is_trainable', False) + self.input_key = getattr(wrapped, 'input_key', 'txt') + self.legacy_ucg_val = None + def empty_chunk(self): """creates an empty PromptChunk and returns it""" @@ -199,8 +203,9 @@ def forward(self, texts): """ Accepts an array of texts; Passes texts through transformers network to create a tensor with numerical representation of those texts. Returns a tensor with shape of (B, T, C), where B is length of the array; T is length, in tokens, of texts (including padding) - T will - be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, and for SD2 it's 1024. + be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, for SD2 it's 1024, and for SDXL it's 1280. An example shape returned by this function can be: (2, 77, 768). + For SDXL, instead of returning one tensor avobe, it returns a tuple with two: the other one with shape (B, 1280) with pooled values. Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one elemenet is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream" """ @@ -233,7 +238,10 @@ def forward(self, texts): embeddings_list = ", ".join([f'{name} [{embedding.checksum()}]' for name, embedding in used_embeddings.items()]) self.hijack.comments.append(f"Used embeddings: {embeddings_list}") - return torch.hstack(zs) + if getattr(self.wrapped, 'return_pooled', False): + return torch.hstack(zs), zs[0].pooled + else: + return torch.hstack(zs) def process_tokens(self, remade_batch_tokens, batch_multipliers): """ @@ -256,9 +264,9 @@ def process_tokens(self, remade_batch_tokens, batch_multipliers): # restoring original mean is likely not correct, but it seems to work well to prevent artifacts that happen otherwise batch_multipliers = torch.asarray(batch_multipliers).to(devices.device) original_mean = z.mean() - z = z * batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape) + z *= batch_multipliers.reshape(batch_multipliers.shape + (1,)).expand(z.shape) new_mean = z.mean() - z = z * (original_mean / new_mean) + z *= (original_mean / new_mean) return z diff --git a/modules/sd_hijack_open_clip.py b/modules/sd_hijack_open_clip.py index 6ac5bda6e14..fcf5ad07de7 100644 --- a/modules/sd_hijack_open_clip.py +++ b/modules/sd_hijack_open_clip.py @@ -16,10 +16,6 @@ def __init__(self, wrapped, hijack): self.id_end = tokenizer.encoder[""] self.id_pad = 0 - self.is_trainable = getattr(wrapped, 'is_trainable', False) - self.input_key = getattr(wrapped, 'input_key', 'txt') - self.legacy_ucg_val = None - def tokenize(self, texts): assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip' @@ -39,3 +35,37 @@ def encode_embedding_init_text(self, init_text, nvpt): embedded = self.wrapped.model.token_embedding.wrapped(ids).squeeze(0) return embedded + + +class FrozenOpenCLIPEmbedder2WithCustomWords(sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase): + def __init__(self, wrapped, hijack): + super().__init__(wrapped, hijack) + + self.comma_token = [v for k, v in tokenizer.encoder.items() if k == ','][0] + self.id_start = tokenizer.encoder[""] + self.id_end = tokenizer.encoder[""] + self.id_pad = 0 + + def tokenize(self, texts): + assert not opts.use_old_emphasis_implementation, 'Old emphasis implementation not supported for Open Clip' + + tokenized = [tokenizer.encode(text) for text in texts] + + return tokenized + + def encode_with_transformers(self, tokens): + d = self.wrapped.encode_with_transformer(tokens) + z = d[self.wrapped.layer] + + pooled = d.get("pooled") + if pooled is not None: + z.pooled = pooled + + return z + + def encode_embedding_init_text(self, init_text, nvpt): + ids = tokenizer.encode(init_text) + ids = torch.asarray([ids], device=devices.device, dtype=torch.int) + embedded = self.wrapped.model.token_embedding.wrapped(ids).squeeze(0) + + return embedded diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 53e27adea2c..e99c9ba5fee 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -14,7 +14,11 @@ import ldm.modules.attention import ldm.modules.diffusionmodules.model +import sgm.modules.attention +import sgm.modules.diffusionmodules.model + diffusionmodules_model_AttnBlock_forward = ldm.modules.diffusionmodules.model.AttnBlock.forward +sgm_diffusionmodules_model_AttnBlock_forward = sgm.modules.diffusionmodules.model.AttnBlock.forward class SdOptimization: @@ -39,6 +43,9 @@ def undo(self): ldm.modules.attention.CrossAttention.forward = hypernetwork.attention_CrossAttention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward + sgm.modules.attention.CrossAttention.forward = hypernetwork.attention_CrossAttention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = sgm_diffusionmodules_model_AttnBlock_forward + class SdOptimizationXformers(SdOptimization): name = "xformers" @@ -51,6 +58,8 @@ def is_available(self): def apply(self): ldm.modules.attention.CrossAttention.forward = xformers_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = xformers_attnblock_forward + sgm.modules.attention.CrossAttention.forward = xformers_attention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = xformers_attnblock_forward class SdOptimizationSdpNoMem(SdOptimization): @@ -65,6 +74,8 @@ def is_available(self): def apply(self): ldm.modules.attention.CrossAttention.forward = scaled_dot_product_no_mem_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sdp_no_mem_attnblock_forward + sgm.modules.attention.CrossAttention.forward = scaled_dot_product_no_mem_attention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = sdp_no_mem_attnblock_forward class SdOptimizationSdp(SdOptimizationSdpNoMem): @@ -76,6 +87,8 @@ class SdOptimizationSdp(SdOptimizationSdpNoMem): def apply(self): ldm.modules.attention.CrossAttention.forward = scaled_dot_product_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sdp_attnblock_forward + sgm.modules.attention.CrossAttention.forward = scaled_dot_product_attention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = sdp_attnblock_forward class SdOptimizationSubQuad(SdOptimization): @@ -86,6 +99,8 @@ class SdOptimizationSubQuad(SdOptimization): def apply(self): ldm.modules.attention.CrossAttention.forward = sub_quad_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = sub_quad_attnblock_forward + sgm.modules.attention.CrossAttention.forward = sub_quad_attention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = sub_quad_attnblock_forward class SdOptimizationV1(SdOptimization): @@ -94,9 +109,9 @@ class SdOptimizationV1(SdOptimization): cmd_opt = "opt_split_attention_v1" priority = 10 - def apply(self): ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward_v1 + sgm.modules.attention.CrossAttention.forward = split_cross_attention_forward_v1 class SdOptimizationInvokeAI(SdOptimization): @@ -109,6 +124,7 @@ def priority(self): def apply(self): ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward_invokeAI + sgm.modules.attention.CrossAttention.forward = split_cross_attention_forward_invokeAI class SdOptimizationDoggettx(SdOptimization): @@ -119,6 +135,8 @@ class SdOptimizationDoggettx(SdOptimization): def apply(self): ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward ldm.modules.diffusionmodules.model.AttnBlock.forward = cross_attention_attnblock_forward + sgm.modules.attention.CrossAttention.forward = split_cross_attention_forward + sgm.modules.diffusionmodules.model.AttnBlock.forward = cross_attention_attnblock_forward def list_optimizers(res): @@ -155,7 +173,7 @@ def get_available_vram(): # see https://github.com/basujindal/stable-diffusion/pull/117 for discussion -def split_cross_attention_forward_v1(self, x, context=None, mask=None): +def split_cross_attention_forward_v1(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): h = self.heads q_in = self.to_q(x) @@ -196,7 +214,7 @@ def split_cross_attention_forward_v1(self, x, context=None, mask=None): # taken from https://github.com/Doggettx/stable-diffusion and modified -def split_cross_attention_forward(self, x, context=None, mask=None): +def split_cross_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): h = self.heads q_in = self.to_q(x) @@ -262,11 +280,13 @@ def split_cross_attention_forward(self, x, context=None, mask=None): # -- Taken from https://github.com/invoke-ai/InvokeAI and modified -- mem_total_gb = psutil.virtual_memory().total // (1 << 30) + def einsum_op_compvis(q, k, v): s = einsum('b i d, b j d -> b i j', q, k) s = s.softmax(dim=-1, dtype=s.dtype) return einsum('b i j, b j d -> b i d', s, v) + def einsum_op_slice_0(q, k, v, slice_size): r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) for i in range(0, q.shape[0], slice_size): @@ -274,6 +294,7 @@ def einsum_op_slice_0(q, k, v, slice_size): r[i:end] = einsum_op_compvis(q[i:end], k[i:end], v[i:end]) return r + def einsum_op_slice_1(q, k, v, slice_size): r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) for i in range(0, q.shape[1], slice_size): @@ -281,6 +302,7 @@ def einsum_op_slice_1(q, k, v, slice_size): r[:, i:end] = einsum_op_compvis(q[:, i:end], k, v) return r + def einsum_op_mps_v1(q, k, v): if q.shape[0] * q.shape[1] <= 2**16: # (512x512) max q.shape[1]: 4096 return einsum_op_compvis(q, k, v) @@ -290,12 +312,14 @@ def einsum_op_mps_v1(q, k, v): slice_size -= 1 return einsum_op_slice_1(q, k, v, slice_size) + def einsum_op_mps_v2(q, k, v): if mem_total_gb > 8 and q.shape[0] * q.shape[1] <= 2**16: return einsum_op_compvis(q, k, v) else: return einsum_op_slice_0(q, k, v, 1) + def einsum_op_tensor_mem(q, k, v, max_tensor_mb): size_mb = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() // (1 << 20) if size_mb <= max_tensor_mb: @@ -305,6 +329,7 @@ def einsum_op_tensor_mem(q, k, v, max_tensor_mb): return einsum_op_slice_0(q, k, v, q.shape[0] // div) return einsum_op_slice_1(q, k, v, max(q.shape[1] // div, 1)) + def einsum_op_cuda(q, k, v): stats = torch.cuda.memory_stats(q.device) mem_active = stats['active_bytes.all.current'] @@ -315,6 +340,7 @@ def einsum_op_cuda(q, k, v): # Divide factor of safety as there's copying and fragmentation return einsum_op_tensor_mem(q, k, v, mem_free_total / 3.3 / (1 << 20)) + def einsum_op(q, k, v): if q.device.type == 'cuda': return einsum_op_cuda(q, k, v) @@ -328,7 +354,8 @@ def einsum_op(q, k, v): # Tested on i7 with 8MB L3 cache. return einsum_op_tensor_mem(q, k, v, 32) -def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): + +def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): h = self.heads q = self.to_q(x) @@ -356,7 +383,7 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): # Based on Birch-san's modified implementation of sub-quadratic attention from https://github.com/Birch-san/diffusers/pull/1 # The sub_quad_attention_forward function is under the MIT License listed under Memory Efficient Attention in the Licenses section of the web UI interface -def sub_quad_attention_forward(self, x, context=None, mask=None): +def sub_quad_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): assert mask is None, "attention-mask not currently implemented for SubQuadraticCrossAttnProcessor." h = self.heads @@ -392,6 +419,7 @@ def sub_quad_attention_forward(self, x, context=None, mask=None): return x + def sub_quad_attention(q, k, v, q_chunk_size=1024, kv_chunk_size=None, kv_chunk_size_min=None, chunk_threshold=None, use_checkpoint=True): bytes_per_token = torch.finfo(q.dtype).bits//8 batch_x_heads, q_tokens, _ = q.shape @@ -442,7 +470,7 @@ def get_xformers_flash_attention_op(q, k, v): return None -def xformers_attention_forward(self, x, context=None, mask=None): +def xformers_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): h = self.heads q_in = self.to_q(x) context = default(context, x) @@ -465,9 +493,10 @@ def xformers_attention_forward(self, x, context=None, mask=None): out = rearrange(out, 'b n h d -> b n (h d)', h=h) return self.to_out(out) + # Based on Diffusers usage of scaled dot product attention from https://github.com/huggingface/diffusers/blob/c7da8fd23359a22d0df2741688b5b4f33c26df21/src/diffusers/models/cross_attention.py # The scaled_dot_product_attention_forward function contains parts of code under Apache-2.0 license listed under Scaled Dot Product Attention in the Licenses section of the web UI interface -def scaled_dot_product_attention_forward(self, x, context=None, mask=None): +def scaled_dot_product_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): batch_size, sequence_length, inner_dim = x.shape if mask is not None: @@ -507,10 +536,12 @@ def scaled_dot_product_attention_forward(self, x, context=None, mask=None): hidden_states = self.to_out[1](hidden_states) return hidden_states -def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None): + +def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): return scaled_dot_product_attention_forward(self, x, context, mask) + def cross_attention_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) @@ -569,6 +600,7 @@ def cross_attention_attnblock_forward(self, x): return h3 + def xformers_attnblock_forward(self, x): try: h_ = x @@ -592,6 +624,7 @@ def xformers_attnblock_forward(self, x): except NotImplementedError: return cross_attention_attnblock_forward(self, x) + def sdp_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) @@ -612,10 +645,12 @@ def sdp_attnblock_forward(self, x): out = self.proj_out(out) return x + out + def sdp_no_mem_attnblock_forward(self, x): with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): return sdp_attnblock_forward(self, x) + def sub_quad_attnblock_forward(self, x): h_ = x h_ = self.norm(h_) diff --git a/modules/sd_models.py b/modules/sd_models.py index 8d639583cc7..e4aae597cf0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -411,6 +411,7 @@ def repair_config(sd_config): sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' +sdxl_clip_weight = 'conditioner.embedders.1.model.ln_final.weight' class SdModelData: @@ -445,6 +446,15 @@ def set_sd_model(self, v): model_data = SdModelData() +def get_empty_cond(sd_model): + if hasattr(sd_model, 'conditioner'): + d = sd_model.get_learned_conditioning([""]) + return d['crossattn'] + else: + return sd_model.cond_stage_model([""]) + + + def load_model(checkpoint_info=None, already_loaded_state_dict=None): from modules import lowvram, sd_hijack checkpoint_info = checkpoint_info or select_checkpoint() @@ -465,7 +475,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): state_dict = get_checkpoint_state_dict(checkpoint_info, timer) checkpoint_config = sd_models_config.find_checkpoint_config(state_dict, checkpoint_info) - clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict + clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict or sdxl_clip_weight in state_dict timer.record("find config") @@ -517,7 +527,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): timer.record("scripts callbacks") with devices.autocast(), torch.no_grad(): - sd_model.cond_stage_model_empty_prompt = sd_model.cond_stage_model([""]) + sd_model.cond_stage_model_empty_prompt = get_empty_cond(sd_model) timer.record("calculate empty prompt") diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 96501569b46..2e92479a157 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -14,6 +14,7 @@ config_sd2v = os.path.join(sd_repo_configs_path, "v2-inference-v.yaml") config_sd2v = os.path.join(sd_xl_repo_configs_path, "sd_2_1_768.yaml") config_sd2_inpainting = os.path.join(sd_repo_configs_path, "v2-inpainting-inference.yaml") +config_sdxl = os.path.join(sd_xl_repo_configs_path, "sd_xl_base.yaml") config_depth_model = os.path.join(sd_repo_configs_path, "v2-midas-inference.yaml") config_unclip = os.path.join(sd_repo_configs_path, "v2-1-stable-unclip-l-inference.yaml") config_unopenclip = os.path.join(sd_repo_configs_path, "v2-1-stable-unclip-h-inference.yaml") @@ -70,7 +71,9 @@ def guess_model_config_from_state_dict(sd, filename): diffusion_model_input = sd.get('model.diffusion_model.input_blocks.0.0.weight', None) sd2_variations_weight = sd.get('embedder.model.ln_final.weight', None) - if sd.get('depth_model.model.pretrained.act_postprocess3.0.project.0.bias', None) is not None: + if sd.get('conditioner.embedders.1.model.ln_final.weight', None) is not None: + return config_sdxl + elif sd.get('depth_model.model.pretrained.act_postprocess3.0.project.0.bias', None) is not None: return config_depth_model elif sd2_variations_weight is not None and sd2_variations_weight.shape[0] == 768: return config_unclip diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index d43b8868659..e8e270c3846 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -1,18 +1,30 @@ from __future__ import annotations +import sys + import torch import sgm.models.diffusion import sgm.modules.diffusionmodules.denoiser_scaling import sgm.modules.diffusionmodules.discretizer -from modules import devices +from modules import devices, shared, prompt_parser -def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: list[str]): +def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: prompt_parser.SdConditioning | list[str]): for embedder in self.conditioner.embedders: embedder.ucg_rate = 0.0 - c = self.conditioner({'txt': batch}) + width = getattr(self, 'target_width', 1024) + height = getattr(self, 'target_height', 1024) + + sdxl_conds = { + "txt": batch, + "original_size_as_tuple": torch.tensor([height, width]).repeat(len(batch), 1).to(devices.device, devices.dtype), + "crop_coords_top_left": torch.tensor([shared.opts.sdxl_crop_top, shared.opts.sdxl_crop_left]).repeat(len(batch), 1).to(devices.device, devices.dtype), + "target_size_as_tuple": torch.tensor([height, width]).repeat(len(batch), 1).to(devices.device, devices.dtype), + } + + c = self.conditioner(sdxl_conds) return c @@ -26,7 +38,7 @@ def extend_sdxl(model): model.model.diffusion_model.dtype = dtype model.model.conditioning_key = 'crossattn' - model.cond_stage_model = [x for x in model.conditioner.embedders if type(x).__name__ == 'FrozenOpenCLIPEmbedder'][0] + model.cond_stage_model = [x for x in model.conditioner.embedders if 'CLIPEmbedder' in type(x).__name__][0] model.cond_stage_key = model.cond_stage_model.input_key model.parameterization = "v" if isinstance(model.denoiser.scaling, sgm.modules.diffusionmodules.denoiser_scaling.VScaling) else "eps" @@ -34,7 +46,14 @@ def extend_sdxl(model): discretization = sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization() model.alphas_cumprod = torch.asarray(discretization.alphas_cumprod, device=devices.device, dtype=dtype) + model.is_xl = True + sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning sgm.models.diffusion.DiffusionEngine.apply_model = apply_model +sgm.modules.attention.print = lambda *args: None +sgm.modules.diffusionmodules.model.print = lambda *args: None +sgm.modules.diffusionmodules.openaimodel.print = lambda *args: None +sgm.modules.encoders.modules.print = lambda *args: None + diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 73289ce47fe..5552a8dc7dc 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -186,7 +186,7 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): for batch_offset in range(0, x_out.shape[0], batch_size): a = batch_offset b = a + batch_size - x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=make_condition_dict(cond_in[a:b], image_cond_in[a:b])) + x_out[a:b] = self.inner_model(x_in[a:b], sigma_in[a:b], cond=make_condition_dict(subscript_cond(cond_in, a, b), image_cond_in[a:b])) else: x_out = torch.zeros_like(x_in) batch_size = batch_size*2 if shared.batch_cond_uncond else batch_size diff --git a/modules/shared.py b/modules/shared.py index b7518de68b7..71afd94f115 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -428,6 +428,8 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors"), + "sdxl_crop_top": OptionInfo(0, "SDXL top coordinate of the crop"), + "sdxl_crop_left": OptionInfo(0, "SDXL left coordinate of the crop"), })) options_templates.update(options_section(('optimizations', "Optimizations"), { diff --git a/requirements.txt b/requirements.txt index 3142085eaf4..b3f8a7f41fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ kornia lark numpy omegaconf +open-clip-torch piexif psutil diff --git a/requirements_versions.txt b/requirements_versions.txt index f71b9d6c555..b826bf431c1 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -15,6 +15,7 @@ kornia==0.6.7 lark==1.1.2 numpy==1.23.5 omegaconf==2.2.3 +open-clip-torch==2.20.0 piexif==1.1.3 psutil~=5.9.5 pytorch_lightning==1.9.4 From 5cf623c58ef3c158e8b25f7c3d516ffc16769fa4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 00:08:19 +0300 Subject: [PATCH 0643/2418] linter --- modules/paths.py | 2 +- modules/sd_models_xl.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/paths.py b/modules/paths.py index 1100a8dc697..c6f8904ea10 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -41,7 +41,7 @@ # import sgm and remove it from sys.path so that when a script imports scripts.something, it doesbn't use sgm's scripts dir. sys.path.insert(0, d) - import sgm + import sgm # noqa: F401 sys.path.pop(0) else: sys.path.append(d) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index e8e270c3846..9224c1a35f7 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - import torch import sgm.models.diffusion From a04c95512148fc6df64535a995fbc8f499cae206 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 00:12:25 +0300 Subject: [PATCH 0644/2418] fix importlib.machinery issue on github's autotests #yolo --- modules/launch_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index aa9d1880240..4f48f3a13d4 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -227,13 +227,14 @@ def run_extensions_installers(settings_file): def mute_sdxl_imports(): """create fake modules that SDXL wants to import but doesn't actually use for our purposes""" - import importlib + class Dummy: + pass - module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec('taming.modules.losses.lpips', None)) + module = Dummy() module.LPIPS = None sys.modules['taming.modules.losses.lpips'] = module - module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec('sgm.data', None)) + module = Dummy() module.StableDataModuleFromConfig = None sys.modules['sgm.data'] = module From b717eb7e56a4e620e77a2225e80223c89cb4f0d1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 08:29:37 +0300 Subject: [PATCH 0645/2418] mute unneeded SDXL imports for tests too --- 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 4f48f3a13d4..56b972d510a 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -334,8 +334,6 @@ def prepare_environment(): if args.update_all_extensions: git_pull_recursive(extensions_dir) - mute_sdxl_imports() - if "--exit" in sys.argv: print("Exiting because of --exit argument") exit(0) @@ -357,6 +355,8 @@ def configure_for_tests(): def start(): + mute_sdxl_imports() + print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}") import webui if '--nowebui' in sys.argv: From ac4ccfa1369e74492b467294eab96c3f558b297b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 09:30:33 +0300 Subject: [PATCH 0646/2418] get attention optimizations to work --- modules/hypernetworks/hypernetwork.py | 2 +- modules/launch_utils.py | 1 + modules/sd_hijack_optimizations.py | 14 +++++++------- modules/sd_models_xl.py | 3 +++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 79670b87715..c4821d21a7e 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -378,7 +378,7 @@ def apply_hypernetworks(hypernetworks, context, layer=None): return context_k, context_v -def attention_CrossAttention_forward(self, x, context=None, mask=None): +def attention_CrossAttention_forward(self, x, context=None, mask=None, **kwargs): h = self.heads q = self.to_q(x) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 56b972d510a..183730d2946 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -239,6 +239,7 @@ class Dummy: sys.modules['sgm.data'] = module + def prepare_environment(): torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118") torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}") diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index e99c9ba5fee..b5f85ba519e 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -173,7 +173,7 @@ def get_available_vram(): # see https://github.com/basujindal/stable-diffusion/pull/117 for discussion -def split_cross_attention_forward_v1(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def split_cross_attention_forward_v1(self, x, context=None, mask=None, **kwargs): h = self.heads q_in = self.to_q(x) @@ -214,7 +214,7 @@ def split_cross_attention_forward_v1(self, x, context=None, mask=None, additiona # taken from https://github.com/Doggettx/stable-diffusion and modified -def split_cross_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def split_cross_attention_forward(self, x, context=None, mask=None, **kwargs): h = self.heads q_in = self.to_q(x) @@ -355,7 +355,7 @@ def einsum_op(q, k, v): return einsum_op_tensor_mem(q, k, v, 32) -def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, **kwargs): h = self.heads q = self.to_q(x) @@ -383,7 +383,7 @@ def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, add # Based on Birch-san's modified implementation of sub-quadratic attention from https://github.com/Birch-san/diffusers/pull/1 # The sub_quad_attention_forward function is under the MIT License listed under Memory Efficient Attention in the Licenses section of the web UI interface -def sub_quad_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def sub_quad_attention_forward(self, x, context=None, mask=None, **kwargs): assert mask is None, "attention-mask not currently implemented for SubQuadraticCrossAttnProcessor." h = self.heads @@ -470,7 +470,7 @@ def get_xformers_flash_attention_op(q, k, v): return None -def xformers_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): h = self.heads q_in = self.to_q(x) context = default(context, x) @@ -496,7 +496,7 @@ def xformers_attention_forward(self, x, context=None, mask=None, additional_toke # Based on Diffusers usage of scaled dot product attention from https://github.com/huggingface/diffusers/blob/c7da8fd23359a22d0df2741688b5b4f33c26df21/src/diffusers/models/cross_attention.py # The scaled_dot_product_attention_forward function contains parts of code under Apache-2.0 license listed under Scaled Dot Product Attention in the Licenses section of the web UI interface -def scaled_dot_product_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def scaled_dot_product_attention_forward(self, x, context=None, mask=None, **kwargs): batch_size, sequence_length, inner_dim = x.shape if mask is not None: @@ -537,7 +537,7 @@ def scaled_dot_product_attention_forward(self, x, context=None, mask=None, addit return hidden_states -def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0): +def scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None, **kwargs): with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): return scaled_dot_product_attention_forward(self, x, context, mask) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 9224c1a35f7..4d1aa497b64 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -55,3 +55,6 @@ def extend_sdxl(model): sgm.modules.diffusionmodules.openaimodel.print = lambda *args: None sgm.modules.encoders.modules.print = lambda *args: None +# this gets the code to load the vanilla attention that we override +sgm.modules.attention.SDP_IS_AVAILABLE = True +sgm.modules.attention.XFORMERS_IS_AVAILABLE = False \ No newline at end of file From 21aec6f567f52271efbbe33a2ab6561f9a47b787 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 09:38:54 +0300 Subject: [PATCH 0647/2418] lint --- modules/sd_models_xl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 4d1aa497b64..1dd4459fe9b 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -57,4 +57,4 @@ def extend_sdxl(model): # this gets the code to load the vanilla attention that we override sgm.modules.attention.SDP_IS_AVAILABLE = True -sgm.modules.attention.XFORMERS_IS_AVAILABLE = False \ No newline at end of file +sgm.modules.attention.XFORMERS_IS_AVAILABLE = False From 594c8e7b263d9b37f4b18b56b159aeb6d1bba1b4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 11:35:52 +0300 Subject: [PATCH 0648/2418] fix CLIP doing the unneeded normalization revert SD2.1 back to use the original repo add SDXL's force_zero_embeddings to negative prompt --- modules/processing.py | 2 +- modules/prompt_parser.py | 14 ++++++++++---- modules/sd_hijack.py | 2 +- modules/sd_hijack_clip.py | 15 +++++++++++++++ modules/sd_models_config.py | 1 - modules/sd_models_xl.py | 3 ++- 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 85d3542327f..f01a6907f8a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -344,7 +344,7 @@ def get_conds_with_caching(self, function, required_prompts, steps, caches, extr def setup_conds(self): prompts = prompt_parser.SdConditioning(self.prompts, width=self.width, height=self.height) - negative_prompts = prompt_parser.SdConditioning(self.negative_prompts, width=self.width, height=self.height) + 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 diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 33810669f7e..b29d079d536 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -116,11 +116,17 @@ class SdConditioning(list): A list with prompts for stable diffusion's conditioner model. Can also specify width and height of created image - SDXL needs it. """ - def __init__(self, prompts, width=None, height=None): + def __init__(self, prompts, is_negative_prompt=False, width=None, height=None, copy_from=None): super().__init__() self.extend(prompts) - self.width = width or getattr(prompts, 'width', None) - self.height = height or getattr(prompts, 'height', None) + + if copy_from is None: + copy_from = prompts + + self.is_negative_prompt = is_negative_prompt or getattr(copy_from, 'is_negative_prompt', False) + self.width = width or getattr(copy_from, 'width', None) + self.height = height or getattr(copy_from, 'height', None) + def get_learned_conditioning(model, prompts: SdConditioning | list[str], steps): @@ -153,7 +159,7 @@ def get_learned_conditioning(model, prompts: SdConditioning | list[str], steps): res.append(cached) continue - texts = [x[1] for x in prompt_schedule] + texts = SdConditioning([x[1] for x in prompt_schedule], copy_from=prompts) conds = model.get_learned_conditioning(texts) cond_schedule = [] diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 266811f955d..647cdfbed18 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -190,7 +190,7 @@ def hijack(self, m): if typename == 'FrozenCLIPEmbedder': model_embeddings = m.cond_stage_model.transformer.text_model.embeddings model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.token_embedding, self) - m.cond_stage_model = sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords(embedder, self) + m.cond_stage_model = sd_hijack_clip.FrozenCLIPEmbedderForSDXLWithCustomWords(embedder, self) conditioner.embedders[i] = m.cond_stage_model if typename == 'FrozenOpenCLIPEmbedder2': embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 6c17a81d26b..b3771909fe9 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -323,3 +323,18 @@ def encode_embedding_init_text(self, init_text, nvpt): embedded = embedding_layer.token_embedding.wrapped(ids.to(embedding_layer.token_embedding.wrapped.weight.device)).squeeze(0) return embedded + + +class FrozenCLIPEmbedderForSDXLWithCustomWords(FrozenCLIPEmbedderWithCustomWords): + def __init__(self, wrapped, hijack): + super().__init__(wrapped, hijack) + + def encode_with_transformers(self, tokens): + outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden") + + if self.wrapped.layer == "last": + z = outputs.last_hidden_state + else: + z = outputs.hidden_states[self.wrapped.layer_idx] + + return z diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 2e92479a157..04c09ab006a 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -12,7 +12,6 @@ config_default = shared.sd_default_config config_sd2 = os.path.join(sd_repo_configs_path, "v2-inference.yaml") config_sd2v = os.path.join(sd_repo_configs_path, "v2-inference-v.yaml") -config_sd2v = os.path.join(sd_xl_repo_configs_path, "sd_2_1_768.yaml") config_sd2_inpainting = os.path.join(sd_repo_configs_path, "v2-inpainting-inference.yaml") config_sdxl = os.path.join(sd_xl_repo_configs_path, "sd_xl_base.yaml") config_depth_model = os.path.join(sd_repo_configs_path, "v2-midas-inference.yaml") diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 1dd4459fe9b..b799ff46f08 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -22,7 +22,8 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: "target_size_as_tuple": torch.tensor([height, width]).repeat(len(batch), 1).to(devices.device, devices.dtype), } - c = self.conditioner(sdxl_conds) + force_zero_negative_prompt = getattr(batch, 'is_negative_prompt', False) and all(x == '' for x in batch) + c = self.conditioner(sdxl_conds, force_zero_embeddings=['txt'] if force_zero_negative_prompt else []) return c From 76ebb175ca996e93c063e7109c9f478a268952b6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 12:59:31 +0300 Subject: [PATCH 0649/2418] lora support --- extensions-builtin/Lora/lora.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cd46e6c7eee..03f1ef856f0 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -179,6 +179,11 @@ def load_lora(name, lora_on_disk): if m: sd_module = shared.sd_model.lora_layer_mapping.get(m.group(1), None) + # SDXL loras seem to already have correct compvis keys, so only need to replace "lora_unet" with "diffusion_model" + if sd_module is None and "lora_unet" in key_diffusers_without_lora_parts: + key = key_diffusers_without_lora_parts.replace("lora_unet", "diffusion_model") + sd_module = shared.sd_model.lora_layer_mapping.get(key, None) + if sd_module is None: keys_failed_to_match[key_diffusers] = key continue From 6f23da603d3cbba82262a3c62cc44c8d5cb9e6db Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 16:18:39 +0300 Subject: [PATCH 0650/2418] fix broken img2img --- modules/sd_models_xl.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index b799ff46f08..b19036f11ed 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -32,6 +32,9 @@ def apply_model(self: sgm.models.diffusion.DiffusionEngine, x, t, cond): return self.model(x, t, cond) +def get_first_stage_encoding(self, x): # SDXL's encode_first_stage does everything so get_first_stage_encoding is just there for compatibility + return x + def extend_sdxl(model): dtype = next(model.model.diffusion_model.parameters()).dtype model.model.diffusion_model.dtype = dtype @@ -50,6 +53,7 @@ def extend_sdxl(model): sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning sgm.models.diffusion.DiffusionEngine.apply_model = apply_model +sgm.models.diffusion.DiffusionEngine.get_first_stage_encoding = get_first_stage_encoding sgm.modules.attention.print = lambda *args: None sgm.modules.diffusionmodules.model.print = lambda *args: None From b8159d0919dcaa3a1a8f29e3aa30c25fe8e5f13b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 17:24:54 +0300 Subject: [PATCH 0651/2418] add XL support for live previews: approx and TAESD --- modules/sd_models_xl.py | 2 +- modules/sd_vae_approx.py | 37 ++++++++++++++++++++++++++----------- modules/sd_vae_taesd.py | 26 +++++++++++++------------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index b19036f11ed..af445a6100d 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -48,7 +48,7 @@ def extend_sdxl(model): discretization = sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization() model.alphas_cumprod = torch.asarray(discretization.alphas_cumprod, device=devices.device, dtype=dtype) - model.is_xl = True + model.is_sdxl = True sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index e2f004683be..b348f3aeecc 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -2,9 +2,9 @@ import torch from torch import nn -from modules import devices, paths +from modules import devices, paths, shared -sd_vae_approx_model = None +sd_vae_approx_models = {} class VAEApprox(nn.Module): @@ -31,19 +31,34 @@ def forward(self, x): return x +def download_model(model_path, model_url): + if not os.path.exists(model_path): + os.makedirs(os.path.dirname(model_path), exist_ok=True) + + print(f'Downloading VAEApprox model to: {model_path}') + torch.hub.download_url_to_file(model_url, model_path) + + def model(): - global sd_vae_approx_model + model_name = "vaeapprox-sdxl.pt" if getattr(shared.sd_model, 'is_sdxl', False) else "model.pt" + loaded_model = sd_vae_approx_models.get(model_name) - if sd_vae_approx_model is None: - model_path = os.path.join(paths.models_path, "VAE-approx", "model.pt") - sd_vae_approx_model = VAEApprox() + if loaded_model is None: + model_path = os.path.join(paths.models_path, "VAE-approx", model_name) if not os.path.exists(model_path): - model_path = os.path.join(paths.script_path, "models", "VAE-approx", "model.pt") - sd_vae_approx_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)) - sd_vae_approx_model.eval() - sd_vae_approx_model.to(devices.device, devices.dtype) + model_path = os.path.join(paths.script_path, "models", "VAE-approx", model_name) + + if not os.path.exists(model_path): + model_path = os.path.join(paths.models_path, "VAE-approx", model_name) + download_model(model_path, 'https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/download/v1.0.0-pre/' + model_name) + + loaded_model = VAEApprox() + loaded_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)) + loaded_model.eval() + loaded_model.to(devices.device, devices.dtype) + sd_vae_approx_models[model_name] = loaded_model - return sd_vae_approx_model + return loaded_model def cheap_approximation(sample): diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 5e8496e8739..5bf7c76e1dd 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -8,9 +8,9 @@ import torch import torch.nn as nn -from modules import devices, paths_internal +from modules import devices, paths_internal, shared -sd_vae_taesd = None +sd_vae_taesd_models = {} def conv(n_in, n_out, **kwargs): @@ -61,9 +61,7 @@ def unscale_latents(x): return x.sub(TAESD.latent_shift).mul(2 * TAESD.latent_magnitude) -def download_model(model_path): - model_url = 'https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth' - +def download_model(model_path, model_url): if not os.path.exists(model_path): os.makedirs(os.path.dirname(model_path), exist_ok=True) @@ -72,17 +70,19 @@ def download_model(model_path): def model(): - global sd_vae_taesd + model_name = "taesdxl_decoder.pth" if getattr(shared.sd_model, 'is_sdxl', False) else "taesd_decoder.pth" + loaded_model = sd_vae_taesd_models.get(model_name) - if sd_vae_taesd is None: - model_path = os.path.join(paths_internal.models_path, "VAE-taesd", "taesd_decoder.pth") - download_model(model_path) + if loaded_model is None: + model_path = os.path.join(paths_internal.models_path, "VAE-taesd", model_name) + download_model(model_path, 'https://github.com/madebyollin/taesd/raw/main/' + model_name) if os.path.exists(model_path): - sd_vae_taesd = TAESD(model_path) - sd_vae_taesd.eval() - sd_vae_taesd.to(devices.device, devices.dtype) + loaded_model = TAESD(model_path) + loaded_model.eval() + loaded_model.to(devices.device, devices.dtype) + sd_vae_taesd_models[model_name] = loaded_model else: raise FileNotFoundError('TAESD model not found') - return sd_vae_taesd.decoder + return loaded_model.decoder From e16ebc917dfc902f041963df0d4e99e8141cf82f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 17:32:35 +0300 Subject: [PATCH 0652/2418] repair --no-half for SDXL --- modules/sd_models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index e4aae597cf0..9e8cb3cfc0d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -395,10 +395,11 @@ def repair_config(sd_config): if not hasattr(sd_config.model.params, "use_ema"): sd_config.model.params.use_ema = False - if shared.cmd_opts.no_half: - sd_config.model.params.unet_config.params.use_fp16 = False - elif shared.cmd_opts.upcast_sampling: - sd_config.model.params.unet_config.params.use_fp16 = True + if hasattr(sd_config.model.params, 'unet_config'): + if shared.cmd_opts.no_half: + sd_config.model.params.unet_config.params.use_fp16 = False + elif shared.cmd_opts.upcast_sampling: + sd_config.model.params.unet_config.params.use_fp16 = True if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: sd_config.model.params.first_stage_config.params.ddconfig.attn_type = "vanilla" From ff73841c608f5f02e6352bb235d9dbf63d922990 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 17:42:16 +0300 Subject: [PATCH 0653/2418] mute SDXL imports in the place there SDXL is imported for the first time instead of launch.py --- modules/launch_utils.py | 18 ------------------ modules/paths.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 183730d2946..01ea7c91601 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -224,22 +224,6 @@ def run_extensions_installers(settings_file): run_extension_installer(os.path.join(extensions_dir, dirname_extension)) -def mute_sdxl_imports(): - """create fake modules that SDXL wants to import but doesn't actually use for our purposes""" - - class Dummy: - pass - - module = Dummy() - module.LPIPS = None - sys.modules['taming.modules.losses.lpips'] = module - - module = Dummy() - module.StableDataModuleFromConfig = None - sys.modules['sgm.data'] = module - - - def prepare_environment(): torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118") torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}") @@ -356,8 +340,6 @@ def configure_for_tests(): def start(): - mute_sdxl_imports() - print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}") import webui if '--nowebui' in sys.argv: diff --git a/modules/paths.py b/modules/paths.py index c6f8904ea10..2505233999b 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -5,6 +5,21 @@ import modules.safe # noqa: F401 +def mute_sdxl_imports(): + """create fake modules that SDXL wants to import but doesn't actually use for our purposes""" + + class Dummy: + pass + + module = Dummy() + module.LPIPS = None + sys.modules['taming.modules.losses.lpips'] = module + + module = Dummy() + module.StableDataModuleFromConfig = None + sys.modules['sgm.data'] = module + + # data_path = cmd_opts_pre.data sys.path.insert(0, script_path) @@ -18,6 +33,8 @@ assert sd_path is not None, f"Couldn't find Stable Diffusion in any of: {possible_sd_paths}" +mute_sdxl_imports() + path_dirs = [ (sd_path, 'ldm', 'Stable Diffusion', []), (os.path.join(sd_path, '../generative-models'), 'sgm', 'Stable Diffusion XL', ["sgm"]), From 6c5f83b19b331d51bde28c5033d13d0d64c11e54 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 21:17:50 +0300 Subject: [PATCH 0654/2418] add support for SDXL loras with te1/te2 modules --- extensions-builtin/Lora/lora.py | 41 +++++++++++++++++++++++++-------- modules/sd_models.py | 3 ++- modules/sd_models_xl.py | 1 - 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 03f1ef856f0..4b5da7b528e 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -68,6 +68,14 @@ def match(match_list, regex_text): return f"transformer_text_model_encoder_layers_{m[0]}_{m[1]}" + if match(m, r"lora_te2_text_model_encoder_layers_(\d+)_(.+)"): + if 'mlp_fc1' in m[1]: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" + elif 'mlp_fc2' in m[1]: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" + else: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" + return key @@ -142,10 +150,20 @@ def __init__(self): def assign_lora_names_to_compvis_modules(sd_model): lora_layer_mapping = {} - for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): - lora_name = name.replace(".", "_") - lora_layer_mapping[lora_name] = module - module.lora_layer_name = lora_name + if shared.sd_model.is_sdxl: + for i, embedder in enumerate(shared.sd_model.conditioner.embedders): + if not hasattr(embedder, 'wrapped'): + continue + + for name, module in embedder.wrapped.named_modules(): + lora_name = f'{i}_{name.replace(".", "_")}' + lora_layer_mapping[lora_name] = module + module.lora_layer_name = lora_name + else: + for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): + lora_name = name.replace(".", "_") + lora_layer_mapping[lora_name] = module + module.lora_layer_name = lora_name for name, module in shared.sd_model.model.named_modules(): lora_name = name.replace(".", "_") @@ -168,10 +186,10 @@ def load_lora(name, lora_on_disk): keys_failed_to_match = {} is_sd2 = 'model_transformer_resblocks' in shared.sd_model.lora_layer_mapping - for key_diffusers, weight in sd.items(): - key_diffusers_without_lora_parts, lora_key = key_diffusers.split(".", 1) - key = convert_diffusers_name_to_compvis(key_diffusers_without_lora_parts, is_sd2) + for key_lora, weight in sd.items(): + key_lora_without_lora_parts, lora_key = key_lora.split(".", 1) + key = convert_diffusers_name_to_compvis(key_lora_without_lora_parts, is_sd2) sd_module = shared.sd_model.lora_layer_mapping.get(key, None) if sd_module is None: @@ -180,12 +198,15 @@ def load_lora(name, lora_on_disk): sd_module = shared.sd_model.lora_layer_mapping.get(m.group(1), None) # SDXL loras seem to already have correct compvis keys, so only need to replace "lora_unet" with "diffusion_model" - if sd_module is None and "lora_unet" in key_diffusers_without_lora_parts: - key = key_diffusers_without_lora_parts.replace("lora_unet", "diffusion_model") + if sd_module is None and "lora_unet" in key_lora_without_lora_parts: + key = key_lora_without_lora_parts.replace("lora_unet", "diffusion_model") + sd_module = shared.sd_model.lora_layer_mapping.get(key, None) + elif sd_module is None and "lora_te1_text_model" in key_lora_without_lora_parts: + key = key_lora_without_lora_parts.replace("lora_te1_text_model", "0_transformer_text_model") sd_module = shared.sd_model.lora_layer_mapping.get(key, None) if sd_module is None: - keys_failed_to_match[key_diffusers] = key + keys_failed_to_match[key_lora] = key continue lora_module = lora.modules.get(key, None) diff --git a/modules/sd_models.py b/modules/sd_models.py index 9e8cb3cfc0d..077021755dc 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -289,7 +289,8 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer if state_dict is None: state_dict = get_checkpoint_state_dict(checkpoint_info, timer) - if hasattr(model, 'conditioner'): + model.is_sdxl = hasattr(model, 'conditioner') + if model.is_sdxl: sd_models_xl.extend_sdxl(model) model.load_state_dict(state_dict, strict=False) diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index af445a6100d..a7240dc0815 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -48,7 +48,6 @@ def extend_sdxl(model): discretization = sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization() model.alphas_cumprod = torch.asarray(discretization.alphas_cumprod, device=devices.device, dtype=dtype) - model.is_sdxl = True sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning From dc3906185656dae75fcefe96625b1dcd0d31579c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Thu, 13 Jul 2023 21:19:41 +0300 Subject: [PATCH 0655/2418] thank you linter --- extensions-builtin/Lora/lora.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 4b5da7b528e..302490fbd22 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -229,9 +229,9 @@ def load_lora(name, lora_on_disk): elif type(sd_module) == torch.nn.Conv2d and weight.shape[2:] == (3, 3): module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (3, 3), bias=False) else: - print(f'Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}') + print(f'Lora layer {key_lora} matched a layer with unsupported type: {type(sd_module).__name__}') continue - raise AssertionError(f"Lora layer {key_diffusers} matched a layer with unsupported type: {type(sd_module).__name__}") + raise AssertionError(f"Lora layer {key_lora} matched a layer with unsupported type: {type(sd_module).__name__}") with torch.no_grad(): module.weight.copy_(weight) @@ -243,7 +243,7 @@ def load_lora(name, lora_on_disk): elif lora_key == "lora_down.weight": lora_module.down = module else: - raise AssertionError(f"Bad Lora layer name: {key_diffusers} - must end in lora_up.weight, lora_down.weight or alpha") + raise AssertionError(f"Bad Lora layer name: {key_lora} - must end in lora_up.weight, lora_down.weight or alpha") if keys_failed_to_match: print(f"Failed to match keys when loading Lora {lora_on_disk.filename}: {keys_failed_to_match}") From a3db187e4f1ef59a571b1023309923f5e5e6dda3 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Fri, 14 Jul 2023 05:48:14 +0900 Subject: [PATCH 0656/2418] handles model hash cache.json error --- modules/hashes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/hashes.py b/modules/hashes.py index 8b7ea0ac363..ec1187fe85d 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -5,7 +5,7 @@ import filelock from modules import shared -from modules.paths import data_path +from modules.paths import data_path, script_path cache_filename = os.path.join(data_path, "cache.json") @@ -26,8 +26,13 @@ def cache(subsection): if not os.path.isfile(cache_filename): cache_data = {} else: - with open(cache_filename, "r", encoding="utf8") as file: - cache_data = json.load(file) + try: + with open(cache_filename, "r", encoding="utf8") as file: + cache_data = json.load(file) + except Exception: + os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) + print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') + cache_data = {} s = cache_data.get(subsection, {}) cache_data[subsection] = s From 6d8dcdefa07d5f8f7e528046b0facdcc51185e60 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 09:16:01 +0300 Subject: [PATCH 0657/2418] initial SDXL refiner support --- modules/sd_hijack.py | 18 ++++++++---- modules/sd_models.py | 3 +- modules/sd_models_config.py | 3 ++ modules/sd_models_xl.py | 57 ++++++++++++++++++++++++++++++------- modules/shared.py | 9 ++++-- 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 647cdfbed18..2b274c18230 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -180,21 +180,29 @@ def apply_optimizations(self, option=None): def hijack(self, m): conditioner = getattr(m, 'conditioner', None) if conditioner: + text_cond_models = [] + for i in range(len(conditioner.embedders)): embedder = conditioner.embedders[i] typename = type(embedder).__name__ if typename == 'FrozenOpenCLIPEmbedder': embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) - m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(embedder, self) - conditioner.embedders[i] = m.cond_stage_model + conditioner.embedders[i] = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(embedder, self) + text_cond_models.append(conditioner.embedders[i]) if typename == 'FrozenCLIPEmbedder': - model_embeddings = m.cond_stage_model.transformer.text_model.embeddings + model_embeddings = embedder.transformer.text_model.embeddings model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.token_embedding, self) - m.cond_stage_model = sd_hijack_clip.FrozenCLIPEmbedderForSDXLWithCustomWords(embedder, self) - conditioner.embedders[i] = m.cond_stage_model + conditioner.embedders[i] = sd_hijack_clip.FrozenCLIPEmbedderForSDXLWithCustomWords(embedder, self) + text_cond_models.append(conditioner.embedders[i]) if typename == 'FrozenOpenCLIPEmbedder2': embedder.model.token_embedding = EmbeddingsWithFixes(embedder.model.token_embedding, self) conditioner.embedders[i] = sd_hijack_open_clip.FrozenOpenCLIPEmbedder2WithCustomWords(embedder, self) + text_cond_models.append(conditioner.embedders[i]) + + if len(text_cond_models) == 1: + m.cond_stage_model = text_cond_models[0] + else: + m.cond_stage_model = conditioner if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: model_embeddings = m.cond_stage_model.roberta.embeddings diff --git a/modules/sd_models.py b/modules/sd_models.py index 077021755dc..267f4d8ed8a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -414,6 +414,7 @@ def repair_config(sd_config): sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' sdxl_clip_weight = 'conditioner.embedders.1.model.ln_final.weight' +sdxl_refiner_clip_weight = 'conditioner.embedders.0.model.ln_final.weight' class SdModelData: @@ -477,7 +478,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): state_dict = get_checkpoint_state_dict(checkpoint_info, timer) checkpoint_config = sd_models_config.find_checkpoint_config(state_dict, checkpoint_info) - clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict or sdxl_clip_weight in state_dict + clip_is_included_into_sd = any([x for x in [sd1_clip_weight, sd2_clip_weight, sdxl_clip_weight, sdxl_refiner_clip_weight] if x in state_dict]) timer.record("find config") diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 04c09ab006a..8266fa39797 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -14,6 +14,7 @@ config_sd2v = os.path.join(sd_repo_configs_path, "v2-inference-v.yaml") config_sd2_inpainting = os.path.join(sd_repo_configs_path, "v2-inpainting-inference.yaml") config_sdxl = os.path.join(sd_xl_repo_configs_path, "sd_xl_base.yaml") +config_sdxl_refiner = os.path.join(sd_xl_repo_configs_path, "sd_xl_refiner.yaml") config_depth_model = os.path.join(sd_repo_configs_path, "v2-midas-inference.yaml") config_unclip = os.path.join(sd_repo_configs_path, "v2-1-stable-unclip-l-inference.yaml") config_unopenclip = os.path.join(sd_repo_configs_path, "v2-1-stable-unclip-h-inference.yaml") @@ -72,6 +73,8 @@ def guess_model_config_from_state_dict(sd, filename): if sd.get('conditioner.embedders.1.model.ln_final.weight', None) is not None: return config_sdxl + if sd.get('conditioner.embedders.0.model.ln_final.weight', None) is not None: + return config_sdxl_refiner elif sd.get('depth_model.model.pretrained.act_postprocess3.0.project.0.bias', None) is not None: return config_depth_model elif sd2_variations_weight is not None and sd2_variations_weight.shape[0] == 768: diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index a7240dc0815..01320c7a504 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -14,15 +14,20 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: width = getattr(self, 'target_width', 1024) height = getattr(self, 'target_height', 1024) + is_negative_prompt = getattr(batch, 'is_negative_prompt', False) + aesthetic_score = shared.opts.sdxl_refiner_low_aesthetic_score if is_negative_prompt else shared.opts.sdxl_refiner_high_aesthetic_score + + devices_args = dict(device=devices.device, dtype=devices.dtype) sdxl_conds = { "txt": batch, - "original_size_as_tuple": torch.tensor([height, width]).repeat(len(batch), 1).to(devices.device, devices.dtype), - "crop_coords_top_left": torch.tensor([shared.opts.sdxl_crop_top, shared.opts.sdxl_crop_left]).repeat(len(batch), 1).to(devices.device, devices.dtype), - "target_size_as_tuple": torch.tensor([height, width]).repeat(len(batch), 1).to(devices.device, devices.dtype), + "original_size_as_tuple": torch.tensor([height, width], **devices_args).repeat(len(batch), 1), + "crop_coords_top_left": torch.tensor([shared.opts.sdxl_crop_top, shared.opts.sdxl_crop_left], **devices_args).repeat(len(batch), 1), + "target_size_as_tuple": torch.tensor([height, width], **devices_args).repeat(len(batch), 1), + "aesthetic_score": torch.tensor([aesthetic_score], **devices_args).repeat(len(batch), 1), } - force_zero_negative_prompt = getattr(batch, 'is_negative_prompt', False) and all(x == '' for x in batch) + force_zero_negative_prompt = is_negative_prompt and all(x == '' for x in batch) c = self.conditioner(sdxl_conds, force_zero_embeddings=['txt'] if force_zero_negative_prompt else []) return c @@ -35,25 +40,55 @@ def apply_model(self: sgm.models.diffusion.DiffusionEngine, x, t, cond): def get_first_stage_encoding(self, x): # SDXL's encode_first_stage does everything so get_first_stage_encoding is just there for compatibility return x + +sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning +sgm.models.diffusion.DiffusionEngine.apply_model = apply_model +sgm.models.diffusion.DiffusionEngine.get_first_stage_encoding = get_first_stage_encoding + + +def encode_embedding_init_text(self: sgm.modules.GeneralConditioner, init_text, nvpt): + res = [] + + for embedder in [embedder for embedder in self.embedders if hasattr(embedder, 'encode_embedding_init_text')]: + encoded = embedder.encode_embedding_init_text(init_text, nvpt) + res.append(encoded) + + return torch.cat(res, dim=1) + + +def process_texts(self, texts): + for embedder in [embedder for embedder in self.embedders if hasattr(embedder, 'process_texts')]: + return embedder.process_texts(texts) + + +def get_target_prompt_token_count(self, token_count): + for embedder in [embedder for embedder in self.embedders if hasattr(embedder, 'get_target_prompt_token_count')]: + return embedder.get_target_prompt_token_count(token_count) + + +# those additions to GeneralConditioner make it possible to use it as model.cond_stage_model from SD1.5 in exist +sgm.modules.GeneralConditioner.encode_embedding_init_text = encode_embedding_init_text +sgm.modules.GeneralConditioner.process_texts = process_texts +sgm.modules.GeneralConditioner.get_target_prompt_token_count = get_target_prompt_token_count + + def extend_sdxl(model): + """this adds a bunch of parameters to make SDXL model look a bit more like SD1.5 to the rest of the codebase.""" + dtype = next(model.model.diffusion_model.parameters()).dtype model.model.diffusion_model.dtype = dtype model.model.conditioning_key = 'crossattn' - - model.cond_stage_model = [x for x in model.conditioner.embedders if 'CLIPEmbedder' in type(x).__name__][0] - model.cond_stage_key = model.cond_stage_model.input_key + model.cond_stage_key = 'txt' + # model.cond_stage_model will be set in sd_hijack model.parameterization = "v" if isinstance(model.denoiser.scaling, sgm.modules.diffusionmodules.denoiser_scaling.VScaling) else "eps" discretization = sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization() model.alphas_cumprod = torch.asarray(discretization.alphas_cumprod, device=devices.device, dtype=dtype) + model.conditioner.wrapped = torch.nn.Module() -sgm.models.diffusion.DiffusionEngine.get_learned_conditioning = get_learned_conditioning -sgm.models.diffusion.DiffusionEngine.apply_model = apply_model -sgm.models.diffusion.DiffusionEngine.get_first_stage_encoding = get_first_stage_encoding - sgm.modules.attention.print = lambda *args: None sgm.modules.diffusionmodules.model.print = lambda *args: None sgm.modules.diffusionmodules.openaimodel.print = lambda *args: None diff --git a/modules/shared.py b/modules/shared.py index 71afd94f115..234ede0d7ce 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -428,8 +428,13 @@ def list_samplers(): "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}).link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU"]}).info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors"), - "sdxl_crop_top": OptionInfo(0, "SDXL top coordinate of the crop"), - "sdxl_crop_left": OptionInfo(0, "SDXL left coordinate of the crop"), +})) + +options_templates.update(options_section(('sdxl', "Stable Diffusion XL"), { + "sdxl_crop_top": OptionInfo(0, "crop top coordinate"), + "sdxl_crop_left": OptionInfo(0, "crop left coordinate"), + "sdxl_refiner_low_aesthetic_score": OptionInfo(2.5, "SDXL low aesthetic score", gr.Number).info("used for refiner model negative prompt"), + "sdxl_refiner_high_aesthetic_score": OptionInfo(6.0, "SDXL high aesthetic score", gr.Number).info("used for refiner model prompt"), })) options_templates.update(options_section(('optimizations', "Optimizations"), { From b7dbeda0d9e475aafa9db0cfe015bf724502ec20 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 09:19:08 +0300 Subject: [PATCH 0658/2418] linter --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 267f4d8ed8a..729f03d7f94 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -478,7 +478,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): state_dict = get_checkpoint_state_dict(checkpoint_info, timer) checkpoint_config = sd_models_config.find_checkpoint_config(state_dict, checkpoint_info) - clip_is_included_into_sd = any([x for x in [sd1_clip_weight, sd2_clip_weight, sdxl_clip_weight, sdxl_refiner_clip_weight] if x in state_dict]) + clip_is_included_into_sd = any(x for x in [sd1_clip_weight, sd2_clip_weight, sdxl_clip_weight, sdxl_refiner_clip_weight] if x in state_dict) timer.record("find config") From abb948dab09841571dd24c6be9ff9d6b212778ea Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 09:28:01 +0300 Subject: [PATCH 0659/2418] raise maximum Negative Guidance minimum sigma due to request in PR discussion --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index 234ede0d7ce..89b7132e429 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -439,7 +439,7 @@ def list_samplers(): options_templates.update(options_section(('optimizations', "Optimizations"), { "cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}), - "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), + "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), From 714c920c20d07d70a0dd07c8c5cb54d9378e92c4 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 09:47:44 +0300 Subject: [PATCH 0660/2418] do not run workflow items twice for PRs from this repo update names --- .github/workflows/on_pull_request.yaml | 6 +++++- .github/workflows/run_tests.yaml | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index 8ebf5918623..c56eea6bcff 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -1,4 +1,4 @@ -name: Run Linting/Formatting on Pull Requests +name: Linter on: - push @@ -6,7 +6,9 @@ on: jobs: lint-python: + name: Python linter runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: Checkout Code uses: actions/checkout@v3 @@ -22,7 +24,9 @@ jobs: - name: Run Ruff run: ruff . lint-js: + name: Javascript linter runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: Checkout Code uses: actions/checkout@v3 diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 178c026ae61..2af21448f25 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -1,4 +1,4 @@ -name: Run basic features tests on CPU with empty SD model +name: Tests on: - push @@ -6,7 +6,9 @@ on: jobs: test: + name: Tests on CPU with empty model runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: Checkout Code uses: actions/checkout@v3 From 9a3f35b028a8026291679c35e1df5b2aea327a1d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 09:56:01 +0300 Subject: [PATCH 0661/2418] repair medvram and lowvram --- modules/lowvram.py | 4 +++- modules/sd_hijack_open_clip.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/lowvram.py b/modules/lowvram.py index da4f33a8a02..6bbc11eb737 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -100,7 +100,9 @@ def first_stage_model_decode_wrap(z): sd_model.depth_model.register_forward_pre_hook(send_me_to_gpu) if sd_model.embedder: sd_model.embedder.register_forward_pre_hook(send_me_to_gpu) - parents[sd_model.cond_stage_model.transformer] = sd_model.cond_stage_model + + if hasattr(sd_model, 'cond_stage_model'): + parents[sd_model.cond_stage_model.transformer] = sd_model.cond_stage_model if use_medvram: sd_model.model.register_forward_pre_hook(send_me_to_gpu) diff --git a/modules/sd_hijack_open_clip.py b/modules/sd_hijack_open_clip.py index fcf5ad07de7..bb0b96c7261 100644 --- a/modules/sd_hijack_open_clip.py +++ b/modules/sd_hijack_open_clip.py @@ -32,7 +32,7 @@ def encode_with_transformers(self, tokens): def encode_embedding_init_text(self, init_text, nvpt): ids = tokenizer.encode(init_text) ids = torch.asarray([ids], device=devices.device, dtype=torch.int) - embedded = self.wrapped.model.token_embedding.wrapped(ids).squeeze(0) + embedded = self.wrapped.model.token_embedding.wrapped(ids.to(self.wrapped.model.token_embedding.wrapped.weight.device)).squeeze(0) return embedded @@ -66,6 +66,6 @@ def encode_with_transformers(self, tokens): def encode_embedding_init_text(self, init_text, nvpt): ids = tokenizer.encode(init_text) ids = torch.asarray([ids], device=devices.device, dtype=torch.int) - embedded = self.wrapped.model.token_embedding.wrapped(ids).squeeze(0) + embedded = self.wrapped.model.token_embedding.wrapped(ids.to(self.wrapped.model.token_embedding.wrapped.weight.device)).squeeze(0) return embedded From 62e32634677f872d0325a8c9330bb7c12fe1f310 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 10:07:08 +0300 Subject: [PATCH 0662/2418] edit names more --- .github/workflows/on_pull_request.yaml | 4 ++-- .github/workflows/run_tests.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index c56eea6bcff..78e608ee945 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -6,7 +6,7 @@ on: jobs: lint-python: - name: Python linter + name: ruff runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: @@ -24,7 +24,7 @@ jobs: - name: Run Ruff run: ruff . lint-js: - name: Javascript linter + name: eslint runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 2af21448f25..e9370cc0758 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -6,7 +6,7 @@ on: jobs: test: - name: Tests on CPU with empty model + name: tests on CPU with empty model runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: From 471a5a66b73921d569242daccc5275cb195e3f06 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 17:54:09 +0300 Subject: [PATCH 0663/2418] add more relevant fields to caching conds --- modules/processing.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index f01a6907f8a..f68e010dd5a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -330,8 +330,21 @@ def get_conds_with_caching(self, function, required_prompts, steps, caches, extr caches is a list with items described above. """ + + cached_params = ( + required_prompts, + steps, + opts.CLIP_stop_at_last_layers, + shared.sd_model.sd_checkpoint_info, + extra_network_data, + opts.sdxl_crop_left, + opts.sdxl_crop_top, + self.width, + self.height, + ) + for cache in caches: - if cache[0] is not None and (required_prompts, steps, opts.CLIP_stop_at_last_layers, shared.sd_model.sd_checkpoint_info, extra_network_data) == cache[0]: + if cache[0] is not None and cached_params == cache[0]: return cache[1] cache = caches[0] @@ -339,7 +352,7 @@ def get_conds_with_caching(self, function, required_prompts, steps, caches, extr with devices.autocast(): cache[1] = function(shared.sd_model, required_prompts, steps) - cache[0] = (required_prompts, steps, opts.CLIP_stop_at_last_layers, shared.sd_model.sd_checkpoint_info, extra_network_data) + cache[0] = cached_params return cache[1] def setup_conds(self): From ac2d47ff4c00b041cae3d882c2832662c2c64935 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 20:27:41 +0300 Subject: [PATCH 0664/2418] add cheap VAE approximation coeffs for SDXL --- modules/sd_vae_approx.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index b348f3aeecc..86bd658ad32 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -64,12 +64,22 @@ def model(): def cheap_approximation(sample): # https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/2 - coefs = torch.tensor([ - [0.298, 0.207, 0.208], - [0.187, 0.286, 0.173], - [-0.158, 0.189, 0.264], - [-0.184, -0.271, -0.473], - ]).to(sample.device) + if shared.sd_model.is_sdxl: + coeffs = [ + [ 0.3448, 0.4168, 0.4395], + [-0.1953, -0.0290, 0.0250], + [ 0.1074, 0.0886, -0.0163], + [-0.3730, -0.2499, -0.2088], + ] + else: + coeffs = [ + [ 0.298, 0.207, 0.208], + [ 0.187, 0.286, 0.173], + [-0.158, 0.189, 0.264], + [-0.184, -0.271, -0.473], + ] + + coefs = torch.tensor(coeffs).to(sample.device) x_sample = torch.einsum("lxy,lr -> rxy", sample, coefs) From 5dee0fa1f812cf9f5fa6675c22c9a57afad39983 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 21:41:21 +0300 Subject: [PATCH 0665/2418] add a message about unsupported samplers --- modules/sd_samplers.py | 3 +++ modules/sd_samplers_compvis.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index f22aad8f216..bea2684c4db 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -28,6 +28,9 @@ def create_sampler(name, model): assert config is not None, f'bad sampler name: {name}' + if model.is_sdxl and config.options.get("no_sdxl", False): + raise Exception(f"Sampler {config.name} is not supported for SDXL") + sampler = config.constructor(model) sampler.config = config diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index bdae8b404b5..4a8396f97ec 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -11,9 +11,9 @@ samplers_data_compvis = [ - sd_samplers_common.SamplerData('DDIM', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.ddim.DDIMSampler, model), [], {"default_eta_is_0": True, "uses_ensd": True}), - sd_samplers_common.SamplerData('PLMS', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.plms.PLMSSampler, model), [], {}), - sd_samplers_common.SamplerData('UniPC', lambda model: VanillaStableDiffusionSampler(modules.models.diffusion.uni_pc.UniPCSampler, model), [], {}), + sd_samplers_common.SamplerData('DDIM', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.ddim.DDIMSampler, model), [], {"default_eta_is_0": True, "uses_ensd": True, "no_sdxl": True}), + sd_samplers_common.SamplerData('PLMS', lambda model: VanillaStableDiffusionSampler(ldm.models.diffusion.plms.PLMSSampler, model), [], {"no_sdxl": True}), + sd_samplers_common.SamplerData('UniPC', lambda model: VanillaStableDiffusionSampler(modules.models.diffusion.uni_pc.UniPCSampler, model), [], {"no_sdxl": True}), ] From 95ee0cb18817df3c4fae2e7ba7063b79b0c60b9c Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Fri, 14 Jul 2023 22:51:58 +0300 Subject: [PATCH 0666/2418] restyle time taken/VRAM display --- javascript/hints.js | 2 -- modules/call_queue.py | 18 +++++++++++++----- style.css | 17 ++++++++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/javascript/hints.js b/javascript/hints.js index dc75ce31374..41201b2f5bd 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -84,8 +84,6 @@ var titles = { "Checkpoint name": "Loads weights from checkpoint before making images. You can either use hash or a part of filename (as seen in settings) for checkpoint name. Recommended to use with Y axis for less switching.", "Inpainting conditioning mask strength": "Only applies to inpainting models. Determines how strongly to mask off the original image for inpainting and img2img. 1.0 means fully masked, which is the default behaviour. 0.0 means a fully unmasked conditioning. Lower values will help preserve the overall composition of the image, but will struggle with large changes.", - "vram": "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).", - "Eta noise seed delta": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.", "Filename word regex": "This regular expression will be used extract words from filename, and they will be joined using the option below into label text used for training. Leave empty to keep filename text as it is.", diff --git a/modules/call_queue.py b/modules/call_queue.py index 3b94f8a4c8e..61aa240fb32 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -85,9 +85,9 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): elapsed = time.perf_counter() - t elapsed_m = int(elapsed // 60) elapsed_s = elapsed % 60 - elapsed_text = f"{elapsed_s:.2f}s" + elapsed_text = f"{elapsed_s:.1f} sec." if elapsed_m > 0: - elapsed_text = f"{elapsed_m}m "+elapsed_text + elapsed_text = f"{elapsed_m} min. "+elapsed_text if run_memmon: mem_stats = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.stop().items()} @@ -95,14 +95,22 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): reserved_peak = mem_stats['reserved_peak'] sys_peak = mem_stats['system_peak'] sys_total = mem_stats['total'] - sys_pct = round(sys_peak/max(sys_total, 1) * 100, 2) + sys_pct = sys_peak/max(sys_total, 1) * 100 - vram_html = f"

    Torch active/reserved: {active_peak}/{reserved_peak} MiB, Sys VRAM: {sys_peak}/{sys_total} MiB ({sys_pct}%)

    " + toltip_a = "Active: peak amount of video memory used during generation (excluding cached data)" + toltip_r = "Reserved: total amout of video memory allocated by the Torch library " + toltip_sys = "System: peak amout of video memory allocated by all running programs, out of total capacity" + + text_a = f"A: {active_peak/1024:.2f} GB" + text_r = f"R: {reserved_peak/1024:.2f} GB" + text_sys = f"Sys: {sys_peak/1024:.1f}/{sys_total/1024:g} GB ({sys_pct:.1f}%)" + + vram_html = f"

    {text_a}, {text_r}, {text_sys}

    " else: vram_html = '' # last item is always HTML - res[-1] += f"

    Time taken: {elapsed_text}

    {vram_html}
    " + res[-1] += f"

    Time taken: {elapsed_text}

    {vram_html}
    " return tuple(res) diff --git a/style.css b/style.css index 5073f0f0963..27ea64671ea 100644 --- a/style.css +++ b/style.css @@ -230,17 +230,28 @@ button.custom-button{ .performance { font-size: 0.85em; color: #444; + display: flex; } .performance p{ display: inline-block; } -.performance .time { - margin-right: 0; +.performance p.time, .performance p.vram, .performance p.time abbr, .performance p.vram abbr { + margin-bottom: 0; + color: var(--block-title-text-color); } -.performance .vram { +.performance p.time { +} + +.performance p.vram { + margin-left: auto; +} + +.performance .measurement{ + color: var(--body-text-color); + font-weight: bold; } #txt2img_generate, #img2img_generate { From 5d94088eac401545286ef8b16455cc88e9797300 Mon Sep 17 00:00:00 2001 From: Marcus Adams Date: Fri, 14 Jul 2023 21:52:00 -0400 Subject: [PATCH 0667/2418] Added [none] filename token. --- modules/images.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/images.py b/modules/images.py index 4bdedb7ff4d..fb5d2e750a1 100644 --- a/modules/images.py +++ b/modules/images.py @@ -380,6 +380,7 @@ def get_vae_filename(self): #get the name of the VAE file. 'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT, 'user': lambda self: self.p.user, 'vae_filename': lambda self: self.get_vae_filename(), + 'none': lambda self: '', # Overrides the default so you can get just the sequence number } default_time_format = '%Y%m%d%H%M%S' @@ -601,13 +602,13 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i else: file_decoration = opts.samples_filename_pattern or "[seed]-[prompt_spaces]" + file_decoration = namegen.apply(file_decoration) + suffix + add_number = opts.save_images_add_number or file_decoration == '' if file_decoration != "" and add_number: file_decoration = f"-{file_decoration}" - file_decoration = namegen.apply(file_decoration) + suffix - if add_number: basecount = get_next_sequence_number(path, basename) fullfn = None From 14cf434bc36d0ef31f31d4c6cd2bd15d7857d5c8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 07:33:16 +0300 Subject: [PATCH 0668/2418] fix an issue in live previews that happens when you use SDXL with fp16 VAE --- modules/processing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index f68e010dd5a..eb4a60eba73 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -539,8 +539,7 @@ def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, see def decode_first_stage(model, x): - with devices.autocast(disable=x.dtype == devices.dtype_vae): - x = model.decode_first_stage(x) + x = model.decode_first_stage(x.to(devices.dtype_vae)) return x From b8bd8ce4cf687e9e02000387adf4d751b22a4a36 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 07:44:37 +0300 Subject: [PATCH 0669/2418] disable rich exception output in console for API by default, use WEBUI_RICH_EXCEPTIONS env var to enable --- modules/api/api.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 11045292624..2a4cd8a2012 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -1,5 +1,6 @@ import base64 import io +import os import time import datetime import uvicorn @@ -98,14 +99,16 @@ def encode_pil_to_base64(image): def api_middleware(app: FastAPI): - rich_available = True + rich_available = False try: - import anyio # importing just so it can be placed on silent list - import starlette # importing just so it can be placed on silent list - from rich.console import Console - console = Console() + if os.environ.get('WEBUI_RICH_EXCEPTIONS', None) is not None: + import anyio # importing just so it can be placed on silent list + import starlette # importing just so it can be placed on silent list + from rich.console import Console + console = Console() + rich_available = True except Exception: - rich_available = False + pass @app.middleware("http") async def log_and_time(req: Request, call_next): @@ -116,14 +119,14 @@ async def log_and_time(req: Request, call_next): endpoint = req.scope.get('path', 'err') if shared.cmd_opts.api_log and endpoint.startswith('/sdapi'): print('API {t} {code} {prot}/{ver} {method} {endpoint} {cli} {duration}'.format( - t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), - code = res.status_code, - ver = req.scope.get('http_version', '0.0'), - cli = req.scope.get('client', ('0:0.0.0', 0))[0], - prot = req.scope.get('scheme', 'err'), - method = req.scope.get('method', 'err'), - endpoint = endpoint, - duration = duration, + t=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + code=res.status_code, + ver=req.scope.get('http_version', '0.0'), + cli=req.scope.get('client', ('0:0.0.0', 0))[0], + prot=req.scope.get('scheme', 'err'), + method=req.scope.get('method', 'err'), + endpoint=endpoint, + duration=duration, )) return res @@ -134,7 +137,7 @@ def handle_exception(request: Request, e: Exception): "body": vars(e).get('body', ''), "errors": str(e), } - if not isinstance(e, HTTPException): # do not print backtrace on known httpexceptions + if not isinstance(e, HTTPException): # do not print backtrace on known httpexceptions message = f"API error: {request.method}: {request.url} {err}" if rich_available: print(message) From 127635409a7959f6c057a68ccb8e70734cbaf9f3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 08:07:25 +0300 Subject: [PATCH 0670/2418] add padding and identification to generation log section (Failed to find Loras, Used embeddings, etc...) --- modules/img2img.py | 2 +- modules/txt2img.py | 2 +- modules/ui.py | 3 +-- modules/ui_common.py | 9 +++++---- style.css | 16 ++++++++++------ 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 664e26888cd..a811e7a4b1b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -240,4 +240,4 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s if opts.do_not_show_images: processed.images = [] - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments, classname="comments") diff --git a/modules/txt2img.py b/modules/txt2img.py index d0be2e73fd9..29d94e8cb2c 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -70,4 +70,4 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step if opts.do_not_show_images: processed.images = [] - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments, classname="comments") diff --git a/modules/ui.py b/modules/ui.py index 39d226ada79..07ecee7b680 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -83,8 +83,7 @@ def gr_show(visible=True): up_down_symbol = '\u2195\ufe0f' # ↕️ -def plaintext_to_html(text): - return ui_common.plaintext_to_html(text) +plaintext_to_html = ui_common.plaintext_to_html def send_gradio_gallery_to_image(x): diff --git a/modules/ui_common.py b/modules/ui_common.py index 57c2d0ade52..11eb2a4b288 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -29,9 +29,10 @@ def update_generation_info(generation_info, html_info, img_index): return html_info, gr.update() -def plaintext_to_html(text): - text = "

    " + "
    \n".join([f"{html.escape(x)}" for x in text.split('\n')]) + "

    " - return text +def plaintext_to_html(text, classname=None): + content = "
    \n".join(html.escape(x) for x in text.split('\n')) + + return f"

    {content}

    " if classname else f"

    {content}

    " def save_files(js_data, images, do_make_zip, index): @@ -157,7 +158,7 @@ def open_folder(f): with gr.Group(): html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext") - html_log = gr.HTML(elem_id=f'html_log_{tabname}') + html_log = gr.HTML(elem_id=f'html_log_{tabname}', elem_classes="html-log") generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}') if tabname == 'txt2img' or tabname == 'img2img': diff --git a/style.css b/style.css index 27ea64671ea..a424067fe3e 100644 --- a/style.css +++ b/style.css @@ -227,29 +227,33 @@ button.custom-button{ align-self: end; } -.performance { +.html-log .comments{ + padding-top: 0.5em; +} + +.html-log .performance { font-size: 0.85em; color: #444; display: flex; } -.performance p{ +.html-log .performance p{ display: inline-block; } -.performance p.time, .performance p.vram, .performance p.time abbr, .performance p.vram abbr { +.html-log .performance p.time, .performance p.vram, .performance p.time abbr, .performance p.vram abbr { margin-bottom: 0; color: var(--block-title-text-color); } -.performance p.time { +.html-log .performance p.time { } -.performance p.vram { +.html-log .performance p.vram { margin-left: auto; } -.performance .measurement{ +.html-log .performance .measurement{ color: var(--body-text-color); font-weight: bold; } From 2b1bae0d755c2d5201f6a6aadeadb5588208d43f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 08:41:22 +0300 Subject: [PATCH 0671/2418] add textual inversion hashes to infotext --- modules/processing.py | 7 ++++--- modules/sd_hijack.py | 5 ++++- modules/sd_hijack_clip.py | 15 ++++++++++++--- modules/shared.py | 1 + modules/textual_inversion/textual_inversion.py | 9 ++++++++- style.css | 4 ++++ 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index cd568a208aa..49441e7761b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -732,9 +732,10 @@ def infotext(iteration=0, position_in_batch=0, use_main_prompt=False): p.setup_conds() - if len(model_hijack.comments) > 0: - for comment in model_hijack.comments: - comments[comment] = 1 + for comment in model_hijack.comments: + comments[comment] = 1 + + p.extra_generation_params.update(model_hijack.extra_generation_params) if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 3b6f95ce2ea..6b5aae4b5f8 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -147,7 +147,6 @@ def undo_weighted_forward(sd_model): class StableDiffusionModelHijack: fixes = None - comments = [] layers = None circular_enabled = False clip = None @@ -156,6 +155,9 @@ class StableDiffusionModelHijack: embedding_db = modules.textual_inversion.textual_inversion.EmbeddingDatabase() def __init__(self): + self.extra_generation_params = {} + self.comments = [] + self.embedding_db.add_embedding_dir(cmd_opts.embeddings_dir) def apply_optimizations(self, option=None): @@ -236,6 +238,7 @@ def apply_circular(self, enable): def clear_comments(self): self.comments = [] + self.extra_generation_params = {} def get_prompt_lengths(self, text): if self.clip is None: diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 3b5a7666717..c1d780a3317 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -229,9 +229,18 @@ def forward(self, texts): z = self.process_tokens(tokens, multipliers) zs.append(z) - if len(used_embeddings) > 0: - embeddings_list = ", ".join([f'{name} [{embedding.checksum()}]' for name, embedding in used_embeddings.items()]) - self.hijack.comments.append(f"Used embeddings: {embeddings_list}") + if opts.textual_inversion_add_hashes_to_infotext and used_embeddings: + hashes = [] + for name, embedding in used_embeddings.items(): + shorthash = embedding.shorthash + if not shorthash: + continue + + name = name.replace(":", "").replace(",", "") + hashes.append(f"{name}: {shorthash}") + + if hashes: + self.hijack.extra_generation_params["TI hashes"] = ", ".join(hashes) return torch.hstack(zs) diff --git a/modules/shared.py b/modules/shared.py index 48478a68b0d..a32fd4edb78 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -472,6 +472,7 @@ def list_samplers(): "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_restart(), + "textual_inversion_add_hashes_to_infotext": OptionInfo(True, "Add Textual Inversion hashes to infotext"), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *hypernetworks]}, refresh=reload_hypernetworks), })) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index cbe975b7540..38e072a884e 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -13,7 +13,7 @@ from PIL import Image, PngImagePlugin from torch.utils.tensorboard import SummaryWriter -from modules import shared, devices, sd_hijack, processing, sd_models, images, sd_samplers, sd_hijack_checkpoint, errors +from modules import shared, devices, sd_hijack, processing, sd_models, images, sd_samplers, sd_hijack_checkpoint, errors, hashes import modules.textual_inversion.dataset from modules.textual_inversion.learn_schedule import LearnRateScheduler @@ -49,6 +49,8 @@ def __init__(self, vec, name, step=None): self.sd_checkpoint_name = None self.optimizer_state_dict = None self.filename = None + self.hash = None + self.shorthash = None def save(self, filename): embedding_data = { @@ -82,6 +84,10 @@ def const_hash(a): self.cached_checksum = f'{const_hash(self.vec.reshape(-1) * 100) & 0xffff:04x}' return self.cached_checksum + def set_hash(self, v): + self.hash = v + self.shorthash = self.hash[0:12] + class DirWithTextualInversionEmbeddings: def __init__(self, path): @@ -199,6 +205,7 @@ def load_from_file(self, path, filename): embedding.vectors = vec.shape[0] embedding.shape = vec.shape[-1] embedding.filename = path + embedding.set_hash(hashes.sha256(embedding.filename, "textual_inversion/" + name) or '') if self.expected_shape == -1 or self.expected_shape == embedding.shape: self.register_embedding(embedding, shared.sd_model) diff --git a/style.css b/style.css index a424067fe3e..9e13d7fd58a 100644 --- a/style.css +++ b/style.css @@ -231,6 +231,10 @@ button.custom-button{ padding-top: 0.5em; } +.html-log .comments:empty{ + padding-top: 0; +} + .html-log .performance { font-size: 0.85em; color: #444; From 510e5fc8c60dd6278d0bc52effc23257c717dc1b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 09:20:43 +0300 Subject: [PATCH 0672/2418] cache git extension repo information --- modules/cache.py | 96 ++++++++++++++++++++++++++++++++++++++++ modules/extensions.py | 26 ++++++++--- modules/hashes.py | 38 ++-------------- modules/ui_extensions.py | 6 --- 4 files changed, 119 insertions(+), 47 deletions(-) create mode 100644 modules/cache.py diff --git a/modules/cache.py b/modules/cache.py new file mode 100644 index 00000000000..4c2db604df5 --- /dev/null +++ b/modules/cache.py @@ -0,0 +1,96 @@ +import json +import os.path + +import filelock + +from modules.paths import data_path, script_path + +cache_filename = os.path.join(data_path, "cache.json") +cache_data = None + + +def dump_cache(): + """ + Saves all cache data to a file. + """ + + with filelock.FileLock(f"{cache_filename}.lock"): + with open(cache_filename, "w", encoding="utf8") as file: + json.dump(cache_data, file, indent=4) + + +def cache(subsection): + """ + Retrieves or initializes a cache for a specific subsection. + + Parameters: + subsection (str): The subsection identifier for the cache. + + Returns: + dict: The cache data for the specified subsection. + """ + + global cache_data + + if cache_data is None: + with filelock.FileLock(f"{cache_filename}.lock"): + if not os.path.isfile(cache_filename): + cache_data = {} + else: + try: + with open(cache_filename, "r", encoding="utf8") as file: + cache_data = json.load(file) + except Exception: + os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) + print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') + cache_data = {} + + s = cache_data.get(subsection, {}) + cache_data[subsection] = s + + return s + + +def cached_data_for_file(subsection, title, filename, func): + """ + Retrieves or generates data for a specific file, using a caching mechanism. + + Parameters: + subsection (str): The subsection of the cache to use. + title (str): The title of the data entry in the subsection of the cache. + filename (str): The path to the file to be checked for modifications. + func (callable): A function that generates the data if it is not available in the cache. + + Returns: + dict or None: The cached or generated data, or None if data generation fails. + + The `cached_data_for_file` function implements a caching mechanism for data stored in files. + It checks if the data associated with the given `title` is present in the cache and compares the + modification time of the file with the cached modification time. If the file has been modified, + the cache is considered invalid and the data is regenerated using the provided `func`. + Otherwise, the cached data is returned. + + If the data generation fails, None is returned to indicate the failure. Otherwise, the generated + or cached data is returned as a dictionary. + """ + + existing_cache = cache(subsection) + ondisk_mtime = os.path.getmtime(filename) + + entry = existing_cache.get(title) + if entry: + cached_mtime = existing_cache[title].get("mtime", 0) + if ondisk_mtime > cached_mtime: + entry = None + + if not entry: + entry = func() + if entry is None: + return None + + entry['mtime'] = ondisk_mtime + existing_cache[title] = entry + + dump_cache() + + return entry diff --git a/modules/extensions.py b/modules/extensions.py index abc6e2b1d0a..c561159afba 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -1,7 +1,7 @@ import os import threading -from modules import shared, errors +from modules import shared, errors, cache from modules.gitpython_hack import Repo from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401 @@ -21,6 +21,7 @@ def active(): class Extension: lock = threading.Lock() + cached_fields = ['remote', 'commit_date', 'branch', 'commit_hash', 'version'] def __init__(self, name, path, enabled=True, is_builtin=False): self.name = name @@ -36,15 +37,29 @@ def __init__(self, name, path, enabled=True, is_builtin=False): self.remote = None self.have_info_from_repo = False + def to_dict(self): + return {x: getattr(self, x) for x in self.cached_fields} + + def from_dict(self, d): + for field in self.cached_fields: + setattr(self, field, d[field]) + def read_info_from_repo(self): if self.is_builtin or self.have_info_from_repo: return - with self.lock: - if self.have_info_from_repo: - return + def read_from_repo(): + with self.lock: + if self.have_info_from_repo: + return + + self.do_read_info_from_repo() + + return self.to_dict() - self.do_read_info_from_repo() + d = cache.cached_data_for_file('extensions-git', self.name, os.path.join(self.path, ".git"), read_from_repo) + self.from_dict(d) + self.status = 'unknown' def do_read_info_from_repo(self): repo = None @@ -58,7 +73,6 @@ def do_read_info_from_repo(self): self.remote = None else: try: - self.status = 'unknown' self.remote = next(repo.remote().urls, None) commit = repo.head.commit self.commit_date = commit.committed_date diff --git a/modules/hashes.py b/modules/hashes.py index ec1187fe85d..b7a33b427c5 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -1,43 +1,11 @@ import hashlib -import json import os.path -import filelock - from modules import shared -from modules.paths import data_path, script_path - - -cache_filename = os.path.join(data_path, "cache.json") -cache_data = None - - -def dump_cache(): - with filelock.FileLock(f"{cache_filename}.lock"): - with open(cache_filename, "w", encoding="utf8") as file: - json.dump(cache_data, file, indent=4) - - -def cache(subsection): - global cache_data - - if cache_data is None: - with filelock.FileLock(f"{cache_filename}.lock"): - if not os.path.isfile(cache_filename): - cache_data = {} - else: - try: - with open(cache_filename, "r", encoding="utf8") as file: - cache_data = json.load(file) - except Exception: - os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) - print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') - cache_data = {} - - s = cache_data.get(subsection, {}) - cache_data[subsection] = s +import modules.cache - return s +dump_cache = modules.cache.dump_cache +cache = modules.cache.cache def calculate_sha256(filename): diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index dff522ef83a..3fa3dea2ed2 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -513,14 +513,8 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" def preload_extensions_git_metadata(): - t0 = time.time() for extension in extensions.extensions: extension.read_info_from_repo() - print( - f"preload_extensions_git_metadata for " - f"{len(extensions.extensions)} extensions took " - f"{time.time() - t0:.2f}s" - ) def create_ui(): From 0aa8d538e147ba87df36d8196845807c8fa3f4e1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 09:24:22 +0300 Subject: [PATCH 0673/2418] suppress printing TI embedding into console by default --- modules/shared.py | 1 + modules/textual_inversion/textual_inversion.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index a32fd4edb78..427dcc505d0 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -472,6 +472,7 @@ def list_samplers(): "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_restart(), + "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), "textual_inversion_add_hashes_to_infotext": OptionInfo(True, "Add Textual Inversion hashes to infotext"), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None", *hypernetworks]}, refresh=reload_hypernetworks), })) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 38e072a884e..6166c76f653 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -256,7 +256,7 @@ def load_textual_inversion_embeddings(self, force_reload=False): self.word_embeddings.update(sorted_word_embeddings) displayed_embeddings = (tuple(self.word_embeddings.keys()), tuple(self.skipped_embeddings.keys())) - if self.previously_displayed_embeddings != displayed_embeddings: + if shared.opts.textual_inversion_print_at_load and self.previously_displayed_embeddings != displayed_embeddings: self.previously_displayed_embeddings = displayed_embeddings print(f"Textual inversion embeddings loaded({len(self.word_embeddings)}): {', '.join(self.word_embeddings.keys())}") if self.skipped_embeddings: From c58cf73c806f08eb8b96bccc2af64403d903695f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 09:33:21 +0300 Subject: [PATCH 0674/2418] remove "## " from changelog.md version --- modules/launch_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 0e0dbca496d..ff77cbfd513 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -69,10 +69,12 @@ def git_tag(): return subprocess.check_output([git, "describe", "--tags"], shell=False, encoding='utf8').strip() except Exception: try: - from pathlib import Path - changelog_md = Path(__file__).parent.parent / "CHANGELOG.md" - with changelog_md.open(encoding="utf-8") as file: - return next((line.strip() for line in file if line.strip()), "") + + changelog_md = os.path.join(os.path.dirname(os.path.dirname(__file__)), "CHANGELOG.md") + with open(changelog_md, "r", encoding="utf-8") as file: + line = next((line.strip() for line in file if line.strip()), "") + line = line.replace("## ", "") + return line except Exception: return "" From 2d9d53be21d339a0723276517fff067db0181af5 Mon Sep 17 00:00:00 2001 From: Jabasukuriputo Wang Date: Sat, 15 Jul 2023 17:09:51 +0800 Subject: [PATCH 0675/2418] allow replacing extensions index with environment variable --- 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 3fa3dea2ed2..f3e4fba7eec 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -1,5 +1,5 @@ import json -import os.path +import os import threading import time from datetime import datetime @@ -564,7 +564,8 @@ def create_ui(): with gr.TabItem("Available", id="available"): with gr.Row(): refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary") - available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json", label="Extension index URL").style(container=False) + extensions_index_url = os.environ.get('WEBUI_EXTENSIONS_INDEX', "https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json") + available_extensions_index = gr.Text(value=extensions_index_url, label="Extension index URL").style(container=False) extension_to_install = gr.Text(elem_id="extension_to_install", visible=False) install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) From 2970d712ee52eaffee36d0e86cc7def71393a9b5 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 16 Jul 2023 00:59:31 +0900 Subject: [PATCH 0676/2418] Warns merge into master --- .github/workflows/warns_merge_master.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/warns_merge_master.yml diff --git a/.github/workflows/warns_merge_master.yml b/.github/workflows/warns_merge_master.yml new file mode 100644 index 00000000000..ae2aab6ba8c --- /dev/null +++ b/.github/workflows/warns_merge_master.yml @@ -0,0 +1,19 @@ +name: Pull requests can't target master branch + +"on": + pull_request: + types: + - opened + - synchronize + - reopened + branches: + - master + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Warning marge into master + run: | + echo -e "::warning::This pull request directly merge into \"master\" branch, normally development happens on \"dev\" branch." + exit 1 From e5d3ae2bf4e9d39c35e6edc96d6449fd42528e55 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 20:39:04 +0300 Subject: [PATCH 0677/2418] user metadata system for custom networks --- .../Lora/ui_extra_networks_lora.py | 2 +- html/extra-networks-card.html | 8 +- javascript/extraNetworks.js | 37 +++- modules/ui_extra_networks.py | 54 +++++- modules/ui_extra_networks_checkpoints.py | 2 +- modules/ui_extra_networks_hypernets.py | 6 +- modules/ui_extra_networks_user_metadata.py | 169 ++++++++++++++++++ style.css | 56 ++++-- 8 files changed, 300 insertions(+), 34 deletions(-) create mode 100644 modules/ui_extra_networks_user_metadata.py diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index da49790b56c..29b16c1ceb3 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -20,7 +20,7 @@ def list_items(self): yield { "name": name, - "filename": path, + "filename": lora_on_disk.filename, "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index 68a84c3aac4..fb787ffe93a 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,11 +1,11 @@
    {background_image} - {metadata_button} +
    + {edit_button} + {metadata_button} +
    -
    {name} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index b87bca3ecb4..68f342de654 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -182,19 +182,20 @@ function extraNetworksSearchButton(tabs_id, event) { var globalPopup = null; var globalPopupInner = null; +function closePopup(){ + if (!globalPopup) return; + + globalPopup.style.display = "none"; +} function popup(contents) { if (!globalPopup) { globalPopup = document.createElement('div'); - globalPopup.onclick = function() { - globalPopup.style.display = "none"; - }; + globalPopup.onclick = closePopup; globalPopup.classList.add('global-popup'); var close = document.createElement('div'); close.classList.add('global-popup-close'); - close.onclick = function() { - globalPopup.style.display = "none"; - }; + close.onclick = closePopup; close.title = "Close"; globalPopup.appendChild(close); @@ -263,3 +264,27 @@ function extraNetworksRequestMetadata(event, extraPage, cardName) { event.stopPropagation(); } + +extraPageUserMetadataEditors = {} + +function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { + var id = tabname + '_' + extraPage + '_edit_user_metadata'; + + editor = extraPageUserMetadataEditors[id] + if(! editor){ + editor = {}; + editor.page = gradioApp().getElementById(id); + editor.nameTextarea = gradioApp().querySelector("#" + id + "_name" + ' textarea'); + editor.button = gradioApp().querySelector("#" + id + "_button"); + extraPageUserMetadataEditors[id] = editor; + } + + editor.nameTextarea.value = cardName; + updateInput(editor.nameTextarea); + + editor.button.click(); + + popup(editor.page); + + event.stopPropagation(); +} diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 693cafb643c..eaae6217605 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -2,7 +2,7 @@ import urllib.parse from pathlib import Path -from modules import shared +from modules import shared, ui_extra_networks_user_metadata, errors from modules.images import read_info_from_image, save_image_with_geninfo from modules.ui import up_down_symbol import gradio as gr @@ -60,13 +60,34 @@ class ExtraNetworksPage: def __init__(self, title): self.title = title self.name = title.lower() + self.id_page = self.name.replace(" ", "_") self.card_page = shared.html("extra-networks-card.html") self.allow_negative_prompt = False self.metadata = {} + self.items = {} def refresh(self): pass + def read_user_metadata(self, item): + filename = item.get("filename", None) + basename, ext = os.path.splitext(filename) + metadata_filename = basename + '.json' + + metadata = {} + try: + if os.path.isfile(metadata_filename): + with open(metadata_filename, "r", encoding="utf8") as file: + metadata = json.load(file) + except Exception as e: + errors.display(e, f"reading extra network user metadata from {metadata_filename}") + + desc = metadata.get("description", None) + if desc is not None: + item["description"] = desc + + item["user_metadata"] = metadata + def link_preview(self, filename): quoted_filename = urllib.parse.quote(filename.replace('\\', '/')) mtime = os.path.getmtime(filename) @@ -119,11 +140,15 @@ def create_html(self, tabname): """ for subdir in subdirs]) - for item in self.list_items(): + self.items = {x["name"]: x for x in self.list_items()} + for item in self.items.values(): metadata = item.get("metadata") if metadata: self.metadata[item["name"]] = metadata + if "user_metadata" not in item: + self.read_user_metadata(item) + items_html += self.create_html_for_item(item, tabname) if items_html == '': @@ -166,7 +191,9 @@ def create_html_for_item(self, item, tabname): metadata_button = "" metadata = item.get("metadata") if metadata: - metadata_button = f"" + metadata_button = f"" + + edit_button = f"
    " local_path = "" filename = item.get("filename", "") @@ -200,6 +227,7 @@ def create_html_for_item(self, item, tabname): "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), "metadata_button": metadata_button, + "edit_button": edit_button, "search_only": " search_only" if search_only else "", "sort_keys": sort_keys, } @@ -247,6 +275,9 @@ def find_description(self, path): pass return None + def create_user_metadata_editor(self, ui, tabname): + return ui_extra_networks_user_metadata.UserMetadataEditor(ui, tabname, self) + def initialize(): extra_pages.clear() @@ -297,20 +328,23 @@ def create_ui(container, button, tabname): ui = ExtraNetworksUi() ui.pages = [] ui.pages_contents = [] + ui.user_metadata_editors = [] ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy()) ui.tabname = tabname with gr.Tabs(elem_id=tabname+"_extra_tabs"): for page in ui.stored_extra_pages: - page_id = page.title.lower().replace(" ", "_") - - with gr.Tab(page.title, id=page_id): - elem_id = f"{tabname}_{page_id}_cards_html" + with gr.Tab(page.title, id=page.id_page): + elem_id = f"{tabname}_{page.id_page}_cards_html" page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + json.dumps(tabname) + '); return []}', inputs=[], outputs=[]) + editor = page.create_user_metadata_editor(ui, tabname) + editor.create_ui() + ui.user_metadata_editors.append(editor) + gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", multiselect=False, visible=False, show_label=False, interactive=True) gr.Button(up_down_symbol, elem_id=tabname+"_extra_sortorder") @@ -363,6 +397,8 @@ def path_is_parent(parent_path, child_path): def setup_ui(ui, gallery): def save_preview(index, images, filename): + # this function is here for backwards compatibility and likely will be removed soon + if len(images) == 0: print("There is no image in gallery to save as a preview.") return [page.create_html(ui.tabname) for page in ui.stored_extra_pages] @@ -394,3 +430,7 @@ def save_preview(index, images, filename): outputs=[*ui.pages] ) + for editor in ui.user_metadata_editors: + editor.setup_ui(gallery) + + diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 8b9ab71b244..bb5071e6a4e 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -18,7 +18,7 @@ def list_items(self): path, ext = os.path.splitext(checkpoint.filename) yield { "name": checkpoint.name_for_extra, - "filename": path, + "filename": checkpoint.filename, "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 7c19b53257a..ea0b7a4403f 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -12,12 +12,12 @@ def refresh(self): shared.reload_hypernetworks() def list_items(self): - for index, (name, path) in enumerate(shared.hypernetworks.items()): - path, ext = os.path.splitext(path) + for index, (name, full_path) in enumerate(shared.hypernetworks.items()): + path, ext = os.path.splitext(full_path) yield { "name": name, - "filename": path, + "filename": full_path, "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(path), diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py new file mode 100644 index 00000000000..8d20d026ed9 --- /dev/null +++ b/modules/ui_extra_networks_user_metadata.py @@ -0,0 +1,169 @@ +import datetime +import html +import json +import os.path + +import gradio as gr + +from modules import generation_parameters_copypaste, images, sysinfo, errors + + +class UserMetadataEditor: + + def __init__(self, ui, tabname, page): + self.ui = ui + self.tabname = tabname + self.page = page + self.id_part = f"{self.tabname}_{self.page.id_page}_edit_user_metadata" + + self.box = None + + self.edit_name_input = None + self.button_edit = None + + self.edit_name = None + self.edit_description = None + self.html_filedata = None + self.html_preview = None + + self.button_cancel = None + self.button_replace_preview = None + self.button_save = None + + def get_user_metadata(self, name): + item = self.page.items.get(name, {}) + + user_metadata = item.get('user_metadata', None) + if user_metadata is None: + user_metadata = {} + item['user_metadata'] = user_metadata + + return user_metadata + + def create_default_editor_elems(self): + with gr.Row(): + with gr.Column(scale=2): + self.edit_name = gr.HTML(elem_classes="extra-network-name") + self.edit_description = gr.Textbox(label="Description", lines=4) + self.html_filedata = gr.HTML() + + with gr.Column(scale=1, min_width=0): + self.html_preview = gr.HTML() + + def create_default_buttons(self): + + with gr.Row(): + self.button_cancel = gr.Button('Cancel') + self.button_replace_preview = gr.Button('Replace preview', variant='primary') + self.button_save = gr.Button('Save', variant='primary') + + self.button_cancel.click(fn=None, _js="closePopup") + + def get_card_html(self, name): + item = self.page.items.get(name, {}) + + preview_url = item.get("preview", None) + + if not preview_url: + filename, _ = os.path.splitext(item["filename"]) + preview_url = self.page.find_preview(filename) + item["preview"] = preview_url + + if preview_url: + preview = f''' +
    + +
    + ''' + else: + preview = "
    " + + return preview + + def get_metadata_table(self, name): + item = self.page.items.get(name, {}) + try: + filename = item["filename"] + + stats = os.stat(filename) + params = [ + ('File size: ', sysinfo.pretty_bytes(stats.st_size)), + ('Created: ', datetime.datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M')), + ('Last modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')), + ] + + return params + except Exception as e: + errors.display(e, f"reading info for {name}") + return [] + + def put_values_into_components(self, name): + user_metadata = self.get_user_metadata(name) + + params = self.get_metadata_table(name) + table = '
    Extension + + Extension + URL Branch Version
    {html.escape(ext.name)}{html.escape(ext.name)} {remote} {ext.branch} {version_link}
    {html.escape(name)}
    {tags_text}
    {html.escape(description)}

    Added: {html.escape(added)}

    {html.escape(description)}

    Added: {html.escape(added)}stars: {stars:,}

    {install_code}
    {html.escape(name)}
    {tags_text}
    {html.escape(description)}

    Added: {html.escape(added)}stars: {stars:,}

    {html.escape(description)}

    + Update: {html.escape(update_time)} Added: {html.escape(added)} Created: {html.escape(create_time)}stars: {stars}

    {install_code}
    ' + "".join(f"" for name, value in params) + '' + + return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name) + + def write_user_metadata(self, name, metadata): + item = self.page.items.get(name, {}) + filename = item.get("filename", None) + basename, ext = os.path.splitext(filename) + + with open(basename + '.json', "w", encoding="utf8") as file: + json.dump(metadata, file) + + def save_user_metadata(self, name, desc): + user_metadata = self.get_user_metadata(name) + user_metadata["description"] = desc + + self.write_user_metadata(name, user_metadata) + + def create_editor(self): + self.create_default_editor_elems() + + self.create_default_buttons() + + self.button_edit\ + .click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview])\ + .then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box]) + + self.button_save.click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[]).then(fn=None, _js="closePopup") + + def create_ui(self): + with gr.Box(visible=False, elem_id=self.id_part, elem_classes="edit-user-metadata") as box: + self.box = box + + self.edit_name_input = gr.Textbox("Edit user metadata card id", visible=False, elem_id=f"{self.id_part}_name") + self.button_edit = gr.Button("Edit user metadata", visible=False, elem_id=f"{self.id_part}_button") + + self.create_editor() + + def save_preview(self, index, gallery, name): + if len(gallery) == 0: + print("There is no image in gallery to save as a preview.") + return [self.get_card_html(name)] + [page.create_html(self.ui.tabname) for page in self.ui.stored_extra_pages] + + item = self.page.items.get(name, {}) + + index = int(index) + index = 0 if index < 0 else index + index = len(gallery) - 1 if index >= len(gallery) else index + + img_info = gallery[index if index >= 0 else 0] + image = generation_parameters_copypaste.image_from_url_text(img_info) + geninfo, items = images.read_info_from_image(image) + + images.save_image_with_geninfo(image, geninfo, item["local_preview"]) + + return [self.get_card_html(name)] + [page.create_html(self.tabname) for page in self.ui.stored_extra_pages] + + def setup_ui(self, gallery): + self.button_replace_preview.click( + fn=self.save_preview, + _js="function(x, y, z){return [selected_gallery_index(), y, z]}", + inputs=[self.edit_name_input, gallery, self.edit_name_input], + outputs=[self.html_preview, *self.ui.pages] + ) + + diff --git a/style.css b/style.css index 9e13d7fd58a..4431c1aaa67 100644 --- a/style.css +++ b/style.css @@ -550,6 +550,9 @@ table.popup-table .link{ background-color: rgba(20, 20, 20, 0.95); } +.global-popup *{ + box-sizing: border-box; +} .global-popup-close:before { content: "×"; @@ -815,32 +818,42 @@ footer { } -.extra-network-cards .card .metadata-button:before, .extra-network-thumbs .card .metadata-button:before{ - content: "🛈"; -} -.extra-network-cards .card .metadata-button, .extra-network-thumbs .card .metadata-button{ +.extra-network-cards .card .button-row, .extra-network-thumbs .card .button-row{ display: none; position: absolute; color: white; right: 0; } -.extra-network-cards .card .metadata-button { +.extra-network-cards .card:hover .button-row, .extra-network-thumbs .card:hover .button-row{ + display: flex; +} + +.extra-network-cards .card .card-button, .extra-network-thumbs .card .card-button{ + color: white; +} + +.extra-network-cards .card .metadata-button:before, .extra-network-thumbs .card .metadata-button:before{ + content: "🛈"; +} + +.extra-network-cards .card .edit-button:before, .extra-network-thumbs .card .edit-button:before{ + content: "🛠"; +} + +.extra-network-cards .card .card-button { text-shadow: 2px 2px 3px black; padding: 0.25em; font-size: 22pt; width: 1.5em; } -.extra-network-thumbs .card .metadata-button { +.extra-network-thumbs .card .card-button { text-shadow: 1px 1px 2px black; padding: 0; font-size: 16pt; width: 1em; top: -0.25em; } -.extra-network-cards .card:hover .metadata-button, .extra-network-thumbs .card:hover .metadata-button{ - display: inline-block; -} -.extra-network-cards .card .metadata-button:hover, .extra-network-thumbs .card .metadata-button:hover{ +.extra-network-cards .card .card-button:hover, .extra-network-thumbs .card .card-button:hover{ color: red; } @@ -861,7 +874,7 @@ footer { position: relative; } -.extra-network-thumbs .card .preview{ +.extra-network-thumbs .card .preview, .standalone-card-preview.card .preview{ position: absolute; object-fit: cover; width: 100%; @@ -905,7 +918,7 @@ footer { word-break: break-all; } -.extra-network-cards .card{ +.extra-network-cards .card, .standalone-card-preview.card{ display: inline-block; margin: 0.5em; width: 16em; @@ -989,3 +1002,22 @@ footer { width: 100%; height:100%; } + +div.block.gradio-box.edit-user-metadata { + min-width: 56em; + background: var(--body-background-fill); + padding: 2em !important; +} + +.edit-user-metadata .extra-network-name{ + font-size: 18pt; + color: var(--body-text-color); +} + +.edit-user-metadata .file-metadata th{ + text-align: left; +} + +.edit-user-metadata .wrap.translucent{ + background: var(--body-background-fill); +} From 5decbf184b185026d5da9e2c7be02d06fd640f12 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sat, 15 Jul 2023 21:05:33 +0300 Subject: [PATCH 0678/2418] eslint --- javascript/extraNetworks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 68f342de654..7007b353f0a 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -182,7 +182,7 @@ function extraNetworksSearchButton(tabs_id, event) { var globalPopup = null; var globalPopupInner = null; -function closePopup(){ +function closePopup() { if (!globalPopup) return; globalPopup.style.display = "none"; @@ -265,13 +265,13 @@ function extraNetworksRequestMetadata(event, extraPage, cardName) { event.stopPropagation(); } -extraPageUserMetadataEditors = {} +var extraPageUserMetadataEditors = {}; function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { var id = tabname + '_' + extraPage + '_edit_user_metadata'; - editor = extraPageUserMetadataEditors[id] - if(! editor){ + var editor = extraPageUserMetadataEditors[id]; + if (!editor) { editor = {}; editor.page = gradioApp().getElementById(id); editor.nameTextarea = gradioApp().querySelector("#" + id + "_name" + ' textarea'); From 11f339733de860b0b51adebe15dc945df7189edf Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 00:56:53 +0300 Subject: [PATCH 0679/2418] add lora user metadata editor dialog inspired by MrKuenning's mockup from #7458 --- .../Lora/ui_edit_user_metadata.py | 187 ++++++++++++++++++ .../Lora/ui_extra_networks_lora.py | 17 +- javascript/extraNetworks.js | 18 +- modules/ui_extra_networks_user_metadata.py | 23 ++- style.css | 9 +- 5 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 extensions-builtin/Lora/ui_edit_user_metadata.py diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py new file mode 100644 index 00000000000..c7dbd1c1a80 --- /dev/null +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -0,0 +1,187 @@ +import html +import json +import random + +import gradio as gr +import re + +from modules import ui_extra_networks_user_metadata + + +def is_non_comma_tagset(tags): + average_tag_length = sum(len(x) for x in tags.keys()) / len(tags) + + return average_tag_length >= 16 + + +re_word = re.compile(r"[-_\w']+") +re_comma = re.compile(r" *, *") + + +def build_tags(metadata): + tags = {} + + for _, tags_dict in metadata.get("ss_tag_frequency", {}).items(): + for tag, tag_count in tags_dict.items(): + tag = tag.strip() + tags[tag] = tags.get(tag, 0) + int(tag_count) + + if tags and is_non_comma_tagset(tags): + new_tags = {} + + for text, text_count in tags.items(): + for word in re.findall(re_word, text): + if len(word) < 3: + continue + + new_tags[word] = new_tags.get(word, 0) + text_count + + tags = new_tags + + ordered_tags = sorted(tags.keys(), key=tags.get, reverse=True) + + return [(tag, tags[tag]) for tag in ordered_tags] + + +class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor): + def __init__(self, ui, tabname, page): + super().__init__(ui, tabname, page) + + self.taginfo = None + self.edit_activation_text = None + self.slider_preferred_weight = None + self.edit_notes = None + + def save_lora_user_metadata(self, name, desc, activation_text, preferred_weight, notes): + user_metadata = self.get_user_metadata(name) + user_metadata["description"] = desc + user_metadata["activation text"] = activation_text + user_metadata["preferred weight"] = preferred_weight + user_metadata["notes"] = notes + + self.write_user_metadata(name, user_metadata) + + def get_metadata_table(self, name): + table = super().get_metadata_table(name) + item = self.page.items.get(name, {}) + metadata = json.loads(item.get("metadata") or '{}') + + keys = [ + ('ss_sd_model_name', "Model:"), + ('ss_resolution', "Resolution:"), + ('ss_clip_skip', "Clip skip:"), + ] + + for key, label in keys: + value = metadata.get(key, None) + if value is not None and str(value) != "None": + table.append((label, html.escape(value))) + + image_count = 0 + for _, params in metadata.get("ss_dataset_dirs", {}).items(): + image_count += int(params.get("img_count", 0)) + + if image_count: + table.append(("Dataset size:", image_count)) + + return table + + def put_values_into_components(self, name): + user_metadata = self.get_user_metadata(name) + values = super().put_values_into_components(name) + + item = self.page.items.get(name, {}) + metadata = json.loads(item.get("metadata") or '{}') + + tags = build_tags(metadata) + gradio_tags = [(tag, str(count)) for tag, count in tags[0:24]] + + return [ + *values[0:4], + gr.HighlightedText.update(value=gradio_tags, visible=True if tags else False), + user_metadata.get('activation text', ''), + float(user_metadata.get('preferred weight', 0.0)), + user_metadata.get('notes', ''), + gr.update(visible=True if tags else False), + gr.update(value=self.generate_random_prompt_from_tags(tags), visible=True if tags else False), + ] + + def generate_random_prompt(self, name): + item = self.page.items.get(name, {}) + metadata = json.loads(item.get("metadata") or '{}') + tags = build_tags(metadata) + + return self.generate_random_prompt_from_tags(tags) + + def generate_random_prompt_from_tags(self, tags): + max_count = None + res = [] + for tag, count in tags: + if not max_count: + max_count = count + + v = random.random() * max_count + if count > v: + res.append(tag) + + return ", ".join(sorted(res)) + + def create_editor(self): + self.create_default_editor_elems() + + self.taginfo = gr.HighlightedText(label="Tags") + self.edit_activation_text = gr.Text(label='Activation text', info="Will be added to prompt along with Lora") + self.slider_preferred_weight = gr.Slider(label='Preferred weight', info="Set to 0 to disable", minimum=0.0, maximum=2.0, step=0.01) + + with gr.Row() as row_random_prompt: + with gr.Column(scale=8): + random_prompt = gr.Textbox(label='Random prompt', lines=4, max_lines=4, interactive=False) + + with gr.Column(scale=1, min_width=120): + generate_random_prompt = gr.Button('Generate').style(full_width=True, size="lg") + + self.edit_notes = gr.TextArea(label='Notes', lines=4) + + generate_random_prompt.click(fn=self.generate_random_prompt, inputs=[self.edit_name_input], outputs=[random_prompt]) + + def select_tag(activation_text, evt: gr.SelectData): + tag = evt.value[0] + + words = re.split(re_comma, activation_text) + if tag in words: + words = [x for x in words if x != tag and x.strip()] + return ", ".join(words) + + return activation_text + ", " + tag if activation_text else tag + + self.taginfo.select(fn=select_tag, inputs=[self.edit_activation_text], outputs=[self.edit_activation_text], show_progress=False) + + self.create_default_buttons() + + viewed_components = [ + self.edit_name, + self.edit_description, + self.html_filedata, + self.html_preview, + self.taginfo, + self.edit_activation_text, + self.slider_preferred_weight, + self.edit_notes, + row_random_prompt, + random_prompt, + ] + + self.button_edit\ + .click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=viewed_components)\ + .then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box]) + + edited_components = [ + self.edit_description, + self.edit_activation_text, + self.slider_preferred_weight, + self.edit_notes, + ] + + self.button_save\ + .click(fn=self.save_lora_user_metadata, inputs=[self.edit_name_input, *edited_components], outputs=[]) \ + .then(fn=None, _js="extraNetworksReloadAll") diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 29b16c1ceb3..95296275cf8 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -3,6 +3,7 @@ import lora from modules import shared, ui_extra_networks +from ui_edit_user_metadata import LoraUserMetadataEditor class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): @@ -18,19 +19,29 @@ def list_items(self): alias = lora_on_disk.get_alias() - yield { + item = { "name": name, "filename": lora_on_disk.filename, "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), - "prompt": json.dumps(f""), "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, "sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)}, - } + self.read_user_metadata(item) + activation_text = item["user_metadata"].get("activation text") + preferred_weight = item["user_metadata"].get("preferred weight", 0.0) + item["prompt"] = json.dumps(f"") + + if activation_text: + item["prompt"] += " + " + json.dumps(" " + activation_text) + + yield item + def allowed_directories_for_previews(self): return [shared.cmd_opts.lora_dir] + def create_user_metadata_editor(self, ui, tabname): + return LoraUserMetadataEditor(ui, tabname, self) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 7007b353f0a..8b67bf2bd2f 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -113,7 +113,7 @@ function setupExtraNetworks() { onUiLoaded(setupExtraNetworks); -var re_extranet = /<([^:]+:[^:]+):[\d.]+>/; +var re_extranet = /<([^:]+:[^:]+):[\d.]+>(.*)/; var re_extranet_g = /\s+<([^:]+:[^:]+):[\d.]+>/g; function tryToRemoveExtraNetworkFromPrompt(textarea, text) { @@ -121,15 +121,22 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text) { var replaced = false; var newTextareaText; if (m) { + var extraTextAfterNet = m[2]; var partToSearch = m[1]; - newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found) { + var foundAtPosition = -1; + newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found, net, pos) { m = found.match(re_extranet); if (m[1] == partToSearch) { replaced = true; + foundAtPosition = pos; return ""; } return found; }); + + if (foundAtPosition >= 0 && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) { + newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length); + } } else { newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) { if (found == text) { @@ -288,3 +295,10 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { event.stopPropagation(); } + +function extraNetworksReloadAll() { + closePopup(); + + gradioApp().getElementById('txt2img_extra_refresh').click(); + gradioApp().getElementById('img2img_extra_refresh').click(); +} diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 8d20d026ed9..0dbd7419aef 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -52,7 +52,7 @@ def create_default_editor_elems(self): def create_default_buttons(self): - with gr.Row(): + with gr.Row(elem_classes="edit-user-metadata-buttons"): self.button_cancel = gr.Button('Cancel') self.button_replace_preview = gr.Button('Replace preview', variant='primary') self.button_save = gr.Button('Save', variant='primary') @@ -88,8 +88,7 @@ def get_metadata_table(self, name): stats = os.stat(filename) params = [ ('File size: ', sysinfo.pretty_bytes(stats.st_size)), - ('Created: ', datetime.datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M')), - ('Last modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')), + ('Modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')), ] return params @@ -100,7 +99,12 @@ def get_metadata_table(self, name): def put_values_into_components(self, name): user_metadata = self.get_user_metadata(name) - params = self.get_metadata_table(name) + try: + params = self.get_metadata_table(name) + except Exception as e: + errors.display(e, f"reading metadata info for {name}") + params = [] + table = '' + "".join(f"" for name, value in params) + '' return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name) @@ -128,7 +132,9 @@ def create_editor(self): .click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview])\ .then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box]) - self.button_save.click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[]).then(fn=None, _js="closePopup") + self.button_save\ + .click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[])\ + .then(fn=None, _js="extraNetworksReloadAll") def create_ui(self): with gr.Box(visible=False, elem_id=self.id_part, elem_classes="edit-user-metadata") as box: @@ -142,7 +148,7 @@ def create_ui(self): def save_preview(self, index, gallery, name): if len(gallery) == 0: print("There is no image in gallery to save as a preview.") - return [self.get_card_html(name)] + [page.create_html(self.ui.tabname) for page in self.ui.stored_extra_pages] + return [self.get_card_html(name)] + self.regenerate_ui_pages() item = self.page.items.get(name, {}) @@ -156,7 +162,10 @@ def save_preview(self, index, gallery, name): images.save_image_with_geninfo(image, geninfo, item["local_preview"]) - return [self.get_card_html(name)] + [page.create_html(self.tabname) for page in self.ui.stored_extra_pages] + return [self.get_card_html(name)] + self.regenerate_ui_pages() + + def regenerate_ui_pages(self): + return [page.create_html(self.tabname) for page in self.ui.stored_extra_pages] def setup_ui(self, gallery): self.button_replace_preview.click( diff --git a/style.css b/style.css index 4431c1aaa67..af6344a8454 100644 --- a/style.css +++ b/style.css @@ -1004,7 +1004,7 @@ footer { } div.block.gradio-box.edit-user-metadata { - min-width: 56em; + width: 56em; background: var(--body-background-fill); padding: 2em !important; } @@ -1021,3 +1021,10 @@ div.block.gradio-box.edit-user-metadata { .edit-user-metadata .wrap.translucent{ background: var(--body-background-fill); } +.edit-user-metadata .gradio-highlightedtext span{ + word-break: break-word; +} + +.edit-user-metadata-buttons{ + margin-top: 1.5em; +} From efceed8c7f99a959bfe1a4d9210f27aac91f7705 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 01:09:19 +0300 Subject: [PATCH 0680/2418] fix styles for dark people --- style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/style.css b/style.css index af6344a8454..4e22cfd6035 100644 --- a/style.css +++ b/style.css @@ -1014,6 +1014,10 @@ div.block.gradio-box.edit-user-metadata { color: var(--body-text-color); } +.edit-user-metadata .file-metadata{ + color: var(--body-text-color); +} + .edit-user-metadata .file-metadata th{ text-align: left; } From d380f939b5ab6a28bed6d1de3cf283e194255963 Mon Sep 17 00:00:00 2001 From: Leon Feng <523684+leon0707@users.noreply.github.com> Date: Sat, 15 Jul 2023 23:31:59 -0400 Subject: [PATCH 0681/2418] Update shared.py --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index a0862055409..564799bca7c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -394,7 +394,7 @@ def list_samplers(): })) options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_model_checkpoint": OptionInfo("", "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list).info("choose VAE model: Automatic = use one with same filename as checkpoint; None = use VAE from checkpoint"), From 8c11b126e5bd5052154c095177390f249e8e9889 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 15 Jul 2023 23:43:49 -0400 Subject: [PATCH 0682/2418] 404 when thumb file not found --- modules/ui_extra_networks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 693cafb643c..a25653150ea 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -8,6 +8,7 @@ import gradio as gr import json import html +from fastapi.exceptions import HTTPException from modules.generation_parameters_copypaste import image_from_url_text @@ -26,6 +27,9 @@ def register_page(page): def fetch_file(filename: str = ""): from starlette.responses import FileResponse + if not os.path.isfile(filename): + raise HTTPException(status_code=404, detail="File not found") + if not any(Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs): raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.") From a1d6ada69ac686a628e79b61b8f86d01592a7209 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 08:38:23 +0300 Subject: [PATCH 0683/2418] allow refreshing single card after editing user metadata instead of all cards --- .../Lora/ui_edit_user_metadata.py | 4 +- .../Lora/ui_extra_networks_lora.py | 54 ++++++++++--------- html/extra-networks-card.html | 2 +- javascript/extraNetworks.js | 19 +++++-- modules/ui_extra_networks.py | 20 +++++++ modules/ui_extra_networks_checkpoints.py | 31 ++++++----- modules/ui_extra_networks_hypernets.py | 31 ++++++----- .../ui_extra_networks_textual_inversion.py | 30 ++++++----- modules/ui_extra_networks_user_metadata.py | 38 ++++++++----- 9 files changed, 142 insertions(+), 87 deletions(-) diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index c7dbd1c1a80..2aa65223e6a 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -182,6 +182,4 @@ def select_tag(activation_text, evt: gr.SelectData): self.edit_notes, ] - self.button_save\ - .click(fn=self.save_lora_user_metadata, inputs=[self.edit_name_input, *edited_components], outputs=[]) \ - .then(fn=None, _js="extraNetworksReloadAll") + self.setup_save_handler(self.button_save, self.save_lora_user_metadata, edited_components) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 95296275cf8..80e741dcae4 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -13,31 +13,37 @@ def __init__(self): def refresh(self): lora.list_available_loras() - def list_items(self): - for index, (name, lora_on_disk) in enumerate(lora.available_loras.items()): - path, ext = os.path.splitext(lora_on_disk.filename) - - alias = lora_on_disk.get_alias() - - item = { - "name": name, - "filename": lora_on_disk.filename, - "preview": self.find_preview(path), - "description": self.find_description(path), - "search_term": self.search_terms_from_path(lora_on_disk.filename), - "local_preview": f"{path}.{shared.opts.samples_format}", - "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, - "sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)}, - } - - self.read_user_metadata(item) - activation_text = item["user_metadata"].get("activation text") - preferred_weight = item["user_metadata"].get("preferred weight", 0.0) - item["prompt"] = json.dumps(f"") - - if activation_text: - item["prompt"] += " + " + json.dumps(" " + activation_text) + def create_item(self, name, index=None): + lora_on_disk = lora.available_loras.get(name) + + path, ext = os.path.splitext(lora_on_disk.filename) + + alias = lora_on_disk.get_alias() + + item = { + "name": name, + "filename": lora_on_disk.filename, + "preview": self.find_preview(path), + "description": self.find_description(path), + "search_term": self.search_terms_from_path(lora_on_disk.filename), + "local_preview": f"{path}.{shared.opts.samples_format}", + "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, + "sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)}, + } + self.read_user_metadata(item) + activation_text = item["user_metadata"].get("activation text") + preferred_weight = item["user_metadata"].get("preferred weight", 0.0) + item["prompt"] = json.dumps(f"") + + if activation_text: + item["prompt"] += " + " + json.dumps(" " + activation_text) + + return item + + def list_items(self): + for index, name in enumerate(lora.available_loras): + item = self.create_item(name, index) yield item def allowed_directories_for_previews(self): diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index fb787ffe93a..eb8b1a67262 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,4 +1,4 @@ -
    +
    {background_image}
    {edit_button} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 8b67bf2bd2f..e453094a1e7 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -296,9 +296,18 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { event.stopPropagation(); } -function extraNetworksReloadAll() { - closePopup(); - - gradioApp().getElementById('txt2img_extra_refresh').click(); - gradioApp().getElementById('img2img_extra_refresh').click(); +function extraNetworksRefreshSingleCard(page, tabname, name) { + requestGet("./sd_extra_networks/get-single-card", {page: page, tabname: tabname, name: name}, function(data) { + if (data && data.html) { + var card = gradioApp().querySelector('.card[data-name=' + JSON.stringify(name) + ']'); // likely using the wrong stringify function + + var newDiv = document.createElement('DIV'); + newDiv.innerHTML = data.html; + var newCard = newDiv.firstElementChild; + + newCard.style = ''; + card.parentElement.insertBefore(newCard, card); + card.parentElement.removeChild(card); + } + }); } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index eaae6217605..42c4d0ac95a 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -51,9 +51,26 @@ def get_metadata(page: str = "", item: str = ""): return JSONResponse({"metadata": metadata}) +def get_single_card(page: str = "", tabname: str = "", name: str = ""): + from starlette.responses import JSONResponse + + page = next(iter([x for x in extra_pages if x.name == page]), None) + + try: + item = page.create_item(name) + except Exception as e: + errors.display(e, "creating item for extra network") + item = page.items.get(name) + + item_html = page.create_html_for_item(item, tabname) + + return JSONResponse({"html": item_html}) + + def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"]) app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"]) + app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"]) class ExtraNetworksPage: @@ -168,6 +185,9 @@ def create_html(self, tabname): return res + def create_item(self, name, index=None): + raise NotImplementedError() + def list_items(self): raise NotImplementedError() diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index bb5071e6a4e..ef8cdf350b9 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -12,21 +12,24 @@ def __init__(self): def refresh(self): shared.refresh_checkpoints() + def create_item(self, name, index=None): + checkpoint: sd_models.CheckpointInfo = sd_models.checkpoints_list.get(name) + path, ext = os.path.splitext(checkpoint.filename) + return { + "name": checkpoint.name_for_extra, + "filename": checkpoint.filename, + "preview": self.find_preview(path), + "description": self.find_description(path), + "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), + "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', + "local_preview": f"{path}.{shared.opts.samples_format}", + "sort_keys": {'default': index, **self.get_sort_keys(checkpoint.filename)}, + + } + def list_items(self): - checkpoint: sd_models.CheckpointInfo - for index, (name, checkpoint) in enumerate(sd_models.checkpoints_list.items()): - path, ext = os.path.splitext(checkpoint.filename) - yield { - "name": checkpoint.name_for_extra, - "filename": checkpoint.filename, - "preview": self.find_preview(path), - "description": self.find_description(path), - "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), - "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', - "local_preview": f"{path}.{shared.opts.samples_format}", - "sort_keys": {'default': index, **self.get_sort_keys(checkpoint.filename)}, - - } + for index, name in enumerate(sd_models.checkpoints_list): + yield self.create_item(name, index) def allowed_directories_for_previews(self): return [v for v in [shared.cmd_opts.ckpt_dir, sd_models.model_path] if v is not None] diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index ea0b7a4403f..8dae23c6a1e 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -11,21 +11,24 @@ def __init__(self): def refresh(self): shared.reload_hypernetworks() + def create_item(self, name, index=None): + full_path = shared.hypernetworks[name] + path, ext = os.path.splitext(full_path) + + return { + "name": name, + "filename": full_path, + "preview": self.find_preview(path), + "description": self.find_description(path), + "search_term": self.search_terms_from_path(path), + "prompt": json.dumps(f""), + "local_preview": f"{path}.preview.{shared.opts.samples_format}", + "sort_keys": {'default': index, **self.get_sort_keys(path + ext)}, + } + def list_items(self): - for index, (name, full_path) in enumerate(shared.hypernetworks.items()): - path, ext = os.path.splitext(full_path) - - yield { - "name": name, - "filename": full_path, - "preview": self.find_preview(path), - "description": self.find_description(path), - "search_term": self.search_terms_from_path(path), - "prompt": json.dumps(f""), - "local_preview": f"{path}.preview.{shared.opts.samples_format}", - "sort_keys": {'default': index, **self.get_sort_keys(path + ext)}, - - } + for index, name in enumerate(shared.hypernetworks): + yield self.create_item(name, index) def allowed_directories_for_previews(self): return [shared.cmd_opts.hypernetwork_dir] diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 58a61c55b3d..159f2d6471e 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -12,20 +12,24 @@ def __init__(self): def refresh(self): sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) + def create_item(self, name, index=None): + embedding = sd_hijack.model_hijack.embedding_db.word_embeddings.get(name) + + path, ext = os.path.splitext(embedding.filename) + return { + "name": name, + "filename": embedding.filename, + "preview": self.find_preview(path), + "description": self.find_description(path), + "search_term": self.search_terms_from_path(embedding.filename), + "prompt": json.dumps(embedding.name), + "local_preview": f"{path}.preview.{shared.opts.samples_format}", + "sort_keys": {'default': index, **self.get_sort_keys(embedding.filename)}, + } + def list_items(self): - for index, embedding in enumerate(sd_hijack.model_hijack.embedding_db.word_embeddings.values()): - path, ext = os.path.splitext(embedding.filename) - yield { - "name": embedding.name, - "filename": embedding.filename, - "preview": self.find_preview(path), - "description": self.find_description(path), - "search_term": self.search_terms_from_path(embedding.filename), - "prompt": json.dumps(embedding.name), - "local_preview": f"{path}.preview.{shared.opts.samples_format}", - "sort_keys": {'default': index, **self.get_sort_keys(embedding.filename)}, - - } + for index, name in enumerate(sd_hijack.model_hijack.embedding_db.word_embeddings): + yield self.create_item(name, index) def allowed_directories_for_previews(self): return list(sd_hijack.model_hijack.embedding_db.embedding_dirs) diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 0dbd7419aef..01ff4e4b470 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -23,8 +23,10 @@ def __init__(self, ui, tabname, page): self.edit_name = None self.edit_description = None + self.edit_notes = None self.html_filedata = None self.html_preview = None + self.html_status = None self.button_cancel = None self.button_replace_preview = None @@ -57,6 +59,8 @@ def create_default_buttons(self): self.button_replace_preview = gr.Button('Replace preview', variant='primary') self.button_save = gr.Button('Save', variant='primary') + self.html_status = gr.HTML(elem_classes="edit-user-metadata-status") + self.button_cancel.click(fn=None, _js="closePopup") def get_card_html(self, name): @@ -107,7 +111,7 @@ def put_values_into_components(self, name): table = '' + "".join(f"" for name, value in params) + '' - return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name) + return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name), user_metadata.get('notes', ''), def write_user_metadata(self, name, metadata): item = self.page.items.get(name, {}) @@ -117,24 +121,30 @@ def write_user_metadata(self, name, metadata): with open(basename + '.json', "w", encoding="utf8") as file: json.dump(metadata, file) - def save_user_metadata(self, name, desc): + def save_user_metadata(self, name, desc, notes): user_metadata = self.get_user_metadata(name) user_metadata["description"] = desc + user_metadata["notes"] = notes self.write_user_metadata(name, user_metadata) + def setup_save_handler(self, button, func, components): + button\ + .click(fn=func, inputs=[self.edit_name_input, *components], outputs=[])\ + .then(fn=None, _js="function(name){closePopup(); extraNetworksRefreshSingleCard(" + json.dumps(self.page.name) + "," + json.dumps(self.tabname) + ", name);}", inputs=[self.edit_name_input], outputs=[]) + def create_editor(self): self.create_default_editor_elems() + self.edit_notes = gr.TextArea(label='Notes', lines=4) + self.create_default_buttons() self.button_edit\ - .click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview])\ + .click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview, self.edit_notes])\ .then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box]) - self.button_save\ - .click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[])\ - .then(fn=None, _js="extraNetworksReloadAll") + self.setup_save_handler(self.button_save, self.save_user_metadata, [self.edit_description, self.edit_notes]) def create_ui(self): with gr.Box(visible=False, elem_id=self.id_part, elem_classes="edit-user-metadata") as box: @@ -147,8 +157,7 @@ def create_ui(self): def save_preview(self, index, gallery, name): if len(gallery) == 0: - print("There is no image in gallery to save as a preview.") - return [self.get_card_html(name)] + self.regenerate_ui_pages() + return self.get_card_html(name), "There is no image in gallery to save as a preview." item = self.page.items.get(name, {}) @@ -162,17 +171,20 @@ def save_preview(self, index, gallery, name): images.save_image_with_geninfo(image, geninfo, item["local_preview"]) - return [self.get_card_html(name)] + self.regenerate_ui_pages() - - def regenerate_ui_pages(self): - return [page.create_html(self.tabname) for page in self.ui.stored_extra_pages] + return self.get_card_html(name), '' def setup_ui(self, gallery): self.button_replace_preview.click( fn=self.save_preview, _js="function(x, y, z){return [selected_gallery_index(), y, z]}", inputs=[self.edit_name_input, gallery, self.edit_name_input], - outputs=[self.html_preview, *self.ui.pages] + outputs=[self.html_preview, self.html_status] + ).then( + fn=None, + _js="function(name){extraNetworksRefreshSingleCard(" + json.dumps(self.page.name) + "," + json.dumps(self.tabname) + ", name);}", + inputs=[self.edit_name_input], + outputs=[] ) + From 47d9dd0240872dc70fd26bc1bf309f49fe17c104 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 09:25:32 +0300 Subject: [PATCH 0684/2418] speedup extra networks listing --- extensions-builtin/Lora/lora.py | 12 ++++++--- .../Lora/ui_edit_user_metadata.py | 9 +++---- .../Lora/ui_extra_networks_lora.py | 9 ++++--- modules/cache.py | 27 ++++++++++--------- modules/ui_extra_networks.py | 20 +++++++++----- modules/ui_extra_networks_checkpoints.py | 4 +-- modules/ui_extra_networks_hypernets.py | 4 +-- .../ui_extra_networks_textual_inversion.py | 4 +-- 8 files changed, 51 insertions(+), 38 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index cd46e6c7eee..c87109221b9 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -3,7 +3,7 @@ import torch from typing import Union -from modules import shared, devices, sd_models, errors, scripts, sd_hijack, hashes +from modules import shared, devices, sd_models, errors, scripts, sd_hijack, hashes, cache metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20} @@ -78,9 +78,16 @@ def __init__(self, name, filename): self.metadata = {} self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors" + def read_metadata(): + metadata = sd_models.read_metadata_from_safetensors(filename) + metadata.pop('ssmd_cover_images', None) # those are cover images, and they are too big to display in UI as text + + return metadata + if self.is_safetensors: try: - self.metadata = sd_models.read_metadata_from_safetensors(filename) + #self.metadata = sd_models.read_metadata_from_safetensors(filename) + self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) except Exception as e: errors.display(e, f"reading lora {filename}") @@ -91,7 +98,6 @@ def __init__(self, name, filename): self.metadata = m - self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text self.alias = self.metadata.get('ss_output_name', self.name) self.hash = None diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 2aa65223e6a..6db63b0955c 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -1,5 +1,4 @@ import html -import json import random import gradio as gr @@ -64,7 +63,7 @@ def save_lora_user_metadata(self, name, desc, activation_text, preferred_weight, def get_metadata_table(self, name): table = super().get_metadata_table(name) item = self.page.items.get(name, {}) - metadata = json.loads(item.get("metadata") or '{}') + metadata = item.get("metadata") or {} keys = [ ('ss_sd_model_name', "Model:"), @@ -91,7 +90,7 @@ def put_values_into_components(self, name): values = super().put_values_into_components(name) item = self.page.items.get(name, {}) - metadata = json.loads(item.get("metadata") or '{}') + metadata = item.get("metadata") or {} tags = build_tags(metadata) gradio_tags = [(tag, str(count)) for tag, count in tags[0:24]] @@ -108,7 +107,7 @@ def put_values_into_components(self, name): def generate_random_prompt(self, name): item = self.page.items.get(name, {}) - metadata = json.loads(item.get("metadata") or '{}') + metadata = item.get("metadata") or {} tags = build_tags(metadata) return self.generate_random_prompt_from_tags(tags) @@ -142,7 +141,7 @@ def create_editor(self): self.edit_notes = gr.TextArea(label='Notes', lines=4) - generate_random_prompt.click(fn=self.generate_random_prompt, inputs=[self.edit_name_input], outputs=[random_prompt]) + generate_random_prompt.click(fn=self.generate_random_prompt, inputs=[self.edit_name_input], outputs=[random_prompt], show_progress=False) def select_tag(activation_text, evt: gr.SelectData): tag = evt.value[0] diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 80e741dcae4..b2bc1810270 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -1,8 +1,8 @@ -import json import os import lora from modules import shared, ui_extra_networks +from modules.ui_extra_networks import quote_js from ui_edit_user_metadata import LoraUserMetadataEditor @@ -20,6 +20,7 @@ def create_item(self, name, index=None): alias = lora_on_disk.get_alias() + # in 1.5 filename changes to be full filename instead of path without extension, and metadata is dict instead of json string item = { "name": name, "filename": lora_on_disk.filename, @@ -27,17 +28,17 @@ def create_item(self, name, index=None): "description": self.find_description(path), "search_term": self.search_terms_from_path(lora_on_disk.filename), "local_preview": f"{path}.{shared.opts.samples_format}", - "metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None, + "metadata": lora_on_disk.metadata, "sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)}, } self.read_user_metadata(item) activation_text = item["user_metadata"].get("activation text") preferred_weight = item["user_metadata"].get("preferred weight", 0.0) - item["prompt"] = json.dumps(f"") + item["prompt"] = quote_js(f"") if activation_text: - item["prompt"] += " + " + json.dumps(" " + activation_text) + item["prompt"] += " + " + quote_js(" " + activation_text) return item diff --git a/modules/cache.py b/modules/cache.py index 4c2db604df5..07180602c86 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -1,12 +1,12 @@ import json import os.path - -import filelock +import threading from modules.paths import data_path, script_path cache_filename = os.path.join(data_path, "cache.json") cache_data = None +cache_lock = threading.Lock() def dump_cache(): @@ -14,7 +14,7 @@ def dump_cache(): Saves all cache data to a file. """ - with filelock.FileLock(f"{cache_filename}.lock"): + with cache_lock: with open(cache_filename, "w", encoding="utf8") as file: json.dump(cache_data, file, indent=4) @@ -33,17 +33,18 @@ def cache(subsection): global cache_data if cache_data is None: - with filelock.FileLock(f"{cache_filename}.lock"): - if not os.path.isfile(cache_filename): - cache_data = {} - else: - try: - with open(cache_filename, "r", encoding="utf8") as file: - cache_data = json.load(file) - except Exception: - os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) - print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') + with cache_lock: + if cache_data is None: + if not os.path.isfile(cache_filename): cache_data = {} + else: + try: + with open(cache_filename, "r", encoding="utf8") as file: + cache_data = json.load(file) + except Exception: + os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) + print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') + cache_data = {} s = cache_data.get(subsection, {}) cache_data[subsection] = s diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 42c4d0ac95a..f9d1fa31da9 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -73,6 +73,12 @@ def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"]) +def quote_js(s): + s = s.replace('\\', '\\\\') + s = s.replace('"', '\\"') + return f'"{s}"' + + class ExtraNetworksPage: def __init__(self, title): self.title = title @@ -203,7 +209,7 @@ def create_html_for_item(self, item, tabname): onclick = item.get("onclick", None) if onclick is None: - onclick = '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' + onclick = '"' + html.escape(f"""return cardClicked({quote_js(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else '' width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else '' @@ -211,9 +217,9 @@ def create_html_for_item(self, item, tabname): metadata_button = "" metadata = item.get("metadata") if metadata: - metadata_button = f"" + metadata_button = f"" - edit_button = f"
    " + edit_button = f"
    " local_path = "" filename = item.get("filename", "") @@ -239,12 +245,12 @@ def create_html_for_item(self, item, tabname): "background_image": background_image, "style": f"'display: none; {height}{width}'", "prompt": item.get("prompt", None), - "tabname": json.dumps(tabname), - "local_preview": json.dumps(item["local_preview"]), + "tabname": quote_js(tabname), + "local_preview": quote_js(item["local_preview"]), "name": item["name"], "description": (item.get("description") or ""), "card_clicked": onclick, - "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"', + "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), "metadata_button": metadata_button, "edit_button": edit_button, @@ -359,7 +365,7 @@ def create_ui(container, button, tabname): page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) - page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + json.dumps(tabname) + '); return []}', inputs=[], outputs=[]) + page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + quote_js(tabname) + '); return []}', inputs=[], outputs=[]) editor = page.create_user_metadata_editor(ui, tabname) editor.create_ui() diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index ef8cdf350b9..e73b5b1f3ae 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -1,8 +1,8 @@ import html -import json import os from modules import shared, ui_extra_networks, sd_models +from modules.ui_extra_networks import quote_js class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): @@ -21,7 +21,7 @@ def create_item(self, name, index=None): "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), - "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', + "onclick": '"' + html.escape(f"""return selectCheckpoint({quote_js(name)})""") + '"', "local_preview": f"{path}.{shared.opts.samples_format}", "sort_keys": {'default': index, **self.get_sort_keys(checkpoint.filename)}, diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 8dae23c6a1e..e53ccb42892 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -1,7 +1,7 @@ -import json import os from modules import shared, ui_extra_networks +from modules.ui_extra_networks import quote_js class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): @@ -21,7 +21,7 @@ def create_item(self, name, index=None): "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(path), - "prompt": json.dumps(f""), + "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 159f2d6471e..d1794e501c1 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -1,7 +1,7 @@ -import json import os from modules import ui_extra_networks, sd_hijack, shared +from modules.ui_extra_networks import quote_js class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): @@ -22,7 +22,7 @@ def create_item(self, name, index=None): "preview": self.find_preview(path), "description": self.find_description(path), "search_term": self.search_terms_from_path(embedding.filename), - "prompt": json.dumps(embedding.name), + "prompt": quote_js(embedding.name), "local_preview": f"{path}.preview.{shared.opts.samples_format}", "sort_keys": {'default': index, **self.get_sort_keys(embedding.filename)}, } From ccd97886da1f659472cdca3de8731f59a70bbc28 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 09:49:22 +0300 Subject: [PATCH 0685/2418] fix bogus metadata for extra networks appearing out of cache fix description editing for checkpoint not immediately appearing on cards --- modules/cache.py | 10 +++++----- modules/ui_extra_networks.py | 3 ++- modules/ui_extra_networks_checkpoints.py | 3 +-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/cache.py b/modules/cache.py index 07180602c86..28d42a8cfe5 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -80,18 +80,18 @@ def cached_data_for_file(subsection, title, filename, func): entry = existing_cache.get(title) if entry: - cached_mtime = existing_cache[title].get("mtime", 0) + cached_mtime = entry.get("mtime", 0) if ondisk_mtime > cached_mtime: entry = None if not entry: - entry = func() - if entry is None: + value = func() + if value is None: return None - entry['mtime'] = ondisk_mtime + entry = {'mtime': ondisk_mtime, 'value': value} existing_cache[title] = entry dump_cache() - return entry + return entry['value'] diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 760fba437ec..a4927c11277 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -52,7 +52,7 @@ def get_metadata(page: str = "", item: str = ""): if metadata is None: return JSONResponse({}) - return JSONResponse({"metadata": metadata}) + return JSONResponse({"metadata": json.dumps(metadata, indent=4, ensure_ascii=False)}) def get_single_card(page: str = "", tabname: str = "", name: str = ""): @@ -66,6 +66,7 @@ def get_single_card(page: str = "", tabname: str = "", name: str = ""): errors.display(e, "creating item for extra network") item = page.items.get(name) + page.read_user_metadata(item) item_html = page.create_html_for_item(item, tabname) return JSONResponse({"html": item_html}) diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index e73b5b1f3ae..76780cfd0af 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -13,7 +13,7 @@ def refresh(self): shared.refresh_checkpoints() def create_item(self, name, index=None): - checkpoint: sd_models.CheckpointInfo = sd_models.checkpoints_list.get(name) + checkpoint: sd_models.CheckpointInfo = sd_models.checkpoint_aliases.get(name) path, ext = os.path.splitext(checkpoint.filename) return { "name": checkpoint.name_for_extra, @@ -24,7 +24,6 @@ def create_item(self, name, index=None): "onclick": '"' + html.escape(f"""return selectCheckpoint({quote_js(name)})""") + '"', "local_preview": f"{path}.{shared.opts.samples_format}", "sort_keys": {'default': index, **self.get_sort_keys(checkpoint.filename)}, - } def list_items(self): From 7b052eb70eb2a35ce4f776b1e2ab1389802a41b5 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 10:07:02 +0300 Subject: [PATCH 0686/2418] add resolution calculation from buckets for lora user metadata page --- extensions-builtin/Lora/lora.py | 1 - .../Lora/ui_edit_user_metadata.py | 28 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index c87109221b9..467ad65f200 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -86,7 +86,6 @@ def read_metadata(): if self.is_safetensors: try: - #self.metadata = sd_models.read_metadata_from_safetensors(filename) self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) except Exception as e: errors.display(e, f"reading lora {filename}") diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 6db63b0955c..354a1d686c0 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -65,17 +65,33 @@ def get_metadata_table(self, name): item = self.page.items.get(name, {}) metadata = item.get("metadata") or {} - keys = [ - ('ss_sd_model_name', "Model:"), - ('ss_resolution', "Resolution:"), - ('ss_clip_skip', "Clip skip:"), - ] + keys = { + 'ss_sd_model_name': "Model:", + 'ss_clip_skip': "Clip skip:", + } - for key, label in keys: + for key, label in keys.items(): value = metadata.get(key, None) if value is not None and str(value) != "None": table.append((label, html.escape(value))) + ss_bucket_info = metadata.get("ss_bucket_info") + if ss_bucket_info and "buckets" in ss_bucket_info: + resolutions = {} + for _, bucket in ss_bucket_info["buckets"].items(): + resolution = bucket["resolution"] + resolution = f'{resolution[1]}x{resolution[0]}' + + resolutions[resolution] = resolutions.get(resolution, 0) + int(bucket["count"]) + + resolutions_list = sorted(resolutions.keys(), key=resolutions.get, reverse=True) + resolutions_text = html.escape(", ".join(resolutions_list[0:4])) + if len(resolutions) > 4: + resolutions_text += ", ..." + resolutions_text = f"{resolutions_text}" + + table.append(('Resolutions:' if len(resolutions_list) > 1 else 'Resolution:', resolutions_text)) + image_count = 0 for _, params in metadata.get("ss_dataset_dirs", {}).items(): image_count += int(params.get("img_count", 0)) From 690d56f3c10e5359e15eeba9c68e56b2eb193ac3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 10:25:34 +0300 Subject: [PATCH 0687/2418] nuke thumbs extra networks view mode (use settings tab to change width/height/scale to get thumbs) --- html/image-update.svg | 7 --- javascript/hints.js | 1 - modules/shared.py | 5 +- modules/ui_extra_networks.py | 9 ++-- style.css | 94 ++++++------------------------------ 5 files changed, 22 insertions(+), 94 deletions(-) delete mode 100644 html/image-update.svg diff --git a/html/image-update.svg b/html/image-update.svg deleted file mode 100644 index 3abf12df0f7..00000000000 --- a/html/image-update.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/javascript/hints.js b/javascript/hints.js index 41201b2f5bd..4167cb28b7c 100644 --- a/javascript/hints.js +++ b/javascript/hints.js @@ -108,7 +108,6 @@ var titles = { "Upscale by": "Adjusts the size of the image by multiplying the original width and height by the selected value. Ignored if either Resize width to or Resize height to are non-zero.", "Resize width to": "Resizes image to this width. If 0, width is inferred from either of two nearby sliders.", "Resize height to": "Resizes image to this height. If 0, height is inferred from either of two nearby sliders.", - "Multiplier for extra networks": "When adding extra network such as Hypernetwork or Lora to prompt, use this multiplier for it.", "Discard weights with matching name": "Regular expression; if weights's name matches it, the weights is not written to the resulting checkpoint. Use ^model_ema to discard EMA weights.", "Extra networks tab order": "Comma-separated list of tab names; tabs listed here will appear in the extra networks UI first and in order listed.", "Negative Guidance minimum sigma": "Skip negative prompt for steps where image is already mostly denoised; the higher this value, the more skips there will be; provides increased performance in exchange for minor quality reduction." diff --git a/modules/shared.py b/modules/shared.py index 427dcc505d0..f6604ef913b 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -466,10 +466,11 @@ def list_samplers(): options_templates.update(options_section(('extra_networks', "Extra Networks"), { "extra_networks_show_hidden_directories": OptionInfo(True, "Show hidden directories").info("directory is hidden if its name starts with \".\"."), "extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more'), - "extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}), - "extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}), "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"), "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), + "extra_networks_card_text_scale": OptionInfo(1.0, "Card text scale", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}).info("1 = original size"), + "extra_networks_card_show_desc": OptionInfo(True, "Show description on card"), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_restart(), "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index a4927c11277..d9deccb2b05 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -132,7 +132,6 @@ def search_terms_from_path(self, filename, possible_directories=None): return "" def create_html(self, tabname): - view = shared.opts.extra_networks_default_view items_html = '' self.metadata = {} @@ -186,10 +185,10 @@ def create_html(self, tabname): self_name_id = self.name.replace(" ", "_") res = f""" -
    +
    {subdirs_html}
    -
    +
    {items_html}
    """ @@ -248,12 +247,12 @@ def create_html_for_item(self, item, tabname): args = { "background_image": background_image, - "style": f"'display: none; {height}{width}'", + "style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", "prompt": item.get("prompt", None), "tabname": quote_js(tabname), "local_preview": quote_js(item["local_preview"]), "name": item["name"], - "description": (item.get("description") or ""), + "description": (item.get("description") or "" if shared.opts.extra_networks_card_show_desc else ""), "card_clicked": onclick, "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', "search_term": item.get("search_term", ""), diff --git a/style.css b/style.css index 4e22cfd6035..f7a8cb1735a 100644 --- a/style.css +++ b/style.css @@ -804,127 +804,67 @@ footer { width: auto; } -.extra-network-cards .nocards, .extra-network-thumbs .nocards{ +.extra-network-cards .nocards{ margin: 1.25em 0.5em 0.5em 0.5em; } -.extra-network-cards .nocards h1, .extra-network-thumbs .nocards h1{ +.extra-network-cards .nocards h1{ font-size: 1.5em; margin-bottom: 1em; } -.extra-network-cards .nocards li, .extra-network-thumbs .nocards li{ +.extra-network-cards .nocards li{ margin-left: 0.5em; } -.extra-network-cards .card .button-row, .extra-network-thumbs .card .button-row{ +.extra-network-cards .card .button-row{ display: none; position: absolute; color: white; right: 0; } -.extra-network-cards .card:hover .button-row, .extra-network-thumbs .card:hover .button-row{ +.extra-network-cards .card:hover .button-row{ display: flex; } -.extra-network-cards .card .card-button, .extra-network-thumbs .card .card-button{ +.extra-network-cards .card .card-button{ color: white; } -.extra-network-cards .card .metadata-button:before, .extra-network-thumbs .card .metadata-button:before{ +.extra-network-cards .card .metadata-button:before{ content: "🛈"; } -.extra-network-cards .card .edit-button:before, .extra-network-thumbs .card .edit-button:before{ +.extra-network-cards .card .edit-button:before{ content: "🛠"; } .extra-network-cards .card .card-button { text-shadow: 2px 2px 3px black; padding: 0.25em; - font-size: 22pt; + font-size: 200%; width: 1.5em; } -.extra-network-thumbs .card .card-button { - text-shadow: 1px 1px 2px black; - padding: 0; - font-size: 16pt; - width: 1em; - top: -0.25em; -} -.extra-network-cards .card .card-button:hover, .extra-network-thumbs .card .card-button:hover{ +.extra-network-cards .card .card-button:hover{ color: red; } -.extra-network-thumbs { - display: flex; - flex-flow: row wrap; - gap: 10px; -} - -.extra-network-thumbs .card { - height: 6em; - width: 6em; - cursor: pointer; - background-image: url('./file=html/card-no-preview.png'); - background-size: cover; - background-position: center center; - position: relative; -} - -.extra-network-thumbs .card .preview, .standalone-card-preview.card .preview{ +.standalone-card-preview.card .preview{ position: absolute; object-fit: cover; width: 100%; height:100%; } -.extra-network-thumbs .card:hover .additional a { - display: inline-block; -} - -.extra-network-thumbs .actions .additional a { - background-image: url('./file=html/image-update.svg'); - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - position: absolute; - top: 0; - left: 0; - width: 24px; - height: 24px; - display: none; - font-size: 0; - text-align: -9999; -} - -.extra-network-thumbs .actions .name { - position: absolute; - bottom: 0; - font-size: 10px; - padding: 3px; - width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - background: rgba(0,0,0,.5); - color: white; -} - -.extra-network-thumbs .card:hover .actions .name { - white-space: normal; - word-break: break-all; -} - .extra-network-cards .card, .standalone-card-preview.card{ display: inline-block; - margin: 0.5em; - width: 16em; - height: 24em; + margin: 0.5rem; + width: 16rem; + height: 24rem; box-shadow: 0 0 5px rgba(128, 128, 128, 0.5); - border-radius: 0.2em; + border-radius: 0.2rem; position: relative; background-size: auto 100%; @@ -958,10 +898,6 @@ footer { color: white; } -.extra-network-cards .card .actions:hover{ - box-shadow: 0 0 0.75em 0.75em rgba(0,0,0,0.5) !important; -} - .extra-network-cards .card .actions .name{ font-size: 1.7em; font-weight: bold; From 9d3dd64fe9e95873347710ca1df1f1e88d1908e1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 10:44:04 +0300 Subject: [PATCH 0688/2418] minor restyling for extra networks --- modules/ui_extra_networks.py | 3 ++- style.css | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index d9deccb2b05..6c73998f903 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -11,6 +11,7 @@ from fastapi.exceptions import HTTPException from modules.generation_parameters_copypaste import image_from_url_text +from modules.ui_components import ToolButton extra_pages = [] allowed_dirs = set() @@ -377,7 +378,7 @@ def create_ui(container, button, tabname): gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", multiselect=False, visible=False, show_label=False, interactive=True) - gr.Button(up_down_symbol, elem_id=tabname+"_extra_sortorder") + ToolButton(up_down_symbol, elem_id=tabname+"_extra_sortorder") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh") ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) diff --git a/style.css b/style.css index f7a8cb1735a..8a66c3d2130 100644 --- a/style.css +++ b/style.css @@ -783,8 +783,7 @@ footer { margin: 0 0.15em; } .extra-networks .tab-nav .search, -.extra-networks .tab-nav .sort, -.extra-networks .tab-nav .sortorder{ +.extra-networks .tab-nav .sort{ display: inline-block; margin: 0.3em; align-self: center; From 5ef7590324891ec7263c767d178a51827a6f9b33 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 11:38:59 +0300 Subject: [PATCH 0689/2418] always show extra networks tabs in the UI --- javascript/extraNetworks.js | 8 ++--- modules/ui.py | 35 ++++++++++++---------- modules/ui_extra_networks.py | 58 ++++++++++++++---------------------- style.css | 8 ++--- 4 files changed, 50 insertions(+), 59 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index e453094a1e7..1835717b55c 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -2,16 +2,14 @@ function setupExtraNetworksForTab(tabname) { gradioApp().querySelector('#' + tabname + '_extra_tabs').classList.add('extra-networks'); var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div'); - var search = gradioApp().querySelector('#' + tabname + '_extra_search textarea'); + var searchDiv = gradioApp().getElementById(tabname + '_extra_search'); + var search = searchDiv.querySelector('textarea'); var sort = gradioApp().getElementById(tabname + '_extra_sort'); var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder'); var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); - search.classList.add('search'); - sort.classList.add('sort'); - sortOrder.classList.add('sortorder'); sort.dataset.sortkey = 'sortDefault'; - tabs.appendChild(search); + tabs.appendChild(searchDiv); tabs.appendChild(sort); tabs.appendChild(sortOrder); tabs.appendChild(refresh); diff --git a/modules/ui.py b/modules/ui.py index 07ecee7b680..085561c1a7b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -310,7 +310,6 @@ def create_toprow(is_img2img): with gr.Row(elem_id=f"{id_part}_tools"): paste = ToolButton(value=paste_symbol, elem_id="paste") clear_prompt_button = ToolButton(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt") - extra_networks_button = ToolButton(value=extra_networks_symbol, elem_id=f"{id_part}_extra_networks") prompt_style_apply = ToolButton(value=apply_style_symbol, elem_id=f"{id_part}_style_apply") save_style = ToolButton(value=save_style_symbol, elem_id=f"{id_part}_style_create") restore_progress_button = ToolButton(value=restore_progress_symbol, elem_id=f"{id_part}_restore_progress", visible=False) @@ -331,7 +330,7 @@ def create_toprow(is_img2img): prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in shared.prompt_styles.styles.items()], value=[], multiselect=True) create_refresh_button(prompt_styles, shared.prompt_styles.reload, lambda: {"choices": [k for k, v in shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles") - return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button + return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, None, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button def setup_progressbar(*args, **kwargs): @@ -419,16 +418,15 @@ def create_ui(): modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False) with gr.Blocks(analytics_enabled=False) as txt2img_interface: - txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=False) + txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, _, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=False) dummy_component = gr.Label(visible=False) txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) - with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks: - from modules import ui_extra_networks - extra_networks_ui = ui_extra_networks.create_ui(extra_networks, extra_networks_button, 'txt2img') + extra_tabs = gr.Tabs(elem_id="txt2img_extra_tabs") + extra_tabs.__enter__() - with gr.Row().style(equal_height=False): + with gr.Tab("Generation", id="txt2img_generation") as txt2img_generation_tab, gr.Row().style(equal_height=False): with gr.Column(variant='compact', elem_id="txt2img_settings"): modules.scripts.scripts_txt2img.prepare_ui() @@ -656,21 +654,24 @@ def create_ui(): token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[token_counter]) negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter]) - ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) + from modules import ui_extra_networks + extra_networks_ui = ui_extra_networks.create_ui(txt2img_interface, [txt2img_generation_tab], 'txt2img') + ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) + + extra_tabs.__exit__() modules.scripts.scripts_current = modules.scripts.scripts_img2img modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) with gr.Blocks(analytics_enabled=False) as img2img_interface: - img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=True) + img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste, _, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=True) img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False) - with FormRow(variant='compact', elem_id="img2img_extra_networks", visible=False) as extra_networks: - from modules import ui_extra_networks - extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks, extra_networks_button, 'img2img') + extra_tabs = gr.Tabs(elem_id="img2img_extra_tabs") + extra_tabs.__enter__() - with FormRow().style(equal_height=False): + with gr.Tab("Generation", id="img2img_generation") as img2img_generation_tab, FormRow().style(equal_height=False): with gr.Column(variant='compact', elem_id="img2img_settings"): copy_image_buttons = [] copy_image_destinations = {} @@ -1026,8 +1027,6 @@ def select_img2img_tab(tab): token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter]) negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[negative_token_counter]) - ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) - img2img_paste_fields = [ (img2img_prompt, "Prompt"), (img2img_negative_prompt, "Negative prompt"), @@ -1055,6 +1054,12 @@ def select_img2img_tab(tab): paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, )) + from modules import ui_extra_networks + extra_networks_ui_img2img = ui_extra_networks.create_ui(img2img_interface, [img2img_generation_tab], 'img2img') + ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) + + extra_tabs.__exit__() + modules.scripts.scripts_current = None with gr.Blocks(analytics_enabled=False) as extras_interface: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 6c73998f903..0eb02873741 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -355,7 +355,7 @@ def tab_name_score(name): return sorted(pages, key=lambda x: tab_scores[x.name]) -def create_ui(container, button, tabname): +def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ui = ExtraNetworksUi() ui.pages = [] ui.pages_contents = [] @@ -363,48 +363,35 @@ def create_ui(container, button, tabname): ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy()) ui.tabname = tabname - with gr.Tabs(elem_id=tabname+"_extra_tabs"): - for page in ui.stored_extra_pages: - with gr.Tab(page.title, id=page.id_page): - elem_id = f"{tabname}_{page.id_page}_cards_html" - page_elem = gr.HTML('Loading...', elem_id=elem_id) - ui.pages.append(page_elem) + related_tabs = [] - page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + quote_js(tabname) + '); return []}', inputs=[], outputs=[]) + for page in ui.stored_extra_pages: + with gr.Tab(page.title, id=page.id_page) as tab: + elem_id = f"{tabname}_{page.id_page}_cards_html" + page_elem = gr.HTML('Loading...', elem_id=elem_id) + ui.pages.append(page_elem) - editor = page.create_user_metadata_editor(ui, tabname) - editor.create_ui() - ui.user_metadata_editors.append(editor) + page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + quote_js(tabname) + '); return []}', inputs=[], outputs=[]) - gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False) - gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", multiselect=False, visible=False, show_label=False, interactive=True) - ToolButton(up_down_symbol, elem_id=tabname+"_extra_sortorder") - button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh") + editor = page.create_user_metadata_editor(ui, tabname) + editor.create_ui() + ui.user_metadata_editors.append(editor) - ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) - ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) - - def toggle_visibility(is_visible): - is_visible = not is_visible - - return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) + related_tabs.append(tab) - def fill_tabs(is_empty): - """Creates HTML for extra networks' tabs when the extra networks button is clicked for the first time.""" + edit_search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) + dropdown_sort = gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True) + button_sortorder = ToolButton(up_down_symbol, elem_id=tabname+"_extra_sortorder", elem_classes="sortorder", visible=False) + button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) - if not ui.pages_contents: - refresh() - - if is_empty: - return True, *ui.pages_contents - - return True, *[gr.update() for _ in ui.pages_contents] + ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) + ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) - state_visible = gr.State(value=False) - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button], show_progress=False) + for tab in unrelated_tabs: + tab.select(fn=lambda: [gr.update(visible=False) for _ in range(5)], inputs=[], outputs=[edit_search, edit_search, dropdown_sort, button_sortorder, button_refresh], show_progress=False) - state_empty = gr.State(value=True) - button.click(fn=fill_tabs, inputs=[state_empty], outputs=[state_empty, *ui.pages], show_progress=False) + for tab in related_tabs: + tab.select(fn=lambda: [gr.update(visible=True) for _ in range(5)], inputs=[], outputs=[edit_search, edit_search, dropdown_sort, button_sortorder, button_refresh], show_progress=False) def refresh(): for pg in ui.stored_extra_pages: @@ -414,6 +401,7 @@ def refresh(): return ui.pages_contents + interface.load(fn=refresh, inputs=[], outputs=[*ui.pages]) button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages) return ui diff --git a/style.css b/style.css index 8a66c3d2130..a7d799550b8 100644 --- a/style.css +++ b/style.css @@ -766,9 +766,10 @@ footer { /* extra networks UI */ .extra-network-cards{ - height: 725px; - overflow: scroll; - resize: vertical; +} + +.extra-networks > div.tab-nav{ + height: 3.4rem; } .extra-networks > div > [id *= '_extra_']{ @@ -784,7 +785,6 @@ footer { } .extra-networks .tab-nav .search, .extra-networks .tab-nav .sort{ - display: inline-block; margin: 0.3em; align-self: center; } From 57d61de25cb6de2e317ae23580971e98c70f542e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 11:52:29 +0300 Subject: [PATCH 0690/2418] fix unneded reload from disk --- modules/ui_extra_networks.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 0eb02873741..c11f1d5bc8a 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -393,6 +393,12 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): for tab in related_tabs: tab.select(fn=lambda: [gr.update(visible=True) for _ in range(5)], inputs=[], outputs=[edit_search, edit_search, dropdown_sort, button_sortorder, button_refresh], show_progress=False) + def pages_html(): + if not ui.pages_contents: + return refresh() + + return ui.pages_contents + def refresh(): for pg in ui.stored_extra_pages: pg.refresh() @@ -401,7 +407,7 @@ def refresh(): return ui.pages_contents - interface.load(fn=refresh, inputs=[], outputs=[*ui.pages]) + interface.load(fn=pages_html, inputs=[], outputs=[*ui.pages]) button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages) return ui From 570f42afd122405116b39b880cdb5163fd5ca3e2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 12:28:50 +0300 Subject: [PATCH 0691/2418] possible fix for FP16 VAE failing in img2img SDXL --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index e7b10808023..6567b3cfbdb 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1303,7 +1303,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): image = torch.from_numpy(batch_images) image = 2. * image - 1. - image = image.to(shared.device) + image = image.to(shared.device, dtype=devices.dtype_vae) self.init_latent = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(image)) From 67ea4eabc3e78c4b496a9fcd21aca95fd5ef7027 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 13:46:33 +0300 Subject: [PATCH 0692/2418] fix cache loading wrong entries from old cache files --- modules/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cache.py b/modules/cache.py index 28d42a8cfe5..ddf44637f40 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -84,7 +84,7 @@ def cached_data_for_file(subsection, title, filename, func): if ondisk_mtime > cached_mtime: entry = None - if not entry: + if not entry or 'value' not in entry: value = func() if value is None: return None From 24bad5dc7b4dd4dfdb47bc1202e4fe438b860e2e Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 13:59:15 +0300 Subject: [PATCH 0693/2418] change extra networks list to have constant height and scrolling --- style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/style.css b/style.css index a7d799550b8..c605cce2ce6 100644 --- a/style.css +++ b/style.css @@ -766,6 +766,9 @@ footer { /* extra networks UI */ .extra-network-cards{ + height: 100vh; + overflow: scroll; + resize: vertical; } .extra-networks > div.tab-nav{ From 7d26c479eebec03c2abb28f7b5226791688a7cea Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 14:39:47 +0300 Subject: [PATCH 0694/2418] changelog for future 1.5.0 --- CHANGELOG.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 925403a9138..30783d9db36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,63 @@ +## 1.5.0 + +### Features: + * SD XL support + * user metadata system for custom networks + * extended Lora metadata editor: set activation text, default weight, view tags, training info + * show github stars for extenstions + * img2img batch mode can read extra stuff from png info + * img2img batch works with subdirectories + * hotkeys to move prompt elements: alt+left/right + * restyle time taken/VRAM display + * add textual inversion hashes to infotext + * optimization: cache git extension repo information + +### Minor: + * checkbox to check/uncheck all extensions in the Installed tab + * add gradio user to infotext and to filename patterns + * allow gif for extra network previews + * add options to change colors in grid + * use natural sort for items in extra networks + * Mac: use empty_cache() from torch 2 to clear VRAM + * added automatic support for installing the right libraries for Navi3 (AMD) + * add option SWIN_torch_compile to accelerate SwinIR upscale + * suppress printing TI embedding info at start to console by default + * speedup extra networks listing + * added `[none]` filename token. + * removed thumbs extra networks view mode (use settings tab to change width/height/scale to get thumbs) + +### Extensions and API: + * api endpoints: /sdapi/v1/server-kill, /sdapi/v1/server-restart, /sdapi/v1/server-stop + * allow Script to have custom metaclass + * add model exists status check /sdapi/v1/options + * rename --add-stop-route to --api-server-stop + * add `before_hr` script callback + * add callback `after_extra_networks_activate` + * disable rich exception output in console for API by default, use WEBUI_RICH_EXCEPTIONS env var to enable + * return http 404 when thumb file not found + * allow replacing extensions index with environment variable + +### Bug Fixes: + * fix for catch errors when retrieving extension index #11290 + * fix very slow loading speed of .safetensors files when reading from network drives + * API cache cleanup + * fix UnicodeEncodeError when writing to file CLIP Interrogator batch mode + * fix warning of 'has_mps' deprecated from PyTorch + * fix problem with extra network saving images as previews losing generation info + * fix throwing exception when trying to resize image with I;16 mode + * fix for #11534: canvas zoom and pan extension hijacking shortcut keys + * fixed launch script to be runnable from any directory + * don't add "Seed Resize: -1x-1" to API image metadata + * correctly remove end parenthesis with ctrl+up/down + * fixing --subpath on newer gradio version + * fix: check fill size none zero when resize (fixes #11425) + * use submit and blur for quick settings textbox + * save img2img batch with images.save_image() + * + + + + ## 1.4.1 ### Bug Fixes: From 643836007f6f47344767322223f96723511f58e0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 14:46:05 +0300 Subject: [PATCH 0695/2418] more tweaking for cards section height --- style.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index c605cce2ce6..88d7135c94c 100644 --- a/style.css +++ b/style.css @@ -766,9 +766,10 @@ footer { /* extra networks UI */ .extra-network-cards{ - height: 100vh; - overflow: scroll; + height: calc(100vh - 24rem); + overflow: clip scroll; resize: vertical; + min-height: 52rem; } .extra-networks > div.tab-nav{ From b75b004fe62826455f1aa77e849e7da13902cb17 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Sun, 16 Jul 2023 23:13:55 +0300 Subject: [PATCH 0696/2418] lora extension rework to include other types of networks --- .../Lora/extra_networks_lora.py | 18 +- extensions-builtin/Lora/lora.py | 537 ------------------ extensions-builtin/Lora/lyco_helpers.py | 15 + extensions-builtin/Lora/network.py | 98 ++++ extensions-builtin/Lora/network_hada.py | 59 ++ extensions-builtin/Lora/network_lora.py | 70 +++ extensions-builtin/Lora/network_lyco.py | 39 ++ extensions-builtin/Lora/networks.py | 443 +++++++++++++++ .../Lora/scripts/lora_script.py | 79 +-- .../Lora/ui_extra_networks_lora.py | 8 +- 10 files changed, 777 insertions(+), 589 deletions(-) delete mode 100644 extensions-builtin/Lora/lora.py create mode 100644 extensions-builtin/Lora/lyco_helpers.py create mode 100644 extensions-builtin/Lora/network.py create mode 100644 extensions-builtin/Lora/network_hada.py create mode 100644 extensions-builtin/Lora/network_lora.py create mode 100644 extensions-builtin/Lora/network_lyco.py create mode 100644 extensions-builtin/Lora/networks.py diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 66ee9c85639..8a6639cf03e 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -1,5 +1,5 @@ from modules import extra_networks, shared -import lora +import networks class ExtraNetworkLora(extra_networks.ExtraNetwork): @@ -9,7 +9,7 @@ def __init__(self): def activate(self, p, params_list): additional = shared.opts.sd_lora - if additional != "None" and additional in lora.available_loras and not any(x for x in params_list if x.items[0] == additional): + 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])) @@ -21,12 +21,12 @@ def activate(self, p, params_list): names.append(params.items[0]) multipliers.append(float(params.items[1]) if len(params.items) > 1 else 1.0) - lora.load_loras(names, multipliers) + networks.load_networks(names, multipliers) if shared.opts.lora_add_hashes_to_infotext: - lora_hashes = [] - for item in lora.loaded_loras: - shorthash = item.lora_on_disk.shorthash + network_hashes = [] + for item in networks.loaded_networks: + shorthash = item.network_on_disk.shorthash if not shorthash: continue @@ -36,10 +36,10 @@ def activate(self, p, params_list): alias = alias.replace(":", "").replace(",", "") - lora_hashes.append(f"{alias}: {shorthash}") + network_hashes.append(f"{alias}: {shorthash}") - if lora_hashes: - p.extra_generation_params["Lora hashes"] = ", ".join(lora_hashes) + if network_hashes: + p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes) def deactivate(self, p): pass diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py deleted file mode 100644 index 9cdff6ed6e3..00000000000 --- a/extensions-builtin/Lora/lora.py +++ /dev/null @@ -1,537 +0,0 @@ -import os -import re -import torch -from typing import Union - -from modules import shared, devices, sd_models, errors, scripts, sd_hijack, hashes, cache - -metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20} - -re_digits = re.compile(r"\d+") -re_x_proj = re.compile(r"(.*)_([qkv]_proj)$") -re_compiled = {} - -suffix_conversion = { - "attentions": {}, - "resnets": { - "conv1": "in_layers_2", - "conv2": "out_layers_3", - "time_emb_proj": "emb_layers_1", - "conv_shortcut": "skip_connection", - } -} - - -def convert_diffusers_name_to_compvis(key, is_sd2): - def match(match_list, regex_text): - regex = re_compiled.get(regex_text) - if regex is None: - regex = re.compile(regex_text) - re_compiled[regex_text] = regex - - r = re.match(regex, key) - if not r: - return False - - match_list.clear() - match_list.extend([int(x) if re.match(re_digits, x) else x for x in r.groups()]) - return True - - m = [] - - if match(m, r"lora_unet_down_blocks_(\d+)_(attentions|resnets)_(\d+)_(.+)"): - suffix = suffix_conversion.get(m[1], {}).get(m[3], m[3]) - return f"diffusion_model_input_blocks_{1 + m[0] * 3 + m[2]}_{1 if m[1] == 'attentions' else 0}_{suffix}" - - if match(m, r"lora_unet_mid_block_(attentions|resnets)_(\d+)_(.+)"): - suffix = suffix_conversion.get(m[0], {}).get(m[2], m[2]) - return f"diffusion_model_middle_block_{1 if m[0] == 'attentions' else m[1] * 2}_{suffix}" - - if match(m, r"lora_unet_up_blocks_(\d+)_(attentions|resnets)_(\d+)_(.+)"): - suffix = suffix_conversion.get(m[1], {}).get(m[3], m[3]) - return f"diffusion_model_output_blocks_{m[0] * 3 + m[2]}_{1 if m[1] == 'attentions' else 0}_{suffix}" - - if match(m, r"lora_unet_down_blocks_(\d+)_downsamplers_0_conv"): - return f"diffusion_model_input_blocks_{3 + m[0] * 3}_0_op" - - if match(m, r"lora_unet_up_blocks_(\d+)_upsamplers_0_conv"): - return f"diffusion_model_output_blocks_{2 + m[0] * 3}_{2 if m[0]>0 else 1}_conv" - - if match(m, r"lora_te_text_model_encoder_layers_(\d+)_(.+)"): - if is_sd2: - if 'mlp_fc1' in m[1]: - return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" - elif 'mlp_fc2' in m[1]: - return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" - else: - return f"model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" - - return f"transformer_text_model_encoder_layers_{m[0]}_{m[1]}" - - if match(m, r"lora_te2_text_model_encoder_layers_(\d+)_(.+)"): - if 'mlp_fc1' in m[1]: - return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" - elif 'mlp_fc2' in m[1]: - return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" - else: - return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" - - return key - - -class LoraOnDisk: - def __init__(self, name, filename): - self.name = name - self.filename = filename - self.metadata = {} - self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors" - - def read_metadata(): - metadata = sd_models.read_metadata_from_safetensors(filename) - metadata.pop('ssmd_cover_images', None) # those are cover images, and they are too big to display in UI as text - - return metadata - - if self.is_safetensors: - try: - self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) - except Exception as e: - errors.display(e, f"reading lora {filename}") - - if self.metadata: - m = {} - for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)): - m[k] = v - - self.metadata = m - - self.alias = self.metadata.get('ss_output_name', self.name) - - self.hash = None - self.shorthash = None - self.set_hash( - self.metadata.get('sshs_model_hash') or - hashes.sha256_from_cache(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or - '' - ) - - def set_hash(self, v): - self.hash = v - self.shorthash = self.hash[0:12] - - if self.shorthash: - available_lora_hash_lookup[self.shorthash] = self - - def read_hash(self): - if not self.hash: - self.set_hash(hashes.sha256(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or '') - - def get_alias(self): - if shared.opts.lora_preferred_name == "Filename" or self.alias.lower() in forbidden_lora_aliases: - return self.name - else: - return self.alias - - -class LoraModule: - def __init__(self, name, lora_on_disk: LoraOnDisk): - self.name = name - self.lora_on_disk = lora_on_disk - self.multiplier = 1.0 - self.modules = {} - self.mtime = None - - self.mentioned_name = None - """the text that was used to add lora to prompt - can be either name or an alias""" - - -class LoraUpDownModule: - def __init__(self): - self.up = None - self.down = None - self.alpha = None - - -def assign_lora_names_to_compvis_modules(sd_model): - lora_layer_mapping = {} - - if shared.sd_model.is_sdxl: - for i, embedder in enumerate(shared.sd_model.conditioner.embedders): - if not hasattr(embedder, 'wrapped'): - continue - - for name, module in embedder.wrapped.named_modules(): - lora_name = f'{i}_{name.replace(".", "_")}' - lora_layer_mapping[lora_name] = module - module.lora_layer_name = lora_name - else: - for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): - lora_name = name.replace(".", "_") - lora_layer_mapping[lora_name] = module - module.lora_layer_name = lora_name - - for name, module in shared.sd_model.model.named_modules(): - lora_name = name.replace(".", "_") - lora_layer_mapping[lora_name] = module - module.lora_layer_name = lora_name - - sd_model.lora_layer_mapping = lora_layer_mapping - - -def load_lora(name, lora_on_disk): - lora = LoraModule(name, lora_on_disk) - lora.mtime = os.path.getmtime(lora_on_disk.filename) - - sd = sd_models.read_state_dict(lora_on_disk.filename) - - # this should not be needed but is here as an emergency fix for an unknown error people are experiencing in 1.2.0 - if not hasattr(shared.sd_model, 'lora_layer_mapping'): - assign_lora_names_to_compvis_modules(shared.sd_model) - - keys_failed_to_match = {} - is_sd2 = 'model_transformer_resblocks' in shared.sd_model.lora_layer_mapping - - for key_lora, weight in sd.items(): - key_lora_without_lora_parts, lora_key = key_lora.split(".", 1) - - key = convert_diffusers_name_to_compvis(key_lora_without_lora_parts, is_sd2) - sd_module = shared.sd_model.lora_layer_mapping.get(key, None) - - if sd_module is None: - m = re_x_proj.match(key) - if m: - sd_module = shared.sd_model.lora_layer_mapping.get(m.group(1), None) - - # SDXL loras seem to already have correct compvis keys, so only need to replace "lora_unet" with "diffusion_model" - if sd_module is None and "lora_unet" in key_lora_without_lora_parts: - key = key_lora_without_lora_parts.replace("lora_unet", "diffusion_model") - sd_module = shared.sd_model.lora_layer_mapping.get(key, None) - elif sd_module is None and "lora_te1_text_model" in key_lora_without_lora_parts: - key = key_lora_without_lora_parts.replace("lora_te1_text_model", "0_transformer_text_model") - sd_module = shared.sd_model.lora_layer_mapping.get(key, None) - - if sd_module is None: - keys_failed_to_match[key_lora] = key - continue - - lora_module = lora.modules.get(key, None) - if lora_module is None: - lora_module = LoraUpDownModule() - lora.modules[key] = lora_module - - if lora_key == "alpha": - lora_module.alpha = weight.item() - continue - - if type(sd_module) == torch.nn.Linear: - module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(sd_module) == torch.nn.modules.linear.NonDynamicallyQuantizableLinear: - module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(sd_module) == torch.nn.MultiheadAttention: - module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(sd_module) == torch.nn.Conv2d and weight.shape[2:] == (1, 1): - module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) - elif type(sd_module) == torch.nn.Conv2d and weight.shape[2:] == (3, 3): - module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (3, 3), bias=False) - else: - print(f'Lora layer {key_lora} matched a layer with unsupported type: {type(sd_module).__name__}') - continue - raise AssertionError(f"Lora layer {key_lora} matched a layer with unsupported type: {type(sd_module).__name__}") - - with torch.no_grad(): - module.weight.copy_(weight) - - module.to(device=devices.cpu, dtype=devices.dtype) - - if lora_key == "lora_up.weight": - lora_module.up = module - elif lora_key == "lora_down.weight": - lora_module.down = module - else: - raise AssertionError(f"Bad Lora layer name: {key_lora} - must end in lora_up.weight, lora_down.weight or alpha") - - if keys_failed_to_match: - print(f"Failed to match keys when loading Lora {lora_on_disk.filename}: {keys_failed_to_match}") - - return lora - - -def load_loras(names, multipliers=None): - already_loaded = {} - - for lora in loaded_loras: - if lora.name in names: - already_loaded[lora.name] = lora - - loaded_loras.clear() - - loras_on_disk = [available_lora_aliases.get(name, None) for name in names] - if any(x is None for x in loras_on_disk): - list_available_loras() - - loras_on_disk = [available_lora_aliases.get(name, None) for name in names] - - failed_to_load_loras = [] - - for i, name in enumerate(names): - lora = already_loaded.get(name, None) - - lora_on_disk = loras_on_disk[i] - - if lora_on_disk is not None: - if lora is None or os.path.getmtime(lora_on_disk.filename) > lora.mtime: - try: - lora = load_lora(name, lora_on_disk) - except Exception as e: - errors.display(e, f"loading Lora {lora_on_disk.filename}") - continue - - lora.mentioned_name = name - - lora_on_disk.read_hash() - - if lora is None: - failed_to_load_loras.append(name) - print(f"Couldn't find Lora with name {name}") - continue - - lora.multiplier = multipliers[i] if multipliers else 1.0 - loaded_loras.append(lora) - - if failed_to_load_loras: - sd_hijack.model_hijack.comments.append("Failed to find Loras: " + ", ".join(failed_to_load_loras)) - - -def lora_calc_updown(lora, module, target): - with torch.no_grad(): - up = module.up.weight.to(target.device, dtype=target.dtype) - down = module.down.weight.to(target.device, dtype=target.dtype) - - if up.shape[2:] == (1, 1) and down.shape[2:] == (1, 1): - updown = (up.squeeze(2).squeeze(2) @ down.squeeze(2).squeeze(2)).unsqueeze(2).unsqueeze(3) - elif up.shape[2:] == (3, 3) or down.shape[2:] == (3, 3): - updown = torch.nn.functional.conv2d(down.permute(1, 0, 2, 3), up).permute(1, 0, 2, 3) - else: - updown = up @ down - - updown = updown * lora.multiplier * (module.alpha / module.up.weight.shape[1] if module.alpha else 1.0) - - return updown - - -def lora_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): - weights_backup = getattr(self, "lora_weights_backup", None) - - if weights_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) - - -def lora_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): - """ - Applies the currently selected set of Loras to the weights of torch layer self. - If weights already have this particular set of loras applied, does nothing. - If not, restores orginal weights from backup and alters weights according to loras. - """ - - lora_layer_name = getattr(self, 'lora_layer_name', None) - if lora_layer_name is None: - return - - current_names = getattr(self, "lora_current_names", ()) - wanted_names = tuple((x.name, x.multiplier) for x in loaded_loras) - - weights_backup = getattr(self, "lora_weights_backup", None) - if weights_backup is None: - if isinstance(self, torch.nn.MultiheadAttention): - weights_backup = (self.in_proj_weight.to(devices.cpu, copy=True), self.out_proj.weight.to(devices.cpu, copy=True)) - else: - weights_backup = self.weight.to(devices.cpu, copy=True) - - self.lora_weights_backup = weights_backup - - if current_names != wanted_names: - lora_restore_weights_from_backup(self) - - for lora in loaded_loras: - module = lora.modules.get(lora_layer_name, None) - if module is not None and hasattr(self, 'weight'): - self.weight += lora_calc_updown(lora, module, self.weight) - continue - - module_q = lora.modules.get(lora_layer_name + "_q_proj", None) - module_k = lora.modules.get(lora_layer_name + "_k_proj", None) - module_v = lora.modules.get(lora_layer_name + "_v_proj", None) - module_out = lora.modules.get(lora_layer_name + "_out_proj", None) - - if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out: - updown_q = lora_calc_updown(lora, module_q, self.in_proj_weight) - updown_k = lora_calc_updown(lora, module_k, self.in_proj_weight) - updown_v = lora_calc_updown(lora, module_v, self.in_proj_weight) - updown_qkv = torch.vstack([updown_q, updown_k, updown_v]) - - self.in_proj_weight += updown_qkv - self.out_proj.weight += lora_calc_updown(lora, module_out, self.out_proj.weight) - continue - - if module is None: - continue - - print(f'failed to calculate lora weights for layer {lora_layer_name}') - - self.lora_current_names = wanted_names - - -def lora_forward(module, input, original_forward): - """ - Old way of applying Lora by executing operations during layer's forward. - Stacking many loras this way results in big performance degradation. - """ - - if len(loaded_loras) == 0: - return original_forward(module, input) - - input = devices.cond_cast_unet(input) - - lora_restore_weights_from_backup(module) - lora_reset_cached_weight(module) - - res = original_forward(module, input) - - lora_layer_name = getattr(module, 'lora_layer_name', None) - for lora in loaded_loras: - module = lora.modules.get(lora_layer_name, None) - if module is None: - continue - - module.up.to(device=devices.device) - module.down.to(device=devices.device) - - res = res + module.up(module.down(input)) * lora.multiplier * (module.alpha / module.up.weight.shape[1] if module.alpha else 1.0) - - return res - - -def lora_reset_cached_weight(self: Union[torch.nn.Conv2d, torch.nn.Linear]): - self.lora_current_names = () - self.lora_weights_backup = None - - -def lora_Linear_forward(self, input): - if shared.opts.lora_functional: - return lora_forward(self, input, torch.nn.Linear_forward_before_lora) - - lora_apply_weights(self) - - return torch.nn.Linear_forward_before_lora(self, input) - - -def lora_Linear_load_state_dict(self, *args, **kwargs): - lora_reset_cached_weight(self) - - return torch.nn.Linear_load_state_dict_before_lora(self, *args, **kwargs) - - -def lora_Conv2d_forward(self, input): - if shared.opts.lora_functional: - return lora_forward(self, input, torch.nn.Conv2d_forward_before_lora) - - lora_apply_weights(self) - - return torch.nn.Conv2d_forward_before_lora(self, input) - - -def lora_Conv2d_load_state_dict(self, *args, **kwargs): - lora_reset_cached_weight(self) - - return torch.nn.Conv2d_load_state_dict_before_lora(self, *args, **kwargs) - - -def lora_MultiheadAttention_forward(self, *args, **kwargs): - lora_apply_weights(self) - - return torch.nn.MultiheadAttention_forward_before_lora(self, *args, **kwargs) - - -def lora_MultiheadAttention_load_state_dict(self, *args, **kwargs): - lora_reset_cached_weight(self) - - return torch.nn.MultiheadAttention_load_state_dict_before_lora(self, *args, **kwargs) - - -def list_available_loras(): - available_loras.clear() - available_lora_aliases.clear() - forbidden_lora_aliases.clear() - available_lora_hash_lookup.clear() - forbidden_lora_aliases.update({"none": 1, "Addams": 1}) - - os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) - - candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) - for filename in candidates: - if os.path.isdir(filename): - continue - - name = os.path.splitext(os.path.basename(filename))[0] - try: - entry = LoraOnDisk(name, filename) - except OSError: # should catch FileNotFoundError and PermissionError etc. - errors.report(f"Failed to load LoRA {name} from {filename}", exc_info=True) - continue - - available_loras[name] = entry - - if entry.alias in available_lora_aliases: - forbidden_lora_aliases[entry.alias.lower()] = 1 - - available_lora_aliases[name] = entry - available_lora_aliases[entry.alias] = entry - - -re_lora_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)") - - -def infotext_pasted(infotext, params): - if "AddNet Module 1" in [x[1] for x in scripts.scripts_txt2img.infotext_fields]: - return # if the other extension is active, it will handle those fields, no need to do anything - - added = [] - - for k in params: - if not k.startswith("AddNet Model "): - continue - - num = k[13:] - - if params.get("AddNet Module " + num) != "LoRA": - continue - - name = params.get("AddNet Model " + num) - if name is None: - continue - - m = re_lora_name.match(name) - if m: - name = m.group(1) - - multiplier = params.get("AddNet Weight A " + num, "1.0") - - added.append(f"") - - if added: - params["Prompt"] += "\n" + "".join(added) - - -available_loras = {} -available_lora_aliases = {} -available_lora_hash_lookup = {} -forbidden_lora_aliases = {} -loaded_loras = [] - -list_available_loras() diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py new file mode 100644 index 00000000000..9ea499fbd0b --- /dev/null +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -0,0 +1,15 @@ +import torch + + +def make_weight_cp(t, wa, wb): + temp = torch.einsum('i j k l, j r -> i r k l', t, wb) + return torch.einsum('i j k l, i r -> r j k l', temp, wa) + + +def rebuild_conventional(up, down, shape, dyn_dim=None): + up = up.reshape(up.size(0), -1) + down = down.reshape(down.size(0), -1) + if dyn_dim is not None: + up = up[:, :dyn_dim] + down = down[:dyn_dim, :] + return (up @ down).reshape(shape) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py new file mode 100644 index 00000000000..a1fe6bbf732 --- /dev/null +++ b/extensions-builtin/Lora/network.py @@ -0,0 +1,98 @@ +import os +from collections import namedtuple + +import torch + +from modules import devices, sd_models, cache, errors, hashes, shared + +NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module']) + +metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20} + + +class NetworkOnDisk: + def __init__(self, name, filename): + self.name = name + self.filename = filename + self.metadata = {} + self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors" + + def read_metadata(): + metadata = sd_models.read_metadata_from_safetensors(filename) + metadata.pop('ssmd_cover_images', None) # those are cover images, and they are too big to display in UI as text + + return metadata + + if self.is_safetensors: + try: + self.metadata = cache.cached_data_for_file('safetensors-metadata', "lora/" + self.name, filename, read_metadata) + except Exception as e: + errors.display(e, f"reading lora {filename}") + + if self.metadata: + m = {} + for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)): + m[k] = v + + self.metadata = m + + self.alias = self.metadata.get('ss_output_name', self.name) + + self.hash = None + self.shorthash = None + self.set_hash( + self.metadata.get('sshs_model_hash') or + hashes.sha256_from_cache(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or + '' + ) + + def set_hash(self, v): + self.hash = v + self.shorthash = self.hash[0:12] + + if self.shorthash: + import networks + networks.available_network_hash_lookup[self.shorthash] = self + + def read_hash(self): + if not self.hash: + self.set_hash(hashes.sha256(self.filename, "lora/" + self.name, use_addnet_hash=self.is_safetensors) or '') + + def get_alias(self): + import networks + if shared.opts.lora_preferred_name == "Filename" or self.alias.lower() in networks.forbidden_network_aliases: + return self.name + else: + return self.alias + + +class Network: # LoraModule + def __init__(self, name, network_on_disk: NetworkOnDisk): + self.name = name + self.network_on_disk = network_on_disk + self.multiplier = 1.0 + self.modules = {} + self.mtime = None + + self.mentioned_name = None + """the text that was used to add the network to prompt - can be either name or an alias""" + + +class ModuleType: + def create_module(self, net: Network, weights: NetworkWeights) -> Network | None: + return None + + +class NetworkModule: + def __init__(self, net: Network, weights: NetworkWeights): + self.network = net + self.network_key = weights.network_key + self.sd_key = weights.sd_key + self.sd_module = weights.sd_module + + def calc_updown(self, target): + raise NotImplementedError() + + def forward(self, x, y): + raise NotImplementedError() + diff --git a/extensions-builtin/Lora/network_hada.py b/extensions-builtin/Lora/network_hada.py new file mode 100644 index 00000000000..15e7ffd81c3 --- /dev/null +++ b/extensions-builtin/Lora/network_hada.py @@ -0,0 +1,59 @@ +import lyco_helpers +import network +import network_lyco + + +class ModuleTypeHada(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["hada_w1_a", "hada_w1_b", "hada_w2_a", "hada_w2_b"]): + return NetworkModuleHada(net, weights) + + return None + + +class NetworkModuleHada(network_lyco.NetworkModuleLyco): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + if hasattr(self.sd_module, 'weight'): + self.shape = self.sd_module.weight.shape + + self.w1a = weights.w["hada_w1_a"] + self.w1b = weights.w["hada_w1_b"] + self.dim = self.w1b.shape[0] + self.w2a = weights.w["hada_w2_a"] + self.w2b = weights.w["hada_w2_b"] + + self.t1 = weights.w.get("hada_t1") + self.t2 = weights.w.get("hada_t2") + + self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None + self.scale = weights.w["scale"].item() if "scale" in weights.w else None + + def calc_updown(self, orig_weight): + w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) + w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) + w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) + w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + + output_shape = [w1a.size(0), w1b.size(1)] + + if self.t1 is not None: + output_shape = [w1a.size(1), w1b.size(1)] + t1 = self.t1.to(orig_weight.device, dtype=orig_weight.dtype) + updown1 = lyco_helpers.make_weight_cp(t1, w1a, w1b) + output_shape += t1.shape[2:] + else: + if len(w1b.shape) == 4: + output_shape += w1b.shape[2:] + updown1 = lyco_helpers.rebuild_conventional(w1a, w1b, output_shape) + + if self.t2 is not None: + t2 = self.t2.to(orig_weight.device, dtype=orig_weight.dtype) + updown2 = lyco_helpers.make_weight_cp(t2, w2a, w2b) + else: + updown2 = lyco_helpers.rebuild_conventional(w2a, w2b, output_shape) + + updown = updown1 * updown2 + + return self.finalize_updown(updown, orig_weight, output_shape) diff --git a/extensions-builtin/Lora/network_lora.py b/extensions-builtin/Lora/network_lora.py new file mode 100644 index 00000000000..b2d965373fb --- /dev/null +++ b/extensions-builtin/Lora/network_lora.py @@ -0,0 +1,70 @@ +import torch + +import network +from modules import devices + + +class ModuleTypeLora(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["lora_up.weight", "lora_down.weight"]): + return NetworkModuleLora(net, weights) + + return None + + +class NetworkModuleLora(network.NetworkModule): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + self.up = self.create_module(weights.w["lora_up.weight"]) + self.down = self.create_module(weights.w["lora_down.weight"]) + self.alpha = weights.w["alpha"] if "alpha" in weights.w else None + + def create_module(self, weight, none_ok=False): + if weight is None and none_ok: + return None + + if type(self.sd_module) == torch.nn.Linear: + module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) + elif type(self.sd_module) == torch.nn.modules.linear.NonDynamicallyQuantizableLinear: + module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) + elif type(self.sd_module) == torch.nn.MultiheadAttention: + module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) + elif type(self.sd_module) == torch.nn.Conv2d and weight.shape[2:] == (1, 1): + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) + elif type(self.sd_module) == torch.nn.Conv2d and weight.shape[2:] == (3, 3): + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (3, 3), bias=False) + else: + print(f'Network layer {self.network_key} matched a layer with unsupported type: {type(self.sd_module).__name__}') + return None + + with torch.no_grad(): + module.weight.copy_(weight) + + module.to(device=devices.cpu, dtype=devices.dtype) + module.weight.requires_grad_(False) + + return module + + def calc_updown(self, target): + up = self.up.weight.to(target.device, dtype=target.dtype) + down = self.down.weight.to(target.device, dtype=target.dtype) + + if up.shape[2:] == (1, 1) and down.shape[2:] == (1, 1): + updown = (up.squeeze(2).squeeze(2) @ down.squeeze(2).squeeze(2)).unsqueeze(2).unsqueeze(3) + elif up.shape[2:] == (3, 3) or down.shape[2:] == (3, 3): + updown = torch.nn.functional.conv2d(down.permute(1, 0, 2, 3), up).permute(1, 0, 2, 3) + else: + updown = up @ down + + updown = updown * self.network.multiplier * (self.alpha / self.up.weight.shape[1] if self.alpha else 1.0) + + return updown + + def forward(self, x, y): + self.up.to(device=devices.device) + self.down.to(device=devices.device) + + return y + self.up(self.down(x)) * self.network.multiplier * (self.alpha / self.up.weight.shape[1] if self.alpha else 1.0) + + diff --git a/extensions-builtin/Lora/network_lyco.py b/extensions-builtin/Lora/network_lyco.py new file mode 100644 index 00000000000..18a822fab28 --- /dev/null +++ b/extensions-builtin/Lora/network_lyco.py @@ -0,0 +1,39 @@ +import torch + +import lyco_helpers +import network +from modules import devices + + +class NetworkModuleLyco(network.NetworkModule): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + if hasattr(self.sd_module, 'weight'): + self.shape = self.sd_module.weight.shape + + self.dim = None + self.bias = weights.w.get("bias") + self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None + self.scale = weights.w["scale"].item() if "scale" in weights.w else None + + def finalize_updown(self, updown, orig_weight, output_shape): + if self.bias is not None: + updown = updown.reshape(self.bias.shape) + updown += self.bias.to(orig_weight.device, dtype=orig_weight.dtype) + updown = updown.reshape(output_shape) + + if len(output_shape) == 4: + updown = updown.reshape(output_shape) + + if orig_weight.size().numel() == updown.size().numel(): + updown = updown.reshape(orig_weight.shape) + + scale = ( + self.scale if self.scale is not None + else self.alpha / self.dim if self.dim is not None and self.alpha is not None + else 1.0 + ) + + return updown * scale * self.network.multiplier + diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py new file mode 100644 index 00000000000..5b0ddfb659c --- /dev/null +++ b/extensions-builtin/Lora/networks.py @@ -0,0 +1,443 @@ +import os +import re + +import network +import network_lora +import network_hada + +import torch +from typing import Union + +from modules import shared, devices, sd_models, errors, scripts, sd_hijack + +module_types = [ + network_lora.ModuleTypeLora(), + network_hada.ModuleTypeHada(), +] + + +re_digits = re.compile(r"\d+") +re_x_proj = re.compile(r"(.*)_([qkv]_proj)$") +re_compiled = {} + +suffix_conversion = { + "attentions": {}, + "resnets": { + "conv1": "in_layers_2", + "conv2": "out_layers_3", + "time_emb_proj": "emb_layers_1", + "conv_shortcut": "skip_connection", + } +} + + +def convert_diffusers_name_to_compvis(key, is_sd2): + def match(match_list, regex_text): + regex = re_compiled.get(regex_text) + if regex is None: + regex = re.compile(regex_text) + re_compiled[regex_text] = regex + + r = re.match(regex, key) + if not r: + return False + + match_list.clear() + match_list.extend([int(x) if re.match(re_digits, x) else x for x in r.groups()]) + return True + + m = [] + + if match(m, r"lora_unet_down_blocks_(\d+)_(attentions|resnets)_(\d+)_(.+)"): + suffix = suffix_conversion.get(m[1], {}).get(m[3], m[3]) + return f"diffusion_model_input_blocks_{1 + m[0] * 3 + m[2]}_{1 if m[1] == 'attentions' else 0}_{suffix}" + + if match(m, r"lora_unet_mid_block_(attentions|resnets)_(\d+)_(.+)"): + suffix = suffix_conversion.get(m[0], {}).get(m[2], m[2]) + return f"diffusion_model_middle_block_{1 if m[0] == 'attentions' else m[1] * 2}_{suffix}" + + if match(m, r"lora_unet_up_blocks_(\d+)_(attentions|resnets)_(\d+)_(.+)"): + suffix = suffix_conversion.get(m[1], {}).get(m[3], m[3]) + return f"diffusion_model_output_blocks_{m[0] * 3 + m[2]}_{1 if m[1] == 'attentions' else 0}_{suffix}" + + if match(m, r"lora_unet_down_blocks_(\d+)_downsamplers_0_conv"): + return f"diffusion_model_input_blocks_{3 + m[0] * 3}_0_op" + + if match(m, r"lora_unet_up_blocks_(\d+)_upsamplers_0_conv"): + return f"diffusion_model_output_blocks_{2 + m[0] * 3}_{2 if m[0]>0 else 1}_conv" + + if match(m, r"lora_te_text_model_encoder_layers_(\d+)_(.+)"): + if is_sd2: + if 'mlp_fc1' in m[1]: + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" + elif 'mlp_fc2' in m[1]: + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" + else: + return f"model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" + + return f"transformer_text_model_encoder_layers_{m[0]}_{m[1]}" + + if match(m, r"lora_te2_text_model_encoder_layers_(\d+)_(.+)"): + if 'mlp_fc1' in m[1]: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc1', 'mlp_c_fc')}" + elif 'mlp_fc2' in m[1]: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('mlp_fc2', 'mlp_c_proj')}" + else: + return f"1_model_transformer_resblocks_{m[0]}_{m[1].replace('self_attn', 'attn')}" + + return key + + +def assign_network_names_to_compvis_modules(sd_model): + network_layer_mapping = {} + + if shared.sd_model.is_sdxl: + for i, embedder in enumerate(shared.sd_model.conditioner.embedders): + if not hasattr(embedder, 'wrapped'): + continue + + for name, module in embedder.wrapped.named_modules(): + network_name = f'{i}_{name.replace(".", "_")}' + network_layer_mapping[network_name] = module + module.network_layer_name = network_name + else: + for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): + network_name = name.replace(".", "_") + network_layer_mapping[network_name] = module + module.network_layer_name = network_name + + for name, module in shared.sd_model.model.named_modules(): + network_name = name.replace(".", "_") + network_layer_mapping[network_name] = module + module.network_layer_name = network_name + + sd_model.network_layer_mapping = network_layer_mapping + + +def load_network(name, network_on_disk): + net = network.Network(name, network_on_disk) + net.mtime = os.path.getmtime(network_on_disk.filename) + + sd = sd_models.read_state_dict(network_on_disk.filename) + + # this should not be needed but is here as an emergency fix for an unknown error people are experiencing in 1.2.0 + if not hasattr(shared.sd_model, 'network_layer_mapping'): + assign_network_names_to_compvis_modules(shared.sd_model) + + keys_failed_to_match = {} + is_sd2 = 'model_transformer_resblocks' in shared.sd_model.network_layer_mapping + + matched_networks = {} + + for key_network, weight in sd.items(): + key_network_without_network_parts, network_part = key_network.split(".", 1) + + key = convert_diffusers_name_to_compvis(key_network_without_network_parts, is_sd2) + sd_module = shared.sd_model.network_layer_mapping.get(key, None) + + if sd_module is None: + m = re_x_proj.match(key) + if m: + sd_module = shared.sd_model.network_layer_mapping.get(m.group(1), None) + + # SDXL loras seem to already have correct compvis keys, so only need to replace "lora_unet" with "diffusion_model" + if sd_module is None and "lora_unet" in key_network_without_network_parts: + key = key_network_without_network_parts.replace("lora_unet", "diffusion_model") + sd_module = shared.sd_model.network_layer_mapping.get(key, None) + elif sd_module is None and "lora_te1_text_model" in key_network_without_network_parts: + key = key_network_without_network_parts.replace("lora_te1_text_model", "0_transformer_text_model") + sd_module = shared.sd_model.network_layer_mapping.get(key, None) + + if sd_module is None: + keys_failed_to_match[key_network] = key + continue + + if key not in matched_networks: + matched_networks[key] = network.NetworkWeights(network_key=key_network, sd_key=key, w={}, sd_module=sd_module) + + matched_networks[key].w[network_part] = weight + + for key, weights in matched_networks.items(): + net_module = None + for nettype in module_types: + net_module = nettype.create_module(net, weights) + if net_module is not None: + break + + if net_module is None: + raise AssertionError(f"Could not find a module type (out of {', '.join([x.__class__.__name__ for x in module_types])}) that would accept those keys: {', '.join(weights.w)}") + + 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}") + + return net + + +def load_networks(names, multipliers=None): + already_loaded = {} + + for net in loaded_networks: + if net.name in names: + already_loaded[net.name] = net + + loaded_networks.clear() + + networks_on_disk = [available_network_aliases.get(name, None) for name in names] + if any(x is None for x in networks_on_disk): + list_available_networks() + + networks_on_disk = [available_network_aliases.get(name, None) for name in names] + + failed_to_load_networks = [] + + for i, name in enumerate(names): + net = already_loaded.get(name, None) + + network_on_disk = networks_on_disk[i] + + if network_on_disk is not None: + if net is None or os.path.getmtime(network_on_disk.filename) > net.mtime: + try: + net = load_network(name, network_on_disk) + except Exception as e: + errors.display(e, f"loading network {network_on_disk.filename}") + continue + + net.mentioned_name = name + + network_on_disk.read_hash() + + if net is None: + failed_to_load_networks.append(name) + print(f"Couldn't find network with name {name}") + continue + + net.multiplier = multipliers[i] if multipliers else 1.0 + loaded_networks.append(net) + + if failed_to_load_networks: + sd_hijack.model_hijack.comments.append("Failed to find networks: " + ", ".join(failed_to_load_networks)) + + +def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.MultiheadAttention]): + weights_backup = getattr(self, "network_weights_backup", None) + + if weights_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) + + +def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, 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. + If not, restores orginal weights from backup and alters weights according to networks. + """ + + network_layer_name = getattr(self, 'network_layer_name', None) + if network_layer_name is None: + return + + current_names = getattr(self, "network_current_names", ()) + wanted_names = tuple((x.name, x.multiplier) for x in loaded_networks) + + weights_backup = getattr(self, "network_weights_backup", None) + if weights_backup is None: + if isinstance(self, torch.nn.MultiheadAttention): + weights_backup = (self.in_proj_weight.to(devices.cpu, copy=True), self.out_proj.weight.to(devices.cpu, copy=True)) + else: + weights_backup = self.weight.to(devices.cpu, copy=True) + + self.network_weights_backup = weights_backup + + if current_names != wanted_names: + network_restore_weights_from_backup(self) + + 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 = 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 + + module_q = net.modules.get(network_layer_name + "_q_proj", None) + module_k = net.modules.get(network_layer_name + "_k_proj", None) + module_v = net.modules.get(network_layer_name + "_v_proj", None) + 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]) + + self.in_proj_weight += updown_qkv + self.out_proj.weight += module_out.calc_updown(self.out_proj.weight) + continue + + if module is None: + continue + + print(f'failed to calculate network weights for layer {network_layer_name}') + + self.network_current_names = wanted_names + + +def network_forward(module, input, original_forward): + """ + Old way of applying Lora by executing operations during layer's forward. + Stacking many loras this way results in big performance degradation. + """ + + if len(loaded_networks) == 0: + return original_forward(module, input) + + input = devices.cond_cast_unet(input) + + network_restore_weights_from_backup(module) + network_reset_cached_weight(module) + + y = original_forward(module, input) + + network_layer_name = getattr(module, 'network_layer_name', None) + for lora in loaded_networks: + module = lora.modules.get(network_layer_name, None) + if module is None: + continue + + y = module.forward(y, input) + + return y + + +def network_reset_cached_weight(self: Union[torch.nn.Conv2d, torch.nn.Linear]): + self.network_current_names = () + self.network_weights_backup = None + + +def network_Linear_forward(self, input): + if shared.opts.lora_functional: + return network_forward(self, input, torch.nn.Linear_forward_before_network) + + network_apply_weights(self) + + return torch.nn.Linear_forward_before_network(self, input) + + +def network_Linear_load_state_dict(self, *args, **kwargs): + network_reset_cached_weight(self) + + return torch.nn.Linear_load_state_dict_before_network(self, *args, **kwargs) + + +def network_Conv2d_forward(self, input): + if shared.opts.lora_functional: + return network_forward(self, input, torch.nn.Conv2d_forward_before_network) + + network_apply_weights(self) + + return torch.nn.Conv2d_forward_before_network(self, input) + + +def network_Conv2d_load_state_dict(self, *args, **kwargs): + network_reset_cached_weight(self) + + return torch.nn.Conv2d_load_state_dict_before_network(self, *args, **kwargs) + + +def network_MultiheadAttention_forward(self, *args, **kwargs): + network_apply_weights(self) + + return torch.nn.MultiheadAttention_forward_before_network(self, *args, **kwargs) + + +def network_MultiheadAttention_load_state_dict(self, *args, **kwargs): + network_reset_cached_weight(self) + + return torch.nn.MultiheadAttention_load_state_dict_before_network(self, *args, **kwargs) + + +def list_available_networks(): + available_networks.clear() + available_network_aliases.clear() + forbidden_network_aliases.clear() + available_network_hash_lookup.clear() + forbidden_network_aliases.update({"none": 1, "Addams": 1}) + + os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) + + candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + for filename in candidates: + if os.path.isdir(filename): + continue + + name = os.path.splitext(os.path.basename(filename))[0] + try: + entry = network.NetworkOnDisk(name, filename) + except OSError: # should catch FileNotFoundError and PermissionError etc. + errors.report(f"Failed to load network {name} from {filename}", exc_info=True) + continue + + available_networks[name] = entry + + if entry.alias in available_network_aliases: + forbidden_network_aliases[entry.alias.lower()] = 1 + + available_network_aliases[name] = entry + available_network_aliases[entry.alias] = entry + + +re_network_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)") + + +def infotext_pasted(infotext, params): + if "AddNet Module 1" in [x[1] for x in scripts.scripts_txt2img.infotext_fields]: + return # if the other extension is active, it will handle those fields, no need to do anything + + added = [] + + for k in params: + if not k.startswith("AddNet Model "): + continue + + num = k[13:] + + if params.get("AddNet Module " + num) != "LoRA": + continue + + name = params.get("AddNet Model " + num) + if name is None: + continue + + m = re_network_name.match(name) + if m: + name = m.group(1) + + multiplier = params.get("AddNet Weight A " + num, "1.0") + + added.append(f"") + + if added: + params["Prompt"] += "\n" + "".join(added) + + +available_networks = {} +available_network_aliases = {} +loaded_networks = [] +available_network_hash_lookup = {} +forbidden_network_aliases = {} + +list_available_networks() diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index e650f469f36..81e6572a23b 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -4,18 +4,19 @@ import gradio as gr from fastapi import FastAPI -import lora +import network +import networks import extra_networks_lora import ui_extra_networks_lora from modules import script_callbacks, ui_extra_networks, extra_networks, shared def unload(): - torch.nn.Linear.forward = torch.nn.Linear_forward_before_lora - torch.nn.Linear._load_from_state_dict = torch.nn.Linear_load_state_dict_before_lora - torch.nn.Conv2d.forward = torch.nn.Conv2d_forward_before_lora - torch.nn.Conv2d._load_from_state_dict = torch.nn.Conv2d_load_state_dict_before_lora - torch.nn.MultiheadAttention.forward = torch.nn.MultiheadAttention_forward_before_lora - torch.nn.MultiheadAttention._load_from_state_dict = torch.nn.MultiheadAttention_load_state_dict_before_lora + torch.nn.Linear.forward = torch.nn.Linear_forward_before_network + torch.nn.Linear._load_from_state_dict = torch.nn.Linear_load_state_dict_before_network + torch.nn.Conv2d.forward = torch.nn.Conv2d_forward_before_network + torch.nn.Conv2d._load_from_state_dict = torch.nn.Conv2d_load_state_dict_before_network + torch.nn.MultiheadAttention.forward = torch.nn.MultiheadAttention_forward_before_network + torch.nn.MultiheadAttention._load_from_state_dict = torch.nn.MultiheadAttention_load_state_dict_before_network def before_ui(): @@ -23,50 +24,50 @@ def before_ui(): extra_networks.register_extra_network(extra_networks_lora.ExtraNetworkLora()) -if not hasattr(torch.nn, 'Linear_forward_before_lora'): - torch.nn.Linear_forward_before_lora = torch.nn.Linear.forward +if not hasattr(torch.nn, 'Linear_forward_before_network'): + torch.nn.Linear_forward_before_network = torch.nn.Linear.forward -if not hasattr(torch.nn, 'Linear_load_state_dict_before_lora'): - torch.nn.Linear_load_state_dict_before_lora = torch.nn.Linear._load_from_state_dict +if not hasattr(torch.nn, 'Linear_load_state_dict_before_network'): + torch.nn.Linear_load_state_dict_before_network = torch.nn.Linear._load_from_state_dict -if not hasattr(torch.nn, 'Conv2d_forward_before_lora'): - torch.nn.Conv2d_forward_before_lora = torch.nn.Conv2d.forward +if not hasattr(torch.nn, 'Conv2d_forward_before_network'): + torch.nn.Conv2d_forward_before_network = torch.nn.Conv2d.forward -if not hasattr(torch.nn, 'Conv2d_load_state_dict_before_lora'): - torch.nn.Conv2d_load_state_dict_before_lora = torch.nn.Conv2d._load_from_state_dict +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, 'MultiheadAttention_forward_before_lora'): - torch.nn.MultiheadAttention_forward_before_lora = torch.nn.MultiheadAttention.forward +if not hasattr(torch.nn, 'MultiheadAttention_forward_before_network'): + torch.nn.MultiheadAttention_forward_before_network = torch.nn.MultiheadAttention.forward -if not hasattr(torch.nn, 'MultiheadAttention_load_state_dict_before_lora'): - torch.nn.MultiheadAttention_load_state_dict_before_lora = torch.nn.MultiheadAttention._load_from_state_dict +if not hasattr(torch.nn, 'MultiheadAttention_load_state_dict_before_network'): + torch.nn.MultiheadAttention_load_state_dict_before_network = torch.nn.MultiheadAttention._load_from_state_dict -torch.nn.Linear.forward = lora.lora_Linear_forward -torch.nn.Linear._load_from_state_dict = lora.lora_Linear_load_state_dict -torch.nn.Conv2d.forward = lora.lora_Conv2d_forward -torch.nn.Conv2d._load_from_state_dict = lora.lora_Conv2d_load_state_dict -torch.nn.MultiheadAttention.forward = lora.lora_MultiheadAttention_forward -torch.nn.MultiheadAttention._load_from_state_dict = lora.lora_MultiheadAttention_load_state_dict +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.MultiheadAttention.forward = networks.network_MultiheadAttention_forward +torch.nn.MultiheadAttention._load_from_state_dict = networks.network_MultiheadAttention_load_state_dict -script_callbacks.on_model_loaded(lora.assign_lora_names_to_compvis_modules) +script_callbacks.on_model_loaded(networks.assign_network_names_to_compvis_modules) script_callbacks.on_script_unloaded(unload) script_callbacks.on_before_ui(before_ui) -script_callbacks.on_infotext_pasted(lora.infotext_pasted) +script_callbacks.on_infotext_pasted(networks.infotext_pasted) shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), { - "sd_lora": shared.OptionInfo("None", "Add Lora to prompt", gr.Dropdown, lambda: {"choices": ["None", *lora.available_loras]}, refresh=lora.list_available_loras), + "sd_lora": shared.OptionInfo("None", "Add network to prompt", gr.Dropdown, lambda: {"choices": ["None", *networks.available_networks]}, refresh=networks.list_available_networks), "lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to Lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}), "lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"), })) shared.options_templates.update(shared.options_section(('compatibility', "Compatibility"), { - "lora_functional": shared.OptionInfo(False, "Lora: use old method that takes longer when you have multiple Loras active and produces same results as kohya-ss/sd-webui-additional-networks extension"), + "lora_functional": shared.OptionInfo(False, "Lora/Networks: use old method that takes longer when you have multiple Loras active and produces same results as kohya-ss/sd-webui-additional-networks extension"), })) -def create_lora_json(obj: lora.LoraOnDisk): +def create_lora_json(obj: network.NetworkOnDisk): return { "name": obj.name, "alias": obj.alias, @@ -75,17 +76,17 @@ def create_lora_json(obj: lora.LoraOnDisk): } -def api_loras(_: gr.Blocks, app: FastAPI): +def api_networks(_: gr.Blocks, app: FastAPI): @app.get("/sdapi/v1/loras") async def get_loras(): - return [create_lora_json(obj) for obj in lora.available_loras.values()] + return [create_lora_json(obj) for obj in networks.available_networks.values()] @app.post("/sdapi/v1/refresh-loras") async def refresh_loras(): - return lora.list_available_loras() + return networks.list_available_networks() -script_callbacks.on_app_started(api_loras) +script_callbacks.on_app_started(api_networks) re_lora = re.compile(" Date: Sun, 16 Jul 2023 23:14:57 +0300 Subject: [PATCH 0697/2418] linter --- extensions-builtin/Lora/network.py | 4 +--- extensions-builtin/Lora/network_lyco.py | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index a1fe6bbf732..4ac6372219b 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -1,9 +1,7 @@ import os from collections import namedtuple -import torch - -from modules import devices, sd_models, cache, errors, hashes, shared +from modules import sd_models, cache, errors, hashes, shared NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module']) diff --git a/extensions-builtin/Lora/network_lyco.py b/extensions-builtin/Lora/network_lyco.py index 18a822fab28..fc135314bd7 100644 --- a/extensions-builtin/Lora/network_lyco.py +++ b/extensions-builtin/Lora/network_lyco.py @@ -1,8 +1,4 @@ -import torch - -import lyco_helpers import network -from modules import devices class NetworkModuleLyco(network.NetworkModule): From ef5dac7786916dd39711edb2b8e90ce96ef78fca Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 00:01:17 +0300 Subject: [PATCH 0698/2418] fix --- extensions-builtin/Lora/network_hada.py | 3 --- extensions-builtin/Lora/networks.py | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions-builtin/Lora/network_hada.py b/extensions-builtin/Lora/network_hada.py index 15e7ffd81c3..799bb3bc07b 100644 --- a/extensions-builtin/Lora/network_hada.py +++ b/extensions-builtin/Lora/network_hada.py @@ -27,9 +27,6 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.t1 = weights.w.get("hada_t1") self.t2 = weights.w.get("hada_t2") - self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None - self.scale = weights.w["scale"].item() if "scale" in weights.w else None - def calc_updown(self, orig_weight): w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 5b0ddfb659c..90374faaab4 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -271,6 +271,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 + continue module_q = net.modules.get(network_layer_name + "_q_proj", None) module_k = net.modules.get(network_layer_name + "_k_proj", None) From 58c3df32f3a73b20ea33d1709a1d25818b8a98dd Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 00:12:18 +0300 Subject: [PATCH 0699/2418] IA3 support --- extensions-builtin/Lora/network_ia3.py | 32 ++++++++++++++++++++++++++ extensions-builtin/Lora/networks.py | 2 ++ 2 files changed, 34 insertions(+) create mode 100644 extensions-builtin/Lora/network_ia3.py diff --git a/extensions-builtin/Lora/network_ia3.py b/extensions-builtin/Lora/network_ia3.py new file mode 100644 index 00000000000..99f2307ce48 --- /dev/null +++ b/extensions-builtin/Lora/network_ia3.py @@ -0,0 +1,32 @@ +import lyco_helpers +import network +import network_lyco + + +class ModuleTypeIa3(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["weight"]): + return NetworkModuleIa3(net, weights) + + return None + + +class NetworkModuleIa3(network_lyco.NetworkModuleLyco): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + self.w = weights.w["weight"] + self.on_input = weights.w["on_input"].item() + + def calc_updown(self, orig_weight): + w = self.w.to(orig_weight.device, dtype=orig_weight.dtype) + + output_shape = [w.size(0), orig_weight.size(1)] + if self.on_input: + output_shape.reverse() + else: + w = w.reshape(-1, 1) + + updown = orig_weight * w + + return self.finalize_updown(updown, orig_weight, output_shape) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 90374faaab4..bf810b5b8dc 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -4,6 +4,7 @@ import network import network_lora import network_hada +import network_ia3 import torch from typing import Union @@ -13,6 +14,7 @@ module_types = [ network_lora.ModuleTypeLora(), network_hada.ModuleTypeHada(), + network_ia3.ModuleTypeIa3(), ] From 46466f09d0b0c14118033dee6af0f876059776d3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 00:29:07 +0300 Subject: [PATCH 0700/2418] Lokr support --- extensions-builtin/Lora/network_ia3.py | 1 - extensions-builtin/Lora/network_lokr.py | 65 +++++++++++++++++++++++++ extensions-builtin/Lora/networks.py | 2 + 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 extensions-builtin/Lora/network_lokr.py diff --git a/extensions-builtin/Lora/network_ia3.py b/extensions-builtin/Lora/network_ia3.py index 99f2307ce48..d8806da0206 100644 --- a/extensions-builtin/Lora/network_ia3.py +++ b/extensions-builtin/Lora/network_ia3.py @@ -1,4 +1,3 @@ -import lyco_helpers import network import network_lyco diff --git a/extensions-builtin/Lora/network_lokr.py b/extensions-builtin/Lora/network_lokr.py new file mode 100644 index 00000000000..f17319242dd --- /dev/null +++ b/extensions-builtin/Lora/network_lokr.py @@ -0,0 +1,65 @@ +import torch + +import lyco_helpers +import network +import network_lyco + + +class ModuleTypeLokr(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + has_1 = "lokr_w1" in weights.w or ("lokr_w1a" in weights.w and "lokr_w1b" in weights.w) + has_2 = "lokr_w2" in weights.w or ("lokr_w2a" in weights.w and "lokr_w2b" in weights.w) + if has_1 and has_2: + return NetworkModuleLokr(net, weights) + + return None + + +def make_kron(orig_shape, w1, w2): + if len(w2.shape) == 4: + w1 = w1.unsqueeze(2).unsqueeze(2) + w2 = w2.contiguous() + return torch.kron(w1, w2).reshape(orig_shape) + + +class NetworkModuleLokr(network_lyco.NetworkModuleLyco): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + self.w1 = weights.w.get("lokr_w1") + self.w1a = weights.w.get("lokr_w1_a") + self.w1b = weights.w.get("lokr_w1_b") + self.dim = self.w1b.shape[0] if self.w1b else self.dim + self.w2 = weights.w.get("lokr_w2") + self.w2a = weights.w.get("lokr_w2_a") + self.w2b = weights.w.get("lokr_w2_b") + self.dim = self.w2b.shape[0] if self.w2b else self.dim + self.t2 = weights.w.get("lokr_t2") + + def calc_updown(self, orig_weight): + if self.w1 is not None: + w1 = self.w1.to(orig_weight.device, dtype=orig_weight.dtype) + else: + w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) + w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) + w1 = w1a @ w1b + + if self.w2 is not None: + w2 = self.w2.to(orig_weight.device, dtype=orig_weight.dtype) + elif self.t2 is None: + w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) + w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + w2 = w2a @ w2b + else: + t2 = self.t2.to(orig_weight.device, dtype=orig_weight.dtype) + w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) + w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + w2 = lyco_helpers.make_weight_cp(t2, w2a, w2b) + + output_shape = [w1.size(0) * w2.size(0), w1.size(1) * w2.size(1)] + if len(orig_weight.shape) == 4: + output_shape = orig_weight.shape + + updown = make_kron(output_shape, w1, w2) + + return self.finalize_updown(updown, orig_weight, output_shape) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index bf810b5b8dc..1b358561ca6 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -5,6 +5,7 @@ import network_lora import network_hada import network_ia3 +import network_lokr import torch from typing import Union @@ -15,6 +16,7 @@ network_lora.ModuleTypeLora(), network_hada.ModuleTypeHada(), network_ia3.ModuleTypeIa3(), + network_lokr.ModuleTypeLokr(), ] From 7870937c770aaba9e681c299f923ba645163c85c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:25:29 +0900 Subject: [PATCH 0701/2418] XYZ always_discard_next_to_last_sigma Co-authored-by: Franck Mahon --- scripts/xyz_grid.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 7821cc655cd..ee30747ca7e 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -144,11 +144,18 @@ def apply_face_restore(p, opt, x): p.restore_faces = is_active -def apply_override(field): +def apply_override(field, boolean: bool = False): def fun(p, x, xs): + if boolean: + x = True if x == "True" else False p.override_settings[field] = x return fun + +def boolean_choice(): + return ["True", "False"] + + def format_value_add_label(p, opt, x): if type(x) == float: x = round(x, 8) @@ -235,6 +242,7 @@ def __init__(self, *args, **kwargs): AxisOption("Face restore", str, apply_face_restore, format_value=format_value), AxisOption("Token merging ratio", float, apply_override('token_merging_ratio')), AxisOption("Token merging ratio high-res", float, apply_override('token_merging_ratio_hr')), + AxisOption("Always discard next-to-last sigma", str, apply_override('always_discard_next_to_last_sigma', boolean=True), choices=boolean_choice), ] From c03856bfdf30fd0e061caefd60231eb86a983c71 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:45:10 +0900 Subject: [PATCH 0702/2418] reversible boolean_choice order --- scripts/xyz_grid.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index ee30747ca7e..bddc28c73f2 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -152,8 +152,10 @@ def fun(p, x, xs): return fun -def boolean_choice(): - return ["True", "False"] +def boolean_choice(reverse: bool = False): + def choice(): + return ["False", "True"] if reverse else ["True", "False"] + return choice def format_value_add_label(p, opt, x): @@ -242,7 +244,7 @@ def __init__(self, *args, **kwargs): AxisOption("Face restore", str, apply_face_restore, format_value=format_value), AxisOption("Token merging ratio", float, apply_override('token_merging_ratio')), AxisOption("Token merging ratio high-res", float, apply_override('token_merging_ratio_hr')), - AxisOption("Always discard next-to-last sigma", str, apply_override('always_discard_next_to_last_sigma', boolean=True), choices=boolean_choice), + AxisOption("Always discard next-to-last sigma", str, apply_override('always_discard_next_to_last_sigma', boolean=True), choices=boolean_choice(reverse=True)), ] From 8941297ceb3e71fa16fd842b135786b0ebc1b2b1 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:45:38 +0900 Subject: [PATCH 0703/2418] lowercase --- scripts/xyz_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index bddc28c73f2..1010845e56b 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -147,7 +147,7 @@ def apply_face_restore(p, opt, x): def apply_override(field, boolean: bool = False): def fun(p, x, xs): if boolean: - x = True if x == "True" else False + x = True if x.lower() == "true" else False p.override_settings[field] = x return fun From 238adeaffb037dedbcefe41e7fd4814a1f17baa2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 09:00:47 +0300 Subject: [PATCH 0704/2418] support specifying te and unet weights separately update lora code support full module --- .../Lora/extra_networks_lora.py | 22 ++++-- extensions-builtin/Lora/lyco_helpers.py | 6 ++ extensions-builtin/Lora/network.py | 40 ++++++++++- extensions-builtin/Lora/network_full.py | 23 ++++++ extensions-builtin/Lora/network_hada.py | 3 +- extensions-builtin/Lora/network_ia3.py | 3 +- extensions-builtin/Lora/network_lokr.py | 3 +- extensions-builtin/Lora/network_lora.py | 72 +++++++++++-------- extensions-builtin/Lora/network_lyco.py | 35 --------- extensions-builtin/Lora/networks.py | 22 ++++-- 10 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 extensions-builtin/Lora/network_full.py delete mode 100644 extensions-builtin/Lora/network_lyco.py diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 8a6639cf03e..084c41d0891 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -14,14 +14,28 @@ def activate(self, p, params_list): params_list.append(extra_networks.ExtraNetworkParams(items=[additional, shared.opts.extra_networks_default_multiplier])) names = [] - multipliers = [] + te_multipliers = [] + unet_multipliers = [] + dyn_dims = [] for params in params_list: assert params.items - names.append(params.items[0]) - multipliers.append(float(params.items[1]) if len(params.items) > 1 else 1.0) + names.append(params.positional[0]) - networks.load_networks(names, multipliers) + te_multiplier = float(params.positional[1]) if len(params.positional) > 1 else 1.0 + te_multiplier = float(params.named.get("te", te_multiplier)) + + unet_multiplier = float(params.positional[2]) if len(params.positional) > 2 else 1.0 + unet_multiplier = float(params.named.get("unet", unet_multiplier)) + + dyn_dim = int(params.positional[3]) if len(params.positional) > 3 else None + dyn_dim = int(params.named["dyn"]) if "dyn" in params.named else dyn_dim + + te_multipliers.append(te_multiplier) + unet_multipliers.append(unet_multiplier) + dyn_dims.append(dyn_dim) + + networks.load_networks(names, te_multipliers, unet_multipliers, dyn_dims) if shared.opts.lora_add_hashes_to_infotext: network_hashes = [] diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py index 9ea499fbd0b..279b34bc928 100644 --- a/extensions-builtin/Lora/lyco_helpers.py +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -13,3 +13,9 @@ def rebuild_conventional(up, down, shape, dyn_dim=None): up = up[:, :dyn_dim] down = down[:dyn_dim, :] return (up @ down).reshape(shape) + + +def rebuild_cp_decomposition(up, down, mid): + up = up.reshape(up.size(0), -1) + down = down.reshape(down.size(0), -1) + return torch.einsum('n m k l, i n, m j -> i j k l', mid, up, down) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 4ac6372219b..fe42dbdd40a 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -68,7 +68,9 @@ class Network: # LoraModule def __init__(self, name, network_on_disk: NetworkOnDisk): self.name = name self.network_on_disk = network_on_disk - self.multiplier = 1.0 + self.te_multiplier = 1.0 + self.unet_multiplier = 1.0 + self.dyn_dim = None self.modules = {} self.mtime = None @@ -88,6 +90,42 @@ def __init__(self, net: Network, weights: NetworkWeights): self.sd_key = weights.sd_key self.sd_module = weights.sd_module + if hasattr(self.sd_module, 'weight'): + self.shape = self.sd_module.weight.shape + + self.dim = None + self.bias = weights.w.get("bias") + self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None + self.scale = weights.w["scale"].item() if "scale" in weights.w else None + + def multiplier(self): + if 'transformer' in self.sd_key[:20]: + return self.network.te_multiplier + else: + return self.network.unet_multiplier + + def calc_scale(self): + if self.scale is not None: + return self.scale + if self.dim is not None and self.alpha is not None: + return self.alpha / self.dim + + return 1.0 + + def finalize_updown(self, updown, orig_weight, output_shape): + if self.bias is not None: + updown = updown.reshape(self.bias.shape) + updown += self.bias.to(orig_weight.device, dtype=orig_weight.dtype) + updown = updown.reshape(output_shape) + + if len(output_shape) == 4: + updown = updown.reshape(output_shape) + + if orig_weight.size().numel() == updown.size().numel(): + updown = updown.reshape(orig_weight.shape) + + return updown * self.calc_scale() * self.multiplier() + def calc_updown(self, target): raise NotImplementedError() diff --git a/extensions-builtin/Lora/network_full.py b/extensions-builtin/Lora/network_full.py new file mode 100644 index 00000000000..f0d8a6e05a7 --- /dev/null +++ b/extensions-builtin/Lora/network_full.py @@ -0,0 +1,23 @@ +import lyco_helpers +import network + + +class ModuleTypeFull(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["diff"]): + return NetworkModuleFull(net, weights) + + return None + + +class NetworkModuleFull(network.NetworkModule): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + self.weight = weights.w.get("diff") + + def calc_updown(self, orig_weight): + output_shape = self.weight.shape + updown = self.weight.to(orig_weight.device, dtype=orig_weight.dtype) + + return self.finalize_updown(updown, orig_weight, output_shape) diff --git a/extensions-builtin/Lora/network_hada.py b/extensions-builtin/Lora/network_hada.py index 799bb3bc07b..5fcb0695fbb 100644 --- a/extensions-builtin/Lora/network_hada.py +++ b/extensions-builtin/Lora/network_hada.py @@ -1,6 +1,5 @@ import lyco_helpers import network -import network_lyco class ModuleTypeHada(network.ModuleType): @@ -11,7 +10,7 @@ def create_module(self, net: network.Network, weights: network.NetworkWeights): return None -class NetworkModuleHada(network_lyco.NetworkModuleLyco): +class NetworkModuleHada(network.NetworkModule): def __init__(self, net: network.Network, weights: network.NetworkWeights): super().__init__(net, weights) diff --git a/extensions-builtin/Lora/network_ia3.py b/extensions-builtin/Lora/network_ia3.py index d8806da0206..7edc4249791 100644 --- a/extensions-builtin/Lora/network_ia3.py +++ b/extensions-builtin/Lora/network_ia3.py @@ -1,5 +1,4 @@ import network -import network_lyco class ModuleTypeIa3(network.ModuleType): @@ -10,7 +9,7 @@ def create_module(self, net: network.Network, weights: network.NetworkWeights): return None -class NetworkModuleIa3(network_lyco.NetworkModuleLyco): +class NetworkModuleIa3(network.NetworkModule): def __init__(self, net: network.Network, weights: network.NetworkWeights): super().__init__(net, weights) diff --git a/extensions-builtin/Lora/network_lokr.py b/extensions-builtin/Lora/network_lokr.py index f17319242dd..920062e24d1 100644 --- a/extensions-builtin/Lora/network_lokr.py +++ b/extensions-builtin/Lora/network_lokr.py @@ -2,7 +2,6 @@ import lyco_helpers import network -import network_lyco class ModuleTypeLokr(network.ModuleType): @@ -22,7 +21,7 @@ def make_kron(orig_shape, w1, w2): return torch.kron(w1, w2).reshape(orig_shape) -class NetworkModuleLokr(network_lyco.NetworkModuleLyco): +class NetworkModuleLokr(network.NetworkModule): def __init__(self, net: network.Network, weights: network.NetworkWeights): super().__init__(net, weights) diff --git a/extensions-builtin/Lora/network_lora.py b/extensions-builtin/Lora/network_lora.py index b2d965373fb..26c0a72c237 100644 --- a/extensions-builtin/Lora/network_lora.py +++ b/extensions-builtin/Lora/network_lora.py @@ -1,5 +1,6 @@ import torch +import lyco_helpers import network from modules import devices @@ -16,29 +17,42 @@ class NetworkModuleLora(network.NetworkModule): def __init__(self, net: network.Network, weights: network.NetworkWeights): super().__init__(net, weights) - self.up = self.create_module(weights.w["lora_up.weight"]) - self.down = self.create_module(weights.w["lora_down.weight"]) - self.alpha = weights.w["alpha"] if "alpha" in weights.w else None + self.up_model = self.create_module(weights.w, "lora_up.weight") + self.down_model = self.create_module(weights.w, "lora_down.weight") + self.mid_model = self.create_module(weights.w, "lora_mid.weight", none_ok=True) + + self.dim = weights.w["lora_down.weight"].shape[0] + + def create_module(self, weights, key, none_ok=False): + weight = weights.get(key) - def create_module(self, weight, none_ok=False): if weight is None and none_ok: return None - if type(self.sd_module) == torch.nn.Linear: - module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(self.sd_module) == torch.nn.modules.linear.NonDynamicallyQuantizableLinear: - module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(self.sd_module) == torch.nn.MultiheadAttention: + is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear, torch.nn.MultiheadAttention] + is_conv = type(self.sd_module) in [torch.nn.Conv2d] + + if is_linear: + weight = weight.reshape(weight.shape[0], -1) module = torch.nn.Linear(weight.shape[1], weight.shape[0], bias=False) - elif type(self.sd_module) == torch.nn.Conv2d and weight.shape[2:] == (1, 1): + elif is_conv and key == "lora_down.weight" or key == "dyn_up": + if len(weight.shape) == 2: + weight = weight.reshape(weight.shape[0], -1, 1, 1) + + if weight.shape[2] != 1 or weight.shape[3] != 1: + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], self.sd_module.kernel_size, self.sd_module.stride, self.sd_module.padding, bias=False) + else: + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) + elif is_conv and key == "lora_mid.weight": + module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], self.sd_module.kernel_size, self.sd_module.stride, self.sd_module.padding, bias=False) + elif is_conv and key == "lora_up.weight" or key == "dyn_down": module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (1, 1), bias=False) - elif type(self.sd_module) == torch.nn.Conv2d and weight.shape[2:] == (3, 3): - module = torch.nn.Conv2d(weight.shape[1], weight.shape[0], (3, 3), bias=False) else: - print(f'Network layer {self.network_key} matched a layer with unsupported type: {type(self.sd_module).__name__}') - return None + raise AssertionError(f'Lora layer {self.network_key} matched a layer with unsupported type: {type(self.sd_module).__name__}') with torch.no_grad(): + if weight.shape != module.weight.shape: + weight = weight.reshape(module.weight.shape) module.weight.copy_(weight) module.to(device=devices.cpu, dtype=devices.dtype) @@ -46,25 +60,27 @@ def create_module(self, weight, none_ok=False): return module - def calc_updown(self, target): - up = self.up.weight.to(target.device, dtype=target.dtype) - down = self.down.weight.to(target.device, dtype=target.dtype) + def calc_updown(self, orig_weight): + up = self.up_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) + down = self.down_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) - if up.shape[2:] == (1, 1) and down.shape[2:] == (1, 1): - updown = (up.squeeze(2).squeeze(2) @ down.squeeze(2).squeeze(2)).unsqueeze(2).unsqueeze(3) - elif up.shape[2:] == (3, 3) or down.shape[2:] == (3, 3): - updown = torch.nn.functional.conv2d(down.permute(1, 0, 2, 3), up).permute(1, 0, 2, 3) + output_shape = [up.size(0), down.size(1)] + if self.mid_model is not None: + # cp-decomposition + mid = self.mid_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) + updown = lyco_helpers.rebuild_cp_decomposition(up, down, mid) + output_shape += mid.shape[2:] else: - updown = up @ down - - updown = updown * self.network.multiplier * (self.alpha / self.up.weight.shape[1] if self.alpha else 1.0) + if len(down.shape) == 4: + output_shape += down.shape[2:] + updown = lyco_helpers.rebuild_conventional(up, down, output_shape, self.network.dyn_dim) - return updown + return self.finalize_updown(updown, orig_weight, output_shape) def forward(self, x, y): - self.up.to(device=devices.device) - self.down.to(device=devices.device) + self.up_model.to(device=devices.device) + self.down_model.to(device=devices.device) - return y + self.up(self.down(x)) * self.network.multiplier * (self.alpha / self.up.weight.shape[1] if self.alpha else 1.0) + return y + self.up_model(self.down_model(x)) * self.multiplier() * self.calc_scale() diff --git a/extensions-builtin/Lora/network_lyco.py b/extensions-builtin/Lora/network_lyco.py deleted file mode 100644 index fc135314bd7..00000000000 --- a/extensions-builtin/Lora/network_lyco.py +++ /dev/null @@ -1,35 +0,0 @@ -import network - - -class NetworkModuleLyco(network.NetworkModule): - def __init__(self, net: network.Network, weights: network.NetworkWeights): - super().__init__(net, weights) - - if hasattr(self.sd_module, 'weight'): - self.shape = self.sd_module.weight.shape - - self.dim = None - self.bias = weights.w.get("bias") - self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None - self.scale = weights.w["scale"].item() if "scale" in weights.w else None - - def finalize_updown(self, updown, orig_weight, output_shape): - if self.bias is not None: - updown = updown.reshape(self.bias.shape) - updown += self.bias.to(orig_weight.device, dtype=orig_weight.dtype) - updown = updown.reshape(output_shape) - - if len(output_shape) == 4: - updown = updown.reshape(output_shape) - - if orig_weight.size().numel() == updown.size().numel(): - updown = updown.reshape(orig_weight.shape) - - scale = ( - self.scale if self.scale is not None - else self.alpha / self.dim if self.dim is not None and self.alpha is not None - else 1.0 - ) - - return updown * scale * self.network.multiplier - diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 1b358561ca6..401430e866e 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -6,6 +6,7 @@ import network_hada import network_ia3 import network_lokr +import network_full import torch from typing import Union @@ -17,6 +18,7 @@ network_hada.ModuleTypeHada(), network_ia3.ModuleTypeIa3(), network_lokr.ModuleTypeLokr(), + network_full.ModuleTypeFull(), ] @@ -52,6 +54,15 @@ def match(match_list, regex_text): m = [] + if match(m, r"lora_unet_conv_in(.*)"): + return f'diffusion_model_input_blocks_0_0{m[0]}' + + if match(m, r"lora_unet_conv_out(.*)"): + return f'diffusion_model_out_2{m[0]}' + + if match(m, r"lora_unet_time_embedding_linear_(\d+)(.*)"): + return f"diffusion_model_time_embed_{m[0] * 2 - 2}{m[1]}" + if match(m, r"lora_unet_down_blocks_(\d+)_(attentions|resnets)_(\d+)_(.+)"): suffix = suffix_conversion.get(m[1], {}).get(m[3], m[3]) return f"diffusion_model_input_blocks_{1 + m[0] * 3 + m[2]}_{1 if m[1] == 'attentions' else 0}_{suffix}" @@ -179,7 +190,7 @@ def load_network(name, network_on_disk): return net -def load_networks(names, multipliers=None): +def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=None): already_loaded = {} for net in loaded_networks: @@ -218,7 +229,9 @@ def load_networks(names, multipliers=None): print(f"Couldn't find network with name {name}") continue - net.multiplier = multipliers[i] if multipliers else 1.0 + net.te_multiplier = te_multipliers[i] if te_multipliers else 1.0 + net.unet_multiplier = unet_multipliers[i] if unet_multipliers else 1.0 + net.dyn_dim = dyn_dims[i] if dyn_dims else 1.0 loaded_networks.append(net) if failed_to_load_networks: @@ -250,7 +263,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn return current_names = getattr(self, "network_current_names", ()) - wanted_names = tuple((x.name, x.multiplier) for x in loaded_networks) + wanted_names = tuple((x.name, x.te_multiplier, x.unet_multiplier, x.dyn_dim) for x in loaded_networks) weights_backup = getattr(self, "network_weights_backup", None) if weights_backup is None: @@ -288,9 +301,10 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn 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 += module_out.calc_updown(self.out_proj.weight) + self.out_proj.weight += updown_out continue if module is None: From 2e07a8ae6b1d92838b3a8a0f6eaf5fcf4a92d48f Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 09:05:18 +0300 Subject: [PATCH 0705/2418] some backwards compatibility linter --- extensions-builtin/Lora/lora.py | 9 +++++++++ extensions-builtin/Lora/network_full.py | 1 - extensions-builtin/Lora/scripts/lora_script.py | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 extensions-builtin/Lora/lora.py diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py new file mode 100644 index 00000000000..9365aa74b4f --- /dev/null +++ b/extensions-builtin/Lora/lora.py @@ -0,0 +1,9 @@ +import networks + +list_available_loras = networks.list_available_networks + +available_loras = networks.available_networks +available_lora_aliases = networks.available_network_aliases +available_lora_hash_lookup = networks.available_network_hash_lookup +forbidden_lora_aliases = networks.forbidden_network_aliases +loaded_loras = networks.loaded_networks diff --git a/extensions-builtin/Lora/network_full.py b/extensions-builtin/Lora/network_full.py index f0d8a6e05a7..109b4c2c594 100644 --- a/extensions-builtin/Lora/network_full.py +++ b/extensions-builtin/Lora/network_full.py @@ -1,4 +1,3 @@ -import lyco_helpers import network diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 81e6572a23b..4c75821eff7 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -6,6 +6,7 @@ import network import networks +import lora # noqa:F401 import extra_networks_lora import ui_extra_networks_lora from modules import script_callbacks, ui_extra_networks, extra_networks, shared From 9251ae3bc78e465058c286e86f3c26cb6f819a31 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 09:29:36 +0300 Subject: [PATCH 0706/2418] delay writing cache to prevent writing the same thing over and over --- modules/cache.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/modules/cache.py b/modules/cache.py index ddf44637f40..71fe6302134 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -1,6 +1,7 @@ import json import os.path import threading +import time from modules.paths import data_path, script_path @@ -8,15 +9,37 @@ cache_data = None cache_lock = threading.Lock() +dump_cache_after = None +dump_cache_thread = None + def dump_cache(): """ - Saves all cache data to a file. + Marks cache for writing to disk. 5 seconds after no one else flags the cache for writing, it is written. """ + global dump_cache_after + global dump_cache_thread + + def thread_func(): + global dump_cache_after + global dump_cache_thread + + while dump_cache_after is not None and time.time() < dump_cache_after: + time.sleep(1) + + with cache_lock: + with open(cache_filename, "w", encoding="utf8") as file: + json.dump(cache_data, file, indent=4) + + dump_cache_after = None + dump_cache_thread = None + with cache_lock: - with open(cache_filename, "w", encoding="utf8") as file: - json.dump(cache_data, file, indent=4) + dump_cache_after = time.time() + 5 + if dump_cache_thread is None: + dump_cache_thread = threading.Thread(name='cache-writer', target=thread_func) + dump_cache_thread.start() def cache(subsection): From 35510f7529dc05437a82496187ef06b852be9ab1 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 10:06:02 +0300 Subject: [PATCH 0707/2418] add alias to lyco network read networks from LyCORIS dir if it exists add credits --- README.md | 1 + extensions-builtin/Lora/networks.py | 3 ++- extensions-builtin/Lora/scripts/lora_script.py | 5 ++++- modules/extra_networks.py | 16 ++++++++++++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e6d8e4bd423..b796d150041 100644 --- a/README.md +++ b/README.md @@ -168,5 +168,6 @@ Licenses for borrowed code can be found in `Settings -> Licenses` screen, and al - Security advice - RyotaK - UniPC sampler - Wenliang Zhao - https://github.com/wl-zhao/UniPC - TAESD - Ollin Boer Bohan - https://github.com/madebyollin/taesd +- LyCORIS - KohakuBlueleaf - Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user. - (You) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 401430e866e..7b4c0312399 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -11,7 +11,7 @@ import torch from typing import Union -from modules import shared, devices, sd_models, errors, scripts, sd_hijack +from modules import shared, devices, sd_models, errors, scripts, sd_hijack, paths module_types = [ network_lora.ModuleTypeLora(), @@ -399,6 +399,7 @@ def list_available_networks(): os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + candidates += list(shared.walk_files(os.path.join(paths.models_path, "LyCORIS"), allowed_extensions=[".pt", ".ckpt", ".safetensors"])) for filename in candidates: if os.path.isdir(filename): continue diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index 4c75821eff7..f478f7188d9 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -22,7 +22,10 @@ def unload(): def before_ui(): ui_extra_networks.register_page(ui_extra_networks_lora.ExtraNetworksPageLora()) - extra_networks.register_extra_network(extra_networks_lora.ExtraNetworkLora()) + + extra_network = extra_networks_lora.ExtraNetworkLora() + extra_networks.register_extra_network(extra_network) + extra_networks.register_extra_network_alias(extra_network, "lyco") if not hasattr(torch.nn, 'Linear_forward_before_network'): diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 41799b0a984..6ae07e91b1c 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -4,16 +4,22 @@ from modules import errors extra_network_registry = {} +extra_network_aliases = {} def initialize(): extra_network_registry.clear() + extra_network_aliases.clear() def register_extra_network(extra_network): extra_network_registry[extra_network.name] = extra_network +def register_extra_network_alias(extra_network, alias): + extra_network_aliases[alias] = extra_network + + def register_default_extra_networks(): from modules.extra_networks_hypernet import ExtraNetworkHypernet register_extra_network(ExtraNetworkHypernet()) @@ -82,20 +88,26 @@ def activate(p, extra_network_data): """call activate for extra networks in extra_network_data in specified order, then call activate for all remaining registered networks with an empty argument list""" + activated = [] + for extra_network_name, extra_network_args in extra_network_data.items(): extra_network = extra_network_registry.get(extra_network_name, None) + + if extra_network is None: + extra_network = extra_network_aliases.get(extra_network_name, None) + if extra_network is None: print(f"Skipping unknown extra network: {extra_network_name}") continue try: extra_network.activate(p, extra_network_args) + activated.append(extra_network) except Exception as e: errors.display(e, f"activating extra network {extra_network_name} with arguments {extra_network_args}") for extra_network_name, extra_network in extra_network_registry.items(): - args = extra_network_data.get(extra_network_name, None) - if args is not None: + if extra_network in activated: continue try: From 543ea5730b8c2eea271739cab74bd962b45a4fea Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:15:52 +0900 Subject: [PATCH 0708/2418] fix extra search button --- javascript/extraNetworks.js | 2 +- modules/ui_extra_networks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 1835717b55c..2361144a9ab 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -177,7 +177,7 @@ function saveCardPreview(event, tabname, filename) { } function extraNetworksSearchButton(tabs_id, event) { - var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > div > textarea'); + var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > label > textarea'); var button = event.target; var text = button.classList.contains("search-all") ? "" : button.textContent.trim(); diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index c11f1d5bc8a..b913cb3ef08 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -163,7 +163,7 @@ def create_html(self, tabname): subdirs = {"": 1, **subdirs} subdirs_html = "".join([f""" - """ for subdir in subdirs]) From 95c5c4d64ef7fb67725df7ce048f633a9dd4a9b6 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 11:18:08 +0300 Subject: [PATCH 0709/2418] fix tabs height on small screens --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 88d7135c94c..7157ac0bd50 100644 --- a/style.css +++ b/style.css @@ -773,7 +773,7 @@ footer { } .extra-networks > div.tab-nav{ - height: 3.4rem; + min-height: 3.4rem; } .extra-networks > div > [id *= '_extra_']{ From 05d23c78376ce73d3de932c7e7b8871914295675 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 11:44:29 +0300 Subject: [PATCH 0710/2418] move generate button below the picture for mobile clients --- .../mobile/javascript/mobile.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 extensions-builtin/mobile/javascript/mobile.js diff --git a/extensions-builtin/mobile/javascript/mobile.js b/extensions-builtin/mobile/javascript/mobile.js new file mode 100644 index 00000000000..12cae4b7576 --- /dev/null +++ b/extensions-builtin/mobile/javascript/mobile.js @@ -0,0 +1,26 @@ +var isSetupForMobile = false; + +function isMobile() { + for (var tab of ["txt2img", "img2img"]) { + var imageTab = gradioApp().getElementById(tab + '_results'); + if (imageTab && imageTab.offsetParent && imageTab.offsetLeft == 0) { + return true; + } + } + + return false; +} + +function reportWindowSize() { + var currentlyMobile = isMobile(); + if (currentlyMobile == isSetupForMobile) return; + isSetupForMobile = currentlyMobile; + + for (var tab of ["txt2img", "img2img"]) { + var button = gradioApp().getElementById(tab + '_generate_box'); + var target = gradioApp().getElementById(currentlyMobile ? tab + '_results' : tab + '_actions_column'); + target.insertBefore(button, target.firstElementChild); + } +} + +window.addEventListener("resize", reportWindowSize); From 0dcf6436a868066ada3aaf30f68a3405a24a842c Mon Sep 17 00:00:00 2001 From: wzgrx <39661556+wzgrx@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:49:53 +0800 Subject: [PATCH 0711/2418] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b3f8a7f41fa..0a2fd236670 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ blendmodes clean-fid einops gfpgan -gradio==3.32.0 +gradio==3.36.1 inflection jsonmerge kornia @@ -30,4 +30,4 @@ tomesd torch torchdiffeq torchsde -transformers==4.25.1 +transformers==4.30.2 From 952effa8b10dba2f2f7f2cf4191f987e3666e9f0 Mon Sep 17 00:00:00 2001 From: wzgrx <39661556+wzgrx@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:50:29 +0800 Subject: [PATCH 0712/2418] Update requirements_versions.txt --- requirements_versions.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/requirements_versions.txt b/requirements_versions.txt index b826bf431c1..09c2d292b26 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -1,13 +1,13 @@ -GitPython==3.1.30 +GitPython==3.1.32 Pillow==9.5.0 -accelerate==0.18.0 +accelerate==0.21.0 basicsr==1.4.2 blendmodes==2022 clean-fid==0.1.35 einops==0.4.1 fastapi==0.94.0 gfpgan==1.3.8 -gradio==3.32.0 +gradio==3.36.1 httpcore<=0.15 inflection==0.5.1 jsonmerge==1.8.0 @@ -22,10 +22,11 @@ pytorch_lightning==1.9.4 realesrgan==0.3.0 resize-right==0.0.2 safetensors==0.3.1 -scikit-image==0.20.0 -timm==0.6.7 -tomesd==0.1.2 +scikit-image==0.21.0 +timm==0.9.2 +tomesd==0.1.3 torch torchdiffeq==0.2.3 torchsde==0.2.5 -transformers==4.25.1 +transformers==4.30.2 +diffusers==0.18.2 From 699108bfbb05c2a7d2ee4a2c7abcfaa0a244d8ea Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 18:56:14 +0300 Subject: [PATCH 0713/2418] hide cards for networks of incompatible stable diffusion version in Lora extra networks interface --- extensions-builtin/Lora/network.py | 20 +++++++++++ .../Lora/scripts/lora_script.py | 2 ++ .../Lora/ui_edit_user_metadata.py | 20 ++++++++--- .../Lora/ui_extra_networks_lora.py | 34 ++++++++++++++++--- html/extra-networks-card.html | 2 +- javascript/extraNetworks.js | 2 +- modules/sd_models.py | 3 ++ modules/ui_extra_networks.py | 3 +- modules/ui_extra_networks_user_metadata.py | 7 +++- style.css | 6 +++- 10 files changed, 84 insertions(+), 15 deletions(-) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index fe42dbdd40a..8ecfa29a87a 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -1,5 +1,6 @@ import os from collections import namedtuple +import enum from modules import sd_models, cache, errors, hashes, shared @@ -8,6 +9,13 @@ metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20} +class SdVersion(enum.Enum): + Unknown = 1 + SD1 = 2 + SD2 = 3 + SDXL = 4 + + class NetworkOnDisk: def __init__(self, name, filename): self.name = name @@ -44,6 +52,18 @@ def read_metadata(): '' ) + self.sd_version = self.detect_version() + + def detect_version(self): + if str(self.metadata.get('ss_base_model_version', "")).startswith("sdxl_"): + return SdVersion.SDXL + elif str(self.metadata.get('ss_v2', "")) == "True": + return SdVersion.SD2 + elif len(self.metadata): + return SdVersion.SD1 + + return SdVersion.Unknown + def set_hash(self, v): self.hash = v self.shorthash = self.hash[0:12] diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index f478f7188d9..cd28afc92e7 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -63,6 +63,8 @@ def before_ui(): "sd_lora": shared.OptionInfo("None", "Add network to prompt", gr.Dropdown, lambda: {"choices": ["None", *networks.available_networks]}, refresh=networks.list_available_networks), "lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to Lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}), "lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"), + "lora_show_all": shared.OptionInfo(False, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"), + "lora_hide_unknown_for_versions": shared.OptionInfo([], "Hide networks of unknown versions for model versions", gr.CheckboxGroup, {"choices": ["SD1", "SD2", "SDXL"]}), })) diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 354a1d686c0..c8730443c72 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -46,14 +46,17 @@ class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor) def __init__(self, ui, tabname, page): super().__init__(ui, tabname, page) + self.select_sd_version = None + self.taginfo = None self.edit_activation_text = None self.slider_preferred_weight = None self.edit_notes = None - def save_lora_user_metadata(self, name, desc, activation_text, preferred_weight, notes): + def save_lora_user_metadata(self, name, desc, sd_version, activation_text, preferred_weight, notes): user_metadata = self.get_user_metadata(name) user_metadata["description"] = desc + user_metadata["sd version"] = sd_version user_metadata["activation text"] = activation_text user_metadata["preferred weight"] = preferred_weight user_metadata["notes"] = notes @@ -112,11 +115,11 @@ def put_values_into_components(self, name): gradio_tags = [(tag, str(count)) for tag, count in tags[0:24]] return [ - *values[0:4], + *values[0:5], + item.get("sd_version", "Unknown"), gr.HighlightedText.update(value=gradio_tags, visible=True if tags else False), user_metadata.get('activation text', ''), float(user_metadata.get('preferred weight', 0.0)), - user_metadata.get('notes', ''), gr.update(visible=True if tags else False), gr.update(value=self.generate_random_prompt_from_tags(tags), visible=True if tags else False), ] @@ -141,10 +144,15 @@ def generate_random_prompt_from_tags(self, tags): return ", ".join(sorted(res)) + def create_extra_default_items_in_left_column(self): + + # this would be a lot better as gr.Radio but I can't make it work + self.select_sd_version = gr.Dropdown(['SD1', 'SD2', 'SDXL', 'Unknown'], value='Unknown', label='Stable Diffusion version', interactive=True) + def create_editor(self): self.create_default_editor_elems() - self.taginfo = gr.HighlightedText(label="Tags") + self.taginfo = gr.HighlightedText(label="Training dataset tags") self.edit_activation_text = gr.Text(label='Activation text', info="Will be added to prompt along with Lora") self.slider_preferred_weight = gr.Slider(label='Preferred weight', info="Set to 0 to disable", minimum=0.0, maximum=2.0, step=0.01) @@ -178,10 +186,11 @@ def select_tag(activation_text, evt: gr.SelectData): self.edit_description, self.html_filedata, self.html_preview, + self.edit_notes, + self.select_sd_version, self.taginfo, self.edit_activation_text, self.slider_preferred_weight, - self.edit_notes, row_random_prompt, random_prompt, ] @@ -192,6 +201,7 @@ def select_tag(activation_text, evt: gr.SelectData): edited_components = [ self.edit_description, + self.select_sd_version, self.edit_activation_text, self.slider_preferred_weight, self.edit_notes, diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index b6171a269e5..4b32098b862 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -1,7 +1,9 @@ import os + +import network import networks -from modules import shared, ui_extra_networks +from modules import shared, ui_extra_networks, paths from modules.ui_extra_networks import quote_js from ui_edit_user_metadata import LoraUserMetadataEditor @@ -13,14 +15,13 @@ def __init__(self): def refresh(self): networks.list_available_networks() - def create_item(self, name, index=None): + def create_item(self, name, index=None, enable_filter=True): lora_on_disk = networks.available_networks.get(name) path, ext = os.path.splitext(lora_on_disk.filename) alias = lora_on_disk.get_alias() - # in 1.5 filename changes to be full filename instead of path without extension, and metadata is dict instead of json string item = { "name": name, "filename": lora_on_disk.filename, @@ -30,6 +31,7 @@ def create_item(self, name, index=None): "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)}, + "sd_version": lora_on_disk.sd_version.name, } self.read_user_metadata(item) @@ -40,15 +42,37 @@ def create_item(self, name, index=None): if activation_text: item["prompt"] += " + " + quote_js(" " + activation_text) + sd_version = item["user_metadata"].get("sd version") + if sd_version in network.SdVersion.__members__: + item["sd_version"] = sd_version + sd_version = network.SdVersion[sd_version] + else: + sd_version = lora_on_disk.sd_version + + if shared.opts.lora_show_all or not enable_filter: + pass + elif sd_version == network.SdVersion.Unknown: + model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1 + if model_version.name in shared.opts.lora_hide_unknown_for_versions: + return None + elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: + return None + elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: + return None + elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: + return None + return item def list_items(self): for index, name in enumerate(networks.available_networks): item = self.create_item(name, index) - yield item + + if item is not None: + yield item def allowed_directories_for_previews(self): - return [shared.cmd_opts.lora_dir] + return [shared.cmd_opts.lora_dir, os.path.join(paths.models_path, "LyCORIS")] def create_user_metadata_editor(self, ui, tabname): return LoraUserMetadataEditor(ui, tabname, self) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index eb8b1a67262..39674666f1e 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,8 +1,8 @@
    {background_image}
    - {edit_button} {metadata_button} + {edit_button}
    diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index e453094a1e7..5582a6e5d3b 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -213,7 +213,7 @@ function popup(contents) { globalPopupInner.classList.add('global-popup-inner'); globalPopup.appendChild(globalPopupInner); - gradioApp().appendChild(globalPopup); + gradioApp().querySelector('.main').appendChild(globalPopup); } globalPopupInner.innerHTML = ''; diff --git a/modules/sd_models.py b/modules/sd_models.py index 729f03d7f94..4d9382dd80f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -290,6 +290,9 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer state_dict = get_checkpoint_state_dict(checkpoint_info, timer) model.is_sdxl = hasattr(model, 'conditioner') + model.is_sd2 = not model.is_sdxl and hasattr(model.cond_stage_model, 'model') + model.is_sd1 = not model.is_sdxl and not model.is_sd2 + if model.is_sdxl: sd_models_xl.extend_sdxl(model) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 6c73998f903..496122980b5 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -62,7 +62,8 @@ def get_single_card(page: str = "", tabname: str = "", name: str = ""): page = next(iter([x for x in extra_pages if x.name == page]), None) try: - item = page.create_item(name) + item = page.create_item(name, enable_filter=False) + page.items[name] = item except Exception as e: errors.display(e, "creating item for extra network") item = page.items.get(name) diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 01ff4e4b470..63d4b5031f6 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -42,6 +42,9 @@ def get_user_metadata(self, name): return user_metadata + def create_extra_default_items_in_left_column(self): + pass + def create_default_editor_elems(self): with gr.Row(): with gr.Column(scale=2): @@ -49,6 +52,8 @@ def create_default_editor_elems(self): self.edit_description = gr.Textbox(label="Description", lines=4) self.html_filedata = gr.HTML() + self.create_extra_default_items_in_left_column() + with gr.Column(scale=1, min_width=0): self.html_preview = gr.HTML() @@ -111,7 +116,7 @@ def put_values_into_components(self, name): table = '' + "".join(f"" for name, value in params) + '' - return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name), user_metadata.get('notes', ''), + return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name), user_metadata.get('notes', '') def write_user_metadata(self, name, metadata): item = self.page.items.get(name, {}) diff --git a/style.css b/style.css index 8a66c3d2130..e249cfd3dc7 100644 --- a/style.css +++ b/style.css @@ -841,7 +841,7 @@ footer { .extra-network-cards .card .card-button { text-shadow: 2px 2px 3px black; - padding: 0.25em; + padding: 0.25em 0.1em; font-size: 200%; width: 1.5em; } @@ -957,6 +957,10 @@ div.block.gradio-box.edit-user-metadata { text-align: left; } +.edit-user-metadata .file-metadata th, .edit-user-metadata .file-metadata td{ + padding: 0.3em 1em; +} + .edit-user-metadata .wrap.translucent{ background: var(--body-background-fill); } From a99d5708e6d603e8f7cfd1b8c6595f8026219ba0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 17 Jul 2023 20:10:24 +0300 Subject: [PATCH 0714/2418] skip installing packages with pip if theyare already installed record time it took to launch --- modules/launch_utils.py | 46 ++++++++++++++++++++++++++++++++++++++- requirements_versions.txt | 4 ++-- webui.py | 9 ++++---- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 434facbc968..03552bc2683 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -1,4 +1,5 @@ # this scripts installs necessary requirements and launches main program in webui.py +import re import subprocess import os import sys @@ -9,6 +10,9 @@ from modules import cmd_args, errors from modules.paths_internal import script_path, extensions_dir +from modules import timer + +timer.startup_timer.record("start") args, _ = cmd_args.parser.parse_known_args() @@ -226,6 +230,44 @@ def run_extensions_installers(settings_file): run_extension_installer(os.path.join(extensions_dir, dirname_extension)) +re_requirement = re.compile(r"\s*([-_a-zA-Z0-9]+)\s*(?:==\s*([-+_.a-zA-Z0-9]+))?\s*") + + +def requrements_met(requirements_file): + """ + Does a simple parse of a requirements.txt file to determine if all rerqirements in it + are already installed. Returns True if so, False if not installed or parsing fails. + """ + + import importlib.metadata + import packaging.version + + with open(requirements_file, "r", encoding="utf8") as file: + for line in file: + if line.strip() == "": + continue + + m = re.match(re_requirement, line) + if m is None: + return False + + package = m.group(1).strip() + version_required = (m.group(2) or "").strip() + + if version_required == "": + continue + + try: + version_installed = importlib.metadata.version(package) + except Exception: + return False + + if packaging.version.parse(version_required) != packaging.version.parse(version_installed): + return False + + return True + + def prepare_environment(): torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118") torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}") @@ -311,7 +353,9 @@ def prepare_environment(): if not os.path.isfile(requirements_file): requirements_file = os.path.join(script_path, requirements_file) - run_pip(f"install -r \"{requirements_file}\"", "requirements") + + if not requrements_met(requirements_file): + run_pip(f"install -r \"{requirements_file}\"", "requirements") run_extensions_installers(settings_file=args.ui_settings_file) diff --git a/requirements_versions.txt b/requirements_versions.txt index b826bf431c1..d07ab456ca9 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -8,7 +8,7 @@ einops==0.4.1 fastapi==0.94.0 gfpgan==1.3.8 gradio==3.32.0 -httpcore<=0.15 +httpcore==0.15 inflection==0.5.1 jsonmerge==1.8.0 kornia==0.6.7 @@ -17,7 +17,7 @@ numpy==1.23.5 omegaconf==2.2.3 open-clip-torch==2.20.0 piexif==1.1.3 -psutil~=5.9.5 +psutil==5.9.5 pytorch_lightning==1.9.4 realesrgan==0.3.0 resize-right==0.0.2 diff --git a/webui.py b/webui.py index 34c2fd18130..2aafc09f8a2 100644 --- a/webui.py +++ b/webui.py @@ -31,21 +31,22 @@ logging.getLogger("torch.distributed.nn").setLevel(logging.ERROR) # sshh... logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) -from modules import paths, timer, import_hook, errors, devices # noqa: F401 - +from modules import timer startup_timer = timer.startup_timer +startup_timer.record("launcher") import torch import pytorch_lightning # noqa: F401 # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them warnings.filterwarnings(action="ignore", category=DeprecationWarning, module="pytorch_lightning") warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvision") - - startup_timer.record("import torch") import gradio # noqa: F401 startup_timer.record("import gradio") +from modules import paths, timer, import_hook, errors, devices # noqa: F401 +startup_timer.record("setup paths") + import ldm.modules.encoders.modules # noqa: F401 startup_timer.record("import ldm") From 17e14ed2d9451859325d275ccc6cdf51fc85a56d Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:23:41 +0800 Subject: [PATCH 0715/2418] Fix wrong key name in lokr module --- extensions-builtin/Lora/network_lokr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network_lokr.py b/extensions-builtin/Lora/network_lokr.py index 920062e24d1..3a94f3e9fc3 100644 --- a/extensions-builtin/Lora/network_lokr.py +++ b/extensions-builtin/Lora/network_lokr.py @@ -6,8 +6,8 @@ class ModuleTypeLokr(network.ModuleType): def create_module(self, net: network.Network, weights: network.NetworkWeights): - has_1 = "lokr_w1" in weights.w or ("lokr_w1a" in weights.w and "lokr_w1b" in weights.w) - has_2 = "lokr_w2" in weights.w or ("lokr_w2a" in weights.w and "lokr_w2b" in weights.w) + has_1 = "lokr_w1" in weights.w or ("lokr_w1_a" in weights.w and "lokr_w1_b" in weights.w) + has_2 = "lokr_w2" in weights.w or ("lokr_w2_a" in weights.w and "lokr_w2_b" in weights.w) if has_1 and has_2: return NetworkModuleLokr(net, weights) From 3d31caf4a53c4bb4469b72790b459eba7b251da9 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:45:42 +0800 Subject: [PATCH 0716/2418] use "is not None" for Tensor --- extensions-builtin/Lora/network_lokr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network_lokr.py b/extensions-builtin/Lora/network_lokr.py index 3a94f3e9fc3..340acdab3d0 100644 --- a/extensions-builtin/Lora/network_lokr.py +++ b/extensions-builtin/Lora/network_lokr.py @@ -28,11 +28,11 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.w1 = weights.w.get("lokr_w1") self.w1a = weights.w.get("lokr_w1_a") self.w1b = weights.w.get("lokr_w1_b") - self.dim = self.w1b.shape[0] if self.w1b else self.dim + self.dim = self.w1b.shape[0] if self.w1b is not None else self.dim self.w2 = weights.w.get("lokr_w2") self.w2a = weights.w.get("lokr_w2_a") self.w2b = weights.w.get("lokr_w2_b") - self.dim = self.w2b.shape[0] if self.w2b else self.dim + self.dim = self.w2b.shape[0] if self.w2b is not None else self.dim self.t2 = weights.w.get("lokr_t2") def calc_updown(self, orig_weight): From 40a18d38a8fcb88d1c2947a2653b52cd2085536f Mon Sep 17 00:00:00 2001 From: lambertae Date: Tue, 18 Jul 2023 00:32:01 -0400 Subject: [PATCH 0717/2418] add restart sampler --- modules/sd_samplers_kdiffusion.py | 70 ++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 71581b76359..c63b677c4b0 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,3 +1,5 @@ +# export PIP_CACHE_DIR=/scratch/dengm/cache +# export XDG_CACHE_HOME=/scratch/dengm/cache from collections import deque import torch import inspect @@ -30,12 +32,76 @@ ('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 Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), + ('Restart (new)', 'restart_sampler', ['restart'], {'scheduler': 'karras', "second_order": True}), ] + +@torch.no_grad() +def restart_sampler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_noise=1., restart_list = {0.1: [10, 2, 2]}): + """Implements restart sampling in Restart Sampling for Improving Generative Processes (2023)""" + '''Restart_list format: {min_sigma: [ restart_steps, restart_times, max_sigma]}''' + + from tqdm.auto import trange, tqdm + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + step_id = 0 + + from k_diffusion.sampling import to_d, append_zero + + def heun_step(x, old_sigma, new_sigma): + nonlocal step_id + denoised = model(x, old_sigma * s_in, **extra_args) + d = to_d(x, old_sigma, denoised) + if callback is not None: + callback({'x': x, 'i': step_id, 'sigma': new_sigma, 'sigma_hat': old_sigma, 'denoised': denoised}) + dt = new_sigma - old_sigma + if new_sigma == 0: + # Euler method + x = x + d * dt + else: + # Heun's method + x_2 = x + d * dt + denoised_2 = model(x_2, new_sigma * s_in, **extra_args) + d_2 = to_d(x_2, new_sigma, denoised_2) + d_prime = (d + d_2) / 2 + x = x + d_prime * dt + step_id += 1 + return x + # print(sigmas) + temp_list = dict() + for key, value in restart_list.items(): + temp_list[int(torch.argmin(abs(sigmas - key), dim=0))] = value + restart_list = temp_list + + + def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): + ramp = torch.linspace(0, 1, n).to(device) + min_inv_rho = (sigma_min ** (1 / rho)) + max_inv_rho = (sigma_max ** (1 / rho)) + if isinstance(min_inv_rho, torch.Tensor): + min_inv_rho = min_inv_rho.to(device) + if isinstance(max_inv_rho, torch.Tensor): + max_inv_rho = max_inv_rho.to(device) + sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho + return append_zero(sigmas).to(device) + + for i in trange(len(sigmas) - 1, disable=disable): + x = heun_step(x, sigmas[i], sigmas[i+1]) + if i + 1 in restart_list: + restart_steps, restart_times, restart_max = restart_list[i + 1] + min_idx = i + 1 + max_idx = int(torch.argmin(abs(sigmas - restart_max), dim=0)) + sigma_restart = get_sigmas_karras(restart_steps, sigmas[min_idx], sigmas[max_idx], device=sigmas.device)[:-1] # remove the zero at the end + for times in range(restart_times): + x = x + torch.randn_like(x) * s_noise * (sigmas[max_idx] ** 2 - sigmas[min_idx] ** 2) ** 0.5 + for (old_sigma, new_sigma) in zip(sigma_restart[:-1], sigma_restart[1:]): + x = heun_step(x, old_sigma, new_sigma) + return x + samplers_data_k_diffusion = [ sd_samplers_common.SamplerData(label, lambda model, funcname=funcname: KDiffusionSampler(funcname, model), aliases, options) for label, funcname, aliases, options in samplers_k_diffusion - if hasattr(k_diffusion.sampling, funcname) + if (hasattr(k_diffusion.sampling, funcname) or funcname == 'restart_sampler') ] sampler_extra_params = { @@ -245,7 +311,7 @@ def __init__(self, funcname, sd_model): self.model_wrap = denoiser(sd_model, quantize=shared.opts.enable_quantization) self.funcname = funcname - self.func = getattr(k_diffusion.sampling, self.funcname) + self.func = getattr(k_diffusion.sampling, self.funcname) if funcname != "restart_sampler" else restart_sampler self.extra_params = sampler_extra_params.get(funcname, []) self.model_wrap_cfg = CFGDenoiser(self.model_wrap) self.sampler_noises = None From 15a94d6cf7fa075c09362e73c1239692d021c559 Mon Sep 17 00:00:00 2001 From: lambertae Date: Tue, 18 Jul 2023 00:39:26 -0400 Subject: [PATCH 0718/2418] remove useless header --- modules/sd_samplers_kdiffusion.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index c63b677c4b0..7888d864c40 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,5 +1,3 @@ -# export PIP_CACHE_DIR=/scratch/dengm/cache -# export XDG_CACHE_HOME=/scratch/dengm/cache from collections import deque import torch import inspect From f0e2098f1a533c88396536282c1d6cd7d847a51c Mon Sep 17 00:00:00 2001 From: brkirch Date: Mon, 17 Jul 2023 23:39:38 -0400 Subject: [PATCH 0719/2418] Add support for `--upcast-sampling` with SD XL --- modules/sd_hijack_unet.py | 8 +++++++- modules/sd_models.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index ca1daf45f3c..2101f1a0415 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -39,7 +39,10 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): if isinstance(cond, dict): for y in cond.keys(): - cond[y] = [x.to(devices.dtype_unet) if isinstance(x, torch.Tensor) else x for x in cond[y]] + if isinstance(cond[y], list): + cond[y] = [x.to(devices.dtype_unet) if isinstance(x, torch.Tensor) else x for x in cond[y]] + else: + cond[y] = cond[y].to(devices.dtype_unet) if isinstance(cond[y], torch.Tensor) else cond[y] with devices.autocast(): return orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs).float() @@ -77,3 +80,6 @@ def hijack_ddpm_edit(): CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) + +CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model, unet_needs_upcast) +CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) diff --git a/modules/sd_models.py b/modules/sd_models.py index 4d9382dd80f..5813b550220 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -326,7 +326,7 @@ def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer timer.record("apply half()") - devices.dtype_unet = model.model.diffusion_model.dtype + devices.dtype_unet = torch.float16 if model.is_sdxl and not shared.cmd_opts.no_half else model.model.diffusion_model.dtype devices.unet_needs_upcast = shared.cmd_opts.upcast_sampling and devices.dtype == torch.float16 and devices.dtype_unet == torch.float16 model.first_stage_model.to(devices.dtype_vae) From 37e048a7e2356f4caebfd976351112f03856f082 Mon Sep 17 00:00:00 2001 From: lambertae Date: Tue, 18 Jul 2023 00:55:02 -0400 Subject: [PATCH 0720/2418] fix floating error --- modules/sd_samplers_kdiffusion.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 7888d864c40..1bb25adf3ba 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -89,11 +89,12 @@ def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): restart_steps, restart_times, restart_max = restart_list[i + 1] min_idx = i + 1 max_idx = int(torch.argmin(abs(sigmas - restart_max), dim=0)) - sigma_restart = get_sigmas_karras(restart_steps, sigmas[min_idx], sigmas[max_idx], device=sigmas.device)[:-1] # remove the zero at the end - for times in range(restart_times): - x = x + torch.randn_like(x) * s_noise * (sigmas[max_idx] ** 2 - sigmas[min_idx] ** 2) ** 0.5 - for (old_sigma, new_sigma) in zip(sigma_restart[:-1], sigma_restart[1:]): - x = heun_step(x, old_sigma, new_sigma) + if max_idx < min_idx: + sigma_restart = get_sigmas_karras(restart_steps, sigmas[min_idx], sigmas[max_idx], device=sigmas.device)[:-1] # remove the zero at the end + for times in range(restart_times): + x = x + torch.randn_like(x) * s_noise * (sigmas[max_idx] ** 2 - sigmas[min_idx] ** 2) ** 0.5 + for (old_sigma, new_sigma) in zip(sigma_restart[:-1], sigma_restart[1:]): + x = heun_step(x, old_sigma, new_sigma) return x samplers_data_k_diffusion = [ From 7bb0fbed136c6a345b211e09102659fd89362576 Mon Sep 17 00:00:00 2001 From: lambertae Date: Tue, 18 Jul 2023 01:02:04 -0400 Subject: [PATCH 0721/2418] code styling --- modules/sd_samplers_kdiffusion.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 1bb25adf3ba..db7013f24cc 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -35,17 +35,15 @@ @torch.no_grad() -def restart_sampler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_noise=1., restart_list = {0.1: [10, 2, 2]}): +def restart_sampler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_noise=1.): """Implements restart sampling in Restart Sampling for Improving Generative Processes (2023)""" '''Restart_list format: {min_sigma: [ restart_steps, restart_times, max_sigma]}''' - - from tqdm.auto import trange, tqdm + restart_list = {0.1: [10, 2, 2]} + from tqdm.auto import trange extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) step_id = 0 - from k_diffusion.sampling import to_d, append_zero - def heun_step(x, old_sigma, new_sigma): nonlocal step_id denoised = model(x, old_sigma * s_in, **extra_args) @@ -70,8 +68,6 @@ def heun_step(x, old_sigma, new_sigma): for key, value in restart_list.items(): temp_list[int(torch.argmin(abs(sigmas - key), dim=0))] = value restart_list = temp_list - - def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): ramp = torch.linspace(0, 1, n).to(device) min_inv_rho = (sigma_min ** (1 / rho)) @@ -82,7 +78,6 @@ def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): max_inv_rho = max_inv_rho.to(device) sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho return append_zero(sigmas).to(device) - for i in trange(len(sigmas) - 1, disable=disable): x = heun_step(x, sigmas[i], sigmas[i+1]) if i + 1 in restart_list: @@ -91,7 +86,8 @@ def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): max_idx = int(torch.argmin(abs(sigmas - restart_max), dim=0)) if max_idx < min_idx: sigma_restart = get_sigmas_karras(restart_steps, sigmas[min_idx], sigmas[max_idx], device=sigmas.device)[:-1] # remove the zero at the end - for times in range(restart_times): + while restart_times > 0: + restart_times -= 1 x = x + torch.randn_like(x) * s_noise * (sigmas[max_idx] ** 2 - sigmas[min_idx] ** 2) ** 0.5 for (old_sigma, new_sigma) in zip(sigma_restart[:-1], sigma_restart[1:]): x = heun_step(x, old_sigma, new_sigma) From d6668347c8b85b11b696ac56777cc396e34ee1f9 Mon Sep 17 00:00:00 2001 From: Leon Feng Date: Tue, 18 Jul 2023 04:19:58 -0400 Subject: [PATCH 0722/2418] remove duplicate --- modules/textual_inversion/logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/logging.py index 734a4b6f463..a822a7a5c49 100644 --- a/modules/textual_inversion/logging.py +++ b/modules/textual_inversion/logging.py @@ -2,7 +2,7 @@ import json import os -saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "clip_grad_mode", "clip_grad_value", "gradient_step", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file", "gradient_step", "latent_sampling_method"} +saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "clip_grad_mode", "clip_grad_value", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file", "gradient_step", "latent_sampling_method"} saved_params_ti = {"embedding_name", "num_vectors_per_token", "save_embedding_every", "save_image_with_stored_embedding"} saved_params_hypernet = {"hypernetwork_name", "layer_structure", "activation_func", "weight_init", "add_layer_norm", "use_dropout", "save_hypernetwork_every"} saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet From 420cc8f68e6aca8a3a0f42ee0e626a6b03712763 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 11:48:40 +0300 Subject: [PATCH 0723/2418] also make None a valid option for options API for #11854 --- modules/api/models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/api/models.py b/modules/api/models.py index b568307141f..b55fa72880f 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -1,4 +1,6 @@ import inspect +import types + from pydantic import BaseModel, Field, create_model from typing import Any, Optional from typing_extensions import Literal @@ -207,11 +209,14 @@ class PreprocessResponse(BaseModel): fields = {} for key, metadata in opts.data_labels.items(): value = opts.data.get(key) + if key == 'sd_model_checkpoint': + value = None optType = opts.typemap.get(type(metadata.default), type(value)) - if (metadata is not None): - fields.update({key: (Optional[optType], Field( - default=metadata.default ,description=metadata.label))}) + if optType == types.NoneType: + pass + elif metadata is not None: + fields.update({key: (Optional[optType], Field(default=metadata.default, description=metadata.label))}) else: fields.update({key: (Optional[optType], Field())}) From 3c570421d3a2eb24528b5f5bb615dcb0c7717e4a Mon Sep 17 00:00:00 2001 From: wfjsw Date: Tue, 18 Jul 2023 19:00:16 +0800 Subject: [PATCH 0724/2418] move start timer --- launch.py | 4 +++- modules/api/models.py | 2 +- modules/launch_utils.py | 3 --- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/launch.py b/launch.py index b103c8f3a2e..e9667c88efb 100644 --- a/launch.py +++ b/launch.py @@ -1,4 +1,4 @@ -from modules import launch_utils +from modules import launch_utils, timer args = launch_utils.args @@ -25,6 +25,8 @@ def main(): + timer.startup_timer.record("start") + if not args.skip_prepare_environment: prepare_environment() diff --git a/modules/api/models.py b/modules/api/models.py index b55fa72880f..96cfe920ebd 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -213,7 +213,7 @@ class PreprocessResponse(BaseModel): value = None optType = opts.typemap.get(type(metadata.default), type(value)) - if optType == types.NoneType: + if isinstance(optType, types.NoneType): pass elif metadata is not None: fields.update({key: (Optional[optType], Field(default=metadata.default, description=metadata.label))}) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 03552bc2683..ea995edafb6 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -10,9 +10,6 @@ from modules import cmd_args, errors from modules.paths_internal import script_path, extensions_dir -from modules import timer - -timer.startup_timer.record("start") args, _ = cmd_args.parser.parse_known_args() From ed82f1c5f1677c85298f4d2c6c030a5551682c71 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 15:55:23 +0300 Subject: [PATCH 0725/2418] lint --- 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 b55fa72880f..96cfe920ebd 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -213,7 +213,7 @@ class PreprocessResponse(BaseModel): value = None optType = opts.typemap.get(type(metadata.default), type(value)) - if optType == types.NoneType: + if isinstance(optType, types.NoneType): pass elif metadata is not None: fields.update({key: (Optional[optType], Field(default=metadata.default, description=metadata.label))}) From 4b5a63aa1135757ef9db58b15f5426e758d285d0 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 17:32:46 +0300 Subject: [PATCH 0726/2418] add a bit more metadata info for the lora user metadata page --- extensions-builtin/Lora/ui_edit_user_metadata.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index c8730443c72..2ca997f7ce9 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -1,3 +1,4 @@ +import datetime import html import random @@ -71,6 +72,7 @@ def get_metadata_table(self, name): keys = { 'ss_sd_model_name': "Model:", 'ss_clip_skip': "Clip skip:", + 'ss_network_module': "Kohya module:", } for key, label in keys.items(): @@ -78,6 +80,10 @@ def get_metadata_table(self, name): if value is not None and str(value) != "None": table.append((label, html.escape(value))) + ss_training_started_at = metadata.get('ss_training_started_at') + if ss_training_started_at: + table.append(("Date trained:", datetime.datetime.utcfromtimestamp(float(ss_training_started_at)).strftime('%Y-%m-%d %H:%M'))) + ss_bucket_info = metadata.get("ss_bucket_info") if ss_bucket_info and "buckets" in ss_bucket_info: resolutions = {} From 66c5f1bb1556a2d86d9f11aeb92f83d4a09832cc Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 17:41:37 +0300 Subject: [PATCH 0727/2418] return sd_model_checkpoint to None --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index a256d090771..6162938a974 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -409,7 +409,7 @@ def list_samplers(): })) options_templates.update(options_section(('sd', "Stable Diffusion"), { - "sd_model_checkpoint": OptionInfo("", "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_checkpoint_cache": OptionInfo(0, "Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae_checkpoint_cache": OptionInfo(0, "VAE Checkpoints to cache in RAM", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae": OptionInfo("Automatic", "SD VAE", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list).info("choose VAE model: Automatic = use one with same filename as checkpoint; None = use VAE from checkpoint"), From be16d274f85a59b742f7480cdb79a596c972f598 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 17:44:56 +0300 Subject: [PATCH 0728/2418] changelog for 1.5.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30783d9db36..007010da78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * SD XL support * user metadata system for custom networks * extended Lora metadata editor: set activation text, default weight, view tags, training info + * Lora extension rework to include other types of networks (all that were previously handled by LyCORIS extension) * show github stars for extenstions * img2img batch mode can read extra stuff from png info * img2img batch works with subdirectories @@ -11,6 +12,9 @@ * restyle time taken/VRAM display * add textual inversion hashes to infotext * optimization: cache git extension repo information + * move generate button next to the generated picture for mobile clients + * hide cards for networks of incompatible Stable Diffusion version in Lora extra networks interface + * skip installing packages with pip if they all are already installed - startup speedup of about 2 seconds ### Minor: * checkbox to check/uncheck all extensions in the Installed tab @@ -25,6 +29,7 @@ * speedup extra networks listing * added `[none]` filename token. * removed thumbs extra networks view mode (use settings tab to change width/height/scale to get thumbs) + * add always_discard_next_to_last_sigma option to XYZ plot ### Extensions and API: * api endpoints: /sdapi/v1/server-kill, /sdapi/v1/server-restart, /sdapi/v1/server-stop From b270ded268c92950a35a7a326da54496ef4151c8 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 18:10:04 +0300 Subject: [PATCH 0729/2418] fix the issue with /sdapi/v1/options failing (this time for sure!) fix automated tests downloading CLIP model --- .github/workflows/run_tests.yaml | 1 + modules/api/models.py | 6 ++---- modules/cmd_args.py | 1 + modules/sd_models.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index e9370cc0758..3dafaf8dcfc 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -41,6 +41,7 @@ jobs: --skip-prepare-environment --skip-torch-cuda-test --test-server + --do-not-download-clip --no-half --disable-opt-split-attention --use-cpu all diff --git a/modules/api/models.py b/modules/api/models.py index 96cfe920ebd..4cd20a92976 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -209,11 +209,9 @@ class PreprocessResponse(BaseModel): fields = {} for key, metadata in opts.data_labels.items(): value = opts.data.get(key) - if key == 'sd_model_checkpoint': - value = None - optType = opts.typemap.get(type(metadata.default), type(value)) + optType = opts.typemap.get(type(metadata.default), type(metadata.default)) - if isinstance(optType, types.NoneType): + if metadata.default is None: pass elif metadata is not None: fields.update({key: (Optional[optType], Field(default=metadata.default, description=metadata.label))}) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index ae78f469efa..e401f6413a4 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -15,6 +15,7 @@ parser.add_argument("--test-server", action='store_true', help="launch.py argument: configure server for testing") parser.add_argument("--skip-prepare-environment", action='store_true', help="launch.py argument: skip all environment preparation") parser.add_argument("--skip-install", action='store_true', help="launch.py argument: skip installation of packages") +parser.add_argument("--do-not-download-clip", action='store_true', help="do not download CLIP model even if it's not included in the checkpoint") parser.add_argument("--data-dir", type=str, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored") parser.add_argument("--config", type=str, default=sd_default_config, help="path to config which constructs model",) parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",) diff --git a/modules/sd_models.py b/modules/sd_models.py index 5813b550220..fb31a7937b9 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -494,7 +494,7 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): sd_model = None try: - with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): + with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd or shared.cmd_opts.do_not_download_clip): sd_model = instantiate_from_config(sd_config.model) except Exception: pass From 7f7db1700bda40ba3171a49b6a4ef38f868b7d0a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 18:16:23 +0300 Subject: [PATCH 0730/2418] linter fix --- modules/api/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index 4cd20a92976..bf97b1a3771 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -1,5 +1,4 @@ import inspect -import types from pydantic import BaseModel, Field, create_model from typing import Any, Optional From 136c8859a49a35cbffe269aafc0bbdfca0b3561d Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Tue, 18 Jul 2023 20:11:30 +0300 Subject: [PATCH 0731/2418] add backwards compatibility --lyco-dir-backcompat option, use that for LyCORIS directory instead of hardcoded value prevent running preload.py for disabled extensions --- CHANGELOG.md | 4 +--- extensions-builtin/Lora/networks.py | 4 ++-- extensions-builtin/Lora/preload.py | 1 + extensions-builtin/Lora/ui_extra_networks_lora.py | 4 ++-- launch.py | 1 + modules/script_loading.py | 5 +++-- modules/shared.py | 3 ++- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 007010da78e..792529ece32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,9 +58,7 @@ * fix: check fill size none zero when resize (fixes #11425) * use submit and blur for quick settings textbox * save img2img batch with images.save_image() - * - - + * prevent running preload.py for disabled extensions ## 1.4.1 diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 7b4c0312399..af8188e3608 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -11,7 +11,7 @@ import torch from typing import Union -from modules import shared, devices, sd_models, errors, scripts, sd_hijack, paths +from modules import shared, devices, sd_models, errors, scripts, sd_hijack module_types = [ network_lora.ModuleTypeLora(), @@ -399,7 +399,7 @@ def list_available_networks(): os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) - candidates += list(shared.walk_files(os.path.join(paths.models_path, "LyCORIS"), allowed_extensions=[".pt", ".ckpt", ".safetensors"])) + candidates += list(shared.walk_files(shared.cmd_opts.lyco_dir_backcompat, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) for filename in candidates: if os.path.isdir(filename): continue diff --git a/extensions-builtin/Lora/preload.py b/extensions-builtin/Lora/preload.py index 863dc5c0b51..50961be33d7 100644 --- a/extensions-builtin/Lora/preload.py +++ b/extensions-builtin/Lora/preload.py @@ -4,3 +4,4 @@ def preload(parser): parser.add_argument("--lora-dir", type=str, help="Path to directory with Lora networks.", default=os.path.join(paths.models_path, 'Lora')) + parser.add_argument("--lyco-dir-backcompat", type=str, help="Path to directory with LyCORIS networks (for backawards compatibility; can also use --lyco-dir).", default=os.path.join(paths.models_path, 'LyCORIS')) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 4b32098b862..3629e5c0cf2 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -3,7 +3,7 @@ import network import networks -from modules import shared, ui_extra_networks, paths +from modules import shared, ui_extra_networks from modules.ui_extra_networks import quote_js from ui_edit_user_metadata import LoraUserMetadataEditor @@ -72,7 +72,7 @@ def list_items(self): yield item def allowed_directories_for_previews(self): - return [shared.cmd_opts.lora_dir, os.path.join(paths.models_path, "LyCORIS")] + return [shared.cmd_opts.lora_dir, shared.cmd_opts.lyco_dir_backcompat] def create_user_metadata_editor(self, ui, tabname): return LoraUserMetadataEditor(ui, tabname, self) diff --git a/launch.py b/launch.py index b103c8f3a2e..1dbc4c6e33e 100644 --- a/launch.py +++ b/launch.py @@ -18,6 +18,7 @@ check_run_python = launch_utils.check_run_python git_clone = launch_utils.git_clone git_pull_recursive = launch_utils.git_pull_recursive +list_extensions = launch_utils.list_extensions run_extension_installer = launch_utils.run_extension_installer prepare_environment = launch_utils.prepare_environment configure_for_tests = launch_utils.configure_for_tests diff --git a/modules/script_loading.py b/modules/script_loading.py index 306a1f35f77..0d55f1932ee 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -12,11 +12,12 @@ def load_module(path): return module -def preload_extensions(extensions_dir, parser): +def preload_extensions(extensions_dir, parser, extension_list=None): if not os.path.isdir(extensions_dir): return - for dirname in sorted(os.listdir(extensions_dir)): + extensions = extension_list if extension_list is not None else os.listdir(extensions_dir) + for dirname in sorted(extensions): preload_script = os.path.join(extensions_dir, dirname, "preload.py") if not os.path.isfile(preload_script): continue diff --git a/modules/shared.py b/modules/shared.py index 6162938a974..1ce7b49e5dd 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -11,6 +11,7 @@ import torch import tqdm +import launch import modules.interrogate import modules.memmon import modules.styles @@ -26,7 +27,7 @@ parser = cmd_args.parser -script_loading.preload_extensions(extensions_dir, parser) +script_loading.preload_extensions(extensions_dir, parser, extension_list=launch.list_extensions(launch.args.ui_settings_file)) script_loading.preload_extensions(extensions_builtin_dir, parser) if os.environ.get('IGNORE_CMD_ARGS_ERRORS', None) is None: From 2b42f73e3d198f7d21de3bba9253fdcd4cc0d76d Mon Sep 17 00:00:00 2001 From: kopyl Date: Tue, 18 Jul 2023 22:43:18 +0300 Subject: [PATCH 0732/2418] Make possible to install web ui without venv with --novenv flag When passing `--novenv` flag to webui.sh it can skip venv. Might be useful for installing in Docker since messing with venv in Docker might be a bit complicated. Example usage: `webui.sh --novenv` Hope this gets approved and pushed into future versions of Web UI --- webui.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/webui.sh b/webui.sh index 5c8d977cb2d..9ec14168c68 100755 --- a/webui.sh +++ b/webui.sh @@ -4,6 +4,11 @@ # change the variables in webui-user.sh instead # ################################################# +usevenv=1 +if [[ $@ == *"--novenv"* ]]; then + usevenv=0 +fi + # If run from macOS, load defaults from webui-macos-env.sh if [[ "$OSTYPE" == "darwin"* ]]; then if [[ -f webui-macos-env.sh ]] @@ -45,7 +50,7 @@ then fi # python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) -if [[ -z "${venv_dir}" ]] +if [[ -z "${venv_dir}" ]] && [[ $usevenv -eq 1 ]] then venv_dir="venv" fi @@ -158,7 +163,7 @@ do fi done -if ! "${python_cmd}" -c "import venv" &>/dev/null +if [[ $usevenv -eq 1 ]] && ! "${python_cmd}" -c "import venv" &>/dev/null then printf "\n%s\n" "${delimiter}" printf "\e[1m\e[31mERROR: python3-venv is not installed, aborting...\e[0m" @@ -178,7 +183,7 @@ else cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } fi -if [[ -z "${VIRTUAL_ENV}" ]]; +if [[ $usevenv -eq 1 ]] && [[ -z "${VIRTUAL_ENV}" ]]; then printf "\n%s\n" "${delimiter}" printf "Create and activate python venv" @@ -201,7 +206,7 @@ then fi else printf "\n%s\n" "${delimiter}" - printf "python venv already activate: ${VIRTUAL_ENV}" + printf "python venv already activate or run without venv: ${VIRTUAL_ENV}" printf "\n%s\n" "${delimiter}" fi From c278e60131d34b58069c91d441e60a5d87f14a22 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 19 Jul 2023 04:58:30 +0900 Subject: [PATCH 0733/2418] add dropdown extra_sort_order lable --- modules/ui_extra_networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index b913cb3ef08..7387d01e1e7 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -380,7 +380,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) edit_search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) - dropdown_sort = gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True) + dropdown_sort = gr.Dropdown(choices=['Default Sort', 'Date Created', 'Date Modified', 'Name'], value='Default Sort', elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") button_sortorder = ToolButton(up_down_symbol, elem_id=tabname+"_extra_sortorder", elem_classes="sortorder", visible=False) button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) From b010eea520caa90d2a31d98ec7c4a9d9d540c9ad Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 19 Jul 2023 00:41:00 +0300 Subject: [PATCH 0734/2418] fix incorrect multiplier for Loras --- 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 084c41d0891..ba2945c6fe1 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -25,7 +25,7 @@ def activate(self, p, params_list): te_multiplier = float(params.positional[1]) if len(params.positional) > 1 else 1.0 te_multiplier = float(params.named.get("te", te_multiplier)) - unet_multiplier = float(params.positional[2]) if len(params.positional) > 2 else 1.0 + unet_multiplier = float(params.positional[2]) if len(params.positional) > 2 else te_multiplier unet_multiplier = float(params.named.get("unet", unet_multiplier)) dyn_dim = int(params.positional[3]) if len(params.positional) > 3 else None From 0c4ca5f43e5e74aa1e7f65b3b6c20a676e158328 Mon Sep 17 00:00:00 2001 From: kopyl Date: Wed, 19 Jul 2023 01:47:39 +0300 Subject: [PATCH 0735/2418] Replace argument with env variable --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index 9ec14168c68..5b39244b89b 100755 --- a/webui.sh +++ b/webui.sh @@ -5,7 +5,7 @@ ################################################# usevenv=1 -if [[ $@ == *"--novenv"* ]]; then +if [[ $venv_dir == "-" ]]; then usevenv=0 fi From 6094310704f4b3853bfa5d05d9c1ace58b2deee7 Mon Sep 17 00:00:00 2001 From: kopyl Date: Wed, 19 Jul 2023 01:48:21 +0300 Subject: [PATCH 0736/2418] improve var naming --- webui.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webui.sh b/webui.sh index 5b39244b89b..ed4cb4c89e0 100755 --- a/webui.sh +++ b/webui.sh @@ -4,9 +4,9 @@ # change the variables in webui-user.sh instead # ################################################# -usevenv=1 +use_venv=1 if [[ $venv_dir == "-" ]]; then - usevenv=0 + use_venv=0 fi # If run from macOS, load defaults from webui-macos-env.sh @@ -50,7 +50,7 @@ then fi # python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) -if [[ -z "${venv_dir}" ]] && [[ $usevenv -eq 1 ]] +if [[ -z "${venv_dir}" ]] && [[ $use_venv -eq 1 ]] then venv_dir="venv" fi @@ -163,7 +163,7 @@ do fi done -if [[ $usevenv -eq 1 ]] && ! "${python_cmd}" -c "import venv" &>/dev/null +if [[ $use_venv -eq 1 ]] && ! "${python_cmd}" -c "import venv" &>/dev/null then printf "\n%s\n" "${delimiter}" printf "\e[1m\e[31mERROR: python3-venv is not installed, aborting...\e[0m" @@ -183,7 +183,7 @@ else cd "${clone_dir}"/ || { printf "\e[1m\e[31mERROR: Can't cd to %s/%s/, aborting...\e[0m" "${install_dir}" "${clone_dir}"; exit 1; } fi -if [[ $usevenv -eq 1 ]] && [[ -z "${VIRTUAL_ENV}" ]]; +if [[ $use_venv -eq 1 ]] && [[ -z "${VIRTUAL_ENV}" ]]; then printf "\n%s\n" "${delimiter}" printf "Create and activate python venv" From c8b55f29e2838e67bd9e394f5dbca4350ccbb68f Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:27:19 +0900 Subject: [PATCH 0737/2418] missing p save_image before-highres-fix --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 6567b3cfbdb..b89ca5c250d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1029,7 +1029,7 @@ def save_intermediate(image, index): image = sd_samplers.sample_to_image(image, index, approximation=0) info = create_infotext(self, self.all_prompts, self.all_seeds, self.all_subseeds, [], iteration=self.iteration, position_in_batch=index) - images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], opts.samples_format, info=info, suffix="-before-highres-fix") + images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], opts.samples_format, info=info, p=self, suffix="-before-highres-fix") if latent_scale_mode is not None: for i in range(samples.shape[0]): From cb7573489670cc7a042d24285e158b797c9558b2 Mon Sep 17 00:00:00 2001 From: yfzhou Date: Wed, 19 Jul 2023 17:53:28 +0800 Subject: [PATCH 0738/2418] =?UTF-8?q?=E3=80=90bug=E3=80=91reload=20altclip?= =?UTF-8?q?=20model=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using BertSeriesModelWithTransformation as the cond_stage_model, the undo_hijack should be performed using the FrozenXLMREmbedderWithCustomWords type; otherwise, it will result in a failed model reload. --- modules/sd_hijack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 3b6f95ce2ea..928233aba96 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -203,7 +203,7 @@ def flatten(el): ldm.modules.diffusionmodules.openaimodel.UNetModel.forward = sd_unet.UNetModel_forward def undo_hijack(self, m): - if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: + if type(m.cond_stage_model) == sd_hijack_xlmr.FrozenXLMREmbedderWithCustomWords: m.cond_stage_model = m.cond_stage_model.wrapped elif type(m.cond_stage_model) == sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords: From 05ccb4d0e3ff38503a2863889e21290e77590ee2 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Wed, 19 Jul 2023 15:49:31 +0300 Subject: [PATCH 0739/2418] bugfix: model name was added together with directory name to infotext and to [model_name] filename pattern --- CHANGELOG.md | 1 + modules/images.py | 2 +- modules/processing.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792529ece32..a561252c251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ * use submit and blur for quick settings textbox * save img2img batch with images.save_image() * prevent running preload.py for disabled extensions + * fix: previously, model name was added together with directory name to infotext and to [model_name] filename pattern; directory name is now not included ## 1.4.1 diff --git a/modules/images.py b/modules/images.py index fb5d2e750a1..38aa933d6e5 100644 --- a/modules/images.py +++ b/modules/images.py @@ -363,7 +363,7 @@ def get_vae_filename(self): #get the name of the VAE file. '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), '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.model_name, replace_spaces=False), + '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'), 'datetime': lambda self, *args: self.datetime(*args), # accepts formats: [datetime], [datetime], [datetime
    " + + return res + + def create_card_view_html(self, tabname): + items_html = "" + self.metadata = {} subdirs = {} + for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])): for dirname in sorted(dirs, key=shared.natural_sort_key): @@ -171,40 +354,45 @@ def create_html(self, tabname): if subdirs: subdirs = {"": 1, **subdirs} - subdirs_html = "".join([f""" - -""" for subdir in subdirs]) + subdirs_html_template = ( + "" + ) + subdirs_html = "".join( + [ + subdirs_html_template.format( + " search-all" if subdir == "" else "", + tabname, + html.escape(subdir if subdir != "" else "all"), + ) for subdir in subdirs + ] + ) self.items = {x["name"]: x for x in self.list_items()} for item in self.items.values(): - metadata = item.get("metadata") - if metadata: - self.metadata[item["name"]] = metadata - - if "user_metadata" not in item: - self.read_user_metadata(item) - - items_html += self.create_html_for_item(item, tabname) + items_html += self.create_item_html(tabname, item) - if items_html == '': + if items_html == "": dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) items_html = shared.html("extra-networks-no-cards.html").format(dirs=dirs) self_name_id = self.name.replace(" ", "_") - res = f""" -
    -{subdirs_html} -
    -
    -{items_html} -
    -""" + res = ( + f"
    {subdirs_html}
    " + f"
    {items_html}
    " + ) return res + def create_html(self, tabname): + if shared.opts.extra_networks_tree_view: + return self.create_tree_view_html(tabname) + else: + return self.create_card_view_html(tabname) + def create_item(self, name, index=None): raise NotImplementedError() @@ -214,66 +402,6 @@ def list_items(self): def allowed_directories_for_previews(self): return [] - def create_html_for_item(self, item, tabname): - """ - Create HTML for card item in tab tabname; can return empty string if the item is not meant to be shown. - """ - - preview = item.get("preview", None) - - onclick = item.get("onclick", None) - if onclick is None: - onclick = '"' + html.escape(f"""return cardClicked({quote_js(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' - - height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else '' - width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else '' - background_image = f'' if preview else '' - metadata_button = "" - metadata = item.get("metadata") - if metadata: - metadata_button = f"" - - edit_button = f"
    " - - local_path = "" - filename = item.get("filename", "") - for reldir in self.allowed_directories_for_previews(): - absdir = os.path.abspath(reldir) - - if filename.startswith(absdir): - local_path = filename[len(absdir):] - - # if this is true, the item must not be shown in the default view, and must instead only be - # shown when searching for it - if shared.opts.extra_networks_hidden_models == "Always": - search_only = False - else: - search_only = "/." in local_path or "\\." in local_path - - if search_only and shared.opts.extra_networks_hidden_models == "Never": - return "" - - sort_keys = " ".join([f'data-sort-{k}="{html.escape(str(v))}"' for k, v in item.get("sort_keys", {}).items()]).strip() - - args = { - "background_image": background_image, - "style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", - "prompt": item.get("prompt", None), - "tabname": quote_js(tabname), - "local_preview": quote_js(item["local_preview"]), - "name": html.escape(item["name"]), - "description": (item.get("description") or "" if shared.opts.extra_networks_card_show_desc else ""), - "card_clicked": onclick, - "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', - "search_term": item.get("search_term", ""), - "metadata_button": metadata_button, - "edit_button": edit_button, - "search_only": " search_only" if search_only else "", - "sort_keys": sort_keys, - } - - return self.card_page.format(**args) - def get_sort_keys(self, path): """ List of default keys used for sorting in the UI. @@ -360,7 +488,6 @@ def tab_name_score(name): return sorted(pages, key=lambda x: tab_scores[x.name]) - def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): from modules.ui import switch_values_symbol @@ -381,7 +508,6 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): elem_id = f"{tabname}_{page.id_page}_cards_html" page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) - page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + quote_js(tabname) + '); return []}', inputs=[], outputs=[]) editor = page.create_user_metadata_editor(ui, tabname) @@ -390,30 +516,60 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) + tab_controls = {} + edit_search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) dropdown_sort = gr.Dropdown(choices=['Path', 'Name', 'Date Created', 'Date Modified', ], value=shared.opts.extra_networks_card_order_field, elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) checkbox_show_dirs = gr.Checkbox(True, label='Show dirs', elem_id=tabname+"_extra_show_dirs", elem_classes="show-dirs", visible=False) + + tab_controls["edit_search"] = edit_search + tab_controls["dropdown_sort"] = dropdown_sort + tab_controls["button_sortorder"] = button_sortorder + tab_controls["button_refresh"] = button_refresh + tab_controls["checkbox_show_dirs"] = checkbox_show_dirs ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) - tab_controls = [edit_search, dropdown_sort, button_sortorder, button_refresh, checkbox_show_dirs] - for tab in unrelated_tabs: - tab.select(fn=lambda: [gr.update(visible=False) for _ in tab_controls], _js='function(){ extraNetworksUrelatedTabSelected("' + tabname + '"); }', inputs=[], outputs=tab_controls, show_progress=False) + tab.select( + fn=lambda: [gr.update(visible=False) for _ in tab_controls], + _js="function(){ extraNetworksUrelatedTabSelected('" + tabname + "'); }", + inputs=[], + outputs=list(tab_controls.values()), + show_progress=False, + ) + + visible_controls = list(tab_controls.keys()) + if shared.opts.extra_networks_tree_view: + visible_controls = ["button_refresh"] for page, tab in zip(ui.stored_extra_pages, related_tabs): allow_prompt = "true" if page.allow_prompt else "false" allow_negative_prompt = "true" if page.allow_negative_prompt else "false" - jscode = 'extraNetworksTabSelected("' + tabname + '", "' + f"{tabname}_{page.id_page}_prompts" + '", ' + allow_prompt + ', ' + allow_negative_prompt + ');' - - tab.select(fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js='function(){ ' + jscode + ' }', inputs=[], outputs=tab_controls, show_progress=False) + jscode = ( + "extraNetworksTabSelected(" + f"'{tabname}', " + f"'{tabname}_{page.id_page}_prompts', " + f"'{allow_prompt}', " + f"'{allow_negative_prompt}'" + ");" + ) + + tab.select( + fn=lambda: [gr.update(visible=k in visible_controls) for k in tab_controls], + _js="function(){ " + jscode + " }", + inputs=[], + outputs=list(tab_controls.values()), + show_progress=False, + ) dropdown_sort.change(fn=lambda: None, _js="function(){ applyExtraNetworkSort('" + tabname + "'); }") + def pages_html(): if not ui.pages_contents: return refresh() @@ -478,5 +634,3 @@ def save_preview(index, images, filename): for editor in ui.user_metadata_editors: editor.setup_ui(gallery) - - diff --git a/style.css b/style.css index ee39a57b73e..680a5f837b7 100644 --- a/style.css +++ b/style.css @@ -1157,3 +1157,95 @@ body.resizing .resize-handle { left: 7.5px; border-left: 1px dashed var(--border-color-primary); } + +.extra-network-cards .card .copy-path-button:before { + content: "⎘"; +} + +.extra-network-cards .card-minimal .button-column { + display: inline-flex; + visibility: hidden; + color: white; + padding-left: 0.5rem; + padding-right: 0.5rem; + align-items: center; +} + +.extra-network-cards .card-minimal:hover .button-column { + visibility: visible; +} + +.extra-network-cards .card-minimal .copy-path-button:before { + content: "⎘"; +} + +.extra-network-cards .card-minimal .metadata-button:before{ + content: "🛈"; +} + +.extra-network-cards .card-minimal .edit-button:before{ + content: "🛠"; +} + +.extra-network-cards .card-minimal .card-button { + color: white; + text-shadow: 2px 2px 3px black; + font-size: 1rem; + width: 1.5rem; +} + +.extra-network-cards .card-minimal .card-button:hover { + color: red; +} + +.extra-network-cards .card-minimal { + display: inline-flex; + position: relative; + overflow: hidden; + cursor: pointer; + font-size: 1rem; + font-weight: bold; + line-break: anywhere; +} + +.file-item { + list-style-type: '📄'; +} + +/* prevents clicking/collapsing of details tags when disabled attribute is used*/ +details[disabled] summary { + pointer-events: none; + user-select: none; +} + +details.folder-item > summary { + list-style-type: '📁'; +} + +details.folder-item[open] > summary { + list-style-type: '📂'; +} + +.file-item, +.folder-item, +.folder-item-summary { + display: block; + font-size: 1rem; + padding: 0.05rem; + cursor: pointer; + user-select: none; +} + +.folder-item-summary:hover, +.file-item:hover { + -webkit-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; + background-color: var(--neutral-200); +} + +.dark .folder-item-summary:hover, +.dark .file-item:hover { + -webkit-transition: all 0.05s ease-in-out; + transition: all 0.05s ease-in-out; + background-color: var(--neutral-800); +} From 67a70ad112e1b0fb262852ab830896302dbd306a Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Mon, 8 Jan 2024 14:11:24 -0500 Subject: [PATCH 1804/2418] fix indentation --- html/extra-networks-card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index d76011d737c..7770094da14 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,7 +1,7 @@
    {background_image}
    - {copy_path_button} + {copy_path_button} {metadata_button} {edit_button}
    From 34fc215249e2bc0acc66cda47319e40b6e46a05f Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Mon, 8 Jan 2024 14:23:01 -0500 Subject: [PATCH 1805/2418] fix linting --- modules/ui_extra_networks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 8667617b9f5..ab484a5dc0b 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -216,7 +216,7 @@ def create_item_html(self, tabname: str, item: dict) -> str: onclick = item.get("onclick", None) if onclick is None: onclick = '"' + html.escape(f"""return cardClicked({quote_js(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"' - + copy_path_button = f"
    " metadata_button = "" @@ -523,7 +523,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) checkbox_show_dirs = gr.Checkbox(True, label='Show dirs', elem_id=tabname+"_extra_show_dirs", elem_classes="show-dirs", visible=False) - + tab_controls["edit_search"] = edit_search tab_controls["dropdown_sort"] = dropdown_sort tab_controls["button_sortorder"] = button_sortorder @@ -560,7 +560,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ) tab.select( - fn=lambda: [gr.update(visible=k in visible_controls) for k in tab_controls], + fn=lambda: [gr.update(visible=k in visible_controls) for k in tab_controls], _js="function(){ " + jscode + " }", inputs=[], outputs=list(tab_controls.values()), From 46413b20a4d88355b5fac5d27cc9fcb634cdb49e Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Mon, 8 Jan 2024 16:23:35 -0500 Subject: [PATCH 1806/2418] Increase limits for upscalers. --- modules/api/models.py | 2 +- scripts/postprocessing_upscale.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/api/models.py b/modules/api/models.py index 33894b3e694..11ba4f0b952 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -143,7 +143,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", ge=1, 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?") diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index ed709688de4..5946678e5be 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -21,13 +21,13 @@ def ui(self): with FormRow(): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: - upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") + upscaling_resize = gr.Slider(minimum=1.0, maximum=100.0, step=0.05, label="Resize", value=2, elem_id="extras_upscaling_resize") with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): with gr.Column(elem_id="upscaling_column_size", scale=4): - upscaling_resize_w = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="extras_upscaling_resize_w") - upscaling_resize_h = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="extras_upscaling_resize_h") + upscaling_resize_w = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id="extras_upscaling_resize_w") + upscaling_resize_h = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id="extras_upscaling_resize_h") with gr.Column(elem_id="upscaling_dimensions_row", scale=1, elem_classes="dimensions-tools"): upscaling_res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="upscaling_res_switch_btn", tooltip="Switch width/height") upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop") From 8d986727b39ee6616190559efa1c41c1942b99b0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 9 Jan 2024 03:01:20 -0600 Subject: [PATCH 1807/2418] include tls arguments in api uvicorn init --- modules/api/api.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/api/api.py b/modules/api/api.py index 9d1292e9571..59e46335231 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -879,7 +879,15 @@ def get_extensions_list(self): def launch(self, server_name, port, root_path): self.app.include_router(self.router) - uvicorn.run(self.app, host=server_name, port=port, timeout_keep_alive=shared.cmd_opts.timeout_keep_alive, root_path=root_path) + uvicorn.run( + self.app, + host=server_name, + port=port, + timeout_keep_alive=shared.cmd_opts.timeout_keep_alive, + root_path=root_path, + ssl_keyfile=shared.cmd_opts.tls_keyfile, + ssl_certfile=shared.cmd_opts.tls_certfile + ) def kill_webui(self): restart.stop_program() From 209c26a1cb9e4be357ab3c5e7613caf3cbc26183 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:11:44 +0800 Subject: [PATCH 1808/2418] improve efficiency and support more device --- modules/devices.py | 60 ++++++++++++++++++++++++++++++------------ modules/shared_init.py | 1 + 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index ff279ac5016..6edfb127896 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -110,6 +110,7 @@ def enable_tf32(): dtype: torch.dtype = torch.float16 dtype_vae: torch.dtype = torch.float16 dtype_unet: torch.dtype = torch.float16 +dtype_inference: torch.dtype = torch.float16 unet_needs_upcast = False @@ -131,21 +132,49 @@ def cond_cast_float(input): ] -def manual_cast_forward(self, *args, **kwargs): - org_dtype = torch_utils.get_param(self).dtype - self.to(dtype) - args = [arg.to(dtype) if isinstance(arg, torch.Tensor) else arg for arg in args] - kwargs = {k: v.to(dtype) if isinstance(v, torch.Tensor) else v for k, v in kwargs.items()} - result = self.org_forward(*args, **kwargs) - self.to(org_dtype) - return result +def manual_cast_forward(target_dtype): + def forward_wrapper(self, *args, **kwargs): + org_dtype = torch_utils.get_param(self).dtype + if not target_dtype == org_dtype == dtype_inference: + self.to(target_dtype) + args = [ + arg.to(target_dtype) + if isinstance(arg, torch.Tensor) + else arg + for arg in args + ] + kwargs = { + k: v.to(target_dtype) + if isinstance(v, torch.Tensor) + else v + for k, v in kwargs.items() + } + + result = self.org_forward(*args, **kwargs) + self.to(org_dtype) + + if target_dtype != dtype_inference: + if isinstance(result, tuple): + result = tuple( + i.to(dtype_inference) + if isinstance(i, torch.Tensor) + else i + for i in result + ) + elif isinstance(result, torch.Tensor): + result = result.to(dtype_inference) + return result + return forward_wrapper @contextlib.contextmanager -def manual_cast(): +def manual_cast(target_dtype): for module_type in patch_module_list: org_forward = module_type.forward - module_type.forward = manual_cast_forward + if module_type == torch.nn.MultiheadAttention and has_xpu(): + module_type.forward = manual_cast_forward(torch.float32) + else: + module_type.forward = manual_cast_forward(target_dtype) module_type.org_forward = org_forward try: yield None @@ -161,15 +190,12 @@ def autocast(disable=False): if fp8 and device==cpu: return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True) - if fp8 and (dtype == torch.float32 or shared.cmd_opts.precision == "full" or cuda_no_autocast()): - return manual_cast() - - if has_mps() and shared.cmd_opts.precision != "full": - return manual_cast() - - if dtype == torch.float32 or shared.cmd_opts.precision == "full": + if dtype == torch.float32 and shared.cmd_opts.precision == "full": return contextlib.nullcontext() + if has_xpu() or has_mps() or cuda_no_autocast(): + return manual_cast(dtype_inference) + return torch.autocast("cuda") diff --git a/modules/shared_init.py b/modules/shared_init.py index 586be3423d0..935e3a21cf2 100644 --- a/modules/shared_init.py +++ b/modules/shared_init.py @@ -29,6 +29,7 @@ def initialize(): devices.dtype = torch.float32 if cmd_opts.no_half else torch.float16 devices.dtype_vae = torch.float32 if cmd_opts.no_half or cmd_opts.no_half_vae else torch.float16 + devices.dtype_inference = torch.float32 if cmd_opts.precision == 'full' else devices.dtype shared.device = devices.device shared.weight_load_location = None if cmd_opts.lowram else "cpu" From 42e6df723c68af775b73c9fa4f43f99345348689 Mon Sep 17 00:00:00 2001 From: KohakuBlueleaf Date: Tue, 9 Jan 2024 22:39:39 +0800 Subject: [PATCH 1809/2418] Fix bugs when arg dtype doesn't match --- modules/devices.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 6edfb127896..e05740524f3 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -134,24 +134,19 @@ def cond_cast_float(input): def manual_cast_forward(target_dtype): def forward_wrapper(self, *args, **kwargs): + if any( + isinstance(arg, torch.Tensor) and arg.dtype != target_dtype + for arg in args + ): + args = [arg.to(target_dtype) if isinstance(arg, torch.Tensor) else arg for arg in args] + kwargs = {k: v.to(target_dtype) if isinstance(v, torch.Tensor) else v for k, v in kwargs.items()} + org_dtype = torch_utils.get_param(self).dtype - if not target_dtype == org_dtype == dtype_inference: + if org_dtype != target_dtype: self.to(target_dtype) - args = [ - arg.to(target_dtype) - if isinstance(arg, torch.Tensor) - else arg - for arg in args - ] - kwargs = { - k: v.to(target_dtype) - if isinstance(v, torch.Tensor) - else v - for k, v in kwargs.items() - } - result = self.org_forward(*args, **kwargs) - self.to(org_dtype) + if org_dtype != target_dtype: + self.to(org_dtype) if target_dtype != dtype_inference: if isinstance(result, tuple): From c2c05fcca8f3547783c5440c04ec10cc63c65db5 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:53:58 +0800 Subject: [PATCH 1810/2418] linting and debugs --- modules/devices.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index e05740524f3..ad36f6562bc 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -140,20 +140,20 @@ def forward_wrapper(self, *args, **kwargs): ): args = [arg.to(target_dtype) if isinstance(arg, torch.Tensor) else arg for arg in args] kwargs = {k: v.to(target_dtype) if isinstance(v, torch.Tensor) else v for k, v in kwargs.items()} - + org_dtype = torch_utils.get_param(self).dtype if org_dtype != target_dtype: self.to(target_dtype) result = self.org_forward(*args, **kwargs) if org_dtype != target_dtype: self.to(org_dtype) - + if target_dtype != dtype_inference: if isinstance(result, tuple): result = tuple( - i.to(dtype_inference) - if isinstance(i, torch.Tensor) - else i + i.to(dtype_inference) + if isinstance(i, torch.Tensor) + else i for i in result ) elif isinstance(result, torch.Tensor): @@ -185,7 +185,7 @@ def autocast(disable=False): if fp8 and device==cpu: return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True) - if dtype == torch.float32 and shared.cmd_opts.precision == "full": + if dtype == torch.float32: return contextlib.nullcontext() if has_xpu() or has_mps() or cuda_no_autocast(): From e00365962b17550a42235d1fbe2ad2c7cc4b8961 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:13:34 +0800 Subject: [PATCH 1811/2418] Apply correct inference precision implementation --- modules/devices.py | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index ad36f6562bc..9e1f207c336 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -132,6 +132,21 @@ def cond_cast_float(input): ] +def cast_output(result): + if isinstance(result, tuple): + result = tuple(i.to(dtype_inference) if isinstance(i, torch.Tensor) else i for i in result) + elif isinstance(result, torch.Tensor): + result = result.to(dtype_inference) + return result + + +def autocast_with_cast_output(self, *args, **kwargs): + result = self.org_forward(*args, **kwargs) + if dtype_inference != dtype: + result = cast_output(result) + return result + + def manual_cast_forward(target_dtype): def forward_wrapper(self, *args, **kwargs): if any( @@ -149,15 +164,7 @@ def forward_wrapper(self, *args, **kwargs): self.to(org_dtype) if target_dtype != dtype_inference: - if isinstance(result, tuple): - result = tuple( - i.to(dtype_inference) - if isinstance(i, torch.Tensor) - else i - for i in result - ) - elif isinstance(result, torch.Tensor): - result = result.to(dtype_inference) + result = cast_output(result) return result return forward_wrapper @@ -178,6 +185,20 @@ def manual_cast(target_dtype): module_type.forward = module_type.org_forward +@contextlib.contextmanager +def precision_full_with_autocast(autocast_ctx): + for module_type in patch_module_list: + org_forward = module_type.forward + module_type.forward = autocast_with_cast_output + module_type.org_forward = org_forward + try: + with autocast_ctx: + yield None + finally: + for module_type in patch_module_list: + module_type.forward = module_type.org_forward + + def autocast(disable=False): if disable: return contextlib.nullcontext() @@ -191,6 +212,9 @@ def autocast(disable=False): if has_xpu() or has_mps() or cuda_no_autocast(): return manual_cast(dtype_inference) + if dtype_inference == torch.float32 and dtype != torch.float32: + return precision_full_with_autocast(torch.autocast("cuda")) + return torch.autocast("cuda") From 1fd69655fe340325863cbd7bf5297e034a6a3a0a Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:15:05 +0800 Subject: [PATCH 1812/2418] Revert "Apply correct inference precision implementation" This reverts commit e00365962b17550a42235d1fbe2ad2c7cc4b8961. --- modules/devices.py | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 9e1f207c336..ad36f6562bc 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -132,21 +132,6 @@ def cond_cast_float(input): ] -def cast_output(result): - if isinstance(result, tuple): - result = tuple(i.to(dtype_inference) if isinstance(i, torch.Tensor) else i for i in result) - elif isinstance(result, torch.Tensor): - result = result.to(dtype_inference) - return result - - -def autocast_with_cast_output(self, *args, **kwargs): - result = self.org_forward(*args, **kwargs) - if dtype_inference != dtype: - result = cast_output(result) - return result - - def manual_cast_forward(target_dtype): def forward_wrapper(self, *args, **kwargs): if any( @@ -164,7 +149,15 @@ def forward_wrapper(self, *args, **kwargs): self.to(org_dtype) if target_dtype != dtype_inference: - result = cast_output(result) + if isinstance(result, tuple): + result = tuple( + i.to(dtype_inference) + if isinstance(i, torch.Tensor) + else i + for i in result + ) + elif isinstance(result, torch.Tensor): + result = result.to(dtype_inference) return result return forward_wrapper @@ -185,20 +178,6 @@ def manual_cast(target_dtype): module_type.forward = module_type.org_forward -@contextlib.contextmanager -def precision_full_with_autocast(autocast_ctx): - for module_type in patch_module_list: - org_forward = module_type.forward - module_type.forward = autocast_with_cast_output - module_type.org_forward = org_forward - try: - with autocast_ctx: - yield None - finally: - for module_type in patch_module_list: - module_type.forward = module_type.org_forward - - def autocast(disable=False): if disable: return contextlib.nullcontext() @@ -212,9 +191,6 @@ def autocast(disable=False): if has_xpu() or has_mps() or cuda_no_autocast(): return manual_cast(dtype_inference) - if dtype_inference == torch.float32 and dtype != torch.float32: - return precision_full_with_autocast(torch.autocast("cuda")) - return torch.autocast("cuda") From 58d5b042cd02f287faabef399134b97d323691f2 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:23:40 +0800 Subject: [PATCH 1813/2418] Apply the correct behavior of precision='full' --- modules/devices.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index ad36f6562bc..29a270d11a6 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -185,11 +185,14 @@ def autocast(disable=False): if fp8 and device==cpu: return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True) - if dtype == torch.float32: - return contextlib.nullcontext() - if has_xpu() or has_mps() or cuda_no_autocast(): - return manual_cast(dtype_inference) + return manual_cast(dtype) + + if fp8 and dtype_inference == torch.float32: + return manual_cast(dtype) + + if dtype == torch.float32 or dtype_inference == torch.float32: + return contextlib.nullcontext() return torch.autocast("cuda") From ca671e5d7b9d03227f01e6bcb350032b6d14e722 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:30:55 +0800 Subject: [PATCH 1814/2418] rearrange if-statements for cpu --- modules/devices.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 29a270d11a6..0321d12c6ab 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -185,15 +185,15 @@ def autocast(disable=False): if fp8 and device==cpu: return torch.autocast("cpu", dtype=torch.bfloat16, enabled=True) - if has_xpu() or has_mps() or cuda_no_autocast(): - return manual_cast(dtype) - if fp8 and dtype_inference == torch.float32: return manual_cast(dtype) if dtype == torch.float32 or dtype_inference == torch.float32: return contextlib.nullcontext() + if has_xpu() or has_mps() or cuda_no_autocast(): + return manual_cast(dtype) + return torch.autocast("cuda") From 3cc7572f5dec429d265ce937249eb4b5cc18d0ba Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Tue, 9 Jan 2024 11:46:10 -0500 Subject: [PATCH 1815/2418] Restore scale factor limit changes to master branch. --- 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 11ba4f0b952..33894b3e694 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -143,7 +143,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, description="By how much to upscale the image, only used when resize_mode=0.") + 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_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 9dd25348248c06770897f4cde64e2663dfa9f2de Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Tue, 9 Jan 2024 11:47:39 -0500 Subject: [PATCH 1816/2418] Restore scale factor limit changes to master branch. --- scripts/postprocessing_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 5946678e5be..a57f9d4a4b6 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -21,7 +21,7 @@ def ui(self): with FormRow(): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: - upscaling_resize = gr.Slider(minimum=1.0, maximum=100.0, step=0.05, label="Resize", value=2, elem_id="extras_upscaling_resize") + upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): From 4d9f2c3ec8e791b9c354292c4333fc85b8f8d740 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 6 Jan 2024 21:41:29 +0900 Subject: [PATCH 1817/2418] update p.seed and p.subseed --- modules/txt2img.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/txt2img.py b/modules/txt2img.py index c4cc12d2f6d..d22a1f319e3 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -67,13 +67,16 @@ def txt2img_upscale(id_task: str, request: gr.Request, gallery, gallery_index, g geninfo = json.loads(generation_info) all_seeds = geninfo["all_seeds"] + all_subseeds = geninfo["all_subseeds"] image_info = gallery[gallery_index] if 0 <= gallery_index < len(gallery) else gallery[0] p.firstpass_image = infotext_utils.image_from_url_text(image_info) gallery_index_from_end = len(gallery) - gallery_index seed = all_seeds[-gallery_index_from_end if gallery_index_from_end < len(all_seeds) + 1 else 0] - p.script_args = modules.scripts.scripts_txt2img.set_named_arg(p.script_args, 'ScriptSeed', 'seed', seed) + subseed = all_subseeds[-gallery_index_from_end if gallery_index_from_end < len(all_seeds) + 1 else 0] + p.seed = seed + p.subseed = subseed with closing(p): processed = modules.scripts.scripts_txt2img.run(p, *p.script_args) From 3db6938caa719aaa38b52edecf42740ef62b0c3c Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Wed, 10 Jan 2024 18:11:48 -0500 Subject: [PATCH 1818/2418] begin redesign of tree module. --- html/extra-networks-card-minimal.html | 3 +- html/extra-networks-pane.html | 11 ++ html/extra-networks-tree-directory.html | 4 + html/extra-networks-tree-file.html | 1 + javascript/extraNetworks.js | 22 +++ modules/shared_options.py | 1 - modules/ui_extra_networks.py | 164 ++++++++++--------- style.css | 204 +++++++++++++----------- 8 files changed, 248 insertions(+), 162 deletions(-) create mode 100644 html/extra-networks-pane.html create mode 100644 html/extra-networks-tree-directory.html create mode 100644 html/extra-networks-tree-file.html diff --git a/html/extra-networks-card-minimal.html b/html/extra-networks-card-minimal.html index a6a54d9f4ac..d66df7dfb3f 100644 --- a/html/extra-networks-card-minimal.html +++ b/html/extra-networks-card-minimal.html @@ -1,3 +1,4 @@
    - {name}{copy_path_button}{metadata_button}{edit_button} + {name} + {copy_path_button}{metadata_button}{edit_button}
    diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html new file mode 100644 index 00000000000..93bad698ddb --- /dev/null +++ b/html/extra-networks-pane.html @@ -0,0 +1,11 @@ +
    + {subdirs_html} +
    +
    +
    + {tree_html} +
    +
    + {items_html} +
    +
    \ No newline at end of file diff --git a/html/extra-networks-tree-directory.html b/html/extra-networks-tree-directory.html new file mode 100644 index 00000000000..cec15588635 --- /dev/null +++ b/html/extra-networks-tree-directory.html @@ -0,0 +1,4 @@ +
    +{folder_name} +{content} +
    \ No newline at end of file diff --git a/html/extra-networks-tree-file.html b/html/extra-networks-tree-file.html new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/html/extra-networks-tree-file.html @@ -0,0 +1 @@ + diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 40309d5575c..33f45c8e4d0 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -253,6 +253,28 @@ function saveCardPreview(event, tabname, filename) { event.preventDefault(); } +function extraNetworksFolderClick(event, tabs_id) { + var els = document.querySelectorAll(".folder-item-summary.selected"); + [...els].forEach(el => { + el.classList.remove("selected"); + }); + event.target.classList.add("selected"); + + var searchTextArea = gradioApp().querySelector("#" + tabs_id + ' > label > textarea'); + var text = event.target.classList.contains("search-all") ? "" : event.target.firstChild.textContent.trim(); + searchTextArea.value = text; + updateInput(searchTextArea); + + if (event.target.parentElement.open) { + // before close + console.log("closed"); + } else { + // before open + console.log("opened"); + //console.log("Opened:", event.target.parentElement); + } +} + function extraNetworksSearchButton(tabs_id, event) { var searchTextarea = gradioApp().querySelector("#" + tabs_id + ' > label > textarea'); var button = event.target; diff --git a/modules/shared_options.py b/modules/shared_options.py index e698c26498c..d2e86ff10b3 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -238,7 +238,6 @@ "extra_networks_dir_button_function": OptionInfo(False, "Add a '/' to the beginning of directory buttons").info("Buttons will display the contents of the selected directory without acting as a search filter."), "extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more'), "extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}), - "extra_networks_tree_view": OptionInfo(False, "Show extra networks using a directory tree view.").needs_reload_ui(), "extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"), "extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"), "extra_networks_card_text_scale": OptionInfo(1.0, "Card text scale", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}).info("1 = original size"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index ab484a5dc0b..6318594fa5b 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -73,10 +73,11 @@ def _get_tree(_paths: list[str]): # the value can be an empty dict if the directory is empty. We want these # placeholders for empty dirs so we can inform the user later. for path in paths: + short_path = os.path.basename(path) # Wrap the path in a list since that is what the `_get_tree` expects. - res[path] = _get_tree([path]) - if res[path]: - res[path] = res[path][os.path.basename(path)] + res[short_path] = _get_tree([path]) + if res[short_path]: + res[short_path] = res[short_path][os.path.basename(path)] return res @@ -153,10 +154,9 @@ def __init__(self, title): self.title = title self.name = title.lower() self.id_page = self.name.replace(" ", "_") - if shared.opts.extra_networks_tree_view: - self.card_page = shared.html("extra-networks-card-minimal.html") - else: - self.card_page = shared.html("extra-networks-card.html") + self.extra_networks_pane_template = shared.html("extra-networks-pane.html") + self.card_page_template = shared.html("extra-networks-card.html") + self.card_page_minimal_template = shared.html("extra-networks-card-minimal.html") self.allow_prompt = True self.allow_negative_prompt = False self.metadata = {} @@ -182,15 +182,14 @@ def link_preview(self, filename): def search_terms_from_path(self, filename, possible_directories=None): abspath = os.path.abspath(filename) - for parentdir in (possible_directories if possible_directories is not None else self.allowed_directories_for_previews()): - parentdir = os.path.abspath(parentdir) + parentdir = os.path.dirname(os.path.abspath(parentdir)) if abspath.startswith(parentdir): - return abspath[len(parentdir):].replace('\\', '/') + return os.path.relpath(abspath, parentdir) return "" - def create_item_html(self, tabname: str, item: dict) -> str: + def create_item_html(self, tabname: str, item: dict, template: Optional[str] = None) -> str: """Generates HTML for a single ExtraNetworks Item Args: @@ -265,7 +264,10 @@ def create_item_html(self, tabname: str, item: dict) -> str: "tabname": quote_js(tabname), } - return self.card_page.format(**args) + if template: + return template.format(**args) + else: + return self.card_page.format(**args) def create_tree_view_html(self, tabname: str) -> str: """Generates HTML for displaying folders in a tree view. @@ -276,53 +278,67 @@ def create_tree_view_html(self, tabname: str) -> str: Returns: HTML string generated for this tree view. """ - self_name_id = self.name.replace(" ", "_") - res = f"
    " - - self.metadata = {} - self.items = {x["name"]: x for x in self.list_items()} + res = f"" + # Generate HTML for the tree. roots = self.allowed_directories_for_previews() tree_items = {v["filename"]: ExtraNetworksItem(v) for v in self.items.values()} tree = get_tree([os.path.abspath(x) for x in roots], items=tree_items) if not tree: - return res + "
    " + return res - file_template = "
  • {}
  • " + file_template = "
  • {card}
  • " dir_template = ( - "
    " - "{}" - "{}
" + "
" + "" + "{folder_name}" + "" + "
    {content}
" "
" ) def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> str: """Recursively builds HTML for a tree.""" - _res = "
    " + _res = "" if not data: - return "
    • DIRECTORY IS EMPTY
    " + return "
  • DIRECTORY IS EMPTY
  • " for k, v in sorted(data.items(), key=lambda x: shared.natural_sort_key(x[0])): if isinstance(v, (ExtraNetworksItem,)): - _res += file_template.format(self.create_item_html(tabname, v.item)) + item_html = self.create_item_html(tabname, v.item, self.card_page_minimal_template) + _res += file_template.format(**{"card": item_html}) else: - _res += dir_template.format("", k, _build_tree(v)) + tmp = dir_template.format( + **{ + "attributes": "", + "tabname": tabname, + "folder_name": k, + "content": _build_tree(v), + } + ) + _res += tmp + return _res - res += "
      " # Add each root directory to the tree. for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])): # If root is empty, append the "disabled" attribute to the template details tag. - res += dir_template.format("open" if v else "open disabled", k, _build_tree(v)) + res += "
        " + res += dir_template.format( + **{ + "attributes": "open" if v else "open", + "tabname": tabname, + "folder_name": k, + "content": _build_tree(v), + } + ) + res += "
      " res += "
    " - res += "
" return res - def create_card_view_html(self, tabname): - items_html = "" - self.metadata = {} + def create_subdirs_html(self, tabname): subdirs = {} for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: @@ -355,43 +371,53 @@ def create_card_view_html(self, tabname): subdirs = {"": 1, **subdirs} subdirs_html_template = ( - "" ) - subdirs_html = "".join( + return "".join( [ subdirs_html_template.format( - " search-all" if subdir == "" else "", - tabname, - html.escape(subdir if subdir != "" else "all"), + **{ + "classes": "search-all" if not subdir else "", + "tabname": tabname, + "content": html.escape(subdir if subdir else "all"), + } ) for subdir in subdirs ] ) + def create_card_view_html(self, tabname): + res = "" self.items = {x["name"]: x for x in self.list_items()} for item in self.items.values(): - items_html += self.create_item_html(tabname, item) + res += self.create_item_html(tabname, item, self.card_page_template) - if items_html == "": + if res == "": dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) - items_html = shared.html("extra-networks-no-cards.html").format(dirs=dirs) - - self_name_id = self.name.replace(" ", "_") - - res = ( - f"
    {subdirs_html}
    " - f"
    {items_html}
    " - ) + res = shared.html("extra-networks-no-cards.html").format(dirs=dirs) return res def create_html(self, tabname): - if shared.opts.extra_networks_tree_view: - return self.create_tree_view_html(tabname) - else: - return self.create_card_view_html(tabname) + self.metadata = {} + self.items = {x["name"]: x for x in self.list_items()} + + tree_view_html = self.create_tree_view_html(tabname) + subdirs_html = self.create_subdirs_html(tabname) + card_view_html = self.create_card_view_html(tabname) + network_type_id = self.name.replace(" ", "_") + + return self.extra_networks_pane_template.format( + **{ + "tabname": tabname, + "network_type_id": network_type_id, + "tree_html": tree_view_html, + "subdirs_html": subdirs_html, + "items_html": card_view_html, + } + ) def create_item(self, name, index=None): raise NotImplementedError() @@ -516,19 +542,19 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) - tab_controls = {} - edit_search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) dropdown_sort = gr.Dropdown(choices=['Path', 'Name', 'Date Created', 'Date Modified', ], value=shared.opts.extra_networks_card_order_field, elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) checkbox_show_dirs = gr.Checkbox(True, label='Show dirs', elem_id=tabname+"_extra_show_dirs", elem_classes="show-dirs", visible=False) - tab_controls["edit_search"] = edit_search - tab_controls["dropdown_sort"] = dropdown_sort - tab_controls["button_sortorder"] = button_sortorder - tab_controls["button_refresh"] = button_refresh - tab_controls["checkbox_show_dirs"] = checkbox_show_dirs + tab_controls = [ + edit_search, + dropdown_sort, + button_sortorder, + button_refresh, + checkbox_show_dirs, + ] ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) @@ -538,32 +564,28 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): fn=lambda: [gr.update(visible=False) for _ in tab_controls], _js="function(){ extraNetworksUrelatedTabSelected('" + tabname + "'); }", inputs=[], - outputs=list(tab_controls.values()), + outputs=tab_controls, show_progress=False, ) - visible_controls = list(tab_controls.keys()) - if shared.opts.extra_networks_tree_view: - visible_controls = ["button_refresh"] - for page, tab in zip(ui.stored_extra_pages, related_tabs): allow_prompt = "true" if page.allow_prompt else "false" allow_negative_prompt = "true" if page.allow_negative_prompt else "false" jscode = ( "extraNetworksTabSelected(" - f"'{tabname}', " - f"'{tabname}_{page.id_page}_prompts', " - f"'{allow_prompt}', " - f"'{allow_negative_prompt}'" + f"'{tabname}', " + f"'{tabname}_{page.id_page}_prompts', " + f"'{allow_prompt}', " + f"'{allow_negative_prompt}'" ");" ) tab.select( - fn=lambda: [gr.update(visible=k in visible_controls) for k in tab_controls], + fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js="function(){ " + jscode + " }", inputs=[], - outputs=list(tab_controls.values()), + outputs=tab_controls, show_progress=False, ) diff --git a/style.css b/style.css index 680a5f837b7..4f285c68b06 100644 --- a/style.css +++ b/style.css @@ -863,7 +863,7 @@ footer { margin-bottom: 1em; } -.extra-network-cards{ +.extra-network-pane{ height: calc(100vh - 24rem); overflow: clip scroll; resize: vertical; @@ -908,53 +908,75 @@ footer { width: auto; } -.extra-network-cards .nocards{ +.extra-network-pane .nocards{ margin: 1.25em 0.5em 0.5em 0.5em; } -.extra-network-cards .nocards h1{ +.extra-network-pane .nocards h1{ font-size: 1.5em; margin-bottom: 1em; } -.extra-network-cards .nocards li{ +.extra-network-pane .nocards li{ margin-left: 0.5em; } +.extra-network-pane :is(.card, .card-minimal) .button-row{ + display: inline-flex; + visibility: hidden; + color: white; +} -.extra-network-cards .card .button-row{ - display: none; +.extra-network-pane .card .button-row { position: absolute; - color: white; right: 0; - z-index: 1 + z-index: 1; } -.extra-network-cards .card:hover .button-row{ - display: flex; + +.extra-network-pane .card-minimal .button-row { + padding-left: 0.5rem; + padding-right: 0.5rem; + align-items: center; } -.extra-network-cards .card .card-button{ +.extra-network-pane :is(.card:hover, .card-minimal:hover) .button-row{ + visibility: visible; +} + +.extra-network-pane .card-button{ color: white; } -.extra-network-cards .card .metadata-button:before{ +.extra-network-pane .copy-path-button:before { + content: "⎘"; +} + +.extra-network-pane .metadata-button:before{ content: "🛈"; } -.extra-network-cards .card .edit-button:before{ +.extra-network-pane .edit-button:before{ content: "🛠"; } -.extra-network-cards .card .card-button { +.extra-network-pane .card-button { + width: 1.5em; text-shadow: 2px 2px 3px black; + color: white; padding: 0.25em 0.1em; - font-size: 200%; - width: 1.5em; } -.extra-network-cards .card .card-button:hover{ + +.extra-network-pane .card-button:hover{ color: red; } +.extra-network-pane .card .card-button { + font-size: 2rem; +} + +.extra-network-pane .card-minimal .card-button { + font-size: 1rem; +} .standalone-card-preview.card .preview{ position: absolute; @@ -963,7 +985,7 @@ footer { height:100%; } -.extra-network-cards .card, .standalone-card-preview.card{ +.extra-network-pane .card, .standalone-card-preview.card{ display: inline-block; margin: 0.5rem; width: 16rem; @@ -980,15 +1002,15 @@ footer { background-image: url('./file=html/card-no-preview.png') } -.extra-network-cards .card:hover{ +.extra-network-pane .card:hover{ box-shadow: 0 0 2px 0.3em rgba(0, 128, 255, 0.35); } -.extra-network-cards .card .actions .additional{ +.extra-network-pane .card .actions .additional{ display: none; } -.extra-network-cards .card .actions{ +.extra-network-pane .card .actions{ position: absolute; bottom: 0; left: 0; @@ -999,45 +1021,45 @@ footer { text-shadow: 0 0 0.2em black; } -.extra-network-cards .card .actions *{ +.extra-network-pane .card .actions *{ color: white; } -.extra-network-cards .card .actions .name{ +.extra-network-pane .card .actions .name{ font-size: 1.7em; font-weight: bold; line-break: anywhere; } -.extra-network-cards .card .actions .description { +.extra-network-pane .card .actions .description { display: block; max-height: 3em; white-space: pre-wrap; line-height: 1.1; } -.extra-network-cards .card .actions .description:hover { +.extra-network-pane .card .actions .description:hover { max-height: none; } -.extra-network-cards .card .actions:hover .additional{ +.extra-network-pane .card .actions:hover .additional{ display: block; } -.extra-network-cards .card ul{ +.extra-network-pane .card ul{ margin: 0.25em 0 0.75em 0.25em; cursor: unset; } -.extra-network-cards .card ul a{ +.extra-network-pane .card ul a{ cursor: pointer; } -.extra-network-cards .card ul a:hover{ +.extra-network-pane .card ul a:hover{ color: red; } -.extra-network-cards .card .preview{ +.extra-network-pane .card .preview{ position: absolute; object-fit: cover; width: 100%; @@ -1158,94 +1180,98 @@ body.resizing .resize-handle { border-left: 1px dashed var(--border-color-primary); } -.extra-network-cards .card .copy-path-button:before { - content: "⎘"; -} - -.extra-network-cards .card-minimal .button-column { +.extra-network-pane .card-minimal { display: inline-flex; - visibility: hidden; - color: white; - padding-left: 0.5rem; - padding-right: 0.5rem; - align-items: center; + flex-grow: 1; + position: relative; + overflow: hidden; + cursor: pointer; + font-size: 1rem; + font-weight: bold; + line-break: anywhere; } -.extra-network-cards .card-minimal:hover .button-column { - visibility: visible; +/* Pushes buttons to right */ +.extra-network-pane .card-minimal .name { + flex-grow: 1; } -.extra-network-cards .card-minimal .copy-path-button:before { - content: "⎘"; +.file-item, +.folder-item, +.folder-item-summary { + padding-left: 0.05rem; + cursor: pointer; + user-select: none; + font-size: 1rem; } -.extra-network-cards .card-minimal .metadata-button:before{ - content: "🛈"; +.extra-network-pane .extra-network-tree .folder-item-summary:hover, +.extra-network-pane .extra-network-tree .file-item:hover { + -webkit-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; + background-color: var(--neutral-200); } -.extra-network-cards .card-minimal .edit-button:before{ - content: "🛠"; +.dark .extra-network-pane .extra-network-tree .folder-item-summary:hover, +.dark .extra-network-pane .extra-network-tree .file-item:hover { + -webkit-transition: all 0.05s ease-in-out; + transition: all 0.05s ease-in-out; + background-color: var(--neutral-800); } -.extra-network-cards .card-minimal .card-button { - color: white; - text-shadow: 2px 2px 3px black; - font-size: 1rem; - width: 1.5rem; +/* prevents clicking/collapsing of details tags when disabled attribute is used*/ +.extra-network-pane .extra-network-tree details[disabled] summary { + pointer-events: none; + user-select: none; } -.extra-network-cards .card-minimal .card-button:hover { - color: red; +.extra-network-pane .extra-network-tree details.folder-item > summary { + list-style-type: '📁'; + text-overflow: ellipsis; } -.extra-network-cards .card-minimal { - display: inline-flex; - position: relative; - overflow: hidden; - cursor: pointer; - font-size: 1rem; - font-weight: bold; - line-break: anywhere; +.extra-network-pane .extra-network-tree details.folder-item[open] > summary { + list-style-type: '📂'; + text-overflow: ellipsis; } -.file-item { - list-style-type: '📄'; +.extra-network-pane .extra-network tree ul.folder-container { + list-style: none; + font-size: 1rem; + text-overflow: ellipsis; } -/* prevents clicking/collapsing of details tags when disabled attribute is used*/ -details[disabled] summary { - pointer-events: none; - user-select: none; +.extra-network-pane .extra-network-tree li.file-item { + display: flex; + position: relative; + align-items: center; } -details.folder-item > summary { - list-style-type: '📁'; +.extra-network-pane .extra-network-tree li.file-item::before { + content: '📄'; + font-size: 0.85rem; + vertical-align: middle; } -details.folder-item[open] > summary { - list-style-type: '📂'; +.extra-network-pane { + display: flex; } -.file-item, -.folder-item, -.folder-item-summary { +.extra-network-pane .extra-network-subdirs { display: block; +} +.extra-network-pane .extra-network-tree { font-size: 1rem; - padding: 0.05rem; - cursor: pointer; - user-select: none; + width: 25%; } - -.folder-item-summary:hover, -.file-item:hover { - -webkit-transition: all 0.1s ease-in-out; - transition: all 0.1s ease-in-out; - background-color: var(--neutral-200); +.extra-network-pane .extra-network-cards { + flex-grow: 1; } -.dark .folder-item-summary:hover, -.dark .file-item:hover { - -webkit-transition: all 0.05s ease-in-out; - transition: all 0.05s ease-in-out; +.dark .extra-network-tree .folder-item-summary.selected{ background-color: var(--neutral-800); } + +.extra-network-tree .folder-item-summary.selected { + background-color: var(--neutral-200); +} From 0011640ab187e5a59098bb80d165a23f9d08568c Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 11 Jan 2024 08:29:42 +0200 Subject: [PATCH 1819/2418] Logging: set formatter correctly for fallback logger too --- modules/logging_config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/logging_config.py b/modules/logging_config.py index 11eee9a63db..8e31d8c9fd1 100644 --- a/modules/logging_config.py +++ b/modules/logging_config.py @@ -36,20 +36,21 @@ def setup_logging(loglevel): # Already configured, do not interfere return + formatter = logging.Formatter( + '%(asctime)s %(levelname)s [%(name)s] %(message)s', + '%Y-%m-%d %H:%M:%S', + ) + if os.environ.get("SD_WEBUI_RICH_LOG"): from rich.logging import RichHandler handler = RichHandler() else: handler = logging.StreamHandler() + handler.setFormatter(formatter) if TqdmLoggingHandler: handler = TqdmLoggingHandler(handler) - formatter = logging.Formatter( - '%(asctime)s %(levelname)s [%(name)s] %(message)s', - '%Y-%m-%d %H:%M:%S', - ) - handler.setFormatter(formatter) log_level = getattr(logging, loglevel.upper(), None) or logging.INFO From 0726a6e12e85a37d1e514f5603acf9f058c11783 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Thu, 11 Jan 2024 15:06:57 -0500 Subject: [PATCH 1820/2418] Finish base layout. Fix bugs. Need to test for stability and clean up. --- .../Lora/ui_extra_networks_lora.py | 5 +- html/extra-networks-card.html | 4 +- html/extra-networks-pane.html | 3 - javascript/extraNetworks.js | 48 ++++---- modules/ui_extra_networks.py | 113 ++++++------------ modules/ui_extra_networks_checkpoints.py | 5 +- modules/ui_extra_networks_hypernets.py | 6 +- .../ui_extra_networks_textual_inversion.py | 5 +- style.css | 24 ++-- 9 files changed, 89 insertions(+), 124 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index df02c663b12..db612fa2ba3 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -24,13 +24,16 @@ def create_item(self, name, index=None, enable_filter=True): alias = lora_on_disk.get_alias() + search_terms = [self.search_terms_from_path(lora_on_disk.filename)] + if lora_on_disk.hash: + search_terms.append(lora_on_disk.hash) 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) + " " + (lora_on_disk.hash or ""), + "search_terms": search_terms, "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/html/extra-networks-card.html b/html/extra-networks-card.html index 7770094da14..d163fe370a5 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -6,9 +6,7 @@ {edit_button}
    -
    - -
    +
    {search_terms}
    {name} {description}
    diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 93bad698ddb..20cf6686755 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,6 +1,3 @@ -
    - {subdirs_html} -
    {tree_html} diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 33f45c8e4d0..4cc67fd18a3 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -24,8 +24,6 @@ function setupExtraNetworksForTab(tabname) { var sort = gradioApp().getElementById(tabname + '_extra_sort'); var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder'); var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); - var showDirsDiv = gradioApp().getElementById(tabname + '_extra_show_dirs'); - var showDirs = gradioApp().querySelector('#' + tabname + '_extra_show_dirs input'); var promptContainer = gradioApp().querySelector('.prompt-container-compact#' + tabname + '_prompt_container'); var negativePrompt = gradioApp().querySelector('#' + tabname + '_neg_prompt'); @@ -33,14 +31,14 @@ function setupExtraNetworksForTab(tabname) { tabs.appendChild(sort); tabs.appendChild(sortOrder); tabs.appendChild(refresh); - tabs.appendChild(showDirsDiv); var applyFilter = function() { var searchTerm = search.value.toLowerCase(); gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { var searchOnly = elem.querySelector('.search_only'); - var text = elem.querySelector('.name').textContent.toLowerCase() + " " + elem.querySelector('.search_term').textContent.toLowerCase(); + + var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { return t.textContent.toLowerCase() }).join(" "); var visible = text.indexOf(searchTerm) != -1; @@ -100,15 +98,6 @@ function setupExtraNetworksForTab(tabname) { extraNetworksApplySort[tabname] = applySort; extraNetworksApplyFilter[tabname] = applyFilter; - - var showDirsUpdate = function() { - var css = '#' + tabname + '_extra_tabs .extra-network-subdirs { display: none; }'; - toggleCss(tabname + '_extra_show_dirs_style', css, !showDirs.checked); - localSet('extra-networks-show-dirs', showDirs.checked ? 1 : 0); - }; - showDirs.checked = localGet('extra-networks-show-dirs', 1) == 1; - showDirs.addEventListener("change", showDirsUpdate); - showDirsUpdate(); } function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) { @@ -136,14 +125,23 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp } } +function clearSearch(tabname) { + // Clear search box. + var tab_id = tabname + "_extra_search"; + var searchTextarea = gradioApp().querySelector("#" + tab_id + ' > label > textarea'); + searchTextarea.value = ""; + updateInput(searchTextarea); +} + -function extraNetworksUrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate) +function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate) extraNetworksMovePromptToTab(tabname, '', false, false); + clearSearch(tabname); } function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt) { // called from python when user selects an extra networks tab extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt); - + clearSearch(tabname); } function applyExtraNetworkFilter(tabname) { @@ -254,6 +252,15 @@ function saveCardPreview(event, tabname, filename) { } function extraNetworksFolderClick(event, tabs_id) { + // If folder is open but not selected, we don't want to collapse it. Instead + // we override the removal of the "open" attribute so that the folder is + // only selected but remains open. Since this is a toggle event, removing + // the "open" attribute instead forces the event to add it back which keeps it open. + if (event.target.parentElement.open && !event.target.classList.contains("selected")) { + // before event handler removes "open" + event.target.parentElement.removeAttribute("open"); + } + var els = document.querySelectorAll(".folder-item-summary.selected"); [...els].forEach(el => { el.classList.remove("selected"); @@ -261,18 +268,9 @@ function extraNetworksFolderClick(event, tabs_id) { event.target.classList.add("selected"); var searchTextArea = gradioApp().querySelector("#" + tabs_id + ' > label > textarea'); - var text = event.target.classList.contains("search-all") ? "" : event.target.firstChild.textContent.trim(); + var text = event.target.classList.contains("search-all") ? "" : event.target.getAttribute("data-path"); searchTextArea.value = text; updateInput(searchTextArea); - - if (event.target.parentElement.open) { - // before close - console.log("closed"); - } else { - // before open - console.log("opened"); - //console.log("Opened:", event.target.parentElement); - } } function extraNetworksSearchButton(tabs_id, event) { diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 6318594fa5b..2e226ba0029 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -48,23 +48,24 @@ def get_tree(paths: Union[str, list[str]], items: dict[str, ExtraNetworksItem]) if isinstance(paths, (str,)): paths = [paths] - def _get_tree(_paths: list[str]): + def _get_tree(_paths: list[str], _root: str): _res = {} for path in _paths: + relpath = os.path.relpath(path, _root) if os.path.isdir(path): dir_items = os.listdir(path) # Ignore empty directories. if not dir_items: continue - dir_tree = _get_tree([os.path.join(path, x) for x in dir_items]) + dir_tree = _get_tree([os.path.join(path, x) for x in dir_items], _root) # We only want to store non-empty folders in the tree. if dir_tree: - _res[os.path.basename(path)] = dir_tree + _res[relpath] = dir_tree else: if path not in items: continue # Add the ExtraNetworksItem to the result. - _res[os.path.basename(path)] = items[path] + _res[relpath] = items[path] return _res res = {} @@ -73,11 +74,13 @@ def _get_tree(_paths: list[str]): # the value can be an empty dict if the directory is empty. We want these # placeholders for empty dirs so we can inform the user later. for path in paths: - short_path = os.path.basename(path) + root = os.path.dirname(path) + relpath = os.path.relpath(path, root) # Wrap the path in a list since that is what the `_get_tree` expects. - res[short_path] = _get_tree([path]) - if res[short_path]: - res[short_path] = res[short_path][os.path.basename(path)] + res[relpath] = _get_tree([path], root) + if res[relpath]: + # We need to pull the inner path out one for these root dirs. + res[relpath] = res[relpath][relpath] return res @@ -245,6 +248,17 @@ def create_item_html(self, tabname: str, item: dict, template: Optional[str] = N sort_keys = " ".join([f'data-sort-{k}="{html.escape(str(v))}"' for k, v in item.get("sort_keys", {}).items()]).strip() + search_terms_html = "" + search_term_template = "{search_term}" + for search_term in item.get("search_terms", []): + search_terms_html += search_term_template.format( + **{ + "style": "display: none;", + "class": "search_terms" + (" search_only" if search_only else ""), + "search_term": search_term, + } + ) + # Some items here might not be used depending on HTML template used. args = { "background_image": background_image, @@ -258,7 +272,7 @@ def create_item_html(self, tabname: str, item: dict, template: Optional[str] = N "prompt": item.get("prompt", None), "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', "search_only": " search_only" if search_only else "", - "search_term": item.get("search_term", ""), + "search_terms": search_terms_html, "sort_keys": sort_keys, "style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", "tabname": quote_js(tabname), @@ -278,7 +292,7 @@ def create_tree_view_html(self, tabname: str) -> str: Returns: HTML string generated for this tree view. """ - res = f"" + res = "" # Generate HTML for the tree. roots = self.allowed_directories_for_previews() @@ -291,7 +305,8 @@ def create_tree_view_html(self, tabname: str) -> str: file_template = "
  • {card}
  • " dir_template = ( "
    " - "" + "" "{folder_name}" "" "
      {content}
    " @@ -309,16 +324,15 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> str: item_html = self.create_item_html(tabname, v.item, self.card_page_minimal_template) _res += file_template.format(**{"card": item_html}) else: - tmp = dir_template.format( + _res += dir_template.format( **{ "attributes": "", "tabname": tabname, - "folder_name": k, + "folder_name": os.path.basename(k), + "data_path": k, "content": _build_tree(v), } ) - _res += tmp - return _res # Add each root directory to the tree. @@ -329,65 +343,15 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> str: **{ "attributes": "open" if v else "open", "tabname": tabname, - "folder_name": k, + "folder_name": os.path.basename(k), + "data_path": k, "content": _build_tree(v), } ) res += "" res += "" - return res - def create_subdirs_html(self, tabname): - subdirs = {} - - for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: - for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])): - for dirname in sorted(dirs, key=shared.natural_sort_key): - x = os.path.join(root, dirname) - - if not os.path.isdir(x): - continue - - subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/") - - if shared.opts.extra_networks_dir_button_function: - if not subdir.startswith("/"): - subdir = "/" + subdir - else: - while subdir.startswith("/"): - subdir = subdir[1:] - - is_empty = len(os.listdir(x)) == 0 - if not is_empty and not subdir.endswith("/"): - subdir = subdir + "/" - - if ("/." in subdir or subdir.startswith(".")) and not shared.opts.extra_networks_show_hidden_directories: - continue - - subdirs[subdir] = 1 - - if subdirs: - subdirs = {"": 1, **subdirs} - - subdirs_html_template = ( - "" - ) - return "".join( - [ - subdirs_html_template.format( - **{ - "classes": "search-all" if not subdir else "", - "tabname": tabname, - "content": html.escape(subdir if subdir else "all"), - } - ) for subdir in subdirs - ] - ) - def create_card_view_html(self, tabname): res = "" self.items = {x["name"]: x for x in self.list_items()} @@ -405,7 +369,6 @@ def create_html(self, tabname): self.items = {x["name"]: x for x in self.list_items()} tree_view_html = self.create_tree_view_html(tabname) - subdirs_html = self.create_subdirs_html(tabname) card_view_html = self.create_card_view_html(tabname) network_type_id = self.name.replace(" ", "_") @@ -414,7 +377,6 @@ def create_html(self, tabname): "tabname": tabname, "network_type_id": network_type_id, "tree_html": tree_view_html, - "subdirs_html": subdirs_html, "items_html": card_view_html, } ) @@ -534,7 +496,12 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): elem_id = f"{tabname}_{page.id_page}_cards_html" page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) - page_elem.change(fn=lambda: None, _js='function(){applyExtraNetworkFilter(' + quote_js(tabname) + '); return []}', inputs=[], outputs=[]) + page_elem.change( + fn=lambda: None, + _js=f"function(){{applyExtraNetworkFilter({tabname}_extra_search); return []}}", + inputs=[], + outputs=[], + ) editor = page.create_user_metadata_editor(ui, tabname) editor.create_ui() @@ -542,18 +509,16 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) - edit_search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) + edit_search = gr.Textbox('', show_label=False, elem_id=f"{tabname}_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) dropdown_sort = gr.Dropdown(choices=['Path', 'Name', 'Date Created', 'Date Modified', ], value=shared.opts.extra_networks_card_order_field, elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) - checkbox_show_dirs = gr.Checkbox(True, label='Show dirs', elem_id=tabname+"_extra_show_dirs", elem_classes="show-dirs", visible=False) tab_controls = [ edit_search, dropdown_sort, button_sortorder, button_refresh, - checkbox_show_dirs, ] ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) @@ -562,7 +527,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): for tab in unrelated_tabs: tab.select( fn=lambda: [gr.update(visible=False) for _ in tab_controls], - _js="function(){ extraNetworksUrelatedTabSelected('" + tabname + "'); }", + _js=f"function(){{ extraNetworksUnrelatedTabSelected('{tabname}'); }}", inputs=[], outputs=tab_controls, show_progress=False, diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 1693e71f16f..e7976ba1260 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -21,13 +21,16 @@ def create_item(self, name, index=None, enable_filter=True): return path, ext = os.path.splitext(checkpoint.filename) + search_terms = [self.search_terms_from_path(checkpoint.filename)] + if checkpoint.sha256: + search_terms.append(checkpoint.sha256) 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 ""), + "search_terms": search_terms, "onclick": '"' + html.escape(f"""return selectCheckpoint({quote_js(name)})""") + '"', "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": checkpoint.metadata, diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index c96c4fa3b12..2fb4bd190a1 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -20,14 +20,16 @@ def create_item(self, name, index=None, enable_filter=True): path, ext = os.path.splitext(full_path) sha256 = sha256_from_cache(full_path, f'hypernet/{name}') shorthash = sha256[0:10] if sha256 else None - + search_terms = [self.search_terms_from_path(path)] + if sha256: + search_terms.append(sha256) 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) + " " + (sha256 or ""), + "search_terms": search_terms, "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 1b334fda174..deb7cb8733b 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -18,13 +18,16 @@ def create_item(self, name, index=None, enable_filter=True): return path, ext = os.path.splitext(embedding.filename) + search_terms = [self.search_terms_from_path(embedding.filename)] + if embedding.hash: + search_terms.append(embedding.hash) 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) + " " + (embedding.hash or ""), + "search_terms": search_terms, "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/style.css b/style.css index 4f285c68b06..70d80d6a170 100644 --- a/style.css +++ b/style.css @@ -878,16 +878,8 @@ footer { margin: 0.3em; } -.extra-network-subdirs{ - padding: 0.2em 0.35em; -} - -.extra-network-subdirs button{ - margin: 0 0.15em; -} .extra-networks .tab-nav .search, -.extra-networks .tab-nav .sort, -.extra-networks .tab-nav .show-dirs +.extra-networks .tab-nav .sort { margin: 0.3em; align-self: center; @@ -1196,6 +1188,10 @@ body.resizing .resize-handle { flex-grow: 1; } +.folder-container { + margin-left: 1.5em !important; +} + .file-item, .folder-item, .folder-item-summary { @@ -1235,7 +1231,7 @@ body.resizing .resize-handle { text-overflow: ellipsis; } -.extra-network-pane .extra-network tree ul.folder-container { +.extra-network-pane .extra-network-tree ul.folder-container { list-style: none; font-size: 1rem; text-overflow: ellipsis; @@ -1257,15 +1253,15 @@ body.resizing .resize-handle { display: flex; } -.extra-network-pane .extra-network-subdirs { - display: block; -} .extra-network-pane .extra-network-tree { font-size: 1rem; - width: 25%; + min-width: 25%; + max-width: 25%; + border: 1px solid var(--block-border-color); } .extra-network-pane .extra-network-cards { flex-grow: 1; + border: 1px solid var(--block-border-color); } .dark .extra-network-tree .folder-item-summary.selected{ From 541881e3188b4f59d82043c5fdfac02607ec3119 Mon Sep 17 00:00:00 2001 From: WebDev <6970043+WebDev9000@users.noreply.github.com> Date: Sat, 13 Jan 2024 02:11:06 -0800 Subject: [PATCH 1821/2418] Adjust brush size with hotkeys. --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 45c7600ac5f..c695debc65b 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -218,6 +218,8 @@ onUiLoaded(async() => { canvas_hotkey_fullscreen: "KeyS", canvas_hotkey_move: "KeyF", canvas_hotkey_overlap: "KeyO", + canvas_hotkey_shrink_brush: "BracketLeft", + canvas_hotkey_grow_brush: "BracketRight", canvas_disabled_functions: [], canvas_show_tooltip: true, canvas_auto_expand: true, @@ -227,6 +229,8 @@ onUiLoaded(async() => { const functionMap = { "Zoom": "canvas_hotkey_zoom", "Adjust brush size": "canvas_hotkey_adjust", + "Shrink brush size": "canvas_hotkey_shrink_brush", + "Grow brush size": "canvas_hotkey_grow_brush", "Moving canvas": "canvas_hotkey_move", "Fullscreen": "canvas_hotkey_fullscreen", "Reset Zoom": "canvas_hotkey_reset", @@ -686,7 +690,9 @@ onUiLoaded(async() => { const hotkeyActions = { [hotkeysConfig.canvas_hotkey_reset]: resetZoom, [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, - [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen + [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen, + [defaultHotkeysConfig.canvas_hotkey_shrink_brush]: () => adjustBrushSize(elemId, 10), + [defaultHotkeysConfig.canvas_hotkey_grow_brush]: () => adjustBrushSize(elemId, -10) }; const action = hotkeyActions[event.code]; From 47b52d9b28aaabb603358fae5d9b824c49aa627b Mon Sep 17 00:00:00 2001 From: WebDev <6970043+WebDev9000@users.noreply.github.com> Date: Sat, 13 Jan 2024 02:31:26 -0800 Subject: [PATCH 1822/2418] Add # to the invalid_filename_chars list --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 87a7bf22139..b6f2358c3ee 100644 --- a/modules/images.py +++ b/modules/images.py @@ -321,7 +321,7 @@ def resize(im, w, h): return res -invalid_filename_chars = '<>:"/\\|?*\n\r\t' +invalid_filename_chars = '#<>:"/\\|?*\n\r\t' invalid_filename_prefix = ' ' invalid_filename_postfix = ' .' re_nonletters = re.compile(r'[\s' + string.punctuation + ']+') From b6dc307c99a25bc3f3f3e96ce640a4710c14e133 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 13 Jan 2024 14:45:15 +0400 Subject: [PATCH 1823/2418] fix_extension_check_for_requirements --- modules/extensions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/extensions.py b/modules/extensions.py index 99e7ee60f64..04bda297e79 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -224,13 +224,16 @@ def list_extensions(): # check for requirements for extension in extensions: + if not extension.enabled: + continue + for req in extension.metadata.requires: required_extension = loaded_extensions.get(req) if required_extension is None: errors.report(f'Extension "{extension.name}" requires "{req}" which is not installed.', exc_info=False) continue - if not extension.enabled: + if not required_extension.enabled: errors.report(f'Extension "{extension.name}" requires "{required_extension.name}" which is disabled.', exc_info=False) continue From 02e6963325e5221e0efb96a63f3dc849550489b7 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Sat, 13 Jan 2024 13:16:39 -0500 Subject: [PATCH 1824/2418] continue cleanup and redesign. --- html/extra-networks-tree-button.html | 11 + javascript/extraNetworks.js | 294 ++++++++++++++++++--------- modules/ui_extra_networks.py | 177 +++++++++++++--- style.css | 277 +++++++++++++++++++------ 4 files changed, 572 insertions(+), 187 deletions(-) create mode 100644 html/extra-networks-tree-button.html diff --git a/html/extra-networks-tree-button.html b/html/extra-networks-tree-button.html new file mode 100644 index 00000000000..920330f7a7d --- /dev/null +++ b/html/extra-networks-tree-button.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 3e3b03f320e..cce2246899e 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -16,88 +16,110 @@ function toggleCss(key, css, enable) { } function setupExtraNetworksForTab(tabname) { - gradioApp().querySelector('#' + tabname + '_extra_tabs').classList.add('extra-networks'); + var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs'); + this_tab.classList.add('extra-networks'); - var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div'); - var searchDiv = gradioApp().getElementById(tabname + '_extra_search'); - var search = searchDiv.querySelector('textarea'); - var sort = gradioApp().getElementById(tabname + '_extra_sort'); - var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder'); - var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); - var promptContainer = gradioApp().querySelector('.prompt-container-compact#' + tabname + '_prompt_container'); - var negativePrompt = gradioApp().querySelector('#' + tabname + '_neg_prompt'); - - tabs.appendChild(searchDiv); - tabs.appendChild(sort); - tabs.appendChild(sortOrder); - tabs.appendChild(refresh); - - var applyFilter = function() { - var searchTerm = search.value.toLowerCase(); - - gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { - var searchOnly = elem.querySelector('.search_only'); - - var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { return t.textContent.toLowerCase() }).join(" "); - - var visible = text.indexOf(searchTerm) != -1; - - if (searchOnly && searchTerm.length < 4) { - visible = false; - } - - elem.style.display = visible ? "" : "none"; - }); - - applySort(); - }; - - var applySort = function() { - var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); - - var reverse = sortOrder.classList.contains("sortReverse"); - var sortKey = sort.querySelector("input").value.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; - sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); - var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; + function registerPrompt(tabname, id) { + var textarea = gradioApp().querySelector("#" + id + " > label > textarea"); - if (sortKeyStore == sort.dataset.sortkey) { - return; + if (!activePromptTextarea[tabname]) { + activePromptTextarea[tabname] = textarea; } - sort.dataset.sortkey = sortKeyStore; - cards.forEach(function(card) { - card.originalParentElement = card.parentElement; + textarea.addEventListener("focus", function() { + activePromptTextarea[tabname] = textarea; }); - var sortedCards = Array.from(cards); - sortedCards.sort(function(cardA, cardB) { - var a = cardA.dataset[sortKey]; - var b = cardB.dataset[sortKey]; - if (!isNaN(a) && !isNaN(b)) { - return parseInt(a) - parseInt(b); + } + + this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) { + var tab_id = elem.getAttribute("id"); + + var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div'); + var searchDiv = gradioApp().QuerySelector("#" + tab_id + "_extra_search"); + console.log("HERE:", tab_id + "_extra_search", searchDiv); + var search = searchDiv.value; + var sort = gradioApp().getElementById(tabname + '_extra_sort'); + var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder'); + var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); + var promptContainer = gradioApp().querySelector('.prompt-container-compact#' + tabname + '_prompt_container'); + var negativePrompt = gradioApp().querySelector('#' + tabname + '_neg_prompt'); + tabs.appendChild(searchDiv); + tabs.appendChild(sort); + tabs.appendChild(sortOrder); + tabs.appendChild(refresh); + var applyFilter = function() { + var searchTerm = search.value.toLowerCase(); + + gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { + var searchOnly = elem.querySelector('.search_only'); + + var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { return t.textContent.toLowerCase() }).join(" "); + + var visible = text.indexOf(searchTerm) != -1; + + if (searchOnly && searchTerm.length < 4) { + visible = false; + } + + elem.style.display = visible ? "" : "none"; + }); + + applySort(); + }; + + var applySort = function() { + var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); + + var reverse = sortOrder.classList.contains("sortReverse"); + var sortKey = sort.querySelector("input").value.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; + sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); + var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; + + if (sortKeyStore == sort.dataset.sortkey) { + return; } - - return (a < b ? -1 : (a > b ? 1 : 0)); - }); - if (reverse) { - sortedCards.reverse(); - } - cards.forEach(function(card) { - card.remove(); - }); - sortedCards.forEach(function(card) { - card.originalParentElement.appendChild(card); + sort.dataset.sortkey = sortKeyStore; + + cards.forEach(function(card) { + card.originalParentElement = card.parentElement; + }); + var sortedCards = Array.from(cards); + sortedCards.sort(function(cardA, cardB) { + var a = cardA.dataset[sortKey]; + var b = cardB.dataset[sortKey]; + if (!isNaN(a) && !isNaN(b)) { + return parseInt(a) - parseInt(b); + } + + return (a < b ? -1 : (a > b ? 1 : 0)); + }); + if (reverse) { + sortedCards.reverse(); + } + cards.forEach(function(card) { + card.remove(); + }); + sortedCards.forEach(function(card) { + card.originalParentElement.appendChild(card); + }); + }; + + search.addEventListener("input", applyFilter); + sortOrder.addEventListener("click", function() { + sortOrder.classList.toggle("sortReverse"); + applySort(); }); - }; + applyFilter(); + + extraNetworksApplySort[tab_id] = applySort; + extraNetworksApplyFilter[tab_id] = applyFilter; - search.addEventListener("input", applyFilter); - sortOrder.addEventListener("click", function() { - sortOrder.classList.toggle("sortReverse"); - applySort(); + registerPrompt(tab_id, tab_id + "_prompt"); + registerPrompt(tab_id, tab_id + "_neg_prompt"); }); - applyFilter(); - extraNetworksApplySort[tabname] = applySort; - extraNetworksApplyFilter[tabname] = applyFilter; + + } function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) { @@ -136,12 +158,12 @@ function clearSearch(tabname) { function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate) extraNetworksMovePromptToTab(tabname, '', false, false); - clearSearch(tabname); + //clearSearch(tabname); } function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt) { // called from python when user selects an extra networks tab extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt); - clearSearch(tabname); + //clearSearch(tabname); } function applyExtraNetworkFilter(tabname) { @@ -159,23 +181,6 @@ var activePromptTextarea = {}; function setupExtraNetworks() { setupExtraNetworksForTab('txt2img'); setupExtraNetworksForTab('img2img'); - - function registerPrompt(tabname, id) { - var textarea = gradioApp().querySelector("#" + id + " > label > textarea"); - - if (!activePromptTextarea[tabname]) { - activePromptTextarea[tabname] = textarea; - } - - textarea.addEventListener("focus", function() { - activePromptTextarea[tabname] = textarea; - }); - } - - registerPrompt('txt2img', 'txt2img_prompt'); - registerPrompt('txt2img', 'txt2img_neg_prompt'); - registerPrompt('img2img', 'img2img_prompt'); - registerPrompt('img2img', 'img2img_neg_prompt'); } onUiLoaded(setupExtraNetworks); @@ -262,6 +267,106 @@ function saveCardPreview(event, tabname, filename) { event.preventDefault(); } +function extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id) { + /** + * Processes `onclick` events when user clicks on files in tree. + * + * @param event The generated event. + * @param btn The clicked `action-list-item` button. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ + var par = btn.parentElement; + var search_id = tabname + "_" + tab_id + "_extra_search"; + var type = par.getAttribute("data-tree-entry-type"); + var path = par.getAttribute("data-path"); +} + +function extraNetworksTreeProcessDirectoryClick(event, btn) { + /** + * Processes `onclick` events when user clicks on directories in tree. + * + * Here is how the tree reacts to clicks for various states: + * unselected unopened directory: Diretory is selected and expanded. + * unselected opened directory: Directory is selected. + * selected opened directory: Directory is collapsed and deselected. + * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. + * + * @param event The generated event. + * @param btn The clicked `action-list-item` button. + */ + var ul = btn.nextElementSibling; + // This is the actual target that the user clicked on within the target button. + // We use this to detect if the chevron was clicked. + var true_targ = event.target; + + function _expand_or_collapse(_ul, _btn) { + // Expands
      if it is collapsed, collapses otherwise. Updates button attributes. + if (_ul.hasAttribute("data-hidden")) { + _ul.removeAttribute("data-hidden"); + _btn.setAttribute("expanded", "true"); + } else { + _ul.setAttribute("data-hidden", ""); + _btn.setAttribute("expanded", "false"); + } + } + + function _remove_selected_from_all() { + // Removes the `selected` attribute from all buttons. + var sels = document.querySelectorAll("button.action-list-content"); + [...sels].forEach(el => { + el.removeAttribute("selected"); + }) + } + + function _select_button(_btn) { + // Removes `selected` attribute from all buttons then adds to passed button. + _remove_selected_from_all(); + _btn.setAttribute("selected", ""); + } + + // If user clicks on the chevron, then we do not select the folder. + if (true_targ.matches(".action-list-item-action--leading, .action-list-item-action-chevron")) { + _expand_or_collapse(ul, btn); + } else { + // User clicked anywhere else on the button. + if (btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden")) { + // If folder is select and open, collapse and deselect button. + _expand_or_collapse(ul, btn); + btn.removeAttribute("selected"); + } else if (!(!btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden"))) { + // If folder is open and not selected, then we don't collapse; just select. + // NOTE: Double inversion sucks but it is the clearest way to show the branching here. + _expand_or_collapse(ul, btn); + _select_button(btn); + } else { + // All other cases, just select the button. + _select_button(btn); + } + + } +} + +function extraNetworksTreeOnClick(event, tabname, tab_id) { + /** + * Handles `onclick` events for buttons within an `extra-network-tree .action-list--tree`. + * + * Determines whether the clicked button in the tree is for a file entry or a directory + * then calls the appropriate function. + * + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ + var btn = event.currentTarget; + var par = btn.parentElement; + if (par.getAttribute("data-tree-entry-type") === "file") { + extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id); + } else { + extraNetworksTreeProcessDirectoryClick(event, btn); + } +} + function extraNetworksFolderClick(event, tabs_id) { // If folder is open but not selected, we don't want to collapse it. Instead // we override the removal of the "open" attribute so that the folder is @@ -434,3 +539,10 @@ window.addEventListener("keydown", function(event) { closePopup(); } }); + +function testprint(e) { + console.log(e); +} + +const testinput = gradioApp().querySelector("#txt2img_lora_extra_search"); +testinput.addEventListener("input", testprint); \ No newline at end of file diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 093ac7b4f0c..9cf5b57fbcb 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -19,6 +19,90 @@ allowed_dirs = set() default_allowed_preview_extensions = ["png", "jpg", "jpeg", "webp", "gif"] +tree_tpl = ( + "" + "
        " + "{content}" + "
      " +) + +tree_ul_tpl = ( + "
        " + "{content}" + "
      " +) + +tree_li_dir_tpl = ( + "
    • " + "{content}" + "
    • " +) +tree_li_file_tpl = ( + "
    • " + "{content}" + "
    • " +) + +tree_btn_dir_tpl = ( + "" +) + +tree_btn_file_action_buttons_tpl = ( + "
      " + "
      " + "
      " + "
      " + "
      " + "
      " +) + +tree_btn_file_tpl = ( + "" + "" +) + + @functools.cache def allowed_preview_extensions_with_extra(extra_extensions=None): return set(default_allowed_preview_extensions) | set(extra_extensions or []) @@ -160,6 +244,7 @@ def __init__(self, title): self.extra_networks_pane_template = shared.html("extra-networks-pane.html") self.card_page_template = shared.html("extra-networks-card.html") self.card_page_minimal_template = shared.html("extra-networks-card-minimal.html") + self.tree_button_template = shared.html("extra-networks-tree-button.html") self.allow_prompt = True self.allow_negative_prompt = False self.metadata = {} @@ -279,7 +364,9 @@ def create_item_html(self, tabname: str, item: dict, template: Optional[str] = N "search_terms": search_terms_html, "sort_keys": sort_keys, "style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", - "tabname": quote_js(tabname), + "tabname": tabname, + "tab_id": self.id_page, + } if template: @@ -306,55 +393,81 @@ def create_tree_view_html(self, tabname: str) -> str: if not tree: return res - file_template = "
    • {card}
    • " - dir_template = ( - "
      " - "" - "{folder_name}" - "" - "
        {content}
      " - "
      " - ) - def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> str: """Recursively builds HTML for a tree.""" _res = "" if not data: - return "
    • DIRECTORY IS EMPTY
    • " + return ( + "
      " + "Directory is empty" + "
      " + ) for k, v in sorted(data.items(), key=lambda x: shared.natural_sort_key(x[0])): if isinstance(v, (ExtraNetworksItem,)): - item_html = self.create_item_html(tabname, v.item, self.card_page_minimal_template) - _res += file_template.format(**{"card": item_html}) + _action_buttons = tree_btn_file_action_buttons_tpl.format( + **{ + "path": quote_js(k), + "filename": quote_js(v.item["name"]), + "tabname": quote_js(tabname), + "tab_id": quote_js(self.id_page), + } + ) + _btn = tree_btn_file_tpl.format( + **{ + "label": v.item["name"], + "filter": v.item["search_terms"], + "tabname": tabname, + "tab_id": self.id_page, + "buttons": _action_buttons, + } + ) + _li = tree_li_file_tpl.format( + **{ + "hash": v.item["shorthash"], + "path": k, + "type": "file", + #"content": _btn, + "content": self.create_item_html(tabname, v.item, self.tree_button_template), + } + ) + _res += _li + #item_html = self.create_item_html(tabname, v.item, self.card_page_minimal_template) + #_res += file_template.format(**{"card": item_html}) else: - _res += dir_template.format( + _btn = tree_btn_dir_tpl.format( **{ - "attributes": "", + "label": os.path.basename(k), "tabname": tabname, - "folder_name": os.path.basename(k), - "data_path": k, - "content": _build_tree(v), + "tab_id": self.id_page, } ) + _ul = tree_ul_tpl.format(**{"content": _build_tree(v)}) + _li = tree_li_dir_tpl.format(**{"content": _btn + _ul, "path": k}) + _res += _li return _res # Add each root directory to the tree. for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])): # If root is empty, append the "disabled" attribute to the template details tag. - res += "
        " - res += dir_template.format( + btn = tree_btn_dir_tpl.format( **{ - "attributes": "open" if v else "open", + "label": os.path.basename(k), "tabname": tabname, - "folder_name": os.path.basename(k), - "data_path": k, - "content": _build_tree(v), + "tab_id": self.id_page, } ) - res += "
      " - res += "
    " - return res + ul = tree_ul_tpl.format(**{"content": _build_tree(v)}) + li = tree_li_dir_tpl.format(**{"content": btn + ul, "path": k}) + res += li + + return tree_tpl.format( + **{ + "content": res, + "tabname": tabname, + "tab_id": self.id_page, + } + ) def create_card_view_html(self, tabname): res = "" @@ -375,7 +488,7 @@ def create_html(self, tabname): tree_view_html = self.create_tree_view_html(tabname) card_view_html = self.create_card_view_html(tabname) - network_type_id = self.name.replace(" ", "_") + network_type_id = self.id_page return self.extra_networks_pane_template.format( **{ @@ -506,7 +619,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ui.pages.append(page_elem) page_elem.change( fn=lambda: None, - _js=f"function(){{applyExtraNetworkFilter({tabname}_extra_search); return []}}", + _js=f"function(){{applyExtraNetworkFilter({tabname}_{page.id_page}_extra_search); return []}}", inputs=[], outputs=[], ) @@ -517,13 +630,11 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) - edit_search = gr.Textbox('', show_label=False, elem_id=f"{tabname}_extra_search", elem_classes="search", placeholder="Search...", visible=False, interactive=True) dropdown_sort = gr.Dropdown(choices=['Path', 'Name', 'Date Created', 'Date Modified', ], value=shared.opts.extra_networks_card_order_field, elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) tab_controls = [ - edit_search, dropdown_sort, button_sortorder, button_refresh, diff --git a/style.css b/style.css index aaafaa9d7e1..8aa41088dff 100644 --- a/style.css +++ b/style.css @@ -955,15 +955,15 @@ footer { color: white; } -.extra-network-pane .copy-path-button:before { +.extra-network-pane .copy-path-button::before { content: "⎘"; } -.extra-network-pane .metadata-button:before{ +.extra-network-pane .metadata-button::before{ content: "🛈"; } -.extra-network-pane .edit-button:before{ +.extra-network-pane .edit-button::before{ content: "🛠"; } @@ -1188,102 +1188,253 @@ body.resizing .resize-handle { border-left: 1px dashed var(--border-color-primary); } -.extra-network-pane .card-minimal { - display: inline-flex; - flex-grow: 1; - position: relative; - overflow: hidden; - cursor: pointer; - font-size: 1rem; - font-weight: bold; - line-break: anywhere; +/* ========================= */ +.extra-network-pane { + display: flex; } -/* Pushes buttons to right */ -.extra-network-pane .card-minimal .name { - flex-grow: 1; +.extra-network-pane .extra-network-cards { + display: block; } -.folder-container { - margin-left: 1.5em !important; +.extra-network-pane .extra-network-tree { + display: block; + font-size: 1rem; + min-width: 25%; + border: 1px solid var(--block-border-color); + overflow: hidden; } -.file-item, -.folder-item, -.folder-item-summary { - padding-left: 0.05rem; +.extra-network-tree .action-list--tree { cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; user-select: none; - font-size: 1rem; + margin: 0; + padding: 0; } -.extra-network-pane .extra-network-tree .folder-item-summary:hover, -.extra-network-pane .extra-network-tree .file-item:hover { - -webkit-transition: all 0.1s ease-in-out; - transition: all 0.1s ease-in-out; - background-color: var(--neutral-200); +/* Remove auto indentation from tree. Will be overridden later. */ +.extra-network-tree .action-list--subgroup { + margin: 0 !important; + padding: 0 !important; + box-shadow: 0.6rem 0 0 var(--body-background-fill) inset, + 0.8rem 0 0 var(--neutral-800) inset; +} + +/* Set indentation for each depth of tree. */ +.extra-network-tree .action-list--subgroup > .action-list-item { + margin-left: 0.4rem !important; + padding-left: 0.4rem !important; } -.dark .extra-network-pane .extra-network-tree .folder-item-summary:hover, -.dark .extra-network-pane .extra-network-tree .file-item:hover { +/* Styles for tree
      elements. */ +.extra-network-tree .action-list { + +} + +/* Styles for tree
    • elements. */ +.extra-network-tree .action-list-item { + list-style: none; + position: relative; + background-color: transparent; +} + +/* Directory
        */ +.extra-network-tree .action-list-content[expanded=false]+.action-list--subgroup { + height: 0; + overflow: hidden; + visibility: hidden; + opacity: 0; +} + +.extra-network-tree .action-list-content[expanded=true]+.action-list--subgroup { + height: auto; + overflow: visible; + visibility: visible; + opacity: 1; +} + +/* File
      • */ +.extra-network-tree .action-list-item--subitem { +} + +/*
      • containing
          */ +.extra-network-tree .action-list-item--has-subitem { +} + +/* BUTTON ELEMENTS */ +/* \ No newline at end of file diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 97a97d61f2c..56c9dc4c35d 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -267,7 +267,7 @@ function extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id) { * Processes `onclick` events when user clicks on files in tree. * * @param event The generated event. - * @param btn The clicked `action-list-item` button. + * @param btn The clicked `tree-list-item` button. * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ @@ -288,7 +288,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. * * @param event The generated event. - * @param btn The clicked `action-list-item` button. + * @param btn The clicked `tree-list-item` button. * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ @@ -310,7 +310,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { function _remove_selected_from_all() { // Removes the `selected` attribute from all buttons. - var sels = document.querySelectorAll("button.action-list-content"); + var sels = document.querySelectorAll("button.tree-list-content"); [...sels].forEach(el => { el.removeAttribute("selected"); }); @@ -331,7 +331,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { // If user clicks on the chevron, then we do not select the folder. - if (true_targ.matches(".action-list-item-action--leading, .action-list-item-action-chevron")) { + if (true_targ.matches(".tree-list-item-action--leading, .tree-list-item-action-chevron")) { _expand_or_collapse(ul, btn); } else { // User clicked anywhere else on the button. @@ -356,7 +356,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { function extraNetworksTreeOnClick(event, tabname, tab_id) { /** - * Handles `onclick` events for buttons within an `extra-network-tree .action-list--tree`. + * Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`. * * Determines whether the clicked button in the tree is for a file entry or a directory * then calls the appropriate function. diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 9cf5b57fbcb..a49c6c1c54b 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -20,28 +20,28 @@ default_allowed_preview_extensions = ["png", "jpg", "jpeg", "webp", "gif"] tree_tpl = ( - " \ No newline at end of file diff --git a/html/extra-networks-tree.html b/html/extra-networks-tree.html new file mode 100644 index 00000000000..4d29b1be273 --- /dev/null +++ b/html/extra-networks-tree.html @@ -0,0 +1,42 @@ +
          +
          + +
          + +
          +
          + +
          +
          + +
          +
          +
          + {tree} +
          +
          \ No newline at end of file diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 56c9dc4c35d..cf98452a296 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -37,15 +37,20 @@ function setupExtraNetworksForTab(tabname) { return; // `continue` doesn't work in `forEach` loops. This is equivalent. } - var tabs = gradioApp().querySelector('#' + tabname + '_extra_tabs > div'); - var sort = gradioApp().getElementById(tabname + '_extra_sort'); - var sortOrder = gradioApp().getElementById(tabname + '_extra_sortorder'); - var refresh = gradioApp().getElementById(tabname + '_extra_refresh'); - var promptContainer = gradioApp().querySelector('.prompt-container-compact#' + tabname + '_prompt_container'); - var negativePrompt = gradioApp().querySelector('#' + tabname + '_neg_prompt'); - tabs.appendChild(sort); - tabs.appendChild(sortOrder); - tabs.appendChild(refresh); + var sort = gradioApp().querySelector("#" + tab_id + "_extra_sort"); + if (!sort) { + return; // `continue` doesn't work in `forEach` loops. This is equivalent. + } + + var sort_dir = gradioApp().querySelector("#" + tab_id + "_extra_sort_dir"); + if (!sort_dir) { + return; // `continue` doesn't work in `forEach` loops. This is equivalent. + } + + var refresh = gradioApp().querySelector("#" + tab_id + "_extra_refresh"); + if (!refresh) { + return; // `continue` doesn't work in `forEach` loops. This is equivalent. + } var applyFilter = function() { var searchTerm = search.value.toLowerCase(); @@ -72,8 +77,8 @@ function setupExtraNetworksForTab(tabname) { var applySort = function() { var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); - var reverse = sortOrder.classList.contains("sortReverse"); - var sortKey = sort.querySelector("input").value.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; + var reverse = sort_dir.dataset.sortdir == "Descending"; + var sortKey = sort.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; @@ -107,10 +112,7 @@ function setupExtraNetworksForTab(tabname) { }; search.addEventListener("input", applyFilter); - sortOrder.addEventListener("click", function() { - sortOrder.classList.toggle("sortReverse"); - applySort(); - }); + applySort(); applyFilter(); extraNetworksApplySort[tab_id] = applySort; @@ -274,7 +276,7 @@ function extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id) { var par = btn.parentElement; var search_id = tabname + "_" + tab_id + "_extra_search"; var type = par.getAttribute("data-tree-entry-type"); - var path = par.getAttribute("data-path"); + var path = btn.getAttribute("data-path"); } function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { @@ -310,7 +312,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { function _remove_selected_from_all() { // Removes the `selected` attribute from all buttons. - var sels = document.querySelectorAll("button.tree-list-content"); + var sels = document.querySelectorAll("div.tree-list-content"); [...sels].forEach(el => { el.removeAttribute("selected"); }); @@ -345,11 +347,11 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { // NOTE: Double inversion sucks but it is the clearest way to show the branching here. _expand_or_collapse(ul, btn); _select_button(btn, tabname, tab_id); - _update_search(tabname, tab_id, btn.parentElement.getAttribute("data-path")); + _update_search(tabname, tab_id, btn.getAttribute("data-path")); } else { // All other cases, just select the button. _select_button(btn, tabname, tab_id); - _update_search(tabname, tab_id, btn.parentElement.getAttribute("data-path")); + _update_search(tabname, tab_id, btn.getAttribute("data-path")); } } } @@ -374,6 +376,48 @@ function extraNetworksTreeOnClick(event, tabname, tab_id) { } } +function extraNetworksTreeSortOnClick(event, tabname, tab_id) { + var curr_mode = event.currentTarget.dataset.sortmode; + var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + tab_id + "_extra_sort_dir"); + var sort_dir = el_sort_dir.dataset.sortdir; + if (curr_mode == "path") { + event.currentTarget.dataset.sortmode = "name"; + event.currentTarget.dataset.sortkey = "sortName-" + sort_dir + "-640"; + event.currentTarget.setAttribute("title", "Sort by filename"); + } else if (curr_mode == "name") { + event.currentTarget.dataset.sortmode = "date_created"; + event.currentTarget.dataset.sortkey = "sortDate_created-" + sort_dir + "-640"; + event.currentTarget.setAttribute("title", "Sort by date created"); + } else if (curr_mode == "date_created") { + event.currentTarget.dataset.sortmode = "date_modified"; + event.currentTarget.dataset.sortkey = "sortDate_modified-" + sort_dir + "-640"; + event.currentTarget.setAttribute("title", "Sort by date modified"); + } else { + event.currentTarget.dataset.sortmode = "path"; + event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640"; + event.currentTarget.setAttribute("title", "Sort by path"); + } + applyExtraNetworkSort(tabname + "_" + tab_id); +} + +function extraNetworksTreeSortDirOnClick(event, tabname, tab_id) { + var curr_dir = event.currentTarget.getAttribute("data-sortdir"); + if (curr_dir == "Ascending") { + event.currentTarget.dataset.sortdir = "Descending"; + event.currentTarget.setAttribute("title", "Sort descending"); + } else { + event.currentTarget.dataset.sortdir = "Ascending"; + event.currentTarget.setAttribute("title", "Sort ascending"); + } + applyExtraNetworkSort(tabname + "_" + tab_id); +} + +function extraNetworksTreeRefreshOnClick(event, tabname, tab_id) { + console.log("refresh clicked"); + var btn_refresh_internal = gradioApp().getElementById(tabname + "_extra_refresh_internal"); + btn_refresh_internal.dispatchEvent(new Event("click")); +} + var globalPopup = null; var globalPopupInner = null; diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index a49c6c1c54b..4ba2bea1a8c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -19,50 +19,6 @@ allowed_dirs = set() default_allowed_preview_extensions = ["png", "jpg", "jpeg", "webp", "gif"] -tree_tpl = ( - "" - "
            " - "{content}" - "
          " -) - -tree_ul_tpl = ( - "
            " - "{content}" - "
          " -) - -tree_li_dir_tpl = ( - "
        • " - "{content}" - "
        • " -) -tree_li_file_tpl = ( - "
        • " - "{content}" - "
        • " -) - -action_list_item_action_leading = ( - "" - "" - "" -) - @functools.cache def allowed_preview_extensions_with_extra(extra_extensions=None): return set(default_allowed_preview_extensions) | set(extra_extensions or []) @@ -201,9 +157,13 @@ def __init__(self, title): self.title = title self.name = title.lower() self.id_page = self.name.replace(" ", "_") - self.extra_networks_pane_template = shared.html("extra-networks-pane.html") - self.card_page_template = shared.html("extra-networks-card.html") - self.tree_button_template = shared.html("extra-networks-tree-button.html") + self.pane_tpl = shared.html("extra-networks-pane.html") + self.tree_tpl = shared.html("extra-networks-tree.html") + self.card_tpl = shared.html("extra-networks-card.html") + self.btn_tree_tpl = shared.html("extra-networks-tree-button.html") + self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") + self.btn_metadata_tpl = shared.html("extra-networks-metadata-button.html") + self.btn_edit_item_tpl = shared.html("extra-networks-edit-item-button.html") self.allow_prompt = True self.allow_negative_prompt = False self.metadata = {} @@ -268,12 +228,8 @@ def create_item_html( onclick = item.get("onclick", None) if onclick is None: - print("HERE") - print("TABNAME:", tabname) - print("PROMPT:", item["prompt"]) - print("NEG_PROMPT:", item.get("negative_prompt", "")) - print("ALLOW_NEG:", self.allow_negative_prompt) - onclick_js_tpl = "cardClicked('{tabname}', '{prompt}', '{neg_prompt}', '{allow_neg}');" + # Don't quote prompt/neg_prompt since they are stored as js strings already. + onclick_js_tpl = "cardClicked('{tabname}', {prompt}, {neg_prompt}, '{allow_neg}');" onclick = onclick_js_tpl.format( **{ "tabname": tabname, @@ -284,15 +240,23 @@ def create_item_html( ) onclick = html.escape(onclick) - - copy_path_button = f"
          " - - metadata_button = "" + btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": item["filename"]}) + btn_metadata = "" metadata = item.get("metadata") if metadata: - metadata_button = f"" - - edit_button = f"
          " + btn_metadata = self.btn_metadata_tpl.format( + **{ + "page_id": self.id_page, + "name": html.escape(item["name"]), + } + ) + btn_edit_item = self.btn_edit_item_tpl.format( + **{ + "tabname": tabname, + "page_id": self.id_page, + "name": html.escape(item["name"]), + } + ) local_path = "" filename = item.get("filename", "") @@ -334,11 +298,11 @@ def create_item_html( args = { "background_image": background_image, "card_clicked": onclick, - "copy_path_button": copy_path_button, + "copy_path_button": btn_copy_path, "description": (item.get("description") or "" if shared.opts.extra_networks_card_show_desc else ""), - "edit_button": edit_button, + "edit_button": btn_edit_item, "local_preview": quote_js(item["local_preview"]), - "metadata_button": metadata_button, + "metadata_button": btn_metadata, "name": html.escape(item["name"]), "prompt": item.get("prompt", None), "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', @@ -355,6 +319,57 @@ def create_item_html( else: return args + def create_tree_dir_item_html(self, tabname: str, dir_path: str, content: Optional[str] = None) -> Optional[str]: + if not content: + return None + + btn = self.btn_tree_tpl.format( + **{ + "search_terms": "", + "subclass": "tree-list-content-dir", + "tabname": tabname, + "tab_id": self.id_page, + "onclick_extra": "", + "data_path": dir_path, + "data_hash": "", + "action_list_item_action_leading": "", + "action_list_item_visual_leading": "🗀", + "action_list_item_label": os.path.basename(dir_path), + "action_list_item_visual_trailing": "", + "action_list_item_action_trailing": "", + } + ) + ul = f"
            {content}
          " + return f"
        • {btn + ul}
        • " + + def create_tree_file_item_html(self, tabname: str, item_name: str, item: dict) -> str: + item_html_args = self.create_item_html(tabname, item) + action_buttons = "".join( + [ + item_html_args["copy_path_button"], + item_html_args["metadata_button"], + item_html_args["edit_button"], + ] + ) + action_buttons = f"
          {action_buttons}
          " + btn = self.btn_tree_tpl.format( + **{ + "search_terms": "", + "subclass": "tree-list-content-file", + "tabname": tabname, + "tab_id": self.id_page, + "onclick_extra": item_html_args["card_clicked"], + "data_path": item_name, + "data_hash": item["shorthash"], + "action_list_item_action_leading": "", + "action_list_item_visual_leading": "🗎", + "action_list_item_label": item["name"], + "action_list_item_visual_trailing": "", + "action_list_item_action_trailing": action_buttons, + } + ) + return f"
        • {btn}
        • " + def create_tree_view_html(self, tabname: str) -> str: """Generates HTML for displaying folders in a tree view. @@ -385,57 +400,9 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional for k, v in sorted(data.items(), key=lambda x: shared.natural_sort_key(x[0])): if isinstance(v, (ExtraNetworksItem,)): - _item_html_args = self.create_item_html(tabname, v.item) - _action_buttons = "".join( - [ - _item_html_args["copy_path_button"], - _item_html_args["metadata_button"], - _item_html_args["edit_button"], - ] - ) - _action_buttons = f"
          {_action_buttons}
          " - _btn = self.tree_button_template.format( - **{ - "search_terms": "", - "subclass": "tree-list-content-file", - "tabname": tabname, - "tab_id": self.id_page, - "onclick_extra": _item_html_args["card_clicked"], - "action_list_item_action_leading": action_list_item_action_leading, - "action_list_item_visual_leading": "🗎", - "action_list_item_label": v.item["name"], - "action_list_item_visual_trailing": "", - "action_list_item_action_trailing": _action_buttons, - } - ) - - _li = tree_li_file_tpl.format( - **{ - "hash": v.item["shorthash"], - "path": k, - "type": "file", - "content": _btn, - } - ) - _file_li.append(_li) + _file_li.append(self.create_tree_file_item_html(tabname, k, v.item)) else: - _btn = self.tree_button_template.format( - **{ - "search_terms": "", - "subclass": "tree-list-content-dir", - "tabname": tabname, - "tab_id": self.id_page, - "onclick_extra": "", - "action_list_item_action_leading": action_list_item_action_leading, - "action_list_item_visual_leading": "🗀", - "action_list_item_label": os.path.basename(k), - "action_list_item_visual_trailing": "", - "action_list_item_action_trailing": "", - } - ) - _ul = tree_ul_tpl.format(**{"content": _build_tree(v)}) - _li = tree_li_dir_tpl.format(**{"content": _btn + _ul, "path": k}) - _dir_li.append(_li) + _dir_li.append(self.create_tree_dir_item_html(tabname, k, _build_tree(v))) # Directories should always be displayed before files. return "".join(_dir_li) + "".join(_file_li) @@ -443,31 +410,15 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional # Add each root directory to the tree. for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])): # If root is empty, append the "disabled" attribute to the template details tag. - btn = self.tree_button_template.format( - **{ - "search_terms": "", - "subclass": "tree-list-content-dir", - "tabname": tabname, - "tab_id": self.id_page, - "onclick_extra": "", - "action_list_item_action_leading": action_list_item_action_leading, - "action_list_item_visual_leading": "🗀", - "action_list_item_label": os.path.basename(k), - "action_list_item_visual_trailing": "", - "action_list_item_action_trailing": "", - } - ) - subtree = _build_tree(v) - if subtree: - ul = tree_ul_tpl.format(**{"content": _build_tree(v)}) - li = tree_li_dir_tpl.format(**{"content": btn + ul, "path": k}) - res += li + item_html = self.create_tree_dir_item_html(tabname, k, _build_tree(v)) + if item_html: + res += item_html - return tree_tpl.format( + return self.tree_tpl.format( **{ - "content": res, "tabname": tabname, "tab_id": self.id_page, + "tree": f"
            {res}
          " } ) @@ -475,8 +426,7 @@ def create_card_view_html(self, tabname): res = "" self.items = {x["name"]: x for x in self.list_items()} for item in self.items.values(): - print("HEEEERRE:", item) - res += self.create_item_html(tabname, item, self.card_page_template) + res += self.create_item_html(tabname, item, self.card_tpl) if res == "": dirs = "".join([f"
        • {x}
        • " for x in self.allowed_directories_for_previews()]) @@ -493,7 +443,7 @@ def create_html(self, tabname): card_view_html = self.create_card_view_html(tabname) network_type_id = self.id_page - return self.extra_networks_pane_template.format( + return self.pane_tpl.format( **{ "tabname": tabname, "network_type_id": network_type_id, @@ -612,6 +562,8 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs = [] + button_refresh = gr.Button("Refresh", elem_id=tabname+"_extra_refresh_internal", visible=False) + for page in ui.stored_extra_pages: with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab: with gr.Column(elem_id=f"{tabname}_{page.id_page}_prompts", elem_classes=["extra-page-prompts"]): @@ -633,51 +585,9 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs.append(tab) - dropdown_sort = gr.Dropdown(choices=['Path', 'Name', 'Date Created', 'Date Modified', ], value=shared.opts.extra_networks_card_order_field, elem_id=tabname+"_extra_sort", elem_classes="sort", multiselect=False, visible=False, show_label=False, interactive=True, label=tabname+"_extra_sort_order") - button_sortorder = ToolButton(switch_values_symbol, elem_id=tabname+"_extra_sortorder", elem_classes=["sortorder"] + ([] if shared.opts.extra_networks_card_order == "Ascending" else ["sortReverse"]), visible=False, tooltip="Invert sort order") - button_refresh = gr.Button('Refresh', elem_id=tabname+"_extra_refresh", visible=False) - - tab_controls = [ - dropdown_sort, - button_sortorder, - button_refresh, - ] - ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) - for tab in unrelated_tabs: - tab.select( - fn=lambda: [gr.update(visible=False) for _ in tab_controls], - _js=f"function(){{ extraNetworksUnrelatedTabSelected('{tabname}'); }}", - inputs=[], - outputs=tab_controls, - show_progress=False, - ) - - for page, tab in zip(ui.stored_extra_pages, related_tabs): - allow_prompt = "true" if page.allow_prompt else "false" - allow_negative_prompt = "true" if page.allow_negative_prompt else "false" - - jscode = ( - "extraNetworksTabSelected(" - f"'{tabname}', " - f"'{tabname}_{page.id_page}_prompts', " - f"'{allow_prompt}', " - f"'{allow_negative_prompt}'" - ");" - ) - - tab.select( - fn=lambda: [gr.update(visible=True) for _ in tab_controls], - _js="function(){ " + jscode + " }", - inputs=[], - outputs=tab_controls, - show_progress=False, - ) - - dropdown_sort.change(fn=lambda: None, _js="function(){ applyExtraNetworkSort('" + tabname + "'); }") - def create_html(): ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] @@ -693,6 +603,8 @@ def refresh(): return ui.pages_contents interface.load(fn=pages_html, inputs=[], outputs=ui.pages) + # NOTE: Event is manually fired in extraNetworks.js:extraNetworksTreeRefreshOnClick() + # button is unused and hidden at all times. Only used in order to fire this event. button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages) return ui diff --git a/style.css b/style.css index 2dafe97fd87..08573248654 100644 --- a/style.css +++ b/style.css @@ -1196,17 +1196,33 @@ body.resizing .resize-handle { overflow: hidden; } -.extra-network-tree .tree-list--tree { - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - margin: 0; +.extra-network-tree .tree-list { + margin: 0 0.25rem; padding: 0; - margin-left: 0.25rem; } +.extra-network-tree .tree-list .tree-list-controls { + position: relative; + display: grid; + width: 100%; + padding: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + font-size: 1rem; + text-align: left; + user-select: none; + background-color: transparent; + border: none; + transition: background 33.333ms linear; + grid-template-rows: min-content; + grid-template-areas: "tree-list-controls-col-0 tree-list-controls-col-1 tree-list-controls-col-2 tree-list-controls-col-3"; + grid-template-columns: minmax(0, auto) min-content min-content min-content; + grid-gap: 0.1rem; + align-items: start; +} + +.extra-network-tree .tree-list--tree {} + /* Remove auto indentation from tree. Will be overridden later. */ .extra-network-tree .tree-list--subgroup { margin: 0 !important; @@ -1221,9 +1237,6 @@ body.resizing .resize-handle { padding-left: 0.4rem !important; } -/* Styles for tree
            elements. */ -.extra-network-tree .tree-list {} - /* Styles for tree
          • elements. */ .extra-network-tree .tree-list-item { list-style: none; @@ -1288,26 +1301,182 @@ body.resizing .resize-handle { padding-top: 0.5rem !important; } -.dark .extra-network-tree button.tree-list-content:hover { +.dark .extra-network-tree div.tree-list-content:hover { -webkit-transition: all 0.05s ease-in-out; transition: all 0.05s ease-in-out; background-color: var(--neutral-800); } -.dark .extra-network-tree button.tree-list-content[selected] { +.dark .extra-network-tree div.tree-list-content[selected] { background-color: var(--neutral-700); } -.extra-network-tree button.tree-list-content:hover { +.extra-network-tree div.tree-list-content:hover { -webkit-transition: all 0.05s ease-in-out; transition: all 0.05s ease-in-out; background-color: var(--neutral-200); } -.extra-network-tree button.tree-list-content[selected] { +.extra-network-tree div.tree-list-content[selected] { background-color: var(--neutral-300); } +/* ==== CHEVRON ICON ACTIONS ==== */ +/* Define the animation for the arrow when it is clicked. */ +.extra-network-tree .tree-list-content-dir[expanded=false] .tree-list-item-action-chevron { + -ms-transform: rotate(135deg); + -webkit-transform: rotate(135deg); + transform: rotate(135deg); + transition: transform 0.2s; +} + +.extra-network-tree .tree-list-content-dir[expanded=true] .tree-list-item-action-chevron { + -ms-transform: rotate(225deg); + -webkit-transform: rotate(225deg); + transform: rotate(225deg); + transition: transform 0.2s; +} + +.tree-list-item-action-chevron { + display: inline-flex; + /* Uses box shadow to generate a pseudo chevron `>` icon. */ + padding: 0.3rem; + box-shadow: 0.1rem 0.1rem 0 0 var(--neutral-200) inset; + transform: rotate(135deg); +} + +/* ==== SEARCH INPUT ACTIONS ==== */ +/* Add icon to left side of */ +.extra-network-tree .tree-list-controls .tree-list-search::before { + content: "🔎︎"; + position: absolute; + margin: 0.5rem; + font-size: 1rem; + color: var(--input-placeholder-color); +} + +.extra-network-tree .tree-list-controls .tree-list-search { + display: inline-flex; + grid-area: tree-list-controls-col-0; + position: relative; + margin: 0.5rem; +} + +.extra-network-tree .tree-list-controls .tree-list-search .tree-list-search-text { + border: 1px solid var(--button-secondary-border-color); + border-radius: 0.5rem; + color: var(--button-secondary-text-color); + background-color: transparent; + width: 100%; + padding-left: 2rem; + line-height: 1rem; +} + +/* clear button (x on right side) styling */ +.extra-network-tree .tree-list-controls .tree-list-search .tree-list-search-text::-webkit-search-cancel-button { + -webkit-appearance: none; + appearance: none; + cursor: pointer; + height: 1rem; + width: 1rem; + mask-image: url('data:image/svg+xml,'); + mask-repeat: no-repeat; + mask-position: center center; + mask-size: 100%; + background-color: var(--input-placeholder-color); +} + +/* ==== SORT ICON ACTIONS ==== */ +.extra-network-tree .tree-list-controls .tree-list-sort { + grid-area: tree-list-controls-col-1; + padding: 0.25rem; + display: inline-flex; + cursor: pointer; + justify-self: center; + align-self: center; +} + +.extra-network-tree .tree-list-controls .tree-list-sort .tree-list-sort-icon { + height: 1.5rem; + width: 1.5rem; + mask-repeat: no-repeat; + mask-position: center center; + mask-size: 100%; + background-color: var(--input-placeholder-color); +} + +.extra-network-tree .tree-list-sort[data-sortmode="path"] .tree-list-sort-icon { + mask-image: url('data:image/svg+xml,'); +} + +.extra-network-tree .tree-list-sort[data-sortmode="name"] .tree-list-sort-icon { + mask-image: url('data:image/svg+xml,'); +} + +.extra-network-tree .tree-list-sort[data-sortmode="date_created"] .tree-list-sort-icon { + mask-image: url('data:image/svg+xml,'); +} + +.extra-network-tree .tree-list-sort[data-sortmode="date_modified"] .tree-list-sort-icon { + mask-image: url('data:image/svg+xml,'); +} + +/* ==== SORT DIRECTION ICON ACTIONS ==== */ +.extra-network-tree .tree-list-controls .tree-list-sort-dir { + grid-area: tree-list-controls-col-2; + padding: 0.25rem; + display: inline-flex; + cursor: pointer; + justify-self: center; + align-self: center; +} + +.extra-network-tree .tree-list-controls .tree-list-sort-dir .tree-list-sort-dir-icon { + height: 1.5rem; + width: 1.5rem; + mask-repeat: no-repeat; + mask-position: center center; + mask-size: 100%; + background-color: var(--input-placeholder-color); +} + +.extra-network-tree .tree-list-sort-dir[data-sortdir="Ascending"] .tree-list-sort-dir-icon { + mask-image: url('data:image/svg+xml,'); +} + +.extra-network-tree .tree-list-sort-dir[data-sortdir="Descending"] .tree-list-sort-dir-icon { + mask-image: url('data:image/svg+xml,'); +} + +/* ==== REFRESH ICON ACTIONS ==== */ +.extra-network-tree .tree-list-controls .tree-list-refresh { + grid-area: tree-list-controls-col-3; + padding: 0.25rem; + display: inline-flex; + cursor: pointer; + justify-self: center; + align-self: center; +} + +.extra-network-tree .tree-list-controls .tree-list-refresh .tree-list-refresh-icon { + height: 1.5rem; + width: 1.5rem; + mask-image: url('data:image/svg+xml,'); + mask-repeat: no-repeat; + mask-position: center center; + mask-size: 100%; + background-color: var(--input-placeholder-color); +} + +.extra-network-tree .tree-list-refresh-icon:active { + -ms-transform: rotate(180deg); + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + transition: transform 0.2s; +} + +/* ==== TREE GRID CONFIG ==== */ + /* Text for button. */ .extra-network-tree .tree-list-item-label { position: relative; @@ -1332,6 +1501,7 @@ body.resizing .resize-handle { align-items: right; } + /* Icon for button when it is before label. */ .extra-network-tree .tree-list-item-visual--leading { grid-area: leading-visual; @@ -1348,7 +1518,7 @@ body.resizing .resize-handle { /* Dropdown arrow for button. */ .extra-network-tree .tree-list-item-action--leading { - margin-right: 0.2rem; + margin-right: 0.5rem; margin-left: 0.2rem; } @@ -1356,30 +1526,6 @@ body.resizing .resize-handle { visibility: hidden; } -/* Define the animation for the arrow when it is clicked. */ -.extra-network-tree .tree-list-content-dir[expanded=false] .tree-list-item-action-chevron { - -ms-transform: rotate(135deg); - -webkit-transform: rotate(135deg); - transform: rotate(135deg); - transition: transform 0.2s; -} - -.extra-network-tree .tree-list-content-dir[expanded=true] .tree-list-item-action-chevron { - -ms-transform: rotate(225deg); - -webkit-transform: rotate(225deg); - transform: rotate(225deg); - transition: transform 0.2s; -} - -.tree-list-item-action-chevron { - display: inline-flex; - /* Uses box shadow to generate a pseudo chevron `>` icon. */ - padding: 0.3rem; - box-shadow: 0.1rem 0.1rem 0 0 var(--neutral-200) inset; - transform: rotate(135deg); -} - - .extra-network-tree .tree-list-item-action--leading { grid-area: leading-action; } @@ -1399,41 +1545,3 @@ body.resizing .resize-handle { .extra-network-tree .tree-list-content:hover .button-row { visibility: visible; } - -/* Add icon to left side of */ -.extra-network-tree .tree-list-search::before { - content: "🔎︎"; - position: absolute; - margin: 0.5rem; - font-size: 1rem; - color: var(--input-placeholder-color); -} - -.extra-network-tree .tree-list-search { - position: relative; - margin: 0.5rem; -} - -.extra-network-tree .tree-list-search .tree-list-search-text { - border: 1px solid var(--button-secondary-border-color); - border-radius: 0.5rem; - color: var(--button-secondary-text-color); - background-color: transparent; - width: 100%; - padding-left: 2rem; - line-height: 1rem; -} - -/* clear button (x on right side) styling */ -.extra-network-tree .tree-list-search .tree-list-search-text::-webkit-search-cancel-button { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - height: 1rem; - width: 1rem; - mask-image: url('data:image/svg+xml,'); - mask-repeat: no-repeat; - mask-position: center center; - mask-size: 100%; - background-color: var(--input-placeholder-color); -} \ No newline at end of file From 1fdc18e6a01eb40889d46fab40f21aa138d64b01 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Mon, 15 Jan 2024 18:01:13 -0500 Subject: [PATCH 1836/2418] Run linting --- modules/ui_extra_networks.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 4ba2bea1a8c..06fa22afada 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -13,7 +13,6 @@ from fastapi.exceptions import HTTPException from modules.infotext_utils import image_from_url_text -from modules.ui_components import ToolButton extra_pages = [] allowed_dirs = set() @@ -225,7 +224,6 @@ def create_item_html( width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else '' background_image = f'' if preview else '' - onclick = item.get("onclick", None) if onclick is None: # Don't quote prompt/neg_prompt since they are stored as js strings already. @@ -239,7 +237,7 @@ def create_item_html( } ) onclick = html.escape(onclick) - + btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": item["filename"]}) btn_metadata = "" metadata = item.get("metadata") @@ -551,8 +549,6 @@ def tab_name_score(name): return sorted(pages, key=lambda x: tab_scores[x.name]) def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): - from modules.ui import switch_values_symbol - ui = ExtraNetworksUi() ui.pages = [] ui.pages_contents = [] @@ -588,6 +584,9 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) + for tab in unrelated_tabs: + tab.select(fn=None, _js='function(){ extraNetworksUrelatedTabSelected("' + tabname + '"); }', inputs=[], outputs=[], show_progress=False) + def create_html(): ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] From c1e04c63b3d2a5102b4cf6deddea337c8a964c53 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:18:20 +0900 Subject: [PATCH 1837/2418] callback postprocess_image_after_composite --- modules/processing.py | 5 +++++ modules/scripts.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/modules/processing.py b/modules/processing.py index dcc807fe38d..2c3365a026f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1029,6 +1029,11 @@ def infotext(index=0, use_main_prompt=False): image = apply_overlay(image, p.paste_to, overlay_image) + if p.scripts is not None: + pp = scripts.PostprocessImageArgs(image) + p.scripts.postprocess_image_after_composite(p, pp) + image = pp.image + if save_samples: images.save_image(image, p.outpath_samples, "", p.seeds[i], p.prompts[i], opts.samples_format, info=infotext(i), p=p) diff --git a/modules/scripts.py b/modules/scripts.py index cf938ebb972..060069cf36a 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -262,6 +262,15 @@ def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs, *args): pass + def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs, *args): + """ + Called for every image after it has been generated. + Same as postprocess_image but after inpaint_full_res composite + So that it operates on the full image instead of the inpaint_full_res crop region. + """ + + pass + def postprocess(self, p, processed, *args): """ This function is called after processing ends for AlwaysVisible scripts. @@ -856,6 +865,14 @@ def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs): except Exception: errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) + def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs): + for script in self.alwayson_scripts: + try: + script_args = p.script_args[script.args_from:script.args_to] + script.postprocess_image_after_composite(p, pp, *script_args) + except Exception: + errors.report(f"Error running postprocess_image_after_composite: {script.filename}", exc_info=True) + def before_component(self, component, **kwargs): for callback, script in self.on_before_component_elem_id.get(kwargs.get("elem_id"), []): try: From 14b9762bcab41fe0f7411498d9d3ffdc69ea1dda Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:08:07 +0900 Subject: [PATCH 1838/2418] immediately stop on second interrupt Revert "immediately stop on second interrupt" This reverts commit ab409072a1f2a9c911a63aee98a6b42081803cdc. immediately stop on second interrupt --- modules/ui_toprow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ui_toprow.py b/modules/ui_toprow.py index 1abc9117ba3..fbe705be130 100644 --- a/modules/ui_toprow.py +++ b/modules/ui_toprow.py @@ -107,8 +107,9 @@ def create_submit_box(self): ) def interrupt_function(): - if shared.state.job_count > 1 and shared.opts.interrupt_after_current: + if not shared.state.stopping_generation and shared.state.job_count > 1 and shared.opts.interrupt_after_current: shared.state.stop_generating() + gr.Info("Generation will stop after finishing this image, click again to stop immediately.") else: shared.state.interrupt() From a75dfe1c0de1564f4607a6f4ed7f4a7c00ef18a0 Mon Sep 17 00:00:00 2001 From: Arturo Albacete Date: Tue, 16 Jan 2024 19:03:48 +0100 Subject: [PATCH 1839/2418] - expand fields to include model name and hash - write these in the CSV log file - ensure old log files are updated w.r.t delimiter count --- modules/ui_common.py | 47 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index f17259c2956..cbb2495d6c9 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -36,6 +36,29 @@ def plaintext_to_html(text, classname=None): return f"

            {content}

            " if classname else f"

            {content}

            " +def update_logfile(logfile_path, fields): + import csv + + with open(logfile_path, "r", encoding="utf8", newline="") as file: + reader = csv.reader(file) + rows = list(reader) + + # blank file: leave it as is + if not rows: + return + + rows[0] = fields + + # append new fields to each row as empty values + for row in rows[1:]: + while len(row) < len(fields): + row.append("") + + with open(logfile_path, "w", encoding="utf8", newline="") as file: + writer = csv.writer(file) + writer.writerows(rows) + + def save_files(js_data, images, do_make_zip, index): import csv filenames = [] @@ -64,11 +87,31 @@ def __init__(self, d=None): os.makedirs(shared.opts.outdir_save, exist_ok=True) + fields = [ + "prompt", + "seed", + "width", + "height", + "sampler", + "cfgs", + "steps", + "filename", + "negative_prompt", + "sd_model_name", + "sd_model_hash", + ] + logfile_path = os.path.join(shared.opts.outdir_save, "log.csv") + + # NOTE: ensure csv integrity when fields are added by + # updating headers and padding with delimeters where needed + if os.path.exists(logfile_path): + update_logfile(logfile_path, fields) + with open(os.path.join(shared.opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file: at_start = file.tell() == 0 writer = csv.writer(file) if at_start: - writer.writerow(["prompt", "seed", "width", "height", "sampler", "cfgs", "steps", "filename", "negative_prompt"]) + writer.writerow(fields) for image_index, filedata in enumerate(images, start_index): image = image_from_url_text(filedata) @@ -86,7 +129,7 @@ def __init__(self, d=None): filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) - writer.writerow([data["prompt"], data["seed"], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], data["negative_prompt"]]) + writer.writerow([data["prompt"], data["seed"], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], data["negative_prompt"], data["sd_model_name"], data["sd_model_hash"]]) # Make Zip if do_make_zip: From 315e40a49c32438551ed6b66138acdf664ecdbc8 Mon Sep 17 00:00:00 2001 From: Arturo Albacete Date: Tue, 16 Jan 2024 19:11:28 +0100 Subject: [PATCH 1840/2418] reuse variable for log file path --- modules/ui_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index cbb2495d6c9..1db72092d59 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -107,7 +107,7 @@ def __init__(self, d=None): if os.path.exists(logfile_path): update_logfile(logfile_path, fields) - with open(os.path.join(shared.opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file: + with open(logfile_path, "a", encoding="utf8", newline='') as file: at_start = file.tell() == 0 writer = csv.writer(file) if at_start: From 4f9626703345ced77935e6bbb06de0b4522d53b7 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Tue, 16 Jan 2024 13:35:01 -0500 Subject: [PATCH 1841/2418] Finish cleanup. --- html/extra-networks-card-minimal.html | 4 - html/extra-networks-card.html | 10 +- html/extra-networks-edit-item-button.html | 2 +- html/extra-networks-metadata-button.html | 2 +- html/extra-networks-pane.html | 6 +- html/extra-networks-tree-button.html | 3 +- html/extra-networks-tree.html | 14 +- javascript/extraNetworks.js | 170 +++++++++++---------- modules/ui_extra_networks.py | 162 +++++++++++++++----- modules/ui_extra_networks_user_metadata.py | 2 +- style.css | 81 +++++++--- 11 files changed, 288 insertions(+), 168 deletions(-) delete mode 100644 html/extra-networks-card-minimal.html diff --git a/html/extra-networks-card-minimal.html b/html/extra-networks-card-minimal.html deleted file mode 100644 index d66df7dfb3f..00000000000 --- a/html/extra-networks-card-minimal.html +++ /dev/null @@ -1,4 +0,0 @@ -
            - {name} - {copy_path_button}{metadata_button}{edit_button} -
            diff --git a/html/extra-networks-card.html b/html/extra-networks-card.html index ca683dc47ec..f1d959a6733 100644 --- a/html/extra-networks-card.html +++ b/html/extra-networks-card.html @@ -1,9 +1,9 @@ -
            +
            {background_image}
            {copy_path_button}{metadata_button}{edit_button}
            -
            -
            {search_terms}
            - {name} - {description} +
            +
            {search_terms}
            + {name} + {description}
            diff --git a/html/extra-networks-edit-item-button.html b/html/extra-networks-edit-item-button.html index 7d2677d9f14..0fe43082ad1 100644 --- a/html/extra-networks-edit-item-button.html +++ b/html/extra-networks-edit-item-button.html @@ -1,4 +1,4 @@
            + onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}', '{name}')">
            \ No newline at end of file diff --git a/html/extra-networks-metadata-button.html b/html/extra-networks-metadata-button.html index ad6d6f41662..285b5b3b658 100644 --- a/html/extra-networks-metadata-button.html +++ b/html/extra-networks-metadata-button.html @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 20cf6686755..bf46ca16305 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,8 +1,8 @@ -
            -
            +
            +
            {tree_html}
            -
            +
            {items_html}
            \ No newline at end of file diff --git a/html/extra-networks-tree-button.html b/html/extra-networks-tree-button.html index 20a9b0b863f..9dc2e2a40c8 100644 --- a/html/extra-networks-tree-button.html +++ b/html/extra-networks-tree-button.html @@ -1,8 +1,7 @@
            diff --git a/html/extra-networks-tree.html b/html/extra-networks-tree.html index 4d29b1be273..23f6af1058c 100644 --- a/html/extra-networks-tree.html +++ b/html/extra-networks-tree.html @@ -2,36 +2,36 @@
            diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index cf98452a296..a3f003bf475 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -31,25 +31,15 @@ function setupExtraNetworksForTab(tabname) { var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs'); this_tab.classList.add('extra-networks'); this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) { - var tab_id = elem.getAttribute("id"); - var search = gradioApp().querySelector("#" + tab_id + "_extra_search"); - if (!search) { - return; // `continue` doesn't work in `forEach` loops. This is equivalent. - } - - var sort = gradioApp().querySelector("#" + tab_id + "_extra_sort"); - if (!sort) { - return; // `continue` doesn't work in `forEach` loops. This is equivalent. - } - - var sort_dir = gradioApp().querySelector("#" + tab_id + "_extra_sort_dir"); - if (!sort_dir) { - return; // `continue` doesn't work in `forEach` loops. This is equivalent. - } - - var refresh = gradioApp().querySelector("#" + tab_id + "_extra_refresh"); - if (!refresh) { - return; // `continue` doesn't work in `forEach` loops. This is equivalent. + var extra_networks_tabname = elem.id; + var search = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_search"); + var sort_mode = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort"); + var sort_dir = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort_dir"); + var refresh = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_refresh"); + + // If any of the buttons above don't exist, we want to skip this iteration of the loop. + if (!search || !sort_mode || !sort_dir || !refresh) { + return; // `return` is equivalent of `continue` but for forEach loops. } var applyFilter = function() { @@ -78,14 +68,14 @@ function setupExtraNetworksForTab(tabname) { var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); var reverse = sort_dir.dataset.sortdir == "Descending"; - var sortKey = sort.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; + var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; - if (sortKeyStore == sort.dataset.sortkey) { + if (sortKeyStore == sort_mode.dataset.sortkey) { return; } - sort.dataset.sortkey = sortKeyStore; + sort_mode.dataset.sortkey = sortKeyStore; cards.forEach(function(card) { card.originalParentElement = card.parentElement; @@ -115,8 +105,8 @@ function setupExtraNetworksForTab(tabname) { applySort(); applyFilter(); - extraNetworksApplySort[tab_id] = applySort; - extraNetworksApplyFilter[tab_id] = applyFilter; + extraNetworksApplySort[extra_networks_tabname] = applySort; + extraNetworksApplyFilter[extra_networks_tabname] = applyFilter; }); registerPrompt(tabname, tabname + "_prompt"); @@ -148,14 +138,6 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp } } -function clearSearch(tabname) { - // Clear search box. - var tab_id = tabname + "_extra_search"; - var searchTextarea = gradioApp().querySelector("#" + tab_id + ' > label > textarea'); - searchTextarea.value = ""; - updateInput(searchTextarea); -} - function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate) extraNetworksMovePromptToTab(tabname, '', false, false); @@ -264,22 +246,20 @@ function saveCardPreview(event, tabname, filename) { event.preventDefault(); } -function extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id) { +function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) { /** * Processes `onclick` events when user clicks on files in tree. * - * @param event The generated event. - * @param btn The clicked `tree-list-item` button. - * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. - * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + * @param event The generated event. + * @param btn The clicked `tree-list-item` button. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ - var par = btn.parentElement; - var search_id = tabname + "_" + tab_id + "_extra_search"; - var type = par.getAttribute("data-tree-entry-type"); - var path = btn.getAttribute("data-path"); + // NOTE: Currently unused. + return; } -function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { +function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname) { /** * Processes `onclick` events when user clicks on directories in tree. * @@ -289,10 +269,10 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { * selected opened directory: Directory is collapsed and deselected. * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. * - * @param event The generated event. - * @param btn The clicked `tree-list-item` button. - * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. - * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + * @param event The generated event. + * @param btn The clicked `tree-list-item` button. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ var ul = btn.nextElementSibling; // This is the actual target that the user clicked on within the target button. @@ -301,12 +281,12 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { function _expand_or_collapse(_ul, _btn) { // Expands
              if it is collapsed, collapses otherwise. Updates button attributes. - if (_ul.hasAttribute("data-hidden")) { - _ul.removeAttribute("data-hidden"); - _btn.setAttribute("expanded", "true"); + if (_ul.hasAttribute("hidden")) { + _ul.removeAttribute("hidden"); + _btn.dataset.expanded = ""; } else { - _ul.setAttribute("data-hidden", ""); - _btn.setAttribute("expanded", "false"); + _ul.setAttribute("hidden", ""); + delete _btn.dataset.expanded; } } @@ -314,19 +294,19 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { // Removes the `selected` attribute from all buttons. var sels = document.querySelectorAll("div.tree-list-content"); [...sels].forEach(el => { - el.removeAttribute("selected"); + delete el.dataset.selected; }); } function _select_button(_btn) { - // Removes `selected` attribute from all buttons then adds to passed button. + // Removes `data-selected` attribute from all buttons then adds to passed button. _remove_selected_from_all(); - _btn.setAttribute("selected", ""); + _btn.dataset.selected = ""; } - function _update_search(_tabname, _tab_id, _search_text) { + function _update_search(_tabname, _extra_networks_tabname, _search_text) { // Update search input with select button's path. - var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + tab_id + "_extra_search"); + var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search"); search_input_elem.value = _search_text; updateInput(search_input_elem); } @@ -337,48 +317,58 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { _expand_or_collapse(ul, btn); } else { // User clicked anywhere else on the button. - if (btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden")) { + if ("selected" in btn.dataset && !(ul.hasAttribute("hidden"))) { // If folder is select and open, collapse and deselect button. _expand_or_collapse(ul, btn); - btn.removeAttribute("selected"); - _update_search(tabname, tab_id, ""); - } else if (!(!btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden"))) { + delete btn.dataset.selected; + _update_search(tabname, extra_networks_tabname, ""); + } else if (!(!("selected" in btn.dataset) && !(ul.hasAttribute("hidden")))) { // If folder is open and not selected, then we don't collapse; just select. // NOTE: Double inversion sucks but it is the clearest way to show the branching here. _expand_or_collapse(ul, btn); - _select_button(btn, tabname, tab_id); - _update_search(tabname, tab_id, btn.getAttribute("data-path")); + _select_button(btn, tabname, extra_networks_tabname); + _update_search(tabname, extra_networks_tabname, btn.dataset.path); } else { // All other cases, just select the button. - _select_button(btn, tabname, tab_id); - _update_search(tabname, tab_id, btn.getAttribute("data-path")); + _select_button(btn, tabname, extra_networks_tabname); + _update_search(tabname, extra_networks_tabname, btn.dataset.path); } } } -function extraNetworksTreeOnClick(event, tabname, tab_id) { +function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) { /** * Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`. * * Determines whether the clicked button in the tree is for a file entry or a directory * then calls the appropriate function. * - * @param event The generated event. - * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. - * @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ var btn = event.currentTarget; var par = btn.parentElement; - if (par.getAttribute("data-tree-entry-type") === "file") { - extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id); + if (par.dataset.treeEntryType === "file") { + extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname); } else { - extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id); + extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname); } } -function extraNetworksTreeSortOnClick(event, tabname, tab_id) { +function extraNetworksTreeSortOnClick(event, tabname, extra_networks_tabname) { + /** + * Handles `onclick` events for the Sort Mode button. + * + * Modifies the data attributes of the Sort Mode button to cycle between + * various sorting modes. + * + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ var curr_mode = event.currentTarget.dataset.sortmode; - var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + tab_id + "_extra_sort_dir"); + var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_sort_dir"); var sort_dir = el_sort_dir.dataset.sortdir; if (curr_mode == "path") { event.currentTarget.dataset.sortmode = "name"; @@ -397,23 +387,43 @@ function extraNetworksTreeSortOnClick(event, tabname, tab_id) { event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640"; event.currentTarget.setAttribute("title", "Sort by path"); } - applyExtraNetworkSort(tabname + "_" + tab_id); + applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } -function extraNetworksTreeSortDirOnClick(event, tabname, tab_id) { - var curr_dir = event.currentTarget.getAttribute("data-sortdir"); - if (curr_dir == "Ascending") { +function extraNetworksTreeSortDirOnClick(event, tabname, extra_networks_tabname) { + /** + * Handles `onclick` events for the Sort Direction button. + * + * Modifies the data attributes of the Sort Direction button to cycle between + * ascending and descending sort directions. + * + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ + if (event.currentTarget.dataset.sortdir == "Ascending") { event.currentTarget.dataset.sortdir = "Descending"; event.currentTarget.setAttribute("title", "Sort descending"); } else { event.currentTarget.dataset.sortdir = "Ascending"; event.currentTarget.setAttribute("title", "Sort ascending"); } - applyExtraNetworkSort(tabname + "_" + tab_id); + applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } -function extraNetworksTreeRefreshOnClick(event, tabname, tab_id) { - console.log("refresh clicked"); +function extraNetworksTreeRefreshOnClick(event, tabname, extra_networks_tabname) { + /** + * Handles `onclick` events for the Refresh Page button. + * + * In order to actually call the python functions in `ui_extra_networks.py` + * to refresh the page, we created an empty gradio button in that file with an + * event handler that refreshes the page. So what this function here does + * is it manually raises a `click` event on that button. + * + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ var btn_refresh_internal = gradioApp().getElementById(tabname + "_extra_refresh_internal"); btn_refresh_internal.dispatchEvent(new Event("click")); } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 06fa22afada..a03207b2bc0 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -155,7 +155,14 @@ class ExtraNetworksPage: def __init__(self, title): self.title = title self.name = title.lower() - self.id_page = self.name.replace(" ", "_") + # This is the actual name of the extra networks tab (not txt2img/img2img). + self.extra_networks_tabname = self.name.replace(" ", "_") + self.allow_prompt = True + self.allow_negative_prompt = False + self.metadata = {} + self.items = {} + self.lister = util.MassFileLister() + # HTML Templates self.pane_tpl = shared.html("extra-networks-pane.html") self.tree_tpl = shared.html("extra-networks-tree.html") self.card_tpl = shared.html("extra-networks-card.html") @@ -163,11 +170,6 @@ def __init__(self, title): self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") self.btn_metadata_tpl = shared.html("extra-networks-metadata-button.html") self.btn_edit_item_tpl = shared.html("extra-networks-edit-item-button.html") - self.allow_prompt = True - self.allow_negative_prompt = False - self.metadata = {} - self.items = {} - self.lister = util.MassFileLister() def refresh(self): pass @@ -202,15 +204,17 @@ def create_item_html( item: dict, template: Optional[str] = None, ) -> Union[str, dict]: - """Generates HTML for a single ExtraNetworks Item + """Generates HTML for a single ExtraNetworks Item. Args: tabname: The name of the active tab. item: Dictionary containing item information. + template: Optional template string to use. Returns: - HTML string generated for this item. - Can be empty if the item is not meant to be shown. + If a template is passed: HTML string generated for this item. + Can be empty if the item is not meant to be shown. + If no template is passed: A dictionary containing the generated item's attributes. """ metadata = item.get("metadata") if metadata: @@ -244,14 +248,14 @@ def create_item_html( if metadata: btn_metadata = self.btn_metadata_tpl.format( **{ - "page_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, "name": html.escape(item["name"]), } ) btn_edit_item = self.btn_edit_item_tpl.format( **{ "tabname": tabname, - "page_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, "name": html.escape(item["name"]), } ) @@ -307,9 +311,9 @@ def create_item_html( "search_only": " search_only" if search_only else "", "search_terms": search_terms_html, "sort_keys": sort_keys, - "style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", + "style": f"display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%", "tabname": tabname, - "tab_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, } if template: @@ -317,7 +321,32 @@ def create_item_html( else: return args - def create_tree_dir_item_html(self, tabname: str, dir_path: str, content: Optional[str] = None) -> Optional[str]: + def create_tree_dir_item_html( + self, + tabname: str, + dir_path: str, + content: Optional[str] = None, + ) -> Optional[str]: + """Generates HTML for a directory item in the tree. + + The generated HTML is of the format: + ```html +
            • +
              +
                + {content} +
              +
            • + ``` + + Args: + tabname: The name of the active tab. + dir_path: Path to the directory for this item. + content: Optional HTML string that will be wrapped by this
                . + + Returns: + HTML formatted string. + """ if not content: return None @@ -326,7 +355,7 @@ def create_tree_dir_item_html(self, tabname: str, dir_path: str, content: Option "search_terms": "", "subclass": "tree-list-content-dir", "tabname": tabname, - "tab_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, "onclick_extra": "", "data_path": dir_path, "data_hash": "", @@ -337,10 +366,32 @@ def create_tree_dir_item_html(self, tabname: str, dir_path: str, content: Option "action_list_item_action_trailing": "", } ) - ul = f"
                  {content}
                " - return f"
              • {btn + ul}
              • " + ul = f"" + return ( + "
              • " + f"{btn + ul}" + "
              • " + ) + + def create_tree_file_item_html(self, tabname: str, file_path: str, item: dict) -> str: + """Generates HTML for a file item in the tree. + + The generated HTML is of the format: + ```html +
              • + +
                +
              • + ``` - def create_tree_file_item_html(self, tabname: str, item_name: str, item: dict) -> str: + Args: + tabname: The name of the active tab. + file_path: The path to the file for this item. + item: Dictionary containing the item information. + + Returns: + HTML formatted string. + """ item_html_args = self.create_item_html(tabname, item) action_buttons = "".join( [ @@ -355,9 +406,9 @@ def create_tree_file_item_html(self, tabname: str, item_name: str, item: dict) - "search_terms": "", "subclass": "tree-list-content-file", "tabname": tabname, - "tab_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, "onclick_extra": item_html_args["card_clicked"], - "data_path": item_name, + "data_path": file_path, "data_hash": item["shorthash"], "action_list_item_action_leading": "", "action_list_item_visual_leading": "🗎", @@ -366,11 +417,17 @@ def create_tree_file_item_html(self, tabname: str, item_name: str, item: dict) - "action_list_item_action_trailing": action_buttons, } ) - return f"
              • {btn}
              • " + return ( + "
              • " + f"{btn}" + "
              • " + ) def create_tree_view_html(self, tabname: str) -> str: """Generates HTML for displaying folders in a tree view. + The generated HTML uses `extra-networks-tree.html` as a template. + Args: tabname: The name of the active tab. @@ -379,7 +436,7 @@ def create_tree_view_html(self, tabname: str) -> str: """ res = "" - # Generate HTML for the tree. + # Setup the tree dictionary. roots = self.allowed_directories_for_previews() tree_items = {v["filename"]: ExtraNetworksItem(v) for v in self.items.values()} tree = get_tree([os.path.abspath(x) for x in roots], items=tree_items) @@ -388,7 +445,17 @@ def create_tree_view_html(self, tabname: str) -> str: return res def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional[str]: - """Recursively builds HTML for a tree.""" + """Recursively builds HTML for a tree. + + Args: + data: Dictionary representing a directory tree. Can be NoneType. + Data keys should be absolute paths from the root and values + should be subdirectory trees or an ExtraNetworksItem. + + Returns: + If data is not None: HTML string + Else: None + """ if not data: return None @@ -402,25 +469,36 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional else: _dir_li.append(self.create_tree_dir_item_html(tabname, k, _build_tree(v))) - # Directories should always be displayed before files. + # Directories should always be displayed before files so we order them here. return "".join(_dir_li) + "".join(_file_li) # Add each root directory to the tree. for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])): - # If root is empty, append the "disabled" attribute to the template details tag. item_html = self.create_tree_dir_item_html(tabname, k, _build_tree(v)) - if item_html: + # Only add non-empty entries to the tree. + if item_html is not None: res += item_html return self.tree_tpl.format( **{ "tabname": tabname, - "tab_id": self.id_page, + "extra_networks_tabname": self.extra_networks_tabname, "tree": f"
                  {res}
                " } ) - def create_card_view_html(self, tabname): + def create_card_view_html(self, tabname: str) -> str: + """Generates HTML for the network Card View section for a tab. + + This HTML goes into the `extra-networks-pane.html`
                with + `id='{tabname}_{extra_networks_tabname}_cards`. + + Args: + tabname: The name of the active tab. + + Returns: + HTML formatted string. + """ res = "" self.items = {x["name"]: x for x in self.list_items()} for item in self.items.values(): @@ -433,20 +511,26 @@ def create_card_view_html(self, tabname): return res def create_html(self, tabname): + """Generates an HTML string for the current pane. + + The generated HTML uses `extra-networks-pane.html` as a template. + + Args: + tabname: The name of the active tab. + + Returns: + HTML formatted string. + """ self.lister.reset() self.metadata = {} self.items = {x["name"]: x for x in self.list_items()} - tree_view_html = self.create_tree_view_html(tabname) - card_view_html = self.create_card_view_html(tabname) - network_type_id = self.id_page - return self.pane_tpl.format( **{ "tabname": tabname, - "network_type_id": network_type_id, - "tree_html": tree_view_html, - "items_html": card_view_html, + "extra_networks_tabname": self.extra_networks_tabname, + "tree_html": self.create_tree_view_html(tabname), + "items_html": self.create_card_view_html(tabname), } ) @@ -561,16 +645,16 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): button_refresh = gr.Button("Refresh", elem_id=tabname+"_extra_refresh_internal", visible=False) for page in ui.stored_extra_pages: - with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab: - with gr.Column(elem_id=f"{tabname}_{page.id_page}_prompts", elem_classes=["extra-page-prompts"]): + with gr.Tab(page.title, elem_id=f"{tabname}_{page.extra_networks_tabname}", elem_classes=["extra-page"]) as tab: + with gr.Column(elem_id=f"{tabname}_{page.extra_networks_tabname}_prompts", elem_classes=["extra-page-prompts"]): pass - elem_id = f"{tabname}_{page.id_page}_cards_html" + elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html" page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) page_elem.change( fn=lambda: None, - _js=f"function(){{applyExtraNetworkFilter({tabname}_{page.id_page}_extra_search); return []}}", + _js=f"function(){{applyExtraNetworkFilter({tabname}_{page.extra_networks_tabname}_extra_search); return []}}", inputs=[], outputs=[], ) diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 989a649b799..2ca937fd117 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -14,7 +14,7 @@ def __init__(self, ui, tabname, page): self.ui = ui self.tabname = tabname self.page = page - self.id_part = f"{self.tabname}_{self.page.id_page}_edit_user_metadata" + self.id_part = f"{self.tabname}_{self.page.extra_networks_tabname}_edit_user_metadata" self.box = None diff --git a/style.css b/style.css index 08573248654..1090e4368ab 100644 --- a/style.css +++ b/style.css @@ -879,13 +879,6 @@ footer { margin-bottom: 1em; } -.extra-network-pane{ - height: calc(100vh - 24rem); - overflow: clip scroll; - resize: vertical; - min-height: 52rem; -} - .extra-networks > div.tab-nav{ min-height: 3.4rem; } @@ -1182,23 +1175,63 @@ body.resizing .resize-handle { /* ========================= */ .extra-network-pane { display: flex; -} - -.extra-network-pane .extra-network-cards { - display: block; + height: calc(100vh - 24rem); + resize: vertical; + min-height: 52rem; } .extra-network-pane .extra-network-tree { - display: block; + flex: 1; + flex-direction: column; + display: flex; font-size: 1rem; - min-width: 25%; border: 1px solid var(--block-border-color); - overflow: hidden; } -.extra-network-tree .tree-list { - margin: 0 0.25rem; +.extra-network-pane .extra-network-cards { + flex: 3; + overflow: clip auto !important; + border: 1px solid var(--block-border-color); +} + +.extra-network-pane .extra-network-tree .tree-list { + flex: 1; + display: flex; + flex-direction: column; padding: 0; + width: 100%; + overflow: hidden; +} + +.extra-network-pane .extra-network-tree .tree-list .tree-list-container { + flex: 1; + overflow: clip auto !important; + width: 100%; +} + + +.extra-network-pane .extra-network-cards::-webkit-scrollbar, +.extra-network-pane .tree-list-container::-webkit-scrollbar { + background-color: transparent; + width: 16px; +} + +.extra-network-pane .extra-network-cards::-webkit-scrollbar-track, +.extra-network-pane .tree-list-container::-webkit-scrollbar-track { + background-color: transparent; + background-clip: content-box; +} + +.extra-network-pane .extra-network-cards::-webkit-scrollbar-thumb, +.extra-network-pane .tree-list-container::-webkit-scrollbar-thumb { + background-color: var(--border-color-primary); + border-radius: 16px; + border: 4px solid var(--background-fill-primary); +} + +.extra-network-pane .extra-network-cards::-webkit-scrollbar-button, +.extra-network-pane .tree-list-container::-webkit-scrollbar-button { + display: none; } .extra-network-tree .tree-list .tree-list-controls { @@ -1244,17 +1277,15 @@ body.resizing .resize-handle { background-color: transparent; } -/* Directory
                  visibility based on expanded attribute. */ -.extra-network-tree .tree-list-content[expanded=false]+.tree-list--subgroup { +/* Directory
                    visibility based on data-expanded attribute. */ +.extra-network-tree .tree-list-content+.tree-list--subgroup { height: 0; - overflow: hidden; visibility: hidden; opacity: 0; } -.extra-network-tree .tree-list-content[expanded=true]+.tree-list--subgroup { +.extra-network-tree .tree-list-content[data-expanded]+.tree-list--subgroup { height: auto; - overflow: visible; visibility: visible; opacity: 1; } @@ -1307,7 +1338,7 @@ body.resizing .resize-handle { background-color: var(--neutral-800); } -.dark .extra-network-tree div.tree-list-content[selected] { +.dark .extra-network-tree div.tree-list-content[data-selected] { background-color: var(--neutral-700); } @@ -1317,20 +1348,20 @@ body.resizing .resize-handle { background-color: var(--neutral-200); } -.extra-network-tree div.tree-list-content[selected] { +.extra-network-tree div.tree-list-content[data-selected] { background-color: var(--neutral-300); } /* ==== CHEVRON ICON ACTIONS ==== */ /* Define the animation for the arrow when it is clicked. */ -.extra-network-tree .tree-list-content-dir[expanded=false] .tree-list-item-action-chevron { +.extra-network-tree .tree-list-content-dir .tree-list-item-action-chevron { -ms-transform: rotate(135deg); -webkit-transform: rotate(135deg); transform: rotate(135deg); transition: transform 0.2s; } -.extra-network-tree .tree-list-content-dir[expanded=true] .tree-list-item-action-chevron { +.extra-network-tree .tree-list-content-dir[data-expanded] .tree-list-item-action-chevron { -ms-transform: rotate(225deg); -webkit-transform: rotate(225deg); transform: rotate(225deg); From ccee26b0653b4f6778c107d68df52da27446abd2 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Tue, 16 Jan 2024 14:54:07 -0500 Subject: [PATCH 1842/2418] fix bugs --- javascript/extraNetworks.js | 28 ++++++++----------- modules/ui_extra_networks.py | 35 ++++++++++++------------ modules/ui_extra_networks_checkpoints.py | 3 +- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index a3f003bf475..caaa3fae0d2 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -31,11 +31,12 @@ function setupExtraNetworksForTab(tabname) { var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs'); this_tab.classList.add('extra-networks'); this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) { - var extra_networks_tabname = elem.id; - var search = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_search"); - var sort_mode = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort"); - var sort_dir = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort_dir"); - var refresh = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_refresh"); + // tabname_full = {tabname}_{extra_networks_tabname} + var tabname_full = elem.id; + var search = gradioApp().querySelector("#" + tabname_full + "_extra_search"); + var sort_mode = gradioApp().querySelector("#" + tabname_full + "_extra_sort"); + var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir"); + var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh"); // If any of the buttons above don't exist, we want to skip this iteration of the loop. if (!search || !sort_mode || !sort_dir || !refresh) { @@ -44,16 +45,13 @@ function setupExtraNetworksForTab(tabname) { var applyFilter = function() { var searchTerm = search.value.toLowerCase(); - gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { var searchOnly = elem.querySelector('.search_only'); - var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { return t.textContent.toLowerCase(); }).join(" "); var visible = text.indexOf(searchTerm) != -1; - if (searchOnly && searchTerm.length < 4) { visible = false; } @@ -66,7 +64,6 @@ function setupExtraNetworksForTab(tabname) { var applySort = function() { var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); - var reverse = sort_dir.dataset.sortdir == "Descending"; var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); @@ -104,9 +101,8 @@ function setupExtraNetworksForTab(tabname) { search.addEventListener("input", applyFilter); applySort(); applyFilter(); - - extraNetworksApplySort[extra_networks_tabname] = applySort; - extraNetworksApplyFilter[extra_networks_tabname] = applyFilter; + extraNetworksApplySort[tabname_full] = applySort; + extraNetworksApplyFilter[tabname_full] = applyFilter; }); registerPrompt(tabname, tabname + "_prompt"); @@ -147,12 +143,12 @@ function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt) { extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt); } -function applyExtraNetworkFilter(tabname) { - setTimeout(extraNetworksApplyFilter[tabname], 1); +function applyExtraNetworkFilter(tabname_full) { + setTimeout(extraNetworksApplyFilter[tabname_full], 1); } -function applyExtraNetworkSort(tabname) { - setTimeout(extraNetworksApplySort[tabname], 1); +function applyExtraNetworkSort(tabname_full) { + setTimeout(extraNetworksApplySort[tabname_full], 1); } var extraNetworksApplyFilter = {}; diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index a03207b2bc0..55cd1da2772 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -237,7 +237,7 @@ def create_item_html( "tabname": tabname, "prompt": item["prompt"], "neg_prompt": item.get("negative_prompt", ""), - "allow_neg": "true" if self.allow_negative_prompt else "false" + "allow_neg": str(self.allow_negative_prompt).lower(), } ) onclick = html.escape(onclick) @@ -291,7 +291,7 @@ def create_item_html( search_terms_html += search_term_template.format( **{ "style": "display: none;", - "class": "search_terms" + (" search_only" if search_only else ""), + "class": f"search_terms{' search_only' if search_only else ''}", "search_term": search_term, } ) @@ -307,7 +307,7 @@ def create_item_html( "metadata_button": btn_metadata, "name": html.escape(item["name"]), "prompt": item.get("prompt", None), - "save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {quote_js(tabname)}, {quote_js(item["local_preview"])})""") + '"', + "save_card_preview": html.escape(f"return saveCardPreview(event, '{tabname}', '{item['local_preview']}');"), "search_only": " search_only" if search_only else "", "search_terms": search_terms_html, "sort_keys": sort_keys, @@ -369,7 +369,7 @@ def create_tree_dir_item_html( ul = f"" return ( "
                  • " - f"{btn + ul}" + f"{btn}{ul}" "
                  • " ) @@ -561,7 +561,7 @@ def find_preview(self, path): Find a preview PNG for a given path (without extension) and call link_preview on it. """ - potential_files = sum([[path + "." + ext, path + ".preview." + ext] for ext in allowed_preview_extensions()], []) + potential_files = sum([[f"{path}.{ext}", f"{path}.preview.{ext}"] for ext in allowed_preview_extensions()], []) for file in potential_files: if self.lister.exists(file): @@ -642,7 +642,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): related_tabs = [] - button_refresh = gr.Button("Refresh", elem_id=tabname+"_extra_refresh_internal", visible=False) + button_refresh = gr.Button("Refresh", elem_id=f"{tabname}_extra_refresh_internal", visible=False) for page in ui.stored_extra_pages: with gr.Tab(page.title, elem_id=f"{tabname}_{page.extra_networks_tabname}", elem_classes=["extra-page"]) as tab: @@ -652,24 +652,25 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html" page_elem = gr.HTML('Loading...', elem_id=elem_id) ui.pages.append(page_elem) - page_elem.change( - fn=lambda: None, - _js=f"function(){{applyExtraNetworkFilter({tabname}_{page.extra_networks_tabname}_extra_search); return []}}", - inputs=[], - outputs=[], - ) - editor = page.create_user_metadata_editor(ui, tabname) editor.create_ui() ui.user_metadata_editors.append(editor) - related_tabs.append(tab) - ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False) - ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False) + ui.button_save_preview = gr.Button('Save preview', elem_id=f"{tabname}_save_preview", visible=False) + ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=f"{tabname}_preview_filename", visible=False) for tab in unrelated_tabs: - tab.select(fn=None, _js='function(){ extraNetworksUrelatedTabSelected("' + tabname + '"); }', inputs=[], outputs=[], show_progress=False) + tab.select(fn=None, _js=f"function(){{extraNetworksUnrelatedTabSelected('{tabname}');}}", inputs=[], outputs=[], show_progress=False) + + for page, tab in zip(ui.stored_extra_pages, related_tabs): + jscode = ( + "function(){{" + f"extraNetworksTabSelected('{tabname}', '{tabname}_{page.extra_networks_tabname}_prompts', {str(page.allow_prompt).lower()}, {str(page.allow_negative_prompt).lower()});" + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + "}}" + ) + tab.select(fn=None, _js=jscode, inputs=[], outputs=[], show_progress=False) def create_html(): ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index e7976ba1260..a8c3367198f 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -2,7 +2,6 @@ import os from modules import shared, ui_extra_networks, sd_models -from modules.ui_extra_networks import quote_js from modules.ui_extra_networks_checkpoints_user_metadata import CheckpointUserMetadataEditor @@ -31,7 +30,7 @@ def create_item(self, name, index=None, enable_filter=True): "preview": self.find_preview(path), "description": self.find_description(path), "search_terms": search_terms, - "onclick": '"' + html.escape(f"""return selectCheckpoint({quote_js(name)})""") + '"', + "onclick": html.escape(f"return selectCheckpoint('{name}');"), "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": checkpoint.metadata, "sort_keys": {'default': index, **self.get_sort_keys(checkpoint.filename)}, From 0b83f4c26376c87505769a3687cb06e321b413a1 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sat, 13 Jan 2024 18:38:05 +0900 Subject: [PATCH 1843/2418] reuse seed from infotexts --- modules/processing_scripts/seed.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 2d3cbb97f06..7b727d17cb1 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -6,6 +6,7 @@ from modules.infotext_utils import PasteField from modules.shared import cmd_opts from modules.ui_components import ToolButton +from modules import infotext_utils class ScriptSeed(scripts.ScriptBuiltinUI): @@ -77,7 +78,6 @@ def setup(self, p, seed, seed_checkbox, subseed, subseed_strength, seed_resize_f 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 (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength @@ -85,21 +85,14 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: 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: + infotext = gen_info.get('infotexts')[index] + gen_parameters = infotext_utils.parse_generation_parameters(infotext) + res = int(gen_parameters.get('Variation seed' if is_subseed else 'Seed', -1)) + except Exception: if gen_info_string: - errors.report(f"Error parsing JSON generation info: {gen_info_string}") + errors.report(f"Error retrieving seed from generation info: {gen_info_string}", exc_info=True) return [res, gr.update()] From 6acd8e28fceccbbea0c09c91aed0eff5a3504315 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 14 Jan 2024 01:58:01 +0900 Subject: [PATCH 1844/2418] save_files info base on infotexts --- modules/ui_common.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index f17259c2956..6a4465e9358 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -40,8 +40,9 @@ def save_files(js_data, images, do_make_zip, index): import csv filenames = [] fullfns = [] + parsed_infotexts = [] - #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it + # quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it class MyObject: def __init__(self, d=None): if d is not None: @@ -49,16 +50,14 @@ def __init__(self, d=None): setattr(self, key, value) data = json.loads(js_data) - p = MyObject(data) + path = shared.opts.outdir_save save_to_dirs = shared.opts.use_save_to_dirs_for_ui extension: str = shared.opts.samples_format start_index = 0 - only_one = False if index > -1 and shared.opts.save_selected_only and (index >= data["index_of_first_image"]): # ensures we are looking at a specific non-grid picture, and we have save_selected_only - only_one = True images = [images[index]] start_index = index @@ -74,10 +73,12 @@ def __init__(self, d=None): image = image_from_url_text(filedata) is_grid = image_index < p.index_of_first_image - i = 0 if is_grid else (image_index - p.index_of_first_image) p.batch_index = image_index-1 - fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs) + + parameters = parameters_copypaste.parse_generation_parameters(data["infotexts"][image_index]) + parsed_infotexts.append(parameters) + fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=parameters['Seed'], prompt=parameters['Prompt'], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs) filename = os.path.relpath(fullfn, path) filenames.append(filename) @@ -86,12 +87,12 @@ def __init__(self, d=None): filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) - writer.writerow([data["prompt"], data["seed"], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], data["negative_prompt"]]) + writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt']]) # Make Zip if do_make_zip: - zip_fileseed = p.all_seeds[index-1] if only_one else p.all_seeds[0] - namegen = modules.images.FilenameGenerator(p, zip_fileseed, p.all_prompts[0], image, True) + p.all_seeds = [parameters['Seed'] for parameters in parsed_infotexts] + namegen = modules.images.FilenameGenerator(p, parsed_infotexts[0]['Seed'], parsed_infotexts[0]['Prompt'], image, True) zip_filename = namegen.apply(shared.opts.grid_zip_filename_pattern or "[datetime]_[[model_name]]_[seed]-[seed_last]") zip_filepath = os.path.join(path, f"{zip_filename}.zip") From d224fed0ce16e6f1507b0da29e7495ab2b0035e4 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:16:07 +0900 Subject: [PATCH 1845/2418] parse_generation_parameters skip_fields --- modules/infotext_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 9a02cdf2410..1049c6c3c59 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -230,7 +230,7 @@ def restore_old_hires_fix_params(res): res['Hires resize-2'] = height -def parse_generation_parameters(x: str): +def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): """parses generation parameters string, the one you see in text field under the picture in UI: ``` girl with an artist's beret, determined, blue eyes, desert scene, computer monitors, heavy makeup, by Alphonse Mucha and Charlie Bowater, ((eyeshadow)), (coquettish), detailed, intricate @@ -240,6 +240,8 @@ def parse_generation_parameters(x: str): returns a dict with field values """ + if skip_fields is None: + skip_fields = shared.opts.infotext_skip_pasting res = {} @@ -356,8 +358,8 @@ def parse_generation_parameters(x: str): infotext_versions.backcompat(res) - skip = set(shared.opts.infotext_skip_pasting) - res = {k: v for k, v in res.items() if k not in skip} + for key in skip_fields: + res.pop(key, None) return res From 45a51c07e2ce5a36dc3cddde9a666a4c7b61af82 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:21:58 +0900 Subject: [PATCH 1846/2418] parse_generation_parameters with no skip_fields --- modules/processing_scripts/seed.py | 2 +- modules/ui_common.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 7b727d17cb1..7a4c0159831 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -88,7 +88,7 @@ def copy_seed(gen_info_string: str, index): try: gen_info = json.loads(gen_info_string) infotext = gen_info.get('infotexts')[index] - gen_parameters = infotext_utils.parse_generation_parameters(infotext) + gen_parameters = infotext_utils.parse_generation_parameters(infotext, []) res = int(gen_parameters.get('Variation seed' if is_subseed else 'Seed', -1)) except Exception: if gen_info_string: diff --git a/modules/ui_common.py b/modules/ui_common.py index 6a4465e9358..6d7f3a67574 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -76,7 +76,7 @@ def __init__(self, d=None): p.batch_index = image_index-1 - parameters = parameters_copypaste.parse_generation_parameters(data["infotexts"][image_index]) + parameters = parameters_copypaste.parse_generation_parameters(data["infotexts"][image_index], []) parsed_infotexts.append(parameters) fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=parameters['Seed'], prompt=parameters['Prompt'], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs) From 6916de5c0bd8df3835d450caa3327d1924db081c Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:16:07 +0900 Subject: [PATCH 1847/2418] parse_generation_parameters skip_fields --- modules/infotext_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index 9a02cdf2410..1049c6c3c59 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -230,7 +230,7 @@ def restore_old_hires_fix_params(res): res['Hires resize-2'] = height -def parse_generation_parameters(x: str): +def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): """parses generation parameters string, the one you see in text field under the picture in UI: ``` girl with an artist's beret, determined, blue eyes, desert scene, computer monitors, heavy makeup, by Alphonse Mucha and Charlie Bowater, ((eyeshadow)), (coquettish), detailed, intricate @@ -240,6 +240,8 @@ def parse_generation_parameters(x: str): returns a dict with field values """ + if skip_fields is None: + skip_fields = shared.opts.infotext_skip_pasting res = {} @@ -356,8 +358,8 @@ def parse_generation_parameters(x: str): infotext_versions.backcompat(res) - skip = set(shared.opts.infotext_skip_pasting) - res = {k: v for k, v in res.items() if k not in skip} + for key in skip_fields: + res.pop(key, None) return res From e1dfd452c0447e729a7341c434b4aab6063aa654 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:27:29 +0900 Subject: [PATCH 1848/2418] parse_generation_parameters with no skip_fields --- modules/txt2img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/txt2img.py b/modules/txt2img.py index e617cb1c576..92a160d61da 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -70,7 +70,7 @@ def txt2img_upscale(id_task: str, request: gr.Request, gallery, gallery_index, g image_info = gallery[gallery_index] if 0 <= gallery_index < len(gallery) else gallery[0] p.firstpass_image = infotext_utils.image_from_url_text(image_info) - parameters = parse_generation_parameters(geninfo.get('infotexts')[gallery_index]) + parameters = parse_generation_parameters(geninfo.get('infotexts')[gallery_index], []) p.seed = parameters.get('Seed', -1) p.subseed = parameters.get('Variation seed', -1) From 2cf23099ebb81832a27c8016f14062885f5a9c98 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Thu, 18 Jan 2024 04:44:21 +0900 Subject: [PATCH 1849/2418] fix console total progress bar when using txt2img_upscale add p.txt2img_upscale as indicator --- modules/processing.py | 7 +++++-- modules/txt2img.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index dcc807fe38d..547ab2f86a0 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1227,8 +1227,11 @@ def init(self, all_prompts, all_seeds, all_subseeds): if not state.processing_has_refined_job_count: if state.job_count == -1: state.job_count = self.n_iter - - shared.total_tqdm.updateTotal((self.steps + (self.hr_second_pass_steps or self.steps)) * state.job_count) + if getattr(self, 'txt2img_upscale', False): + total_steps = (self.hr_second_pass_steps or self.steps) * state.job_count + else: + total_steps = (self.steps + (self.hr_second_pass_steps or self.steps)) * state.job_count + shared.total_tqdm.updateTotal(total_steps) state.job_count = state.job_count * 2 state.processing_has_refined_job_count = True diff --git a/modules/txt2img.py b/modules/txt2img.py index 92a160d61da..4efcb4c3d71 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -64,6 +64,7 @@ def txt2img_upscale(id_task: str, request: gr.Request, gallery, gallery_index, g p.enable_hr = True p.batch_size = 1 p.n_iter = 1 + p.txt2img_upscale = True geninfo = json.loads(generation_info) From f25c81a74462554890ac7327a30629b332db1084 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Wed, 17 Jan 2024 22:38:51 -0500 Subject: [PATCH 1850/2418] Fix embeddings add/remove to/from prompt on click bugs. --- javascript/extraNetworks.js | 13 +++---------- modules/ui_extra_networks.py | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index caaa3fae0d2..1e2786ab0d2 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -169,8 +169,8 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) { var m = text.match(isNeg ? re_extranet_neg : re_extranet); var replaced = false; var newTextareaText; + var extraTextBeforeNet = opts.extra_networks_add_text_separator; if (m) { - var extraTextBeforeNet = opts.extra_networks_add_text_separator; var extraTextAfterNet = m[2]; var partToSearch = m[1]; var foundAtPosition = -1; @@ -183,7 +183,6 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) { } return found; }); - if (foundAtPosition >= 0) { if (extraTextAfterNet && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) { newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length); @@ -193,13 +192,8 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) { } } } else { - newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) { - if (found == text) { - replaced = true; - return ""; - } - return found; - }); + newTextareaText = textarea.value.replaceAll(new RegExp(`((?:${extraTextBeforeNet})?${text})`, "g"), ""); + replaced = (newTextareaText != textarea.value); } if (replaced) { @@ -211,7 +205,6 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) { } function updatePromptArea(text, textArea, isNeg) { - if (!tryToRemoveExtraNetworkFromPrompt(textArea, text, isNeg)) { textArea.value = textArea.value + opts.extra_networks_add_text_separator + text; } diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 55cd1da2772..5dd4e443f8e 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -236,7 +236,7 @@ def create_item_html( **{ "tabname": tabname, "prompt": item["prompt"], - "neg_prompt": item.get("negative_prompt", ""), + "neg_prompt": item.get("negative_prompt", "''"), "allow_neg": str(self.allow_negative_prompt).lower(), } ) From 0181c1f76b97162c42401f1e6286ae73d8aa6033 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Fri, 19 Jan 2024 00:14:03 +0800 Subject: [PATCH 1851/2418] Fix nested manual cast --- modules/devices.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/devices.py b/modules/devices.py index 0321d12c6ab..3702862979c 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -165,6 +165,8 @@ def forward_wrapper(self, *args, **kwargs): @contextlib.contextmanager def manual_cast(target_dtype): for module_type in patch_module_list: + if hasattr(module_type, "org_forward"): + continue org_forward = module_type.forward if module_type == torch.nn.MultiheadAttention and has_xpu(): module_type.forward = manual_cast_forward(torch.float32) @@ -175,7 +177,9 @@ def manual_cast(target_dtype): yield None finally: for module_type in patch_module_list: - module_type.forward = module_type.org_forward + if hasattr(module_type, "org_forward"): + module_type.forward = module_type.org_forward + delattr(module_type, "org_forward") def autocast(disable=False): From 50e444fa1d1c779b0c9c915e44ed2d78b587f277 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Thu, 18 Jan 2024 12:13:09 -0500 Subject: [PATCH 1852/2418] Fix missing important style. --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 1090e4368ab..57c52354a0b 100644 --- a/style.css +++ b/style.css @@ -28,7 +28,7 @@ div.gradio-container{ } .hidden{ - display: none; + display: none !important; } .compact{ From 69f4f148dce0868b748b700f96942d4036e848c9 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Thu, 18 Jan 2024 12:13:33 -0500 Subject: [PATCH 1853/2418] Fix various bugs including refresh bug. --- javascript/extraNetworks.js | 7 +++++-- modules/ui_extra_networks.py | 31 ++++++++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 1e2786ab0d2..3029afec847 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -55,8 +55,11 @@ function setupExtraNetworksForTab(tabname) { if (searchOnly && searchTerm.length < 4) { visible = false; } - - elem.style.display = visible ? "" : "none"; + if (visible) { + elem.classList.remove("hidden"); + } else { + elem.classList.add("hidden"); + } }); applySort(); diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 5dd4e443f8e..656e7f181e4 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -216,22 +216,17 @@ def create_item_html( Can be empty if the item is not meant to be shown. If no template is passed: A dictionary containing the generated item's attributes. """ - metadata = item.get("metadata") - if metadata: - self.metadata[item["name"]] = metadata - - if "user_metadata" not in item: - self.read_user_metadata(item) - preview = item.get("preview", None) - height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else '' - width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else '' + style_height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else '' + style_width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else '' + style_font_size = f"font-size: {shared.opts.extra_networks_card_text_scale*100}%;" + card_style = style_height + style_width + style_font_size background_image = f'' if preview else '' onclick = item.get("onclick", None) if onclick is None: # Don't quote prompt/neg_prompt since they are stored as js strings already. - onclick_js_tpl = "cardClicked('{tabname}', {prompt}, {neg_prompt}, '{allow_neg}');" + onclick_js_tpl = "cardClicked('{tabname}', {prompt}, {neg_prompt}, {allow_neg});" onclick = onclick_js_tpl.format( **{ "tabname": tabname, @@ -286,11 +281,10 @@ def create_item_html( ).strip() search_terms_html = "" - search_term_template = "{search_term}" + search_term_template = "" for search_term in item.get("search_terms", []): search_terms_html += search_term_template.format( **{ - "style": "display: none;", "class": f"search_terms{' search_only' if search_only else ''}", "search_term": search_term, } @@ -301,7 +295,7 @@ def create_item_html( "background_image": background_image, "card_clicked": onclick, "copy_path_button": btn_copy_path, - "description": (item.get("description") or "" if shared.opts.extra_networks_card_show_desc else ""), + "description": (item.get("description", "") or "" if shared.opts.extra_networks_card_show_desc else ""), "edit_button": btn_edit_item, "local_preview": quote_js(item["local_preview"]), "metadata_button": btn_metadata, @@ -311,7 +305,7 @@ def create_item_html( "search_only": " search_only" if search_only else "", "search_terms": search_terms_html, "sort_keys": sort_keys, - "style": f"display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%", + "style": card_style, "tabname": tabname, "extra_networks_tabname": self.extra_networks_tabname, } @@ -500,7 +494,6 @@ def create_card_view_html(self, tabname: str) -> str: HTML formatted string. """ res = "" - self.items = {x["name"]: x for x in self.list_items()} for item in self.items.values(): res += self.create_item_html(tabname, item, self.card_tpl) @@ -524,6 +517,14 @@ def create_html(self, tabname): self.lister.reset() self.metadata = {} self.items = {x["name"]: x for x in self.list_items()} + # Populate the instance metadata for each item. + for item in self.items.values(): + metadata = item.get("metadata") + if metadata: + self.metadata[item["name"]] = metadata + + if "user_metadata" not in item: + self.read_user_metadata(item) return self.pane_tpl.format( **{ From a97147bc8a43ade7c18bb755f0cfac111fc1a619 Mon Sep 17 00:00:00 2001 From: n0kovo Date: Fri, 19 Jan 2024 00:10:02 +0100 Subject: [PATCH 1854/2418] Add support for DAT upscaler models --- modules/dat_model.py | 81 +++++++++++++++++++++++++++++++++++++++ modules/shared_items.py | 5 +++ modules/shared_options.py | 3 ++ 3 files changed, 89 insertions(+) create mode 100644 modules/dat_model.py diff --git a/modules/dat_model.py b/modules/dat_model.py new file mode 100644 index 00000000000..8637351c500 --- /dev/null +++ b/modules/dat_model.py @@ -0,0 +1,81 @@ +import os +import sys + +from modules import modelloader, devices +from modules.shared import cmd_opts, opts +from modules.upscaler import Upscaler, UpscalerData +from modules.upscaler_utils import upscale_with_model + +from icecream import ic + +class UpscalerDAT(Upscaler): + def __init__(self, user_path): + self.name = "DAT" + self.user_path = user_path + self.scalers = [] + super().__init__() + + for file in self.find_models(ext_filter=[".pt", ".pth"]): + name = modelloader.friendly_name(file) + scaler_data = UpscalerData(name, file, upscaler=self, scale=None) + self.scalers.append(scaler_data) + + for model in get_dat_models(self): + if model.name in opts.dat_enabled_models: + self.scalers.append(model) + + def do_upscale(self, img, selected_model): + try: + info = self.load_model(selected_model) + except Exception as e: + errors.report(f"Unable to load DAT model {path}", exc_info=True) + return img + + model_descriptor = modelloader.load_spandrel_model( + info.local_data_path, + device=self.device, + prefer_half=(not cmd_opts.no_half and not cmd_opts.upcast_sampling), + expected_architecture="DAT", + ) + return upscale_with_model( + model_descriptor, + img, + tile_size=opts.DAT_tile, + tile_overlap=opts.DAT_tile_overlap, + ) + + def load_model(self, path): + for scaler in self.scalers: + if scaler.data_path == path: + if scaler.local_data_path.startswith("http"): + scaler.local_data_path = modelloader.load_file_from_url( + scaler.data_path, + model_dir=self.model_download_path, + ) + if not os.path.exists(scaler.local_data_path): + raise FileNotFoundError(f"DAT data missing: {scaler.local_data_path}") + return scaler + raise ValueError(f"Unable to find model info: {path}") + + +def get_dat_models(scaler): + return [ + UpscalerData( + name="DAT x2", + path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x2.pth", + scale=2, + upscaler=scaler, + ), + UpscalerData( + name="DAT x3", + path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x3.pth", + scale=3, + upscaler=scaler, + ), + UpscalerData( + name="DAT x4", + path="https://github.com/n0kovo/dat_upscaler_models/raw/main/DAT/DAT_x4.pth", + scale=4, + upscaler=scaler, + ), + ] diff --git a/modules/shared_items.py b/modules/shared_items.py index 13fb2814f2b..88f636452c7 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -8,6 +8,11 @@ def realesrgan_models_names(): return [x.name for x in modules.realesrgan_model.get_realesrgan_models(None)] +def dat_models_names(): + import modules.dat_model + return [x.name for x in modules.dat_model.get_dat_models(None)] + + def postprocessing_scripts(): import modules.scripts diff --git a/modules/shared_options.py b/modules/shared_options.py index 63488f4e700..48a206ce941 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -97,6 +97,9 @@ "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), + "dat_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which DAT models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.dat_models_names()}), + "DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), + "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), })) From 2e7efe47b6c78a59f9f3d64c1d30f54751a31a56 Mon Sep 17 00:00:00 2001 From: n0kovo Date: Fri, 19 Jan 2024 00:39:14 +0100 Subject: [PATCH 1855/2418] Minor cleanup --- modules/dat_model.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/dat_model.py b/modules/dat_model.py index 8637351c500..495d5f4937d 100644 --- a/modules/dat_model.py +++ b/modules/dat_model.py @@ -1,12 +1,10 @@ import os -import sys -from modules import modelloader, devices +from modules import modelloader, errors from modules.shared import cmd_opts, opts from modules.upscaler import Upscaler, UpscalerData from modules.upscaler_utils import upscale_with_model -from icecream import ic class UpscalerDAT(Upscaler): def __init__(self, user_path): @@ -24,13 +22,13 @@ def __init__(self, user_path): if model.name in opts.dat_enabled_models: self.scalers.append(model) - def do_upscale(self, img, selected_model): + def do_upscale(self, img, path): try: - info = self.load_model(selected_model) - except Exception as e: + info = self.load_model(path) + except Exception: errors.report(f"Unable to load DAT model {path}", exc_info=True) return img - + model_descriptor = modelloader.load_spandrel_model( info.local_data_path, device=self.device, From 1ddb886a804dc69f542ebc71bdd7baec48f677b6 Mon Sep 17 00:00:00 2001 From: n0kovo Date: Fri, 19 Jan 2024 00:48:46 +0100 Subject: [PATCH 1856/2418] Fix wrong options value --- 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 48a206ce941..74a2a67f933 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -97,7 +97,7 @@ "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap for ESRGAN upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which Real-ESRGAN models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), - "dat_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Select which DAT models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.dat_models_names()}), + "dat_enabled_models": OptionInfo(["DAT x2", "DAT x3", "DAT x4"], "Select which DAT models to show in the web UI.", gr.CheckboxGroup, lambda: {"choices": shared_items.dat_models_names()}), "DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), From d31dc7a73996ee611a72ef641237606fc9eddca5 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 20 Jan 2024 00:40:03 +0400 Subject: [PATCH 1857/2418] fix extras big batch crashes --- modules/postprocessing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 7449b0dc51c..f14882321ec 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -62,8 +62,6 @@ def get_images(extras_mode, image, image_folder, input_dir): else: image_data = image_placeholder - shared.state.assign_current_image(image_data) - parameters, existing_pnginfo = images.read_info_from_image(image_data) if parameters: existing_pnginfo["parameters"] = parameters @@ -92,6 +90,8 @@ def get_images(extras_mode, image, image_folder, input_dir): pp.image.info = existing_pnginfo pp.image.info["postprocessing"] = infotext + shared.state.assign_current_image(pp.image) + if save_output: fullfn, _ = images.save_image(pp.image, path=outpath, basename=basename, extension=opts.samples_format, info=infotext, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo, forced_filename=forced_filename, suffix=suffix) From 4dde96109bb9621bb5608f7f792dc569d692ecf5 Mon Sep 17 00:00:00 2001 From: WebDev <6970043+WebDev9000@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:38:34 -0800 Subject: [PATCH 1858/2418] Update zoom.js --- extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 4 ++-- 1 file changed, 2 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 dd6028c1e25..df60c1a1775 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -218,8 +218,8 @@ onUiLoaded(async() => { canvas_hotkey_fullscreen: "KeyS", canvas_hotkey_move: "KeyF", canvas_hotkey_overlap: "KeyO", - canvas_hotkey_shrink_brush: "BracketLeft", - canvas_hotkey_grow_brush: "BracketRight", + canvas_hotkey_shrink_brush: "KeyQ", + canvas_hotkey_grow_brush: "KeyW", canvas_disabled_functions: [], canvas_show_tooltip: true, canvas_auto_expand: true, From bcdcc8be7d5fb923d64b7eb8a9a831f26f179d97 Mon Sep 17 00:00:00 2001 From: WebDev <6970043+WebDev9000@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:39:07 -0800 Subject: [PATCH 1859/2418] Update hotkey_config.py --- .../canvas-zoom-and-pan/scripts/hotkey_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py index 9d9accce8cf..6d9e34d8b0e 100644 --- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py +++ b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py @@ -4,8 +4,8 @@ shared.options_templates.update(shared.options_section(('canvas_hotkey', "Canvas Hotkeys"), { "canvas_hotkey_zoom": shared.OptionInfo("Alt", "Zoom canvas", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), "canvas_hotkey_adjust": shared.OptionInfo("Ctrl", "Adjust brush size", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), - "canvas_hotkey_shrink_brush": shared.OptionInfo("[", "Shrink the brush size"), - "canvas_hotkey_grow_brush": shared.OptionInfo("]", "Enlarge the brush size"), + "canvas_hotkey_shrink_brush": shared.OptionInfo("Q", "Shrink the brush size"), + "canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"), "canvas_hotkey_move": shared.OptionInfo("F", "Moving the canvas").info("To work correctly in firefox, turn off 'Automatically search the page text when typing' in the browser settings"), "canvas_hotkey_fullscreen": shared.OptionInfo("S", "Fullscreen Mode, maximizes the picture so that it fits into the screen and stretches it to its full width "), "canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"), From 30f31d23c6d212389aef035137c381dd79d44a26 Mon Sep 17 00:00:00 2001 From: WebDev <6970043+WebDev9000@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:39:33 -0800 Subject: [PATCH 1860/2418] Update hotkey_config.py --- extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py index 6d9e34d8b0e..89b7c31f22d 100644 --- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py +++ b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py @@ -5,7 +5,7 @@ "canvas_hotkey_zoom": shared.OptionInfo("Alt", "Zoom canvas", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), "canvas_hotkey_adjust": shared.OptionInfo("Ctrl", "Adjust brush size", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), "canvas_hotkey_shrink_brush": shared.OptionInfo("Q", "Shrink the brush size"), - "canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"), + "canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"), "canvas_hotkey_move": shared.OptionInfo("F", "Moving the canvas").info("To work correctly in firefox, turn off 'Automatically search the page text when typing' in the browser settings"), "canvas_hotkey_fullscreen": shared.OptionInfo("S", "Fullscreen Mode, maximizes the picture so that it fits into the screen and stretches it to its full width "), "canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"), From 56676ff923497901d56fcbca1ac549095e71d72d Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 20 Jan 2024 11:49:05 +0400 Subject: [PATCH 1861/2418] fix tab indexes reset after restart ui --- modules/ui.py | 4 ++-- modules/ui_postprocessing.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index a716a04055d..ebd33a85616 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -532,7 +532,7 @@ def add_copy_image_controls(tab_name, elem): if category == "image": with gr.Tabs(elem_id="mode_img2img"): - img2img_selected_tab = gr.State(0) + img2img_selected_tab = gr.Number(value=0, visible=False) with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=opts.img2img_editor_height) @@ -613,7 +613,7 @@ def copy_image(img): elif category == "dimensions": with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): - selected_scale_tab = gr.State(value=0) + selected_scale_tab = gr.Number(value=0, visible=False) with gr.Tabs(): with gr.Tab(label="Resize to", elem_id="img2img_tab_resize_to") as tab_scale_to: diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 7a132ac22ed..ff22a178075 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -5,7 +5,7 @@ def create_ui(): dummy_component = gr.Label(visible=False) - tab_index = gr.State(value=0) + tab_index = gr.Number(value=0, visible=False) with gr.Row(equal_height=False, variant='compact'): with gr.Column(variant='compact'): From 81126027f5226e7ee58e1a99194eb9ec7b8ec6e7 Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:31:12 +0800 Subject: [PATCH 1862/2418] Avoid early disable --- modules/devices.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/devices.py b/modules/devices.py index 3702862979c..3bde169904b 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -164,9 +164,11 @@ def forward_wrapper(self, *args, **kwargs): @contextlib.contextmanager def manual_cast(target_dtype): + applied = False for module_type in patch_module_list: if hasattr(module_type, "org_forward"): continue + applied = True org_forward = module_type.forward if module_type == torch.nn.MultiheadAttention and has_xpu(): module_type.forward = manual_cast_forward(torch.float32) @@ -176,6 +178,8 @@ def manual_cast(target_dtype): try: yield None finally: + if not applied: + return for module_type in patch_module_list: if hasattr(module_type, "org_forward"): module_type.forward = module_type.org_forward From 4a66d2fb228584bb38dc22db6a3e657561834c7a Mon Sep 17 00:00:00 2001 From: Kohaku-Blueleaf <59680068+KohakuBlueleaf@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:33:59 +0800 Subject: [PATCH 1863/2418] Avoid exceptions to be silenced --- modules/devices.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 3bde169904b..dfffaf24fd0 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -178,12 +178,11 @@ def manual_cast(target_dtype): try: yield None finally: - if not applied: - return - for module_type in patch_module_list: - if hasattr(module_type, "org_forward"): - module_type.forward = module_type.org_forward - delattr(module_type, "org_forward") + if applied: + for module_type in patch_module_list: + if hasattr(module_type, "org_forward"): + module_type.forward = module_type.org_forward + delattr(module_type, "org_forward") def autocast(disable=False): From ed383eb5a0db78d0da60420bb464943e4cc9b847 Mon Sep 17 00:00:00 2001 From: Andray Date: Sat, 20 Jan 2024 15:37:49 +0400 Subject: [PATCH 1864/2418] keep postprocessing upscale selected tab after restart --- scripts/postprocessing_upscale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index a57f9d4a4b6..e269682d0f3 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -15,7 +15,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): order = 1000 def ui(self): - selected_tab = gr.State(value=0) + selected_tab = gr.Number(value=0, visible=False) with gr.Column(): with FormRow(): From 2310cd66e5381fbe6b966894381c6ee7b762898f Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Sat, 20 Jan 2024 11:43:45 -0500 Subject: [PATCH 1865/2418] Add toggle button for tree view. Use default settings for sortmode and direction. --- html/extra-networks-pane.html | 55 ++++++++++++++++++++-- html/extra-networks-tree.html | 37 --------------- javascript/extraNetworks.js | 20 ++++++-- modules/ui_extra_networks.py | 7 +++ style.css | 86 +++++++++++++++++++++++------------ 5 files changed, 132 insertions(+), 73 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index bf46ca16305..73dad2ab248 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,8 +1,55 @@
                    -
                    - {tree_html} +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    + +
                    -
                    - {items_html} +
                    +
                    + {tree_html} +
                    +
                    + {items_html} +
                    \ No newline at end of file diff --git a/html/extra-networks-tree.html b/html/extra-networks-tree.html index 23f6af1058c..39649e86093 100644 --- a/html/extra-networks-tree.html +++ b/html/extra-networks-tree.html @@ -1,41 +1,4 @@
                    -
                    - -
                    - -
                    -
                    - -
                    -
                    - -
                    -
                    {tree}
                    diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 3029afec847..ce788328ce5 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -348,7 +348,7 @@ function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) { } } -function extraNetworksTreeSortOnClick(event, tabname, extra_networks_tabname) { +function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) { /** * Handles `onclick` events for the Sort Mode button. * @@ -382,7 +382,7 @@ function extraNetworksTreeSortOnClick(event, tabname, extra_networks_tabname) { applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } -function extraNetworksTreeSortDirOnClick(event, tabname, extra_networks_tabname) { +function extraNetworksControlSortDirOnClick(event, tabname, extra_networks_tabname) { /** * Handles `onclick` events for the Sort Direction button. * @@ -403,7 +403,21 @@ function extraNetworksTreeSortDirOnClick(event, tabname, extra_networks_tabname) applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } -function extraNetworksTreeRefreshOnClick(event, tabname, extra_networks_tabname) { +function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabname) { + /** + * Handles `onclick` events for the Tree View button. + * + * Toggles the tree view in the extra networks pane. + * + * @param event The generated event. + * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. + * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. + */ + gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree").classList.toggle("hidden"); + event.currentTarget.classList.toggle("extra-network-control--enabled"); +} + +function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) { /** * Handles `onclick` events for the Refresh Page button. * diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 656e7f181e4..4c8a407449e 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -526,10 +526,17 @@ def create_html(self, tabname): if "user_metadata" not in item: self.read_user_metadata(item) + data_sortdir = shared.opts.extra_networks_card_order + data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip() + data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" + return self.pane_tpl.format( **{ "tabname": tabname, "extra_networks_tabname": self.extra_networks_tabname, + "data_sortmode": data_sortmode, + "data_sortkey": data_sortkey, + "data_sortdir": data_sortdir, "tree_html": self.create_tree_view_html(tabname), "items_html": self.create_card_view_html(tabname), } diff --git a/style.css b/style.css index 57c52354a0b..f3fd1571bb1 100644 --- a/style.css +++ b/style.css @@ -1178,6 +1178,13 @@ body.resizing .resize-handle { height: calc(100vh - 24rem); resize: vertical; min-height: 52rem; + flex-direction: column; +} + +.extra-network-pane .extra-network-pane-content { + display: flex; + flex: 1; + flex-direction: row; } .extra-network-pane .extra-network-tree { @@ -1234,7 +1241,7 @@ body.resizing .resize-handle { display: none; } -.extra-network-tree .tree-list .tree-list-controls { +.extra-network-pane .extra-network-control { position: relative; display: grid; width: 100%; @@ -1248,8 +1255,7 @@ body.resizing .resize-handle { border: none; transition: background 33.333ms linear; grid-template-rows: min-content; - grid-template-areas: "tree-list-controls-col-0 tree-list-controls-col-1 tree-list-controls-col-2 tree-list-controls-col-3"; - grid-template-columns: minmax(0, auto) min-content min-content min-content; + grid-template-columns: minmax(0, auto) repeat(4, min-content); grid-gap: 0.1rem; align-items: start; } @@ -1342,16 +1348,16 @@ body.resizing .resize-handle { background-color: var(--neutral-700); } +.extra-network-tree div.tree-list-content[data-selected] { + background-color: var(--neutral-300); +} + .extra-network-tree div.tree-list-content:hover { -webkit-transition: all 0.05s ease-in-out; transition: all 0.05s ease-in-out; background-color: var(--neutral-200); } -.extra-network-tree div.tree-list-content[data-selected] { - background-color: var(--neutral-300); -} - /* ==== CHEVRON ICON ACTIONS ==== */ /* Define the animation for the arrow when it is clicked. */ .extra-network-tree .tree-list-content-dir .tree-list-item-action-chevron { @@ -1378,7 +1384,7 @@ body.resizing .resize-handle { /* ==== SEARCH INPUT ACTIONS ==== */ /* Add icon to left side of */ -.extra-network-tree .tree-list-controls .tree-list-search::before { +.extra-network-pane .extra-network-control .extra-network-control--search::before { content: "🔎︎"; position: absolute; margin: 0.5rem; @@ -1386,14 +1392,12 @@ body.resizing .resize-handle { color: var(--input-placeholder-color); } -.extra-network-tree .tree-list-controls .tree-list-search { +.extra-network-pane .extra-network-control .extra-network-control--search { display: inline-flex; - grid-area: tree-list-controls-col-0; position: relative; - margin: 0.5rem; } -.extra-network-tree .tree-list-controls .tree-list-search .tree-list-search-text { +.extra-network-pane .extra-network-control .extra-network-control--search .extra-network-control--search-text { border: 1px solid var(--button-secondary-border-color); border-radius: 0.5rem; color: var(--button-secondary-text-color); @@ -1404,7 +1408,7 @@ body.resizing .resize-handle { } /* clear button (x on right side) styling */ -.extra-network-tree .tree-list-controls .tree-list-search .tree-list-search-text::-webkit-search-cancel-button { +.extra-network-pane .extra-network-control .extra-network-control--search .extra-network-control--search-text::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; cursor: pointer; @@ -1418,8 +1422,7 @@ body.resizing .resize-handle { } /* ==== SORT ICON ACTIONS ==== */ -.extra-network-tree .tree-list-controls .tree-list-sort { - grid-area: tree-list-controls-col-1; +.extra-network-pane .extra-network-control .extra-network-control--sort { padding: 0.25rem; display: inline-flex; cursor: pointer; @@ -1427,7 +1430,7 @@ body.resizing .resize-handle { align-self: center; } -.extra-network-tree .tree-list-controls .tree-list-sort .tree-list-sort-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort .extra-network-control--sort-icon { height: 1.5rem; width: 1.5rem; mask-repeat: no-repeat; @@ -1436,25 +1439,24 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-tree .tree-list-sort[data-sortmode="path"] .tree-list-sort-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort[data-sortmode="path"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-tree .tree-list-sort[data-sortmode="name"] .tree-list-sort-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort[data-sortmode="name"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-tree .tree-list-sort[data-sortmode="date_created"] .tree-list-sort-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort[data-sortmode="date_created"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-tree .tree-list-sort[data-sortmode="date_modified"] .tree-list-sort-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort[data-sortmode="date_modified"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } /* ==== SORT DIRECTION ICON ACTIONS ==== */ -.extra-network-tree .tree-list-controls .tree-list-sort-dir { - grid-area: tree-list-controls-col-2; +.extra-network-pane .extra-network-control .extra-network-control--sort-dir { padding: 0.25rem; display: inline-flex; cursor: pointer; @@ -1462,7 +1464,7 @@ body.resizing .resize-handle { align-self: center; } -.extra-network-tree .tree-list-controls .tree-list-sort-dir .tree-list-sort-dir-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort-dir .extra-network-control--sort-dir-icon { height: 1.5rem; width: 1.5rem; mask-repeat: no-repeat; @@ -1471,17 +1473,43 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-tree .tree-list-sort-dir[data-sortdir="Ascending"] .tree-list-sort-dir-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort-dir[data-sortdir="Ascending"] .extra-network-control--sort-dir-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-tree .tree-list-sort-dir[data-sortdir="Descending"] .tree-list-sort-dir-icon { +.extra-network-pane .extra-network-control .extra-network-control--sort-dir[data-sortdir="Descending"] .extra-network-control--sort-dir-icon { mask-image: url('data:image/svg+xml,'); } +/* ==== TREE VIEW ICON ACTIONS ==== */ +.extra-network-pane .extra-network-control .extra-network-control--tree-view { + padding: 0.25rem; + display: inline-flex; + cursor: pointer; + justify-self: center; + align-self: center; +} + +.extra-network-pane .extra-network-control .extra-network-control--tree-view .extra-network-control--tree-view-icon { + height: 1.5rem; + width: 1.5rem; + mask-image: url('data:image/svg+xml,'); + mask-repeat: no-repeat; + mask-position: center center; + mask-size: 100%; + background-color: var(--input-placeholder-color); +} + +.dark .extra-network-pane .extra-network-control .extra-network-control--enabled { + background-color: var(--neutral-700); +} + +.dark .extra-network-pane .extra-network-control .extra-network-control--enabled { + background-color: var(--neutral-300); +} + /* ==== REFRESH ICON ACTIONS ==== */ -.extra-network-tree .tree-list-controls .tree-list-refresh { - grid-area: tree-list-controls-col-3; +.extra-network-pane .extra-network-control .extra-network-control--refresh { padding: 0.25rem; display: inline-flex; cursor: pointer; @@ -1489,7 +1517,7 @@ body.resizing .resize-handle { align-self: center; } -.extra-network-tree .tree-list-controls .tree-list-refresh .tree-list-refresh-icon { +.extra-network-pane .extra-network-control .extra-network-control--refresh .extra-network-control--refresh-icon { height: 1.5rem; width: 1.5rem; mask-image: url('data:image/svg+xml,'); @@ -1499,7 +1527,7 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-tree .tree-list-refresh-icon:active { +.extra-network-pane .extra-network-control .extra-network-control--refresh-icon:active { -ms-transform: rotate(180deg); -webkit-transform: rotate(180deg); transform: rotate(180deg); From b67a49441fc420f37c6bef1172a0b1ad5c42f30f Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Sat, 20 Jan 2024 13:28:37 -0500 Subject: [PATCH 1866/2418] Add option in settings to enable/disable tree view by default. --- html/extra-networks-pane.html | 4 ++-- modules/shared_options.py | 1 + modules/ui_extra_networks.py | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 73dad2ab248..9f5b3ecee62 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -29,7 +29,7 @@
                    @@ -45,7 +45,7 @@
                    -
                    +
                    {tree_html}
                    diff --git a/modules/shared_options.py b/modules/shared_options.py index 63488f4e700..e0a6d977c30 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -251,6 +251,7 @@ "extra_networks_card_show_desc": OptionInfo(True, "Show description on card"), "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), + "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(), "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 4c8a407449e..80160b847b6 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -529,6 +529,11 @@ def create_html(self, tabname): data_sortdir = shared.opts.extra_networks_card_order data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip() data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" + tree_view_btn_extra_class = "" + tree_view_div_extra_class = "hidden" + if shared.opts.extra_networks_tree_view_default_enabled: + tree_view_btn_extra_class = "extra-network-control--enabled" + tree_view_div_extra_class = "" return self.pane_tpl.format( **{ @@ -537,6 +542,8 @@ def create_html(self, tabname): "data_sortmode": data_sortmode, "data_sortkey": data_sortkey, "data_sortdir": data_sortdir, + "tree_view_btn_extra_class": tree_view_btn_extra_class, + "tree_view_div_extra_class": tree_view_div_extra_class, "tree_html": self.create_tree_view_html(tabname), "items_html": self.create_card_view_html(tabname), } From 25e8273d2f6481deca221b29d35093f6d0c9da6a Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:21:36 +0900 Subject: [PATCH 1867/2418] re-work multi --styles-file --styles-file change to append str --styles-file is [] then defaults to [styles.csv] --styles-file accepts paths or paths with wildcard "*" the first `--styles-file` entry is use as the default styles file path if filename a wildcard then the first matching file is used if no match is found, create a new "styles.csv" in the same dir as the first path when saving a new style it will be save in the default styles file when saving a existing style, it will be saved to file it belongs to order of the styles files in the styles dropdown can be controlled to a certain degree by the order of --styles-file --- modules/cmd_args.py | 2 +- modules/shared.py | 3 +- modules/styles.py | 85 +++++++++++++++++++------------------ modules/ui_prompt_styles.py | 9 ++-- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/modules/cmd_args.py b/modules/cmd_args.py index e58059a1fea..f1251b6c850 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -88,7 +88,7 @@ parser.add_argument("--gradio-inpaint-tool", type=str, help="does not do anything") parser.add_argument("--gradio-allowed-path", action='append', help="add path to gradio's allowed_paths, make it possible to serve files from it", default=[data_path]) parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last") -parser.add_argument("--styles-file", type=str, help="filename to use for styles", default=os.path.join(data_path, 'styles.csv')) +parser.add_argument("--styles-file", type=str, action='append', help="path or wildcard path of styles files, allow multiple entries.", default=[]) parser.add_argument("--autolaunch", action='store_true', help="open the webui URL in the system's default browser upon launch", default=False) parser.add_argument("--theme", type=str, help="launches the UI with light or dark theme", default=None) parser.add_argument("--use-textbox-seed", action='store_true', help="use textbox for seeds in UI (no up/down, but possible to input long seeds)", default=False) diff --git a/modules/shared.py b/modules/shared.py index 636619391fc..ccdca4e7040 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,3 +1,4 @@ +import os import sys import gradio as gr @@ -11,7 +12,7 @@ batch_cond_uncond = True # old field, unused now in favor of shared.opts.batch_cond_uncond parallel_processing_allowed = True -styles_filename = cmd_opts.styles_file +styles_filename = cmd_opts.styles_file = cmd_opts.styles_file if len(cmd_opts.styles_file) > 0 else [os.path.join(data_path, 'styles.csv')] config_filename = cmd_opts.ui_settings_file hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} diff --git a/modules/styles.py b/modules/styles.py index 026c43001a4..9edcc7e4447 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -1,16 +1,15 @@ +from pathlib import Path import csv -import fnmatch import os -import os.path import typing import shutil class PromptStyle(typing.NamedTuple): name: str - prompt: str - negative_prompt: str - path: str = None + prompt: str | None + negative_prompt: str | None + path: str | None = None def merge_prompts(style_prompt: str, prompt: str) -> str: @@ -79,14 +78,19 @@ def extract_original_prompts(style: PromptStyle, prompt, negative_prompt): class StyleDatabase: - def __init__(self, path: str): + def __init__(self, paths: list[str | Path]): self.no_style = PromptStyle("None", "", "", None) self.styles = {} - self.path = path - - folder, file = os.path.split(self.path) - filename, _, ext = file.partition('*') - self.default_path = os.path.join(folder, filename + ext) + self.paths = paths + self.all_styles_files: list[Path] = [] + + folder, file = os.path.split(self.paths[0]) + if '*' in file or '?' in file: + # if the first path is a wildcard pattern, find the first match else use "folder/styles.csv" as the default path + self.default_path = next(Path(folder).glob(file), Path(os.path.join(folder, 'styles.csv'))) + self.paths.insert(0, self.default_path) + else: + self.default_path = Path(self.paths[0]) self.prompt_fields = [field for field in PromptStyle._fields if field != "path"] @@ -99,33 +103,31 @@ def reload(self): """ self.styles.clear() - path, filename = os.path.split(self.path) - - if "*" in filename: - fileglob = filename.split("*")[0] + "*.csv" - filelist = [] - for file in os.listdir(path): - if fnmatch.fnmatch(file, fileglob): - filelist.append(file) - # Add a visible divider to the style list - half_len = round(len(file) / 2) - divider = f"{'-' * (20 - half_len)} {file.upper()}" - divider = f"{divider} {'-' * (40 - len(divider))}" - self.styles[divider] = PromptStyle( - f"{divider}", None, None, "do_not_save" - ) - # Add styles from this CSV file - self.load_from_csv(os.path.join(path, file)) - if len(filelist) == 0: - print(f"No styles found in {path} matching {fileglob}") - return - elif not os.path.exists(self.path): - print(f"Style database not found: {self.path}") - return - else: - self.load_from_csv(self.path) - - def load_from_csv(self, path: str): + # scans for all styles files + all_styles_files = [] + for pattern in self.paths: + folder, file = os.path.split(pattern) + if '*' in file or '?' in file: + found_files = Path(folder).glob(file) + [all_styles_files.append(file) for file in found_files] + else: + # if os.path.exists(pattern): + all_styles_files.append(Path(pattern)) + + # Remove any duplicate entries + seen = set() + self.all_styles_files = [s for s in all_styles_files if not (s in seen or seen.add(s))] + + for styles_file in self.all_styles_files: + if len(all_styles_files) > 1: + # add divider when more than styles file + # '---------------- STYLES ----------------' + divider = f' {styles_file.stem.upper()} '.center(40, '-') + self.styles[divider] = PromptStyle(f"{divider}", None, None, "do_not_save") + if styles_file.is_file(): + self.load_from_csv(styles_file) + + def load_from_csv(self, path: str | Path): with open(path, "r", encoding="utf-8-sig", newline="") as file: reader = csv.DictReader(file, skipinitialspace=True) for row in reader: @@ -137,7 +139,7 @@ def load_from_csv(self, path: str): negative_prompt = row.get("negative_prompt", "") # Add style to database self.styles[row["name"]] = PromptStyle( - row["name"], prompt, negative_prompt, path + row["name"], prompt, negative_prompt, str(path) ) def get_style_paths(self) -> set: @@ -145,11 +147,11 @@ def get_style_paths(self) -> set: # Update any styles without a path to the default path for style in list(self.styles.values()): if not style.path: - self.styles[style.name] = style._replace(path=self.default_path) + self.styles[style.name] = style._replace(path=str(self.default_path)) # Create a list of all distinct paths, including the default path style_paths = set() - style_paths.add(self.default_path) + style_paths.add(str(self.default_path)) for _, style in self.styles.items(): if style.path: style_paths.add(style.path) @@ -177,7 +179,6 @@ def apply_negative_styles_to_prompt(self, prompt, styles): def save_styles(self, path: str = None) -> None: # The path argument is deprecated, but kept for backwards compatibility - _ = path style_paths = self.get_style_paths() diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py index 0d74c23fa19..d67e3f17e10 100644 --- a/modules/ui_prompt_styles.py +++ b/modules/ui_prompt_styles.py @@ -22,9 +22,12 @@ def save_style(name, prompt, negative_prompt): if not name: return gr.update(visible=False) - style = styles.PromptStyle(name, prompt, negative_prompt) + existing_style = shared.prompt_styles.styles.get(name) + path = existing_style.path if existing_style is not None else None + + style = styles.PromptStyle(name, prompt, negative_prompt, path) shared.prompt_styles.styles[style.name] = style - shared.prompt_styles.save_styles(shared.styles_filename) + shared.prompt_styles.save_styles() return gr.update(visible=True) @@ -34,7 +37,7 @@ def delete_style(name): return shared.prompt_styles.styles.pop(name, None) - shared.prompt_styles.save_styles(shared.styles_filename) + shared.prompt_styles.save_styles() return '', '', '' From 8459015017fde8833a4159b8d56a32f92ebf4186 Mon Sep 17 00:00:00 2001 From: Arturo Albacete Date: Sat, 20 Jan 2024 21:19:53 +0100 Subject: [PATCH 1868/2418] skip if headers haven't changed --- modules/ui_common.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index 92b00719238..2c67834c6c5 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -1,3 +1,4 @@ +import csv import dataclasses import json import html @@ -37,8 +38,6 @@ def plaintext_to_html(text, classname=None): def update_logfile(logfile_path, fields): - import csv - with open(logfile_path, "r", encoding="utf8", newline="") as file: reader = csv.reader(file) rows = list(reader) @@ -47,6 +46,10 @@ def update_logfile(logfile_path, fields): if not rows: return + # file is already synced, do nothing + if len(rows[0]) == len(fields): + return + rows[0] = fields # append new fields to each row as empty values @@ -60,7 +63,6 @@ def update_logfile(logfile_path, fields): def save_files(js_data, images, do_make_zip, index): - import csv filenames = [] fullfns = [] parsed_infotexts = [] From f190b85182a89f873fa99d897ac20310047131ea Mon Sep 17 00:00:00 2001 From: Arturo Albacete Date: Sat, 20 Jan 2024 21:27:38 +0100 Subject: [PATCH 1869/2418] restore saving fields --- modules/ui_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_common.py b/modules/ui_common.py index 2c67834c6c5..6eb740f16c5 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -132,7 +132,7 @@ def __init__(self, d=None): filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) - writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt']]) + writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]]) # Make Zip if do_make_zip: From 4aa99f77abd439469f20e6e2941dd553031ed2d0 Mon Sep 17 00:00:00 2001 From: Arturo Albacete Date: Sat, 20 Jan 2024 22:04:53 +0100 Subject: [PATCH 1870/2418] add docstring --- modules/ui_common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ui_common.py b/modules/ui_common.py index 6eb740f16c5..29fe7d0e920 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -38,6 +38,7 @@ def plaintext_to_html(text, classname=None): def update_logfile(logfile_path, fields): + """Update a logfile from old format to new format to maintain CSV integrity.""" with open(logfile_path, "r", encoding="utf8", newline="") as file: reader = csv.reader(file) rows = list(reader) From e36827af3254f7bac9f8c78d6d56c709959b40b6 Mon Sep 17 00:00:00 2001 From: w-e-w <40751091+w-e-w@users.noreply.github.com> Date: Sun, 21 Jan 2024 07:20:52 +0900 Subject: [PATCH 1871/2418] improve get_crop_region --- modules/masking.py | 43 +++++++++---------------------------------- modules/processing.py | 2 +- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/modules/masking.py b/modules/masking.py index be9f84c76f6..29a3945278e 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -3,40 +3,15 @@ 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)""" - - h, w = mask.shape - - crop_left = 0 - for i in range(w): - if not (mask[:, i] == 0).all(): - break - crop_left += 1 - - crop_right = 0 - for i in reversed(range(w)): - if not (mask[:, i] == 0).all(): - break - crop_right += 1 - - crop_top = 0 - for i in range(h): - if not (mask[i] == 0).all(): - break - crop_top += 1 - - crop_bottom = 0 - for i in reversed(range(h)): - if not (mask[i] == 0).all(): - break - crop_bottom += 1 - - return ( - int(max(crop_left-pad, 0)), - int(max(crop_top-pad, 0)), - int(min(w - crop_right + pad, w)), - int(min(h - crop_bottom + pad, h)) - ) + 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: + 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]) 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 6b63179512e..72d8093ba32 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1562,7 +1562,7 @@ 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(np.array(mask), self.inpaint_full_res_padding) + 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 From 2974b9cee94dc474ffbc9e9617d14c9aaf9e1e63 Mon Sep 17 00:00:00 2001 From: Stefan Benten Date: Sun, 21 Jan 2024 14:05:47 +0100 Subject: [PATCH 1872/2418] modules/api/api.py: add api endpoint to refresh embeddings list --- modules/api/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/api/api.py b/modules/api/api.py index b3d74e513a3..b6bb9d06ab1 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -230,6 +230,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=list[models.RealesrganItem]) self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=list[models.PromptStyleItem]) self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse) + self.add_api_route("/sdapi/v1/refresh-embeddings", self.refresh_embeddings, methods=["POST"]) self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) self.add_api_route("/sdapi/v1/refresh-vae", self.refresh_vae, methods=["POST"]) self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse) @@ -643,6 +644,10 @@ def convert_embeddings(embeddings): "skipped": convert_embeddings(db.skipped_embeddings), } + def refresh_embeddings(self): + with self.queue_lock: + sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) + def refresh_checkpoints(self): with self.queue_lock: shared.refresh_checkpoints() From d7d3166a2749fe04f0ba1d8d0f88c8e8819a379d Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Sun, 21 Jan 2024 11:27:24 -0500 Subject: [PATCH 1873/2418] Fix broken scrollbars --- html/extra-networks-tree.html | 4 +--- style.css | 20 +++++++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/html/extra-networks-tree.html b/html/extra-networks-tree.html index 39649e86093..beec888cd9d 100644 --- a/html/extra-networks-tree.html +++ b/html/extra-networks-tree.html @@ -1,5 +1,3 @@
                    -
                    - {tree} -
                    + {tree}
                    \ No newline at end of file diff --git a/style.css b/style.css index f3fd1571bb1..ff1d907226a 100644 --- a/style.css +++ b/style.css @@ -1179,20 +1179,20 @@ body.resizing .resize-handle { resize: vertical; min-height: 52rem; flex-direction: column; + overflow: hidden; } .extra-network-pane .extra-network-pane-content { display: flex; flex: 1; - flex-direction: row; + overflow: hidden; } .extra-network-pane .extra-network-tree { flex: 1; - flex-direction: column; - display: flex; font-size: 1rem; border: 1px solid var(--block-border-color); + overflow: clip auto !important; } .extra-network-pane .extra-network-cards { @@ -1210,34 +1210,28 @@ body.resizing .resize-handle { overflow: hidden; } -.extra-network-pane .extra-network-tree .tree-list .tree-list-container { - flex: 1; - overflow: clip auto !important; - width: 100%; -} - .extra-network-pane .extra-network-cards::-webkit-scrollbar, -.extra-network-pane .tree-list-container::-webkit-scrollbar { +.extra-network-pane .extra-network-tree::-webkit-scrollbar { background-color: transparent; width: 16px; } .extra-network-pane .extra-network-cards::-webkit-scrollbar-track, -.extra-network-pane .tree-list-container::-webkit-scrollbar-track { +.extra-network-pane .extra-network-tree::-webkit-scrollbar-track { background-color: transparent; background-clip: content-box; } .extra-network-pane .extra-network-cards::-webkit-scrollbar-thumb, -.extra-network-pane .tree-list-container::-webkit-scrollbar-thumb { +.extra-network-pane .extra-network-tree::-webkit-scrollbar-thumb { background-color: var(--border-color-primary); border-radius: 16px; border: 4px solid var(--background-fill-primary); } .extra-network-pane .extra-network-cards::-webkit-scrollbar-button, -.extra-network-pane .tree-list-container::-webkit-scrollbar-button { +.extra-network-pane .extra-network-tree::-webkit-scrollbar-button { display: none; } From 26e1cd7ec47c8d234d2ea3f189b1147329c9059c Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Sun, 21 Jan 2024 11:34:08 -0500 Subject: [PATCH 1874/2418] Remove unnecessary template and simplify tree list. --- html/extra-networks-tree.html | 3 --- modules/ui_extra_networks.py | 11 +---------- 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 html/extra-networks-tree.html diff --git a/html/extra-networks-tree.html b/html/extra-networks-tree.html deleted file mode 100644 index beec888cd9d..00000000000 --- a/html/extra-networks-tree.html +++ /dev/null @@ -1,3 +0,0 @@ -
                    - {tree} -
                    \ No newline at end of file diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 80160b847b6..157b3a6d4a9 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -164,7 +164,6 @@ def __init__(self, title): self.lister = util.MassFileLister() # HTML Templates self.pane_tpl = shared.html("extra-networks-pane.html") - self.tree_tpl = shared.html("extra-networks-tree.html") self.card_tpl = shared.html("extra-networks-card.html") self.btn_tree_tpl = shared.html("extra-networks-tree-button.html") self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") @@ -420,8 +419,6 @@ def create_tree_file_item_html(self, tabname: str, file_path: str, item: dict) - def create_tree_view_html(self, tabname: str) -> str: """Generates HTML for displaying folders in a tree view. - The generated HTML uses `extra-networks-tree.html` as a template. - Args: tabname: The name of the active tab. @@ -473,13 +470,7 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional if item_html is not None: res += item_html - return self.tree_tpl.format( - **{ - "tabname": tabname, - "extra_networks_tabname": self.extra_networks_tabname, - "tree": f"
                      {res}
                    " - } - ) + return f"
                      {res}
                    " def create_card_view_html(self, tabname: str) -> str: """Generates HTML for the network Card View section for a tab. From fd383140cf405100f3c619f106472273a7545beb Mon Sep 17 00:00:00 2001 From: v0xie <28695009+v0xie@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:52:34 -0800 Subject: [PATCH 1875/2418] fix: wrong devices for eye and constraint --- extensions-builtin/Lora/network_oft.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 342fcd0dcaf..d1c46a4b22e 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -57,12 +57,12 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): def calc_updown(self, orig_weight): oft_blocks = self.oft_blocks.to(orig_weight.device) - eye = torch.eye(self.block_size, device=self.oft_blocks.device) + eye = torch.eye(self.block_size, device=oft_blocks.device) if self.is_kohya: block_Q = oft_blocks - oft_blocks.transpose(1, 2) # ensure skew-symmetric orthogonal matrix norm_Q = torch.norm(block_Q.flatten()) - new_norm_Q = torch.clamp(norm_Q, max=self.constraint) + new_norm_Q = torch.clamp(norm_Q, max=self.constraint.to(oft_blocks.device)) block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8)) oft_blocks = torch.matmul(eye + block_Q, (eye - block_Q).float().inverse()) From f4e931f18fa4f94aece1f4dabd4dd0d635ecec13 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 22 Jan 2024 23:20:30 +0300 Subject: [PATCH 1876/2418] put extra networks controls row into the tabs UI element for #14588 --- html/extra-networks-pane.html | 2 +- javascript/extraNetworks.js | 24 +++++++++++++++-- modules/ui.py | 4 +-- modules/ui_extra_networks.py | 2 +- style.css | 51 +++++++++++++++++++---------------- 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 9f5b3ecee62..0c763f71058 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,5 +1,5 @@
                    -
                    +