From f6374934db001617e2b4dda438fc4017d065e33b Mon Sep 17 00:00:00 2001 From: Vespinian Date: Wed, 15 Mar 2023 17:53:32 -0400 Subject: [PATCH 001/142] 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 c973b7708..bf8f40125 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 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 002/142] 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 7f4101b23..5bbc27a5d 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 7e6033327..6c6230028 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -92,7 +92,7 @@ apply_style_symbol = '\U0001f4cb' # 📋 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 create_ui(): 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 create_ui(): 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 003/142] 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 9b6e32415..464e4d8c9 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -758,8 +758,8 @@ def create_ui(): 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 56680cd84ab68a283772cf697f8a72408a3f4167 Mon Sep 17 00:00:00 2001 From: papuSpartan Date: Sat, 1 Apr 2023 02:07:08 -0500 Subject: [PATCH 004/142] 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 68e08114d..94bba5ca0 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 81c0b82a3..4314f97b1 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -101,3 +101,7 @@ parser.add_argument("--no-gradio-queue", action='store_true', help="Disables gra 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 6ea874dfc..0b74aa0f0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -9,6 +9,7 @@ from omegaconf import OmegaConf 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 005/142] 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 4314f97b1..80df94658 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -105,3 +105,4 @@ parser.add_argument("--no-download-sd-model", action='store_true', help="don't d # 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 006/142] 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 0b74aa0f0..2c05ec17b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -9,7 +9,6 @@ from omegaconf import OmegaConf 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 007/142] 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 94bba5ca0..846c4c200 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 80df94658..4314f97b1 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -105,4 +105,3 @@ parser.add_argument("--no-download-sd-model", action='store_true', help="don't d # 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 008/142] 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 846c4c200..68e08114d 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 6d9c6a8de..e115aadd1 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -29,6 +29,7 @@ from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 2c05ec17b..87c49b83c 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 5fd0eecbd..d7379e24f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -427,6 +427,50 @@ options_templates.update(options_section((None, "Hidden options"), { "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 df65431a3..045230ab9 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 009/142] 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 e115aadd1..557355725 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): # 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 010/142] 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 4a440193b..5311e7bc3 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 627fbe0b5..5c693b7a7 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 create_ui(): 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 011/142] 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 4314f97b1..8e5a7fabf 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -103,5 +103,4 @@ parser.add_argument("--no-hashing", action='store_true', help="disable sha256 ha 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 557355725..670a7a28a 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 87c49b83c..696a2333d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -16,6 +16,7 @@ from modules import paths, shared, modelloader, devices, script_callbacks, sd_va 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 d7379e24f..c7572e980 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -429,7 +429,7 @@ options_templates.update(options_section((None, "Hidden options"), { 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 @@ options_templates.update(options_section(('token_merging', 'Token Merging'), { 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 012/142] 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 045230ab9..035227152 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 013/142] :p --- modules/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/shared.py b/modules/shared.py index c7572e980..568acdc4c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -429,7 +429,7 @@ options_templates.update(options_section((None, "Hidden options"), { 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 014/142] 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 6df76858f..a7ede5344 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -282,6 +282,32 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model 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 @@ infotext_to_setting_name_mapping = [ ('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 670a7a28a..95058d0b7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -31,6 +31,12 @@ 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. 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): # 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 568acdc4c..d9db7317a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -446,7 +446,7 @@ options_templates.update(options_section(('token_merging', 'Token Merging'), { ), # 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 035227152..f972f975f 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 015/142] 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 d9db7317a..edf11e435 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -446,7 +446,7 @@ options_templates.update(options_section(('token_merging', 'Token Merging'), { ), # 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 016/142] 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 a7ede5344..ba2ca5ed7 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -283,7 +283,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model 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 017/142] 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 696a2333d..efcf730d6 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 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 018/142] 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 34c1b8608..83382e932 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -306,6 +306,8 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model 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 @@ infotext_to_setting_name_mapping = [ ('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 d5d1da5af..6807a301e 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 7b81ffc96..a7a72dd57 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -488,10 +488,7 @@ options_templates.update(options_section(('token_merging', 'Token Merging'), { 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 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 019/142] 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 a7a72dd57..eb06909cc 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -488,7 +488,7 @@ options_templates.update(options_section(('token_merging', 'Token Merging'), { 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 7fd3a4e6d7b1c70461eed0c8a7dc4f2412cdaf1c Mon Sep 17 00:00:00 2001 From: Micky Brunetti Date: Tue, 9 May 2023 15:35:57 +0200 Subject: [PATCH 020/142] 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 9b00f76e9..4d2026e1c 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 021/142] 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 4d2026e1c..17d1f7026 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 022/142] 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 67a1a83a3..dec6ed016 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 990ca80cb64532368f88d2037bba31167314263d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 9 May 2023 23:02:44 +0300 Subject: [PATCH 023/142] 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 a168be5b8..d42965b15 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 000000000..5bb28a748 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,10 @@ +target-version = "py310" + +select = [ + "E999", # syntax error +] + +extend-exclude = [ + "extensions", + "extensions-disabled", +] 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 024/142] 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 000000000..9e9662ad4 --- /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 025/142] 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 bc11cc6e4..2339de7f4 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -110,7 +110,6 @@ class LDSR: 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 8e03c7f89..db2231dd3 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -165,7 +165,7 @@ class VQModel(pl.LightningModule): 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 9bb95dfd1..d47c39fcc 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 ff4e9c4e5..eb4f0b420 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -136,14 +136,14 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ 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 a41965ab6..3d5d76ccc 100644 --- a/modules/images.py +++ b/modules/images.py @@ -409,13 +409,13 @@ class FilenameGenerator: 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 9fc3a698e..cdae301ae 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 696653725..e084e948b 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -92,7 +92,7 @@ def get_learned_conditioning_prompt_schedules(prompts, steps): 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 efd7fca5e..9ec1adf2a 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 c4a09d15d..9fc89dc6a 100644 --- a/modules/sd_disable_initialization.py +++ b/modules/sd_disable_initialization.py @@ -61,7 +61,7 @@ class DisableInitialization: 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 f4bb0266f..d81352116 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 3c727d3b7..41ed54a29 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 f10865cd1..b623d53d1 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 36f643e10..11c1a344e 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 4368eb63d..f753b75f7 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 d02f6e82c..2171f3aaf 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -246,7 +246,7 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: 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 create_ui(): with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' gr.HTML( - f"

Process images in a directory on the same machine where the server is running." + - 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 create_ui(): 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 create_ui(): 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 create_ui(): 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 d9faf85a3..ed70abe58 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 8c3dea564..49e062898 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 9e9662ad4..1e164abc2 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 670bb8ace..b10fed6cf 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -72,7 +72,7 @@ def get_matched_noise(_np_src_image, np_mask_rgb, noise_q=1, color_variation=0.0 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 ef1186ac6..edb70ac01 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -98,13 +98,13 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): 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 @@ class ScriptPostprocessingUpscaleSimple(ScriptPostprocessingUpscale): 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 a725d74a3..2ff42ef82 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -316,7 +316,7 @@ def draw_xyz_grid(p, xs, ys, zs, x_labels, y_labels, z_labels, cell, draw_legend 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 727ebd31d..ec3d2aba1 100644 --- a/webui.py +++ b/webui.py @@ -360,7 +360,7 @@ def webui(): 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 026/142] 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 2339de7f4..a5fb8907d 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 da19cff12..e8dc083c5 100644 --- a/extensions-builtin/LDSR/scripts/ldsr_model.py +++ b/extensions-builtin/LDSR/scripts/ldsr_model.py @@ -7,7 +7,8 @@ from basicsr.utils.download_util import load_file_from_url 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 db2231dd3..6303fed54 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 @@ class VQModel(pl.LightningModule): 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 5c0488e5f..4d3f6c56a 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -450,7 +450,7 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 43ca8d36f..8028918af 100644 --- a/extensions-builtin/ScuNET/scunet_model_arch.py +++ b/extensions-builtin/ScuNET/scunet_model_arch.py @@ -61,7 +61,9 @@ class WMSA(nn.Module): 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 @@ class WMSA(nn.Module): 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 e8783bca1..d77c3a923 100644 --- a/extensions-builtin/SwinIR/scripts/swinir_model.py +++ b/extensions-builtin/SwinIR/scripts/swinir_model.py @@ -45,7 +45,7 @@ class UpscalerSwinIR(Upscaler): 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 d47c39fcc..f52d371b7 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -15,7 +15,8 @@ from secrets import compare_digest 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 import checkpoints_list, unload_model_weights, reload_mod 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: 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 @@ class Api: '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 4a70f440c..4d2910765 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -223,8 +223,9 @@ for key in _options: 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 11dcc3ee7..f1a7cf09f 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -7,7 +7,7 @@ from torch import nn, Tensor 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 6071fea75..7f8bc7c05 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 04f27c9f7..aa2a14efd 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 3d5d76ccc..5eb6d8556 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 cdae301ae..32b1ecd64 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -13,7 +13,6 @@ from modules.shared import opts, state 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 9f7d657fd..22df9216f 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -11,7 +11,6 @@ import torch.hub 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 cb85ac4ff..cf6850004 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 f880bc3c7..611c2b699 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -479,7 +479,7 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 a241c8a7c..0a9defa10 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -54,7 +54,8 @@ class UniPCSampler(object): 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 1a76e5529..6f5233c13 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -664,7 +664,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: 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 e084e948b..3a720721f 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 ba1bdcd4a..d7d8d2e31 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 2171f3aaf..6beda76f3 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 e2eaa7308..0ad4fe999 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -45,7 +45,7 @@ class Upscaler: 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 027/142] 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 ba1293dfc..0ab432290 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 c7fd5739b..aa2fdb3a9 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -13,7 +13,6 @@ import modules.upscaler 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 d77c3a923..55dd94ab8 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 basicsr.utils.download_util import load_file_from_url 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 f1a7cf09f..00c407dee 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 e72936838..820e6b127 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -5,11 +5,9 @@ VQGAN code, adapted from the original created by the Unleashing Transformers aut 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 8d84bbc90..8e56cb892 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 2ea00929c..8f1ff4288 100644 --- a/modules/config_states.py +++ b/modules/config_states.py @@ -14,7 +14,7 @@ from collections import OrderedDict 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 f4369257c..85aa69349 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -6,7 +6,7 @@ from PIL import Image 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 7f8bc7c05..4de9dd8da 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 34d9d6544..829f8cd97 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -3,7 +3,6 @@ import sys 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 fe8b18b20..f1c59c466 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 1fc49537c..9fe749b7a 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 modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ -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 76599f5ad..be168736e 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 5eb6d8556..7392cb8bf 100644 --- a/modules/images.py +++ b/modules/images.py @@ -19,7 +19,7 @@ import json 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 32b1ecd64..d704bf900 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 40ce21017..5c2f92a15 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 cf6850004..92ada6941 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 11b330bcf..a4c4ef4e4 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 6f5233c13..c3932d6ba 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -2,7 +2,6 @@ import json import math import os import sys -import warnings import hashlib import torch @@ -11,10 +10,10 @@ from PIL import Image, ImageFilter, ImageOps 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 d81352116..81573b781 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -3,7 +3,7 @@ from torch.nn.functional import silu 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 55a2ce4d1..344d75c8d 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 41ed54a29..6fe6b6ffe 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 4ac51c386..28528329b 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 11c1a344e..1c09c709c 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 7a79925a3..9bfe1237d 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 0fc9f4566..3b8e96222 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 521e485ad..b7176125c 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 4631965b4..44cd2c0c5 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 11642075f..c22769cf7 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 d7d8d2e31..7770d22f5 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 5593f88c7..ee0e850ac 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -2,7 +2,7 @@ import base64 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 da0bcb26b..d0cad09eb 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 f753b75f7..9ed9ba459 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 16841d0f2..f022381ce 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 6beda76f3..f7e575935 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -14,10 +14,10 @@ from PIL import Image, PngImagePlugin from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call from modules import sd_hijack, sd_models, localization, script_callbacks, ui_extensions, deepbooru, 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.shared as shared 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 49e062898..800e467a1 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 f25639e57..c7dc11540 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 0ad4fe999..777593b00 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -2,8 +2,6 @@ import os 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 beab3fdf5..e056c3f6a 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 1e164abc2..9caa9ba29 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 f36a36754..cc6f0d490 100644 --- a/scripts/custom_code.py +++ b/scripts/custom_code.py @@ -4,7 +4,7 @@ import ast 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 b10fed6cf..665dbe897 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -7,9 +7,9 @@ import modules.scripts as scripts 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 ddcbd2d3a..c0bbecc12 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -4,9 +4,9 @@ import modules.scripts as scripts 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 e9b115170..fb06beab4 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 76dc5778b..149bc85fb 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 @@ import gradio as gr 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 332d76d91..d873a09c4 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -4,9 +4,9 @@ import modules.scripts as scripts 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 2ff42ef82..332e0ecde 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -10,15 +10,13 @@ import numpy as np 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 ec3d2aba1..482770754 100644 --- a/webui.py +++ b/webui.py @@ -43,7 +43,7 @@ if ".dev" in torch.__version__ or "+git" in torch.__version__: 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 028/142] 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 e8dc083c5..fbbe90050 100644 --- a/extensions-builtin/LDSR/scripts/ldsr_model.py +++ b/extensions-builtin/LDSR/scripts/ldsr_model.py @@ -7,8 +7,8 @@ from basicsr.utils.download_util import load_file_from_url 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 d906a5717..e01ca6551 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 122fce7f5..1c4554a20 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -2,7 +2,6 @@ import os 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 829f8cd97..bc2c0450f 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -6,7 +6,7 @@ import time 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 fbe6215a6..0131dea42 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 e1265e3fe..dbb35964c 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 acf1894ba..5f6474c03 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 9ec1adf2a..c24d8dbb5 100644 --- a/modules/realesrgan_model.py +++ b/modules/realesrgan_model.py @@ -17,9 +17,9 @@ class UpscalerRealESRGAN(Upscaler): 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 a7d2203fc..57b158624 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -2,7 +2,6 @@ import os 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 344d75c8d..058575b79 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -4,7 +4,7 @@ import ldm.models.diffusion.ddpm 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 1c09c709c..d1e946a5b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -15,7 +15,6 @@ import ldm.modules.midas as midas 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 @@ class CheckpointInfo: 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 ff361f22b..4f1bf21d8 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 44cd2c0c5..7d70f041a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -12,7 +12,7 @@ import modules.memmon 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 f7e575935..782b569da 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -10,10 +10,10 @@ import gradio as gr 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 777593b00..e145be30e 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -41,7 +41,7 @@ class Upscaler: 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 9caa9ba29..0883c1274 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 482770754..5d5e80b58 100644 --- a/webui.py +++ b/webui.py @@ -16,12 +16,12 @@ from packaging import version 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 @@ startup_timer.record("import torch") 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 029/142] 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 0883c1274..24f5b1aed 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 5bb28a748..000000000 --- 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 030/142] 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 24f5b1aed..2f65fd6c7 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 031/142] 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 6303fed54..f457ca938 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -288,5 +288,5 @@ class VQModelInterface(VQModel): 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 4d3f6c56a..d8fc30e36 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -1116,7 +1116,7 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class Layout2ImgDiffusionV1(LatentDiffusionV1): 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 0ab432290..9795540ff 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 7db971fd5..b70e2de73 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -53,7 +53,7 @@ script_callbacks.on_infotext_pasted(lora.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"] + [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 8f1ff4288..75da862ab 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 1c4554a20..547e1b4c6 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -78,7 +78,7 @@ class DeepDanbooru: 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 c705a3cb6..d8a34a0fd 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 9fe749b7a..6ef0bfdfa 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 be168736e..e3f9eb13d 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -5,13 +5,13 @@ import modules.hypernetworks.hypernetwork 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 22df9216f..a1c8e5371 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -159,7 +159,7 @@ class InterrogateModels: 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 92ada6941..25612bf81 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 611c2b699..094321170 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -1130,7 +1130,7 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 30d6d6586..d63078de5 100644 --- a/modules/scripts_auto_postprocessing.py +++ b/modules/scripts_auto_postprocessing.py @@ -17,7 +17,7 @@ class ScriptPostprocessingForMainUI(scripts.Script): 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 81573b781..e374aeb88 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 b623d53d1..a174bbe10 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 bfcc55749..7427648fb 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -83,7 +83,7 @@ class VanillaStableDiffusionSampler: 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 3b8e96222..2f733cf5c 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -86,7 +86,7 @@ class CFGDenoiser(torch.nn.Module): 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 7d70f041a..e26915858 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { "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 @@ options_templates.update(options_section(('ui', "User interface"), { "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 @@ class Options: 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 9ed9ba459..c37bb2ad8 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -167,7 +167,7 @@ class EmbeddingDatabase: 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 782b569da..84d661b29 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1222,7 +1222,7 @@ def create_ui(): ) 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 create_ui(): 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 create_ui(): 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 800e467a1..ab5859176 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 setup_ui(ui, gallery): 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 46fa9cb05..cac73c511 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 032/142] 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 f457ca938..8cc82d543 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -24,7 +24,7 @@ class VQModel(pl.LightningModule): n_embed, embed_dim, ckpt_path=None, - ignore_keys=[], + ignore_keys=None, image_key="image", colorize_nlabels=None, monitor=None, @@ -62,7 +62,7 @@ class VQModel(pl.LightningModule): 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 @@ class VQModel(pl.LightningModule): 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 @@ class VQModel(pl.LightningModule): 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 d8fc30e36..f16d65043 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -48,7 +48,7 @@ class DDPMV1(pl.LightningModule): 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 @@ class DDPMV1(pl.LightningModule): 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 @@ class DDPMV1(pl.LightningModule): 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 @@ class LatentDiffusionV1(DDPMV1): 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 863f42db6..75f7bedc7 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 0e28ae6ee..d4c0b0da3 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 f52d371b7..9efb558e6 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -34,14 +34,16 @@ import piexif.helper 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 00c407dee..ff1c0b4b8 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -161,10 +161,13 @@ class Fuse_sft_block(nn.Module): 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 820e6b127..b24a0394c 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -326,7 +326,7 @@ class Generator(nn.Module): @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 @@ class VQAutoEncoder(nn.Module): 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 f1c59c466..7fbbe7071 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -19,14 +19,14 @@ registered_param_bindings = [] 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 094321170..af4dea15e 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -52,7 +52,7 @@ class DDPM(pl.LightningModule): 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 @@ class DDPM(pl.LightningModule): 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 @@ class DDPM(pl.LightningModule): 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 @@ class LatentDiffusion(DDPM): 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 a4c4ef4e4..6f8ad6315 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_wrapper( 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 e6c2f2c0b..2d5b972fa 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 7427648fb..b1ee3be75 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -55,7 +55,7 @@ class VanillaStableDiffusionSampler: 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 ee0e850ac..d85a48886 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -17,7 +17,7 @@ class EmbeddingEncoder(json.JSONEncoder): 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 f63fc72ff..fda588984 100644 --- a/modules/textual_inversion/learn_schedule.py +++ b/modules/textual_inversion/learn_schedule.py @@ -32,8 +32,8 @@ class LearnScheduleIterator: 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 2f65fd6c7..346a0cde8 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 033/142] 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 a5fb8907d..27e385496 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -88,7 +88,7 @@ class LDSR: 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 9795540ff..7b56136ff 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 aa2fdb3a9..1f5ea0d3d 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -132,7 +132,7 @@ class UpscalerScuNET(modules.upscaler.Upscaler): 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 75f7bedc7..de195d9b0 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -848,7 +848,7 @@ class SwinIR(nn.Module): 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 d4c0b0da3..15777af9d 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -1001,7 +1001,7 @@ class Swin2SR(nn.Module): 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 8e56cb892..ececdbae4 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -94,7 +94,7 @@ def setup_model(dirname): 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 85aa69349..a009eb427 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 1978673d7..f9db41bc0 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 7fbbe7071..b0e945a12 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -247,7 +247,7 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model 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 6ef0bfdfa..38ef074f1 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -177,34 +177,34 @@ class Hypernetwork: 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 7392cb8bf..c4e98c757 100644 --- a/modules/images.py +++ b/modules/images.py @@ -149,7 +149,7 @@ def draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0): 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 a1c8e5371..111b1322c 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -207,8 +207,8 @@ class InterrogateModels: 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 3a720721f..b4aff704a 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 2d5b972fa..1e791c5bd 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 d945b89fb..0c12ebd54 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 @@ class ScriptRunner: 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 @@ class ScriptRunner: 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 b11568c05..6751406cb 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -66,9 +66,9 @@ class ScriptPostprocessingRunner: 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 @@ class ScriptPostprocessingRunner: 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 9fa5c5c50..c0c350f67 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -223,7 +223,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): 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 e26915858..913c9e631 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -211,7 +211,7 @@ class OptionInfo: 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 @@ class Options: 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 fda588984..c56bea45e 100644 --- a/modules/textual_inversion/learn_schedule.py +++ b/modules/textual_inversion/learn_schedule.py @@ -12,7 +12,7 @@ class LearnScheduleIterator: 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 c37bb2ad8..470353325 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -29,7 +29,7 @@ textual_inversion_templates = {} 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 @@ class EmbeddingDatabase: 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 @@ class EmbeddingDatabase: 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 @@ class EmbeddingDatabase: 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 84d661b29..83bfb7d8c 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 create_ui(): 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 create_ui(): 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 ab5859176..2fd82e8ea 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -90,7 +90,7 @@ class ExtraNetworksPage: 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 cac73c511..f05049e1f 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 e145be30e..8acb6e96f 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -55,7 +55,7 @@ class Upscaler: 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 346a0cde8..c88907be8 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 149bc85fb..27af5ff6c 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -156,7 +156,7 @@ class Script(scripts.Script): 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 d873a09c4..0b1d30968 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -56,7 +56,7 @@ class Script(scripts.Script): 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 @@ class Script(scripts.Script): 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 332e0ecde..38a20381f 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -704,7 +704,7 @@ class Script(scripts.Script): 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 034/142] 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 27e385496..2173de792 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -157,7 +157,7 @@ class LDSR: 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 8cc82d543..81c5101b7 100644 --- a/extensions-builtin/LDSR/sd_hijack_autoencoder.py +++ b/extensions-builtin/LDSR/sd_hijack_autoencoder.py @@ -237,7 +237,7 @@ class VQModel(pl.LightningModule): 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 f16d65043..57c02d126 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -375,7 +375,7 @@ class DDPMV1(pl.LightningModule): @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 @@ class DDPMV1(pl.LightningModule): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 9efb558e6..594fa655d 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -165,7 +165,7 @@ def api_middleware(app: FastAPI): 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 af4dea15e..3fb76b651 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -405,7 +405,7 @@ class DDPM(pl.LightningModule): @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 @@ class DDPM(pl.LightningModule): 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 @@ class LatentDiffusion(DDPM): 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 @@ class LatentDiffusion(DDPM): 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 6f8ad6315..f6c49f874 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_wrapper( 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_wrapper( 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 058575b79..c1977b194 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -23,7 +23,7 @@ def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=F 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 035/142] 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 b70e2de73..13d297d7d 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -53,7 +53,7 @@ script_callbacks.on_infotext_pasted(lora.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"] + 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 de195d9b0..73e37cfad 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 15777af9d..3ca9be782 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 ff1c0b4b8..45c70f84f 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -161,13 +161,10 @@ class Fuse_sft_block(nn.Module): 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 e3f9eb13d..8b6255e2b 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -5,13 +5,13 @@ import modules.hypernetworks.hypernetwork 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 f6c49f874..a227b9478 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_wrapper( 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 6751406cb..bac1335dc 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -124,7 +124,7 @@ class ScriptPostprocessingRunner: 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 c0c350f67..cc6e8c21e 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -223,7 +223,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): 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 913c9e631..ac67adc02 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { "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 470353325..9e1b2b9a8 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -166,8 +166,7 @@ class EmbeddingDatabase: # 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 83bfb7d8c..7ee99473a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1230,8 +1230,8 @@ def create_ui(): 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 036/142] 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 25612bf81..2a479bcb5 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 7ee99473a..1efb656a4 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -13,7 +13,7 @@ import numpy as np 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 create_ui(): return res + loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config_file) + components = [] component_dict = {} shared.settings_components = component_dict @@ -1558,6 +1550,9 @@ def create_ui(): 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 create_ui(): (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 create_ui(): 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 create_ui(): ] ) - 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 000000000..728fec9ea --- /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 b823c7ddb..4ac919b5e 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 5d5e80b58..2eecfaa08 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 webui(): 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 037/142] 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 055953236..f80c1600a 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -202,13 +202,22 @@ def efficient_dot_product_attention( 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 038/142] 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 eb5f4e762..1d1b07bd4 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 @@ class UniPC: 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 039/142] repair #10266 --- modules/sub_quadratic_attention.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index f80c1600a..cc38debdc 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -201,23 +201,15 @@ def efficient_dot_product_attention( 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 040/142] 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 948e6f008..289dd311d 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 ac67adc02..fc39161ea 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -420,6 +420,7 @@ options_templates.update(options_section(('infotext', "Infotext"), { 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 041/142] 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 7bce02e5f..476029044 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 042/142] 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 17d1f7026..95262ca31 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 043/142] 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 6765bafe0..a3d3e1f88 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 @@ default_sd_model_file = sd_model_file # 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 044/142] 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 c4e98c757..d8527179d 100644 --- a/modules/images.py +++ b/modules/images.py @@ -24,6 +24,13 @@ from modules.shared import opts 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 draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin=0): 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 d85a48886..5858a55f5 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -3,9 +3,7 @@ import json 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 045/142] 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+ z95>S_#Ag*w$efep>jylzX)aZp302Dra_~Z=zG`U{3N&jq&blcM>T~eLqP)I1Mqd;c zQEB=gI77Bp<6wFH6boiz9u?g#hcARn{BW9aEUPkofE8a95N)HQjp!uXa!QK9_vE03 z;>lHLq7K9kH8~Q7UFKPUBowsgX*qa-Xmt^pB}iEV?n@^~F8E-n=L2Nvv0Aar<=z=$ zrKpf*e0e>1sDpltcE17t*=+bCjQ^edai&-y>ZR$kbu%dOMev4R0ADytKnjNTAsr)~ z!3`-h#LFalU$I~c(1H4VUc?W|zh;P+MCCM*V3d(DM7)sx2fi3*f593f4C)lj{i@qD z#mgc-%}kIjfSna(oCWsC9Q1LGxBXX7@q*0g3Z*ilvFKOs#DJQ7qdvJuYEs4jI)LMYIYHYv0uMF0N7Qi z%|=}WzTIT+d5M#bB2|nO;(~mtzZ6ZVkLMHPM&8gHf)kH=QblQdUPCr}C*`0SqN!xj zAU{Yy)**Q4xJOjqezIzp z3Huiu5Kcr}fg6B|vV~2w(WwIfnRqffXWi_tzkzsv z9sS~n;3Q)UcSa1y4(r5<&lUVTGJ_;vCk?hTHiKlT`c_Wf-4D*llk`D(bYEmdUBwra zZSIjyj^6l++yH#l8|4n50~K1JpM@3xmAE0!^|NVYxJwfs-zUkU;u`QM@R181WDU`M zDaMYpi+(*UHd1rUM%4)Rg4~BI9q*>X^*O6;p$)T%VtBI0E-D+Ay(-!Dtd6`Db3yJU z!zyAzo?zB)6im|J_Eb867Eogxw}Di(7K@QjIY;goDaO%o1blCcu?mS88nuGm`@o#>}_PBFBXaP zg>f_3p|M#jtFl2h(PJE_6-a8_T~5p4h~GStW}cPTR3hxC7a44aiTQyGj-{XjIA8WmUs55A$((!D7hT_;OV+$+!hf&*14wg!(JY z--(479&o%bwKDDl?0`(@P=mVE+F4qBK24 z|7>J%nNFMU{+n4elSKk_kNd0FEOOmEzL;wjvC!}!}Np#pmX6wqw^lvEx0Q?L%wPv z7%_YNo+?+vR%fOezllyG<-*`(R^MJ>>LkptX&?#7=P+^E3w+*B!)6&9@o8B)KC)dikDG{s!r$zdU(q+s=^MJI1z*^sQ=%8bch`PX)`P7uc*j zb^#XNwhJ_kRaQZ73rn0;2gpp{>9W~74Y@Te$YULB7sTmYL=s}ZqjxdWSB;lYMUUgA zSjMTs%<`91N55##lnVrJpi5@xv8*+Y7Pu`0E#PlFYC!Wwd~(qk#TLZFIhm&z!44VO znly4V)FkR(sW3T(WF9c!aG&=~rOBIB##pZnry=dQT=0T#EA8Sz+Lb(XywDlr>ke1PYUPzm_)cnH)0Ku17> zMCAlD^qvT4)NYiu@)S3i#|^RZq0NNv25*qomCvlx2*H~J*;6|D_!MCoUAT z|4tL*OubQ7JV>Wma)+2X*KIWY!XpVc7v zAuQ&3G1E~^7^&|~x;NCZeU3HG;b@6#>}at>9PcRys^dQCZwHg$P_nc=(@dLTa-fbN z_Q?!v$?Ax)4y8MG$<}da1cn-V%n}==vCzjK@kRDgkK(FOE(}TGIj2aVIVYr%(@`Z( zc#uc4XVO&iaWRA0xfPr&7mgI2jNedz$MJV$4gBE9{`#MUtUnQ4HWKGPr2y6A)+$*$ zR`t4?s4igY39>p-?+yo-CVfr?a+))H(5BI22b(ibmj?B)*Wewlbqv;!x$XLSdIj_2 z8f?#~K)M3U%DR#m_A-b(3lfuYBVQV(znF^amso^OBSBla>rgGZ>$uSn%sh+BD>QPb zS+G+_kABCf&aaxunH0%%d;^&{V>b6SHSOdAoKTlB>JktU>!r1Bk1Zv3a*5`^|BB6| z4{jJ8@qyFzm6EA}iF*DKj7t^83P&R`Uj?$`(lhJ!D8}xC$EPPvhfmL%{(6q_!JfG7M60j_L_uv&e4Q=UK#hhQjxt-=ETrf`}np>Sr_u_H{> zb#&Ofpm>LhPdP}WpdJpPpB(CdM@fxrWWH-I*2?CGTp6!x1lnx6901X>8(XptZ}%Un$=viO}iED%UR1Dtw1@C z6r^A{DrfuwWNF&yrt4d zUX%MbAJnYbpyvO`(f+QJCUxyTZd~_jtrBX~Y}E=1lO{`q4u%eJoBfwAOT&29$gn_G zgaxu~zAW(%&4GjPhX`z`$y_1JhE9dP;?gSrvwp==jhIWYD#sGsxuK8NOO>3qzAhqHG&^4lMN__oHkCr*5e1o9pl z{t;VM3FZDl?!iJ~N!M|W0JtZ2q;ShdaV>0jBp#2+WR>+yc%1)^cvhP*aq@taCjBGv zjDi1_c;-DWaWnc~5jQiw^ANABHG~WJ52DFUF&vjKt1TDKT2ROBBUxX#EE(JX4NHap zNfu^Cq5oU#WBgB z@32yh>4KB?`(5y(9eP}_qHpVvjP1U=DD4+lznS%cQb_GlLrOU|bYNEAfbM$aOi6We z59MLj|NYR{qYAA{A93LG-(;zQQ_EMFK42N{7b$vG9$fmgUkmP^BftJz%VZyf){m z1yq4IY})ri8jlqmb?8HBg!b$jo*pdz^0G}`I={Jm)sqWEol8-Fi(1q5Iyi)-$hwQG z#PyXwzfPoaHMs!Wcf-7D_Qm8;d~2}HJiO2%e%)J$UM1<1qz7ibF7C2M?D;q&WB+HP z3avt3ROCy`Hg|qxW9m!tk-81$J}MF)cv?JgwFHWNacWlK-({w;bKS)?;c z0tzNa7&jr4*`y?YY)iDy0<@EKdx~P`exiT?4*a^3_Uek%_4J=!=~vNMU1x^l)_+}h z_3Aq3`mFi5(xfg37otRSoJs`m1PRD|r)P0p;Ajh))zQvV{i%<~q^~JF=Cd!Z{U+rb zUAn%pY|WbDQ-Xi_j!%98EnMQN{KPo0G?=mED%$vG7S>~?=8z`0$I}uGy&^KT^pQIb z{B8XG*~g;_20vJX3pu;3PsLR<8PU2qQPjQ^^_Qsg?3mzDx`MOV!pqbWq(C=7oVQW( zEvR(<8Ko3rdj%hS=ANBld{*s^S=Z&M+22pXC8QMtY3tTbl>*yXmaXa;Rvo-IIht#5D41tttq)y_n4)lVyr=gyNOQ zj&Jk! z+&L%Hzq;qHv?xsapXyaPtwf0`jV4bCzDV{gvLC#@0&pbMgc_TGf`jzmjkucD@VTZw zBN*eO>qeg$miHtY=UCwK2OsPUGGNPVwdf+&>|GsE8#^YK|FGGtj1ue(IjuZ6oO+>%r%gk(e{?IZ^MI zGGru#k-B_&YGzHdZ-G7?I`u2qDyDN%rGoYAb%}0OuwSPReF_BMQ5NLab&%_V;4Sl4 zXw^MPv&UG>hx~&dG^BeNoxK5|l*@N6Q%&Q_rsc793r4kgPD~oJrDNKE3eEjqu45zO~>qMmtICFt~@i zhOc0-DI|~kwtO{Q$v1^|&|R4{M9^6pIJXmfGoYpzD)N%NMcMZ57~-pO;?S2P0~>em zP7|ig8gcNm6PXi9+>veDLiV-V1nA+Rfz!&De|q53XQt{!U~3jlF+W?rdi8So_dHl1 z{Xtv!(Oo*kK=_?wY~jE4c%rP@s{h4xCD#2)Y8|{~;QS|SF{r>(YZy4kDW*fw$nupc z5$*Erk-V)m`o8y@uZ+4^RC{3Z*7wXFZ`3PWHF(gLdDHTGZO1#tO?z2SZm^~mc=<(h z&*T?y&t68)JLNr~n(bg3+7KLx@*=XN??rSAQ`>ShZt)2T;d9_E3rFbJEpE}N1Enx| z=6xA_GD1UP`Hfw@SDt<6%R${*B=*}Z%5GTsu*jeA#})+ModT(hpjM!Yyw zo*bI^_?V582hWbJ{qXc2;}@+Ox^QHD`QzACr(Qp7%&pzEPK_q*>a}bad@`|j8&S7i zBbXxri!jgRbG+!RU#mXl%2q7l<1>Hf&P@}M@>Xi9Zd9964Od{*=rW~?q*I>}aP`q1 zqe8SV1s`G)I+EE+l%q4;i-~fE?|LvgpB%LQpuXIw9Q-LiE*q#{AebTIi!WP7XED<$ zi0>_x(?t!Oxx^d$jcp*uN5fS_$42+&SR}4fpg{`az(LGu>4$&5c=Ur0=oscJSIh;U zim%pe*ySF>OlP_tz%k6*_yiaV?vUa(j8=0I4Bxmg65I1cadbbj3*~U&3Y$8;?~3tl zmQ7!%;{fSbCnhYNJX6Ta!S9-gL+!^d-Kx*8Cm(7vCk4q{@Nk>#>R*DFYh0Gv>qQST?+>GUDT!+rSO;IEvk0!am*Gh7@tx6=%YBkIjv_; zTy)SY_$nQ8X(Xk-hJRAC-qz>sL+j8D!F0kgZ6Z=fCM%OtpGIBWIlnf|-edBt;T{HE zTJ1II=N!|c>A{E1w$=q_plsM`wQq`>KU6j~%qZj(g4`tLJ$q2qiW;1Dv-zCHhD zu-|Ei8~;I^bMT?FJ%5<5R45-;3lV%#3@b;7`v&{$U4x^t<_G&(_sQd6`B~m8Kg|3C z8;C!5L_kFp%R>Zz1|JJJ1%|8d{~Sd-k`BjW@02Y(k~|12)*|nuZFBn2owChQMYJ1L zu>&cas2J?0+N29_+*itW9yNr^>e(oMhhjmXdhJ{l>HPE0XWXeh%!B_YwC{gAW*~eN zgR3={g8lrjaf3O>47?uZ)~cttC56nZ8*pX++@34H1xX{L@8nqNd)aKKRFEd-*)aDl zU$#z;$t;jd)0B0zR5r=cJa7i0_51XK`ZjETf+>eB#`MSh8S_Yv=Fr)WQFQ1&vN?*B z$A5Jj6vcr?dU>P>RD1>5WB1|oyqDxRGUXM0ioEdo#mnN#u@j=SQCpve-0yweu*Km8 zeT6iID0`)MrYI`!`~Ojd8rLKo8QQLp_UWg; z{6ZI}6`#6Zv`bB2C@gtgXJ~> z-g6{Q#pihyw@WASAnKbFo+8fY21mWFc;FQsI-q}1(0y-A#~*UwTC%v8@hPaYk@caG zs;{WR?UfUrBGPk%quhOAIJdi^ldS#8a45XF(`tZnF(W{|Qp6n50Q?NcO#QN71H7dw zSA)cIfu{(BLGOV3#e|VA)OW?E;ui~^Fdbaq0he~Nw#GxFmjf5`v|Cy}2jAb@fm{Cp z>0bfwE#r-=W_!SqK*2f~$2!PS2Il=?oA-T8KPNmzbjbw{-un>mjLlpsD#^4B6dDLx zVxUi=RN7PO*AnK@Ug>4fr-mMA$~}N$wMWydxljw*S20BJbiiA1?;#qNmK=J7;FK3_ zoMm}+CfZ^_Kav;jt*5{qN(8^8d)k-GcTRYU=#vW^^%+9-VQi*NR6x7GOPgz{XeU!j z8nF2~Yc8{=UFLyY;VA-1wRhm7%*R|PQ&&`ywHFwh?$X`L)WI=p9h?GCVL0Ox+^;1+ zLQuAe{-02PWfMnh2SqI4tfM%sMCqts8$CS2!L|xI*kX=$gEd`YAG5J7z*nFQ+Kt?v z&s8gls0rOKA#fWOU)e`1$8?mXI)$r@UUh?BJvuJAeeJphTX*j`a>H}MHFNigYkyH@ zNv+|{THiCNTE9Lo3`6qGO#L36!d=5i3pi5;ytIdUR~LVx zcEN*BhLQ07Vq#~aPQ%GTs~Mn`NrA*fZ4H}m3NKaj>rj-|1h*<0M|{O>{FeUTfUkz7 z(b|jRVY{w3ScRyQLxp;g3K8{|7V*y5P?e9Ex-to*LTnNMe~x)q_@;|LSE#{6;Zt1| z{#?-rrQieu+1_jkZv#pXC(J$*16Qf!P`nMs;$RINh%r|`nHbhP>?vI>tE~!Vt#Xxa z?ocROZ#jfxs}=aFcSqTB##i}(IJ+DJ-V-hojW(K*hV?roZ>wNy1#`oa$DgY^s`co& zC!p=Y5vj%WGS*1B1p1xa(E@OVQ7ll*Sf1 z9UJh)kU>3bIp7^__+4T-)AyVAGJVDe{bBMAz`IJn=*sjN->QfMAqY;L{a)YO4t?Ms zHTMFJbV&UzHa#}J=<1pt4}Iv`&LEw;PWZ3*HOPnY0e>*~zM>Bgv?1uT2MUkuCXZjN zQ_K(f2IQ&rQpQL6eF5g5@xjm6%;y5>#|0AP9Wg%ObshdepFBRs7ZXMCFnu5OPQpj; zdfnDzY`x8aCt8H7+4==Y^*Yk<=&~NPZDd;P9d!?yy2b9 zyP`fx^k#3kV=&W;kGhVNs|X*L%6RYruOpYZ+>q>&SLqe9Rjcu?zyToxU=RuT9c^XE;yH*;+7t zaPtb!PXd^FU-3^u@3HBNrM`J6SIvuP{zgMB&EIzM{Ehut_I}9|h4=d?dB2R!o+V>L zi}$i;sjx|l8Cx6OhqPGHCeKpQo}*o3El}8`#e_{-JePS__%v!L{CT1cTTI6H(>zDv z&x01zJV)V^mMQ#&BHp2I=3~h#{Dm6jF9=+UG0EHr%Udq&T;h817z7_E=+J<#i^08G z%9}Oz(jcEynv!vLr7cU{zI#bZlYSW9ro@)C|>-5NGZ2pu zuxH!wNr*g=NnP(NV@(JA8Jo+?VmjcxLQk6YSXrQiYziMB^AP$FwZ#F;dbG8f;MS|5 z8ljZD;pA7-tunx8LvFp=@(idK2bIzOvVgwL)DyJBTG?FSDPlk_aJ0yXFm|L(1)Xu| z{SFNurg=8)WSk8U#-8NB5f-?qRd(?GPdRXe1*AqtQ}vUr>4r7Hw=j>B#Q0Da;K=wl zwlMFn*}T8O^mD>f!~?m&!FzM!hOwDTqJk;v7(hci(KmWCZEjO7VJ6`&#PCql15GJL zxCa5P;6lmKDMRJ4Q}+;4W<5espq;~q*~3rLQr&+64k{vf@im7`hfN^D0#_M7Cmff) z<_1TvneBlSeWnAGIU`J;`BC(fF?dp~_?lZTmmLd~`&F*+6wx;qIOI6grNYz|m1IoE z30@Eyb}v&;;#Q*BC&5|se4p8^4aZ_wvKoXz zdUdvTOn7$HrT1XZIos|GD}L64BB0LPMyY(vIP<79EJZxQJZ6WZrsIfnhWgHeN5+PI z=-|OFD}l!s=v}ksy{=;oMB;HDv>oEC6h&tD%^!IkGhLg zIfS{3-kOD{y4L8>JsQLpIt2KA%#XsK6Kci$_%H*)eSj#J;Ftk@M*|5Auy~xgRM5G% zsZO~0)-1@pa4ZXhm!+B!n^bX^E@<>79>IHY`6}>d!VJx0d>YjJT1o^_nI}zJtOqs^ zb-*qpUn4kSo6mdTQ;oGQ+{H%|4+>%y#mb{3!^Q%$gy(H;G+Jy19fpH`vV6tGCuLK{ zWums3@!5#5#8l@Wt<55dDWk&@Q^sPB_I4PbwLrla3crG*BjWmG&{y#J*#AVb#NcE` zvlI|N*s_Bc8-8t5%>cGC zmSkMX%web-K&G3aEM10bqX(006_k1hL8*6OW0wQ>(*UU8^WYoP07&@7nG*$Hs9oZ2 zJ{~uu=Y#Q!3Oj7xo5;y+3%i90u(DDdKiE}C)AwChf+W%is4Od{Erx(g>ugl zTz}BUZ^7`FLO1kH{&9M=-kj>H*9cYSB@E^s(Veai0+>#QQ4z%swbT z$2#xn2wA*WL6>c)2L*Ke6r@q~IC$bmvYq|x@5Jvhv9jM&@W4;O06g==8J)VB|kTz=g<)t*xZ)c_`6(ua=N!=q92n_9h6Ak z@ZD~M?f^`kulNxZ(bDi!Dn9}H1)OR5 z7q`O0!1n7k?x$rW1mAFkFdY*5JlBDP5|~K)Dab(Y3TJl6HT`l6!bQ`5{jpMlnidU)uVUr}vgEECn zfAyF4M*-eW!SybobAXfPwB!ZNDg?(YhZjuwf|^=1uh2PshFC99Y!EVPC` z!b!+IS=LGX9my(=0`NFigvw134}~r}{Bls5oIAgmlSHL7!JnRZg_fXK%`IMmvQTVIJ&X7WNa~_*Mls4u`(M z_q}ABh&c2p+s12s%omu>1lcA~o0mxHF~5C$PcyuOOfr)x#H)`cxOGCj6#4*ky2HtQ z%WMbuEE^t=oxPNdljKdt0sha7-&-bm6C?od#BkOZwk&GYt||LssetMSeFME5mYUWn ztp;oXM{&L8h`(8`kb~c33-gQ(@5}Pb`F$D4v+TcJl1KTBpG%UGTEf30aYV(=xIVBB z_?}~CW(VjqdNZDtzWn?3+&;Ljqp1Hl;gi=2XB*n-L)yf5X8hzre;$7MvJmj}F9((*0!Zb>@Xfty~h3 z)KU$}p8XW?n94F34Bj)ylIm~qGvU=Ei>#}@o%20ETL#JnKFJJQLn#4@zw>qp*{;Gpd~^J%wo7*eVT0VPi?d_AeHS8ZP$N9C0PumRLx~sDS!%Sc5|> z`3W}X0mdJKvFt8HhhUz7;x2xVUrZT zwF^rnH5IVR7n{<=m=|jg5`6Bp zY4`NM?_%#6P7Az>HhQOxy%*R-MRC5~#ojyoE5atZ@K{8&V-WR3f+XsjO+Gv}P<^8* zagE+)lhSs#to>N#tnQZev|W~E?Z@2Wmdjfh|J^+B-}T~0z&p3=asW$8O&$7HK9~Lh z#C$D>end$Z|2XhlI{5xGF8*=g7b1MFJuO+O+H*2g&f2qoL$!~A&%A zPbZb*u(?~KTM&9ftFVy|a2`=1uLZ1C%FjQp6B|g%o)-M!bjMohY2c#{j>FzgAE;AAQ~y^<`&jL#n|634>E*eY}&0^2Nm|7UZjJH@1I@ly}&M_uu)Ia zL501y7wI5Nk=J&ru#`E;m9@+xP1iqx4#JR-4l0?_X!^HBGOeLxY7AtVz6|&&{|vwv z+VF?9f++1(`KFl0ZKm)K8^d^AhN(P}^Qe2wtaxYYU$(r%qUK~ zwNB@+-nZccS=u@A6GZ!5@e}MsT!l}zk?D(cUja7(Bb{4;>BFuo`>pWdW7zl$v>z$$ zhU=cr3*&^3p2jJZZ|0;isFw@aUtK#>q~=2M3*Wa~A6m@cm`D2YqZy?Ff&K z!1om<51u@FmU4d{9dqL21j}6U6SVI<_|(UkKBQ@KA7lFdkq&+E^I|9y^-}c7ZYlf) z+Htm9iXTb^AbudtA-@r}T;VU{*IjH#GxV2|j<2w}$fbVRe2%4qTg^DCX0KR7r*^Vm zSrUr`TUnaQ(>Uxa6M7tU3c+^mw9-MRsV%kb%#E7=ku(*4y7@3mQ{mt5;xEt^vK>`k z5?Nis2lr8Ab^FTC%(wiMsMH$J80%HBPHzcWx6syv`r~~axx$;$VV%(9xE7V<3gd0! zR!20JXE6sEz8v_6NLO_Bi-0c~jyuJS56y=Ff8NHw#qf84Pw^Q$f@PhsQ$t&ryms^!;b@qLZMT#t2uz|nf{Z4>sshT@DD4C6epVfwgX%b9fmaD z#_YEUtsPdNtA5q~5^M>fJ?g@~XTF^IcIda@*{{CZbM>m|e>Ir(uIRgN!Gd*i-HjV& zQtF{YslSU~WtEL1hOha5LsVdQ5=XAI0vHLvCGn}!N%iUS`tB`TjH$uqqM-g}@DcsZ zRDJkri8CxiK?X?_tcPRskn=~S_Ag*<|oP(FBzbs&@C*y$or zkY~>@t*#2QC0#x`{P|SuTDPyG4t)onD?^)dP@?@dOvz|R*HRxo zoc89-6@Ze#qP|w*(&aXXJ_>#apWv$>ejQkLYtwCa3;Ky!E60VDO;6Dph|f_^nSAC%w>Q1FW>VUI0SqHUXy1qf$m922n{R?A~YuKxamJo@@OBJQYICZ7;zQ^j71 z{WG#@s*HmEa%>z?0YAmY@mv%P)?`~;mG%79&`dc6@E7!XzE6zuh|*yB63^)4W?h=O z(Oy=a=6T#p6la%N6_(8VmzK#Voa^U&*&F;UV-P@KE92tI6cq8b9qEFaQhfP z4+u%)Z+h}5*G8+}_iLG*zsk3;MEVxmX>hlZNN6i|kKHL_l4@MvGq42+V@wWrNE#L|tF&`WMLxrzm zP@Jl*BGjYBw_JaYMfo-ZUqzNjiy7>Vt3>v$fLGHT-JqC{N@OS9K&%mC@WdyG8_YMb z=1~jKS~m7e>>Xn4G7DI7fOoLr{n-ngfK4r4pMJibX4wnrmtLQKz9!h?xC^9nf2mAdXtBOHmLcE~H$3pZHXN2FqJiJ> zC@ifnW`k5qC6jI_g=GStd6#fy!ag&}gnVfg!yu`6Q>7EcK-OqTg-3Kd^%-&9&pmC5 zwKKav?ly;WYIq~`fXlvL09XBSw^@w6Jf#B(F8*HVz*6w3bfBP%zZW{t2XNR`%s8t8 zPV+oV>^2|Z*j8nlaWZ|H6%t%$`rse<9bEjq=6hl-=&0E;(O39;&8b8`gi%;rX8OuL z?ACuG`to%wx^Xa1@6iU0h}zp_8AT>QPh{G3v+=2OJK!ru$OmgLhF_5IW$`8Txb)Fk=S zYQ3Jy^t&mzG0>IIrxwXy;qNuyRPqP@$9|%(@b?<$n7*<>M4xRI_#DFRTa_&wwJ+N& zz)hyF@QJ>{-wT^U?aMZc=_~xb=5(e{`WC_VxjOSX5mz733M1-|_ zHjU*Eo2KR!3jdUg|1t2pxcJoj6+X2WdH#&=&vfv?|0kaDh4@$PJ6E*u@N?Y3x3+_R zdzU`hOhx}gXPyIn*y;ZfaCk0kZ5DC#EB9zE)yf-LUX8CS!~gf|YSm;Fo&`V8Wo~k; zBZsh-V}8fE865|pG~|R#hwg=NG^u8ehTt4~@=rsZURi>|2 zX{tgSaV!w{!)hSjlQnscH|WH$7S0v@xgMnD$*?Qai3~Bo= zmjn5Nhe-n4clmuiw>}5CK9z002XTEk^3CwpHoOYMsXhm}J__HdPhZ3!C{k|k2}C_V zQuI+DFTPWszBt9wi+|okY@X}y_u?ZVY#?tM=^M=Q;sZXEw>oT)6TJB1v@Nf`a<~^C zaGO5buch@V_~g;UO2=rBf^vB@uwnq`(IDa@j0P07qW1RU@5lpxkAn|>ys_NV=6kkv z8Sb?%;79eR4RTR7{Bth;>}>ewTzptB-h4*K)2<&&7s+vfa=o&a86q5Bu(8X)TbOY~MoRAI9mZ6cGpappUgA zMIY;0VyM=NevEfV9Xvg?uFBl)~VvnYjE#=$p-~&F% z#s8GsSNW=Q#bBp>;oFk0q+*cI(#{*PTpDKw zvgZe{iEZ&%&n7^sO?Mj(ZcuthtQC1SC4##{piZ&lRPQE3z-P0x)E3DPL5JyEADY`} zZH{A5JFRjK{fIOZtKmc+KFnAv!^Qsy_(?8)F&BTQgHQap{FAi6kGCGg7q<%4gWiKL zey{3-K5Z`d{|&mxy<_-LciUk%|I>i?P;hutmfR|=? z9eFGp{&{nYjepE25#9!gb}DG&56^~w+QlEP-|*m{w(;l7wb|BVj`&ay@Ht1W&9)wM z#Qc`w^X2Sp_~&rPkHVjw4gVah0m0|X;o0y{yZFPi;h#1!!y$aM-$sjO7(COm(lFDa zbzv19BJ063t%!Z*be?%IeuVDg?*#rV2j73i#s3)iq~B%GUO}q{;1o-wZJ+P*3P?Fb z0(SV$1O6!BSkWkxLD=X^LG~0C_F?lU?tA4hwo_)W!nV*_?HJCUH}`FL-lPrsI_}%- zfwLCD17|q?2l`Dg29T#7O$VF;e%SXL)2Rxdf;{$U^4J;Pnc{&z)$nssTE21I{@6UDZ2213{cf9T% z@?19j^JX{3UjX=79Hki!*yx_=hx$~_TT){D3&a+~nxS|#Io;!U<_YAvi6 z@KtT+X`2{Mv7Dg)alqAFe54VnO-H>{CVD(pi&$gLM&eyZoq?4ZaE9*^CQF5L3D{4^6vK z$}*;G|K_MGYb<0x6zyxTba>#cu}v&Dcr3D5?u7fz=2P#4T4tQ~M=scw8IN1hfFCM~ z{;<(^47~GR60?zfkNOEoAsf84+(z)=cEC>pKL+!-WnACbAgmkS+`QvW^B~6xFV(&rsdkUq;LR5=H{wfu}~7(@hJ zFUZob;Yxo5OC@l}o94k4{08 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 046/142] 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 2eecfaa08..293a16ccd 100644 --- a/webui.py +++ b/webui.py @@ -36,7 +36,7 @@ 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 +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 047/142] 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 cfc0cffa2..136e8855c 100644 --- a/launch.py +++ b/launch.py @@ -22,6 +22,9 @@ stored_commit_hash = None 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 048/142] launch.py: Simplify run() --- launch.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/launch.py b/launch.py index 136e8855c..ae75f6fda 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: + 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)) - 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") + 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 049/142] 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 ae75f6fda..fed14d93a 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 050/142] 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 fed14d93a..670af87c1 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 051/142] 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 9a0b8d22b..58bc4770c 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 052/142] 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 58bc4770c..0708398b9 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 053/142] 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 fc39161ea..f387b5ae2 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -381,7 +381,7 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), { "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 054/142] 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 27af5ff6c..9607077a7 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 @@ class Script(scripts.Script): 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 055/142] Reindent autocrop with 4 spaces --- modules/textual_inversion/autocrop.py | 178 +++++++++++++------------- 1 file changed, 90 insertions(+), 88 deletions(-) diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index 7770d22f5..8e667a4d3 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -10,63 +10,64 @@ RED = "#F00" def crop_image(im, settings): - """ Intelligently crop an image to the subject matter """ + """ 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 + 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) + im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) + im_debug = im.copy() - # 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) + focus = focal_point(im_debug, settings) - x1 = focus.x - x_half - if x1 < 0: - x1 = 0 - elif x1 + settings.crop_width > im.width: - x1 = im.width - settings.crop_width + # 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) - y1 = focus.y - y_half - if y1 < 0: - y1 = 0 - elif y1 + settings.crop_height > im.height: - y1 = im.height - settings.crop_height + x1 = focus.x - x_half + if x1 < 0: + x1 = 0 + elif x1 + settings.crop_width > im.width: + x1 = im.width - settings.crop_width - x2 = x1 + settings.crop_width - y2 = y1 + settings.crop_height + y1 = focus.y - y_half + if y1 < 0: + y1 = 0 + elif y1 + settings.crop_height > im.height: + y1 = im.height - settings.crop_height - crop = [x1, y1, x2, y2] + x2 = x1 + settings.crop_width + y2 = y1 + settings.crop_height - results = [] + crop = [x1, y1, x2, y2] - results.append(im.crop(tuple(crop))) + results = [] - 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() + results.append(im.crop(tuple(crop))) - return results + 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 056/142] 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 0bfc28a0d..d9e46b5e4 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_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) + def test_options_write(self): + response = requests.get(self.url_options) + self.assertEqual(response.status_code, 200) - pre_value = response.json()["send_seed"] + pre_value = response.json()["send_seed"] - self.assertEqual(requests.post(self.url_options, json={"send_seed":not pre_value}).status_code, 200) + self.assertEqual(requests.post(self.url_options, json={"send_seed": not pre_value}).status_code, 200) - response = requests.get(self.url_options) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["send_seed"], not pre_value) + response = requests.get(self.url_options) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["send_seed"], not pre_value) - requests.post(self.url_options, json={"send_seed": pre_value}) + requests.post(self.url_options, json={"send_seed": pre_value}) - def test_cmd_flags(self): - self.assertEqual(requests.get(self.url_cmd_flags).status_code, 200) + def test_cmd_flags(self): + self.assertEqual(requests.get(self.url_cmd_flags).status_code, 200) - def test_samplers(self): - self.assertEqual(requests.get(self.url_samplers).status_code, 200) + def test_samplers(self): + self.assertEqual(requests.get(self.url_samplers).status_code, 200) - def test_upscalers(self): - self.assertEqual(requests.get(self.url_upscalers).status_code, 200) + def test_upscalers(self): + self.assertEqual(requests.get(self.url_upscalers).status_code, 200) - def test_sd_models(self): - self.assertEqual(requests.get(self.url_sd_models).status_code, 200) + def test_sd_models(self): + self.assertEqual(requests.get(self.url_sd_models).status_code, 200) - def test_hypernetworks(self): - self.assertEqual(requests.get(self.url_hypernetworks).status_code, 200) + def test_hypernetworks(self): + self.assertEqual(requests.get(self.url_hypernetworks).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_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 057/142] 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 f387b5ae2..210424ac8 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 2fd82e8ea..e35d0bfee 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -90,7 +90,7 @@ class ExtraNetworksPage: 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 058/142] 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 2173de792..7f450086f 100644 --- a/extensions-builtin/LDSR/ldsr_model_arch.py +++ b/extensions-builtin/LDSR/ldsr_model_arch.py @@ -130,11 +130,11 @@ class LDSR: 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 57c02d126..631a08ef0 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -460,7 +460,7 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 @@ class LatentDiffusionV1(DDPMV1): 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 8028918af..b51a88062 100644 --- a/extensions-builtin/ScuNET/scunet_model_arch.py +++ b/extensions-builtin/ScuNET/scunet_model_arch.py @@ -265,4 +265,4 @@ class SCUNet(nn.Module): 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 55dd94ab8..0ba504874 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 73e37cfad..93b932747 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch.py +++ b/extensions-builtin/SwinIR/swinir_model_arch.py @@ -805,7 +805,7 @@ class SwinIR(nn.Module): 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 3ca9be782..dad22cca2 100644 --- a/extensions-builtin/SwinIR/swinir_model_arch_v2.py +++ b/extensions-builtin/SwinIR/swinir_model_arch_v2.py @@ -241,7 +241,7 @@ class SwinTransformerBlock(nn.Module): 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 @@ class SwinTransformerBlock(nn.Module): 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 @@ class SwinTransformerBlock(nn.Module): 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 @@ class PatchMerging(nn.Module): 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 @@ class BasicLayer(nn.Module): 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 @@ class PatchEmbed(nn.Module): 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 @@ class RSTB(nn.Module): 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 @@ class Upsample(nn.Sequential): 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 @@ class Upsample_hf(nn.Sequential): 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 @@ class UpsampleOneStep(nn.Sequential): 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): ) 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): ) 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): 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 @@ class Swin2SR(nn.Module): 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 @@ if __name__ == '__main__': 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 670af87c1..62b33f149 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 594fa655d..165985c35 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -227,7 +227,7 @@ class Api: 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 @@ class Api: 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 4d2910765..006ccdb75 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 e01ca6551..f4a4ab36f 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -102,4 +102,4 @@ parser.add_argument("--no-gradio-queue", action='store_true', help="Disables gra 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 45c70f84f..12db68142 100644 --- a/modules/codeformer/codeformer_arch.py +++ b/modules/codeformer/codeformer_arch.py @@ -119,7 +119,7 @@ class TransformerSALayer(nn.Module): 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 @@ class Fuse_sft_block(nn.Module): @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 @@ class CodeFormer(VQAutoEncoder): 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 @@ class CodeFormer(VQAutoEncoder): 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 @@ class CodeFormer(VQAutoEncoder): 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 b24a0394c..09ee6660d 100644 --- a/modules/codeformer/vqgan_arch.py +++ b/modules/codeformer/vqgan_arch.py @@ -13,7 +13,7 @@ from basicsr.utils.registry import ARCH_REGISTRY 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 @@ class AttnBlock(nn.Module): # 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 @@ class Encoder(nn.Module): 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 @@ class Generator(nn.Module): 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 @@ class VQAutoEncoder(nn.Module): 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 @@ class VQGANDiscriminator(nn.Module): 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 4de9dd8da..2b9888baf 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 @@ class GaussianNoise(nn.Module): 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 eb4f0b420..bdf9b3b7a 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -199,7 +199,7 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ 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 38ef074f1..570b5603d 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 3b8b62d93..b2de3662f 100644 --- a/modules/images.py +++ b/modules/images.py @@ -367,7 +367,7 @@ class FilenameGenerator: 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 5c2f92a15..d74c6b955 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -42,7 +42,7 @@ if has_mps: # 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 @@ if has_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 + 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 a5c4d2da5..be9f84c76 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -4,7 +4,7 @@ from PIL import Image, ImageFilter, ImageOps def get_crop_region(mask, pad=0): """finds a rectangular region that contains all masked ares in an image. Returns (x1, y1, x2, y2) coordinates of the rectangle. For example, if a user has painted the top-right part of a 512x512 image", the result may be (256, 0, 512, 256)""" - + h, w = mask.shape crop_left = 0 diff --git a/modules/ngrok.py b/modules/ngrok.py index 7a7b4b26c..67a74e853 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 c3932d6ba..f902b9df9 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -164,7 +164,7 @@ class StableDiffusionProcessing: 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 171097320..7d9dd7361 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 e374aeb88..7e50f1abc 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 @@ class StableDiffusionModelHijack: 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 a174bbe10..f00fe55cb 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 d1e946a5b..3316d0211 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 enable_midas_autodownload(): 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 2f733cf5c..e9e41818c 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -93,10 +93,10 @@ class CFGDenoiser(torch.nn.Module): 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 @@ class KDiffusionSampler: 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 @@ class KDiffusionSampler: 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 @@ class KDiffusionSampler: 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 cc38debdc..497568eb5 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -179,7 +179,7 @@ def efficient_dot_product_attention( 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 41610e03a..b9621fc95 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -118,7 +118,7 @@ class PersonalizedBase(Dataset): 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 @@ class BatchLoaderRandom(BatchLoader): 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 d0cad09eb..a009d8e80 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 9e1b2b9a8..d489ed1e0 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 1efb656a4..ff82fff6c 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1171,7 +1171,7 @@ def create_ui(): process_focal_crop_entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_entropy_weight") process_focal_crop_edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_edges_weight") process_focal_crop_debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug") - + with gr.Column(visible=False) as process_multicrop_col: gr.Markdown('Each image is center-cropped with an automatically chosen width and height.') with gr.Row(): @@ -1183,7 +1183,7 @@ def create_ui(): 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 create_ui(): 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 create_ui(): 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 ed70abe58..af4977332 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 e056c3f6a..a407a3cad 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 @@ class BertSeriesModelWithTransformation(BertPreTrainedModel): 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 @@ class BertSeriesModelWithTransformation(BertPreTrainedModel): 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 c88907be8..d4a1bbf4e 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 bb00fb3f1..1e833fa89 100644 --- a/scripts/img2imgalt.py +++ b/scripts/img2imgalt.py @@ -149,9 +149,9 @@ class Script(scripts.Script): 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 @@ class Script(scripts.Script): 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 ad6609be9..2d5feaf9b 100644 --- a/scripts/loopback.py +++ b/scripts/loopback.py @@ -14,7 +14,7 @@ class Script(scripts.Script): 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 @@ class Script(scripts.Script): p.seed = processed.seed + 1 p.denoising_strength = calculate_denoising_strength(i + 1) - + if state.skipped: break @@ -121,7 +121,7 @@ class Script(scripts.Script): all_images.append(last_image) p.inpainting_fill = original_inpainting_fill - + if state.interrupted: break @@ -132,7 +132,7 @@ class Script(scripts.Script): 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 c0bbecc12..ea0632b68 100644 --- a/scripts/poor_mans_outpainting.py +++ b/scripts/poor_mans_outpainting.py @@ -19,7 +19,7 @@ class Script(scripts.Script): 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 fb06beab4..88324fe6a 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -96,7 +96,7 @@ class Script(scripts.Script): 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 9607077a7..2378816f9 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 @@ class Script(scripts.Script): 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 0b1d30968..e614c23b9 100644 --- a/scripts/sd_upscale.py +++ b/scripts/sd_upscale.py @@ -16,7 +16,7 @@ class Script(scripts.Script): 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 059/142] 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 289dd311d..c2e37834f 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 f387b5ae2..22b456184 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -420,7 +420,7 @@ options_templates.update(options_section(('infotext', "Infotext"), { 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 060/142] 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 df8c6861b..0a276b0b4 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 061/142] 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 62b33f149..516746acc 100644 --- a/launch.py +++ b/launch.py @@ -3,16 +3,12 @@ import subprocess 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 a23f6d706..005a9b0aa 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 062/142] 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 10ab81c9e..6354e73ba 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 063/142] 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 516746acc..ae67fe5c5 100644 --- a/launch.py +++ b/launch.py @@ -5,6 +5,7 @@ import sys 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 @@ args, _ = cmd_args.parser.parse_known_args() 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 @@ Use --skip-python-version-check to suppress this warning. """) +@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 064/142] 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 ae67fe5c5..578af2290 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 065/142] 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 293a16ccd..cdb967405 100644 --- a/webui.py +++ b/webui.py @@ -28,7 +28,16 @@ warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvisi 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 066/142] 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 1df1dd45d..b09b384e7 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 d274b8297e8588ce1ea08200935e46c100288de3 Mon Sep 17 00:00:00 2001 From: AUTOMATIC <16777216c@gmail.com> Date: Thu, 11 May 2023 14:49:14 +0300 Subject: [PATCH 067/142] 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 cf3fef3d0..1a0f7ae52 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 2378816f9..b918a764e 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 068/142] update changelog for release --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0f7ae52..d1727864d 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 069/142] 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 0123b877b..d0bdfc16e 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 070/142] 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 b09b384e7..96a20a6bd 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 4ac919b5e..8c7be275c 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 cdb967405..d3aafe6d0 100644 --- a/webui.py +++ b/webui.py @@ -28,15 +28,8 @@ warnings.filterwarnings(action="ignore", category=UserWarning, module="torchvisi 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 071/142] minor fix --- webui.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/webui.py b/webui.py index d3aafe6d0..293a16ccd 100644 --- a/webui.py +++ b/webui.py @@ -31,8 +31,6 @@ startup_timer.record("import torch") 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 072/142] 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 46043e333..f4a4ab36f 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -102,5 +102,4 @@ parser.add_argument("--no-gradio-queue", action='store_true', help="Disables gra 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 8ba3a96b5..6828e8981 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): # 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 073/142] 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 1e791c5bd..e8f507743 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -40,7 +40,7 @@ class RestrictedUnpickler(pickle.Unpickler): 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 074/142] 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 fb56254fc..a0a98bbcb 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -282,33 +282,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model 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 @@ infotext_to_setting_name_mapping = [ ('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 6828e8981..32ff61e92 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -34,7 +34,7 @@ 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') +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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 4787193c0..4c9a0a1fb 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 4b3465855..0d96c14cb 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -459,47 +459,13 @@ options_templates.update(options_section((None, "Hidden options"), { })) 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_hr": OptionInfo( + 0, "Merging Ratio (high-res pass)", + gr.Slider, {"minimum": 0, "maximum": 0.9, "step": 0.1} ), "token_merging_ratio": OptionInfo( - 0.5, "Merging Ratio", + 0, "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.", - 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} ) })) 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 075/142] 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 0d96c14cb..e49e9b74c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -350,6 +350,8 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "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 @@ options_templates.update(options_section((None, "Hidden options"), { "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 076/142] 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 32ff61e92..94fe2625c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -34,7 +34,7 @@ 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') +# 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 077/142] 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 f6a6f2fbd..ee9c65e7d 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 ff82fff6c..ff25c4ce9 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 078/142] 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 d0bdfc16e..86e5ca67c 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 + for (const text in original_lines) { + if(dumped[text] !== undefined) continue; + dumped[text] = localization[text] || text; + } - dumped[text] = localization[text] || text - }) - - return dumped + return dumped; } function download_localization() { 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 079/142] 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 7d9dd7361..e83c6ecf5 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -53,6 +53,21 @@ class CFGDenoiserParams: 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 @@ class CFGDenoisedParams: 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 @@ callback_map = dict( 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 e9e41818c..55f0d3a3e 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -8,6 +8,7 @@ from modules.shared import opts, state 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 @@ class CFGDenoiser(torch.nn.Module): 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 @@ class CFGDenoiser(torch.nn.Module): 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 080/142] 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 e83c6ecf5..57dfd457f 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -64,7 +64,7 @@ class CFGDenoisedParams: """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 @@ class AfterCFGCallbackParams: """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 081/142] 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 57dfd457f..3c21a3628 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -78,9 +78,6 @@ class AfterCFGCallbackParams: 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 55f0d3a3e..61f23ad7f 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -183,8 +183,7 @@ class CFGDenoiser(torch.nn.Module): 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 082/142] 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 a0a98bbcb..f1a2204c3 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -311,7 +311,7 @@ infotext_to_setting_name_mapping = [ ('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 a5e8d0bd0..7ec9967e3 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -350,8 +350,8 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "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 2423bfd22..302b3dabd 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 0e03deedc..17ae9484c 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 083/142] 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 bc0742381..d3dc130c5 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -2,7 +2,7 @@ from collections import namedtuple 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 000000000..ccc979593 --- /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 4631965b4..6760a9005 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -425,7 +425,7 @@ options_templates.update(options_section(('ui', "Live previews"), { "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), + "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 727ebd31d..0a9284348 100644 --- a/webui.py +++ b/webui.py @@ -144,10 +144,21 @@ Use --skip-version-check commandline argument to disable this check. """.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 084/142] 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 d3dc130c5..b1e8a7806 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -26,8 +26,8 @@ approximation_indexes = {"Full": 0, "Tiny AE": 1, "Approx NN": 2, "Approx cheap" 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 085/142] 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 | 80 ++++++++++++++++++++------------- modules/shared.py | 33 +++++++++++--- style.css | 20 +++++++++ 3 files changed, 97 insertions(+), 36 deletions(-) diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js index 87a289d31..9251fd711 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'; +settingsHintsSetup = false - elem = gradioApp().querySelector('#'+elem_id) - elem.insertBefore(a, elem.querySelector('label')) +onOptionsChanged(function(){ + if(settingsHintsSetup) return + settingsHintsSetup = true - return a - } + 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] - 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" + if(! commentBefore && !commentAfter) return - 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' + 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 - data.forEach(function(obj){ - var tr = document.createElement('tr') - var td = document.createElement('td') - td.textContent = obj.name - tr.appendChild(td) + if(!span) return - var td = document.createElement('td') - td.textContent = obj.label - tr.appendChild(td) - - table.appendChild(tr) - }) - - popup(table); - }) - }); + 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 7ec9967e3..24fdcd593 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -199,8 +199,9 @@ interrogator = modules.interrogate.InterrogateModels("interrogate") 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 @@ class OptionInfo: 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 @@ options_templates = {} 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 @@ options_templates.update(options_section(('saving-to-dirs', "Saving to a directo "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 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "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 @@ options_templates.update(options_section(('ui', "User interface"), { "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 @@ class Options: 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 8c7be275c..1e9785928 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 086/142] 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 e35d0bfee..0baccf566 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 @@ class ExtraNetworksPage: 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 087/142] 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 1f5ea0d3d..cc2cbc6a1 100644 --- a/extensions-builtin/ScuNET/scripts/scunet_model.py +++ b/extensions-builtin/ScuNET/scripts/scunet_model.py @@ -10,7 +10,7 @@ from tqdm import tqdm 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 @@ class UpscalerScuNET(modules.upscaler.Upscaler): 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 9251fd711..6d1933dc8 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 24fdcd593..a05776443 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -228,6 +228,12 @@ class OptionInfo: 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 @@ options_templates.update(options_section(('saving-images', "Saving images/grids" "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 @@ options_templates.update(options_section(('saving-to-dirs', "Saving to a directo })) 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 @@ 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_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 @@ options_templates.update(options_section(('compatibility', "Compatibility"), { })) 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 @@ options_templates.update(options_section(('ui', "User interface"), { "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 @@ options_templates.update(options_section(('ui', "Live previews"), { "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 1e9785928..0c2f453c4 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 088/142] 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 c2e37834f..269863c94 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 a05776443..07f18b1b7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -445,7 +445,7 @@ options_templates.update(options_section(('infotext', "Infotext"), { 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 089/142] 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 7e50f1abc..14e7f7993 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -216,6 +216,9 @@ class StableDiffusionModelHijack: 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 4c9a0a1fb..dddbc6e1c 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -414,6 +414,9 @@ class SdModelData: 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 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 090/142] 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 7d435297a..a24e62d42 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 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 091/142] 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 a24e62d42..e0ca454d3 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 092/142] 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 a725d74a3..db768fd2a 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 093/142] 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 578af2290..f83809116 100644 --- a/launch.py +++ b/launch.py @@ -58,7 +58,7 @@ Use --skip-python-version-check to suppress this warning. @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 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 094/142] 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 ccc979593..927a72987 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 0a9284348..0d0816bcc 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 095/142] 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 8c3dea564..9e6e05313 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -334,9 +334,19 @@ def setup_ui(ui, gallery): 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 096/142] 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 7d435297a..863aae741 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 097/142] 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 5672267d5..da820b394 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 @@ axis_options = [ 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 098/142] 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 0baccf566..752cf2b83 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 099/142] 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 75da862ab..db65bcdbf 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 bc2c0450f..1053253ed 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 @@ class Extension: 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 @@ class Extension: 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 af4977332..aaa7e571a 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 100/142] 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 1053253ed..f16f059ec 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -66,13 +66,11 @@ class Extension: 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 aaa7e571a..6ad9a97c5 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 101/142] 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 f16f059ec..359a7aa51 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -3,7 +3,6 @@ import sys 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 6ad9a97c5..d7a0f6856 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 102/142] 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 94fe2625c..15806f78d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -13,7 +13,7 @@ from skimage import exposure 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 4f1bf21d8..f22aad8f2 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -14,12 +14,18 @@ samplers_for_img2img = [] 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 bc0742381..92880cafd 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -2,7 +2,7 @@ from collections import namedtuple 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 b1ee3be75..bdae8b404 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -11,7 +11,7 @@ import modules.models.diffusion.uni_pc 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 @@ class VanillaStableDiffusionSampler: 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 61f23ad7f..5455561a0 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -11,21 +11,21 @@ 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'], {}), + ('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 103/142] 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 15806f78d..678c4468f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -681,12 +681,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: 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 5455561a0..552c6c64f 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -14,20 +14,20 @@ samplers_k_diffusion = [ ('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 104/142] 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 ff25c4ce9..8e51e7823 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 105/142] 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 07f18b1b7..3abf71c00 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -458,8 +458,8 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" "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 106/142] Minor change --- webui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.py b/webui.py index 0d0816bcc..0aa03ea8f 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 0d31f20cbd556ea4ba3d8ad9254bcce71c32088c Mon Sep 17 00:00:00 2001 From: bobzilladev Date: Tue, 16 May 2023 13:15:30 -0400 Subject: [PATCH 107/142] 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 cfc0cffa2..871d3ea8e 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 d906a5717..bf18b7b7e 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("--precision", type=str, help="evaluate at this precision", 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 7a7b4b26c..caa352d1a 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 f07bcc41e..5f5405f06 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -59,7 +59,7 @@ if cmd_opts.ngrok is not None: 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 108/142] 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 0c2f453c4..f8ffbd8d3 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 109/142] 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 c85bc79aa..4d9a522cd 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 110/142] 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 36f643e10..e37fcb8fe 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 111/142] add credits --- README.md | 1 + html/licenses.html | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.md b/README.md index 67a1a83a3..c1e193c03 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 bc995aa07..ef6f2c0a4 100644 --- a/html/licenses.html +++ b/html/licenses.html @@ -661,4 +661,30 @@ 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. + + +

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 112/142] 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 b1e8a7806..20a9af206 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 927a72987..d23812ef1 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -61,16 +61,28 @@ class TAESD(nn.Module): 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 6760a9005..96036d383 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -425,7 +425,7 @@ options_templates.update(options_section(('ui', "Live previews"), { "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "show_progress_every_n_steps": OptionInfo(10, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), - "show_progress_type": OptionInfo("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 0aa03ea8f..727ebd31d 100644 --- a/webui.py +++ b/webui.py @@ -144,21 +144,10 @@ Use --skip-version-check commandline argument to disable this check. """.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 113/142] 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 3abf71c00..648a2a191 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -2,6 +2,7 @@ import datetime 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 8e51e7823..bed8464e6 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1609,12 +1609,8 @@ def create_ui(): 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 d7a0f6856..4ba3bdd7c 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 293a16ccd..39dec3cae 100644 --- a/webui.py +++ b/webui.py @@ -234,7 +234,10 @@ def initialize(): 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 webui(): 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 webui(): 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 114/142] 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 f4a4ab36f..6144db5cc 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -103,3 +103,4 @@ parser.add_argument("--skip-version-check", action='store_true', help="Do not ch 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 39dec3cae..5172f049c 100644 --- a/webui.py +++ b/webui.py @@ -8,7 +8,7 @@ import warnings 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 webui(): 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 webui(): 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 115/142] 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 678c4468f..cd63b9a60 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -316,6 +316,7 @@ class Processed: 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 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 116/142] 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 ceda6a357..d99c933da 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 d23812ef1..5e8496e87 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 117/142] 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 d99c933da..763829f1c 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -26,22 +26,19 @@ approximation_indexes = {"Full": 0, "Approx NN": 1, "Approx cheap": 2, "TAESD": 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 118/142] 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 000000000..1cfd94876 --- /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 000000000..48f9df7d4 --- /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 7328401f5..46654d835 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 000000000..c0ba40678 --- /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 119/142] 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 d42965b15..7b7219fd6 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 120/142] 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 | 70 +-- 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 | 176 ++++---- javascript/textualInversion.js | 34 +- javascript/ui.js | 413 ++++++++--------- javascript/ui_settings_hints.js | 124 +++--- script.js | 74 +-- 21 files changed, 1549 insertions(+), 1507 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 5c7a836a2..ed9baf9d0 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 5160081d2..059338d69 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 b2bdf0532..f7a15cae1 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 fe0089248..e316a3650 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 d2c2f1905..fdf00b4d2 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 2a2d2f8e7..efeaf3a5b 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 4d9a522cd..0c80fa74c 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 ef64ee2e5..f9e84e706 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 3746df99f..477b7d809 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", + "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)", + "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 48196be40..0d04ab3b4 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 a612705d2..91a6377b5 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 64aee93b7..057e2d392 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 32066ab8f..ecd123793 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 6297a12bc..31d226dee 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 86e5ca67c..3d043a9ad 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 83fce1f86..a68a76f25 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 8d2c34924..cd273e480 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 +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' + 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) + divProgress.appendChild(divInner); + parentProgressbar.insertBefore(divProgress, progressbarContainer); - if(parentGallery){ - var livePreview = document.createElement('div') - livePreview.className='livePreview' - parentGallery.insertBefore(livePreview, gallery) + if (parentGallery) { + var livePreview = document.createElement('div'); + livePreview.className = 'livePreview'; + parentGallery.insertBefore(livePreview, gallery); } - var removeProgressBar = function(){ - setTitle("") - parentProgressbar.removeChild(divProgress) - 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 0354b860c..37e3d0756 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 ed9673d64..f4727ca3a 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') + 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") + 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 6d1933dc8..0db41b116 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 03afe8445..f6a3883af 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 121/142] 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 293a16ccd..2cd551bdb 100644 --- a/webui.py +++ b/webui.py @@ -144,16 +144,11 @@ Use --skip-version-check commandline argument to disable this check. """.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 webui(): 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 122/142] Fixed: #10460 --- modules/extras.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/extras.py b/modules/extras.py index bdf9b3b7a..aabb3b262 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -242,9 +242,10 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ 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 123/142] 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 aabb3b262..830b53aa2 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -245,7 +245,8 @@ def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ 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 run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ } 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 run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_ 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 124/142] 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 3746df99f..b00b504a4 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; + } +} - gradioApp().querySelectorAll('select').forEach(function(select){ - if (select.onchange != null) return; +function updateTooltipForSelect(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 125/142] 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 8c3dea564..4f754f433 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -172,7 +172,7 @@ class ExtraNetworksPage: # 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 @@ class ExtraNetworksPage: "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 126/142] 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 | 34 +++++++++++++++------------- modules/shared.py | 8 +++++-- 4 files changed, 55 insertions(+), 45 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index cd63b9a60..2b8dd361d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -29,12 +29,6 @@ from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion 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 @@ class StableDiffusionProcessing: 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 @@ class StableDiffusionProcessing: self.all_subseeds = None self.iteration = 0 self.is_hr_pass = False + self.sampler = None @property @@ -280,6 +277,12 @@ class StableDiffusionProcessing: 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 @@ class Processed: 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 @@ class Processed: 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 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): 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 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): 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 269863c94..f405f07fe 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 e612be10d..4bd8783e2 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 + current_token_merging_ratio = getattr(sd_model, 'applied_token_merged_ratio', 0) - 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 - ) + 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 47bc6d0ea..76af8b9cd 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -413,8 +413,13 @@ options_templates.update(options_section(('sd', "Stable Diffusion"), { "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 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" "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 127/142] 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 1d5462172..6853b14f9 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 128/142] 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 b2de3662f..4e8cd9934 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)) + save_image_with_geninfo(image_to_save, info, temp_file_path, extension, params.pnginfo) - 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) - - # 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 471df23b5..c6e45fb1c 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -4,7 +4,7 @@ from pathlib import Path 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 setup_ui(ui, gallery): 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 129/142] 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 76af8b9cd..23563582e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -378,6 +378,7 @@ options_templates.update(options_section(('system', "System"), { "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 @@ options_templates.update(options_section(('interrogate', "Interrogate Options"), })) 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 c6e45fb1c..8669cc1a9 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -105,6 +105,9 @@ class ExtraNetworksPage: 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 @@ class ExtraNetworksPage: 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 @@ class ExtraNetworksPage: 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 130/142] 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 165985c35..eee99bbb2 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -204,6 +204,7 @@ class Api: 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 @@ class Api: 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 006ccdb75..1ff2fb338 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 0c12ebd54..e33d8c819 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -17,6 +17,9 @@ class PostprocessImageArgs: 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 @@ class ScriptRunner: 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 @@ class ScriptRunner: 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 131/142] 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 c22769cf7..34e1b5e15 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -43,7 +43,7 @@ class StyleDatabase: 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 132/142] 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 b00b504a4..7b6f37ad9 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))}