diff --git a/CHANGELOG.md b/CHANGELOG.md index e0cc8e74a..d15bd20b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Add HAT upscaler - Switch Diffusers prompt parser -This is a big one, with some major changes and new functionality... +This is a big one, with some major changes and new functionality... And probably the biggest release since introduction of **Diffusers** Note that for this release its recommended to perform a clean install (fresh `git clone`) @@ -71,14 +71,15 @@ Upgrades are still possible and supported, but above is recommended for best exp thanks @AI-Casanova - better **Lora** handling thanks @AI-Casanova - - new setting: *settings -> diffusers -> force inpaint* + - new setting: *settings -> diffusers -> force inpaint* as some models behave better when in *inpaint* mode even for normal *img2img* tasks - **Upscalers**: + - pretty much a rewrite and tons of new upscalers - built-in list is now at **42** - fix long outstanding memory leak in legacy code, amazing this went undetected for so long - more high quality upscalers available by default - *SwinIR:2, ESRGAN:12, RealESRGAN:6, SCUNet:2* + **SwinIR** (2), **ESRGAN** (12), **RealESRGAN** (6), **SCUNet** (2) - two additional latent upscalers based on SD upscale models when using Diffusers backend - *SD Upscale 2x, SD Upscale 4x* + **SD Upscale 2x**, **SD Upscale 4x*** Note: Recommended usage for *SD Upscale* is by using second pass instead of upscaler as it allows for tuning of prompt, seed, sampler settings which are used to guide upscaler - upscalers are available in **xyz grid** @@ -88,6 +89,12 @@ Upgrades are still possible and supported, but above is recommended for best exp simply set *denoising strength* to 0 so hires does not get triggered - unified init/download/execute/progress code - easier installation + - and if that is not enough, install extension: + + and it will add 15 more upscalers from different families: + **HAT** (6), **RealHAT** (2), **DAT** (1), **RRDBNet** (1), **SPSRNet** (1), **SRFormer** (2), **SwiftSR** (2) + and yes, you can download and add your own, just place them in `models/chaiNNer` + note: extension will probably be added to default built-in list in the near-future - **Samplers**: - moved ui options to submenu - default list for new installs is now all samplers, list can be modified in settings diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 9f95e6d48..08fc2647f 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 9f95e6d4812ca7acb3ec56cbf45fbc85aef0236d +Subproject commit 08fc2647f1fe413699612df923b5f495d26853ef diff --git a/html/upscalers.json b/html/upscalers.json index eb1abf7f1..373ff3c75 100644 --- a/html/upscalers.json +++ b/html/upscalers.json @@ -5,17 +5,17 @@ ], "ESRGAN": [ ["4x GAN", "https://github.com/cszn/KAIR/releases/download/v1.0/ESRGAN.pth"], - ["4x Ultrasharp", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x-UltraSharp.pth"], - ["4x Valar", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_Valar_v1.pth"], - ["4x Box", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4xBox.pth"], - ["4x BigFace V3", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_BigFace_v3.pth"], - ["4x Remacri", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_foolhardy_Remacri.pth"], - ["4x NMKD Siax", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_NMKD-Siax_200k.pth"], - ["4x NMKD Superscale", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_NMKD-Superscale-SP_178000_G.pth"], - ["4x NMKD YandereNeo", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/4x_NMKD-YandereNeoXL_200k.pth"], + ["4x Ultrasharp", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-UltraSharp-4x.pth"], + ["4x Valar", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-Valar-4x.pth"], + ["4x Box", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-Box-4x.pth"], + ["4x BigFace V3", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-BigFace-v3-4x.pth"], + ["4x Remacri", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-Remacri-4x.pth"], + ["4x NMKD Siax", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-NMKD-Siax-4x.pth"], + ["4x NMKD Superscale", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-NMKD-Superscale-4x.pth"], + ["4x NMKD YandereNeoXL", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-NMKD-YandereNeoXL-4x.pth"], ["8x NMKD Faces", "https://huggingface.co/Zabin/Resizers/resolve/main/8x_NMKD-Faces_160000_G.pth"], - ["8x Superscale", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/8x_NMKD-Superscale_150000_G.pth"], - ["8x HugePaint", "https://huggingface.co/Aptronym/SDNext/resolve/main/Upscalers/8x_HugePaint_v1.pth"] + ["8x Superscale", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-NMKD-Superscale-8x.pth"], + ["8x HugePaint", "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/ESRGAN-HugePaint-8x.pth"] ], "RealESRGAN": [ ["4x General V3", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth"], diff --git a/installer.py b/installer.py index 7f9f113af..fe4690565 100644 --- a/installer.py +++ b/installer.py @@ -692,6 +692,10 @@ def ensure_base_requirements(): import rich # pylint: disable=unused-import except ImportError: install('rich', 'rich') + try: + import rich # pylint: disable=unused-import + except ImportError: + pass def install_requirements(): diff --git a/modules/modelloader.py b/modules/modelloader.py index 3e5843d73..12d1c87bd 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -527,6 +527,7 @@ def load_upscalers(): classname = str(cls) if classname not in used_classes: used_classes[classname] = cls + names = [] for cls in reversed(used_classes.values()): name = cls.__name__ cmd_name = f"{name.lower().replace('upscaler', '')}_models_path" @@ -535,8 +536,6 @@ def load_upscalers(): scaler.user_path = commandline_model_path scaler.model_download_path = commandline_model_path or scaler.model_path datas += scaler.scalers - shared.sd_upscalers = sorted( - datas, - key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" # Special case for UpscalerNone keeps it at the beginning of the list. - ) - shared.log.debug(f"Loaded upscalers: total={len(shared.sd_upscalers)} downloaded={len([x for x in shared.sd_upscalers if x.data_path is not None and os.path.isfile(x.data_path)])} user={len([x for x in shared.sd_upscalers if x.custom])}") + names.append(name[8:]) + shared.sd_upscalers = sorted(datas, key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "") # Special case for UpscalerNone keeps it at the beginning of the list. + shared.log.debug(f"Loaded upscalers: total={len(shared.sd_upscalers)} downloaded={len([x for x in shared.sd_upscalers if x.data_path is not None and os.path.isfile(x.data_path)])} user={len([x for x in shared.sd_upscalers if x.custom])} {names}") diff --git a/modules/postprocess/scunet_model.py b/modules/postprocess/scunet_model.py index dcb8ff496..83b492ef4 100644 --- a/modules/postprocess/scunet_model.py +++ b/modules/postprocess/scunet_model.py @@ -2,7 +2,7 @@ import PIL.Image import numpy as np import torch from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn -from modules import devices, script_callbacks +from modules import devices from modules.postprocess.scunet_model_arch import SCUNet as net from modules.shared import opts, log, console, device from modules.upscaler import Upscaler @@ -67,7 +67,7 @@ class UpscalerSCUNet(Upscaler): output = E.div_(W) return output - def do_upscale(self, img: PIL.Image.Image, selected_file): # pylint: disable=arguments-renamed + def do_upscale(self, img: PIL.Image.Image, selected_file): devices.torch_gc() model = self.load_model(selected_file) if model is None: diff --git a/modules/postprocess/sdupscaler4_model.py b/modules/postprocess/sdupscaler_model.py similarity index 98% rename from modules/postprocess/sdupscaler4_model.py rename to modules/postprocess/sdupscaler_model.py index 27b844dc5..f4c069925 100644 --- a/modules/postprocess/sdupscaler4_model.py +++ b/modules/postprocess/sdupscaler_model.py @@ -6,7 +6,7 @@ from modules.upscaler import Upscaler, UpscalerData class UpscalerSD(Upscaler): def __init__(self, dirname): # pylint: disable=super-init-not-called - self.name = "StableDiffusion" + self.name = "SDUpscale" self.user_path = dirname if shared.backend != shared.Backend.DIFFUSERS: super().__init__() diff --git a/modules/script_loading.py b/modules/script_loading.py index 61f08527a..5ac82f538 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -13,13 +13,16 @@ 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: - # stdout = io.StringIO() - with contextlib.redirect_stdout(io.StringIO()) as stdout: + if '/sd-extension-' in path: # safe extensions without stdout intercept module_spec.loader.exec_module(module) - setup_logging() # reset since scripts can hijaack logging - for line in stdout.getvalue().splitlines(): - if len(line) > 0: - errors.log.info(f"Extension: script='{os.path.relpath(path)}' {line.strip()}") + else: + # stdout = io.StringIO() + with contextlib.redirect_stdout(io.StringIO()) as stdout: + module_spec.loader.exec_module(module) + setup_logging() # reset since scripts can hijaack logging + for line in stdout.getvalue().splitlines(): + if len(line) > 0: + errors.log.info(f"Extension: script='{os.path.relpath(path)}' {line.strip()}") except Exception as e: errors.display(e, f'Module load: {path}') return module diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 5b36d6740..0427b040a 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -97,23 +97,26 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): def list_items(self): for k, style in shared.prompt_styles.styles.items(): - fn = os.path.splitext(style.filename)[0] - txt = f'Prompt: {style.prompt}' - if len(style.negative_prompt) > 0: + fn = os.path.splitext(getattr(style, 'filename', ''))[0] + name = getattr(style, 'name', '') + if name == '': + continue + txt = f'Prompt: {getattr(style, "prompt", "")}' + if len(getattr(style, 'negative_prompt', '')) > 0: txt += f'\nNegative: {style.negative_prompt}' yield { "type": 'Style', - "name": style.name, + "name": name, "title": k, "filename": style.filename, - "search_term": f'{txt} {self.search_terms_from_path(style.name)}', - "preview": style.preview if style.preview is not None and style.preview.startswith('data:') else self.find_preview(fn), - "description": style.description if style.description is not None and len(style.description) > 0 else txt, - "prompt": style.prompt or '', - "negative": style.negative_prompt or '', - "extra": style.extra or '', + "search_term": f'{txt} {self.search_terms_from_path(name)}', + "preview": style.preview if getattr(style, 'preview', None) is not None and style.preview.startswith('data:') else self.find_preview(fn), + "description": style.description if getattr(style, 'description', None) is not None and len(style.description) > 0 else txt, + "prompt": getattr(style, 'prompt', ''), + "negative": getattr(style, 'negative_prompt', ''), + "extra": getattr(style, 'extra', ''), "local_preview": f"{fn}.{shared.opts.samples_format}", - "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(style.name)})""") + '"', + "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(name)})""") + '"', } def allowed_directories_for_previews(self): diff --git a/webui.py b/webui.py index 42bf13713..0c8335058 100644 --- a/webui.py +++ b/webui.py @@ -304,6 +304,7 @@ def webui(restart=False): load_model() shared.opts.save(shared.config_filename) log.info(f"Startup time: {timer.startup.summary()}") + timer.startup.reset() if not restart: # override all loggers to use the same handlers as the main logger