Refactor for ComfyUI Manager compatibility
This commit is contained in:
@@ -1,189 +1,189 @@
|
||||
import torch
|
||||
import math
|
||||
|
||||
def normalize(latent, target_min=None, target_max=None):
|
||||
"""
|
||||
Normalize a tensor `latent` between `target_min` and `target_max`.
|
||||
|
||||
Args:
|
||||
latent (torch.Tensor): The input tensor to be normalized.
|
||||
target_min (float, optional): The minimum value after normalization.
|
||||
- When `None` min will be tensor min range value.
|
||||
target_max (float, optional): The maximum value after normalization.
|
||||
- When `None` max will be tensor max range value.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The normalized tensor
|
||||
"""
|
||||
min_val = latent.min()
|
||||
max_val = latent.max()
|
||||
|
||||
if target_min is None:
|
||||
target_min = min_val
|
||||
if target_max is None:
|
||||
target_max = max_val
|
||||
|
||||
normalized = (latent - min_val) / (max_val - min_val)
|
||||
scaled = normalized * (target_max - target_min) + target_min
|
||||
return scaled
|
||||
|
||||
def slerp(a, b, t):
|
||||
"""
|
||||
Perform Spherical Linear Interpolation (SLERP) between two tensors.
|
||||
|
||||
This function interpolates between two input tensors `a` and `b` using SLERP,
|
||||
which is a method for smoothly transitioning between orientations or vectors
|
||||
represented as tensors.
|
||||
|
||||
Args:
|
||||
a (tensor): The first input tensor.
|
||||
b (tensor): The second input tensor.
|
||||
t (float): The blending factor, a value between 0 and 1 that controls the interpolation.
|
||||
|
||||
Returns:
|
||||
tensor: The result of SLERP interpolation between `a` and `b`.
|
||||
|
||||
Note:
|
||||
SLERP provides a smooth, shortest-path interpolation between two orientations or vectors
|
||||
represented as tensors. It's commonly used in applications like 3D graphics and robotics.
|
||||
"""
|
||||
if a.shape != b.shape:
|
||||
raise ValueError("Input tensors a and b must have the same shape.")
|
||||
|
||||
a = torch.nn.functional.normalize(a, dim=-1)
|
||||
b = torch.nn.functional.normalize(b, dim=-1)
|
||||
|
||||
dot_product = torch.sum(a * b, dim=-1).clamp(-1.0, 1.0)
|
||||
angle = torch.acos(dot_product)
|
||||
|
||||
slerp_result = (
|
||||
(a * torch.sin((1 - t) * angle) + b * torch.sin(t * angle)) /
|
||||
torch.sin(angle)
|
||||
)
|
||||
|
||||
slerp_result = normalize(slerp_result)
|
||||
|
||||
return slerp_result
|
||||
|
||||
def hslerp(a, b, t):
|
||||
"""
|
||||
Perform Hybrid Spherical Linear Interpolation (HSLERP) between two tensors.
|
||||
|
||||
This function combines two input tensors `a` and `b` using HSLERP, which is a specialized
|
||||
interpolation method for smooth transitions between orientations or colors.
|
||||
|
||||
Args:
|
||||
a (tensor): The first input tensor.
|
||||
b (tensor): The second input tensor.
|
||||
t (float): The blending factor, a value between 0 and 1 that controls the interpolation.
|
||||
|
||||
Returns:
|
||||
tensor: The result of HSLERP interpolation between `a` and `b`.
|
||||
|
||||
Note:
|
||||
HSLERP provides smooth transitions between orientations or colors, particularly useful
|
||||
in applications like image processing and 3D graphics.
|
||||
"""
|
||||
if a.shape != b.shape:
|
||||
raise ValueError("Input tensors a and b must have the same shape.")
|
||||
|
||||
num_channels = a.size(1)
|
||||
|
||||
interpolation_tensor = torch.zeros(1, num_channels, 1, 1, device=a.device, dtype=a.dtype)
|
||||
interpolation_tensor[0, 0, 0, 0] = 1.0
|
||||
|
||||
result = (1 - t) * a + t * b
|
||||
|
||||
if t < 0.5:
|
||||
result += (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
|
||||
else:
|
||||
result -= (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
|
||||
|
||||
return result
|
||||
|
||||
import torch
|
||||
|
||||
blending_modes = {
|
||||
# Linearly combines the two input tensors a and b using the parameter t.
|
||||
'add': lambda a, b, t: (a * t + b * (1 - t)),
|
||||
|
||||
# Interpolates between tensors a and b using normalized linear interpolation.
|
||||
'bislerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Interpolates between tensors a and b using cosine interpolation.
|
||||
'cosine interp': lambda a, b, t: (a + b - (a - b) * torch.cos(t * torch.tensor(math.pi))) / 2,
|
||||
|
||||
# Interpolates between tensors a and b using cubic interpolation.
|
||||
'cuberp': lambda a, b, t: a + (b - a) * (3 * t ** 2 - 2 * t ** 3),
|
||||
|
||||
# Computes the absolute difference between tensors a and b, scaled by t.
|
||||
'difference': lambda a, b, t: (abs(a - b) * t),
|
||||
|
||||
# Combines tensors a and b using an exclusion formula, scaled by t.
|
||||
'exclusion': lambda a, b, t: ((a + b - 2 * a * b) * t),
|
||||
|
||||
# Interpolates between tensors a and b using normalized linear interpolation,
|
||||
# with a twist when t is greater than or equal to 0.5.
|
||||
'hslerp': lambda a, b, t: (a * (1 - t) + b * t) if t < 0.5 else (a * t + b * (1 - t)),
|
||||
|
||||
# Adds tensor b to tensor a, scaled by t.
|
||||
'inject': lambda a, b, t: (a + b * t),
|
||||
|
||||
# Interpolates between tensors a and b using linear interpolation.
|
||||
'lerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Generates random values and combines tensors a and b with random weights, scaled by t.
|
||||
'random': lambda a, b, t: (a + (torch.rand_like(b) * b - a) * t),
|
||||
|
||||
# Interpolates between tensors a and b using spherical linear interpolation (SLERP).
|
||||
'slerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Subtracts tensor b from tensor a, scaled by t.
|
||||
'subtract': lambda a, b, t: (a * t - b * t),
|
||||
}
|
||||
|
||||
class WAS_ConditioningBlend:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning_a": ("CONDITIONING", ),
|
||||
"conditioning_b": ("CONDITIONING", ),
|
||||
"blending_mode": (list(blending_modes.keys()), ),
|
||||
"blending_strength": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
RETURN_NAMES = ("conditioning",)
|
||||
FUNCTION = "combine"
|
||||
|
||||
CATEGORY = "conditioning"
|
||||
|
||||
def combine(self, conditioning_a, conditioning_b, blending_mode, blending_strength, seed):
|
||||
|
||||
if seed > 0:
|
||||
torch.manual_seed(seed)
|
||||
|
||||
a = conditioning_a[0][0].clone()
|
||||
b = conditioning_b[0][0].clone()
|
||||
|
||||
pa = conditioning_a[0][1]["pooled_output"].clone()
|
||||
pb = conditioning_b[0][1]["pooled_output"].clone()
|
||||
|
||||
cond = normalize(blending_modes[blending_mode](a, b, 1 - blending_strength))
|
||||
pooled = normalize(blending_modes[blending_mode](pa, pb, 1 - blending_strength))
|
||||
|
||||
conditioning = [[cond, {"pooled_output": pooled}]]
|
||||
|
||||
return (conditioning, )
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ConditioningBlend": WAS_ConditioningBlend,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ConditioningBlend": "Conditioning (Blend)",
|
||||
}
|
||||
import torch
|
||||
import math
|
||||
|
||||
def normalize(latent, target_min=None, target_max=None):
|
||||
"""
|
||||
Normalize a tensor `latent` between `target_min` and `target_max`.
|
||||
|
||||
Args:
|
||||
latent (torch.Tensor): The input tensor to be normalized.
|
||||
target_min (float, optional): The minimum value after normalization.
|
||||
- When `None` min will be tensor min range value.
|
||||
target_max (float, optional): The maximum value after normalization.
|
||||
- When `None` max will be tensor max range value.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The normalized tensor
|
||||
"""
|
||||
min_val = latent.min()
|
||||
max_val = latent.max()
|
||||
|
||||
if target_min is None:
|
||||
target_min = min_val
|
||||
if target_max is None:
|
||||
target_max = max_val
|
||||
|
||||
normalized = (latent - min_val) / (max_val - min_val)
|
||||
scaled = normalized * (target_max - target_min) + target_min
|
||||
return scaled
|
||||
|
||||
def slerp(a, b, t):
|
||||
"""
|
||||
Perform Spherical Linear Interpolation (SLERP) between two tensors.
|
||||
|
||||
This function interpolates between two input tensors `a` and `b` using SLERP,
|
||||
which is a method for smoothly transitioning between orientations or vectors
|
||||
represented as tensors.
|
||||
|
||||
Args:
|
||||
a (tensor): The first input tensor.
|
||||
b (tensor): The second input tensor.
|
||||
t (float): The blending factor, a value between 0 and 1 that controls the interpolation.
|
||||
|
||||
Returns:
|
||||
tensor: The result of SLERP interpolation between `a` and `b`.
|
||||
|
||||
Note:
|
||||
SLERP provides a smooth, shortest-path interpolation between two orientations or vectors
|
||||
represented as tensors. It's commonly used in applications like 3D graphics and robotics.
|
||||
"""
|
||||
if a.shape != b.shape:
|
||||
raise ValueError("Input tensors a and b must have the same shape.")
|
||||
|
||||
a = torch.nn.functional.normalize(a, dim=-1)
|
||||
b = torch.nn.functional.normalize(b, dim=-1)
|
||||
|
||||
dot_product = torch.sum(a * b, dim=-1).clamp(-1.0, 1.0)
|
||||
angle = torch.acos(dot_product)
|
||||
|
||||
slerp_result = (
|
||||
(a * torch.sin((1 - t) * angle) + b * torch.sin(t * angle)) /
|
||||
torch.sin(angle)
|
||||
)
|
||||
|
||||
slerp_result = normalize(slerp_result)
|
||||
|
||||
return slerp_result
|
||||
|
||||
def hslerp(a, b, t):
|
||||
"""
|
||||
Perform Hybrid Spherical Linear Interpolation (HSLERP) between two tensors.
|
||||
|
||||
This function combines two input tensors `a` and `b` using HSLERP, which is a specialized
|
||||
interpolation method for smooth transitions between orientations or colors.
|
||||
|
||||
Args:
|
||||
a (tensor): The first input tensor.
|
||||
b (tensor): The second input tensor.
|
||||
t (float): The blending factor, a value between 0 and 1 that controls the interpolation.
|
||||
|
||||
Returns:
|
||||
tensor: The result of HSLERP interpolation between `a` and `b`.
|
||||
|
||||
Note:
|
||||
HSLERP provides smooth transitions between orientations or colors, particularly useful
|
||||
in applications like image processing and 3D graphics.
|
||||
"""
|
||||
if a.shape != b.shape:
|
||||
raise ValueError("Input tensors a and b must have the same shape.")
|
||||
|
||||
num_channels = a.size(1)
|
||||
|
||||
interpolation_tensor = torch.zeros(1, num_channels, 1, 1, device=a.device, dtype=a.dtype)
|
||||
interpolation_tensor[0, 0, 0, 0] = 1.0
|
||||
|
||||
result = (1 - t) * a + t * b
|
||||
|
||||
if t < 0.5:
|
||||
result += (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
|
||||
else:
|
||||
result -= (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
|
||||
|
||||
return result
|
||||
|
||||
import torch
|
||||
|
||||
blending_modes = {
|
||||
# Linearly combines the two input tensors a and b using the parameter t.
|
||||
'add': lambda a, b, t: (a * t + b * (1 - t)),
|
||||
|
||||
# Interpolates between tensors a and b using normalized linear interpolation.
|
||||
'bislerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Interpolates between tensors a and b using cosine interpolation.
|
||||
'cosine interp': lambda a, b, t: (a + b - (a - b) * torch.cos(t * torch.tensor(math.pi))) / 2,
|
||||
|
||||
# Interpolates between tensors a and b using cubic interpolation.
|
||||
'cuberp': lambda a, b, t: a + (b - a) * (3 * t ** 2 - 2 * t ** 3),
|
||||
|
||||
# Computes the absolute difference between tensors a and b, scaled by t.
|
||||
'difference': lambda a, b, t: (abs(a - b) * t),
|
||||
|
||||
# Combines tensors a and b using an exclusion formula, scaled by t.
|
||||
'exclusion': lambda a, b, t: ((a + b - 2 * a * b) * t),
|
||||
|
||||
# Interpolates between tensors a and b using normalized linear interpolation,
|
||||
# with a twist when t is greater than or equal to 0.5.
|
||||
'hslerp': lambda a, b, t: (a * (1 - t) + b * t) if t < 0.5 else (a * t + b * (1 - t)),
|
||||
|
||||
# Adds tensor b to tensor a, scaled by t.
|
||||
'inject': lambda a, b, t: (a + b * t),
|
||||
|
||||
# Interpolates between tensors a and b using linear interpolation.
|
||||
'lerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Generates random values and combines tensors a and b with random weights, scaled by t.
|
||||
'random': lambda a, b, t: (a + (torch.rand_like(b) * b - a) * t),
|
||||
|
||||
# Interpolates between tensors a and b using spherical linear interpolation (SLERP).
|
||||
'slerp': lambda a, b, t: (a * (1 - t) + b * t),
|
||||
|
||||
# Subtracts tensor b from tensor a, scaled by t.
|
||||
'subtract': lambda a, b, t: (a * t - b * t),
|
||||
}
|
||||
|
||||
class WAS_ConditioningBlend:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"conditioning_a": ("CONDITIONING", ),
|
||||
"conditioning_b": ("CONDITIONING", ),
|
||||
"blending_mode": (list(blending_modes.keys()), ),
|
||||
"blending_strength": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
RETURN_NAMES = ("conditioning",)
|
||||
FUNCTION = "combine"
|
||||
|
||||
CATEGORY = "conditioning"
|
||||
|
||||
def combine(self, conditioning_a, conditioning_b, blending_mode, blending_strength, seed):
|
||||
|
||||
if seed > 0:
|
||||
torch.manual_seed(seed)
|
||||
|
||||
a = conditioning_a[0][0].clone()
|
||||
b = conditioning_b[0][0].clone()
|
||||
|
||||
pa = conditioning_a[0][1]["pooled_output"].clone()
|
||||
pb = conditioning_b[0][1]["pooled_output"].clone()
|
||||
|
||||
cond = normalize(blending_modes[blending_mode](a, b, 1 - blending_strength))
|
||||
pooled = normalize(blending_modes[blending_mode](pa, pb, 1 - blending_strength))
|
||||
|
||||
conditioning = [[cond, {"pooled_output": pooled}]]
|
||||
|
||||
return (conditioning, )
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ConditioningBlend": WAS_ConditioningBlend,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ConditioningBlend": "Conditioning (Blend)",
|
||||
}
|
||||
@@ -1,61 +1,61 @@
|
||||
# Provides demonstration of PR https://github.com/comfyanonymous/ComfyUI/pull/1574/commits/297d1cff422198806cda40e4b6d71a6e6aa05453
|
||||
|
||||
import torch
|
||||
|
||||
class WAS_VAEEncodeForInpaint:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", ), "mask": ("MASK", ), "mask_offset": ("INT", {"default": 6, "min": -128, "max": 128, "step": 1}),}}
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "encode"
|
||||
|
||||
CATEGORY = "latent/inpaint"
|
||||
|
||||
def encode(self, vae, pixels, mask, mask_offset=6):
|
||||
x = (pixels.shape[1] // 8) * 8
|
||||
y = (pixels.shape[2] // 8) * 8
|
||||
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear")
|
||||
|
||||
pixels = pixels.clone()
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
x_offset = (pixels.shape[1] % 8) // 2
|
||||
y_offset = (pixels.shape[2] % 8) // 2
|
||||
pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
|
||||
mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset]
|
||||
|
||||
mask_erosion = self.modify_mask(mask, mask_offset)
|
||||
|
||||
m = (1.0 - mask_erosion.round()).squeeze(1)
|
||||
for i in range(3):
|
||||
pixels[:,:,:,i] -= 0.5
|
||||
pixels[:,:,:,i] *= m
|
||||
pixels[:,:,:,i] += 0.5
|
||||
t = vae.encode(pixels)
|
||||
|
||||
return ({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())}, )
|
||||
|
||||
def modify_mask(self, mask, modify_by):
|
||||
if modify_by == 0:
|
||||
return mask
|
||||
if modify_by > 0:
|
||||
kernel_size = 2 * modify_by + 1
|
||||
kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size))
|
||||
padding = modify_by
|
||||
modified_mask = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1)
|
||||
else:
|
||||
kernel_size = 2 * abs(modify_by) + 1
|
||||
kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size))
|
||||
padding = abs(modify_by)
|
||||
eroded_mask = torch.nn.functional.conv2d(1 - mask.round(), kernel_tensor, padding=padding)
|
||||
modified_mask = torch.clamp(1 - eroded_mask, 0, 1)
|
||||
return modified_mask
|
||||
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VAEEncodeForInpaint (WAS)": WAS_VAEEncodeForInpaint,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VAEEncodeForInpaint (WAS)": "Inpainting VAE Encode (WAS)",
|
||||
# Provides demonstration of PR https://github.com/comfyanonymous/ComfyUI/pull/1574/commits/297d1cff422198806cda40e4b6d71a6e6aa05453
|
||||
|
||||
import torch
|
||||
|
||||
class WAS_VAEEncodeForInpaint:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", ), "mask": ("MASK", ), "mask_offset": ("INT", {"default": 6, "min": -128, "max": 128, "step": 1}),}}
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "encode"
|
||||
|
||||
CATEGORY = "latent/inpaint"
|
||||
|
||||
def encode(self, vae, pixels, mask, mask_offset=6):
|
||||
x = (pixels.shape[1] // 8) * 8
|
||||
y = (pixels.shape[2] // 8) * 8
|
||||
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear")
|
||||
|
||||
pixels = pixels.clone()
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
x_offset = (pixels.shape[1] % 8) // 2
|
||||
y_offset = (pixels.shape[2] % 8) // 2
|
||||
pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
|
||||
mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset]
|
||||
|
||||
mask_erosion = self.modify_mask(mask, mask_offset)
|
||||
|
||||
m = (1.0 - mask_erosion.round()).squeeze(1)
|
||||
for i in range(3):
|
||||
pixels[:,:,:,i] -= 0.5
|
||||
pixels[:,:,:,i] *= m
|
||||
pixels[:,:,:,i] += 0.5
|
||||
t = vae.encode(pixels)
|
||||
|
||||
return ({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())}, )
|
||||
|
||||
def modify_mask(self, mask, modify_by):
|
||||
if modify_by == 0:
|
||||
return mask
|
||||
if modify_by > 0:
|
||||
kernel_size = 2 * modify_by + 1
|
||||
kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size))
|
||||
padding = modify_by
|
||||
modified_mask = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1)
|
||||
else:
|
||||
kernel_size = 2 * abs(modify_by) + 1
|
||||
kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size))
|
||||
padding = abs(modify_by)
|
||||
eroded_mask = torch.nn.functional.conv2d(1 - mask.round(), kernel_tensor, padding=padding)
|
||||
modified_mask = torch.clamp(1 - eroded_mask, 0, 1)
|
||||
return modified_mask
|
||||
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VAEEncodeForInpaint (WAS)": WAS_VAEEncodeForInpaint,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VAEEncodeForInpaint (WAS)": "Inpainting VAE Encode (WAS)",
|
||||
}
|
||||
@@ -1,101 +1,101 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps, ImageFilter, ImageEnhance
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
# Vivid Light and Overlay methods adopted from layeris (an overlooked gem)
|
||||
# https://github.com/subwaymatch/layer-is-python
|
||||
def vivid_light(A, B, opacity=1.0):
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
b = np.where(B > 0, 1 - (1 - A) / (2 * B), 0)
|
||||
d = np.where(B < 1, A / (2 * (1 - B)), 1)
|
||||
|
||||
result = np.clip(np.where(B <= 0.5, b, d), 0, 1)
|
||||
return alpha_blend(A, result, opacity)
|
||||
|
||||
def overlay(A, B, opacity=1.0):
|
||||
B = rgb_float_if_hex(B)
|
||||
d1 = (2 * A) * B
|
||||
d2 = 1 - 2 * (1 - A) * (1 - B)
|
||||
result = np.where(A <= 0.5, d1, d2)
|
||||
return alpha_blend(A, result, opacity)
|
||||
|
||||
def alpha_blend(base, blend, opacity):
|
||||
if opacity < 1.0:
|
||||
return base * (1.0 - opacity) + blend * opacity
|
||||
return blend
|
||||
|
||||
def hex_to_rgb_float(hex_string):
|
||||
return np.array(list((int(hex_string.lstrip('#')[i:i + 2], 16) / 255) for i in (0, 2, 4)))
|
||||
|
||||
def rgb_float_if_hex(blend_data):
|
||||
if isinstance(blend_data, str):
|
||||
return hex_to_rgb_float(blend_data)
|
||||
return blend_data
|
||||
|
||||
def vivid_sharpen(image, radius=5, strength=1.0):
|
||||
original = image.copy()
|
||||
sg = Image.new('RGB', original.size, (255, 255, 255))
|
||||
sg.paste(original, (0, 0))
|
||||
sg = ImageOps.invert(sg)
|
||||
sg = sg.filter(ImageFilter.GaussianBlur(radius=radius))
|
||||
|
||||
original_data = np.array(original).astype(float) / 255.0
|
||||
sg_data = np.array(sg).astype(float) / 255.0
|
||||
|
||||
result_data = vivid_light(original_data, sg_data, 1.0)
|
||||
result_data = overlay(original_data, result_data, 1.0)
|
||||
|
||||
result_image = Image.fromarray((result_data * 255).astype('uint8'))
|
||||
result_image = Image.blend(original, result_image, strength)
|
||||
|
||||
return result_image
|
||||
|
||||
class VividSharpen:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"radius": ("FLOAT", {"default": 1.5, "min": 0.01, "max": 64.0, "step": 0.01}),
|
||||
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
|
||||
FUNCTION = "sharpen"
|
||||
|
||||
CATEGORY = "image/postprocessing"
|
||||
|
||||
def sharpen(self, images, radius, strength):
|
||||
|
||||
results = []
|
||||
if images.size(0) > 1:
|
||||
for image in images:
|
||||
image = tensor2pil(image)
|
||||
results.append(pil2tensor(vivid_sharpen(image, radius=radius, strength=strength)))
|
||||
results = torch.cat(results, dim=0)
|
||||
else:
|
||||
results = pil2tensor(vivid_sharpen(tensor2pil(images), radius=radius, strength=strength))
|
||||
|
||||
return (results,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VividSharpen": VividSharpen,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VividSharpen": "VividSharpen",
|
||||
}
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps, ImageFilter, ImageEnhance
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
# Vivid Light and Overlay methods adopted from layeris (an overlooked gem)
|
||||
# https://github.com/subwaymatch/layer-is-python
|
||||
def vivid_light(A, B, opacity=1.0):
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
b = np.where(B > 0, 1 - (1 - A) / (2 * B), 0)
|
||||
d = np.where(B < 1, A / (2 * (1 - B)), 1)
|
||||
|
||||
result = np.clip(np.where(B <= 0.5, b, d), 0, 1)
|
||||
return alpha_blend(A, result, opacity)
|
||||
|
||||
def overlay(A, B, opacity=1.0):
|
||||
B = rgb_float_if_hex(B)
|
||||
d1 = (2 * A) * B
|
||||
d2 = 1 - 2 * (1 - A) * (1 - B)
|
||||
result = np.where(A <= 0.5, d1, d2)
|
||||
return alpha_blend(A, result, opacity)
|
||||
|
||||
def alpha_blend(base, blend, opacity):
|
||||
if opacity < 1.0:
|
||||
return base * (1.0 - opacity) + blend * opacity
|
||||
return blend
|
||||
|
||||
def hex_to_rgb_float(hex_string):
|
||||
return np.array(list((int(hex_string.lstrip('#')[i:i + 2], 16) / 255) for i in (0, 2, 4)))
|
||||
|
||||
def rgb_float_if_hex(blend_data):
|
||||
if isinstance(blend_data, str):
|
||||
return hex_to_rgb_float(blend_data)
|
||||
return blend_data
|
||||
|
||||
def vivid_sharpen(image, radius=5, strength=1.0):
|
||||
original = image.copy()
|
||||
sg = Image.new('RGB', original.size, (255, 255, 255))
|
||||
sg.paste(original, (0, 0))
|
||||
sg = ImageOps.invert(sg)
|
||||
sg = sg.filter(ImageFilter.GaussianBlur(radius=radius))
|
||||
|
||||
original_data = np.array(original).astype(float) / 255.0
|
||||
sg_data = np.array(sg).astype(float) / 255.0
|
||||
|
||||
result_data = vivid_light(original_data, sg_data, 1.0)
|
||||
result_data = overlay(original_data, result_data, 1.0)
|
||||
|
||||
result_image = Image.fromarray((result_data * 255).astype('uint8'))
|
||||
result_image = Image.blend(original, result_image, strength)
|
||||
|
||||
return result_image
|
||||
|
||||
class VividSharpen:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"radius": ("FLOAT", {"default": 1.5, "min": 0.01, "max": 64.0, "step": 0.01}),
|
||||
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
|
||||
FUNCTION = "sharpen"
|
||||
|
||||
CATEGORY = "image/postprocessing"
|
||||
|
||||
def sharpen(self, images, radius, strength):
|
||||
|
||||
results = []
|
||||
if images.size(0) > 1:
|
||||
for image in images:
|
||||
image = tensor2pil(image)
|
||||
results.append(pil2tensor(vivid_sharpen(image, radius=radius, strength=strength)))
|
||||
results = torch.cat(results, dim=0)
|
||||
else:
|
||||
results = pil2tensor(vivid_sharpen(tensor2pil(images), radius=radius, strength=strength))
|
||||
|
||||
return (results,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VividSharpen": VividSharpen,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VividSharpen": "VividSharpen",
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import importlib
|
||||
import time
|
||||
|
||||
extras = [
|
||||
".ConditioningBlend",
|
||||
".VAEEncodeForInpaint",
|
||||
".VividSharpen",
|
||||
]
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
module_timings = {}
|
||||
|
||||
print("[\033[94m\033[1mWAS Extras\033[0m] Loading extra custom nodes...")
|
||||
|
||||
for module_name in extras:
|
||||
start_time = time.time()
|
||||
|
||||
success = True
|
||||
try:
|
||||
module = importlib.import_module(module_name, package=__name__)
|
||||
except Exception:
|
||||
success = False
|
||||
pass
|
||||
|
||||
end_time = time.time()
|
||||
timing = end_time - start_time
|
||||
|
||||
module_timings[module.__file__] = (timing, success)
|
||||
|
||||
NODE_CLASS_MAPPINGS.update(getattr(module, 'NODE_CLASS_MAPPINGS', {}))
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(getattr(module, 'NODE_DISPLAY_NAME_MAPPINGS', {}))
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
print("[\033[94m\033[1mWAS Extras\033[0m] Import times for extras:")
|
||||
for module, (timing, success) in module_timings.items():
|
||||
print(f" {timing:.1f} seconds{('' if success else ' (IMPORT FAILED)')}: {module}")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user