This commit is contained in:
blepping
2024-04-09 09:11:06 -06:00
parent 98ed8801b8
commit 7f79833d98
10 changed files with 902 additions and 410 deletions
+9 -7
View File
@@ -5,9 +5,10 @@ settings.load_settings()
if settings.SETTINGS.btp_enabled:
from .py import betterTaesdPreview # noqa: F401
from .py import (
deepshrink,
hypertile,
from .py.nodes import (
blockOps,
deepShrink,
hyperTile,
modelPatchConditional,
refinerAfter,
samplers,
@@ -15,14 +16,15 @@ from .py import (
)
NODE_CLASS_MAPPINGS = {
"BlehHyperTile": hypertile.HyperTileBleh,
"BlehDeepShrink": deepshrink.DeepShrinkBleh,
"BlehBlockOps": blockOps.BlehBlockOps,
"BlehDeepShrink": deepShrink.DeepShrinkBleh,
"BlehDiscardPenultimateSigma": sigmas.DiscardPenultimateSigma,
"BlehInsaneChainSampler": samplers.BlehInsaneChainSampler,
"BlehForceSeedSampler": samplers.BlehForceSeedSampler,
"BlehHyperTile": hyperTile.HyperTileBleh,
"BlehInsaneChainSampler": samplers.BlehInsaneChainSampler,
"BlehLatentUpscaleBy": blockOps.BlehLatentUpscaleBy,
"BlehModelPatchConditional": modelPatchConditional.ModelPatchConditionalNode,
"BlehRefinerAfter": refinerAfter.BlehRefinerAfter,
"BlehLatentUpscaleBy": deepshrink.BlehLatentUpscaleBy,
}
NODE_DISPLAY_NAME_MAPPINGS = {
-403
View File
@@ -1,403 +0,0 @@
# Adapted from the ComfyUI built-in node
import bisect
import importlib as il
import comfy
import numpy as np
import torch
dare = il.import_module("custom_nodes.ComfyUI-DareMerge.ddare.merge")
UPSCALE_METHODS = (
"bicubic",
"nearest-exact",
"bilinear",
"area",
"bislerp",
"bislerp_alt",
"colorize",
"hslerp",
"bibislerp",
)
FILTER_SIZES = (
np.array([1.0]),
np.array([1.0, 1.0]),
np.array([1.0, 2.0, 1.0]),
np.array([1.0, 3.0, 3.0, 1.0]),
np.array([1.0, 4.0, 6.0, 4.0, 1.0]),
np.array([1.0, 5.0, 10.0, 10.0, 5.0, 1.0]),
np.array([1.0, 6.0, 15.0, 20.0, 15.0, 6.0, 1.0]),
)
def make_filter(channels, dtype, size=3):
a = FILTER_SIZES[size - 1]
filt = torch.tensor(a[:, None] * a[None, :], dtype=dtype)
filt = filt / torch.sum(filt)
return filt[None, None, :, :].repeat((channels, 1, 1, 1))
def scale_samples(
samples,
width,
height,
mode="bicubic",
mode_h=None,
antialias_size=0,
):
if mode_h is None:
mode_h = mode
if mode in ("bicubic", "nearest-exact", "bilinear", "area"):
result = torch.nn.functional.interpolate(
samples,
size=(height, width),
mode=mode,
antialias=antialias_size > 7,
)
result = biderp(samples, width, height, mode, mode_h)
if antialias_size < 0 or antialias_size > 7:
return result
channels = result.shape[1]
filt = make_filter(channels, result.dtype, antialias_size).to(result.device)
return torch.nn.functional.conv2d(result, filt, groups=channels, padding="same")
# Modified from ComfyUI
def biderp(samples, width, height, mode="bislerp", mode_h=None):
def slerp_orig(b1, b2, r):
c = b1.shape[-1]
# norms
b1_norms = torch.norm(b1, dim=-1, keepdim=True)
b2_norms = torch.norm(b2, dim=-1, keepdim=True)
# normalize
b1_normalized = b1 / b1_norms
b2_normalized = b2 / b2_norms
# zero when norms are zero
b1_normalized[b1_norms.expand(-1, c) == 0.0] = 0.0
b2_normalized[b2_norms.expand(-1, c) == 0.0] = 0.0
# slerp
dot = (b1_normalized * b2_normalized).sum(1)
omega = torch.acos(dot)
so = torch.sin(omega)
# technically not mathematically correct, but more pleasing?
res = (torch.sin((1.0 - r.squeeze(1)) * omega) / so).unsqueeze(
1,
) * b1_normalized + (torch.sin(r.squeeze(1) * omega) / so).unsqueeze(
1,
) * b2_normalized
res *= (b1_norms * (1.0 - r) + b2_norms * r).expand(-1, c)
# edge cases for same or polar opposites
res[dot > 1 - 1e-5] = b1[dot > 1 - 1e-5]
res[dot < 1e-5 - 1] = (b1 * (1.0 - r) + b2 * r)[dot < 1e-5 - 1]
return res
# Modified from DARE merge
def hslerp(a, b, t):
interpolation_tensor = torch.zeros(
1,
a.shape[-1],
device=a.device,
dtype=a.dtype,
)
interpolation_tensor[0, 0] = 1.0
result = (1 - t) * a + t * b
norm = (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
norm[t < 0.5, None] *= -1
result += norm
return result
if mode_h is None:
mode_h = mode
modes = {
"bislerp_alt": dare.merge_tensors_slerp,
"colorize": dare.merge_tensors_colorize,
"hslerp": hslerp,
"bislerp": slerp_orig,
"bibislerp": dare.merge_tensors_bislerp,
}
derp_w, derp_h = modes.get(mode, slerp_orig), modes.get(mode_h, slerp_orig)
def generate_bilinear_data(length_old, length_new, device):
coords_1 = torch.arange(length_old, dtype=torch.float32, device=device).reshape(
(1, 1, 1, -1),
)
coords_1 = torch.nn.functional.interpolate(
coords_1,
size=(1, length_new),
mode="bilinear",
)
ratios = coords_1 - coords_1.floor()
coords_1 = coords_1.to(torch.int64)
coords_2 = (
torch.arange(length_old, dtype=torch.float32, device=device).reshape(
(1, 1, 1, -1),
)
+ 1
)
coords_2[:, :, :, -1] -= 1
coords_2 = torch.nn.functional.interpolate(
coords_2,
size=(1, length_new),
mode="bilinear",
)
coords_2 = coords_2.to(torch.int64)
return ratios, coords_1, coords_2
orig_dtype = samples.dtype
samples = samples.float()
n, c, h, w = samples.shape
h_new, w_new = (height, width)
# linear w
ratios, coords_1, coords_2 = generate_bilinear_data(w, w_new, samples.device)
coords_1 = coords_1.expand((n, c, h, -1))
coords_2 = coords_2.expand((n, c, h, -1))
ratios = ratios.expand((n, 1, h, -1))
pass_1 = samples.gather(-1, coords_1).movedim(1, -1).reshape((-1, c))
pass_2 = samples.gather(-1, coords_2).movedim(1, -1).reshape((-1, c))
ratios = ratios.movedim(1, -1).reshape((-1, 1))
result = derp_w(pass_1, pass_2, ratios)
result = result.reshape(n, h, w_new, c).movedim(-1, 1)
# linear h
ratios, coords_1, coords_2 = generate_bilinear_data(h, h_new, samples.device)
coords_1 = coords_1.reshape((1, 1, -1, 1)).expand((n, c, -1, w_new))
coords_2 = coords_2.reshape((1, 1, -1, 1)).expand((n, c, -1, w_new))
ratios = ratios.reshape((1, 1, -1, 1)).expand((n, 1, -1, w_new))
pass_1 = result.gather(-2, coords_1).movedim(1, -1).reshape((-1, c))
pass_2 = result.gather(-2, coords_2).movedim(1, -1).reshape((-1, c))
ratios = ratios.movedim(1, -1).reshape((-1, 1))
result = derp_h(pass_1, pass_2, ratios)
result = result.reshape(n, h_new, w_new, c).movedim(-1, 1)
return result.to(orig_dtype)
class DeepShrinkBleh:
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "bleh/model_patches"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"commasep_block_numbers": (
"STRING",
{
"default": "3",
},
),
"downscale_w": (
"FLOAT",
{"default": 2.0, "min": 0.01, "max": 32.0, "step": 0.1},
),
"downscale_h": (
"FLOAT",
{"default": 2.0, "min": 0.01, "max": 32.0, "step": 0.1},
),
"start_percent": (
"FLOAT",
{"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},
),
"start_fadeout_percent": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},
),
"end_percent": (
"FLOAT",
{"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001},
),
"downscale_after_skip": ("BOOLEAN", {"default": True}),
"downscale_method_w": (UPSCALE_METHODS,),
"downscale_method_h": (UPSCALE_METHODS,),
"upscale_method_w": (UPSCALE_METHODS,),
"upscale_method_h": (UPSCALE_METHODS,),
"antialias_downscale_size": ("INT", {"default": 0, "max": 7}),
"antialias_upscale_size": ("INT", {"default": 0, "max": 7}),
},
}
def patch(
self,
model,
commasep_block_numbers,
downscale_w,
downscale_h,
start_percent,
start_fadeout_percent,
end_percent,
downscale_after_skip,
downscale_method_w,
downscale_method_h,
upscale_method_w,
upscale_method_h,
antialias_downscale_size,
antialias_upscale_size,
):
# if downscale_factor == 1 or start_percent == 1 or end_percent == 0:
# return (model.clone(),)
block_numbers = tuple(
int(x) for x in commasep_block_numbers.split(",") if x.strip()
)
# if downscale_factor < 1 and start_fadeout_percent != 1:
# raise ValueError(
# "BlehDeepShrink: Setting scale factor below 1 cannot be combined with fadeout",
# )
downscale_w, downscale_h = 1.0 / downscale_w, 1.0 / downscale_h
if not (block_numbers and all(val > 0 and val <= 32 for val in block_numbers)):
raise ValueError(
"BlehDeepShrink: Bad value for block numbers: must be comma-separated list of numbers between 1-32",
)
# antialias_downscale = antialias_downscale and downscale_method in (
# "bicubic",
# "bilinear",
# )
# antialias_upscale = antialias_upscale and upscale_method in (
# "bicubic",
# "bilinear",
# )
if start_fadeout_percent < start_percent:
start_fadeout_percent = start_percent
elif start_fadeout_percent > end_percent:
# No fadeout.
start_fadeout_percent = 1000.0
sigma_start = model.model.model_sampling.percent_to_sigma(
max(0.001, start_percent),
)
sigma_end = model.model.model_sampling.percent_to_sigma(end_percent)
# Arbitrary number that should have good enough precision
pct_steps = 400
pct_incr = 1.0 / pct_steps
sig2pct = tuple(
model.model.model_sampling.percent_to_sigma(x / pct_steps)
for x in range(pct_steps, -1, -1)
)
# print(sig2pct)
def input_block_patch(h, transformer_options):
sigma = transformer_options["sigmas"][0].item()
if (
sigma > sigma_start
or sigma < sigma_end
or transformer_options["block"][1] not in block_numbers
):
return h
# This is obviously terrible but I couldn't find a better way to get the percentage from the current sigma.
idx = bisect.bisect_right(sig2pct, sigma)
if idx >= len(sig2pct):
# Sigma out of range somehow?
return h
pct = pct_incr * (pct_steps - idx)
if (
pct < start_fadeout_percent
or start_fadeout_percent > end_percent
or pct > end_percent
):
scaled_scale_w, scaled_scale_h = downscale_w, downscale_h
else:
# May or not be accurate but the idea is to scale the downscale factor by the percentage
# of the start fade to end deep shrink we have currently traversed. It at least sort of works.
downscale_pct = 1.0 - (
(pct - start_fadeout_percent)
/ (end_percent - start_fadeout_percent)
)
scaled_scale_w = 1.0 - ((1.0 - downscale_w) * downscale_pct)
scaled_scale_h = 1.0 - ((1.0 - downscale_h) * downscale_pct)
orig_width, orig_height = h.shape[-1], h.shape[-2]
width, height = (
round(orig_width * scaled_scale_w),
round(orig_height * scaled_scale_h),
)
# if scaled_scale >= 0.98 or width >= orig_width or height >= orig_height:
# return h
if (0.98 <= scaled_scale_w <= 1.02 and 0.98 <= scaled_scale_h <= 1.02) or (
width == orig_width and height == orig_height
):
return h
return scale_samples(
h,
width,
height,
mode=downscale_method_w,
mode_h=downscale_method_h,
antialias_size=antialias_downscale_size,
)
def output_block_patch(h, hsp, _transformer_options):
if h.shape[2] == hsp.shape[2] and h.shape[3] == hsp.shape[3]:
return h, hsp
return scale_samples(
h,
hsp.shape[-1],
hsp.shape[-2],
mode=upscale_method_w,
mode_h=upscale_method_h,
antialias_size=antialias_upscale_size,
), hsp
m = model.clone()
if start_percent >= 1.0:
return (m,)
if downscale_after_skip:
m.set_model_input_block_patch_after_skip(input_block_patch)
else:
m.set_model_input_block_patch(input_block_patch)
m.set_model_output_block_patch(output_block_patch)
return (m,)
class BlehLatentUpscaleBy:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"samples": ("LATENT",),
"upscale_method": (UPSCALE_METHODS,),
"scale_width": (
"FLOAT",
{"default": 1.5, "min": 0.01, "max": 8.0, "step": 0.01},
),
"scale_height": (
"FLOAT",
{"default": 1.5, "min": 0.01, "max": 8.0, "step": 0.01},
),
"antialias": ("BOOLEAN", {"default": False}),
},
}
RETURN_TYPES = ("LATENT",)
FUNCTION = "upscale"
CATEGORY = "latent"
def upscale(self, samples, upscale_method, scale_width, scale_height):
s = samples.copy()
width = round(samples["samples"].shape[3] * scale_width)
height = round(samples["samples"].shape[2] * scale_height)
s["samples"] = scale_samples(
samples["samples"],
width,
height,
upscale_method,
)
return (s,)
+721
View File
@@ -0,0 +1,721 @@
# Adapted from the ComfyUI built-in node
import bisect
import importlib as il
import math
from enum import Enum, auto
import numpy as np
import torch
import yaml
from torch import fft
dare = il.import_module("custom_nodes.ComfyUI-DareMerge.ddare.merge")
def normalize(latent, target_min=None, target_max=None):
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)
return normalized * (target_max - target_min) + target_min
def hslerp(a, b, t):
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
blending_modes = {
# Args:
# - a (tensor): Latent input 1
# - b (tensor): Latent input 2
# - t (float): Blending factor
# Interpolates between tensors a and b using normalized linear interpolation.
"bislerp": lambda a, b, t: normalize((1 - t) * a + t * b),
# Transfer the color from `b` to `a` by t` factor
"colorize": lambda a, b, t: a + (b - a) * t,
# Interpolates between tensors a and b using cosine interpolation.
"cosinterp": 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),
# Interpolates between tensors a and b using normalized linear interpolation,
# with a twist when t is greater than or equal to 0.5.
"hslerp": hslerp,
# 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: (1 - t) * a + t * b,
# Simulates a brightening effect by adding tensor b to tensor a, scaled by t.
"lineardodge": lambda a, b, t: normalize(a + b * t),
}
mscales = {
"none": (),
"bandpass": (
(5, 0.0), # Low-pass filter
(15, 1.0), # Pass-through filter (allows mid-range frequencies)
(25, 0.0), # High-pass filter
),
"lowpass": (
(
10,
1.0,
),
), # Allows low-frequency components, suppresses high-frequency components
"highpass": (
(
10,
0.0,
),
), # Suppresses low-frequency components, allows high-frequency components
"passthrough": ((10, 1.0),), # Passes all frequencies unchanged, no filtering
"gaussianblur": (
(
10,
0.5,
), # Blurs the image by allowing a range of frequencies with a Gaussian shape
),
"edge": (
(
10,
2.0,
)
), # Enhances edges and high-frequency features while suppressing low-frequency details
"sharpen": (
(
10,
1.5,
)
), # Increases the sharpness of the image by emphasizing high-frequency components
"multilowpass": ((5, 1.0), (10, 0.5), (15, 0.2)), # Multi-scale low-pass filter
"multihighpass": ((5, 0.0), (10, 0.5), (15, 0.8)), # Multi-scale high-pass filter
"multipassthrough": (
(5, 1.0),
(10, 1.0),
(15, 1.0),
), # Pass-through at different scales
"multigaussianblur": ((5, 0.5), (10, 0.8), (15, 0.2)), # Multi-scale Gaussian blur
"multiedge": ((5, 1.2), (10, 1.5), (15, 2.0)), # Multi-scale edge enhancement
"multisharpen": ((5, 1.5), (10, 2.0), (15, 2.5)), # Multi-scale sharpening
}
UPSCALE_METHODS = (
"bicubic",
"nearest-exact",
"bilinear",
"area",
"bislerp",
"bislerp_alt",
"colorize",
"hslerp",
"bibislerp",
"cosinterp",
"cuberp",
"inject",
"lerp",
"lineardodge",
)
FILTER_SIZES = (
np.array([1.0]),
np.array([1.0, 1.0]),
np.array([1.0, 2.0, 1.0]),
np.array([1.0, 3.0, 3.0, 1.0]),
np.array([1.0, 4.0, 6.0, 4.0, 1.0]),
np.array([1.0, 5.0, 10.0, 10.0, 5.0, 1.0]),
np.array([1.0, 6.0, 15.0, 20.0, 15.0, 6.0, 1.0]),
)
def make_filter(channels, dtype, size=3):
a = FILTER_SIZES[size - 1]
filt = torch.tensor(a[:, None] * a[None, :], dtype=dtype)
filt = filt / torch.sum(filt)
return filt[None, None, :, :].repeat((channels, 1, 1, 1))
def scale_samples(
samples,
width,
height,
mode="bicubic",
mode_h=None,
antialias_size=0,
):
if mode_h is None:
mode_h = mode
if mode in ("bicubic", "nearest-exact", "bilinear", "area"):
result = torch.nn.functional.interpolate(
samples,
size=(height, width),
mode=mode,
antialias=antialias_size > 7,
)
else:
result = biderp(samples, width, height, mode, mode_h)
if antialias_size < 1 or antialias_size > 7:
return result
channels = result.shape[1]
filt = make_filter(channels, result.dtype, antialias_size).to(result.device)
return torch.nn.functional.conv2d(result, filt, groups=channels, padding="same")
# Modified from ComfyUI
def biderp(samples, width, height, mode="bislerp", mode_h=None):
def slerp_orig(b1, b2, r):
c = b1.shape[-1]
# norms
b1_norms = torch.norm(b1, dim=-1, keepdim=True)
b2_norms = torch.norm(b2, dim=-1, keepdim=True)
# normalize
b1_normalized = b1 / b1_norms
b2_normalized = b2 / b2_norms
# zero when norms are zero
b1_normalized[b1_norms.expand(-1, c) == 0.0] = 0.0
b2_normalized[b2_norms.expand(-1, c) == 0.0] = 0.0
# slerp
dot = (b1_normalized * b2_normalized).sum(1)
omega = torch.acos(dot)
so = torch.sin(omega)
# technically not mathematically correct, but more pleasing?
res = (torch.sin((1.0 - r.squeeze(1)) * omega) / so).unsqueeze(
1,
) * b1_normalized + (torch.sin(r.squeeze(1) * omega) / so).unsqueeze(
1,
) * b2_normalized
res *= (b1_norms * (1.0 - r) + b2_norms * r).expand(-1, c)
# edge cases for same or polar opposites
res[dot > 1 - 1e-5] = b1[dot > 1 - 1e-5]
res[dot < 1e-5 - 1] = (b1 * (1.0 - r) + b2 * r)[dot < 1e-5 - 1]
return res
# Modified from DARE merge
def hslerp(a, b, t):
interpolation_tensor = torch.zeros(
1,
a.shape[-1],
device=a.device,
dtype=a.dtype,
)
interpolation_tensor[0, 0] = 1.0
result = (1 - t) * a + t * b
norm = (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor
norm[t < 0.5, None] *= -1
result += norm
return result
if mode_h is None:
mode_h = mode
modes = {
"bislerp_alt": dare.merge_tensors_slerp,
"colorize": blending_modes["colorize"],
"hslerp": hslerp,
"bislerp": slerp_orig,
"bibislerp": blending_modes["bislerp"],
"inject": blending_modes["inject"],
"lerp": blending_modes["lerp"],
"lineardodge": blending_modes["lineardodge"],
"cosinterp": blending_modes["cosinterp"],
"cuberp": blending_modes["cuberp"],
}
derp_w, derp_h = modes.get(mode, slerp_orig), modes.get(mode_h, slerp_orig)
def generate_bilinear_data(length_old, length_new, device):
coords_1 = torch.arange(length_old, dtype=torch.float32, device=device).reshape(
(1, 1, 1, -1),
)
coords_1 = torch.nn.functional.interpolate(
coords_1,
size=(1, length_new),
mode="bilinear",
)
ratios = coords_1 - coords_1.floor()
coords_1 = coords_1.to(torch.int64)
coords_2 = (
torch.arange(length_old, dtype=torch.float32, device=device).reshape(
(1, 1, 1, -1),
)
+ 1
)
coords_2[:, :, :, -1] -= 1
coords_2 = torch.nn.functional.interpolate(
coords_2,
size=(1, length_new),
mode="bilinear",
)
coords_2 = coords_2.to(torch.int64)
return ratios, coords_1, coords_2
orig_dtype = samples.dtype
samples = samples.float()
n, c, h, w = samples.shape
h_new, w_new = (height, width)
# linear w
ratios, coords_1, coords_2 = generate_bilinear_data(w, w_new, samples.device)
coords_1 = coords_1.expand((n, c, h, -1))
coords_2 = coords_2.expand((n, c, h, -1))
ratios = ratios.expand((n, 1, h, -1))
pass_1 = samples.gather(-1, coords_1).movedim(1, -1).reshape((-1, c))
pass_2 = samples.gather(-1, coords_2).movedim(1, -1).reshape((-1, c))
ratios = ratios.movedim(1, -1).reshape((-1, 1))
result = derp_w(pass_1, pass_2, ratios)
result = result.reshape(n, h, w_new, c).movedim(-1, 1)
# linear h
ratios, coords_1, coords_2 = generate_bilinear_data(h, h_new, samples.device)
coords_1 = coords_1.reshape((1, 1, -1, 1)).expand((n, c, -1, w_new))
coords_2 = coords_2.reshape((1, 1, -1, 1)).expand((n, c, -1, w_new))
ratios = ratios.reshape((1, 1, -1, 1)).expand((n, 1, -1, w_new))
pass_1 = result.gather(-2, coords_1).movedim(1, -1).reshape((-1, c))
pass_2 = result.gather(-2, coords_2).movedim(1, -1).reshape((-1, c))
ratios = ratios.movedim(1, -1).reshape((-1, 1))
result = derp_h(pass_1, pass_2, ratios)
result = result.reshape(n, h_new, w_new, c).movedim(-1, 1)
return result.to(orig_dtype)
def ffilter(x, threshold, scale, scales=None, strength=1.0):
# FFT
if isinstance(x, list):
x = x[0]
if not isinstance(x, torch.Tensor):
return x
x_freq = fft.fftn(x.float(), dim=(-2, -1))
x_freq = fft.fftshift(x_freq, dim=(-2, -1))
_batch, _channels, height, width = x_freq.shape
mask = torch.ones(x_freq.shape, device=x.device)
crow, ccol = height // 2, width // 2
mask[
...,
crow - threshold : crow + threshold,
ccol - threshold : ccol + threshold,
] = scale
if scales:
for scale_params in scales:
for scale_threshold, scale_value in scale_params:
scaled_scale_value = scale_value * strength
scale_mask = torch.ones(x_freq.shape, device=x.device)
scale_mask[
...,
crow - scale_threshold : crow + scale_threshold,
ccol - scale_threshold : ccol + scale_threshold,
] = scaled_scale_value
mask = mask + (scale_mask - mask) * strength
x_freq *= mask
# IFFT
x_freq = fft.ifftshift(x_freq, dim=(-2, -1))
return fft.ifftn(x_freq, dim=(-2, -1)).real.to(x.dtype)
class OpType(Enum):
# scale, strength, blend, blend mode, use hidden mean
SLICE = auto()
# scale, filter, filter strength, threshold
FFILTER = auto()
# type (bicubic, nearest, bilinear, area), scale width, scale height, antialias
SCALE_TORCH = auto()
# type (bicubic, nearest, bilinear, area), antialias
UNSCALE_TORCH = auto()
# type width (slerp, slerp_alt, hslerp, colorize), type height, scale width, scale height, antialias size
SCALE = auto()
# type width (slerp, slerp_alt, hslerp, colorize), type height, antialias size
UNSCALE = auto()
# direction (h, v)
FLIP = auto()
# count
ROT90 = auto()
# true/false - only makes sense with output block
TARGET_SKIP = auto()
# factor
MULTIPLY = auto()
class CondType(Enum):
TYPE = auto()
BLOCK = auto()
STAGE = auto()
FROM_PERCENT = auto()
TO_PERCENT = auto()
PERCENT = auto()
COUNT = auto()
class BlockType(Enum):
INPUT = auto()
INPUT_AFTER_SKIP = auto()
MIDDLE = auto()
OUTPUT = auto()
class BlockCond:
def __init__(self, typ, value):
self.typ = getattr(CondType, typ.upper().strip())
self.value = set(value if isinstance(value, (list, tuple)) else (value,))
def test(self, state, count=0):
match self.typ:
case CondType.FROM_PERCENT:
pct = state[CondType.PERCENT]
result = all(pct >= v for v in self.value)
case CondType.TO_PERCENT:
pct = state[CondType.PERCENT]
result = all(pct <= v for v in self.value)
case CondType.COUNT:
result = all(count < v for v in self.value)
case _:
result = state[self.typ] in self.value
return result
def __repr__(self):
return f"<Cond({self.typ}): {self.value}>"
class BlockConds:
def __init__(self, conds, count=0):
self.count = count
if not conds:
self.conds = ()
return
self.conds = tuple(BlockCond(ct, cv) for ct, cv in conds.items())
def test(self, state):
count = self.count
result = all(c.test(state, count) for c in self.conds)
if result:
self.count += 1
return result
def __repr__(self):
return f"<Conds[{self.count}]: {self.conds}"
def hidden_mean(h):
hidden_mean = h.mean(1).unsqueeze(1)
b = hidden_mean.shape[0]
hidden_max, _ = torch.max(hidden_mean.view(b, -1), dim=-1, keepdim=True)
hidden_min, _ = torch.min(hidden_mean.view(b, -1), dim=-1, keepdim=True)
return (hidden_mean - hidden_min.unsqueeze(2).unsqueeze(3)) / (
hidden_max - hidden_min
).unsqueeze(2).unsqueeze(3)
class BlockOp:
def __init__(self, typ, *args):
self.typ = getattr(OpType, typ.upper().strip())
self.args = args
def eval(self, state):
t = out = state[state["target"]]
match self.typ:
case OpType.SCALE_TORCH | OpType.UNSCALE_TORCH:
if self.typ == OpType.SCALE_TORCH:
mode, scale_w, scale_h, antialias = self.args
width, height = (
round(t.shape[-1] * scale_w),
round(t.shape[-2] * scale_h),
)
else:
hsp = state["hsp"]
if t.shape[-1] == hsp.shape[-1] and t.shape[-2] == hsp.shape[-2]:
return
mode, antialias = self.args
width, height = hsp.shape[-1], hsp.shape[-2]
out = scale_samples(
t,
width,
height,
mode,
antialias_size=8 if antialias else 0,
)
case OpType.SCALE | OpType.UNSCALE:
if self.typ == OpType.SCALE:
mode_w, mode_h, scale_w, scale_h, antialias_size = self.args
width, height = (
round(t.shape[-1] * scale_w),
round(t.shape[-2] * scale_h),
)
else:
hsp = state["hsp"]
if t.shape[-1] == hsp.shape[-1] and t.shape[-2] == hsp.shape[-2]:
return
mode_w, mode_h, antialias_size = self.args
width, height = hsp.shape[-1], hsp.shape[-2]
print("SCALE", width, height, mode_w, mode_h, antialias_size)
out = scale_samples(
t,
width,
height,
mode=mode_w,
mode_h=mode_h,
antialias_size=antialias_size,
)
case OpType.FLIP:
out = torch.flip(
t,
dims=(2 if self.args == "h" else 3,),
)
case OpType.ROT90:
print("ROTBEFORE", t.shape)
out = torch.rot90(t, self.args[0], dims=(3, 2))
print("ROTAFTER", out.shape)
case OpType.TARGET_SKIP:
state["target"] = "hsp" if self.args[0] is True else "h"
return
case OpType.FFILTER:
scale, filt, strength, threshold = self.args
if isinstance(filt, str):
filt = mscales[filt]
out = ffilter(t, threshold, scale, filt, strength)
case OpType.SLICE:
scale, strength, blend, mode, use_hm = self.args
slice_size = round(t.shape[1] * scale)
print("SLICE", slice_size)
sliced = t[:, :slice_size]
if use_hm:
result = sliced * ((strength - 1) * hidden_mean(t) + 1)
else:
result = sliced * strength
if blend != 1:
result = blending_modes[mode](sliced, result, blend)
out[:, :slice_size] = result
case OpType.MULTIPLY:
out *= self.args[0]
case _:
raise ValueError("Unhandled")
state[state["target"]] = out
def __repr__(self):
return f"<Op({self.typ}): {self.args!r}>"
class BlockRule:
@classmethod
def from_dict(cls, val):
if not isinstance(val, (list, tuple)):
val = (val,)
return tuple(
cls(
conds=d.get("if", ()),
ops=d.get("ops", ()),
matched=d.get("then", ()),
nomatched=d.get("else", ()),
)
for d in val
)
def __init__(self, conds=(), ops=(), matched=(), nomatched=()):
self.conds = BlockConds(conds)
self.ops = tuple(BlockOp(o[0], *o[1:]) for o in ops)
self.matched = BlockRule.from_dict(matched)
self.nomatched = BlockRule.from_dict(nomatched)
def get_all_types(self):
result = {c.value for c in self.conds if c.typ == CondType.TYPE}
for r in self.matched:
result |= r.get_all_types()
for r in self.nomatched:
result |= r.get_all_types()
return result
def eval(self, state):
# print("EVAL", state | {"h": None, "hsp": None})
if not self.conds.test(state):
for r in self.nomatched:
r.eval(state)
return
state["target"] = "h"
for o in self.ops:
o.eval(state)
for r in self.matched:
r.eval(state)
def __repr__(self):
return f"<Rule: IF({self.conds}) THEN({self.ops}, {self.matched}) ELSE({self.nomatched})>"
class BlehBlockOps:
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "bleh/model_patches"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"rules": ("STRING", {"multiline": True, "forceInput": True}),
},
}
def patch(
self,
model,
rules,
):
rules = rules.strip()
print("PARSING", repr(rules))
if len(rules) == 0:
return (model.clone(),)
parsed_rules = yaml.safe_load(rules)
rules = tuple(BlockRule.from_dict(r)[0] for r in parsed_rules)
print("RULES:", rules)
# Arbitrary number that should have good enough precision
pct_steps = 400
pct_incr = 1.0 / pct_steps
sig2pct = tuple(
model.model.model_sampling.percent_to_sigma(x / pct_steps)
for x in range(pct_steps, -1, -1)
)
def get_pct(topts):
sigma = topts["sigmas"][0].item()
# This is obviously terrible but I couldn't find a better way to get the percentage from the current sigma.
idx = bisect.bisect_right(sig2pct, sigma)
if idx >= len(sig2pct):
# Sigma out of range somehow?
return None
return pct_incr * (pct_steps - idx)
stages = (1280, 640, 320)
def make_state(typ, topts, h, hsp=None):
pct = get_pct(topts)
if pct is None:
return None
stage = stages.index(h.shape[1]) + 1 if h.shape[1] in stages else -1
return {
CondType.TYPE: typ,
CondType.PERCENT: pct,
CondType.BLOCK: topts["block"][1],
CondType.STAGE: stage,
"h": h,
"hsp": hsp,
"target": "h",
}
def block_patch(typ, h, topts):
state = make_state(typ, topts, h)
if state is None:
return h
for rule in rules:
rule.eval(state)
return state["h"]
def output_block_patch(h, hsp, transformer_options):
state = make_state("output", transformer_options, h, hsp)
if state is None:
return h
for rule in rules:
rule.eval(state)
return state["h"], state["hsp"]
m = model.clone()
m.set_model_input_block_patch_after_skip(
lambda *args: block_patch("input_after_skip", *args),
)
m.set_model_input_block_patch(lambda *args: block_patch("input", *args))
m.set_model_patch(
lambda *args: block_patch("middle", *args),
"middle_block_patch",
)
m.set_model_output_block_patch(output_block_patch)
return (m,)
class BlehLatentUpscaleBy:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"samples": ("LATENT",),
"upscale_method": (UPSCALE_METHODS,),
"scale_width": (
"FLOAT",
{"default": 1.5, "min": 0.01, "max": 8.0, "step": 0.01},
),
"scale_height": (
"FLOAT",
{"default": 1.5, "min": 0.01, "max": 8.0, "step": 0.01},
),
"antialias": ("BOOLEAN", {"default": False}),
},
}
RETURN_TYPES = ("LATENT",)
FUNCTION = "upscale"
CATEGORY = "latent"
def upscale(self, samples, upscale_method, scale_width, scale_height):
s = samples.copy()
width = round(samples["samples"].shape[3] * scale_width)
height = round(samples["samples"].shape[2] * scale_height)
s["samples"] = scale_samples(
samples["samples"],
width,
height,
upscale_method,
)
return (s,)
+171
View File
@@ -0,0 +1,171 @@
# Adapted from the ComfyUI built-in node
import bisect
import torch
from comfy.utils import bislerp
class DeepShrinkBleh:
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "bleh/model_patches"
upscale_methods = (
"bicubic",
"nearest-exact",
"bilinear",
"area",
"bislerp",
)
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"commasep_block_numbers": (
"STRING",
{
"default": "3",
},
),
"downscale_factor": (
"FLOAT",
{"default": 2.0, "min": 1.0, "max": 32.0, "step": 0.1},
),
"start_percent": (
"FLOAT",
{"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},
),
"start_fadeout_percent": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},
),
"end_percent": (
"FLOAT",
{"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001},
),
"downscale_after_skip": ("BOOLEAN", {"default": True}),
"downscale_method": (cls.upscale_methods,),
"upscale_method": (cls.upscale_methods,),
"antialias_downscale": ("BOOLEAN", {"default": False}),
"antialias_upscale": ("BOOLEAN", {"default": False}),
},
}
def patch(
self,
model,
commasep_block_numbers,
downscale_factor,
start_percent,
start_fadeout_percent,
end_percent,
downscale_after_skip,
downscale_method,
upscale_method,
antialias_downscale,
antialias_upscale,
):
block_numbers = tuple(
int(x) for x in commasep_block_numbers.split(",") if x.strip()
)
downscale_factor = 1.0 / downscale_factor
if not (block_numbers and all(val > 0 and val <= 32 for val in block_numbers)):
raise ValueError(
"BlehDeepShrink: Bad value for block numbers: must be comma-separated list of numbers between 1-32",
)
antialias_downscale = antialias_downscale and downscale_method in (
"bicubic",
"bilinear",
)
antialias_upscale = antialias_upscale and upscale_method in (
"bicubic",
"bilinear",
)
if start_fadeout_percent < start_percent:
start_fadeout_percent = start_percent
elif start_fadeout_percent > end_percent:
# No fadeout.
start_fadeout_percent = 1000.0
sigma_start = model.model.model_sampling.percent_to_sigma(start_percent)
sigma_end = model.model.model_sampling.percent_to_sigma(end_percent)
# Arbitrary number that should have good enough precision
pct_steps = 400
pct_incr = 1.0 / pct_steps
sig2pct = tuple(
model.model.model_sampling.percent_to_sigma(x / pct_steps)
for x in range(pct_steps, -1, -1)
)
def input_block_patch(h, transformer_options):
sigma = transformer_options["sigmas"][0].item()
if (
sigma > sigma_start
or sigma < sigma_end
or transformer_options["block"][1] not in block_numbers
):
return h
# This is obviously terrible but I couldn't find a better way to get the percentage from the current sigma.
idx = bisect.bisect_right(sig2pct, sigma)
if idx >= len(sig2pct):
# Sigma out of range somehow?
return h
pct = pct_incr * (pct_steps - idx)
if (
pct < start_fadeout_percent
or start_fadeout_percent > end_percent
or pct > end_percent
):
scaled_scale = downscale_factor
else:
# May or not be accurate but the idea is to scale the downscale factor by the percentage
# of the start fade to end deep shrink we have currently traversed. It at least sort of works.
downscale_pct = 1.0 - (
(pct - start_fadeout_percent)
/ (end_percent - start_fadeout_percent)
)
scaled_scale = 1.0 - ((1.0 - downscale_factor) * downscale_pct)
orig_width, orig_height = h.shape[-1], h.shape[-2]
width, height = (
round(orig_width * scaled_scale),
round(orig_height * scaled_scale),
)
if scaled_scale >= 0.98 or width >= orig_width or height >= orig_height:
return h
if downscale_method == "bislerp":
return bislerp(h, width, height)
return torch.nn.functional.interpolate(
h,
size=(height, width),
mode=downscale_method,
antialias=antialias_downscale,
)
def output_block_patch(h, hsp, _transformer_options):
if h.shape[2] == hsp.shape[2]:
return h, hsp
if upscale_method == "bislerp":
return bislerp(
h,
hsp.shape[-1],
hsp.shape[-2],
), hsp
return torch.nn.functional.interpolate(
h,
size=(hsp.shape[-2], hsp.shape[-1]),
mode=upscale_method,
antialias=antialias_upscale,
), hsp
m = model.clone()
if downscale_factor == 0.0 or start_percent >= 1.0:
return (m,)
if downscale_after_skip:
m.set_model_input_block_patch_after_skip(input_block_patch)
else:
m.set_model_input_block_patch(input_block_patch)
m.set_model_output_block_patch(output_block_patch)
return (m,)
View File
+1
View File
@@ -13,6 +13,7 @@ ignore = [
"D102",
"D103",
"D104",
"D105",
"D107",
"D211",
"D213",