From 9bf0b1ae1f8137fec76ffec84ef6b4e132d1b262 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 07:46:47 -0400 Subject: [PATCH 01/11] allow experimental to override precision --- extensions-builtin/multidiffusion-upscaler-for-automatic1111 | 2 +- modules/devices.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 index 51cb83ce2..70b3c5ea3 160000 --- a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 +++ b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 @@ -1 +1 @@ -Subproject commit 51cb83ce2a53bf147a9091ed5269dcce8f1662d7 +Subproject commit 70b3c5ea3c9f684d04e7ff59167565974415735c diff --git a/modules/devices.py b/modules/devices.py index 843ed4dd8..b49745bd3 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -81,6 +81,8 @@ def torch_gc(force=False): def test_fp16(): + if shared.cmd_opts.experimental: + return True try: x = torch.tensor([[1.5,.0,.0,.0]]).to(device).half() layerNorm = torch.nn.LayerNorm(4, eps=0.00001, elementwise_affine=True, dtype=torch.float16, device=device) @@ -114,7 +116,7 @@ def set_cuda_params(): pass global dtype, dtype_vae, dtype_unet, unet_needs_upcast # pylint: disable=global-statement ok = test_fp16() - if shared.cmd_opts.use_directml: # TODO DirectML does not have full autocast capabilities + if shared.cmd_opts.use_directml and not shared.cmd_opts.experimental: # TODO DirectML does not have full autocast capabilities shared.opts.no_half = True shared.opts.no_half_vae = True if ok and shared.opts.cuda_dtype == 'FP32': From 7254925dcab94cfaf5db4b57ee1cc7be6e524c8d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 11:46:48 -0400 Subject: [PATCH 02/11] add settings search --- extensions-builtin/sd-webui-controlnet | 2 +- javascript/ui.js | 13 +++++++++++++ modules/shared.py | 2 +- modules/ui.py | 3 ++- modules/ui_extensions.py | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index cdba83b6e..2e0dc37d2 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit cdba83b6e1a59f3b59bbcf5f0a6e0a585d666a01 +Subproject commit 2e0dc37d222aaba355a71dac0eda4bb7ca54f05f diff --git a/javascript/ui.js b/javascript/ui.js index 9313c75c6..14eff9927 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -260,6 +260,19 @@ onUiUpdate(() => { }); }; } + const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; + settings_search.oninput = (e) => { + gradioApp().querySelectorAll('#settings > div').forEach((elem) => { + elem.style.display = 'block'; + }); + gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { + section.querySelectorAll('.block').forEach((setting) => { + const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); + const el = setting.parentElement.classList.contains('form') ? setting.parentElement : setting; // if parent is form use that instead + el.style.display = visible ? 'block' : 'none'; + }); + }); + }; }); onOptionsChanged(() => { diff --git a/modules/shared.py b/modules/shared.py index 40f7e6a8d..7050ddde7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -444,7 +444,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"), "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), - "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound",component_args=hide_dirs), + "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), "show_progress_every_n_steps": OptionInfo(1, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approx NN", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), diff --git a/modules/ui.py b/modules/ui.py index 33e4dcffd..1b2c6c836 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1323,9 +1323,10 @@ def create_ui(): unload_sd_model = gr.Button(value='Unload checkpoint', variant='primary', elem_id="sett_unload_sd_model") reload_sd_model = gr.Button(value='Reload checkpoint', variant='primary', elem_id="sett_reload_sd_model") # reload_script_bodies = gr.Button(value='Reload scripts', variant='primary', elem_id="settings_reload_script_bodies") + with gr.Row(): + _settings_search = gr.Text(label="Search", elem_id="settings_search") # TODO settings search result = gr.HTML(elem_id="settings_result") - quicksettings_names = opts.quicksettings_list quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'} quicksettings_list = [] diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 4c566e332..9be1c3400 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -18,7 +18,7 @@ sort_ordering = { "user extensions": (True, lambda x: x.get('sort_user', '')), "update avilable": (True, lambda x: x.get('sort_update', '')), "updated date": (True, lambda x: x.get('updated', '2000-01-01T00:00')), - "created date": (False, lambda x: x.get('created', '2000-01-01T00:00')), + "created date": (True, lambda x: x.get('created', '2000-01-01T00:00')), "name": (False, lambda x: x.get('name', '').lower()), "enabled": (False, lambda x: x.get('sort_enabled', '').lower()), "size": (True, lambda x: x.get('size', 0)), From 09141ee1a8f51df7ef0a4b0c2ffbd053a3628b74 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sun, 28 May 2023 20:00:03 +0300 Subject: [PATCH 03/11] Fix int64 with UniPC && Add OneAPI version logging --- installer.py | 3 ++- modules/sd_samplers_compvis.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/installer.py b/installer.py index 25a6e18c2..8a36342d3 100644 --- a/installer.py +++ b/installer.py @@ -289,7 +289,8 @@ def check_torch(): log.info(f'Torch {torch.__version__}') if args.use_ipex and allow_ipex: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import - log.info(f'Torch backend: Intel OneAPI {torch.__version__}') + log.info(f'Torch backend: Intel IPEX {ipex.__version__}') + log.info(f'{os.popen("icpx --version").read().rstrip()}') log.info(f'Torch detected GPU: {torch.xpu.get_device_name("xpu")} VRAM {round(torch.xpu.get_device_properties("xpu").total_memory / 1024 / 1024)}') elif torch.cuda.is_available() and (allow_cuda or allow_rocm): if torch.version.cuda and allow_cuda: diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 98b0a3614..0fb411111 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -97,7 +97,10 @@ class VanillaStableDiffusionSampler: unconditional_conditioning = unconditional_conditioning[:, :cond.shape[1]] if self.mask is not None: - img_orig = self.sampler.model.q_sample(self.init_latent, ts) + if shared.cmd_opts.use_ipex: + img_orig = self.sampler.model.q_sample(self.init_latent, ts.type(torch.int64)) + else: + img_orig = self.sampler.model.q_sample(self.init_latent, ts) x = img_orig * self.mask + self.nmask * x # Wrap the image conditioning back up since the DDIM code can accept the dict directly. From 2ee38ccd0eb98f42177679105c8e9b96ea7ff1c1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 16:07:10 -0400 Subject: [PATCH 04/11] update --- CHANGELOG.md | 5 +++++ modules/hf_hub.py => cli/hfsearch.py | 0 extensions-builtin/sd-extension-system-info | 2 +- installer.py | 5 +++-- javascript/ui.js | 21 ++++++++++++--------- 5 files changed, 21 insertions(+), 12 deletions(-) rename modules/hf_hub.py => cli/hfsearch.py (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a6975c8..c7c38970d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log for SD.Next +## Update for 05/28/2023 + +- settings search option +- system info live gpu memory and load graphs + ## Update for 05/26/2023 Some quality-of-life improvements... diff --git a/modules/hf_hub.py b/cli/hfsearch.py similarity index 100% rename from modules/hf_hub.py rename to cli/hfsearch.py diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 4915b9857..46386f93d 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 4915b98576426f3fa77eec7b51965260736a5060 +Subproject commit 46386f93de0a9614ef94cf75acb0909e59859274 diff --git a/installer.py b/installer.py index 8a36342d3..2e13c515f 100644 --- a/installer.py +++ b/installer.py @@ -258,7 +258,7 @@ def check_torch(): elif allow_rocm and (shutil.which('rocminfo') is not None or os.path.exists('/opt/rocm/bin/rocminfo') or os.path.exists('/dev/kfd')): log.info('AMD ROCm toolkit detected') os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '10.3.0') - os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.9,max_split_size_mb:512') + os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0 torchvision==0.15.1 --index-url https://download.pytorch.org/whl/rocm5.4.2') xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') elif allow_ipex and args.use_ipex and shutil.which('sycl-ls') is not None: @@ -293,6 +293,7 @@ def check_torch(): log.info(f'{os.popen("icpx --version").read().rstrip()}') log.info(f'Torch detected GPU: {torch.xpu.get_device_name("xpu")} VRAM {round(torch.xpu.get_device_properties("xpu").total_memory / 1024 / 1024)}') elif torch.cuda.is_available() and (allow_cuda or allow_rocm): + log.debug(f'Torch allocator: {torch.cuda.get_allocator_backend()}') if torch.version.cuda and allow_cuda: log.info(f'Torch backend: nVidia CUDA {torch.version.cuda} cuDNN {torch.backends.cudnn.version() if torch.backends.cudnn.is_available() else "N/A"}') elif torch.version.hip and allow_rocm: @@ -522,7 +523,7 @@ def set_environment(): os.environ.setdefault('ACCELERATE', 'True') os.environ.setdefault('FORCE_CUDA', '1') os.environ.setdefault('ATTN_PRECISION', 'fp16') - os.environ.setdefault('PYTORCH_CUDA_ALLOC_CONF', 'garbage_collection_threshold:0.9,max_split_size_mb:512') + os.environ.setdefault('PYTORCH_CUDA_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') os.environ.setdefault('CUDA_LAUNCH_BLOCKING', '0') os.environ.setdefault('CUDA_CACHE_DISABLE', '0') os.environ.setdefault('CUDA_AUTO_BOOST', '1') diff --git a/javascript/ui.js b/javascript/ui.js index 14eff9927..7a2bd837d 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -262,16 +262,19 @@ onUiUpdate(() => { } const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; settings_search.oninput = (e) => { - gradioApp().querySelectorAll('#settings > div').forEach((elem) => { - elem.style.display = 'block'; - }); - gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { - section.querySelectorAll('.block').forEach((setting) => { - const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); - const el = setting.parentElement.classList.contains('form') ? setting.parentElement : setting; // if parent is form use that instead - el.style.display = visible ? 'block' : 'none'; + setTimeout(() => { + gradioApp().querySelectorAll('#settings > div').forEach((elem) => { + if (elem.id === 'settings_tab_licenses') return; + elem.style.display = 'block'; }); - }); + gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { + section.querySelectorAll('.block').forEach((setting) => { + const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); + if (setting.parentElement.classList.contains('form')) setting.parentElement.style.display = visible ? 'flex' : 'none'; + else setting.style.display = visible ? 'block' : 'none'; + }); + }); + }, 50); }; }); From 54257dd2268d1bebbc25346cca859c610c05b04e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 17:09:58 -0400 Subject: [PATCH 05/11] refactoring for pylint --- installer.py | 2 +- modules/deepbooru_model.py | 2 +- modules/esrgan_model.py | 6 +- modules/gfpgan_model.py | 14 +- modules/localization.py | 6 +- modules/lowvram.py | 4 +- modules/masking.py | 3 +- modules/processing.py | 4 +- modules/prompt_parser.py | 2 +- modules/script_loading.py | 6 +- modules/scripts_postprocessing.py | 12 +- modules/sd_disable_initialization.py | 25 +- modules/sd_hijack_checkpoint.py | 7 +- modules/sd_hijack_clip_old.py | 2 +- modules/sd_hijack_inpainting.py | 8 +- modules/sd_hijack_optimizations.py | 18 +- modules/sd_hijack_unet.py | 16 +- modules/sd_models.py | 5 +- modules/sd_samplers_compvis.py | 4 +- modules/sd_samplers_kdiffusion.py | 4 +- modules/sd_vae_approx.py | 2 +- modules/sub_quadratic_attention.py | 11 +- modules/ui.py | 2 +- modules/ui_extensions.py | 890 ++++++++++++------------- modules/ui_extra_networks_hypernets.py | 2 +- 25 files changed, 522 insertions(+), 535 deletions(-) diff --git a/installer.py b/installer.py index 2e13c515f..0b3a9de6b 100644 --- a/installer.py +++ b/installer.py @@ -705,7 +705,7 @@ def extensions_preload(force = False): from modules.paths_internal import extensions_builtin_dir, extensions_dir extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] for ext_dir in extension_folders: - preload_extensions(ext_dir, parser, args.debug) + preload_extensions(ext_dir, parser) except: log.error('Error running extension preloading') if args.profile: diff --git a/modules/deepbooru_model.py b/modules/deepbooru_model.py index c2c77cd25..33e24396d 100644 --- a/modules/deepbooru_model.py +++ b/modules/deepbooru_model.py @@ -671,7 +671,7 @@ class DeepDanbooruModel(nn.Module): t_771 = torch.sigmoid(t_770) return t_771 - def load_state_dict(self, state_dict, **kwargs): + def load_state_dict(self, state_dict, **kwargs): # pylint: disable=arguments-differ,unused-argument self.tags = state_dict.get('tags', []) super(DeepDanbooruModel, self).load_state_dict({k: v for k, v in state_dict.items() if k != 'tags'}) diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index f2565ca47..b685711ef 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -17,7 +17,7 @@ def mod2normal(state_dict): if 'conv_first.weight' in state_dict: crt_net = {} items = [] - for k, v in state_dict.items(): + for k, _v in state_dict.items(): items.append(k) crt_net['model.0.weight'] = state_dict['conv_first.weight'] @@ -53,7 +53,7 @@ def resrgan2normal(state_dict, nb=23): re8x = 0 crt_net = {} items = [] - for k, v in state_dict.items(): + for k, _v in state_dict.items(): items.append(k) crt_net['model.0.weight'] = state_dict['conv_first.weight'] @@ -186,7 +186,7 @@ class UpscalerESRGAN(Upscaler): elif "conv_first.weight" in state_dict: state_dict = mod2normal(state_dict) elif "model.0.weight" not in state_dict: - raise Exception("The file is not a recognized ESRGAN model.") + raise TypeError("The file is not a recognized ESRGAN model.") in_nc, out_nc, nf, nb, plus, mscale = infer_params(state_dict) diff --git a/modules/gfpgan_model.py b/modules/gfpgan_model.py index 9f332a730..728df70bf 100644 --- a/modules/gfpgan_model.py +++ b/modules/gfpgan_model.py @@ -13,9 +13,8 @@ loaded_gfpgan_model = None def gfpgann(): import facexlib - import gfpgan - global loaded_gfpgan_model - global model_path + import gfpgan # pylint: disable=unused-import + global loaded_gfpgan_model # pylint: disable=global-statement if loaded_gfpgan_model is not None: loaded_gfpgan_model.gfpgan.to(devices.device_gfpgan) return loaded_gfpgan_model @@ -54,7 +53,7 @@ def gfpgan_fix_faces(np_image): send_model_to(model, devices.device_gfpgan) np_image_bgr = np_image[:, :, ::-1] - cropped_faces, restored_faces, gfpgan_output_bgr = model.enhance(np_image_bgr, has_aligned=False, only_center_face=False, paste_back=True) + _cropped_faces, _restored_faces, gfpgan_output_bgr = model.enhance(np_image_bgr, has_aligned=False, only_center_face=False, paste_back=True) np_image = gfpgan_output_bgr[:, :, ::-1] model.face_helper.clean_all() @@ -69,7 +68,6 @@ gfpgan_constructor = None def setup_model(dirname): - global model_path if not os.path.exists(model_path): os.makedirs(model_path) @@ -77,9 +75,9 @@ def setup_model(dirname): import gfpgan import facexlib - global user_path - global have_gfpgan - global gfpgan_constructor + global user_path # pylint: disable=global-statement + global have_gfpgan # pylint: disable=global-statement + global gfpgan_constructor # pylint: disable=global-statement load_file_from_url_orig = gfpgan.utils.load_file_from_url facex_load_file_from_url_orig = facexlib.detection.load_file_from_url diff --git a/modules/localization.py b/modules/localization.py index 5b58f9e8c..d18d5137b 100644 --- a/modules/localization.py +++ b/modules/localization.py @@ -1,5 +1,4 @@ import json -import os import sys import modules.errors as errors @@ -7,9 +6,8 @@ import modules.errors as errors localizations = {} -def list_localizations(dirname): +def list_localizations(dirname): # pylint: disable=unused-argument localizations.clear() - return localizations """ for file in os.listdir(dirname): fn, ext = os.path.splitext(file) @@ -23,6 +21,8 @@ def list_localizations(dirname): fn, ext = os.path.splitext(file.filename) localizations[fn] = file.path """ + return localizations + def localization_js(current_localization_name): fn = localizations.get(current_localization_name, None) diff --git a/modules/lowvram.py b/modules/lowvram.py index e254cc131..cb684acd2 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -6,7 +6,7 @@ cpu = torch.device("cpu") def send_everything_to_cpu(): - global module_in_gpu + global module_in_gpu # pylint: disable=global-statement if module_in_gpu is not None: module_in_gpu.to(cpu) @@ -22,7 +22,7 @@ def setup_for_low_vram(sd_model, use_medvram): we add this as forward_pre_hook to a lot of modules and this way all but one of them will be in CPU """ - global module_in_gpu + global module_in_gpu # pylint: disable=global-statement module = parents.get(module, module) diff --git a/modules/masking.py b/modules/masking.py index a5c4d2da5..484650530 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 @@ -96,4 +96,3 @@ def fill(image, mask): image_mod.alpha_composite(blurred) return image_mod.convert("RGB") - diff --git a/modules/processing.py b/modules/processing.py index f46eb65fe..4c3cca1db 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -220,7 +220,7 @@ class StableDiffusionProcessing: source_image = devices.cond_cast_float(source_image) # HACK: Using introspection as the Depth2Image model doesn't appear to uniquely # identify itself with a field common to all models. The conditioning_key is also hybrid. - if opts.sd_backend == 'Diffusers': # TODO: img2img_image_conditioning + if opts.sd_backend == 'Diffusers': # TODO: Diffusers img2img_image_conditioning return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) if isinstance(self.sd_model, LatentDepth2ImageDiffusion): return self.depth2img_image_conditioning(source_image) @@ -649,7 +649,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: devices.torch_gc() if p.scripts is not None: p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n) - else: # TODO Diffusers + else: # TODO Diffusers main processing generator = [torch.Generator(device="cpu").manual_seed(s) for s in seeds] if shared.sd_model.scheduler.name != p.sampler_name: sampler = sd_samplers.all_samplers_map.get(p.sampler_name, None) diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index e2647a6f0..1474e69d3 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -13,7 +13,7 @@ from typing import List import lark import torch from compel import Compel -from modules.shared import log, opts +from modules.shared import opts # a prompt like this: "fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]" # will be represented with prompt_schedule like this (assuming steps=100): diff --git a/modules/script_loading.py b/modules/script_loading.py index 6827515fc..b28f6b65d 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -6,7 +6,7 @@ import modules.errors as errors preloaded = [] -def load_module(path, detailed=False): +def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) try: @@ -17,7 +17,7 @@ def load_module(path, detailed=False): -def preload_extensions(extensions_dir, parser, detailed=False): +def preload_extensions(extensions_dir, parser): if not os.path.isdir(extensions_dir): return for dirname in sorted(os.listdir(extensions_dir)): @@ -28,7 +28,7 @@ def preload_extensions(extensions_dir, parser, detailed=False): if not os.path.isfile(preload_script): continue try: - module = load_module(preload_script, detailed) + module = load_module(preload_script) if hasattr(module, 'preload'): module.preload(parser) except Exception as e: diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index 64563ab62..7baa4c738 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -31,23 +31,19 @@ class ScriptPostprocessing: The return value should be a dictionary that maps parameter names to components used in processing. Values of those components will be passed to process() function. """ - - pass + pass # pylint: disable=unnecessary-pass def process(self, pp: PostprocessedImage, **args): """ This function is called to postprocess the image. args contains a dictionary with all values returned by components from ui() """ - - pass + pass # pylint: disable=unnecessary-pass def image_changed(self): pass - - def wrap_call(func, filename, funcname, *args, default=None, **kwargs): try: res = func(*args, **kwargs) @@ -66,7 +62,7 @@ class ScriptPostprocessingRunner: def initialize_scripts(self, scripts_data): self.scripts = [] - for script_class, path, basedir, script_module in scripts_data: + for script_class, path, _basedir, _script_module in scripts_data: script: ScriptPostprocessing = script_class() script.filename = path @@ -124,7 +120,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): process_args[name] = value script.process(pp, **process_args) diff --git a/modules/sd_disable_initialization.py b/modules/sd_disable_initialization.py index c4a09d15d..c30525c30 100644 --- a/modules/sd_disable_initialization.py +++ b/modules/sd_disable_initialization.py @@ -35,10 +35,10 @@ class DisableInitialization: return original def __enter__(self): - def do_nothing(*args, **kwargs): + def do_nothing(*args, **kwargs): # pylint: disable=unused-argument pass - def create_model_and_transforms_without_pretrained(*args, pretrained=None, **kwargs): + def create_model_and_transforms_without_pretrained(*args, pretrained=None, **kwargs): # pylint: disable=unused-argument return self.create_model_and_transforms(*args, pretrained=None, **kwargs) def CLIPTextModel_from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs): @@ -61,16 +61,16 @@ 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): + def transformers_utils_hub_get_from_cache(url, *args, local_files_only=False, **kwargs): # pylint: disable=unused-argument return transformers_utils_hub_get_file_from_cache(self.transformers_utils_hub_get_from_cache, url, *args, **kwargs) - def transformers_tokenization_utils_base_cached_file(url, *args, local_files_only=False, **kwargs): + def transformers_tokenization_utils_base_cached_file(url, *args, local_files_only=False, **kwargs): # pylint: disable=unused-argument return transformers_utils_hub_get_file_from_cache(self.transformers_tokenization_utils_base_cached_file, url, *args, **kwargs) - def transformers_configuration_utils_cached_file(url, *args, local_files_only=False, **kwargs): + def transformers_configuration_utils_cached_file(url, *args, local_files_only=False, **kwargs): # pylint: disable=unused-argument return transformers_utils_hub_get_file_from_cache(self.transformers_configuration_utils_cached_file, url, *args, **kwargs) self.replace(torch.nn.init, 'kaiming_uniform_', do_nothing) @@ -78,16 +78,15 @@ class DisableInitialization: self.replace(torch.nn.init, '_no_grad_uniform_', do_nothing) if self.disable_clip: - self.create_model_and_transforms = self.replace(open_clip, 'create_model_and_transforms', create_model_and_transforms_without_pretrained) - self.CLIPTextModel_from_pretrained = self.replace(ldm.modules.encoders.modules.CLIPTextModel, 'from_pretrained', CLIPTextModel_from_pretrained) - self.transformers_modeling_utils_load_pretrained_model = self.replace(transformers.modeling_utils.PreTrainedModel, '_load_pretrained_model', transformers_modeling_utils_load_pretrained_model) - self.transformers_tokenization_utils_base_cached_file = self.replace(transformers.tokenization_utils_base, 'cached_file', transformers_tokenization_utils_base_cached_file) - self.transformers_configuration_utils_cached_file = self.replace(transformers.configuration_utils, 'cached_file', transformers_configuration_utils_cached_file) - self.transformers_utils_hub_get_from_cache = self.replace(transformers.utils.hub, 'get_from_cache', transformers_utils_hub_get_from_cache) + self.create_model_and_transforms = self.replace(open_clip, 'create_model_and_transforms', create_model_and_transforms_without_pretrained) # pylint: disable=attribute-defined-outside-init + self.CLIPTextModel_from_pretrained = self.replace(ldm.modules.encoders.modules.CLIPTextModel, 'from_pretrained', CLIPTextModel_from_pretrained) # pylint: disable=attribute-defined-outside-init + self.transformers_modeling_utils_load_pretrained_model = self.replace(transformers.modeling_utils.PreTrainedModel, '_load_pretrained_model', transformers_modeling_utils_load_pretrained_model) # pylint: disable=attribute-defined-outside-init + self.transformers_tokenization_utils_base_cached_file = self.replace(transformers.tokenization_utils_base, 'cached_file', transformers_tokenization_utils_base_cached_file) # pylint: disable=attribute-defined-outside-init + self.transformers_configuration_utils_cached_file = self.replace(transformers.configuration_utils, 'cached_file', transformers_configuration_utils_cached_file) # pylint: disable=attribute-defined-outside-init + self.transformers_utils_hub_get_from_cache = self.replace(transformers.utils.hub, 'get_from_cache', transformers_utils_hub_get_from_cache) # pylint: disable=attribute-defined-outside-init def __exit__(self, exc_type, exc_val, exc_tb): for obj, field, original in self.replaced: setattr(obj, field, original) self.replaced.clear() - diff --git a/modules/sd_hijack_checkpoint.py b/modules/sd_hijack_checkpoint.py index 2604d969f..6146c19a6 100644 --- a/modules/sd_hijack_checkpoint.py +++ b/modules/sd_hijack_checkpoint.py @@ -5,15 +5,15 @@ import ldm.modules.diffusionmodules.openaimodel def BasicTransformerBlock_forward(self, x, context=None): - return checkpoint(self._forward, x, context) + return checkpoint(self._forward, x, context) # pylint: disable=protected-access def AttentionBlock_forward(self, x): - return checkpoint(self._forward, x) + return checkpoint(self._forward, x) # pylint: disable=protected-access def ResBlock_forward(self, x, emb): - return checkpoint(self._forward, x, emb) + return checkpoint(self._forward, x, emb) # pylint: disable=protected-access stored = [] @@ -43,4 +43,3 @@ def remove(): ldm.modules.diffusionmodules.openaimodel.AttentionBlock.forward = stored[2] stored.clear() - diff --git a/modules/sd_hijack_clip_old.py b/modules/sd_hijack_clip_old.py index a3476e956..21af997e9 100644 --- a/modules/sd_hijack_clip_old.py +++ b/modules/sd_hijack_clip_old.py @@ -70,7 +70,7 @@ def process_text_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, def forward_old(self: sd_hijack_clip.FrozenCLIPEmbedderWithCustomWordsBase, texts): - batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, token_count = process_text_old(self, texts) + batch_multipliers, remade_batch_tokens, used_custom_terms, hijack_comments, hijack_fixes, _token_count = process_text_old(self, texts) self.hijack.comments += hijack_comments diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 4b23c132d..02691b705 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -4,12 +4,11 @@ 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.ddpm import LatentDiffusion # pylint: disable=unused-import +from ldm.models.diffusion.plms import PLMSSampler # pylint: disable=unused-import +from ldm.models.diffusion.ddim import DDIMSampler, noise_like # pylint: disable=unused-import from ldm.models.diffusion.sampling_util import norm_thresholding - @torch.no_grad() def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, @@ -63,7 +62,6 @@ def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=F if quantize_denoised: pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0) if dynamic_threshold is not None: - from ldm.models.diffusion.sampling_util import norm_thresholding pred_x0 = norm_thresholding(pred_x0, dynamic_threshold) # direction pointing to x_t dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index e8c8ce763..ef02ccc35 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -51,7 +51,7 @@ def get_available_vram(): # see https://github.com/basujindal/stable-diffusion/pull/117 for discussion -def split_cross_attention_forward_v1(self, x, context=None, mask=None): +def split_cross_attention_forward_v1(self, x, context=None, mask=None): # pylint: disable=unused-argument h = self.heads q_in = self.to_q(x) @@ -90,7 +90,7 @@ def split_cross_attention_forward_v1(self, x, context=None, mask=None): # taken from https://github.com/Doggettx/stable-diffusion and modified -def split_cross_attention_forward(self, x, context=None, mask=None): +def split_cross_attention_forward(self, x, context=None, mask=None): # pylint: disable=unused-argument h = self.heads q_in = self.to_q(x) context = default(context, x) @@ -231,7 +231,7 @@ def einsum_op(q, k, v): # Tested on i7 with 8MB L3 cache. return einsum_op_tensor_mem(q, k, v, 32) -def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): +def split_cross_attention_forward_invokeAI(self, x, context=None, mask=None): # pylint: disable=unused-argument h = self.heads q = self.to_q(x) @@ -315,7 +315,7 @@ 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 + query_chunk_size = q_tokens # pylint: disable=unused-variable kv_chunk_size = k_tokens with devices.without_autocast(disable=q.dtype == v.dtype): @@ -336,7 +336,7 @@ def get_xformers_flash_attention_op(q, k, v): try: flash_attention_op = xformers.ops.MemoryEfficientAttentionFlashAttentionOp - fw, bw = flash_attention_op + fw, _bw = flash_attention_op if fw.supports(xformers.ops.fmha.Inputs(query=q, key=k, value=v, attn_bias=None)): return flash_attention_op except Exception as e: @@ -345,7 +345,7 @@ def get_xformers_flash_attention_op(q, k, v): return None -def xformers_attention_forward(self, x, context=None, mask=None): +def xformers_attention_forward(self, x, context=None, mask=None): # pylint: disable=unused-argument h = self.heads q_in = self.to_q(x) context = default(context, x) @@ -481,7 +481,7 @@ def xformers_attnblock_forward(self, x): q = self.q(h_) k = self.k(h_) v = self.v(h_) - b, c, h, w = q.shape + b, c, h, w = q.shape # pylint: disable=unused-variable q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) dtype = q.dtype if shared.opts.upcast_attn: @@ -503,7 +503,7 @@ def sdp_attnblock_forward(self, x): q = self.q(h_) k = self.k(h_) v = self.v(h_) - b, c, h, w = q.shape + b, c, h, w = q.shape # pylint: disable=unused-variable q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) dtype = q.dtype if shared.opts.upcast_attn: @@ -531,7 +531,7 @@ def sub_quad_attnblock_forward(self, x): q = self.q(h_) k = self.k(h_) v = self.v(h_) - b, c, h, w = q.shape + b, c, h, w = q.shape # pylint: disable=unused-variable q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) q = q.contiguous() k = k.contiguous() diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 252e8e5fc..c7fee64b5 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -47,25 +47,25 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): class GELUHijack(torch.nn.GELU, torch.nn.Module): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called torch.nn.GELU.__init__(self, *args, **kwargs) - def forward(self, x): + def forward(self, input): # pylint: disable=redefined-builtin if devices.unet_needs_upcast: - return torch.nn.GELU.forward(self.float(), x.float()).to(devices.dtype_unet) + return torch.nn.GELU.forward(self.float(), input.float()).to(devices.dtype_unet) else: - return torch.nn.GELU.forward(self, x) + return torch.nn.GELU.forward(self, input) ddpm_edit_hijack = None def hijack_ddpm_edit(): - global ddpm_edit_hijack + global ddpm_edit_hijack # pylint: disable=global-statement if not ddpm_edit_hijack: CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond) CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) -unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast +unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast # pylint: disable=unnecessary-lambda-assignment CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available() or shared.cmd_opts.use_ipex: @@ -73,8 +73,8 @@ if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_ CondFunc('ldm.modules.attention.GEGLU.forward', lambda orig_func, self, x: orig_func(self.float(), x.float()).to(devices.dtype_unet), unet_needs_upcast) CondFunc('open_clip.transformer.ResidualAttentionBlock.__init__', lambda orig_func, *args, **kwargs: kwargs.update({'act_layer': GELUHijack}) and False or orig_func(*args, **kwargs), lambda _, *args, **kwargs: kwargs.get('act_layer') is None or kwargs['act_layer'] == torch.nn.GELU) -first_stage_cond = lambda _, self, *args, **kwargs: devices.unet_needs_upcast and self.model.diffusion_model.dtype == torch.float16 -first_stage_sub = lambda orig_func, self, x, **kwargs: orig_func(self, x.to(devices.dtype_vae), **kwargs) +first_stage_cond = lambda _, self, *args, **kwargs: devices.unet_needs_upcast and self.model.diffusion_model.dtype == torch.float16 # pylint: disable=unnecessary-lambda-assignment +first_stage_sub = lambda orig_func, self, x, **kwargs: orig_func(self, x.to(devices.dtype_vae), **kwargs) # pylint: disable=unnecessary-lambda-assignment CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) diff --git a/modules/sd_models.py b/modules/sd_models.py index 80626abde..e33465be0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -28,7 +28,7 @@ checkpoints_loaded = collections.OrderedDict() skip_next_load = False -class CheckpointInfo: # TODO Diffusers +class CheckpointInfo: def __init__(self, filename): name = '' self.name = None @@ -48,7 +48,6 @@ class CheckpointInfo: # TODO Diffusers self.hash = model_hash(self.filename) self.sha256 = hashes.sha256_from_cache(self.filename, f"checkpoint/{name}") else: # TODO Diffusers - # sd_model.unet.config._name_or_path.split("/")[-2] repo = [r for r in modelloader.diffuser_repos if filename == r['filename']] if len(repo) == 0: shared.log.error(f'Cannot find diffuser model: {filename}') @@ -540,7 +539,7 @@ def reload_model_weights(sd_model=None, info=None): sd_model.to(devices.cpu) if shared.opts.model_reuse_dict and sd_model is not None: shared.log.info('Reusing previous model dictionary') - sd_hijack.model_hijack.undo_hijack(sd_model) # TODO double undo hijack + sd_hijack.model_hijack.undo_hijack(sd_model) else: unload_model_weights() sd_model = None diff --git a/modules/sd_samplers_compvis.py b/modules/sd_samplers_compvis.py index 0fb411111..5f5544479 100644 --- a/modules/sd_samplers_compvis.py +++ b/modules/sd_samplers_compvis.py @@ -40,7 +40,7 @@ class VanillaStableDiffusionSampler: self.conditioning_key = sd_model.model.conditioning_key - def number_of_needed_noises(self, p): + def number_of_needed_noises(self, p): # pylint: disable=unused-argument return 0 def launch_sampling(self, steps, func): @@ -128,7 +128,7 @@ class VanillaStableDiffusionSampler: self.update_step(res[1]) return x, ts, cond, uncond, res - def unipc_after_update(self, x, model_x): + def unipc_after_update(self, x, model_x): # pylint: disable=unused-argument self.update_step(x) def initialize(self, p): diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 0928b8ee1..8622c0b9c 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -97,10 +97,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} # pylint: disable=unnecessary-lambda-assignment 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]} # pylint: disable=unnecessary-lambda-assignment if not is_edit_model: x_in = torch.cat([torch.stack([x[i] for _ in range(n)]) for i, n in enumerate(repeats)] + [x]) diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index e2f004683..20e337255 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -32,7 +32,7 @@ class VAEApprox(nn.Module): def model(): - global sd_vae_approx_model + global sd_vae_approx_model # pylint: disable=global-statement if sd_vae_approx_model is None: model_path = os.path.join(paths.models_path, "VAE-approx", "model.pt") diff --git a/modules/sub_quadratic_attention.py b/modules/sub_quadratic_attention.py index 87c18a38d..bab11d411 100644 --- a/modules/sub_quadratic_attention.py +++ b/modules/sub_quadratic_attention.py @@ -19,7 +19,7 @@ from torch.utils.checkpoint import checkpoint def narrow_trunc( - input: Tensor, + input: Tensor, # pylint: disable=redefined-builtin dim: int, start: int, length: int @@ -79,8 +79,8 @@ def _query_chunk_attention( summarize_chunk: SummarizeChunk, kv_chunk_size: int, ) -> Tensor: - batch_x_heads, k_tokens, k_channels_per_head = key.shape - _, _, v_channels_per_head = value.shape + _batch_x_heads, k_tokens, _k_channels_per_head = key.shape + _, _, _v_channels_per_head = value.shape def chunk_scanner(chunk_idx: int) -> AttnChunk: key_chunk = narrow_trunc( @@ -113,7 +113,6 @@ def _query_chunk_attention( return all_values / all_weights -# TODO: refactor CrossAttention#get_attention_scores to share code with this def _get_attention_scores_no_kv_chunking( query: Tensor, key: Tensor, @@ -164,7 +163,7 @@ def efficient_dot_product_attention( Returns: Output of shape `[batch * num_heads, query_tokens, channels_per_head]`. """ - batch_x_heads, q_tokens, q_channels_per_head = query.shape + _batch_x_heads, q_tokens, q_channels_per_head = query.shape _, k_tokens, _ = key.shape scale = q_channels_per_head ** -0.5 @@ -202,7 +201,7 @@ def efficient_dot_product_attention( value=value, ) - # TODO: maybe we should use torch.empty_like(query) to allocate storage in-advance, + # 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( diff --git a/modules/ui.py b/modules/ui.py index 1b2c6c836..0e79e983d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1324,7 +1324,7 @@ def create_ui(): reload_sd_model = gr.Button(value='Reload checkpoint', variant='primary', elem_id="sett_reload_sd_model") # reload_script_bodies = gr.Button(value='Reload scripts', variant='primary', elem_id="settings_reload_script_bodies") with gr.Row(): - _settings_search = gr.Text(label="Search", elem_id="settings_search") # TODO settings search + _settings_search = gr.Text(label="Search", elem_id="settings_search") result = gr.HTML(elem_id="settings_result") quicksettings_names = opts.quicksettings_list diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 9be1c3400..dc3298fe8 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -1,445 +1,445 @@ -import json -import os.path -import shutil -import errno -import html -from datetime import datetime -import git -import gradio as gr -from modules import extensions, shared, paths, errors -from modules.call_queue import wrap_gradio_gpu_call - - -extensions_index = "https://vladmandic.github.io/sd-data/pages/extensions.json" -hide_tags = ["localization"] -extensions_list = [] -sort_ordering = { - "default": (True, lambda x: x.get('sort_default', '')), - "user extensions": (True, lambda x: x.get('sort_user', '')), - "update avilable": (True, lambda x: x.get('sort_update', '')), - "updated date": (True, lambda x: x.get('updated', '2000-01-01T00:00')), - "created date": (True, lambda x: x.get('created', '2000-01-01T00:00')), - "name": (False, lambda x: x.get('name', '').lower()), - "enabled": (False, lambda x: x.get('sort_enabled', '').lower()), - "size": (True, lambda x: x.get('size', 0)), - "stars": (True, lambda x: x.get('stars', 0)), - "commits": (True, lambda x: x.get('commits', 0)), - "issues": (True, lambda x: x.get('issues', 0)), -} - - -def update_extension_list(): - global extensions_list # pylint: disable=global-statement - try: - with open(os.path.join(paths.script_path, "html", "extensions.json"), "r", encoding="utf-8") as f: - extensions_list = json.loads(f.read()) - shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}') - except: - shared.log.debug(f'Extensions list failed to load: {os.path.join(paths.script_path, "html", "extensions.json")}') - found = [] - for ext in extensions.extensions: - ext.read_info_from_repo() - for ext in extensions_list: - installed = [extension for extension in extensions.extensions - if extension.git_name == ext['name'] - or extension.name == ext['name'] - or (extension.remote or '').startswith(ext['url'].replace('.git', ''))] - if len(installed) > 0: - found.append(installed[0]) - not_matched = [extension for extension in extensions.extensions if extension not in found] - for ext in not_matched: - entry = { - "name": ext.name or "", - "description": ext.description or "", - "url": ext.remote or "", - "tags": [], - "stars": 0, - "issues": 0, - "commits": 0, - "size": 0, - "long": ext.git_name or ext.name or "", - "added": ext.ctime, - "created": ext.ctime, - "updated": ext.mtime, - } - extensions_list.append(entry) - - -def check_access(): - assert not shared.cmd_opts.disable_extension_access, "extension access disabled because of command line flags" - - -def apply_and_restart(disable_list, update_list, disable_all): - check_access() - shared.log.debug(f'Extensions apply: disable={disable_list} update={update_list}') - disabled = json.loads(disable_list) - assert type(disabled) == list, f"wrong disable_list data for apply_and_restart: {disable_list}" - update = json.loads(update_list) - assert type(update) == list, f"wrong update_list data for apply_and_restart: {update_list}" - update = set(update) - for ext in extensions.extensions: - if ext.name not in update: - continue - try: - ext.fetch_and_reset_hard() - except Exception as e: - errors.display(e, f'extensions apply update: {ext.name}') - shared.opts.disabled_extensions = disabled - shared.opts.disable_all_extensions = disable_all - shared.opts.save(shared.config_filename) - shared.restart_server(restart=True) - - -def check_updates(_id_task, disable_list, search_text, sort_column): - check_access() - disabled = json.loads(disable_list) - assert type(disabled) == list, f"wrong disable_list data for apply_and_restart: {disable_list}" - exts = [ext for ext in extensions.extensions if ext.remote is not None and ext.name not in disabled] - shared.log.info(f'Extensions update check: update={len(exts)} disabled={len(disable_list)}') - shared.state.job_count = len(exts) - for ext in exts: - shared.state.textinfo = ext.name - try: - ext.check_updates() - if ext.can_update: - ext.fetch_and_reset_hard() - ext.read_info_from_repo() - commit_date = ext.commit_date or 1577836800 - shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') - else: - commit_date = ext.commit_date or 1577836800 - shared.log.debug(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') - except FileNotFoundError as e: - if 'FETCH_HEAD' not in str(e): - raise - except Exception: - errors.display(e, f'extensions check update: {ext.name}') - shared.state.nextjob() - return refresh_extensions_list_from_data(search_text, sort_column), "Extension update complete | Restart required" - - -def make_commit_link(commit_hash, remote, text=None): - if text is None: - text = commit_hash[:8] - if remote.startswith("https://github.com/"): - href = os.path.join(remote, "commit", commit_hash) - return f'{text}' - else: - return text - - -def normalize_git_url(url): - if url is None: - return "" - url = url.replace(".git", "") - return url - - -def install_extension_from_url(dirname, url, branch_name, search_text, sort_column): - check_access() - assert url, 'No URL specified' - if dirname is None or dirname == "": - *parts, last_part = url.split('/') # pylint: disable=unused-variable - last_part = normalize_git_url(last_part) - dirname = last_part - target_dir = os.path.join(extensions.extensions_dir, dirname) - shared.log.info(f'Installing extension: {url} into {target_dir}') - assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}' - normalized_url = normalize_git_url(url) - assert len([x for x in extensions.extensions if normalize_git_url(x.remote) == normalized_url]) == 0, 'Extension with this URL is already installed' - tmpdir = os.path.join(paths.data_path, "tmp", dirname) - try: - shutil.rmtree(tmpdir, True) - if not branch_name: - # if no branch is specified, use the default branch - with git.Repo.clone_from(url, tmpdir) as repo: - repo.remote().fetch() - for submodule in repo.submodules: - submodule.update() - else: - with git.Repo.clone_from(url, tmpdir, branch=branch_name) as repo: - repo.remote().fetch() - for submodule in repo.submodules: - submodule.update() - try: - os.rename(tmpdir, target_dir) - except OSError as err: - if err.errno == errno.EXDEV: - shutil.move(tmpdir, target_dir) - else: - raise err - from launch import run_extension_installer - run_extension_installer(target_dir) - extensions.list_extensions() - return [refresh_extensions_list_from_data(search_text, sort_column), html.escape(f"Extension installed: {target_dir} | Restart required")] - finally: - shutil.rmtree(tmpdir, True) - - -def install_extension(extension_to_install, search_text, sort_column): - shared.log.info(f'Extension install: {extension_to_install}') - code, message = install_extension_from_url(None, extension_to_install, None, search_text, sort_column) - return code, message - - -def uninstall_extension(extension_path, search_text, sort_column): - def errorRemoveReadonly(func, path, exc): - import stat - excvalue = exc[1] - shared.log.debug(f'Exception during cleanup: {func} {path} {excvalue.strerror}') - if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES: - shared.log.debug(f'Retrying cleanup: {path}') - os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) - func(path) - - ext = [extension for extension in extensions.extensions if os.path.abspath(extension.path) == os.path.abspath(extension_path)] - if len(ext) > 0 and os.path.isdir(extension_path): - found = ext[0] - try: - shutil.rmtree(found.path, ignore_errors=False, onerror=errorRemoveReadonly) - except Exception as e: - shared.log.warning(f'Extension uninstall failed: {found.path} {e}') - extensions.extensions = [extension for extension in extensions.extensions if os.path.abspath(found.path) != os.path.abspath(extension_path)] - update_extension_list() - code = refresh_extensions_list_from_data(search_text, sort_column) - shared.log.info(f'Extension uninstalled: {found.path}') - return code, f"Extension uninstalled: {found.path} | Restart required" - else: - shared.log.warning(f'Extension uninstall cannot find extension: {extension_path}') - code = refresh_extensions_list_from_data(search_text, sort_column) - return code, f"Extension uninstalled failed: {extension_path}" - - -def update_extension(extension_path, search_text, sort_column): - exts = [extension for extension in extensions.extensions if os.path.abspath(extension.path) == os.path.abspath(extension_path)] - shared.state.job_count = len(exts) - for ext in exts: - shared.log.debug(f'Extensions update start: {ext.name} {ext.commit_hash} {ext.commit_date}') - shared.state.textinfo = ext.name - try: - ext.check_updates() - if ext.can_update: - ext.fetch_and_reset_hard() - ext.read_info_from_repo() - commit_date = ext.commit_date or 1577836800 - shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') - else: - commit_date = ext.commit_date or 1577836800 - shared.log.info(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') - except FileNotFoundError as e: - if 'FETCH_HEAD' not in str(e): - raise - except Exception as e: - shared.log.error(f'Extensions update failed: {ext.name}') - errors.display(e, f'extensions check update: {ext.name}') - shared.log.debug(f'Extensions update finish: {ext.name} {ext.commit_hash} {ext.commit_date}') - shared.state.nextjob() - return refresh_extensions_list_from_data(search_text, sort_column), f"Extension updated | {extension_path} | Restart required" - - -def refresh_extensions_list(search_text, sort_column): - global extensions_list # pylint: disable=global-statement - import urllib.request - try: - with urllib.request.urlopen(extensions_index) as response: - text = response.read() - extensions_list = json.loads(text) - with open(os.path.join(paths.script_path, "html", "extensions.json"), "w", encoding="utf-8") as outfile: - json_object = json.dumps(extensions_list, indent=2) - outfile.write(json_object) - shared.log.debug(f'Updated extensions list: {len(extensions_list)} {extensions_index} {outfile}') - except Exception as e: - shared.log.warning(f'Updated extensions list failed: {extensions_index} {e}') - update_extension_list() - code = refresh_extensions_list_from_data(search_text, sort_column) - return code, f'Extensions | {len(extensions.extensions)} registered | {len(extensions_list)} available' - - -def search_extensions(search_text, sort_column): - code = refresh_extensions_list_from_data(search_text, sort_column) - return code, f'Search | {search_text} | {sort_column}' - - -def refresh_extensions_list_from_data(search_text, sort_column): - shared.log.debug(f'Extensions manager: refresh list search="{search_text}" sort="{sort_column}"') - code = """ - - - - - - - - - - - - - - - - - - - - """ - for ext in extensions_list: - extension = [extension for extension in extensions.extensions if extension.git_name == ext['name'] or extension.name == ext['name']] - if len(extension) > 0: - extension[0].read_info_from_repo() - ext['installed'] = len(extension) > 0 - ext['commit_date'] = extension[0].commit_date if len(extension) > 0 else 1577836800 - ext['is_builtin'] = extension[0].is_builtin if len(extension) > 0 else False - ext['version'] = extension[0].version if len(extension) > 0 else '' - ext['enabled'] = extension[0].enabled if len(extension) > 0 else '' - ext['remote'] = extension[0].remote if len(extension) > 0 else None - ext['path'] = extension[0].path if len(extension) > 0 else '' - ext['sort_default'] = f"{'1' if ext['is_builtin'] else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" - sort_reverse, sort_function = sort_ordering[sort_column] - - def dt(x: str): - val = ext.get(x, None) - if val is not None: - return datetime.fromisoformat(val[:-1]).strftime('%a %b%d %Y %H:%M') - else: - return "N/A" - - for ext in sorted(extensions_list, key=sort_function, reverse=sort_reverse): - name = ext.get("name", "unknown") - added = dt('added') - created = dt('created') - pushed = dt('pushed') - updated = dt('updated') - url = ext.get('url', None) - size = ext.get('size', 0) - stars = ext.get('stars', 0) - issues = ext.get('issues', 0) - commits = ext.get('commits', 0) - description = ext.get("description", "") - installed = ext.get("installed", False) - enabled = ext.get("enabled", False) - path = ext.get("path", "") - remote = ext.get("remote", None) - commit_date = ext.get("commit_date", 1577836800) or 1577836800 - update_available = (remote is not None) & (installed) & (datetime.utcfromtimestamp(commit_date + 60 * 60) < datetime.fromisoformat(ext.get('updated', '2000-01-01T00:00:00.000Z')[:-1])) - ext['sort_user'] = f"{'0' if ext['is_builtin'] else '1'}{'1' if ext['installed'] else '0'}{ext.get('name', '')}" - ext['sort_enabled'] = f"{'0' if ext['enabled'] else '1'}{'1' if ext['is_builtin'] else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" - ext['sort_update'] = f"{'1' if update_available else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" - tags = ext.get("tags", []) - tags_string = ' '.join(tags) - tags = tags + ["installed"] if installed else tags - if len([x for x in tags if x in hide_tags]) > 0: - continue - if search_text and search_text.strip(): - if search_text.lower() not in html.escape(name).lower() and search_text.lower() not in html.escape(description).lower() and search_text.lower() not in html.escape(tags_string).lower(): - continue - version_code = '' - type_code = '' - install_code = '' - enabled_code = '' - if installed: - type_code = f"""
{"SYSTEM" if ext['is_builtin'] else 'USER'}
""" - version_code = f"""
{ext['version']}
""" - enabled_code = f"""""" - masked_path = html.escape(path.replace('\\', '/')) - if not ext['is_builtin']: - install_code = f"""""" - if update_available: - install_code += f"""""" - else: - install_code = f"""""" - tags_text = ", ".join([f"{x}" for x in tags]) - code += f""" - - {enabled_code} - - - - - - """ - code += "
EnabledExtensionDescriptionTypeCurrent version
{html.escape(name)}
{tags_text}
{html.escape(description)} -

Created {html.escape(created)} | Added {html.escape(added)} | Pushed {html.escape(pushed)} | Updated {html.escape(updated)}

-

Stars {html.escape(str(stars))} | Size {html.escape(str(size))} | Commits {html.escape(str(commits))} | Issues {html.escape(str(issues))}

-
{type_code}{version_code}{install_code}
" - return code - - -def create_ui(): - import modules.ui - with gr.Blocks(analytics_enabled=False) as ui: - extensions_disable_all = gr.Radio(label="Disable all extensions", choices=["none", "user", "all"], value=shared.opts.disable_all_extensions, elem_id="extensions_disable_all", visible=False) - extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False).style(container=False) - extensions_update_list = gr.Text(elem_id="extensions_update_list", visible=False).style(container=False) - with gr.Tabs(elem_id="tabs_extensions"): - with gr.TabItem("Manage Extensions", id="manage"): - with gr.Row(elem_id="extensions_installed_top"): - extension_to_install = gr.Text(elem_id="extension_to_install", visible=False) - install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) - uninstall_extension_button = gr.Button(elem_id="uninstall_extension_button", visible=False) - update_extension_button = gr.Button(elem_id="update_extension_button", visible=False) - with gr.Column(scale=4): - search_text = gr.Text(label="Search") - info = gr.HTML('Note: After any operation such as install/uninstall or enable/disable, please restart the server') - with gr.Column(scale=1): - sort_column = gr.Dropdown(value="default", label="Sort by", choices=list(sort_ordering.keys()), multiselect=False) - with gr.Column(scale=1): - refresh_extensions_button = gr.Button(value="Refresh extension list", variant="primary") - check = gr.Button(value="Update installed extensions", variant="primary") - apply = gr.Button(value="Apply changes & restart server", variant="primary") - update_extension_list() - extensions_table = gr.HTML(refresh_extensions_list_from_data(search_text.value, sort_column.value)) - check.click( - fn=wrap_gradio_gpu_call(check_updates, extra_outputs=[gr.update()]), - _js="extensions_check", - inputs=[info, extensions_disabled_list, search_text, sort_column], - outputs=[extensions_table, info], - ) - apply.click( - fn=apply_and_restart, - _js="extensions_apply", - inputs=[extensions_disabled_list, extensions_update_list, extensions_disable_all], - outputs=[], - ) - refresh_extensions_button.click( - fn=modules.ui.wrap_gradio_call(refresh_extensions_list, extra_outputs=[gr.update(), gr.update()]), - inputs=[search_text, sort_column], - outputs=[extensions_table, info], - ) - install_extension_button.click( - fn=modules.ui.wrap_gradio_call(install_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), - inputs=[extension_to_install, search_text, sort_column], - outputs=[extensions_table, info], - ) - uninstall_extension_button.click( - fn=modules.ui.wrap_gradio_call(uninstall_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), - inputs=[extension_to_install, search_text, sort_column], - outputs=[extensions_table, info], - ) - update_extension_button.click( - fn=modules.ui.wrap_gradio_call(update_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), - inputs=[extension_to_install, search_text, sort_column], - outputs=[extensions_table, info], - ) - search_text.change( - fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]), - inputs=[search_text, sort_column], - outputs=[extensions_table, info], - ) - sort_column.change( - fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]), - inputs=[search_text, sort_column], - outputs=[extensions_table, info], - ) - with gr.TabItem("Manual install", id="install_from_url"): - install_url = gr.Text(label="URL for extension's git repository") - install_branch = gr.Text(label="Specific branch name", placeholder="Leave empty for default main branch") - install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto") - install_button = gr.Button(value="Install", variant="primary") - info = gr.HTML(elem_id="extension_info") - install_button.click( - fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]), - inputs=[install_dirname, install_url, install_branch, search_text, sort_column], - outputs=[extensions_table, info], - ) - return ui +import json +import os.path +import shutil +import errno +import html +from datetime import datetime +import git +import gradio as gr +from modules import extensions, shared, paths, errors +from modules.call_queue import wrap_gradio_gpu_call + + +extensions_index = "https://vladmandic.github.io/sd-data/pages/extensions.json" +hide_tags = ["localization"] +extensions_list = [] +sort_ordering = { + "default": (True, lambda x: x.get('sort_default', '')), + "user extensions": (True, lambda x: x.get('sort_user', '')), + "update avilable": (True, lambda x: x.get('sort_update', '')), + "updated date": (True, lambda x: x.get('updated', '2000-01-01T00:00')), + "created date": (True, lambda x: x.get('created', '2000-01-01T00:00')), + "name": (False, lambda x: x.get('name', '').lower()), + "enabled": (False, lambda x: x.get('sort_enabled', '').lower()), + "size": (True, lambda x: x.get('size', 0)), + "stars": (True, lambda x: x.get('stars', 0)), + "commits": (True, lambda x: x.get('commits', 0)), + "issues": (True, lambda x: x.get('issues', 0)), +} + + +def update_extension_list(): + global extensions_list # pylint: disable=global-statement + try: + with open(os.path.join(paths.script_path, "html", "extensions.json"), "r", encoding="utf-8") as f: + extensions_list = json.loads(f.read()) + shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}') + except: + shared.log.debug(f'Extensions list failed to load: {os.path.join(paths.script_path, "html", "extensions.json")}') + found = [] + for ext in extensions.extensions: + ext.read_info_from_repo() + for ext in extensions_list: + installed = [extension for extension in extensions.extensions + if extension.git_name == ext['name'] + or extension.name == ext['name'] + or (extension.remote or '').startswith(ext['url'].replace('.git', ''))] + if len(installed) > 0: + found.append(installed[0]) + not_matched = [extension for extension in extensions.extensions if extension not in found] + for ext in not_matched: + entry = { + "name": ext.name or "", + "description": ext.description or "", + "url": ext.remote or "", + "tags": [], + "stars": 0, + "issues": 0, + "commits": 0, + "size": 0, + "long": ext.git_name or ext.name or "", + "added": ext.ctime, + "created": ext.ctime, + "updated": ext.mtime, + } + extensions_list.append(entry) + + +def check_access(): + assert not shared.cmd_opts.disable_extension_access, "extension access disabled because of command line flags" + + +def apply_and_restart(disable_list, update_list, disable_all): + check_access() + shared.log.debug(f'Extensions apply: disable={disable_list} update={update_list}') + disabled = json.loads(disable_list) + assert type(disabled) == list, f"wrong disable_list data for apply_and_restart: {disable_list}" + update = json.loads(update_list) + assert type(update) == list, f"wrong update_list data for apply_and_restart: {update_list}" + update = set(update) + for ext in extensions.extensions: + if ext.name not in update: + continue + try: + ext.fetch_and_reset_hard() + except Exception as e: + errors.display(e, f'extensions apply update: {ext.name}') + shared.opts.disabled_extensions = disabled + shared.opts.disable_all_extensions = disable_all + shared.opts.save(shared.config_filename) + shared.restart_server(restart=True) + + +def check_updates(_id_task, disable_list, search_text, sort_column): + check_access() + disabled = json.loads(disable_list) + assert type(disabled) == list, f"wrong disable_list data for apply_and_restart: {disable_list}" + exts = [ext for ext in extensions.extensions if ext.remote is not None and ext.name not in disabled] + shared.log.info(f'Extensions update check: update={len(exts)} disabled={len(disable_list)}') + shared.state.job_count = len(exts) + for ext in exts: + shared.state.textinfo = ext.name + try: + ext.check_updates() + if ext.can_update: + ext.fetch_and_reset_hard() + ext.read_info_from_repo() + commit_date = ext.commit_date or 1577836800 + shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') + else: + commit_date = ext.commit_date or 1577836800 + shared.log.debug(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') + except FileNotFoundError as e: + if 'FETCH_HEAD' not in str(e): + raise + except Exception: + errors.display(e, f'extensions check update: {ext.name}') + shared.state.nextjob() + return refresh_extensions_list_from_data(search_text, sort_column), "Extension update complete | Restart required" + + +def make_commit_link(commit_hash, remote, text=None): + if text is None: + text = commit_hash[:8] + if remote.startswith("https://github.com/"): + href = os.path.join(remote, "commit", commit_hash) + return f'{text}' + else: + return text + + +def normalize_git_url(url): + if url is None: + return "" + url = url.replace(".git", "") + return url + + +def install_extension_from_url(dirname, url, branch_name, search_text, sort_column): + check_access() + assert url, 'No URL specified' + if dirname is None or dirname == "": + *parts, last_part = url.split('/') # pylint: disable=unused-variable + last_part = normalize_git_url(last_part) + dirname = last_part + target_dir = os.path.join(extensions.extensions_dir, dirname) + shared.log.info(f'Installing extension: {url} into {target_dir}') + assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}' + normalized_url = normalize_git_url(url) + assert len([x for x in extensions.extensions if normalize_git_url(x.remote) == normalized_url]) == 0, 'Extension with this URL is already installed' + tmpdir = os.path.join(paths.data_path, "tmp", dirname) + try: + shutil.rmtree(tmpdir, True) + if not branch_name: + # if no branch is specified, use the default branch + with git.Repo.clone_from(url, tmpdir) as repo: + repo.remote().fetch() + for submodule in repo.submodules: + submodule.update() + else: + with git.Repo.clone_from(url, tmpdir, branch=branch_name) as repo: + repo.remote().fetch() + for submodule in repo.submodules: + submodule.update() + try: + os.rename(tmpdir, target_dir) + except OSError as err: + if err.errno == errno.EXDEV: + shutil.move(tmpdir, target_dir) + else: + raise err + from launch import run_extension_installer + run_extension_installer(target_dir) + extensions.list_extensions() + return [refresh_extensions_list_from_data(search_text, sort_column), html.escape(f"Extension installed: {target_dir} | Restart required")] + finally: + shutil.rmtree(tmpdir, True) + + +def install_extension(extension_to_install, search_text, sort_column): + shared.log.info(f'Extension install: {extension_to_install}') + code, message = install_extension_from_url(None, extension_to_install, None, search_text, sort_column) + return code, message + + +def uninstall_extension(extension_path, search_text, sort_column): + def errorRemoveReadonly(func, path, exc): + import stat + excvalue = exc[1] + shared.log.debug(f'Exception during cleanup: {func} {path} {excvalue.strerror}') + if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES: + shared.log.debug(f'Retrying cleanup: {path}') + os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + func(path) + + ext = [extension for extension in extensions.extensions if os.path.abspath(extension.path) == os.path.abspath(extension_path)] + if len(ext) > 0 and os.path.isdir(extension_path): + found = ext[0] + try: + shutil.rmtree(found.path, ignore_errors=False, onerror=errorRemoveReadonly) + except Exception as e: + shared.log.warning(f'Extension uninstall failed: {found.path} {e}') + extensions.extensions = [extension for extension in extensions.extensions if os.path.abspath(found.path) != os.path.abspath(extension_path)] + update_extension_list() + code = refresh_extensions_list_from_data(search_text, sort_column) + shared.log.info(f'Extension uninstalled: {found.path}') + return code, f"Extension uninstalled: {found.path} | Restart required" + else: + shared.log.warning(f'Extension uninstall cannot find extension: {extension_path}') + code = refresh_extensions_list_from_data(search_text, sort_column) + return code, f"Extension uninstalled failed: {extension_path}" + + +def update_extension(extension_path, search_text, sort_column): + exts = [extension for extension in extensions.extensions if os.path.abspath(extension.path) == os.path.abspath(extension_path)] + shared.state.job_count = len(exts) + for ext in exts: + shared.log.debug(f'Extensions update start: {ext.name} {ext.commit_hash} {ext.commit_date}') + shared.state.textinfo = ext.name + try: + ext.check_updates() + if ext.can_update: + ext.fetch_and_reset_hard() + ext.read_info_from_repo() + commit_date = ext.commit_date or 1577836800 + shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') + else: + commit_date = ext.commit_date or 1577836800 + shared.log.info(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {datetime.utcfromtimestamp(commit_date)}') + except FileNotFoundError as e: + if 'FETCH_HEAD' not in str(e): + raise + except Exception as e: + shared.log.error(f'Extensions update failed: {ext.name}') + errors.display(e, f'extensions check update: {ext.name}') + shared.log.debug(f'Extensions update finish: {ext.name} {ext.commit_hash} {ext.commit_date}') + shared.state.nextjob() + return refresh_extensions_list_from_data(search_text, sort_column), f"Extension updated | {extension_path} | Restart required" + + +def refresh_extensions_list(search_text, sort_column): + global extensions_list # pylint: disable=global-statement + import urllib.request + try: + with urllib.request.urlopen(extensions_index) as response: + text = response.read() + extensions_list = json.loads(text) + with open(os.path.join(paths.script_path, "html", "extensions.json"), "w", encoding="utf-8") as outfile: + json_object = json.dumps(extensions_list, indent=2) + outfile.write(json_object) + shared.log.debug(f'Updated extensions list: {len(extensions_list)} {extensions_index} {outfile}') + except Exception as e: + shared.log.warning(f'Updated extensions list failed: {extensions_index} {e}') + update_extension_list() + code = refresh_extensions_list_from_data(search_text, sort_column) + return code, f'Extensions | {len(extensions.extensions)} registered | {len(extensions_list)} available' + + +def search_extensions(search_text, sort_column): + code = refresh_extensions_list_from_data(search_text, sort_column) + return code, f'Search | {search_text} | {sort_column}' + + +def refresh_extensions_list_from_data(search_text, sort_column): + shared.log.debug(f'Extensions manager: refresh list search="{search_text}" sort="{sort_column}"') + code = """ + + + + + + + + + + + + + + + + + + + + """ + for ext in extensions_list: + extension = [extension for extension in extensions.extensions if extension.git_name == ext['name'] or extension.name == ext['name']] + if len(extension) > 0: + extension[0].read_info_from_repo() + ext['installed'] = len(extension) > 0 + ext['commit_date'] = extension[0].commit_date if len(extension) > 0 else 1577836800 + ext['is_builtin'] = extension[0].is_builtin if len(extension) > 0 else False + ext['version'] = extension[0].version if len(extension) > 0 else '' + ext['enabled'] = extension[0].enabled if len(extension) > 0 else '' + ext['remote'] = extension[0].remote if len(extension) > 0 else None + ext['path'] = extension[0].path if len(extension) > 0 else '' + ext['sort_default'] = f"{'1' if ext['is_builtin'] else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" + sort_reverse, sort_function = sort_ordering[sort_column] + + def dt(x: str): + val = ext.get(x, None) + if val is not None: + return datetime.fromisoformat(val[:-1]).strftime('%a %b%d %Y %H:%M') + else: + return "N/A" + + for ext in sorted(extensions_list, key=sort_function, reverse=sort_reverse): + name = ext.get("name", "unknown") + added = dt('added') + created = dt('created') + pushed = dt('pushed') + updated = dt('updated') + url = ext.get('url', None) + size = ext.get('size', 0) + stars = ext.get('stars', 0) + issues = ext.get('issues', 0) + commits = ext.get('commits', 0) + description = ext.get("description", "") + installed = ext.get("installed", False) + enabled = ext.get("enabled", False) + path = ext.get("path", "") + remote = ext.get("remote", None) + commit_date = ext.get("commit_date", 1577836800) or 1577836800 + update_available = (remote is not None) & (installed) & (datetime.utcfromtimestamp(commit_date + 60 * 60) < datetime.fromisoformat(ext.get('updated', '2000-01-01T00:00:00.000Z')[:-1])) + ext['sort_user'] = f"{'0' if ext['is_builtin'] else '1'}{'1' if ext['installed'] else '0'}{ext.get('name', '')}" + ext['sort_enabled'] = f"{'0' if ext['enabled'] else '1'}{'1' if ext['is_builtin'] else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" + ext['sort_update'] = f"{'1' if update_available else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00')}" + tags = ext.get("tags", []) + tags_string = ' '.join(tags) + tags = tags + ["installed"] if installed else tags + if len([x for x in tags if x in hide_tags]) > 0: + continue + if search_text and search_text.strip(): + if search_text.lower() not in html.escape(name).lower() and search_text.lower() not in html.escape(description).lower() and search_text.lower() not in html.escape(tags_string).lower(): + continue + version_code = '' + type_code = '' + install_code = '' + enabled_code = '' + if installed: + type_code = f"""
{"SYSTEM" if ext['is_builtin'] else 'USER'}
""" + version_code = f"""
{ext['version']}
""" + enabled_code = f"""""" + masked_path = html.escape(path.replace('\\', '/')) + if not ext['is_builtin']: + install_code = f"""""" + if update_available: + install_code += f"""""" + else: + install_code = f"""""" + tags_text = ", ".join([f"{x}" for x in tags]) + code += f""" + + {enabled_code} + + + + + + """ + code += "
EnabledExtensionDescriptionTypeCurrent version
{html.escape(name)}
{tags_text}
{html.escape(description)} +

Created {html.escape(created)} | Added {html.escape(added)} | Pushed {html.escape(pushed)} | Updated {html.escape(updated)}

+

Stars {html.escape(str(stars))} | Size {html.escape(str(size))} | Commits {html.escape(str(commits))} | Issues {html.escape(str(issues))}

+
{type_code}{version_code}{install_code}
" + return code + + +def create_ui(): + import modules.ui + with gr.Blocks(analytics_enabled=False) as ui: + extensions_disable_all = gr.Radio(label="Disable all extensions", choices=["none", "user", "all"], value=shared.opts.disable_all_extensions, elem_id="extensions_disable_all", visible=False) + extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False).style(container=False) + extensions_update_list = gr.Text(elem_id="extensions_update_list", visible=False).style(container=False) + with gr.Tabs(elem_id="tabs_extensions"): + with gr.TabItem("Manage Extensions", id="manage"): + with gr.Row(elem_id="extensions_installed_top"): + extension_to_install = gr.Text(elem_id="extension_to_install", visible=False) + install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) + uninstall_extension_button = gr.Button(elem_id="uninstall_extension_button", visible=False) + update_extension_button = gr.Button(elem_id="update_extension_button", visible=False) + with gr.Column(scale=4): + search_text = gr.Text(label="Search") + info = gr.HTML('Note: After any operation such as install/uninstall or enable/disable, please restart the server') + with gr.Column(scale=1): + sort_column = gr.Dropdown(value="default", label="Sort by", choices=list(sort_ordering.keys()), multiselect=False) + with gr.Column(scale=1): + refresh_extensions_button = gr.Button(value="Refresh extension list", variant="primary") + check = gr.Button(value="Update installed extensions", variant="primary") + apply = gr.Button(value="Apply changes & restart server", variant="primary") + update_extension_list() + extensions_table = gr.HTML(refresh_extensions_list_from_data(search_text.value, sort_column.value)) + check.click( + fn=wrap_gradio_gpu_call(check_updates, extra_outputs=[gr.update()]), + _js="extensions_check", + inputs=[info, extensions_disabled_list, search_text, sort_column], + outputs=[extensions_table, info], + ) + apply.click( + fn=apply_and_restart, + _js="extensions_apply", + inputs=[extensions_disabled_list, extensions_update_list, extensions_disable_all], + outputs=[], + ) + refresh_extensions_button.click( + fn=modules.ui.wrap_gradio_call(refresh_extensions_list, extra_outputs=[gr.update(), gr.update()]), + inputs=[search_text, sort_column], + outputs=[extensions_table, info], + ) + install_extension_button.click( + fn=modules.ui.wrap_gradio_call(install_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), + inputs=[extension_to_install, search_text, sort_column], + outputs=[extensions_table, info], + ) + uninstall_extension_button.click( + fn=modules.ui.wrap_gradio_call(uninstall_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), + inputs=[extension_to_install, search_text, sort_column], + outputs=[extensions_table, info], + ) + update_extension_button.click( + fn=modules.ui.wrap_gradio_call(update_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]), + inputs=[extension_to_install, search_text, sort_column], + outputs=[extensions_table, info], + ) + search_text.change( + fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]), + inputs=[search_text, sort_column], + outputs=[extensions_table, info], + ) + sort_column.change( + fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]), + inputs=[search_text, sort_column], + outputs=[extensions_table, info], + ) + with gr.TabItem("Manual install", id="install_from_url"): + install_url = gr.Text(label="URL for extension's git repository") + install_branch = gr.Text(label="Specific branch name", placeholder="Leave empty for default main branch") + install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto") + install_button = gr.Button(value="Install", variant="primary") + info = gr.HTML(elem_id="extension_info") + install_button.click( + fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]), + inputs=[install_dirname, install_url, install_branch, search_text, sort_column], + outputs=[extensions_table, info], + ) + return ui diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 545898486..d29863212 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -12,7 +12,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): def list_items(self): for name, path in shared.hypernetworks.items(): - path, ext = os.path.splitext(path) + path, _ext = os.path.splitext(path) yield { "name": name, "filename": path, From 9dbe8bc6e4c708d92eae8cd61c6b1063eb9cd100 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 29 May 2023 00:34:31 +0300 Subject: [PATCH 06/11] Fix cuda with ipex on memmon.py --- modules/memmon.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/memmon.py b/modules/memmon.py index 66fd9a8d4..1ea110f51 100644 --- a/modules/memmon.py +++ b/modules/memmon.py @@ -24,17 +24,13 @@ class MemUsageMonitor(threading.Thread): #torch.cuda.is_available() reports False when using IPEX. if shared.cmd_opts.use_ipex: self.cuda_mem_get_info() - torch.cuda.memory_stats("xpu") + torch.xpu.memory_stats("xpu") else: self.disabled = True else: try: - if shared.cmd_opts.use_ipex: - self.cuda_mem_get_info() - torch.cuda.memory_stats("xpu") - else: - self.cuda_mem_get_info() - torch.cuda.memory_stats(self.device) + self.cuda_mem_get_info() + torch.cuda.memory_stats(self.device) except Exception: self.disabled = True From 6013ab39606f681b3880ca10f72fe1c945d11938 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 21:26:09 -0400 Subject: [PATCH 07/11] remove allocator info --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index 0b3a9de6b..d5b464156 100644 --- a/installer.py +++ b/installer.py @@ -293,7 +293,7 @@ def check_torch(): log.info(f'{os.popen("icpx --version").read().rstrip()}') log.info(f'Torch detected GPU: {torch.xpu.get_device_name("xpu")} VRAM {round(torch.xpu.get_device_properties("xpu").total_memory / 1024 / 1024)}') elif torch.cuda.is_available() and (allow_cuda or allow_rocm): - log.debug(f'Torch allocator: {torch.cuda.get_allocator_backend()}') + # log.debug(f'Torch allocator: {torch.cuda.get_allocator_backend()}') if torch.version.cuda and allow_cuda: log.info(f'Torch backend: nVidia CUDA {torch.version.cuda} cuDNN {torch.backends.cudnn.version() if torch.backends.cudnn.is_available() else "N/A"}') elif torch.version.hip and allow_rocm: From b2c3bc5aaa9522ddcf2bce55a3746293dd9cef72 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 28 May 2023 21:26:29 -0400 Subject: [PATCH 08/11] update extensions --- extensions-builtin/sd-extension-system-info | 2 +- extensions-builtin/stable-diffusion-webui-images-browser | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 46386f93d..2a811ca0c 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 46386f93de0a9614ef94cf75acb0909e59859274 +Subproject commit 2a811ca0c8b6913a1a2732bf459287addc9cb4f2 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index c61fae964..75af6d0c3 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit c61fae964ac94bc369fd0e346805e3e2885c69b4 +Subproject commit 75af6d0c32b72350b2f140f186cd8ce0e24dda10 From 5f1fd7bd665a78b879e5b371945207de42c2d058 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 29 May 2023 13:43:03 -0400 Subject: [PATCH 09/11] update common ui --- TODO.md | 1 + extensions-builtin/sd-extension-system-info | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- modules/cmd_args.py | 2 + modules/extras.py | 3 - modules/img2img.py | 4 +- modules/postprocessing.py | 8 +- modules/processing.py | 14 +- modules/sd_models.py | 22 +- modules/sd_samplers.py | 6 +- modules/shared.py | 14 +- .../textual_inversion/textual_inversion.py | 2 +- modules/txt2img.py | 4 +- modules/ui.py | 8 +- modules/ui_common.py | 203 +++++++++--------- modules/ui_postprocessing.py | 45 ++-- 16 files changed, 180 insertions(+), 160 deletions(-) diff --git a/TODO.md b/TODO.md index dff1f03a4..d1ff57730 100644 --- a/TODO.md +++ b/TODO.md @@ -48,3 +48,4 @@ Tech that can be integrated as part of the core workflow... ## Random - Bunch of stuff: +- diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 2a811ca0c..8046b1544 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 2a811ca0c8b6913a1a2732bf459287addc9cb4f2 +Subproject commit 8046b1544513cea06d1c41748c22727c930323ab diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 2e0dc37d2..09cb9a32d 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 2e0dc37d222aaba355a71dac0eda4bb7ca54f05f +Subproject commit 09cb9a32d1051aa827f1bb092cf17fcbf996ed7f diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 2c62342bf..6e658f6c9 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -44,6 +44,8 @@ group.add_argument('--use-directml', default = False, action='store_true', help group.add_argument("--use-cuda", default=False, action='store_true', help="Force use nVidia CUDA backend, default: %(default)s") group.add_argument("--use-rocm", default=False, action='store_true', help="Force use AMD ROCm backend, default: %(default)s") group.add_argument('--subpath', type=str, help='Customize the URL subpath for usage with reverse proxy') +group.add_argument('--backend', type=str, choices=[None, 'original', 'diffusers'], default=None, required=False, help='force backend type') + # removed args are added here as hidden in fixed format for compatbility reasons group.add_argument("-f", action='store_true', help=argparse.SUPPRESS) # allows running as root; implemented outside of webui diff --git a/modules/extras.py b/modules/extras.py index 683064661..3247c27a0 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -24,9 +24,6 @@ def run_pnginfo(image): for key, text in items.items(): if key != 'UserComment': info += f"
{html.escape(str(key))}: {html.escape(str(text))}
" - if len(info) == 0: - message = "Nothing found in the image." - info = f"

{message}

" return '', geninfo, info diff --git a/modules/img2img.py b/modules/img2img.py index dda82dfcd..0e0b9bf8c 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -5,7 +5,7 @@ import modules.scripts from modules import sd_samplers, shared from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images -from modules.ui import plaintext_to_html +from modules.ui import plaintext_to_html, infotext_to_html import modules.processing as processing from modules.memstats import memory_stats @@ -165,4 +165,4 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s p.close() generation_info_js = processed.js() shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} img') - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, infotext_to_html(processed.info), plaintext_to_html(processed.comments) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index b880d6749..63d0f359e 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -48,8 +48,8 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp outpath = output_dir else: outpath = opts.outdir_samples or opts.outdir_extras_samples - infotext = '' for image, name, ext in zip(image_data, image_names, image_ext): + infotext = '' if shared.state.interrupted: shared.log.debug('Postprocess interrupted') break @@ -62,10 +62,12 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp basename = os.path.splitext(os.path.basename(name))[0] else: basename = '' - infotext = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in pp.info.items() if v is not None]) _geninfo, items = images.read_info_from_image(image) for k, v in items.items(): pp.image.info[k] = v + if 'parameters' in items: + infotext = items['parameters'] + ', ' + infotext = infotext + ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in pp.info.items() if v is not None]) pp.image.info["postprocessing"] = infotext if save_output: images.save_image(pp.image, path=outpath, basename=basename, seed=None, prompt=None, extension=ext or opts.samples_format, info=infotext, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=None) @@ -73,7 +75,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp outputs.append(pp.image) devices.torch_gc() - return outputs, ui_common.plaintext_to_html(infotext), '' + return outputs, ui_common.infotext_to_html(infotext), pp.image.info def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True): #pylint: disable=unused-argument diff --git a/modules/processing.py b/modules/processing.py index 4c3cca1db..9e7e7c742 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -19,7 +19,7 @@ from installer import git_commit 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 # pylint: disable=unused-import from modules.sd_hijack import model_hijack -from modules.shared import opts, cmd_opts, state, log +from modules.shared import opts, cmd_opts, state, log, backend, Backend import modules.shared as shared import modules.paths as paths import modules.face_restoration @@ -220,7 +220,7 @@ class StableDiffusionProcessing: source_image = devices.cond_cast_float(source_image) # HACK: Using introspection as the Depth2Image model doesn't appear to uniquely # identify itself with a field common to all models. The conditioning_key is also hybrid. - if opts.sd_backend == 'Diffusers': # TODO: Diffusers img2img_image_conditioning + if backend == Backend.DIFFUSERS: # TODO: Diffusers img2img_image_conditioning return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) if isinstance(self.sd_model, LatentDepth2ImageDiffusion): return self.depth2img_image_conditioning(source_image) @@ -522,7 +522,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: assert p.prompt is not None seed = get_fixed_seed(p.seed) subseed = get_fixed_seed(p.subseed) - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: modules.sd_hijack.model_hijack.apply_circular(p.tiling) modules.sd_hijack.model_hijack.clear_comments() comments = {} @@ -573,11 +573,11 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: cache[0] = (required_prompts, steps) return cache[1] - ema_scope_context = p.sd_model.ema_scope if opts.sd_backend == 'Original' else nullcontext + ema_scope_context = p.sd_model.ema_scope if backend == Backend.ORIGINAL else nullcontext with torch.no_grad(), ema_scope_context(): with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) - if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN" and opts.sd_backend == 'Original': + if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN" and backend == Backend.ORIGINAL: sd_vae_approx.model() if state.job_count == -1: state.job_count = p.n_iter @@ -618,7 +618,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: 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) if len(model_hijack.comments) > 0: @@ -671,7 +671,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: for i, x_sample in enumerate(x_samples_ddim): p.batch_index = i - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) x_sample = x_sample.astype(np.uint8) else: diff --git a/modules/sd_models.py b/modules/sd_models.py index e33465be0..7b1444e6a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -35,7 +35,7 @@ class CheckpointInfo: self.hash = None self.filename = filename abspath = os.path.abspath(filename) - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: if shared.opts.ckpt_dir is not None and abspath.startswith(shared.opts.ckpt_dir): name = abspath.replace(shared.opts.ckpt_dir, '') elif abspath.startswith(model_path): @@ -104,7 +104,7 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() checkpoint_aliases.clear() - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: model_list = modelloader.load_models(model_path=os.path.join(models_path, 'Stable-diffusion'), model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) else: model_list = modelloader.load_diffusers(model_path=os.path.join(models_path, 'Diffusers'), command_path=shared.opts.diffusers_dir) @@ -113,7 +113,7 @@ def list_models(): if checkpoint_info.name is not None: checkpoint_info.register() if shared.cmd_opts.ckpt is not None: - if not os.path.exists(shared.cmd_opts.ckpt) and shared.opts.sd_backend == 'Original': + if not os.path.exists(shared.cmd_opts.ckpt) and shared.backend == shared.Backend.ORIGINAL: if shared.cmd_opts.ckpt.lower() != "none": shared.log.warning(f"Requested checkpoint not found: {shared.cmd_opts.ckpt}") else: @@ -227,7 +227,7 @@ def read_metadata_from_safetensors(filename): def read_state_dict(checkpoint_file, map_location=None): # pylint: disable=unused-argument - if shared.opts.sd_backend == 'Diffusers': + if shared.backend == shared.Backend.DIFFUSERS: return None try: pl_sd = None @@ -376,9 +376,9 @@ class SdModelData: if self.sd_model is None: with self.lock: try: - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: load_model() - elif shared.opts.sd_backend == 'Diffusers': + elif shared.backend == shared.Backend.DIFFUSERS: load_diffuser() else: shared.log.error(f"Unknown Stable Diffusion backend: {shared.opts.sd_backend}") @@ -429,6 +429,12 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No shared.log.info(f'Loading diffuser model: {checkpoint_info.filename}') scheduler = diffusers.UniPCMultistepScheduler.from_pretrained(checkpoint_info.filename, subfolder="scheduler") sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.filename, scheduler=scheduler, **diffusor_config) + if shared.cmd_opts.medvram: + sd_model.enable_model_cpu_offload() + if shared.cmd_opts.lowvram: + sd_model.enable_sequential_cpu_offload() + if shared.opts.cross_attention_optimization == "xFormers": + sd_model.enable_xformers_memory_efficient_attention() sd_model.sd_checkpoint_info = checkpoint_info sd_model.sd_model_checkpoint = checkpoint_info.filename sd_model.sd_model_hash = checkpoint_info.hash @@ -550,7 +556,7 @@ 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() - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) else: load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer) @@ -575,7 +581,7 @@ def unload_model_weights(sd_model=None, _info=None): from modules import sd_hijack if model_data.sd_model: model_data.sd_model.to(devices.cpu) - if shared.opts.sd_backend == 'Original': + if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_model) model_data.sd_model = None sd_model = None diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 2a13f5030..8ac3f46ef 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,8 +1,8 @@ from modules import sd_samplers_compvis, sd_samplers_kdiffusion, sd_samplers_diffusors, shared from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # pylint: disable=unused-import -from modules.shared import opts +from modules.shared import backend, Backend -if opts.sd_backend == 'Original': +if backend == Backend.ORIGINAL: all_samplers = [ *sd_samplers_kdiffusion.samplers_data_k_diffusion, *sd_samplers_compvis.samplers_data_compvis, @@ -23,7 +23,7 @@ def create_sampler(name, model): else: config = all_samplers[0] assert config is not None, f'bad sampler name: {name}' - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: sampler = config.constructor(model) sampler.config = config return sampler diff --git a/modules/shared.py b/modules/shared.py index 7050ddde7..a1fce4156 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -4,10 +4,10 @@ import time import json import datetime import urllib.request +from enum import Enum import gradio as gr import tqdm import requests -# from ldm.models.diffusion.ddpm import LatentDiffusion from modules import 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 # pylint: disable=W0611 import modules.interrogate @@ -72,6 +72,11 @@ ui_reorder_categories = [ ] +class Backend(Enum): + ORIGINAL = 1 + DIFFUSERS = 2 + + def reload_hypernetworks(): from modules.hypernetworks import hypernetwork global hypernetworks # pylint: disable=W0603 @@ -634,6 +639,13 @@ opts = Options() config_filename = cmd_opts.config opts.load(config_filename) cmd_opts = cmd_args.compatibility_args(opts, cmd_opts) +if cmd_opts.backend == 'diffusers': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Diffusers' +if cmd_opts.backend == 'original': + log.info('Overriding backend to Diffusers') + opts.data['sd_backend'] = 'Original' +backend = Backend.DIFFUSERS if opts.sd_backend == 'Diffusers' else Backend.ORIGINAL prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 137309d4d..722593525 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -207,7 +207,7 @@ class EmbeddingDatabase: continue def load_textual_inversion_embeddings(self, force_reload=False): - if shared.opts.sd_backend == 'Diffusers': # TODO Diffusers + if shared.backend == shared.Backend.DIFFUSERS: # TODO Diffusers return if not force_reload: need_reload = False diff --git a/modules/txt2img.py b/modules/txt2img.py index e2e37afc5..5b0d3309e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -3,7 +3,7 @@ from modules import sd_samplers, shared from modules.generation_parameters_copypaste import create_override_settings_dict from modules.processing import StableDiffusionProcessingTxt2Img, process_images # from modules.shared import opts, sd_model, debug -from modules.ui import plaintext_to_html +from modules.ui import plaintext_to_html, infotext_to_html from modules.memstats import memory_stats @@ -58,4 +58,4 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step p.close() generation_info_js = processed.js() shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} txt') - return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + return processed.images, generation_info_js, infotext_to_html(processed.info), plaintext_to_html(processed.comments) diff --git a/modules/ui.py b/modules/ui.py index 0e79e983d..66d01be55 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -13,7 +13,7 @@ from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_grad from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing from modules.ui_components import FormRow, FormColumn, FormGroup, ToolButton, FormHTML # pylint: disable=unused-import from modules.paths import script_path, data_path -from modules.shared import opts, cmd_opts +from modules.shared import opts, cmd_opts, backend, Backend from modules import prompt_parser import modules.codeformer_model import modules.generation_parameters_copypaste as parameters_copypaste @@ -63,6 +63,10 @@ def plaintext_to_html(text): return ui_common.plaintext_to_html(text) +def infotext_to_html(text): + return ui_common.infotext_to_html(text) + + def send_gradio_gallery_to_image(x): if len(x) == 0: return None @@ -204,7 +208,7 @@ def update_token_counter(text, steps): prompt_schedules = [[[steps, text]]] flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) prompts = [prompt_text for step, prompt_text in flat_prompts] - if opts.sd_backend == 'Original': + if backend == Backend.ORIGINAL: token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) else: tokenizer = modules.shared.sd_model.tokenizer diff --git a/modules/ui_common.py b/modules/ui_common.py index 5f51f9bf2..cfd9e6ee9 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -1,6 +1,7 @@ import json import html import os +import shutil import platform import subprocess import gradio as gr @@ -8,6 +9,7 @@ from modules import call_queue, shared from modules.generation_parameters_copypaste import image_from_url_text import modules.images + folder_symbol = '\U0001f4c2' # 📂 @@ -16,60 +18,99 @@ def update_generation_info(generation_info, html_info, img_index): generation_info = json.loads(generation_info) if img_index < 0 or img_index >= len(generation_info["infotexts"]): return html_info, gr.update() - return plaintext_to_html(generation_info["infotexts"][img_index]), gr.update() + html_text = infotext_to_html(generation_info["infotexts"][img_index]) + return html_text, gr.update() except Exception: pass - # if the json parse or anything else fails, just return the old html_info return html_info, gr.update() def plaintext_to_html(text): - text = "

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

" - return text + res = '

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

' + return res + + +def infotext_to_html(text): + res = '

Prompt: ' + html.escape(text).replace('\n', '
') + '

' + sections = res.split('Steps:') # before and after prompt+negprompt' + if len(sections) > 1: + res = sections[0] + '
Steps: ' + sections[1].strip().replace(', ', ' | ') + res = res.replace('

', '
') + return res + + +def delete_files(js_data, images, _do_make_zip, index): + try: + data = json.loads(js_data) + except Exception: + data = { 'index_of_first_image': 0 } + start_index = 0 + if index > -1 and shared.opts.save_selected_only and (index >= data['index_of_first_image']): + images = [images[index]] + start_index = index + filenames = [] + filenames = [] + fullfns = [] + for _image_index, filedata in enumerate(images, start_index): + if 'name' in filedata and os.path.isfile(filedata['name']): + fullfn = filedata['name'] + filenames.append(os.path.basename(fullfn)) + try: + os.remove(fullfn) + fullfns.append(fullfn) + shared.log.info(f"Deleting image: {fullfn}") + except Exception as e: + shared.log.error(f'Error deleting file: {fullfn} {e}') + images = [image for image in images if image['name'] not in fullfns] + return images, plaintext_to_html(f"Deleted: {filenames[0] if len(filenames) > 0 else 'none'}") def save_files(js_data, images, do_make_zip, index): - if js_data is None or len(js_data) == 0: - return - filenames = [] - fullfns = [] + os.makedirs(shared.opts.outdir_save, exist_ok=True) - #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it - class MyObject: + class MyObject: #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it def __init__(self, d=None): if d is not None: for key, value in d.items(): setattr(self, key, value) - data = json.loads(js_data) + try: + data = json.loads(js_data) + except Exception: + data = { 'index_of_first_image': 0 } p = MyObject(data) - path = shared.opts.outdir_save - save_to_dirs = shared.opts.use_save_to_dirs_for_ui - extension: str = shared.opts.samples_format start_index = 0 - if index > -1 and shared.opts.save_selected_only and (index >= data["index_of_first_image"]): # ensures we are looking at a specific non-grid picture, and we have save_selected_only + if index > -1 and shared.opts.save_selected_only and (index >= data['index_of_first_image']): # ensures we are looking at a specific non-grid picture, and we have save_selected_only # pylint: disable=no-member images = [images[index]] start_index = index - os.makedirs(shared.opts.outdir_save, exist_ok=True) + filenames = [] + fullfns = [] for image_index, filedata in enumerate(images, start_index): - image = image_from_url_text(filedata) - is_grid = image_index < p.index_of_first_image # pylint: disable=no-member - i = 0 if is_grid else (image_index - p.index_of_first_image) # pylint: disable=no-member - if len(p.all_seeds) <= i: # pylint: disable=no-member - p.all_seeds.append(p.seed) # pylint: disable=no-member - if len(p.all_prompts) <= i: # pylint: disable=no-member - p.all_prompts.append(p.prompt) # pylint: disable=no-member - fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs) # pylint: disable=no-member - if fullfn is None: - continue - filename = os.path.relpath(fullfn, path) - filenames.append(filename) - fullfns.append(fullfn) - if txt_fullfn: - filenames.append(os.path.basename(txt_fullfn)) - fullfns.append(txt_fullfn) + if 'name' in filedata and os.path.isfile(filedata['name']): + fullfn = filedata['name'] + filenames.append(os.path.basename(fullfn)) + fullfns.append(fullfn) + shutil.copy(fullfn, shared.opts.outdir_save) + shared.log.info(f"Copying image: {fullfn} -> {shared.opts.outdir_save}") + else: + image = image_from_url_text(filedata) + is_grid = image_index < p.index_of_first_image # pylint: disable=no-member + i = 0 if is_grid else (image_index - p.index_of_first_image) # pylint: disable=no-member + if len(p.all_seeds) <= i: # pylint: disable=no-member + p.all_seeds.append(p.seed) # pylint: disable=no-member + if len(p.all_prompts) <= i: # pylint: disable=no-member + p.all_prompts.append(p.prompt) # pylint: disable=no-member + fullfn, txt_fullfn = modules.images.save_image(image, shared.opts.outdir_save, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=shared.opts.samples_format, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=shared.opts.use_save_to_dirs_for_ui) # pylint: disable=no-member + if fullfn is None: + continue + filename = os.path.relpath(fullfn, shared.opts.outdir_save) + filenames.append(filename) + fullfns.append(fullfn) + if txt_fullfn: + filenames.append(os.path.basename(txt_fullfn)) + fullfns.append(txt_fullfn) if do_make_zip: - zip_filepath = os.path.join(path, "images.zip") + zip_filepath = os.path.join(shared.opts.outdir_save, "images.zip") from zipfile import ZipFile with ZipFile(zip_filepath, "w") as zip_file: for i in range(len(fullfns)): @@ -105,87 +146,47 @@ def create_output_panel(tabname, outdir): with gr.Group(elem_id=f"{tabname}_gallery_container"): result_gallery = gr.Gallery(value=['html/logo.png'], label='Output', show_label=False, elem_id=f"{tabname}_gallery").style(preview=False, container=False, columns=[1,2,3,4,5,6]) # <576px, <768px, <992px, <1200px, <1400px, >1400px - generation_info = None with gr.Column(): with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): open_folder_button = gr.Button('show', visible=not shared.cmd_opts.hide_ui_dir_config) - - if tabname != "extras": - save = gr.Button('save', elem_id=f'save_{tabname}') - save_zip = gr.Button('zip', elem_id=f'save_zip_{tabname}') - + save = gr.Button('save', elem_id=f'save_{tabname}') + save_zip = gr.Button('zip', elem_id=f'save_zip_{tabname}') + delete = gr.Button('delete', elem_id=f'delete_{tabname}') buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) - open_folder_button.click( - fn=lambda: open_folder(shared.opts.outdir_samples or outdir), - inputs=[], - outputs=[], - ) - - if tabname != "extras": - download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') - - with gr.Group(): - html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext") - html_log = gr.HTML(elem_id=f'html_log_{tabname}') - - generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}') - if tabname == 'txt2img' or tabname == 'img2img': - generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button") - generation_info_button.click( - fn=update_generation_info, - _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", - inputs=[generation_info, html_info, html_info], - outputs=[html_info, html_info], - show_progress=False, - ) - - save.click( - fn=call_queue.wrap_gradio_call(save_files), - _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", - inputs=[ - generation_info, - result_gallery, - html_info, - html_info, - ], - outputs=[ - download_files, - html_log, - ], - show_progress=False, - ) - - save_zip.click( - fn=call_queue.wrap_gradio_call(save_files), - _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", - inputs=[ - generation_info, - result_gallery, - html_info, - html_info, - ], - outputs=[ - download_files, - html_log, - ] - ) - - else: - html_info_x = gr.HTML(elem_id=f'html_info_x_{tabname}') + open_folder_button.click(fn=lambda: open_folder(shared.opts.outdir_samples or outdir), inputs=[], outputs=[]) + download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') + with gr.Group(): html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext") html_log = gr.HTML(elem_id=f'html_log_{tabname}') + generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}') + generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button") + generation_info_button.click(fn=update_generation_info, _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", show_progress=False, + inputs=[generation_info, html_info, html_info], + outputs=[html_info, html_info], + ) + save.click(fn=call_queue.wrap_gradio_call(save_files), _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", show_progress=False, + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[download_files, html_log], + ) + save_zip.click(fn=call_queue.wrap_gradio_call(save_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[download_files, html_log], + ) + delete.click(fn=call_queue.wrap_gradio_call(delete_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", + inputs=[generation_info, result_gallery, html_info, html_info], + outputs=[result_gallery, html_log], + ) - paste_field_names = [] if tabname == "txt2img": paste_field_names = modules.scripts.scripts_txt2img.paste_field_names elif tabname == "img2img": paste_field_names = modules.scripts.scripts_img2img.paste_field_names - + else: + paste_field_names = [] for paste_tabname, paste_button in buttons.items(): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery, + paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names )) - - return result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log + return result_gallery, generation_info, html_info, html_log diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 9a56d26e8..860316e49 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -3,13 +3,18 @@ from modules import scripts_postprocessing, scripts, shared, gfpgan_model, codef import modules.generation_parameters_copypaste as parameters_copypaste from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call # pylint: disable=unused-import from modules.extras import run_pnginfo +from modules.ui_common import infotext_to_html + + +def wrap_pnginfo(image): + _, geninfo, info = run_pnginfo(image) + return '', infotext_to_html(geninfo), info def submit_click(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs): - result_images, html_info_x, html_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs) - if result_images is not None and len(result_images) > 0: - _html_info, _generation_info, html_info_x = run_pnginfo(result_images[0]) - return result_images, html_info_x, html_info + + result_images, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs) + return result_images, geninfo, '{}', '' def create_ui(): @@ -37,32 +42,21 @@ def create_ui(): skip = gr.Button('Skip', elem_id=f"{id_part}_skip", variant='secondary') skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) - result_images, html_info_x, html_info, _html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) - html_info = gr.HTML(elem_id="pnginfo_html_info") - generation_info = gr.Textbox(elem_id="pnginfo_generation_info", label="Parameters", visible=False) - generation_info_pretty = gr.Textbox(elem_id="pnginfo_generation_info_pretty", label="Parameters") - gr.HTML('Full metadata') - html2_info = gr.HTML(elem_id="pnginfo_html2_info") + result_images, generation_info, html_info, html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) + gr.HTML('File metadata') + exif_info = gr.HTML(elem_id="pnginfo_html_info") for tabname, button in buttons.items(): - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=generation_info, source_image_component=extras_image)) - - def pretty_geninfo(generation_info: str): - if generation_info is None: - return '' - sections = generation_info.split('Steps:') - if len(sections) > 1: - param = sections[0].strip() + '\nSteps:' + sections[1].strip().replace(', ', '\n') - return param - return generation_info + parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=html_info, source_image_component=extras_image)) tab_single.select(fn=lambda: 0, inputs=[], outputs=[tab_index]) tab_batch.select(fn=lambda: 1, inputs=[], outputs=[tab_index]) tab_batch_dir.select(fn=lambda: 2, inputs=[], outputs=[tab_index]) - generation_info.change(fn=pretty_geninfo, inputs=[generation_info], outputs=[generation_info_pretty]) + # html_info.change(fn=pretty_geninfo, inputs=[html_info], outputs=[html_info_pretty]) + _dummy = gr.HTML(visible=False) extras_image.change( - fn=wrap_gradio_call(run_pnginfo), + fn=wrap_gradio_call(wrap_pnginfo), inputs=[extras_image], - outputs=[html_info, generation_info, html2_info], + outputs=[_dummy, html_info, exif_info], ) submit.click( fn=call_queue.wrap_gradio_gpu_call(submit_click, extra_outputs=[None, '']), @@ -73,12 +67,13 @@ def create_ui(): extras_batch_input_dir, extras_batch_output_dir, show_extras_results, - *script_inputs + *script_inputs, ], outputs=[ result_images, - html_info_x, html_info, + generation_info, + html_log, ] ) From 8354b7c6d95c8c447ad7179ed5d7d63e1a02bf34 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 29 May 2023 15:42:24 -0400 Subject: [PATCH 10/11] style changes --- CHANGELOG.md | 4 +++- javascript/black-orange.css | 6 +++--- javascript/style.css | 6 ++++++ modules/ui.py | 12 ++++++------ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c38970d..a463e9d35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ ## Update for 05/28/2023 - settings search option -- system info live gpu memory and load graphs +- fully common save/zip/delete (new) options in all tabs +- system info live gpu memory and load graphs for nvidia gpus +- minor style changes ## Update for 05/26/2023 diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 69bd91674..7a18eeecf 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -81,12 +81,12 @@ svg.feather.feather-image, .feather .feather-image { display: none } #quicksettings > div, #quicksettings > fieldset { min-width: 24em; max-width: 26em; line-height: 2em; } #refresh_sd_model_checkpoint { height: 48px; margin-left: -14px; background: #333333; box-shadow: none; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } -#refresh_txt2img_styles, #refresh_img2img_styles { height: 40px; } +#refresh_txt2img_styles, #refresh_img2img_styles { height: 40px; margin-left: -8px; } #save-animation { border-radius: 0 !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } #settings > div.flex-wrap { width: 15em; } #tab_extensions table { background-color: #222222; } -#txt2img_actions_column, #img2img_actions_column { min-width: 260px !important; max-width: 260px !important; } +#txt2img_actions_column, #img2img_actions_column { min-width: 280px !important; max-width: 280px !important; gap: 0.6em } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } @@ -102,7 +102,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_subseed_row { padding: 0; margin-top: 16px; } #txt2img_subseed_show, #img2img_subseed_show { display: None } #txt2img_subseed_strength { margin-top: 0; } -#txt2img_tools, #img2img_tools { margin-top: 54px; scale: 120%; margin-left: 26px; } +#txt2img_tools, #img2img_tools { margin-top: 54px; scale: 120%; margin-left: 40px; filter: hue-rotate(180deg) saturate(0.5); } #txtimg_hr_finalres { max-width: 200px; } #pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) } #txt2img_extra_refresh, #txt2img_extra_close { height: 1.7em; } diff --git a/javascript/style.css b/javascript/style.css index f060604cc..f11bd9d31 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -115,6 +115,8 @@ a{ #txt2img_gallery img, #img2img_gallery img, #extras_gallery img{ object-fit: scale-down; + width: -webkit-fill-available !important; + height: -webkit-fill-available !important; } #txt2img_actions_column, #img2img_actions_column { gap: 0.5em; @@ -677,3 +679,7 @@ footer { #extras_upscale { margin-top: 10px } #modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } + +.thumbnail-item > img { + +} \ No newline at end of file diff --git a/modules/ui.py b/modules/ui.py index 66d01be55..e6dfc2fb3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -50,12 +50,12 @@ sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None # Important that they exactly match script.js for tooltip to work. random_symbol = '\U0001f3b2\ufe0f' # 🎲️ reuse_symbol = '\u267b\ufe0f' # ♻️ -paste_symbol = '\u2199\ufe0f' # ↙ -refresh_symbol = '\U0001f504' # 🔄 -save_style_symbol = '\U0001f4be' # 💾 -apply_style_symbol = '\U0001f4cb' # 📋 -clear_prompt_symbol = '\U0001f5d1\ufe0f' # 🗑️ -extra_networks_symbol = '\U0001F3B4' # 🎴 +paste_symbol = '\U0001F4D8' # '\u2199\ufe0f' # ↙ +refresh_symbol = '\U0001F504' # 🔄 +save_style_symbol = '\U0001F6C5' # '\U0001f4be' # 💾 +apply_style_symbol = '\U0001F9F3' # '\U0001f4cb' # 📋 +clear_prompt_symbol = '\U0001F6AE' # '\U0001f5d1\ufe0f' # 🗑️ +extra_networks_symbol = '\U0001F310' # '\U0001F3B4' # 🎴 switch_values_symbol = '\U000021C5' # ⇅ From 24bbe045a77e040b99908876ba0c7783c992a22d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 29 May 2023 20:55:33 -0400 Subject: [PATCH 11/11] fix paste --- javascript/black-orange.css | 2 +- javascript/style.css | 3 ++- modules/generation_parameters_copypaste.py | 2 ++ modules/ui_common.py | 7 +++---- modules/ui_postprocessing.py | 8 ++++---- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 7a18eeecf..1d8e5523b 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -81,7 +81,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } #quicksettings > div, #quicksettings > fieldset { min-width: 24em; max-width: 26em; line-height: 2em; } #refresh_sd_model_checkpoint { height: 48px; margin-left: -14px; background: #333333; box-shadow: none; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } -#refresh_txt2img_styles, #refresh_img2img_styles { height: 40px; margin-left: -8px; } +#refresh_txt2img_styles, #refresh_img2img_styles { height: 45px; margin-left: -8px; } #save-animation { border-radius: 0 !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } #settings > div.flex-wrap { width: 15em; } diff --git a/javascript/style.css b/javascript/style.css index f11bd9d31..02781546f 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -92,6 +92,7 @@ button.custom-button{ .performance p{ display: inline-block; + color: var(--primary-100) !important } .performance .time { @@ -116,7 +117,7 @@ a{ #txt2img_gallery img, #img2img_gallery img, #extras_gallery img{ object-fit: scale-down; width: -webkit-fill-available !important; - height: -webkit-fill-available !important; + height: inherit !important; } #txt2img_actions_column, #img2img_actions_column { gap: 0.5em; diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 1fb8d159a..1ea0e24cb 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -350,7 +350,9 @@ def create_override_settings_dict(text_pairs): def connect_paste(button, local_paste_fields, input_comp, override_settings_component, tabname): + def paste_func(prompt): + shared.log.debug(f'paste prompt: {prompt}') if prompt is not None and 'Negative prompt' not in prompt and 'Steps' not in prompt: prompt = None if not prompt and not shared.cmd_opts.hide_ui_dir_config: diff --git a/modules/ui_common.py b/modules/ui_common.py index cfd9e6ee9..e5582086d 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -31,7 +31,7 @@ def plaintext_to_html(text): def infotext_to_html(text): - res = '

Prompt: ' + html.escape(text).replace('\n', '
') + '

' + res = '

Prompt: ' + html.escape(text or '').replace('\n', '
') + '

' sections = res.split('Steps:') # before and after prompt+negprompt' if len(sections) > 1: res = sections[0] + '
Steps: ' + sections[1].strip().replace(', ', ' | ') @@ -86,7 +86,7 @@ def save_files(js_data, images, do_make_zip, index): filenames = [] fullfns = [] for image_index, filedata in enumerate(images, start_index): - if 'name' in filedata and os.path.isfile(filedata['name']): + if 'name' in filedata and ('tmp' not in filedata['name']) and os.path.isfile(filedata['name']): fullfn = filedata['name'] filenames.append(os.path.basename(fullfn)) fullfns.append(fullfn) @@ -186,7 +186,6 @@ def create_output_panel(tabname, outdir): paste_field_names = [] for paste_tabname, paste_button in buttons.items(): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, - paste_field_names=paste_field_names + paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names )) return result_gallery, generation_info, html_info, html_log diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 860316e49..bd928e277 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -8,7 +8,7 @@ from modules.ui_common import infotext_to_html def wrap_pnginfo(image): _, geninfo, info = run_pnginfo(image) - return '', infotext_to_html(geninfo), info + return '', infotext_to_html(geninfo), info, geninfo def submit_click(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs): @@ -45,18 +45,18 @@ def create_ui(): result_images, generation_info, html_info, html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) gr.HTML('File metadata') exif_info = gr.HTML(elem_id="pnginfo_html_info") + gen_info = gr.Text(elem_id="pnginfo_gen_info", visible=False) for tabname, button in buttons.items(): - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=html_info, source_image_component=extras_image)) + parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=gen_info, source_image_component=extras_image)) tab_single.select(fn=lambda: 0, inputs=[], outputs=[tab_index]) tab_batch.select(fn=lambda: 1, inputs=[], outputs=[tab_index]) tab_batch_dir.select(fn=lambda: 2, inputs=[], outputs=[tab_index]) - # html_info.change(fn=pretty_geninfo, inputs=[html_info], outputs=[html_info_pretty]) _dummy = gr.HTML(visible=False) extras_image.change( fn=wrap_gradio_call(wrap_pnginfo), inputs=[extras_image], - outputs=[_dummy, html_info, exif_info], + outputs=[_dummy, html_info, exif_info, gen_info], ) submit.click( fn=call_queue.wrap_gradio_gpu_call(submit_click, extra_outputs=[None, '']),