Skip to content
代码片段 群组 项目
processing.py 49.0 KB
更新 更旧
  • 了解如何忽略特定修订
  • import json
    import math
    import os
    import sys
    
    
    import torch
    import numpy as np
    from PIL import Image, ImageFilter, ImageOps
    import random
    
    import cv2
    from skimage import exposure
    
    from typing import Any, Dict, List, Optional
    
    import modules.sd_hijack
    
    from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, script_callbacks, extra_networks, sd_vae_approx, scripts
    
    from modules.sd_hijack import model_hijack
    from modules.shared import opts, cmd_opts, state
    import modules.shared as shared
    
    import modules.paths as paths
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    import modules.face_restoration
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    import modules.styles
    
    import modules.sd_models as sd_models
    import modules.sd_vae as sd_vae
    
    import logging
    
    Jay Smith's avatar
    Jay Smith 已提交
    from ldm.data.util import AddMiDaS
    from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion
    
    Jay Smith's avatar
    Jay Smith 已提交
    from einops import repeat, rearrange
    
    from blendmodes.blend import blendLayers, BlendType
    
    # some of those options should not be changed at all because they would break the model, so I removed them from options.
    opt_C = 4
    opt_f = 8
    
    
    
    def setup_color_correction(image):
    
        logging.info("Calibrating color correction.")
    
        correction_target = cv2.cvtColor(np.asarray(image.copy()), cv2.COLOR_RGB2LAB)
        return correction_target
    
    
    
    def apply_color_correction(correction, original_image):
    
        logging.info("Applying color correction.")
    
        image = Image.fromarray(cv2.cvtColor(exposure.match_histograms(
            cv2.cvtColor(
    
                np.asarray(original_image),
    
                cv2.COLOR_RGB2LAB
            ),
            correction,
            channel_axis=2
        ), cv2.COLOR_LAB2RGB).astype("uint8"))
    
        image = blendLayers(image, original_image, BlendType.LUMINOSITY)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
    def apply_overlay(image, paste_loc, index, overlays):
        if overlays is None or index >= len(overlays):
            return image
    
        overlay = overlays[index]
    
        if paste_loc is not None:
            x, y, w, h = paste_loc
            base_image = Image.new('RGBA', (overlay.width, overlay.height))
            image = images.resize_image(1, image, w, h)
            base_image.paste(image, (x, y))
            image = base_image
    
        image = image.convert('RGBA')
        image.alpha_composite(overlay)
        image = image.convert('RGB')
    
    def txt2img_image_conditioning(sd_model, x, width, height):
        if sd_model.model.conditioning_key not in {'hybrid', 'concat'}:
            # Dummy zero conditioning if we're not using inpainting model.
            # Still takes up a bit of memory, but no encoder call.
            # Pretty sure we can just make this a 1x1 image since its not going to be used besides its batch size.
            return x.new_zeros(x.shape[0], 5, 1, 1, dtype=x.dtype, device=x.device)
    
        # The "masked-image" in this case will just be all zeros since the entire image is masked.
        image_conditioning = torch.zeros(x.shape[0], 3, height, width, device=x.device)
        image_conditioning = sd_model.get_first_stage_encoding(sd_model.encode_first_stage(image_conditioning))
    
        # Add the fake full 1s mask to the first dimension.
        image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0)
        image_conditioning = image_conditioning.to(x.dtype)
    
        return image_conditioning
    
    
    
        """
        The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing
        """
    
        def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, ddim_discretize: str = None, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None):
    
                print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr)
    
            self.outpath_samples: str = outpath_samples
            self.outpath_grids: str = outpath_grids
            self.prompt: str = prompt
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            self.prompt_for_display: str = None
    
            self.negative_prompt: str = (negative_prompt or "")
    
            self.styles: list = styles or []
    
            self.subseed: int = subseed
            self.subseed_strength: float = subseed_strength
            self.seed_resize_from_h: int = seed_resize_from_h
            self.seed_resize_from_w: int = seed_resize_from_w
    
            self.batch_size: int = batch_size
            self.n_iter: int = n_iter
            self.steps: int = steps
            self.cfg_scale: float = cfg_scale
            self.width: int = width
            self.height: int = height
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            self.restore_faces: bool = restore_faces
    
            self.tiling: bool = tiling
    
            self.do_not_save_samples: bool = do_not_save_samples
            self.do_not_save_grid: bool = do_not_save_grid
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            self.extra_generation_params: dict = extra_generation_params or {}
    
            self.overlay_images = overlay_images
    
            self.do_not_reload_embeddings = do_not_reload_embeddings
    
            self.color_corrections = None
    
            self.denoising_strength: float = denoising_strength
    
            self.sampler_noise_scheduler_override = None
    
            self.ddim_discretize = ddim_discretize or opts.ddim_discretize
    
            self.s_churn = s_churn or opts.s_churn
            self.s_tmin = s_tmin or opts.s_tmin
            self.s_tmax = s_tmax or float('inf')  # not representable as a standard ui option
            self.s_noise = s_noise or opts.s_noise
    
            self.override_settings = {k: v for k, v in (override_settings or {}).items() if k not in shared.restricted_opts}
    
            self.override_settings_restore_afterwards = override_settings_restore_afterwards
    
            self.is_using_inpainting_conditioning = False
    
            self.disable_extra_networks = False
    
            if not seed_enable_extras:
                self.subseed = -1
                self.subseed_strength = 0
                self.seed_resize_from_h = 0
                self.seed_resize_from_w = 0
    
    
            self.scripts = None
    
            self.script_args = script_args
    
            self.all_prompts = None
    
            self.all_seeds = None
            self.all_subseeds = None
    
            self.iteration = 0
    
        def txt2img_image_conditioning(self, x, width=None, height=None):
    
            self.is_using_inpainting_conditioning = self.sd_model.model.conditioning_key in {'hybrid', 'concat'}
    
            return txt2img_image_conditioning(self.sd_model, x, width or self.width, height or self.height)
    
    Jay Smith's avatar
    Jay Smith 已提交
        def depth2img_image_conditioning(self, source_image):
            # Use the AddMiDaS helper to Format our source image to suit the MiDaS model
            transformer = AddMiDaS(model_type="dpt_hybrid")
            transformed = transformer({"jpg": rearrange(source_image[0], "c h w -> h w c")})
            midas_in = torch.from_numpy(transformed["midas_in"][None, ...]).to(device=shared.device)
            midas_in = repeat(midas_in, "1 ... -> n ...", n=self.batch_size)
    
    
            conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(source_image))
    
    Jay Smith's avatar
    Jay Smith 已提交
            conditioning = torch.nn.functional.interpolate(
                self.sd_model.depth_model(midas_in),
                size=conditioning_image.shape[2:],
                mode="bicubic",
                align_corners=False,
            )
    
            (depth_min, depth_max) = torch.aminmax(conditioning)
            conditioning = 2. * (conditioning - depth_min) / (depth_max - depth_min) - 1.
            return conditioning
    
        def edit_image_conditioning(self, source_image):
    
            conditioning_image = self.sd_model.encode_first_stage(source_image).mode()
    
    
            return conditioning_image
    
        def inpainting_image_conditioning(self, source_image, latent_image, image_mask=None):
    
            self.is_using_inpainting_conditioning = True
    
    
            # Handle the different mask inputs
            if image_mask is not None:
                if torch.is_tensor(image_mask):
                    conditioning_mask = image_mask
                else:
                    conditioning_mask = np.array(image_mask.convert("L"))
                    conditioning_mask = conditioning_mask.astype(np.float32) / 255.0
                    conditioning_mask = torch.from_numpy(conditioning_mask[None, None])
    
                    # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0
                    conditioning_mask = torch.round(conditioning_mask)
            else:
    
                conditioning_mask = source_image.new_ones(1, 1, *source_image.shape[-2:])
    
    
            # Create another latent image, this time with a masked version of the original input.
            # Smoothly interpolate between the masked and unmasked latent conditioning image using a parameter.
    
            conditioning_mask = conditioning_mask.to(device=source_image.device, dtype=source_image.dtype)
    
            conditioning_image = torch.lerp(
                source_image,
                source_image * (1.0 - conditioning_mask),
                getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight)
            )
    
    Jim Hays's avatar
    Jim Hays 已提交
    
    
            # Encode the new masked image using first stage of network.
    
            conditioning_image = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(conditioning_image))
    
    
            # Create the concatenated conditioning tensor to be fed to `c_concat`
            conditioning_mask = torch.nn.functional.interpolate(conditioning_mask, size=latent_image.shape[-2:])
            conditioning_mask = conditioning_mask.expand(conditioning_image.shape[0], -1, -1, -1)
            image_conditioning = torch.cat([conditioning_mask, conditioning_image], dim=1)
            image_conditioning = image_conditioning.to(shared.device).type(self.sd_model.dtype)
    
            return image_conditioning
    
    
    Jay Smith's avatar
    Jay Smith 已提交
        def img2img_image_conditioning(self, source_image, latent_image, image_mask=None):
    
            source_image = devices.cond_cast_float(source_image)
    
    
    Jay Smith's avatar
    Jay Smith 已提交
            # 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 isinstance(self.sd_model, LatentDepth2ImageDiffusion):
    
                return self.depth2img_image_conditioning(source_image)
    
            if self.sd_model.cond_stage_key == "edit":
                return self.edit_image_conditioning(source_image)
    
    
    Jay Smith's avatar
    Jay Smith 已提交
            if self.sampler.conditioning_key in {'hybrid', 'concat'}:
    
                return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask)
    
    Jay Smith's avatar
    Jay Smith 已提交
    
            # Dummy zero conditioning if we're not using inpainting or depth model.
            return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1)
    
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
        def init(self, all_prompts, all_seeds, all_subseeds):
    
        def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts):
    
        def close(self):
            self.sampler = None
    
    
        def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", subseed=None, all_prompts=None, all_negative_prompts=None, all_seeds=None, all_subseeds=None, index_of_first_image=0, infotexts=None, comments=""):
    
            self.images = images_list
            self.prompt = p.prompt
    
            self.negative_prompt = p.negative_prompt
    
            self.subseed = subseed
            self.subseed_strength = p.subseed_strength
    
            self.width = p.width
            self.height = p.height
    
    Kyle's avatar
    Kyle 已提交
            self.image_cfg_scale = getattr(p, 'image_cfg_scale', None)
    
            self.batch_size = p.batch_size
            self.restore_faces = p.restore_faces
            self.face_restoration_model = opts.face_restoration_model if p.restore_faces else None
            self.sd_model_hash = shared.sd_model.sd_model_hash
            self.seed_resize_from_w = p.seed_resize_from_w
            self.seed_resize_from_h = p.seed_resize_from_h
            self.denoising_strength = getattr(p, 'denoising_strength', None)
            self.extra_generation_params = p.extra_generation_params
            self.index_of_first_image = index_of_first_image
    
    Milly's avatar
    Milly 已提交
            self.styles = p.styles
    
    Milly's avatar
    Milly 已提交
            self.job_timestamp = state.job_timestamp
    
            self.clip_skip = opts.CLIP_stop_at_last_layers
    
    C43H66N12O12S2's avatar
    C43H66N12O12S2 已提交
            self.eta = p.eta
    
            self.ddim_discretize = p.ddim_discretize
            self.s_churn = p.s_churn
            self.s_tmin = p.s_tmin
            self.s_tmax = p.s_tmax
            self.s_noise = p.s_noise
    
            self.sampler_noise_scheduler_override = p.sampler_noise_scheduler_override
    
            self.prompt = self.prompt if type(self.prompt) != list else self.prompt[0]
            self.negative_prompt = self.negative_prompt if type(self.negative_prompt) != list else self.negative_prompt[0]
    
            self.seed = int(self.seed if type(self.seed) != list else self.seed[0]) if self.seed is not None else -1
    
            self.subseed = int(self.subseed if type(self.subseed) != list else self.subseed[0]) if self.subseed is not None else -1
    
            self.is_using_inpainting_conditioning = p.is_using_inpainting_conditioning
    
            self.all_prompts = all_prompts or p.all_prompts or [self.prompt]
            self.all_negative_prompts = all_negative_prompts or p.all_negative_prompts or [self.negative_prompt]
            self.all_seeds = all_seeds or p.all_seeds or [self.seed]
            self.all_subseeds = all_subseeds or p.all_subseeds or [self.subseed]
    
            self.infotexts = infotexts or [info]
    
                "negative_prompt": self.all_negative_prompts[0],
                "all_negative_prompts": self.all_negative_prompts,
    
                "seed": self.seed,
                "all_seeds": self.all_seeds,
                "subseed": self.subseed,
                "all_subseeds": self.all_subseeds,
    
                "subseed_strength": self.subseed_strength,
    
                "width": self.width,
                "height": self.height,
    
                "cfg_scale": self.cfg_scale,
                "steps": self.steps,
    
                "batch_size": self.batch_size,
                "restore_faces": self.restore_faces,
                "face_restoration_model": self.face_restoration_model,
                "sd_model_hash": self.sd_model_hash,
                "seed_resize_from_w": self.seed_resize_from_w,
                "seed_resize_from_h": self.seed_resize_from_h,
                "denoising_strength": self.denoising_strength,
                "extra_generation_params": self.extra_generation_params,
                "index_of_first_image": self.index_of_first_image,
    
                "infotexts": self.infotexts,
    
    Milly's avatar
    Milly 已提交
                "styles": self.styles,
    
    Milly's avatar
    Milly 已提交
                "job_timestamp": self.job_timestamp,
    
                "is_using_inpainting_conditioning": self.is_using_inpainting_conditioning,
    
        def infotext(self, p: StableDiffusionProcessing, index):
    
            return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size)
    
    
    
    # from https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
    def slerp(val, low, high):
        low_norm = low/torch.norm(low, dim=1, keepdim=True)
        high_norm = high/torch.norm(high, dim=1, keepdim=True)
    
        dot = (low_norm*high_norm).sum(1)
    
        if dot.mean() > 0.9995:
            return low * val + high * (1 - val)
    
        omega = torch.acos(dot)
    
        so = torch.sin(omega)
        res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
        return res
    
    def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, p=None):
    
        eta_noise_seed_delta = opts.eta_noise_seed_delta or 0
    
        # if we have multiple seeds, this means we are working with batch size>1; this then
        # enables the generation of additional tensors with noise that the sampler will use during its processing.
    
        # Using those pre-generated tensors instead of simple torch.randn allows a batch with seeds [100, 101] to
    
        # produce the same images as with two batches [100], [101].
    
        if p is not None and p.sampler is not None and (len(seeds) > 1 and opts.enable_batch_seeds or eta_noise_seed_delta > 0):
    
            sampler_noises = [[] for _ in range(p.sampler.number_of_needed_noises(p))]
        else:
            sampler_noises = None
    
    
        for i, seed in enumerate(seeds):
            noise_shape = shape if seed_resize_from_h <= 0 or seed_resize_from_w <= 0 else (shape[0], seed_resize_from_h//8, seed_resize_from_w//8)
    
            subnoise = None
            if subseeds is not None:
                subseed = 0 if i >= len(subseeds) else subseeds[i]
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                subnoise = devices.randn(subseed, noise_shape)
    
    
            # randn results depend on device; gpu and cpu get different results for same seed;
            # the way I see it, it's better to do this on CPU, so that everyone gets same result;
    
            # but the original script had it like this, so I do not dare change it for now because
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            noise = devices.randn(seed, noise_shape)
    
    
            if subnoise is not None:
                noise = slerp(subseed_strength, noise, subnoise)
    
            if noise_shape != shape:
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                x = devices.randn(seed, shape)
                dx = (shape[2] - noise_shape[2]) // 2
    
                dy = (shape[1] - noise_shape[1]) // 2
                w = noise_shape[2] if dx >= 0 else noise_shape[2] + 2 * dx
                h = noise_shape[1] if dy >= 0 else noise_shape[1] + 2 * dy
                tx = 0 if dx < 0 else dx
                ty = 0 if dy < 0 else dy
                dx = max(-dx, 0)
                dy = max(-dy, 0)
    
                x[:, ty:ty+h, tx:tx+w] = noise[:, dy:dy+h, dx:dx+w]
                noise = x
    
    
            if sampler_noises is not None:
                cnt = p.sampler.number_of_needed_noises(p)
    
                if eta_noise_seed_delta > 0:
                    torch.manual_seed(seed + eta_noise_seed_delta)
    
                for j in range(cnt):
                    sampler_noises[j].append(devices.randn_without_seed(tuple(noise_shape)))
    
    
        if sampler_noises is not None:
            p.sampler.sampler_noises = [torch.stack(n).to(shared.device) for n in sampler_noises]
    
    
        x = torch.stack(xs).to(shared.device)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    def decode_first_stage(model, x):
        with devices.autocast(disable=x.dtype == devices.dtype_vae):
    
            x = model.decode_first_stage(x)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
        return x
    
    
    
    def get_fixed_seed(seed):
        if seed is None or seed == '' or seed == -1:
            return int(random.randrange(4294967294))
    
        return seed
    
    
    
    def fix_seed(p):
    
        p.seed = get_fixed_seed(p.seed)
        p.subseed = get_fixed_seed(p.subseed)
    
    def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0):
    
        index = position_in_batch + iteration * p.batch_size
    
    
        clip_skip = getattr(p, 'clip_skip', opts.CLIP_stop_at_last_layers)
    
            "Image CFG scale": getattr(p, 'image_cfg_scale', None),
    
            "Seed": all_seeds[index],
            "Face restoration": (opts.face_restoration_model if p.restore_faces else None),
            "Size": f"{p.width}x{p.height}",
            "Model hash": getattr(p, 'sd_model_hash', None if not opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash),
    
            "Model": (None if not opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')),
    
            "Variation seed": (None if p.subseed_strength == 0 else all_subseeds[index]),
            "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength),
            "Seed resize from": (None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"),
            "Denoising strength": getattr(p, 'denoising_strength', None),
    
            "Conditional mask weight": getattr(p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if p.is_using_inpainting_conditioning else None,
    
            "Clip skip": None if clip_skip <= 1 else clip_skip,
    
            "ENSD": None if opts.eta_noise_seed_delta == 0 else opts.eta_noise_seed_delta,
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
        generation_params.update(p.extra_generation_params)
    
        generation_params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in generation_params.items() if v is not None])
    
        negative_prompt_text = "\nNegative prompt: " + p.all_negative_prompts[index] if p.all_negative_prompts[index] else ""
    
        return f"{all_prompts[index]}{negative_prompt_text}\n{generation_params_text}".strip()
    
    def process_images(p: StableDiffusionProcessing) -> Processed:
    
        stored_opts = {k: opts.data[k] for k in p.override_settings.keys()}
    
        try:
            for k, v in p.override_settings.items():
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                    sd_models.reload_model_weights()
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                    sd_vae.reload_vae_weights()
    
        finally:
            # restore opts to original state
            if p.override_settings_restore_afterwards:
                for k, v in stored_opts.items():
                    setattr(opts, k, v)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                    if k == 'sd_model_checkpoint':
                        sd_models.reload_model_weights()
    
                    if k == 'sd_vae':
                        sd_vae.reload_vae_weights()
    
    
        return res
    
    
    def process_images_inner(p: StableDiffusionProcessing) -> Processed:
    
        """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch"""
    
    
        if type(p.prompt) == list:
            assert(len(p.prompt) > 0)
        else:
            assert p.prompt is not None
    
        devices.torch_gc()
    
        seed = get_fixed_seed(p.seed)
        subseed = get_fixed_seed(p.subseed)
    
        modules.sd_hijack.model_hijack.apply_circular(p.tiling)
    
        modules.sd_hijack.model_hijack.clear_comments()
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
        if type(p.prompt) == list:
    
            p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.prompt]
        else:
            p.all_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_styles_to_prompt(p.prompt, p.styles)]
    
        if type(p.negative_prompt) == list:
            p.all_negative_prompts = [shared.prompt_styles.apply_negative_styles_to_prompt(x, p.styles) for x in p.negative_prompt]
    
            p.all_negative_prompts = p.batch_size * p.n_iter * [shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles)]
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
    
        if type(seed) == list:
    
            p.all_seeds = seed
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
        else:
    
            p.all_seeds = [int(seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(p.all_prompts))]
    
        if type(subseed) == list:
    
            p.all_subseeds = subseed
    
            p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))]
    
    
        def infotext(iteration=0, position_in_batch=0):
    
            return create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, comments, iteration, position_in_batch)
    
        if os.path.exists(cmd_opts.embeddings_dir) and not p.do_not_reload_embeddings:
    
            model_hijack.embedding_db.load_textual_inversion_embeddings()
    
        if p.scripts is not None:
    
            p.scripts.process(p)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
        cached_uc = [None, None]
        cached_c = [None, None]
    
        def get_conds_with_caching(function, required_prompts, steps, cache):
            """
            Returns the result of calling function(shared.sd_model, required_prompts, steps)
            using a cache to store the result if the same arguments have been used before.
    
            cache is an array containing two elements. The first element is a tuple
            representing the previously used arguments, or None if no arguments
            have been used before. The second element is where the previously
            computed result is stored.
            """
    
            if cache[0] is not None and (required_prompts, steps) == cache[0]:
                return cache[1]
    
            with devices.autocast():
                cache[1] = function(shared.sd_model, required_prompts, steps)
    
            cache[0] = (required_prompts, steps)
            return cache[1]
    
    
        with torch.no_grad(), p.sd_model.ema_scope():
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            with devices.autocast():
    
                p.init(p.all_prompts, p.all_seeds, p.all_subseeds)
    
                # for OSX, loading the model during sampling changes the generated picture, so it is loaded here
                if shared.opts.live_previews_enable and opts.show_progress_type == "Approx NN":
    
                    sd_vae_approx.model()
    
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            if state.job_count == -1:
                state.job_count = p.n_iter
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
    
            for n in range(p.n_iter):
    
                if state.skipped:
                    state.skipped = False
    
    Jim Hays's avatar
    Jim Hays 已提交
    
    
                prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size]
    
                negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size]
    
                seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size]
                subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size]
    
                if len(prompts) == 0:
    
                prompts, extra_network_data = extra_networks.parse_prompts(prompts)
    
                if not p.disable_extra_networks:
                    with devices.autocast():
                        extra_networks.activate(p, extra_network_data)
    
    Artem Zagidulin's avatar
    Artem Zagidulin 已提交
                if p.scripts is not None:
    
                    p.scripts.process_batch(p, batch_number=n, prompts=prompts, seeds=seeds, subseeds=subseeds)
    
    Artem Zagidulin's avatar
    Artem Zagidulin 已提交
    
    
                # params.txt should be saved after scripts.process_batch, since the
                # infotext could be modified by that callback
                # Example: a wildcard processed by process_batch sets an extra model
                # strength, which is saved as "Model Strength: 1.0" in the infotext
                if n == 0:
                    with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
                        processed = Processed(p, [], p.seed, "")
                        file.write(processed.infotext(p, 0))
    
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, negative_prompts, p.steps, cached_uc)
                c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, cached_c)
    
                    for comment in model_hijack.comments:
                        comments[comment] = 1
    
                    shared.state.job = f"Batch {n+1} out of {p.n_iter}"
    
                with devices.without_autocast() if devices.unet_needs_upcast else devices.autocast():
    
                    samples_ddim = p.sample(conditioning=c, unconditional_conditioning=uc, seeds=seeds, subseeds=subseeds, subseed_strength=p.subseed_strength, prompts=prompts)
    
                x_samples_ddim = [decode_first_stage(p.sd_model, samples_ddim[i:i+1].to(dtype=devices.dtype_vae))[0].cpu() for i in range(samples_ddim.size(0))]
    
                for x in x_samples_ddim:
                    devices.test_for_nans(x, "vae")
    
    
                x_samples_ddim = torch.stack(x_samples_ddim).float()
    
                x_samples_ddim = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0)
    
    
                del samples_ddim
    
                if shared.cmd_opts.lowvram or shared.cmd_opts.medvram:
                    lowvram.send_everything_to_cpu()
    
                devices.torch_gc()
    
    
                if p.scripts is not None:
                    p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n)
    
                for i, x_sample in enumerate(x_samples_ddim):
    
                    x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2)
                    x_sample = x_sample.astype(np.uint8)
    
    
                    if p.restore_faces:
    
                        if opts.save and not p.do_not_save_samples and opts.save_images_before_face_restoration:
    
                            images.save_image(Image.fromarray(x_sample), p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-face-restoration")
    
                        x_sample = modules.face_restoration.restore_faces(x_sample)
                        devices.torch_gc()
    
    Vladimir Repin's avatar
    Vladimir Repin 已提交
    
    
                    if p.scripts is not None:
                        pp = scripts.PostprocessImageArgs(image)
                        p.scripts.postprocess_image(p, pp)
                        image = pp.image
    
    
                    if p.color_corrections is not None and i < len(p.color_corrections):
    
                        if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction:
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                            image_without_cc = apply_overlay(image, p.paste_to, i, p.overlay_images)
    
                            images.save_image(image_without_cc, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction")
    
                        image = apply_color_correction(p.color_corrections[i], image)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                    image = apply_overlay(image, p.paste_to, i, p.overlay_images)
    
    
                    if opts.samples_save and not p.do_not_save_samples:
    
                        images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p)
    
                    text = infotext(n, i)
                    infotexts.append(text)
    
                    if opts.enable_pnginfo:
                        image.info["parameters"] = text
    
    Jim Hays's avatar
    Jim Hays 已提交
                del x_samples_ddim
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
    
                devices.torch_gc()
    
            p.color_corrections = None
    
    
            unwanted_grid_because_of_img_count = len(output_images) < 2 and opts.grid_only_if_multiple
    
            if (opts.return_grid or opts.grid_save) and not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                grid = images.image_grid(output_images, p.batch_size)
    
                if opts.return_grid:
    
                    text = infotext()
                    infotexts.insert(0, text)
    
                    if opts.enable_pnginfo:
                        grid.info["parameters"] = text
    
                    images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], opts.grid_format, info=infotext(), short_filename=not opts.grid_extended_filename, p=p, grid=True)
    
        if not p.disable_extra_networks:
            extra_networks.deactivate(p, extra_network_data)
    
    
        devices.torch_gc()
    
        res = Processed(p, output_images, p.all_seeds[0], infotext(), comments="".join(["\n\n" + x for x in comments]), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, infotexts=infotexts)
    
    
        if p.scripts is not None:
            p.scripts.postprocess(p, res)
    
        return res
    
    def old_hires_fix_first_pass_dimensions(width, height):
        """old algorithm for auto-calculating first pass size"""
    
        desired_pixel_count = 512 * 512
        actual_pixel_count = width * height
        scale = math.sqrt(desired_pixel_count / actual_pixel_count)
        width = math.ceil(scale * width / 64) * 64
        height = math.ceil(scale * height / 64) * 64
    
        return width, height
    
    
    
    class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
        sampler = None
    
        def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, **kwargs):
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            super().__init__(**kwargs)
            self.enable_hr = enable_hr
            self.denoising_strength = denoising_strength
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            self.hr_scale = hr_scale
            self.hr_upscaler = hr_upscaler
    
            self.hr_second_pass_steps = hr_second_pass_steps
            self.hr_resize_x = hr_resize_x
            self.hr_resize_y = hr_resize_y
            self.hr_upscale_to_x = hr_resize_x
            self.hr_upscale_to_y = hr_resize_y
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
            if firstphase_width != 0 or firstphase_height != 0:
    
                self.hr_upscale_to_x = self.width
                self.hr_upscale_to_y = self.height
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                self.width = firstphase_width
                self.height = firstphase_height
    
            self.truncate_x = 0
            self.truncate_y = 0
    
            self.applied_old_hires_behavior_to = None
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
        def init(self, all_prompts, all_seeds, all_subseeds):
            if self.enable_hr:
    
                if opts.use_old_hires_fix_width_height and self.applied_old_hires_behavior_to != (self.width, self.height):
                    self.hr_resize_x = self.width
                    self.hr_resize_y = self.height
                    self.hr_upscale_to_x = self.width
                    self.hr_upscale_to_y = self.height
    
                    self.width, self.height = old_hires_fix_first_pass_dimensions(self.width, self.height)
                    self.applied_old_hires_behavior_to = (self.width, self.height)
    
    
                if self.hr_resize_x == 0 and self.hr_resize_y == 0:
                    self.extra_generation_params["Hires upscale"] = self.hr_scale
                    self.hr_upscale_to_x = int(self.width * self.hr_scale)
                    self.hr_upscale_to_y = int(self.height * self.hr_scale)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                else:
    
                    self.extra_generation_params["Hires resize"] = f"{self.hr_resize_x}x{self.hr_resize_y}"
    
                    if self.hr_resize_y == 0:
                        self.hr_upscale_to_x = self.hr_resize_x
                        self.hr_upscale_to_y = self.hr_resize_x * self.height // self.width
                    elif self.hr_resize_x == 0:
                        self.hr_upscale_to_x = self.hr_resize_y * self.width // self.height
                        self.hr_upscale_to_y = self.hr_resize_y
                    else:
                        target_w = self.hr_resize_x
                        target_h = self.hr_resize_y
                        src_ratio = self.width / self.height
                        dst_ratio = self.hr_resize_x / self.hr_resize_y
    
                        if src_ratio < dst_ratio:
                            self.hr_upscale_to_x = self.hr_resize_x
                            self.hr_upscale_to_y = self.hr_resize_x * self.height // self.width
                        else:
                            self.hr_upscale_to_x = self.hr_resize_y * self.width // self.height
                            self.hr_upscale_to_y = self.hr_resize_y
    
                        self.truncate_x = (self.hr_upscale_to_x - target_w) // opt_f
                        self.truncate_y = (self.hr_upscale_to_y - target_h) // opt_f
    
    
                # special case: the user has chosen to do nothing
                if self.hr_upscale_to_x == self.width and self.hr_upscale_to_y == self.height:
                    self.enable_hr = False
                    self.denoising_strength = None
                    self.extra_generation_params.pop("Hires upscale", None)
                    self.extra_generation_params.pop("Hires resize", None)
                    return
    
                if not state.processing_has_refined_job_count:
                    if state.job_count == -1:
                        state.job_count = self.n_iter
    
                    shared.total_tqdm.updateTotal((self.steps + (self.hr_second_pass_steps or self.steps)) * state.job_count)
                    state.job_count = state.job_count * 2
                    state.processing_has_refined_job_count = True
    
                if self.hr_second_pass_steps:
                    self.extra_generation_params["Hires steps"] = self.hr_second_pass_steps
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                if self.hr_upscaler is not None:
                    self.extra_generation_params["Hires upscaler"] = self.hr_upscaler
    
        def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts):
    
            self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
    
            latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest")
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            if self.enable_hr and latent_scale_mode is None:
                assert len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) > 0, f"could not find upscaler named {self.hr_upscaler}"
    
            x = create_random_tensors([opt_C, self.height // opt_f, self.width // opt_f], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self)
            samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x))
    
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                return samples
    
    
            target_width = self.hr_upscale_to_x
            target_height = self.hr_upscale_to_y
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                """saves image before applying hires fix, if enabled in options; takes as an argument either an image or batch with latent space images"""
    
    
                if not opts.save or self.do_not_save_samples or not opts.save_images_before_highres_fix:
                    return
    
                if not isinstance(image, Image.Image):
    
                    image = sd_samplers.sample_to_image(image, index, approximation=0)
    
                info = create_infotext(self, self.all_prompts, self.all_seeds, self.all_subseeds, [], iteration=self.iteration, position_in_batch=index)
                images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], opts.samples_format, info=info, suffix="-before-highres-fix")
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            if latent_scale_mode is not None:
    
                for i in range(samples.shape[0]):
                    save_intermediate(samples, i)
    
    
    MMaker's avatar
    MMaker 已提交
                samples = torch.nn.functional.interpolate(samples, size=(target_height // opt_f, target_width // opt_f), mode=latent_scale_mode["mode"], antialias=latent_scale_mode["antialias"])
    
    Jim Hays's avatar
    Jim Hays 已提交
                # Avoid making the inpainting conditioning unless necessary as
    
                # this does need some extra compute to decode / encode the image again.
                if getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) < 1.0:
                    image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples), samples)
                else:
                    image_conditioning = self.txt2img_image_conditioning(samples)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            else:
    
                decoded_samples = decode_first_stage(self.sd_model, samples)
    
                lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0)
    
                batch_images = []
                for i, x_sample in enumerate(lowres_samples):
                    x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2)
                    x_sample = x_sample.astype(np.uint8)
                    image = Image.fromarray(x_sample)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
                    image = images.resize_image(0, image, target_width, target_height, upscaler_name=self.hr_upscaler)
    
                    image = np.array(image).astype(np.float32) / 255.0
                    image = np.moveaxis(image, 2, 0)
                    batch_images.append(image)
    
                decoded_samples = torch.from_numpy(np.array(batch_images))
                decoded_samples = decoded_samples.to(shared.device)
                decoded_samples = 2. * decoded_samples - 1.
    
    
                samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples))
    
                image_conditioning = self.img2img_image_conditioning(decoded_samples, samples)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            shared.state.nextjob()
    
            img2img_sampler_name = self.sampler_name if self.sampler_name != 'PLMS' else 'DDIM'  # PLMS does not support img2img so we just silently switch ot DDIM
            self.sampler = sd_samplers.create_sampler(img2img_sampler_name, self.sd_model)
    
            samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2]
    
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
            noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self)
    
    
            # GC now before running the next img2img to prevent running out of memory
            x = None
            devices.torch_gc()
    
            samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning)
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
    
            return samples
    
    
    
    class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
        sampler = None
    
    
        def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.75, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, **kwargs):
    
            super().__init__(**kwargs)
    
            self.init_images = init_images
            self.resize_mode: int = resize_mode
            self.denoising_strength: float = denoising_strength
    
            self.image_cfg_scale: float = image_cfg_scale if shared.sd_model.cond_stage_key == "edit" else None
    
            self.init_latent = None
            self.image_mask = mask
    
            self.latent_mask = None
    
            self.mask_for_overlay = None
            self.mask_blur = mask_blur
            self.inpainting_fill = inpainting_fill
            self.inpaint_full_res = inpaint_full_res
    
            self.inpaint_full_res_padding = inpaint_full_res_padding
    
            self.inpainting_mask_invert = inpainting_mask_invert
    
            self.initial_noise_multiplier = opts.initial_noise_multiplier if initial_noise_multiplier is None else initial_noise_multiplier
    
            self.image_conditioning = None
    
    AUTOMATIC's avatar
    AUTOMATIC 已提交
        def init(self, all_prompts, all_seeds, all_subseeds):
    
            self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
    
            if image_mask is not None:
                image_mask = image_mask.convert('L')
    
                if self.inpainting_mask_invert:
                    image_mask = ImageOps.invert(image_mask)
    
                    image_mask = image_mask.filter(ImageFilter.GaussianBlur(self.mask_blur))
    
                    self.mask_for_overlay = image_mask
                    mask = image_mask.convert('L')
    
                    crop_region = masking.get_crop_region(np.array(mask), self.inpaint_full_res_padding)
    
                    crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
    
                    x1, y1, x2, y2 = crop_region
    
                    mask = mask.crop(crop_region)
    
                    image_mask = images.resize_image(2, mask, self.width, self.height)
    
                    self.paste_to = (x1, y1, x2-x1, y2-y1)
                else:
    
                    image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height)
                    np_mask = np.array(image_mask)
    
    JJ's avatar
    JJ 已提交
                    np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8)
    
                    self.mask_for_overlay = Image.fromarray(np_mask)
    
            latent_mask = self.latent_mask if self.latent_mask is not None else image_mask
    
            add_color_corrections = opts.img2img_color_correction and self.color_corrections is None
            if add_color_corrections:
                self.color_corrections = []
    
                image = images.flatten(img, opts.img2img_background_color)
    
                if crop_region is None and self.resize_mode != 3:
    
                    image = images.resize_image(self.resize_mode, image, self.width, self.height)
    
    
                    image_masked = Image.new('RGBa', (image.width, image.height))
                    image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L')))
    
                    self.overlay_images.append(image_masked.convert('RGBA'))
    
    
                # crop_region is not None if we are doing inpaint full res
    
                if crop_region is not None:
                    image = image.crop(crop_region)
                    image = images.resize_image(2, image, self.width, self.height)
    
    
                    if self.inpainting_fill != 1:
    
                        image = masking.fill(image, latent_mask)
    
                    self.color_corrections.append(setup_color_correction(image))
    
    
                image = np.array(image).astype(np.float32) / 255.0
                image = np.moveaxis(image, 2, 0)
    
                imgs.append(image)
    
            if len(imgs) == 1:
                batch_images = np.expand_dims(imgs[0], axis=0).repeat(self.batch_size, axis=0)