import math import torch from typing import Any, Dict, Optional, Tuple from modules.AutoDetailer import AD_util, bbox, tensor_util from modules.AutoDetailer import SEGS from modules.Utilities import util from modules.AutoEncoders import VariationalAE from modules.Device import Device from modules.sample import ksampler_util, samplers, sampling, sampling_util # FIXME: Improve slow inference times class DifferentialDiffusion: """#### Class for applying differential diffusion to a model.""" def apply(self, model: torch.nn.Module) -> Tuple[torch.nn.Module]: """#### Apply differential diffusion to a model. #### Args: - `model` (torch.nn.Module): The input model. #### Returns: - `Tuple[torch.nn.Module]`: The modified model. """ model = model.clone() model.set_model_denoise_mask_function(self.forward) return (model,) def forward( self, sigma: torch.Tensor, denoise_mask: torch.Tensor, extra_options: Dict[str, Any], ) -> torch.Tensor: """#### Forward function for differential diffusion. #### Args: - `sigma` (torch.Tensor): The sigma tensor. - `denoise_mask` (torch.Tensor): The denoise mask tensor. - `extra_options` (Dict[str, Any]): Additional options. #### Returns: - `torch.Tensor`: The processed denoise mask tensor. """ model = extra_options["model"] step_sigmas = extra_options["sigmas"] sigma_to = model.inner_model.model_sampling.sigma_min sigma_from = step_sigmas[0] ts_from = model.inner_model.model_sampling.timestep(sigma_from) ts_to = model.inner_model.model_sampling.timestep(sigma_to) current_ts = model.inner_model.model_sampling.timestep(sigma[0]) threshold = (current_ts - ts_to) / (ts_from - ts_to) return (denoise_mask >= threshold).to(denoise_mask.dtype) def to_latent_image(pixels: torch.Tensor, vae: VariationalAE.VAE) -> torch.Tensor: """#### Convert pixels to a latent image using a VAE. #### Args: - `pixels` (torch.Tensor): The input pixel tensor. - `vae` (VariationalAE.VAE): The VAE model. #### Returns: - `torch.Tensor`: The latent image tensor. """ pixels.shape[1] pixels.shape[2] return VariationalAE.VAEEncode().encode(vae, pixels)[0] def calculate_sigmas2( model: torch.nn.Module, sampler: str, scheduler: str, steps: int ) -> torch.Tensor: """#### Calculate sigmas for a model. #### Args: - `model` (torch.nn.Module): The input model. - `sampler` (str): The sampler name. - `scheduler` (str): The scheduler name. - `steps` (int): The number of steps. #### Returns: - `torch.Tensor`: The calculated sigmas. """ return ksampler_util.calculate_sigmas( model.get_model_object("model_sampling"), scheduler, steps ) def get_noise_sampler( x: torch.Tensor, cpu: bool, total_sigmas: torch.Tensor, **kwargs ) -> Optional[sampling_util.BrownianTreeNoiseSampler]: """#### Get a noise sampler. #### Args: - `x` (torch.Tensor): The input tensor. - `cpu` (bool): Whether to use CPU. - `total_sigmas` (torch.Tensor): The total sigmas tensor. - `kwargs` (dict): Additional arguments. #### Returns: - `Optional[sampling_util.BrownianTreeNoiseSampler]`: The noise sampler. """ if "extra_args" in kwargs and "seed" in kwargs["extra_args"]: sigma_min, sigma_max = total_sigmas[total_sigmas > 0].min(), total_sigmas.max() seed = kwargs["extra_args"].get("seed", None) return sampling_util.BrownianTreeNoiseSampler( x, sigma_min, sigma_max, seed=seed, cpu=cpu ) return None def ksampler2( sampler_name: str, total_sigmas: torch.Tensor, extra_options: Dict[str, Any] = {}, inpaint_options: Dict[str, Any] = {}, pipeline: bool = False, ) -> sampling.KSAMPLER: """#### Get a ksampler. #### Args: - `sampler_name` (str): The sampler name. - `total_sigmas` (torch.Tensor): The total sigmas tensor. - `extra_options` (Dict[str, Any], optional): Additional options. Defaults to {}. - `inpaint_options` (Dict[str, Any], optional): Inpaint options. Defaults to {}. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `sampling.KSAMPLER`: The ksampler. """ if sampler_name == "dpmpp_2m_sde": def sample_dpmpp_sde(model, x, sigmas, pipeline, **kwargs): noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs) if noise_sampler is not None: kwargs["noise_sampler"] = noise_sampler return samplers.sample_dpmpp_2m_sde( model, x, sigmas, pipeline=pipeline, **kwargs ) sampler_function = sample_dpmpp_sde else: return sampling.sampler_object(sampler_name, pipeline=pipeline) return sampling.KSAMPLER(sampler_function, extra_options, inpaint_options) class Noise_RandomNoise: """#### Class for generating random noise.""" def __init__(self, seed: int): """#### Initialize the Noise_RandomNoise class. #### Args: - `seed` (int): The seed for random noise. """ self.seed = seed def generate_noise(self, input_latent: Dict[str, torch.Tensor]) -> torch.Tensor: """#### Generate random noise. #### Args: - `input_latent` (Dict[str, torch.Tensor]): The input latent tensor. #### Returns: - `torch.Tensor`: The generated noise tensor. """ latent_image = input_latent["samples"] batch_inds = ( input_latent["batch_index"] if "batch_index" in input_latent else None ) return ksampler_util.prepare_noise(latent_image, self.seed, batch_inds) def sample_with_custom_noise( model: torch.nn.Module, add_noise: bool, noise_seed: int, cfg: int, positive: Any, negative: Any, sampler: Any, sigmas: torch.Tensor, latent_image: Dict[str, torch.Tensor], noise: Optional[torch.Tensor] = None, callback: Optional[callable] = None, pipeline: bool = False, ) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """#### Sample with custom noise. #### Args: - `model` (torch.nn.Module): The input model. - `add_noise` (bool): Whether to add noise. - `noise_seed` (int): The noise seed. - `cfg` (int): Classifier-Free Guidance Scale - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `sampler` (Any): The sampler. - `sigmas` (torch.Tensor): The sigmas tensor. - `latent_image` (Dict[str, torch.Tensor]): The latent image tensor. - `noise` (Optional[torch.Tensor], optional): The noise tensor. Defaults to None. - `callback` (Optional[callable], optional): The callback function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]`: The sampled and denoised tensors. """ latent = latent_image latent_image = latent["samples"] out = latent.copy() out["samples"] = latent_image if noise is None: noise = Noise_RandomNoise(noise_seed).generate_noise(out) noise_mask = None if "noise_mask" in latent: noise_mask = latent["noise_mask"] disable_pbar = not util.PROGRESS_BAR_ENABLED device = Device.get_torch_device() noise = noise.to(device) latent_image = latent_image.to(device) if noise_mask is not None: noise_mask = noise_mask.to(device) samples = sampling.sample_custom( model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, disable_pbar=disable_pbar, seed=noise_seed, pipeline=pipeline, ) samples = samples.to(Device.intermediate_device()) out["samples"] = samples out_denoised = out return out, out_denoised def separated_sample( model: torch.nn.Module, add_noise: bool, seed: int, steps: int, cfg: int, sampler_name: str, scheduler: str, positive: Any, negative: Any, latent_image: Dict[str, torch.Tensor], start_at_step: Optional[int], end_at_step: Optional[int], return_with_leftover_noise: bool, sigma_ratio: float = 1.0, sampler_opt: Optional[Dict[str, Any]] = None, noise: Optional[torch.Tensor] = None, callback: Optional[callable] = None, scheduler_func: Optional[callable] = None, pipeline: bool = False, ) -> Dict[str, torch.Tensor]: """#### Perform separated sampling. #### Args: - `model` (torch.nn.Module): The input model. - `add_noise` (bool): Whether to add noise. - `seed` (int): The seed for random noise. - `steps` (int): The number of steps. - `cfg` (int): Classifier-Free Guidance Scale - `sampler_name` (str): The sampler name. - `scheduler` (str): The scheduler name. - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `latent_image` (Dict[str, torch.Tensor]): The latent image tensor. - `start_at_step` (Optional[int]): The step to start at. - `end_at_step` (Optional[int]): The step to end at. - `return_with_leftover_noise` (bool): Whether to return with leftover noise. - `sigma_ratio` (float, optional): The sigma ratio. Defaults to 1.0. - `sampler_opt` (Optional[Dict[str, Any]], optional): The sampler options. Defaults to None. - `noise` (Optional[torch.Tensor], optional): The noise tensor. Defaults to None. - `callback` (Optional[callable], optional): The callback function. Defaults to None. - `scheduler_func` (Optional[callable], optional): The scheduler function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Dict[str, torch.Tensor]`: The sampled tensor. """ total_sigmas = calculate_sigmas2(model, sampler_name, scheduler, steps) sigmas = total_sigmas if start_at_step is not None: sigmas = sigmas[start_at_step:] * sigma_ratio impact_sampler = ksampler2(sampler_name, total_sigmas, pipeline=pipeline) res = sample_with_custom_noise( model, add_noise, seed, cfg, positive, negative, impact_sampler, sigmas, latent_image, noise=noise, callback=callback, pipeline=pipeline, ) return res[1] def ksampler_wrapper( model: torch.nn.Module, seed: int, steps: int, cfg: int, sampler_name: str, scheduler: str, positive: Any, negative: Any, latent_image: Dict[str, torch.Tensor], denoise: float, refiner_ratio: Optional[float] = None, refiner_model: Optional[torch.nn.Module] = None, refiner_clip: Optional[Any] = None, refiner_positive: Optional[Any] = None, refiner_negative: Optional[Any] = None, sigma_factor: float = 1.0, noise: Optional[torch.Tensor] = None, scheduler_func: Optional[callable] = None, pipeline: bool = False, ) -> Dict[str, torch.Tensor]: """#### Wrapper for ksampler. #### Args: - `model` (torch.nn.Module): The input model. - `seed` (int): The seed for random noise. - `steps` (int): The number of steps. - `cfg` (int): Classifier-Free Guidance Scale - `sampler_name` (str): The sampler name. - `scheduler` (str): The scheduler name. - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `latent_image` (Dict[str, torch.Tensor]): The latent image tensor. - `denoise` (float): The denoise factor. - `refiner_ratio` (Optional[float], optional): The refiner ratio. Defaults to None. - `refiner_model` (Optional[torch.nn.Module], optional): The refiner model. Defaults to None. - `refiner_clip` (Optional[Any], optional): The refiner clip. Defaults to None. - `refiner_positive` (Optional[Any], optional): The refiner positive prompt. Defaults to None. - `refiner_negative` (Optional[Any], optional): The refiner negative prompt. Defaults to None. - `sigma_factor` (float, optional): The sigma factor. Defaults to 1.0. - `noise` (Optional[torch.Tensor], optional): The noise tensor. Defaults to None. - `scheduler_func` (Optional[callable], optional): The scheduler function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Dict[str, torch.Tensor]`: The refined latent tensor. """ advanced_steps = math.floor(steps / denoise) start_at_step = advanced_steps - steps end_at_step = start_at_step + steps refined_latent = separated_sample( model, True, seed, advanced_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step, False, sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func, pipeline=pipeline, ) return refined_latent def enhance_detail( image: torch.Tensor, model: torch.nn.Module, clip: Any, vae: VariationalAE.VAE, guide_size: int, guide_size_for_bbox: bool, max_size: int, bbox: Tuple[int, int, int, int], seed: int, steps: int, cfg: int, sampler_name: str, scheduler: str, positive: Any, negative: Any, denoise: float, noise_mask: Optional[torch.Tensor], force_inpaint: bool, wildcard_opt: Optional[Any] = None, wildcard_opt_concat_mode: Optional[Any] = None, detailer_hook: Optional[callable] = None, refiner_ratio: Optional[float] = None, refiner_model: Optional[torch.nn.Module] = None, refiner_clip: Optional[Any] = None, refiner_positive: Optional[Any] = None, refiner_negative: Optional[Any] = None, control_net_wrapper: Optional[Any] = None, cycle: int = 1, inpaint_model: bool = False, noise_mask_feather: int = 0, scheduler_func: Optional[callable] = None, pipeline: bool = False, ) -> Tuple[torch.Tensor, Optional[Any]]: """#### Enhance detail of an image. #### Args: - `image` (torch.Tensor): The input image tensor. - `model` (torch.nn.Module): The model. - `clip` (Any): The clip model. - `vae` (VariationalAE.VAE): The VAE model. - `guide_size` (int): The guide size. - `guide_size_for_bbox` (bool): Whether to use guide size for bbox. - `max_size` (int): The maximum size. - `bbox` (Tuple[int, int, int, int]): The bounding box. - `seed` (int): The seed for random noise. - `steps` (int): The number of steps. - `cfg` (int): Classifier-Free Guidance Scale - `sampler_name` (str): The sampler name. - `scheduler` (str): The scheduler name. - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `denoise` (float): The denoise factor. - `noise_mask` (Optional[torch.Tensor]): The noise mask tensor. - `force_inpaint` (bool): Whether to force inpaint. - `wildcard_opt` (Optional[Any], optional): The wildcard options. Defaults to None. - `wildcard_opt_concat_mode` (Optional[Any], optional): The wildcard concat mode. Defaults to None. - `detailer_hook` (Optional[callable], optional): The detailer hook. Defaults to None. - `refiner_ratio` (Optional[float], optional): The refiner ratio. Defaults to None. - `refiner_model` (Optional[torch.nn.Module], optional): The refiner model. Defaults to None. - `refiner_clip` (Optional[Any], optional): The refiner clip. Defaults to None. - `refiner_positive` (Optional[Any], optional): The refiner positive prompt. Defaults to None. - `refiner_negative` (Optional[Any], optional): The refiner negative prompt. Defaults to None. - `control_net_wrapper` (Optional[Any], optional): The control net wrapper. Defaults to None. - `cycle` (int, optional): The number of cycles. Defaults to 1. - `inpaint_model` (bool, optional): Whether to use inpaint model. Defaults to False. - `noise_mask_feather` (int, optional): The noise mask feather. Defaults to 0. - `scheduler_func` (Optional[callable], optional): The scheduler function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Tuple[torch.Tensor, Optional[Any]]`: The refined image tensor and optional cnet_pils. """ if noise_mask is not None: noise_mask = tensor_util.tensor_gaussian_blur_mask( noise_mask, noise_mask_feather ) noise_mask = noise_mask.squeeze(3) h = image.shape[1] w = image.shape[2] bbox_h = bbox[3] - bbox[1] bbox_w = bbox[2] - bbox[0] # for cropped_size upscale = guide_size / min(w, h) new_w = int(w * upscale) new_h = int(h * upscale) if new_w > max_size or new_h > max_size: upscale *= max_size / max(new_w, new_h) new_w = int(w * upscale) new_h = int(h * upscale) if upscale <= 1.0 or new_w == 0 or new_h == 0: print("Detailer: force inpaint") upscale = 1.0 new_w = w new_h = h print( f"Detailer: segment upscale for ({bbox_w, bbox_h}) | crop region {w, h} x {upscale} -> {new_w, new_h}" ) # upscale upscaled_image = tensor_util.tensor_resize(image, new_w, new_h) cnet_pils = None # prepare mask latent_image = to_latent_image(upscaled_image, vae) if noise_mask is not None: latent_image["noise_mask"] = noise_mask refined_latent = latent_image # ksampler for i in range(0, cycle): ( model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, _upscaled_latent2, denoise2, ) = ( model, seed + i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, ) noise = None refined_latent = ksampler_wrapper( model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, noise=noise, scheduler_func=scheduler_func, pipeline=pipeline, ) # non-latent downscale - latent downscale cause bad quality try: # try to decode image normally refined_image = vae.decode(refined_latent["samples"]) except Exception: # usually an out-of-memory exception from the decode, so try a tiled approach refined_image = vae.decode_tiled( refined_latent["samples"], tile_x=64, tile_y=64, ) # downscale refined_image = tensor_util.tensor_resize(refined_image, w, h) # prevent mixing of device refined_image = refined_image.cpu() # don't convert to latent - latent break image # preserving pil is much better return refined_image, cnet_pils class DetailerForEach: """#### Class for detailing each segment of an image.""" @staticmethod def do_detail( image: torch.Tensor, segs: Tuple[torch.Tensor, Any], model: torch.nn.Module, clip: Any, vae: VariationalAE.VAE, guide_size: int, guide_size_for_bbox: bool, max_size: int, seed: int, steps: int, cfg: int, sampler_name: str, scheduler: str, positive: Any, negative: Any, denoise: float, feather: int, noise_mask: Optional[torch.Tensor], force_inpaint: bool, wildcard_opt: Optional[Any] = None, detailer_hook: Optional[callable] = None, refiner_ratio: Optional[float] = None, refiner_model: Optional[torch.nn.Module] = None, refiner_clip: Optional[Any] = None, refiner_positive: Optional[Any] = None, refiner_negative: Optional[Any] = None, cycle: int = 1, inpaint_model: bool = False, noise_mask_feather: int = 0, scheduler_func_opt: Optional[callable] = None, pipeline: bool = False, ) -> Tuple[torch.Tensor, list, list, list, list, Tuple[torch.Tensor, list]]: """#### Perform detailing on each segment of an image. #### Args: - `image` (torch.Tensor): The input image tensor. - `segs` (Tuple[torch.Tensor, Any]): The segments. - `model` (torch.nn.Module): The model. - `clip` (Any): The clip model. - `vae` (VariationalAE.VAE): The VAE model. - `guide_size` (int): The guide size. - `guide_size_for_bbox` (bool): Whether to use guide size for bbox. - `max_size` (int): The maximum size. - `seed` (int): The seed for random noise. - `steps` (int): The number of steps. - `cfg` (int): Classifier-Free Guidance Scale. - `sampler_name` (str): The sampler name. - `scheduler` (str): The scheduler name. - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `denoise` (float): The denoise factor. - `feather` (int): The feather value. - `noise_mask` (Optional[torch.Tensor]): The noise mask tensor. - `force_inpaint` (bool): Whether to force inpaint. - `wildcard_opt` (Optional[Any], optional): The wildcard options. Defaults to None. - `detailer_hook` (Optional[callable], optional): The detailer hook. Defaults to None. - `refiner_ratio` (Optional[float], optional): The refiner ratio. Defaults to None. - `refiner_model` (Optional[torch.nn.Module], optional): The refiner model. Defaults to None. - `refiner_clip` (Optional[Any], optional): The refiner clip. Defaults to None. - `refiner_positive` (Optional[Any], optional): The refiner positive prompt. Defaults to None. - `refiner_negative` (Optional[Any], optional): The refiner negative prompt. Defaults to None. - `cycle` (int, optional): The number of cycles. Defaults to 1. - `inpaint_model` (bool, optional): Whether to use inpaint model. Defaults to False. - `noise_mask_feather` (int, optional): The noise mask feather. Defaults to 0. - `scheduler_func_opt` (Optional[callable], optional): The scheduler function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Tuple[torch.Tensor, list, list, list, list, Tuple[torch.Tensor, list]]`: The detailed image tensor, cropped list, enhanced list, enhanced alpha list, cnet PIL list, and new segments. """ image = image.clone() enhanced_alpha_list = [] enhanced_list = [] cropped_list = [] cnet_pil_list = [] segs = AD_util.segs_scale_match(segs, image.shape) new_segs = [] wildcard_concat_mode = None wmode, wildcard_chooser = bbox.process_wildcard_for_segs(wildcard_opt) ordered_segs = segs[1] if ( noise_mask_feather > 0 and "denoise_mask_function" not in model.model_options ): model = DifferentialDiffusion().apply(model)[0] for i, seg in enumerate(ordered_segs): cropped_image = AD_util.crop_ndarray4( image.cpu().numpy(), seg.crop_region ) # Never use seg.cropped_image to handle overlapping area cropped_image = tensor_util.to_tensor(cropped_image) mask = tensor_util.to_tensor(seg.cropped_mask) mask = tensor_util.tensor_gaussian_blur_mask(mask, feather) is_mask_all_zeros = (seg.cropped_mask == 0).all().item() if is_mask_all_zeros: print("Detailer: segment skip [empty mask]") continue cropped_mask = seg.cropped_mask seg_seed, wildcard_item = wildcard_chooser.get(seg) seg_seed = seed + i if seg_seed is None else seg_seed cropped_positive = [ [ condition, { k: ( crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v ) for k, v in details.items() }, ] for condition, details in positive ] cropped_negative = [ [ condition, { k: ( crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v ) for k, v in details.items() }, ] for condition, details in negative ] orig_cropped_image = cropped_image.clone() enhanced_image, cnet_pils = enhance_detail( cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler, cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint, wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode, detailer_hook=detailer_hook, refiner_ratio=refiner_ratio, refiner_model=refiner_model, refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper, cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt, pipeline=pipeline, ) if enhanced_image is not None: # don't latent composite-> converting to latent caused poor quality # use image paste image = image.cpu() enhanced_image = enhanced_image.cpu() tensor_util.tensor_paste( image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask, ) # this code affecting to `cropped_image`. enhanced_list.append(enhanced_image) # Convert enhanced_pil_alpha to RGBA mode enhanced_image_alpha = tensor_util.tensor_convert_rgba(enhanced_image) new_seg_image = ( enhanced_image.numpy() ) # alpha should not be applied to seg_image # Apply the mask mask = tensor_util.tensor_resize( mask, *tensor_util.tensor_get_size(enhanced_image) ) tensor_util.tensor_putalpha(enhanced_image_alpha, mask) enhanced_alpha_list.append(enhanced_image_alpha) cropped_list.append(orig_cropped_image) # NOTE: Don't use `cropped_image` new_seg = SEGS.SEG( new_seg_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper, ) new_segs.append(new_seg) image_tensor = tensor_util.tensor_convert_rgb(image) cropped_list.sort(key=lambda x: x.shape, reverse=True) enhanced_list.sort(key=lambda x: x.shape, reverse=True) enhanced_alpha_list.sort(key=lambda x: x.shape, reverse=True) return ( image_tensor, cropped_list, enhanced_list, enhanced_alpha_list, cnet_pil_list, (segs[0], new_segs), ) def empty_pil_tensor(w: int = 64, h: int = 64) -> torch.Tensor: """#### Create an empty PIL tensor. #### Args: - `w` (int, optional): The width of the tensor. Defaults to 64. - `h` (int, optional): The height of the tensor. Defaults to 64. #### Returns: - `torch.Tensor`: The empty tensor. """ return torch.zeros((1, h, w, 3), dtype=torch.float32) class DetailerForEachTest(DetailerForEach): """#### Test class for DetailerForEach.""" def doit( self, image: torch.Tensor, segs: Any, model: torch.nn.Module, clip: Any, vae: VariationalAE.VAE, guide_size: int, guide_size_for: bool, max_size: int, seed: int, steps: int, cfg: Any, sampler_name: str, scheduler: str, positive: Any, negative: Any, denoise: float, feather: int, noise_mask: Optional[torch.Tensor], force_inpaint: bool, wildcard: Optional[Any], detailer_hook: Optional[callable] = None, cycle: int = 1, inpaint_model: bool = False, noise_mask_feather: int = 0, scheduler_func_opt: Optional[callable] = None, pipeline: bool = False, ) -> Tuple[torch.Tensor, list, list, list, list]: """#### Perform detail enhancement for testing. #### Args: - `image` (torch.Tensor): The input image tensor. - `segs` (Any): The segments. - `model` (torch.nn.Module): The model. - `clip` (Any): The clip model. - `vae` (VariationalAE.VAE): The VAE model. - `guide_size` (int): The guide size. - `guide_size_for` (bool): Whether to use guide size for. - `max_size` (int): The maximum size. - `seed` (int): The seed for random noise. - `steps` (int): The number of steps. - `cfg` (Any): The configuration. - `sampler_name` (str): The sampler name. - `scheduler` (str): The scheduler name. - `positive` (Any): The positive prompt. - `negative` (Any): The negative prompt. - `denoise` (float): The denoise factor. - `feather` (int): The feather value. - `noise_mask` (Optional[torch.Tensor]): The noise mask tensor. - `force_inpaint` (bool): Whether to force inpaint. - `wildcard` (Optional[Any]): The wildcard options. - `detailer_hook` (Optional[callable], optional): The detailer hook. Defaults to None. - `cycle` (int, optional): The number of cycles. Defaults to 1. - `inpaint_model` (bool, optional): Whether to use inpaint model. Defaults to False. - `noise_mask_feather` (int, optional): The noise mask feather. Defaults to 0. - `scheduler_func_opt` (Optional[callable], optional): The scheduler function. Defaults to None. - `pipeline` (bool, optional): Whether to use pipeline. Defaults to False. #### Returns: - `Tuple[torch.Tensor, list, list, list, list]`: The enhanced image tensor, cropped list, cropped enhanced list, cropped enhanced alpha list, and cnet PIL list. """ ( enhanced_img, cropped, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list, new_segs, ) = DetailerForEach.do_detail( image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard, detailer_hook, cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt, pipeline=pipeline, ) cnet_pil_list = [empty_pil_tensor()] return ( enhanced_img, cropped, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list, )