Spaces:
Running
on
Zero
Running
on
Zero
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.""" | |
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, | |
) | |