diff --git a/CHANGELOG.md b/CHANGELOG.md index 540075581..cfd929c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,9 +72,14 @@ Upgrades are still possible and supported, but above is recommended for best exp description file present in format of *[model].txt* - to enable search, make sure all models have set hash values *Models -> Valida -> Calculate hashes* -- **Intel Arc/IPEX**: - - more optimizations, built-in binary wheels for Windows - thanks @Disty0 @Nuullll +- **Compute** + - **Intel Arc/IPEX**: + - more optimizations, built-in binary wheels for Windows + thanks @Disty0 @Nuullll + - **AMD ROCm**: + - updated installer to support detect `ROCm` *5.4/5.5/5.6/5.7* + - **CUDA**: + - testing moved to `torch` *2.2.0-dev/cu121* - **Startup** - All main CLI parameters can now be set as environment variable as well for example `--data-dir ` can be specified as `SD_DATADIR=` before starting SD.Next diff --git a/installer.py b/installer.py index 3001f0357..f18754e69 100644 --- a/installer.py +++ b/installer.py @@ -397,6 +397,9 @@ def check_torch(): if rocm_ver in ['5.5', '5.6']: # install torch nightly via torchvision to avoid wasting bandwidth when torchvision depends on torch from yesterday torch_command = os.environ.get('TORCH_COMMAND', f'torchvision --pre --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') + elif rocm_ver in ['5.7']: + # there is no torch nightly for rocm 5.7 yet + torch_command = os.environ.get('TORCH_COMMAND', f'torchvision --pre --index-url https://download.pytorch.org/whl/nightly/rocm5.6') else: torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/rocm5.4.2') xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') diff --git a/modules/postprocess/codeformer_model.py b/modules/postprocess/codeformer_model.py index 5f274b089..e11747018 100644 --- a/modules/postprocess/codeformer_model.py +++ b/modules/postprocess/codeformer_model.py @@ -45,7 +45,6 @@ def setup_model(dirname): self.cmd_dir = dirname def create_models(self): - if self.net is not None and self.face_helper is not None: self.net.to(devices.device_codeformer) return self.net, self.face_helper @@ -60,14 +59,11 @@ def setup_model(dirname): net.load_state_dict(checkpoint) net.eval() shared.log.info(f"Model loaded: type=CodeFormer model={ckpt_path}") - if hasattr(retinaface, 'device'): retinaface.device = devices.device_codeformer face_helper = FaceRestoreHelper(1, face_size=512, crop_ratio=(1, 1), det_model='retinaface_resnet50', save_ext='png', use_parse=True, device=devices.device_codeformer) - self.net = net self.face_helper = face_helper - return net, face_helper def send_model_to(self, device): @@ -77,25 +73,19 @@ def setup_model(dirname): def restore(self, np_image, w=None): np_image = np_image[:, :, ::-1] - original_resolution = np_image.shape[0:2] - self.create_models() if self.net is None or self.face_helper is None: return np_image - self.send_model_to(devices.device_codeformer) - self.face_helper.clean_all() self.face_helper.read_image(np_image) self.face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5) self.face_helper.align_warp_face() - for cropped_face in self.face_helper.cropped_faces: cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True) normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer) - try: with devices.inference_context(): output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0] @@ -105,33 +95,23 @@ def setup_model(dirname): except Exception as e: shared.log.error(f'CodeForomer error: {e}') restored_face = tensor2img(cropped_face_t, rgb2bgr=True, min_max=(-1, 1)) - restored_face = restored_face.astype('uint8') self.face_helper.add_restored_face(restored_face) - self.face_helper.get_inverse_affine(None) - restored_img = self.face_helper.paste_faces_to_input_image() restored_img = restored_img[:, :, ::-1] - if original_resolution != restored_img.shape[0:2]: restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR) - self.face_helper.clean_all() - if shared.opts.face_restoration_unload: self.send_model_to(devices.cpu) - return restored_img global have_codeformer # pylint: disable=global-statement have_codeformer = True - global codeformer # pylint: disable=global-statement codeformer = FaceRestorerCodeFormer(dirname) shared.face_restorers.append(codeformer) except Exception as e: errors.display(e, 'codeformer') - - # sys.path = stored_sys_path diff --git a/modules/postprocess/esrgan_model.py b/modules/postprocess/esrgan_model.py index 3532ed4fa..8f1291b13 100644 --- a/modules/postprocess/esrgan_model.py +++ b/modules/postprocess/esrgan_model.py @@ -4,7 +4,7 @@ from PIL import Image from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn import modules.postprocess.esrgan_model_arch as arch from modules import images, devices -from modules.upscaler import Upscaler +from modules.upscaler import Upscaler, UpscalerData from modules.shared import opts, log, console @@ -122,7 +122,7 @@ class UpscalerESRGAN(Upscaler): self.user_path = dirname super().__init__() self.scalers = self.find_scalers() - + self.models = {} def do_upscale(self, img, selected_model): model = self.load_model(selected_model) @@ -130,12 +130,19 @@ class UpscalerESRGAN(Upscaler): return img model.to(devices.device_esrgan) img = esrgan_upscale(model, img) + if opts.upscaler_unload and selected_model in self.models: + del self.models[selected_model] + log.debug(f"Upscaler unloaded: type={self.name} model={selected_model}") + devices.torch_gc(force=True) return img def load_model(self, path: str): - info = self.find_model(path) + info: UpscalerData = self.find_model(path) if info is None: return + if self.models.get(info.local_data_path, None) is not None: + log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}") + return self.models[info.local_data_path] state_dict = torch.load(info.local_data_path, map_location='cpu' if devices.device_esrgan.type == 'mps' else None) log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path}") @@ -147,7 +154,9 @@ class UpscalerESRGAN(Upscaler): model = arch.SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=num_conv, upscale=4, act_type='prelu') model.load_state_dict(state_dict) model.eval() - return model + self.models[info.local_data_path] = model + return self.models[info.local_data_path] + if "body.0.rdb1.conv1.weight" in state_dict and "conv_first.weight" in state_dict: nb = 6 if "RealESRGAN_x4plus_anime_6B" in info.local_data_path else 23 state_dict = resrgan2normal(state_dict, nb) @@ -155,14 +164,12 @@ class UpscalerESRGAN(Upscaler): state_dict = mod2normal(state_dict) elif "model.0.weight" not in state_dict: raise TypeError("The file is not a recognized ESRGAN model.") - in_nc, out_nc, nf, nb, plus, mscale = infer_params(state_dict) - model = arch.RRDBNet(in_nc=in_nc, out_nc=out_nc, nf=nf, nb=nb, upscale=mscale, plus=plus) model.load_state_dict(state_dict) model.eval() - - return model + self.models[info.local_data_path] = model + return self.models[info.local_data_path] def upscale_without_tiling(model, img): diff --git a/modules/postprocess/realesrgan_model.py b/modules/postprocess/realesrgan_model.py index 0646ce339..92cf375d2 100644 --- a/modules/postprocess/realesrgan_model.py +++ b/modules/postprocess/realesrgan_model.py @@ -5,7 +5,7 @@ from basicsr.archs.rrdbnet_arch import RRDBNet from modules.postprocess.realesrgan_model_arch import SRVGGNetCompact from modules.upscaler import Upscaler from modules.shared import opts, device, log - +from modules import devices class UpscalerRealESRGAN(Upscaler): def __init__(self, dirname): @@ -13,6 +13,7 @@ class UpscalerRealESRGAN(Upscaler): self.user_path = dirname super().__init__() self.scalers = self.find_scalers() + self.models = {} for scaler in self.scalers: if scaler.name == 'RealESRGAN 2x+': scaler.model = lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2) @@ -41,22 +42,29 @@ class UpscalerRealESRGAN(Upscaler): except Exception: log.error("Error importing Real-ESRGAN:") return img - info = self.find_model(selected_model) if info is None or not os.path.exists(info.local_data_path): return img - - upsampler = RealESRGANer( - scale=info.scale, - model_path=info.local_data_path, - model=info.model(), - half=not opts.no_half and not opts.upcast_sampling, - tile=opts.ESRGAN_tile, - tile_pad=opts.ESRGAN_tile_overlap, - device=device, - ) - + if self.models.get(info.local_data_path, None) is not None: + log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}") + upsampler=self.models[info.local_data_path] + else: + upsampler = RealESRGANer( + name=info.name, + scale=info.scale, + model_path=info.local_data_path, + model=info.model(), + half=not opts.no_half and not opts.upcast_sampling, + tile=opts.ESRGAN_tile, + tile_pad=opts.ESRGAN_tile_overlap, + device=device, + ) + self.models[info.local_data_path] = upsampler upsampled = upsampler.enhance(np.array(img), outscale=info.scale)[0] + if opts.upscaler_unload and info.local_data_path in self.models: + del self.models[info.local_data_path] + log.debug(f"Upscaler unloaded: type={self.name} model={selected_model}") + devices.torch_gc(force=True) image = Image.fromarray(upsampled) return image diff --git a/modules/postprocess/realesrgan_model_arch.py b/modules/postprocess/realesrgan_model_arch.py index b0da8d522..fa290d5f9 100644 --- a/modules/postprocess/realesrgan_model_arch.py +++ b/modules/postprocess/realesrgan_model_arch.py @@ -29,6 +29,7 @@ class RealESRGANer(): """ def __init__(self, + name, scale, model_path, dni_weight=None, @@ -39,6 +40,7 @@ class RealESRGANer(): half=False, device=None, gpu_id=None): + self.name = name self.scale = scale self.tile_size = tile self.tile_pad = tile_pad @@ -63,6 +65,7 @@ class RealESRGANer(): from modules.modelloader import load_file_from_url model_path = load_file_from_url(url=model_path, model_dir=os.path.join(ROOT_DIR, 'weights'), progress=True, file_name=None) loadnet = torch.load(model_path, map_location=torch.device('cpu')) + log.info(f"Upscaler loaded: type={self.name} model={model_path}") # prefer to use params_ema if 'params_ema' in loadnet: diff --git a/modules/postprocess/scunet_model.py b/modules/postprocess/scunet_model.py index b266547e2..3b21006af 100644 --- a/modules/postprocess/scunet_model.py +++ b/modules/postprocess/scunet_model.py @@ -14,18 +14,24 @@ class UpscalerSCUNet(Upscaler): self.user_path = dirname super().__init__() self.scalers = self.find_scalers() + self.models = {} def load_model(self, path: str): info = self.find_model(path) if info is None: return - model = net(in_nc=3, config=[4, 4, 4, 4, 4, 4, 4], dim=64) - model.load_state_dict(torch.load(info.local_data_path), strict=True) - model.eval() - log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path}") - for _, v in model.named_parameters(): - v.requires_grad = False - model = model.to(device) + if self.models.get(info.local_data_path, None) is not None: + log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}") + model=self.models[info.local_data_path] + else: + model = net(in_nc=3, config=[4, 4, 4, 4, 4, 4, 4], dim=64) + model.load_state_dict(torch.load(info.local_data_path), strict=True) + model.eval() + log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path}") + for _, v in model.named_parameters(): + v.requires_grad = False + model = model.to(device) + self.models[info.local_data_path] = model return model @staticmethod @@ -83,7 +89,12 @@ class UpscalerSCUNet(Upscaler): devices.torch_gc() output = np_output.transpose((1, 2, 0)) # CHW to HWC output = output[:, :, ::-1] # BGR to RGB - return PIL.Image.fromarray((output * 255).astype(np.uint8)) + img = PIL.Image.fromarray((output * 255).astype(np.uint8)) + if opts.upscaler_unload and selected_file in self.models: + del self.models[selected_file] + log.debug(f"Upscaler unloaded: type={self.name} model={selected_file}") + devices.torch_gc(force=True) + return img def on_ui_settings(): diff --git a/modules/postprocess/sdupscaler4_model.py b/modules/postprocess/sdupscaler4_model.py index 574f02cf1..27b844dc5 100644 --- a/modules/postprocess/sdupscaler4_model.py +++ b/modules/postprocess/sdupscaler4_model.py @@ -19,17 +19,22 @@ class UpscalerSD(Upscaler): None, None, ] + self.models = {} def load_model(self, path: str): from modules.sd_models import set_diffuser_options - scaler = [x for x in self.scalers if x.data_path == path][0] - if scaler.model is None: + scaler: UpscalerData = [x for x in self.scalers if x.data_path == path][0] + if self.models.get(path, None) is not None: + shared.log.debug(f"Upscaler cached: type={scaler.name} model={path}") + return self.models[path] + else: devices.set_cuda_params() - scaler.model = diffusers.DiffusionPipeline.from_pretrained(path, cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype) - if hasattr(scaler.model, "set_progress_bar_config"): - scaler.model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + 'Upscale', ncols=80, colour='#327fba') + model = diffusers.DiffusionPipeline.from_pretrained(path, cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype) + if hasattr(model, "set_progress_bar_config"): + model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + 'Upscale', ncols=80, colour='#327fba') set_diffuser_options(scaler.model, vae=None, op='upscaler') - return scaler.model + self.models[path] = model + return self.models[path] def callback(self, _step: int, _timestep: int, _latents: torch.FloatTensor): pass @@ -61,4 +66,8 @@ class UpscalerSD(Upscaler): model = model.to(devices.device) output = model(**args) image = output.images[0] + if shared.opts.upscaler_unload and selected_model in self.models: + del self.models[selected_model] + shared.log.debug(f"Upscaler unloaded: type={self.name} model={selected_model}") + devices.torch_gc(force=True) return image diff --git a/modules/postprocess/swinir_model.py b/modules/postprocess/swinir_model.py index 712e59dd7..2ccfab042 100644 --- a/modules/postprocess/swinir_model.py +++ b/modules/postprocess/swinir_model.py @@ -14,11 +14,15 @@ class UpscalerSwinIR(Upscaler): self.user_path = dirname super().__init__() self.scalers = self.find_scalers() + self.models = {} def load_model(self, path, scale=4): info = self.find_model(path) if info is None: return + if self.models.get(info.local_data_path, None) is not None: + shared.log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}") + return self.models[info.local_data_path] pretrained_model = torch.load(info.local_data_path) model_v2 = net2( upscale=scale, @@ -54,6 +58,7 @@ class UpscalerSwinIR(Upscaler): else: model.load_state_dict(pretrained_model, strict=True) shared.log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path} param={param}") + self.models[info.local_data_path] = model return model except Exception as e: shared.log.error(f'Upscaler invalid parameters: type={self.name} model={info.local_data_path} {e}') @@ -65,7 +70,10 @@ class UpscalerSwinIR(Upscaler): return img model = model.to(shared.device, dtype=devices.dtype) img = upscale(img, model) - devices.torch_gc() + if shared.opts.upscaler_unload and selected_model in self.models: + del self.models[selected_model] + shared.log.debug(f"Upscaler unloaded: type={self.name} model={selected_model}") + devices.torch_gc(force=True) return img diff --git a/modules/shared.py b/modules/shared.py index 978e5197f..4470f757c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -613,7 +613,8 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"), "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), - 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), + "upscaler_unload": OptionInfo(False, "Unload upscaler after processing"), + 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": False}), "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), # "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 99e0109eb..512a6f1e6 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -128,52 +128,68 @@ class EmbeddingDatabase: if ext.lower() != ".pt" and ext.lower() != ".safetensors": return pipe = shared.sd_model - if filename == "": - pipe.tokenizer = pipe.tokenizer.__class__.from_pretrained(pipe.tokenizer.name_or_path) - pipe.text_encoder.resize_token_embeddings(len(pipe.tokenizer)) - return name = os.path.basename(fn) embedding = Embedding(vec=None, name=name, filename=path) + if not hasattr(pipe, "tokenizer") or not hasattr(pipe, 'text_encoder'): + self.skipped_embeddings[name] = embedding + return try: - done = False - if hasattr(pipe,"load_textual_inversion"): - try: - token_ids = pipe.tokenizer.convert_tokens_to_ids(name) - if token_ids > 49407: # already loaded - done = True - else: - pipe.load_textual_inversion(path, token=name, cache_dir=shared.opts.diffusers_dir, local_files_only=True) - done = True + is_xl = hasattr(pipe, 'text_encoder_2') + try: + if not is_xl: # only use for sd15/sd21 + pipe.load_textual_inversion(path, token=name, cache_dir=shared.opts.diffusers_dir, local_files_only=True) self.register_embedding(embedding, shared.sd_model) - except Exception: - pass - if not done and "safetensors" in path: + except Exception: + pass + is_loaded = pipe.tokenizer.convert_tokens_to_ids(name) > 49407 + if is_loaded: + self.register_embedding(embedding, shared.sd_model) + else: embeddings_dict = {} - from safetensors.torch import safe_open - with safe_open(path, framework="pt") as f: - for k in f.keys(): - embeddings_dict[k] = f.get_tensor(k) + if ext.lower() in ['.safetensors']: + with safetensors.torch.safe_open(path, framework="pt") as f: + for k in f.keys(): + embeddings_dict[k] = f.get_tensor(k) + else: + raise NotImplementedError + """ + # alternatively could disable load_textual_inversion and load everything here + elif ext.lower() in ['.pt', '.bin']: + data = torch.load(path, map_location="cpu") + embedding.tag = data.get('name', None) + embedding.step = data.get('step', None) + embedding.sd_checkpoint = data.get('sd_checkpoint', None) + embedding.sd_checkpoint_name = data.get('sd_checkpoint_name', None) + param_dict = data.get('string_to_param', None) + embeddings_dict['clip_l'] = [] + for tokens in param_dict.values(): + for vec in tokens: + embeddings_dict['clip_l'].append(vec) + """ clip_l = pipe.text_encoder.get_input_embeddings().weight if hasattr(pipe, 'text_encoder') and hasattr(pipe.text_encoder, "resize_token_embeddings") else None clip_g = pipe.text_encoder_2.get_input_embeddings().weight if hasattr(pipe, 'text_encoder_2') and hasattr(pipe.text_encoder_2, "resize_token_embeddings") else None + is_sd = clip_l is not None and 'clip_l' in embeddings_dict and clip_g is None and 'clip_g' not in embeddings_dict + is_xl = clip_l is not None and 'clip_l' in embeddings_dict and clip_g is not None and 'clip_g' in embeddings_dict tokens = [] for i in range(len(embeddings_dict["clip_l"])): - if clip_l is not None and len(clip_l.data[0]) == len(embeddings_dict["clip_l"][i]): + if (is_sd or is_xl) and (len(clip_l.data[0]) == len(embeddings_dict["clip_l"][i])): tokens.append(name if i == 0 else f"{name}_{i}") num_added = pipe.tokenizer.add_tokens(tokens) if num_added > 0: token_ids = pipe.tokenizer.convert_tokens_to_ids(tokens) - if clip_l is not None: + if is_sd: # only used for sd15 if load_textual_inversion failed and format is safetensors pipe.text_encoder.resize_token_embeddings(len(pipe.tokenizer)) for i in range(len(token_ids)): clip_l.data[token_ids[i]] = embeddings_dict["clip_l"][i] - if clip_g is not None: + elif is_xl: + pipe.text_encoder.resize_token_embeddings(len(pipe.tokenizer)) pipe.text_encoder_2.resize_token_embeddings(len(pipe.tokenizer)) for i in range(len(token_ids)): + clip_l.data[token_ids[i]] = embeddings_dict["clip_l"][i] clip_g.data[token_ids[i]] = embeddings_dict["clip_g"][i] - self.register_embedding(embedding, shared.sd_model) - else: - raise NotImplementedError - # self.word_embeddings[name] = embedding + self.register_embedding(embedding, shared.sd_model) + else: + raise NotImplementedError except Exception: self.skipped_embeddings[name] = embedding diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index 12996eb09..059295e54 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -1,14 +1,10 @@ from PIL import Image -import numpy as np import gradio as gr from modules import scripts_postprocessing, shared from modules.ui_components import FormRow, ToolButton import modules.ui_symbols as symbols -upscale_cache = {} - - class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): name = "Upscale" order = 1000 @@ -58,25 +54,12 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}" else: info["Postprocess upscale by"] = upscale_by - - cache_key = (hash(np.array(image.getdata()).tobytes()), upscaler.name, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) - cached_image = upscale_cache.pop(cache_key, None) - - if cached_image is not None: - image = cached_image - else: - image = upscaler.scaler.upscale(image, upscale_by, upscaler.data_path) - - upscale_cache[cache_key] = image - if len(upscale_cache) > shared.opts.upscaling_max_images_in_cache: - upscale_cache.pop(next(iter(upscale_cache), None), None) - + image = upscaler.scaler.upscale(image, upscale_by, upscaler.data_path) if upscale_mode == 1 and upscale_crop: cropped = Image.new("RGB", (upscale_to_width, upscale_to_height)) cropped.paste(image, box=(upscale_to_width // 2 - image.width // 2, upscale_to_height // 2 - image.height // 2)) image = cropped info["Postprocess crop to"] = f"{image.width}x{image.height}" - return image def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): # pylint: disable=arguments-differ @@ -104,7 +87,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): pp.image = upscaled_image def image_changed(self): - upscale_cache.clear() + pass class ScriptPostprocessingUpscaleSimple(ScriptPostprocessingUpscale):