Files
2024-08-04 11:24:02 -06:00

624 lines
17 KiB
Python

import os
import torch
import numpy as np
from . import expression as expr
from . import latent
from .external import MODULES as EXT
from .utils import scale_noise, resolve_value
ALLOW_UNSAFE = os.environ.get("COMFYUI_OCS_ALLOW_UNSAFE_EXPRESSIONS") is not None
ALLOW_ALL_UNSAFE = os.environ.get("COMFYUI_OCS_ALLOW_ALL_UNSAFE") is not None
EXT_BLEH = EXT.get("bleh")
EXT_SONAR = EXT.get("sonar")
if "bleh" in EXT:
BLENDING_MODES = EXT_BLEH.latent_utils.BLENDING_MODES
else:
BLENDING_MODES = {
"lerp": lambda a, b, t: (1 - t) * a + t * b,
}
HANDLERS = {}
class NormHandler(expr.BaseHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.numeric("factor", 1.0),
expr.Arg.numscalar_sequence("dim", (-3, -2, -1)),
)
def handle(self, obj, getter):
tensor, factor, dim = self.safe_get_all(obj, getter)
return scale_noise(tensor, factor, normalize_dims=dim)
validate_output = expr.Arg.tensor("output")
class MeanHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.numscalar_sequence("dim", (-3, -2, -1)),
)
def handle(self, obj, getter):
tensor, dim = self.safe_get_all(obj, getter)
return tensor.mean(keepdim=True, dim=dim)
class StdHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.numscalar_sequence("dim", (-3, -2, -1)),
)
def handle(self, obj, getter):
tensor, dim = self.safe_get_all(obj, getter)
return tensor.std(keepdim=True, dim=dim)
class RollHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.numeric_scalar("amount", 0.5),
expr.Arg.numscalar_sequence("dim", (-2,)),
)
def handle(self, obj, getter):
tensor, amount, dim = self.safe_get_all(obj, getter)
if isinstance(amount, float) and amount < 1.0 and amount > -1.0:
if len(dim) > 1:
raise ValueError(
"Cannot use percentage based amount with multiple roll dimensions",
)
amount = int(tensor.shape[dim[0]] * amount)
return tensor.roll(amount, dims=dim)
class FlipHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.integer("dim"),
expr.Arg.boolean("mirror", False),
)
def handle(self, obj, getter):
tensor, dim, mirror = self.safe_get_all(obj, getter)
if dim < 0:
dim += tensor.ndim
if dim < 0 or dim >= tensor.ndim:
raise ValueError(
f"Dimension out of range, wanted {dim}, tensor has {tensor.ndim} dimension(s)"
)
if not mirror:
return torch.flip(tensor, (dim,))
result = tensor.detach().clone()
pivot = tensor.shape[dim] // 2
out_slice = (
np.s_[:] if d != dim else np.s_[pivot:] for d in range(tensor.ndim)
)
in_slice = (np.s_[:] if d != dim else np.s_[:pivot] for d in range(tensor.ndim))
result[*out_slice] = torch.flip(tensor[*in_slice], dims=(dim,))
return result
class BlendHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor1"),
expr.Arg.tensor("tensor2"),
expr.Arg.numeric("scale", 0.5),
expr.Arg.string("mode", "lerp"),
)
def handle(self, obj, getter):
t1, t2, scale, mode = self.safe_get_all(obj, getter)
blend_handler = BLENDING_MODES.get(mode)
if not blend_handler:
raise KeyError(f"Unknown blend mode {mode!r}")
return blend_handler(t1, t2, scale)
class ContrastAdaptiveSharpeningHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.numeric("scale", 0.5),
)
def handle(self, obj, getter):
t, scale = self.safe_get_all(obj, getter)
return latent.contrast_adaptive_sharpening(t, scale)
class ScaleHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.one_of(
"scale",
(
expr.ValidateArg.validate_numeric_scalar,
expr.ValidateArg.validate_numscalar_sequence,
),
),
expr.Arg.string("mode", "bicubic"),
expr.Arg.boolean("absolute_scale", False),
)
def handle(self, obj, getter):
t, scale, mode, abs_scale = self.safe_get_all(obj, getter)
if isinstance(scale, (list, tuple)):
if len(scale) != 2:
raise ValueError(
"When passing scale as a tuple, it must be in the form (h, w)"
)
else:
scale = (scale, scale)
if abs_scale:
scale = tuple(int(v) for v in scale)
else:
scale = (int(t.shape[-1] * scale[0]), int(t.shape[-2] * scale[1]))
if not all(v > 0 for v in scale):
raise ValueError(f"Invalid scale: scale values must be > 0, got: {scale!r}")
return latent.scale_samples(t, scale[1], scale[0], mode=mode)
class NoiseHandler(NormHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.string("type", "gaussian"),
)
def handle(self, obj, getter):
t, typ = self.safe_get_all(obj, getter)
ctx = getter.handlers
smin, smax, s, sn = (
h(ctx, *getter.args, **getter.kwargs) if h is not None else None
for h in (
ctx.get(k) for k in ("sigma_min", "sigma_max", "sigma", "sigma_next")
)
)
ns = latent.get_noise_sampler(typ, t, smin, smax, normalized=False)
return ns(s, sn)
class UnsafeTorchTensorMethodHandler(NormHandler):
input_validators = (
expr.Arg.tensor("__tensor"),
expr.Arg.string("__method"),
)
if ALLOW_ALL_UNSAFE:
class AlwaysContains:
def __contains__(self, k):
return True
whitelist = AlwaysContains()
elif ALLOW_UNSAFE:
whitelist = {
"abs",
"absolute",
"acos",
"acosh",
"add",
"addbmm",
"addcdiv",
"addcmul",
"addmm",
"addmv",
"addr",
"adjoint",
"all",
"allclose",
"amax",
"amin",
"aminmax",
"angle",
"any",
"arccos",
"arccosh",
"arcsin",
"arcsinh",
"arctan",
"arctan2",
"arctanh",
"argmax",
"argmin",
"argsort",
"argwhere",
"as_strided",
"asin",
"asinh",
"atan",
"atan2",
"atanh",
"baddbmm",
"bernoulli",
"bincount",
"bitwise_and",
"bitwise_left_shift",
"bitwise_not",
"bitwise_or",
"bitwise_right_shift",
"bitwise_xor",
"bmm",
"broadcast_to",
"ceil",
"cholesky",
"cholesky_inverse",
"cholesky_solve",
"chunk",
"clamp",
"clip",
"clone",
"conj",
"conj_physical",
"contiguous",
"copysign",
"corrcoef",
"cos",
"cosh",
"count_nonzero",
"cov",
"cross",
"cummax",
"cummin",
"cumprod",
"cumsum",
"deg2rad",
"det",
"detach",
"diag",
"diag_embed",
"diagflat",
"diagonal",
"diagonal_scatter",
"diff",
"digamma",
"dim",
"dist",
"div",
"divide",
"dot",
"dsplit",
"eq",
"equal",
"erf",
"erfc",
"erfinv",
"exp",
"expand",
"expand_as",
"expm1",
"fix",
"flatten",
"flip",
"fliplr",
"flipud",
"float_power",
"floor",
"floor_divide",
"fmax",
"fmin",
"fmod",
"frac",
"frexp",
"gather",
"gcd",
"ge",
"geqrf",
"ger",
"greater",
"greater_equal",
"gt",
"hardshrink",
"heaviside",
"histc",
"hsplit",
"hypot",
"i0",
"igamma",
"igammac",
"index_add",
"index_copy",
"index_fill",
"index_put",
"index_reduce",
"index_select",
"inner",
"inverse",
"isclose",
"isfinite",
"isinf",
"isnan",
"isneginf",
"isposinf",
"kthvalue",
"lcm()",
"ldexp",
"le",
"lerp",
"less",
"less_equal",
"lgamma",
"log",
"log10",
"log1p",
"log2",
"logaddexp",
"logaddexp2",
"logcumsumexp",
"logdet",
"logical_and",
"logical_not",
"logical_or",
"logical_xor",
"logit",
"logsumexp",
"lt",
"lu",
"lu_solve",
"masked_fill",
"masked_scatter",
"masked_select",
"matmul",
"matrix_exp",
"max",
"maximum",
"mean",
"median",
"min",
"minimum",
"mm",
"mode",
"moveaxis",
"movedim",
"msort",
"mul",
"multinomial",
"multiply",
"mv",
"mvlgamma",
"nan_to_num",
"nanmean",
"nanmedian",
"nanquantile",
"nansum",
"narrow",
"narrow_copy",
"ne",
"neg",
"negative",
"new_empty",
"new_full",
"new_ones",
"new_zeros",
"nextafter",
"nonzero",
"norm",
"not_equal",
"numel",
"orgqr",
"ormqr",
"outer",
"permute",
"polygamma",
"positive",
"pow",
"prod",
"qr",
"quantile",
"rad2deg",
"ravel",
"reciprocal",
"remainder",
"renorm",
"repeat",
"repeat_interleave",
"reshape",
"reshape_as",
"resolve_conj",
"resolve_neg",
"roll",
"rot90",
"round",
"rsqrt",
"scatter",
"scatter_add",
"scatter_reduce",
"select",
"select_scatter",
"sgn",
"sigmoid",
"sign",
"signbit",
"sin",
"sinc",
"sinh",
"slice_scatter",
"slogdet",
"smm",
"softmax",
"sort",
"sparse_mask",
"split",
"sqrt",
"square",
"squeeze",
"sspaddmm",
"std",
"stft",
"sub",
"subtract",
"sum",
"sum_to_size",
"svd",
"swapaxes",
"swapdims",
"t",
"take",
"take_along_dim",
"tan",
"tanh",
"tensor_split",
"tile",
"topk",
"transpose",
"triangular_solve",
"tril",
"triu",
"true_divide",
"trunc",
"unflatten",
"unfold",
"unique",
"unique_consecutive",
"unsqueeze",
"var",
"vdot",
"view",
"view_as",
"vsplit",
"where",
"xlogy",
}
else:
whitelist = set()
def handle(self, obj, getter):
if "__method" in obj.kwargs or "__tensor" in obj.kwargs:
raise ValueError(
"Tensor method call doesn't support passing method or tensor with keyword args"
)
tensor = self.safe_get("__tensor", obj, getter=getter)
method = self.safe_get("__method", obj, getter=getter)
args = (
self.safe_get(idx, obj, getter=getter) for idx in range(2, len(obj.args))
)
kwargs = {k: self.safe_get(k, obj, getter=getter) for k in obj.kwargs.keys()}
if method not in self.whitelist:
raise ValueError(f"Method {method} not whitelisted: cannot call")
methodfun = getattr(tensor, method, None)
if methodfun is None:
raise KeyError(f"Unknown method {method} for Torch tensor")
return methodfun(*args, **kwargs)
class UnsafeTorchHandler(expr.BaseHandler):
input_validators = (expr.Arg.string("path"),)
if not ALLOW_ALL_UNSAFE:
def handle(self, obj, getter):
raise ValueError("Unsafe Torch access not allowed")
else:
def handle(self, obj, getter):
path = self.safe_get("path", obj, getter)
keys = path.split(".")
if not keys or not all(k for k in keys):
raise ValueError(f"Bad path {path}")
return resolve_value(keys, torch)
if EXT_BLEH:
class BlehEnhanceHandler(expr.BaseHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.string("mode"),
expr.Arg.numeric_scalar("scale", 1.0),
)
output_validator = expr.Arg.tensor("output")
def handle(self, obj, getter):
tensor, mode, scale = self.safe_get_all(obj, getter)
return EXT_BLEH.latent_utils.enhance_tensor(
tensor, mode, scale=scale, adjust_scale=False
)
HANDLERS["t_bleh_enhance"] = BlehEnhanceHandler()
if EXT_SONAR:
class SonarPowerFilterHandler(expr.BaseHandler):
input_validators = (
expr.Arg.tensor("tensor"),
expr.Arg.present("filter"),
)
output_validator = expr.Arg.tensor("output")
default_power_filter = {
"mix": 1.0,
"normalization_factor": 1.0,
"common_mode": 0.0,
"channel_correlation": "1,1,1,1,1,1",
}
@classmethod
def make_power_filter(cls, fdict, *, toplevel=True):
fdict = fdict.copy()
compose_with = fdict.pop("compose_with", None)
if compose_with:
if not isinstance(compose_with, dict):
raise TypeError("compose_with must be a dictionary")
fdict["compose_with"] = cls.make_power_filter(
compose_with, toplevel=False
)
topargs = {
k: fdict.pop(k, dv) for k, dv in cls.default_power_filter.items()
}
power_filter = EXT_SONAR.powernoise.PowerFilter(**fdict)
if not toplevel:
return power_filter
cc = topargs.get("channel_correlation")
if cc is not None:
if not isinstance(cc, (list, tuple)) or not all(
isinstance(v, (int, float)) for v in cc
):
raise TypeError(
"Bad channel correlation type: must be comma separated string or numeric sequence"
)
topargs["channel_correlation"] = ",".join(repr(v) for v in cc)
return EXT_SONAR.powernoise.PowerNoiseItem(
1, power_filter=power_filter, time_brownian=True, **topargs
)
def handle(self, obj, getter):
tensor, filter_def = self.safe_get_all(obj, getter)
if not isinstance(filter_def, dict):
raise TypeError("filter argument must be a dictionary")
power_filter = self.make_power_filter(filter_def)
filter_rfft = power_filter.make_filter(tensor.shape).to(
tensor.device, non_blocking=True
)
ns = power_filter.make_noise_sampler_internal(
tensor,
lambda *_unused, latent=tensor: latent,
filter_rfft,
normalized=False,
)
return ns(None, None)
HANDLERS["t_sonar_power_filter"] = SonarPowerFilterHandler()
TENSOR_OP_HANDLERS = {
"t_norm": NormHandler(),
"t_mean": MeanHandler(),
"t_std": StdHandler(),
"t_blend": BlendHandler(),
"t_roll": RollHandler(),
"t_flip": FlipHandler(),
"t_contrast_adaptive_sharpening": ContrastAdaptiveSharpeningHandler(),
"t_scale": ScaleHandler(),
"t_noise": NoiseHandler(),
"unsafe_tensor_method": UnsafeTorchTensorMethodHandler(),
"unsafe_torch": UnsafeTorchHandler(),
}
HANDLERS |= TENSOR_OP_HANDLERS