|
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:
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
mixed_noise = (1 - strength) * from_noise + strength * to_noise
|
|
|
|
|
|
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
|
|
|
|
|
|
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
|
|
|
|
|
|
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
|
|
|
|
|
|
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])
|
|
|
|
|
|
class AnyType(str):
|
|
def __ne__(self, __value: object) -> bool:
|
|
return False
|
|
|
|
any_typ = AnyType("*")
|
|
|
|
|
|
|
|
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 {}
|
|
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):
|
|
|
|
|
|
|
|
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):
|
|
|
|
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):
|
|
|
|
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
|
|
|