Author SHA1 Message Date
blepping e36623a5f1 Add round and step to node FLOAT inputs that did not have it 2025-01-30 06:41:34 -07:00
blepping ca3ee58750 Internal cleanups and refactoring.
Some integration improvements.
Bump date in changelog
2025-01-30 06:10:36 -07:00
blepping a31eb6940b Better approach to integration with external nodes
Documentation updates
Other cleanups
2024-12-22 10:36:23 -07:00
blepping 3222b02318 Momentum sampler refactor/improvements (I hope) 2024-12-12 15:16:42 -07:00
blepping dcfea85e9c Add SonarResizedNoise node 2024-12-12 11:39:14 -07:00
blepping 3ba9f2e3d1 More distributions! 2024-12-11 17:57:30 -07:00
blepping b5be44720c Distro noise improvements, add SonarAdvancedDistroNoise node 2024-12-11 11:14:48 -07:00
blepping 30b37e98c2 Generalized distribution noise for most torch.distributions 2024-12-09 21:16:35 -07:00
blepping a951ad7392 Fix Brownian arg passing 2024-12-06 09:25:05 -07:00
blepping 27126d9f93 Add WaveletFilteredNoise node, other fixes 2024-12-05 15:41:15 -07:00
blepping 7365a9f30b Refactor noise generation
Try to make option passing and CPU/GPU noise selection work
Add advanced custom noise node that allows for parameter passing
Add wavelet noise type
2024-12-05 13:17:27 -07:00
5 changed files with 98 additions and 147 deletions
-4
View File
@@ -2,10 +2,6 @@
Note, only relatively significant changes to user-visible functionality will be included here. Most recent changes at the top.
## 20250227
* Add 5D latent (video models) support for most custom noise types.
## 20250130
*Note*: May change seeds.
+12 -23
View File
@@ -2216,12 +2216,6 @@ class SonarBlendFilterNoiseNode(
@classmethod
def INPUT_TYPES(cls):
result = super().INPUT_TYPES(include_rescale=False, include_chain=False)
bleh_filter_presets = (
() if bleh is None else tuple(bleh.py.latent_utils.FILTER_PRESETS.keys())
)
bleh_enhance_methods = (
() if bleh is None else ("none", *bleh.py.latent_utils.ENHANCE_METHODS)
)
result["required"] |= {
"sonar_custom_noise": (
WILDCARD_NOISE,
@@ -2230,7 +2224,7 @@ class SonarBlendFilterNoiseNode(
},
),
"blend_mode": (("simple_add", *utils.BLENDING_MODES.keys()),),
"ffilter": (bleh_filter_presets,),
"ffilter": (tuple(bleh.py.latent_utils.FILTER_PRESETS.keys()),),
"ffilter_custom": ("STRING", {"default": ""}),
"ffilter_scale": (
"FLOAT",
@@ -2256,7 +2250,7 @@ class SonarBlendFilterNoiseNode(
"INT",
{"default": 1, "min": 1, "max": 32},
),
"enhance_mode": (bleh_enhance_methods,),
"enhance_mode": (("none", *bleh.py.latent_utils.ENHANCE_METHODS),),
"enhance_strength": (
"FLOAT",
{
@@ -2391,18 +2385,11 @@ class KRestartSamplerCustomNoise(metaclass=IntegratedNode):
@classmethod
def INPUT_TYPES(cls):
if restart is not None:
get_normal_schedulers = getattr(
restart.nodes,
"get_supported_normal_schedulers",
restart.nodes.get_supported_restart_schedulers,
)
restart_normal_schedulers = get_normal_schedulers()
restart_schedulers = restart.nodes.get_supported_restart_schedulers()
restart_default_segments = restart.restart_sampling.DEFAULT_SEGMENTS
else:
restart_default_segments = ""
restart_normal_schedulers = restart_schedulers = ()
get_normal_schedulers = getattr(
restart.nodes,
"get_supported_normal_schedulers",
restart.nodes.get_supported_restart_schedulers,
)
return {
"required": {
"model": ("MODEL",),
@@ -2423,7 +2410,7 @@ class KRestartSamplerCustomNoise(metaclass=IntegratedNode):
},
),
"sampler": ("SAMPLER",),
"scheduler": (restart_normal_schedulers,),
"scheduler": (get_normal_schedulers(),),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"latent_image": ("LATENT",),
@@ -2433,11 +2420,13 @@ class KRestartSamplerCustomNoise(metaclass=IntegratedNode):
"segments": (
"STRING",
{
"default": restart_default_segments,
"default": restart.restart_sampling.DEFAULT_SEGMENTS,
"multiline": False,
},
),
"restart_scheduler": (restart_schedulers,),
"restart_scheduler": (
restart.nodes.get_supported_restart_schedulers(),
),
"chunked_mode": ("BOOLEAN", {"default": True}),
},
"optional": {
+24 -26
View File
@@ -1388,8 +1388,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="onef_pinkishgreenish",
noise_mix=(
(OneFNoiseGenerator, {"alpha": 0.5}, None),
(OneFNoiseGenerator, {"alpha": -0.5}, None),
partial(OneFNoiseGenerator, alpha=0.5),
partial(OneFNoiseGenerator, alpha=-0.5),
),
output_fun=lambda t: t.mul_(0.5),
),
@@ -1399,8 +1399,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="onef_pinkish_mix",
noise_mix=(
(OneFNoiseGenerator, {"alpha": 0.5}, lambda t: t.mul_(-1.0)),
(OneFNoiseGenerator, {"alpha": 0.5}, None),
(partial(OneFNoiseGenerator, alpha=0.5), lambda t: t.mul_(-1.0)),
partial(OneFNoiseGenerator, alpha=0.5),
),
output_fun=lambda t: t.mul_(0.5),
),
@@ -1410,8 +1410,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="onef_greenish_mix",
noise_mix=(
(OneFNoiseGenerator, {"alpha": 0.5}, lambda t: t.mul_(-1.0)),
(OneFNoiseGenerator, {"alpha": 0.5}, None),
(partial(OneFNoiseGenerator, alpha=0.5), lambda t: t.mul_(-1.0)),
partial(OneFNoiseGenerator, alpha=0.5),
),
output_fun=lambda t: t.mul_(0.5),
),
@@ -1455,8 +1455,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="rainbow_mild",
noise_mix=(
(GreenTestNoiseGenerator, {}, lambda t: t.mul_(0.55)),
(GreenTestNoiseGenerator, {}, lambda t: t.mul_(0.7)),
(GreenTestNoiseGenerator, lambda t: t.mul_(0.55)),
(GreenTestNoiseGenerator, lambda t: t.mul_(0.7)),
),
output_fun=lambda t: t.mul_(1.15),
),
@@ -1466,8 +1466,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="rainbow_intense",
noise_mix=(
(GreenTestNoiseGenerator, {}, lambda t: t.mul_(0.75)),
(GreenTestNoiseGenerator, {}, lambda t: t.mul_(0.5)),
(GreenTestNoiseGenerator, lambda t: t.mul_(0.75)),
(GreenTestNoiseGenerator, lambda t: t.mul_(0.5)),
),
output_fun=lambda t: t.mul_(1.15),
),
@@ -1502,8 +1502,8 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
MixedNoiseGenerator,
name="pyramid_mix",
noise_mix=(
(PyramidNoiseGenerator, {"discount": 0.6}, lambda t: t.mul_(0.2)),
(PyramidNoiseGenerator, {"discount": 0.6}, lambda t: t.mul_(-0.8)),
(partial(PyramidNoiseGenerator, discount=0.6), lambda t: t.mul_(0.2)),
(partial(PyramidNoiseGenerator, discount=0.6), lambda t: t.mul_(-0.8)),
),
),
),
@@ -1513,13 +1513,11 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
name="pyramid_mix_area",
noise_mix=(
(
PyramidNoiseGenerator,
{"discount": 0.5, "upscale_mode": "area"},
partial(PyramidNoiseGenerator, discount=0.5, upscale_mode="area"),
lambda t: t.mul_(0.2),
),
(
PyramidNoiseGenerator,
{"discount": 0.5, "upscale_mode": "area"},
partial(PyramidNoiseGenerator, discount=0.5, upscale_mode="area"),
lambda t: t.mul_(-0.8),
),
),
@@ -1531,19 +1529,19 @@ NOISE_SAMPLERS: dict[NoiseType, Callable] = {
name="pyramid_mix_bislerp",
noise_mix=(
(
PyramidNoiseGenerator,
{
"discount": 0.5,
"upscale_mode": "bislerp",
},
partial(
PyramidNoiseGenerator,
discount=0.5,
upscale_mode="bislerp",
),
lambda t: t.mul_(0.2),
),
(
PyramidNoiseGenerator,
{
"discount": 0.5,
"upscale_mode": "bislerp",
},
partial(
PyramidNoiseGenerator,
discount=0.5,
upscale_mode="bislerp",
),
lambda t: t.mul_(-0.8),
),
),
+61 -93
View File
@@ -79,6 +79,7 @@ class NoiseError(Exception):
class NoiseGenerator:
name = "unknown"
SAVE_X = False
MIN_DIMS = 1
MAX_DIMS = 0
@@ -99,7 +100,7 @@ class NoiseGenerator:
setattr(self, k, kwarg_params.pop(k))
self.options = kwarg_params
self.update_x(x)
# print("CREATE NG", self, kwargs)
print("CREATE NG", self, kwargs)
@classmethod
@property
@@ -113,13 +114,11 @@ class NoiseGenerator:
}
def update_x(self, x):
self.x = None if not self.SAVE_X else x.detach().clone()
self.shape = x.shape
if x.ndim in {4, 5}:
self.batch, self.channels = x.shape[:2]
self.height, self.width = x.shape[-2:]
self.frames = x.shape[-3] if x.ndim == 5 else None
else:
self.batch = self.channels = self.frames = self.height = self.width = None
self.batch, self.channels, self.height, self.width = (
x.shape if x.ndim == 4 else (None,) * 4
)
self.device = x.device
self.gen_device = torch.device("cpu") if self.cpu else self.device
self.layout = x.layout
@@ -134,11 +133,14 @@ class NoiseGenerator:
layout=self.layout,
device=self.gen_device,
)
# print("GEN NOISE", noise)
if to_device and noise.device != self.device:
noise = tensor_to(noise, self.device)
# print("MADE NOISE", noise)
return noise
def output_hook(self, noise):
# print("NOISE OUT1", noise)
if noise.device != self.device:
noise = tensor_to(noise, self.device)
return scale_noise(
@@ -163,35 +165,9 @@ class NoiseGenerator:
return f"<NoiseGenerator({self.name}): device={self.device}, shape={self.shape}, dtype={self.dtype}, {pretty_params}>"
class FramesToChannelsNoiseGenerator(NoiseGenerator):
MIN_DIMS = 4
MAX_DIMS = 5
def get_adjusted_shape(self):
if self.frames:
return (self.batch, self.channels * self.frames, self.height, self.width)
return (self.batch, self.channels, self.height, self.width)
def fix_output_frames(self, noise):
if not self.frames:
return noise
return noise.reshape(
self.batch,
self.channels,
self.frames,
self.height,
self.width,
)
def rand_like(self, *args, **kwargs):
noise = super().rand_like(*args, **kwargs)
adjusted_shape = self.get_adjusted_shape()
if noise.shape != adjusted_shape:
return noise.reshape(*adjusted_shape)
return noise
class MixedNoiseGenerator(NoiseGenerator):
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
def ng_params(cls):
@@ -204,20 +180,15 @@ class MixedNoiseGenerator(NoiseGenerator):
}
def __init__(self, x, *args, **kwargs):
min_dim = max_dim = None
self.name = kwargs["name"]
for item in kwargs["noise_mix"]:
ng_class = item[0] if isinstance(item, (tuple, list)) else item
cmin, cmax = ng_class.MIN_DIMS, ng_class.MAX_DIMS
min_dim = max(min_dim if min_dim is not None else cmin, cmin)
max_dim = min(max_dim if max_dim is not None else cmax, cmax)
self.MIN_DIMS = min_dim
self.MAX_DIMS = max_dim
super().__init__(x, *args, **kwargs)
ng_list = []
for ng_class, ng_class_kwargs, transform_fun in self.noise_mix:
for item in self.noise_mix:
if isinstance(item, (tuple, list)):
ng_class, transform_fun = item
else:
ng_class, transform_fun = item, None
ng_kwargs = {k: v for k, v in kwargs.items() if k in self.pass_args}
ng_list.append((ng_class(x, **ng_class_kwargs, **ng_kwargs), transform_fun))
ng_list.append((ng_class(x, **ng_kwargs), transform_fun))
self.ng_list = ng_list
def generate(self, *args):
@@ -271,8 +242,9 @@ class BrownianNoiseGenerator(NoiseGenerator):
return self.brownian_tree_ns(*args)
class PerlinOldNoiseGenerator(FramesToChannelsNoiseGenerator):
class PerlinOldNoiseGenerator(NoiseGenerator):
name = "perlin_old"
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
@@ -465,18 +437,18 @@ class PerlinOldNoiseGenerator(FramesToChannelsNoiseGenerator):
blend = utils.BLENDING_MODES[self.blend_mode]
noise = self.rand_like(fun=torch.rand).div_(self.div_fac)
channels, height, width = noise.shape[1:]
_batch, channels, noise_height, noise_width = noise.shape
for _ in range(self.iterations):
noise += self.perlin_noise(
(height, self.width),
(height, width),
batch_size=channels,
(noise_height, noise_width),
(noise_height, noise_width),
batch_size=channels, # This should be the number of channels.
blend=blend,
dtype=noise.dtype,
layout=noise.layout,
device=noise.device,
)
return self.fix_output_frames(noise)
return noise
class UniformNoiseGenerator(NoiseGenerator):
@@ -501,8 +473,9 @@ class UniformNoiseGenerator(NoiseGenerator):
)
class HighresPyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
class HighresPyramidNoiseGenerator(NoiseGenerator):
name = "highres_pyramid"
MIN_DIMS = MAX_DIMS = 4
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -529,10 +502,15 @@ class HighresPyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
}
def generate(self, s, sn):
adjusted_shape = self.get_adjusted_shape()
b, c, h, w = adjusted_shape
(
b,
c,
h,
w,
) = self.shape # EDIT: w and h get over-written, rename for a different variant!
orig_w, orig_h = w, h
noise = self.uniform_ng(s, sn).reshape(*adjusted_shape)
noise = self.uniform_ng(s, sn)
rs = (
torch.rand(
self.iterations,
@@ -553,11 +531,12 @@ class HighresPyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
).mul_(self.discount**i)
if h >= orig_h * 15 or w >= orig_w * 15:
break # Lowest resolution is 1x1
return self.fix_output_frames(noise)
return noise
class PyramidOldNoiseGenerator(FramesToChannelsNoiseGenerator):
class PyramidOldNoiseGenerator(NoiseGenerator):
name = "pyramid_old"
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
@@ -570,11 +549,10 @@ class PyramidOldNoiseGenerator(FramesToChannelsNoiseGenerator):
}
def generate(self, *_args):
adjusted_shape = self.get_adjusted_shape()
b, c, h, w = adjusted_shape
b, c, h, w = self.shape
orig_h, orig_w = h, w
noise = torch.zeros(
size=adjusted_shape,
size=self.shape,
dtype=self.dtype,
layout=self.layout,
device=self.gen_device,
@@ -596,11 +574,12 @@ class PyramidOldNoiseGenerator(FramesToChannelsNoiseGenerator):
orig_h,
mode=self.upscale_mode,
).mul_(self.discount**i)
return self.fix_output_frames(noise)
return noise
class PyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
class PyramidNoiseGenerator(NoiseGenerator):
name = "pyramid"
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
@@ -613,10 +592,12 @@ class PyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
# Modified from https://wandb.ai/johnowhitaker/multires_noise/reports/Multi-Resolution-Noise-for-Diffusion-Model-Training--VmlldzozNjYyOTU2
def generate(self, *_args):
noise = self.rand_like()
b, c, h, w = noise.shape
orig_w, orig_h = w, h
b, c, w, h = (
self.shape
) # NOTE: w and h get over-written, rename for a different variant!
orig_w, orig_h = w, h
noise = self.rand_like()
for i in range(self.iterations):
r = (
torch.rand(1, generator=self.generator).cpu().item() * 2 + 2
@@ -640,7 +621,7 @@ class PyramidNoiseGenerator(FramesToChannelsNoiseGenerator):
)
if w == 1 or h == 1:
break # Lowest resolution is 1x1
return self.fix_output_frames(noise)
return noise
class StudentTNoiseGenerator(NoiseGenerator):
@@ -672,10 +653,9 @@ class StudentTNoiseGenerator(NoiseGenerator):
return torch.copysign(torch.pow(torch.abs(noise), self.pow_fac), noise)
class GreenTestNoiseGenerator(FramesToChannelsNoiseGenerator):
class GreenTestNoiseGenerator(NoiseGenerator):
name = "green_test"
MIN_DIMS = 4
MAX_DIMS = 5
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
@@ -697,7 +677,7 @@ class GreenTestNoiseGenerator(FramesToChannelsNoiseGenerator):
power[0, 0] = self.power_base
noise = torch.fft.ifft2(torch.fft.fft2(noise) / torch.sqrt(power))
noise *= scale / noise.std()
return self.fix_output_frames(torch.real(noise))
return torch.real(noise)
class PinkOldNoiseGenerator(NoiseGenerator):
@@ -714,10 +694,9 @@ class PinkOldNoiseGenerator(NoiseGenerator):
return self.rand_like() * spectral_density
class OneFNoiseGenerator(FramesToChannelsNoiseGenerator):
class OneFNoiseGenerator(NoiseGenerator):
name = "onef"
MIN_DIMS = 4
MAX_DIMS = 5
MIN_DIMS = MAX_DIMS = 4
@classmethod
@property
@@ -733,19 +712,19 @@ class OneFNoiseGenerator(FramesToChannelsNoiseGenerator):
# Referenced from: https://github.com/WASasquatch/PowerNoiseSuite
def generate(self, *_args):
# batch, _channels, height, width = self.shape
batch, _channels, height, width = self.shape
noise = self.rand_like()
freq_x = tensor_to(torch.fft.fftfreq(self.height, self.hfac), noise)
freq_y = tensor_to(torch.fft.fftfreq(self.width, self.wfac), noise)
freq_x = tensor_to(torch.fft.fftfreq(height, self.hfac), noise)
freq_y = tensor_to(torch.fft.fftfreq(width, self.wfac), noise)
fx, fy = torch.meshgrid(freq_x, freq_y, indexing="ij")
power = (fx**2 + fy**2) ** (-self.alpha / 2.0)
if self.k != 0:
power = self.k / power
power[0, 0] = self.base_power
power = power.unsqueeze(0).expand(self.batch, 1, self.height, self.width)
power = power.unsqueeze(0).expand(batch, 1, height, width)
noise_fft = torch.fft.fftn(noise)
noise_fft /= (
@@ -754,7 +733,7 @@ class OneFNoiseGenerator(FramesToChannelsNoiseGenerator):
else power.to(noise_fft.dtype)
)
return self.fix_output_frames(torch.fft.ifftn(noise_fft).real)
return torch.fft.ifftn(noise_fft).real
class PowerLawNoiseGenerator(NoiseGenerator):
@@ -1279,10 +1258,9 @@ class PowerOldNoiseGenerator(NoiseGenerator):
# Idea from https://github.com/ClownsharkBatwing/RES4LYF/ (wave and mode defaults also from that source)
class WaveletNoiseGenerator(FramesToChannelsNoiseGenerator):
class WaveletNoiseGenerator(NoiseGenerator):
name = "wavelet"
MIN_DIMS = 4
MAX_DIMS = 5
MIN_DIMS = MAX_DIMS = 4
def __init__(self, *args, **kwargs):
if not HAVE_WAVELETS:
@@ -1333,21 +1311,11 @@ class WaveletNoiseGenerator(FramesToChannelsNoiseGenerator):
}
def generate(self, *args):
adjusted_shape = self.get_adjusted_shape()
noise = (
self.rand_like()
if self.noise_sampler is None
else self.noise_sampler(*args)
)
if noise.shape != adjusted_shape:
noise = noise.reshape(*adjusted_shape)
if self.frames:
noise = noise.reshape(
self.batch,
self.channels * self.frames,
self.height,
self.width,
)
yl, yh = self.wavelet_forward(noise)
if self.yl_scale != 1:
yl *= self.yl_scale
@@ -1364,7 +1332,7 @@ class WaveletNoiseGenerator(FramesToChannelsNoiseGenerator):
for lidx in range(min(ht.shape[2], len(hscale))):
# print(">> SCALE IDX", lidx)
ht[:, :, lidx, :, :] *= hscale[lidx]
return self.fix_output_frames(self.wavelet_inverse((yl, yh)))
return self.wavelet_inverse((yl, yh))
__all__ = (
+1 -1
View File
@@ -636,7 +636,7 @@ class SonarPowerNoiseNode(SonarCustomNoiseNodeBase):
"max": 100.0,
"step": 0.001,
"round": False,
"tooltip": "Attempts to desaturate the latent by injecting the average across channels (controlled by channel_correction). Applied after mix.",
"tooltip": "Attempts to desaturate thelatent by injecting the average across channels (controlled by channel_correction). Applied after mix.",
},
),
"channel_correlation": (