From 940b6d02ba34d9958cd4df05c16755a4a615f09b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Mar 2024 11:40:58 -0400 Subject: [PATCH] facehires support batch size&count, add override strength --- CHANGELOG.md | 5 +++-- modules/images.py | 4 ++-- modules/masking.py | 2 +- modules/processing.py | 26 +++++++++++++------------- modules/processing_class.py | 10 +++++++--- modules/sd_models.py | 4 ++-- modules/shared.py | 1 + scripts/face-details.py | 20 ++++++++++---------- 8 files changed, 39 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c854c633..ad2d0d78a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,13 @@ - items that require `diffusers==0.27.0.dev`: - EDM samplers for Playground 2.5 - Stable Cascade - - LEdits++ pipeline + - LEdits++ pipeline: - fix reference models: - Warp Wuerstchen: pipeline does not have all components - Kandinsky 2.1: pipeline does not have all components - Kandinsky 2.2: pipeline does not have all components -## Update for 2024-03-13 +## Update for 2024-03-14 - [Playground v2.5](https://huggingface.co/playgroundai/playground-v2.5-1024px-aesthetic) - new model version from Playground: based on SDXL, but with some cool new concepts @@ -90,6 +90,7 @@ - set as default face restorer in settings -> postprocessing - disabled by default, to enable simply check *face restore* in your generate advanced settings - strength, steps and sampler are set using by hires section in refine menu + - strength can be overriden in settings -> postprocessing - will use secondary prompt and secondary negative prompt if present in refine - **Watermarking** - SD.Next disables all known watermarks in models, but does allow user to set custom watermark diff --git a/modules/images.py b/modules/images.py index 32cd3ccec..2f52acef6 100644 --- a/modules/images.py +++ b/modules/images.py @@ -216,7 +216,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0): def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type='image'): - shared.log.debug(f'Image resize: input={im} mode={resize_mode} target={width}x{height} upscaler={upscaler_name} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access + shared.log.debug(f'Image resize: input={im} mode={resize_mode} target={width}x{height} upscaler={upscaler_name} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access """ Resizes an image with the specified resize_mode, width, and height. Args: @@ -597,7 +597,7 @@ save_thread.start() def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument - debug(f'Save from function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access + debug(f'Save: fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access if image is None: shared.log.warning('Image is none') return None, None diff --git a/modules/masking.py b/modules/masking.py index 17495a9f1..6eb1abf35 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -372,7 +372,7 @@ def outpaint(input_image: Image.Image, outpaint_type: str = 'Edge'): def run_mask(input_image: Image.Image, input_mask: Image.Image = None, return_type: str = None, mask_blur: int = None, mask_padding: int = None, segment_enable=True, invert=None): - debug(f'Run mask: function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access + debug(f'Run mask: fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access if input_image is None: return input_mask diff --git a/modules/processing.py b/modules/processing.py index 0110d694e..d5247d8e5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -268,18 +268,19 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: extra_network_data = None debug(f'Processing inner: args={vars(p)}') for n in range(p.n_iter): + debug(f'Processing inner: iteration={n+1}/{p.n_iter}') p.iteration = n if shared.state.skipped: - shared.log.debug(f'Process skipped: {n}/{p.n_iter}') + shared.log.debug(f'Process skipped: {n+1}/{p.n_iter}') shared.state.skipped = False continue if shared.state.interrupted: - shared.log.debug(f'Process interrupted: {n}/{p.n_iter}') + shared.log.debug(f'Process interrupted: {n+1}/{p.n_iter}') break - p.prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size] - p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] - p.seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] - p.subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] + p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size] + p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size] + p.seeds = p.all_seeds[n * p.batch_size:(n+1) * p.batch_size] + p.subseeds = p.all_subseeds[n * p.batch_size:(n+1) * p.batch_size] if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner): p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) if len(p.prompts) == 0: @@ -313,8 +314,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner): p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n) if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner): - p.prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size] - p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size] + p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size] + p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size] batch_params = scripts.PostprocessBatchListArgs(list(x_samples_ddim)) p.scripts.postprocess_batch_list(p, batch_params, batch_number=n) x_samples_ddim = batch_params.images @@ -326,6 +327,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: shared.sd_model.restore_pipeline() for i, x_sample in enumerate(x_samples_ddim): + if hasattr(p, 'recursion'): + continue + debug(f'Processing result: index={i+1}/{len(x_samples_ddim)} iteration={n+1}/{p.n_iter}') p.batch_index = i if type(x_sample) == Image.Image: image = x_sample @@ -335,11 +339,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: image = Image.fromarray(x_sample) if p.restore_faces: if not p.do_not_save_samples and shared.opts.save_images_before_face_restoration: - orig = p.restore_faces - p.restore_faces = False - info = infotext(i) - p.restore_faces = orig - images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restore") + images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=infotext(i), p=p, suffix="-before-face-restore") p.ops.append('face') x_sample = face_restoration.restore_faces(x_sample, p) image = Image.fromarray(x_sample) diff --git a/modules/processing_class.py b/modules/processing_class.py index 0961372a0..790ec9867 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -1,4 +1,6 @@ import os +import sys +import inspect import hashlib from typing import Any, Dict, List from dataclasses import dataclass, field @@ -10,6 +12,9 @@ from modules import shared, devices, images, scripts, masking, sd_samplers, sd_m from modules.sd_hijack_hypertile import hypertile_set +debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None + + @dataclass(repr=False) class StableDiffusionProcessing: """ @@ -507,18 +512,17 @@ class StableDiffusionProcessingControl(StableDiffusionProcessingImg2Img): def switch_class(p: StableDiffusionProcessing, new_class: type, dct: dict = None): - import inspect signature = inspect.signature(type(new_class).__init__, follow_wrapped=True) possible = list(signature.parameters) kwargs = {} - for k, v in p.__dict__.items(): + for k, v in p.__dict__.copy().items(): if k in possible: kwargs[k] = v if dct is not None: for k, v in dct.items(): if k in possible: kwargs[k] = v - shared.log.debug(f"Switching class: {p.__class__} -> {new_class}") + debug(f"Switching class: {p.__class__.__name__} -> {new_class.__name__} fn={sys._getframe(1).f_code.co_name}") # pylint: disable=protected-access p.__class__ = new_class p.__init__(**kwargs) for k, v in p.__dict__.items(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 91fc06168..9e68d14bb 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -754,9 +754,9 @@ def move_model(model, device=None, force=False): if device == devices.device: # force vae back to gpu if not in txt2img mode model.vae.to(device) if hasattr(model.vae, '_hf_hook'): - debug_move(f'Model move: to={device} class={model.vae.__class__} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access + debug_move(f'Model move: to={device} class={model.vae.__class__} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access model.vae._hf_hook.execution_device = device # pylint: disable=protected-access - debug_move(f'Model move: device={device} class={model.__class__} accelerate={getattr(model, "has_accelerate", False)} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access + debug_move(f'Model move: device={device} class={model.__class__} accelerate={getattr(model, "has_accelerate", False)} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access if hasattr(model, "components"): # accelerate patch for name, m in model.components.items(): if not hasattr(m, "_hf_hook"): # not accelerate hook diff --git a/modules/shared.py b/modules/shared.py index f55072512..f824c73c4 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -669,6 +669,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "postprocessing_sep_face_restoration": OptionInfo("

Face Restoration

", "", gr.HTML), "face_restoration_model": OptionInfo("Face HiRes", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), + "facehires_strength": OptionInfo(0.0, "Face HiRes strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "face_restoration_unload": OptionInfo(False, "Move model to CPU when complete"), diff --git a/scripts/face-details.py b/scripts/face-details.py index 64250f1c2..759fb6ec3 100644 --- a/scripts/face-details.py +++ b/scripts/face-details.py @@ -92,7 +92,7 @@ class FaceRestorerYolo(FaceRestoration): from modules import devices, processing_class if not hasattr(p, 'facehires'): p.facehires = 0 - if np_image is None or p.facehires >= p.batch_size: + if np_image is None or p.facehires >= p.batch_size * p.n_iter: return np_image self.load() if self.model is None: @@ -119,7 +119,7 @@ class FaceRestorerYolo(FaceRestoration): 'sampler_name': orig_p.get('hr_sampler_name', 'default'), 'steps': orig_p.get('hr_second_pass_steps', 0), 'negative_prompt': orig_p.get('refiner_negative', ''), - 'denoising_strength': orig_p.get('denoising_strength', 0.3), + 'denoising_strength': shared.opts.facehires_strength if shared.opts.facehires_strength > 0 else orig_p.get('denoising_strength', 0.3), 'styles': [], 'prompt': orig_p.get('refiner_prompt', ''), # TODO facehires expose as tunable @@ -137,6 +137,7 @@ class FaceRestorerYolo(FaceRestoration): if len(p.negative_prompt) == 0: p.negative_prompt = orig_p.get('all_negative_prompts', [''])[0] + shared.log.debug(f'Face HiRes: faces={[f.__dict__ for f in faces]} strength={p.denoising_strength} blur={p.mask_blur} padding={p.inpaint_full_res_padding} steps={p.steps}') for face in faces: if face.mask is None: continue @@ -145,21 +146,20 @@ class FaceRestorerYolo(FaceRestoration): continue p.init_images = [image] p.image_mask = [face.mask] - shared.log.debug(f'Face HiRes: face={p.facehires} {face.__dict__} strength={p.denoising_strength} blur={p.mask_blur} padding={p.inpaint_full_res_padding} steps={p.steps}') + p.recursion = True pp = processing.process_images_inner(p) + del p.recursion p.overlay_images = None # skip applying overlay twice if pp is not None and pp.images is not None and len(pp.images) > 0: - image = pp.images[0] - - if np_image is None or getattr(p, 'facehires', 0) >= p.batch_size: - p.facehires = 0 + image = pp.images[0] # update image to be reused for next face # restore pipeline p = processing_class.switch_class(p, orig_cls, orig_p) + p.init_images = getattr(orig_p, 'init_images', None) + p.image_mask = getattr(orig_p, 'image_mask', None) shared.opts.data['mask_apply_overlay'] = orig_apply_overlay - if pp is not None and pp.images is not None and len(pp.images) > 0: - image = pp.images[0] - np_image = np.array(image) + np_image = np.array(image) + # shared.log.debug(f'Face HiRes complete: faces={len(faces)} time={t1-t0:.3f}') return np_image