import itertools from typing import Optional import numpy as np import torch from PIL import Image, ImageDraw import math import cv2 def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'): latent_size = latent_image.size() latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]] if noise_device == "cpu": variation_generator = torch.manual_seed(variation_seed) else: torch.cuda.manual_seed(variation_seed) variation_generator = None variation_latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, generator=variation_generator, device=noise_device) variation_noise = variation_latent.expand(latent_image.size()[0], -1, -1, -1) if variation_strength == 0: return latent_image elif mask is None: result = (1 - variation_strength) * latent_image + variation_strength * variation_noise else: # this seems precision is not enough when variation_strength is 0.0 mixed_noise = mix_noise(latent_image, variation_noise, variation_strength, variation_method=variation_method) result = (mask == 1).float() * mixed_noise + (mask == 0).float() * latent_image return result # CREDIT: https://github.com/BlenderNeko/ComfyUI_Noise/blob/afb14757216257b12268c91845eac248727a55e2/nodes.py#L68 # https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3 def slerp(val, low, high): dims = low.shape low = low.reshape(dims[0], -1) high = high.reshape(dims[0], -1) low_norm = low/torch.norm(low, dim=1, keepdim=True) high_norm = high/torch.norm(high, dim=1, keepdim=True) low_norm[low_norm != low_norm] = 0.0 high_norm[high_norm != high_norm] = 0.0 omega = torch.acos((low_norm*high_norm).sum(1)) 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.reshape(dims) def mix_noise(from_noise, to_noise, strength, variation_method): to_noise = to_noise.to(from_noise.device) if variation_method == 'slerp': mixed_noise = slerp(strength, from_noise, to_noise) else: # linear mixed_noise = (1 - strength) * from_noise + strength * to_noise # NOTE: Since the variance of the Gaussian noise in mixed_noise has changed, it must be corrected through scaling. scale_factor = math.sqrt((1 - strength) ** 2 + strength ** 2) mixed_noise /= scale_factor return mixed_noise def prepare_noise(latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear"): """ creates random noise given a latent image and a seed. optional arg skip can be used to skip and discard x number of noise generations for a given seed """ latent_size = latent_image.size() latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]] if variation_strength is not None and variation_strength > 0 or incremental_seed_mode.startswith("variation str inc"): if noise_device == "cpu": variation_generator = torch.manual_seed(variation_seed) else: torch.cuda.manual_seed(variation_seed) variation_generator = None variation_latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, generator=variation_generator, device=noise_device) else: variation_latent = None def apply_variation(input_latent, strength_up=None): if variation_latent is None: return input_latent else: strength = variation_strength if strength_up is not None: strength += strength_up variation_noise = variation_latent.expand(input_latent.size()[0], -1, -1, -1) mixed_noise = mix_noise(input_latent, variation_noise, strength, variation_method) return mixed_noise # method: incremental seed batch noise if noise_inds is None and incremental_seed_mode == "incremental": batch_cnt = latent_size[0] latents = None for i in range(batch_cnt): if noise_device == "cpu": generator = torch.manual_seed(seed+i) else: torch.cuda.manual_seed(seed+i) generator = None latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device=noise_device) latent = apply_variation(latent) if latents is None: latents = latent else: latents = torch.cat((latents, latent), dim=0) return latents # method: incremental variation batch noise elif noise_inds is None and incremental_seed_mode.startswith("variation str inc"): batch_cnt = latent_size[0] latents = None for i in range(batch_cnt): if noise_device == "cpu": generator = torch.manual_seed(seed) else: torch.cuda.manual_seed(seed) generator = None latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device=noise_device) step = float(incremental_seed_mode[18:]) latent = apply_variation(latent, step*i) if latents is None: latents = latent else: latents = torch.cat((latents, latent), dim=0) return latents # method: comfy batch noise if noise_device == "cpu": generator = torch.manual_seed(seed) else: torch.cuda.manual_seed(seed) generator = None if noise_inds is None: latents = torch.randn(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device=noise_device) latents = apply_variation(latents) return latents unique_inds, inverse = np.unique(noise_inds, return_inverse=True) noises = [] for i in range(unique_inds[-1] + 1): noise = torch.randn([1] + list(latent_image.size())[1:], dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device=noise_device) if i in unique_inds: noises.append(noise) noises = [noises[i] for i in inverse] noises = torch.cat(noises, axis=0) return noises def pil2tensor(image): return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) def empty_pil_tensor(w=64, h=64): image = Image.new("RGB", (w, h)) draw = ImageDraw.Draw(image) draw.rectangle((0, 0, w-1, h-1), fill=(0, 0, 0)) return pil2tensor(image) def try_install_custom_node(custom_node_url, msg): try: import cm_global cm_global.try_call(api='cm.try-install-custom-node', sender="Inspire Pack", custom_node_url=custom_node_url, msg=msg) except Exception as e: print(msg) print(f"[Inspire Pack] ComfyUI-Manager is outdated. The custom node installation feature is not available.") def empty_latent(): return torch.zeros([1, 4, 8, 8]) # wildcard trick is taken from pythongossss's class AnyType(str): def __ne__(self, __value: object) -> bool: return False any_typ = AnyType("*") # author: Trung0246 ---> class TautologyStr(str): def __ne__(self, other): return False class ByPassTypeTuple(tuple): def __getitem__(self, index): if index > 0: index = 0 item = super().__getitem__(index) if isinstance(item, str): return TautologyStr(item) return item class TaggedCache: def __init__(self, tag_settings: Optional[dict]=None): self._tag_settings = tag_settings or {} # tag cache size self._data = {} def __getitem__(self, key): for tag_data in self._data.values(): if key in tag_data: return tag_data[key] raise KeyError(f'Key `{key}` does not exist') def __setitem__(self, key, value: tuple): # value: (tag: str, (islist: bool, data: *)) # if key already exists, pop old value for tag_data in self._data.values(): if key in tag_data: tag_data.pop(key, None) break tag = value[0] if tag not in self._data: try: from cachetools import LRUCache default_size = 20 if 'ckpt' in tag: default_size = 5 elif tag in ['latent', 'image']: default_size = 100 self._data[tag] = LRUCache(maxsize=self._tag_settings.get(tag, default_size)) except (ImportError, ModuleNotFoundError): # TODO: implement a simple lru dict self._data[tag] = {} self._data[tag][key] = value def __delitem__(self, key): for tag_data in self._data.values(): if key in tag_data: del tag_data[key] return raise KeyError(f'Key `{key}` does not exist') def __contains__(self, key): return any(key in tag_data for tag_data in self._data.values()) def items(self): yield from itertools.chain(*map(lambda x :x.items(), self._data.values())) def get(self, key, default=None): """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" for tag_data in self._data.values(): if key in tag_data: return tag_data[key] return default def clear(self): # clear all cache self._data = {} def make_3d_mask(mask): if len(mask.shape) == 4: return mask.squeeze(0) elif len(mask.shape) == 2: return mask.unsqueeze(0) return mask def dilate_mask(mask: torch.Tensor, dilation_factor: float) -> torch.Tensor: """Dilate a mask using a square kernel with a given dilation factor.""" kernel_size = int(dilation_factor * 2) + 1 kernel = np.ones((abs(kernel_size), abs(kernel_size)), np.uint8) masks = make_3d_mask(mask).numpy() dilated_masks = [] for m in masks: if dilation_factor > 0: m2 = cv2.dilate(m, kernel, iterations=1) else: m2 = cv2.erode(m, kernel, iterations=1) dilated_masks.append(torch.from_numpy(m2)) return torch.stack(dilated_masks) def flatten_non_zero_override(masks: torch.Tensor): """ flatten multiple layer mask tensor to 1 layer mask tensor. Override the lower layer with the tensor from the upper layer, but only override non-zero values. :param masks: 3d mask :return: flatten mask """ final_mask = masks[0] for i in range(1, masks.size(0)): non_zero_mask = masks[i] != 0 final_mask[non_zero_mask] = masks[i][non_zero_mask] return final_mask