Author SHA1 Message Date
blepping d8aadb2359 Perlin documentation and cleanups
Add OCSNoise PerlinSimple node
2024-08-27 07:52:12 -06:00
blepping 9b7e6b6083 Enable alt_cfgpp_scale for dpmpp_2s, dpmpp_sde and res samplers 2024-08-27 04:59:06 -06:00
blepping 7fc26d0488 Euler should allow alt_cfgpp 2024-08-25 20:27:00 -06:00
blepping 7418974f6b Initial Perlin3D and 2D implementation 2024-08-21 06:50:54 -06:00
8 changed files with 1058 additions and 13 deletions
+80 -3
View File
@@ -126,11 +126,11 @@ noise:
batch_size: 32 batch_size: 32
# Whether to cache noise. # Whether to cache noise.
caching: true caching: false
# Interval (in full steps) to reset the cache. Brownian noise takes time into account so # Interval (in full steps) to reset the cache. Brownian noise takes time into account so
# if using Brownian you will generally want to reset each step. # if using Brownian with caching enabled you will generally want to reset each step.
cache_reset_interval: 1 cache_reset_interval: 9999
# Immiscible noise processing, see: https://arxiv.org/abs/2406.12303 # Immiscible noise processing, see: https://arxiv.org/abs/2406.12303
immiscible: immiscible:
@@ -211,6 +211,8 @@ noise:
Then the rest of the parameters will use the defaults shown above. Then the rest of the parameters will use the defaults shown above.
***
### `OCS Group` ### `OCS Group`
Defines a group of substeps. Defines a group of substeps.
@@ -300,6 +302,8 @@ post_filter: null
</details> </details>
***
### `OCS Substeps` ### `OCS Substeps`
#### Step Methods (Samplers) #### Step Methods (Samplers)
@@ -599,3 +603,76 @@ The example above means:
3. Go to the second item (after 2 steps, jump back one step). 3. Go to the second item (after 2 steps, jump back one step).
The node `start_step` parameter is effectively the same as `[start_step, 0]` as a schedule item. The node `start_step` parameter is effectively the same as `[start_step, 0]` as a schedule item.
***
### `OCSNoise to SONAR_CUSTOM_NOISE`
Adapter that enables using OCS noise generators with nodes that accept `SONAR_CUSTOM_NOISE`.
Most built-in OCS nodes will accept either type currently.
***
### `OCSNoise PerlinSimple`
Generates 2D or 3D Perlin noise with many tuneable parameters. Can be plugged in to samplers for ancestral or SDE sampling. For initial noise or img2img workflows, use the `NoisyLatentLike` node from `ComfyUI-sonar` (see [Integration](#integration)).
3D Perlin noise works by taking a slice in the depth dimension each time the noise sampler is called.
For more tuneable parameters, see the `OCSNoise PerlinAdvanced` node.
**Note**: The shape of the latent must be a multiple of `lacunarity ** (octaves - 1) * res` (`**` indicates raising something to a power). Most latent types will have one latent pixel equaling eight normal pixels - i.e. if your image is 512x512, the latent would be 64x64.
#### Node Parameters
* `depth`: When non-zero, 3D perlin noise will be generated.
* `detail_level`: Controls the detail level of the noise when `break_pattern` is non-zero. No effect when using 100% raw Perlin noise.
* `octaves`: Generally controls the detail level of the noise. Each octave involves generating a layer of noise so there is a performance cost to increasing octaves.
* `persistence`: Controls how rough the generated noise is. Lower values will result in smoother noise, higher values will look more like Gaussian noise. Comma-separated list, multiple items will apply to octaves in sequence.
* `lacunarity`: Lacunarity controls the frequency multiplier between successive octaves. Only has an effect when octaves is greater than one. Comma-separated list, multiple items will apply to octaves in sequence.
* `res_height`: Number of periods of noise to generate along an axis. Comma-separated list, multiple items will apply to octaves in sequence.
* `break_pattern`: Applies a function to break the Perlin pattern, making it more like normal noise. The value is the blend strength, where 1.0 indicates 100% pattern broken noise and 0.5 indicates 50% raw noise and 50% pattern broken noise. Generally should be at least 0.9 unless you want to generate colorful blobs.
***
### `OCSNoise PerlinAdvanced`
Generates 2D or 3D Perlin noise with many tuneable parameters. Can be plugged in to samplers for ancestral or SDE sampling. For initial noise or img2img workflows, use the `NoisyLatentLike` node from `ComfyUI-sonar` (see [Integration](#integration)).
3D Perlin noise works by taking a slice in the depth dimension each time the noise sampler is called.
**Note**: The shape of the latent in the relevant dimension _including padding_ must be a multiple of `lacunarity ** (octaves - 1) * res`. Most latent types will have one latent pixel equaling eight normal pixels - i.e. if your image is 512x512, the latent would be 64x64.
#### Node Parameters
* `depth`: When non-zero, 3D perlin noise will be generated.
* `detail_level`: Controls the detail level of the noise when `break_pattern` is non-zero. No effect when using 100% raw Perlin noise.
* `octaves`: Generally controls the detail level of the noise. Each octave involves generating a layer of noise so there is a performance cost to increasing octaves.
* `persistence`: Controls how rough the generated noise is. Lower values will result in smoother noise, higher values will look more like Gaussian noise. Comma-separated list, multiple items will apply to octaves in sequence.
* `lacunarity_height`: Lacunarity controls the frequency multiplier between successive octaves. Only has an effect when octaves is greater than one. Comma-separated list, multiple items will apply to octaves in sequence.
* `lacunarity_width`: " "
* `lacunarity_depth`: " "
* `res_height`: Number of periods of noise to generate along an axis. Comma-separated list, multiple items will apply to octaves in sequence.
* `res_width`: " "
* `res_depth`: " "
* `break_pattern`: Applies a function to break the Perlin pattern, making it more like normal noise. The value is the blend strength, where 1.0 indicates 100% pattern broken noise and 0.5 indicates 50% raw noise and 50% pattern broken noise. Generally should be at least 0.9 unless you want to generate colorful blobs.
* `initial_depth`: First zero-based depth index the noise generator will return. Only has an effect when depth is non-zero.
* `wrap_depth`: If non-zero, instead of generating a new chunk of noise when the last slice is used will instead jump back to the specified zero-based depth index. Only has an effect when depth is non-zero. Since this is repeating the same noise, you may need to reduce `s_noise` in samplers especially if your `depth` value is low.
* `max_depth`: Basically crops the depth dimension to the specified value (inclusive). Negative values start from the end, the default of -1 does no cropping. Only has an effect when depth is non-zero. The reason you might want to use this is changing `depth` will also effectively change the seed.
* `tileable_height`: Makes the specified dimension tileable. (May or may not work correctly.)
* `tileable_width`: " "
* `tileable_depth`: " "
* `blend`: Blending function used when generating Perlin noise. When set to values other than LERP may not work at all or may not actually generate Perlin noise. If you have `ComfyUI-bleh` there will be many more blending options (see [Integration](#integration)).
* `pattern_break_blend`: Blending function used to blend pattern broken noise with raw noise. If you have `ComfyUI-bleh` there will be many more blending options (see [Integration](#integration)).
* `depth_over_channels`: When disabled, each channel will have its own separate 3D noise pattern. When enabled, depth is multiplied by the number of channels and each channel is a slice of depth. Only has an effect when depth is non-zero.
* `pad_height`: Pads the specified dimension by the size. Equal padding will be added on both sides and cropped out after generation.
* `pad_width`: " "
* `pad_depth`: " "
* `initial_amplitude`: Controls the amplitude for the first octave. The amplitude gets multiplied by `persistence` after each octave.
* `initial_frequency_height`: Controls the frequency for the first octave for the this axis. The frequency gets multiplied by `lacunarity` after each octave.
* `initial_frequency_width`: " "
* `initial_frequency_depth`: " "
* `normalize`: Controls whether the output noise is normalized after generation.
* `device`: Controls what device is used to generate the noise. GPU noise may be slightly faster but you will get different results on different GPUs.
+2 -2
View File
@@ -1,5 +1,5 @@
from .py import nodes from .py import nodes
from .py import custom_noise
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
"OCS Sampler": nodes.SamplerNode, "OCS Sampler": nodes.SamplerNode,
@@ -9,5 +9,5 @@ NODE_CLASS_MAPPINGS = {
"OCS MultiParam": nodes.MultiParamNode, "OCS MultiParam": nodes.MultiParamNode,
"OCS ModelSetMaxSigma": nodes.ModelSetMaxSigmaNode, "OCS ModelSetMaxSigma": nodes.ModelSetMaxSigmaNode,
"OCS SimpleRestartSchedule": nodes.SimpleRestartSchedule, "OCS SimpleRestartSchedule": nodes.SimpleRestartSchedule,
} } | custom_noise.NODE_CLASS_MAPPINGS
__all__ = ["NODE_CLASS_MAPPINGS"] __all__ = ["NODE_CLASS_MAPPINGS"]
+8
View File
@@ -0,0 +1,8 @@
from . import noise_perlin
from . import nodes
NODE_CLASS_MAPPINGS = {
"OCSNoise PerlinSimple": noise_perlin.PerlinSimpleNode,
"OCSNoise PerlinAdvanced": noise_perlin.PerlinAdvancedNode,
"OCSNoise to SONAR_CUSTOM_NOISE": nodes.ToSonarNode,
}
+178
View File
@@ -0,0 +1,178 @@
import abc
import torch
from typing import Callable, Any
from ..noise import scale_noise
class CustomNoiseItemBase(abc.ABC):
def __init__(self, factor, **kwargs):
self.factor = factor
self.keys = set(kwargs.keys())
for k, v in kwargs.items():
setattr(self, k, v)
def clone_key(self, k):
return getattr(self, k)
def clone(self):
return self.__class__(self.factor, **{k: self.clone_key(k) for k in self.keys})
def set_factor(self, factor):
self.factor = factor
return self
def get_normalize(self, k, default=None):
val = getattr(self, k, None)
return default if val is None else val
@abc.abstractmethod
def make_noise_sampler(
self,
x: torch.Tensor,
sigma_min=None,
sigma_max=None,
seed=None,
cpu=True,
normalized=True,
):
raise NotImplementedError
class CustomNoiseChain:
def __init__(self, items=None):
self.items = items if items is not None else []
def clone(self):
return CustomNoiseChain(
[i.clone() for i in self.items],
)
def add(self, item):
if item is None:
raise ValueError("Attempt to add nil item")
self.items.append(item)
@property
def factor(self):
return sum(abs(i.factor) for i in self.items)
def rescaled(self, scale=1.0):
divisor = self.factor / scale
divisor = divisor if divisor != 0 else 1.0
result = self.clone()
if divisor != 1:
for i in result.items:
i.set_factor(i.factor / divisor)
return result
@torch.no_grad()
def make_noise_sampler(
self,
x: torch.Tensor,
sigma_min=None,
sigma_max=None,
seed=None,
cpu=True,
normalized=True,
) -> Callable:
noise_samplers = tuple(
i.make_noise_sampler(
x,
sigma_min,
sigma_max,
seed=seed,
cpu=cpu,
normalized=False,
)
for i in self.items
)
if not noise_samplers or not all(noise_samplers):
raise ValueError("Failed to get noise sampler")
factor = self.factor
def noise_sampler(sigma, sigma_next):
result = None
for ns in noise_samplers:
noise = ns(sigma, sigma_next)
if result is None:
result = noise
else:
result += noise
return scale_noise(result, factor, normalized=normalized)
return noise_sampler
class CustomNoiseNodeBase(abc.ABC):
DESCRIPTION = "An Overly Complicated Sampling custom noise item."
RETURN_TYPES = ("OCS_NOISE",)
OUTPUT_TOOLTIPS = ("A custom noise chain.",)
CATEGORY = "OveryComplicatedSampling/noise"
FUNCTION = "go"
@abc.abstractmethod
def get_item_class(self):
raise NotImplementedError
@classmethod
def INPUT_TYPES(cls, *, include_rescale=True, include_chain=True):
result = {
"required": {
"factor": (
"FLOAT",
{
"default": 1.0,
"min": -100.0,
"max": 100.0,
"step": 0.001,
"round": False,
"tooltip": "Scaling factor for the generated noise of this type.",
},
),
},
"optional": {},
}
if include_rescale:
result["required"] |= {
"rescale": (
"FLOAT",
{
"default": 0.0,
"min": 0.0,
"max": 100.0,
"step": 0.001,
"round": False,
"tooltip": "When non-zero, this custom noise item and other custom noise items items connected to it will have their factor scaled to add up to the specified rescale value.",
},
),
}
if include_chain:
result["optional"] |= {
"ocs_noise_opt": (
"OCS_NOISE",
{
"tooltip": "Optional input for more custom noise items.",
},
),
}
return result
def go(
self,
factor=1.0,
rescale=0.0,
ocs_noise_opt=None,
**kwargs: dict[str, Any],
):
nis = ocs_noise_opt.clone() if ocs_noise_opt else CustomNoiseChain()
if factor != 0:
nis.add(self.get_item_class()(factor, **kwargs))
return (nis if rescale == 0 else nis.rescaled(rescale),)
class NormalizeNoiseNodeMixin:
@staticmethod
def get_normalize(val: str) -> None | bool:
return None if val == "default" else val == "forced"
+16
View File
@@ -0,0 +1,16 @@
class ToSonarNode:
RETURN_TYPES = ("SONAR_CUSTOM_NOISE",)
CATEGORY = "OveryComplicatedSampling/noise"
FUNCTION = "go"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"ocs_noise": ("OCS_NOISE",),
},
}
@classmethod
def go(cls, ocs_noise):
return (ocs_noise,)
+744
View File
@@ -0,0 +1,744 @@
import torch
import math
import itertools
from .base import CustomNoiseItemBase, CustomNoiseNodeBase, NormalizeNoiseNodeMixin
from ..latent import normalize_to_scale
from ..noise import scale_noise
from ..filtering import BLENDING_MODES
# Perlin generation routines based on https://github.com/Extraltodeus/noise_latent_perlinpinpin which was based on https://gist.github.com/vadimkantorov/ac1b097753f217c5c11bc2ff396e0a57 which was based on https://github.com/pvigier/perlin-numpy
def smoothstep_function(t):
return 6 * t**5 - 15 * t**4 + 10 * t**3
class DEFAULTS:
depth = 16
res = ((1,), (1,), (1,))
octaves = 2
persistence = (1.0,)
lacunarity = ((2,), (2,), (2,))
initial_amplitude = 1.0
initial_frequency = (1.0, 1.0, 1.0)
break_pattern = 1.0
detail_level = 0.0
tileable = (False, False, False)
fade = smoothstep_function
blend = "lerp"
pattern_break_blend = "lerp"
depth_over_channels = False
initial_depth = 0
wrap_depth = 0
max_depth = -1
pad = (0, 0, 0)
generator = None
device = "default"
@classmethod
def get_commasep(cls, key, idx=None):
val = getattr(cls, key)
if idx is not None:
val = val[idx]
return ", ".join(repr(v) for v in val)
def rand_perlin(
shape,
res,
*,
tileable=DEFAULTS.tileable,
fade=DEFAULTS.fade,
blend=BLENDING_MODES[DEFAULTS.blend],
generator=DEFAULTS.generator,
device=DEFAULTS.device,
):
dims = len(res)
didxs = tuple(range(dims))
delta, d = zip(*((res[i] / shape[i], int(shape[i] // res[i])) for i in didxs))
grid = (
torch.stack(
torch.meshgrid(*(torch.arange(0, res[i], delta[i]) for i in didxs)),
dim=-1,
)
% 1
).to(device=device)
noise = (
2
* math.pi
* torch.rand(
max(1, dims - 1),
*(round(res[i]) + 1 for i in didxs),
generator=generator,
device=device,
)
)
if dims == 1:
gradients = torch.cos(noise[0])
elif dims == 2:
gradients = torch.stack((torch.cos(noise[0]), torch.sin(noise[0])), dim=-1)
elif dims == 3:
gradients = torch.stack(
(
torch.sin(noise[0]) * torch.cos(noise[1]),
torch.sin(noise[0]) * torch.sin(noise[1]),
torch.cos(noise[0]),
),
dim=-1,
)
elif dims == 4:
# No idea if this makes sense.
gradients = torch.stack(
(
torch.sin(noise[0]) * torch.cos(noise[1]),
torch.sin(noise[0]) * torch.sin(noise[1]),
torch.sin(noise[1]) * torch.cos(noise[2]),
torch.sin(noise[1]) * torch.sin(noise[2]),
),
dim=-1,
)
else:
raise ValueError("Currently only dimensions up to 4 are supported")
del noise
for tidx, tile in enumerate(tileable[:dims]):
if not tile:
continue
gradients[tuple(-1 if didx == tidx else None for didx in didxs)] = gradients[
tuple(0 if didx == tidx else None for didx in didxs)
]
shape_slices = tuple(slice(0, shape[i]) for i in didxs)
def tile_grads(slices):
result = gradients[*(slice(*slices[i]) for i in didxs)]
for i in didxs:
result = result.repeat_interleave(d[i], i)
return result
def dot(grad, shift):
return (
torch.stack(tuple(grid[*shape_slices, i] + shift[i] for i in didxs), dim=-1)
* grad[shape_slices]
).sum(dim=-1)
# It's just binary with the bits reversed and -1 for enabled columns.
def get_shift(n, dims, *, on_value, off_value):
return tuple(
on_value if n & (1 << bitidx) else off_value for bitidx in range(dims)
)
def blend_reduce(vals, t, depth=0):
curr_t = t[..., depth]
if len(vals) == 2:
return blend(*vals, curr_t)
return blend_reduce(
tuple(blend(v1, v2, curr_t) for v1, v2 in itertools.batched(vals, 2)),
t,
depth + 1,
)
ns = tuple(
dot(
tile_grads(get_shift(i, dims, off_value=(None, -1), on_value=(1, None))),
get_shift(i, dims, off_value=0, on_value=-1),
)
for i in range(1 << dims)
)
return math.sqrt(2) * blend_reduce(ns, fade(grid[shape_slices]))
def generate_fractal_noise(
shape,
res=DEFAULTS.res,
octaves=DEFAULTS.octaves,
persistence=DEFAULTS.persistence,
lacunarity=DEFAULTS.lacunarity,
initial_amplitude=DEFAULTS.initial_amplitude,
initial_frequency=DEFAULTS.initial_frequency,
tileable=DEFAULTS.tileable,
fade=DEFAULTS.fade,
blend=BLENDING_MODES[DEFAULTS.blend],
generator=DEFAULTS.generator,
device=DEFAULTS.device,
):
ndim = len(shape)
def get_wrap_dim(val, *dims):
for dim in dims:
nelem = len(val) if not isinstance(val, torch.Tensor) else val.shape[0]
val = val[dim % nelem]
return val
def get_unwrapped_octaves_dims(val):
return torch.tensor(
tuple(
get_wrap_dim(val, didx, oidx)
for oidx in range(octaves)
for didx in range(ndim)
),
dtype=torch.float,
device="cpu",
).view(octaves, ndim)
res = get_unwrapped_octaves_dims(res)
lacunarity = get_unwrapped_octaves_dims(lacunarity)
initial_frequency = initial_frequency[-ndim:]
persistence = persistence[:octaves]
noise = torch.zeros(shape, dtype=torch.float32, device=device)
frequency = torch.ones(ndim, dtype=torch.float, device="cpu")
frequency[: len(initial_frequency)] = frequency.new(initial_frequency)
amplitude = initial_amplitude
for octave in range(octaves):
noise += amplitude * rand_perlin(
shape,
tuple(
frequency[didx].item() * res[octave][didx].item()
for didx in range(ndim)
),
tileable=tileable,
fade=fade,
blend=blend,
generator=generator,
device=device,
)
# print(
# f"Octave {octave}: freq={frequency}, amp={amplitude}, lac={lacunarity[octave]}, pers={get_wrap_dim(persistence, octave)}"
# )
frequency *= lacunarity[octave]
amplitude *= get_wrap_dim(persistence, octave)
# print(f"Octave {octave}: POST: freq={frequency}, amp={amplitude}")
return noise
def create_noisy_latents_perlin(
width,
height,
depth,
*,
batch_size=1,
detail_level=DEFAULTS.detail_level,
octaves=DEFAULTS.octaves,
persistence=DEFAULTS.persistence,
lacunarity=DEFAULTS.lacunarity,
tileable=DEFAULTS.tileable,
res=DEFAULTS.res,
break_pattern=DEFAULTS.break_pattern,
channels=4,
blend=BLENDING_MODES[DEFAULTS.blend],
pattern_break_blend=BLENDING_MODES[DEFAULTS.pattern_break_blend],
depth_over_channels=DEFAULTS.depth_over_channels,
pad=DEFAULTS.pad,
initial_frequency=DEFAULTS.initial_frequency,
initial_amplitude=DEFAULTS.initial_amplitude,
generator=DEFAULTS.generator,
device=DEFAULTS.device,
):
pad_depth, pad_height, pad_width = pad
if depth < 1:
depth_over_channels = False
pad_depth = 0
shape = (height, width)
eff_shape = (
height + pad_height * 2,
width + pad_width * 2,
)
eff_channels = channels if not depth_over_channels else 1
eff_depth = depth if not depth_over_channels else depth * channels
if depth > 0:
shape = (depth, height, width)
eff_shape = (
eff_depth + pad_depth * 2,
height + pad_height * 2,
width + pad_width * 2,
)
noise = torch.zeros(
(batch_size, channels, *shape),
dtype=torch.float32,
device=device,
)
noise_dims = len(shape)
for i in range(batch_size):
for j in range(eff_channels):
noise_values = generate_fractal_noise(
eff_shape,
res=res,
octaves=octaves,
persistence=persistence,
lacunarity=lacunarity,
tileable=tileable,
blend=blend,
initial_frequency=initial_frequency,
initial_amplitude=initial_amplitude,
generator=generator,
device=device,
)
noise_values = normalize_to_scale(noise_values, -1.0, 1.0, dim=())
if break_pattern != 0:
result = torch.remainder(torch.abs(noise_values) * 1000000, 11) / 11
result = (
((1 + detail_level / 10) * torch.erfinv(2 * result - 1) * (2**0.5))
.mul_(0.2)
.clamp_(-1, 1)
)
result = pattern_break_blend(noise_values, result, break_pattern)
else:
result = noise_values
if pad_width + pad_height + pad_depth > 0:
result = (
result[
...,
pad_depth : eff_depth + pad_depth,
pad_height : height + pad_height,
pad_width : width + pad_width,
]
if noise_dims == 3
else result[
...,
pad_height : height + pad_height,
pad_width : width + pad_width,
]
)
if not depth_over_channels:
noise[i, j, ...] = result
continue
noise[i, ...] = result.view(depth, channels, height, width).movedim(0, 1)
return noise.movedim(-3, 0) if noise_dims == 3 else noise
class PerlinItem(CustomNoiseItemBase):
def __init__(
self,
factor,
*,
depth=20,
detail_level=DEFAULTS.detail_level,
octaves=DEFAULTS.octaves,
persistence=DEFAULTS.persistence,
lacunarity_depth=DEFAULTS.lacunarity[0],
lacunarity_height=DEFAULTS.lacunarity[1],
lacunarity_width=DEFAULTS.lacunarity[2],
lacunarity=None,
tileable_depth=DEFAULTS.tileable[0],
tileable_height=DEFAULTS.tileable[1],
tileable_width=DEFAULTS.tileable[2],
tileable=None,
res_depth=DEFAULTS.res[0],
res_height=DEFAULTS.res[1],
res_width=DEFAULTS.res[2],
res=None,
initial_frequency_depth=DEFAULTS.initial_frequency[0],
initial_frequency_height=DEFAULTS.initial_frequency[1],
initial_frequency_width=DEFAULTS.initial_frequency[2],
initial_frequency=None,
initial_amplitude=DEFAULTS.initial_amplitude,
wrap_depth=DEFAULTS.wrap_depth,
initial_depth=DEFAULTS.initial_depth,
max_depth=DEFAULTS.max_depth,
break_pattern=DEFAULTS.break_pattern,
blend=DEFAULTS.blend,
pattern_break_blend=DEFAULTS.pattern_break_blend,
depth_over_channels=DEFAULTS.depth_over_channels,
pad=None,
pad_depth=DEFAULTS.pad[0],
pad_height=DEFAULTS.pad[1],
pad_width=DEFAULTS.pad[2],
device=None,
normalized=None,
**kwargs,
):
if tileable is None:
tileable = (tileable_depth, tileable_height, tileable_width)[
int(depth == 0) :
]
if res is None:
res = self.maybe_parse_dhw_triple(
(res_depth, res_height, res_width), depth, int
)
if lacunarity is None:
lacunarity = self.maybe_parse_dhw_triple(
(
lacunarity_depth,
lacunarity_height,
lacunarity_width,
),
depth,
)
if pad is None:
pad = (pad_depth, pad_height, pad_width)
if initial_frequency is None:
initial_frequency = (
initial_frequency_depth,
initial_frequency_height,
initial_frequency_width,
)[int(depth == 0) :]
persistence = self.maybe_parse_commasep_list(persistence)
super().__init__(
factor,
depth=depth,
detail_level=detail_level,
octaves=octaves,
persistence=persistence,
lacunarity=lacunarity,
tileable=tileable,
res=res,
initial_frequency=initial_frequency,
initial_amplitude=initial_amplitude,
initial_depth=initial_depth,
wrap_depth=wrap_depth,
max_depth=max_depth,
break_pattern=break_pattern,
blend=blend,
pattern_break_blend=pattern_break_blend,
depth_over_channels=depth_over_channels,
pad=pad,
device=device,
normalized=normalized
if not isinstance(normalized, str)
else NormalizeNoiseNodeMixin.get_normalize(normalized),
**kwargs,
)
@classmethod
def maybe_parse_dhw_triple(cls, val, depth, convert=float):
return tuple(cls.maybe_parse_commasep_list(v) for v in val)[int(depth == 0) :]
@classmethod
def maybe_parse_commasep_list(cls, val, convert=float):
if not isinstance(val, str):
return val
return tuple(convert(v) for v in val.strip().split(",") if v.strip())
def make_noise_sampler(
self,
x: torch.Tensor,
sigma_min: float | None,
sigma_max: float | None,
seed: int | None,
cpu: bool = True,
normalized=True,
):
normalized = self.get_normalize("normalized", normalized)
cpu = cpu if self.device == "default" else self.device == "cpu"
device = torch.device(0 if not cpu else "cpu")
noise_chunk = None
noise_index = self.initial_depth
max_idx = None
b, c, h, w = x.shape
x_device, x_dtype = x.device, x.dtype
del x
blend = BLENDING_MODES[self.blend]
pattern_break_blend = BLENDING_MODES[self.pattern_break_blend]
def noise_sampler(_s, _sn):
nonlocal noise_chunk, noise_index, max_idx
if noise_chunk is None:
# print("-->", noise_index, self.depth)
noise_chunk = create_noisy_latents_perlin(
w,
h,
self.depth,
batch_size=b,
channels=c,
detail_level=self.detail_level,
octaves=self.octaves,
persistence=self.persistence,
lacunarity=self.lacunarity,
initial_frequency=self.initial_frequency,
initial_amplitude=self.initial_amplitude,
break_pattern=self.break_pattern,
res=self.res,
tileable=self.tileable,
blend=blend,
pattern_break_blend=pattern_break_blend,
depth_over_channels=self.depth_over_channels,
pad=self.pad,
device=device,
).to(device=x_device, dtype=x_dtype)
if self.depth < 1: # 2D mode
noise = noise_chunk
noise_chunk = None
return scale_noise(noise, self.factor, normalized=normalized)
if self.max_depth != 0 and self.max_depth != -1:
noise_chunk = noise_chunk[: self.max_depth]
chunk_shape = noise_chunk.shape
max_idx = (
chunk_shape[0] - 1
if self.wrap_depth == 0
else min(self.wrap_depth, chunk_shape[0] - 1)
)
if max_idx < 0:
max_idx += chunk_shape[0]
noise = noise_chunk[noise_index]
noise_index += 1
if noise_index > max_idx:
noise_index = 0
if not self.wrap_depth:
noise_chunk = None
return scale_noise(noise, self.factor, normalized=normalized)
return noise_sampler
class PerlinAdvancedNode(CustomNoiseNodeBase, NormalizeNoiseNodeMixin):
DESCRIPTION = "Advanced Perlin noise generator, allows generating 2D or 3D Perlin noise. See the OCSNoise PerlinSimple node for less tuneable parameters."
@classmethod
def INPUT_TYPES(cls):
result = super().INPUT_TYPES()
result["required"] |= {
"depth": (
"INT",
{
"default": DEFAULTS.depth,
"tooltip": "When non-zero, 3D perlin noise will be generated.",
},
),
"detail_level": (
"FLOAT",
{
"default": DEFAULTS.detail_level,
"tooltip": "Controls the detail level of the noise when break_pattern is non-zero. No effect when using 100% raw Perlin noise.",
},
),
"octaves": (
"INT",
{
"default": DEFAULTS.octaves,
"tooltip": "Generally controls the detail level of the noise. Each octave involves generating a layer of noise so there is a performance cost to increasing octaves.",
},
),
"persistence": (
"STRING",
{
"default": DEFAULTS.get_commasep("persistence"),
"tooltip": "Controls how rough the generated noise is. Lower values will result in smoother noise, higher values will look more like Gaussian noise. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"lacunarity_height": (
"STRING",
{
"default": DEFAULTS.get_commasep("lacunarity", 0),
"tooltip": "Lacunarity controls the frequency multiplier between successive octaves. Only has an effect when octaves is greater than one. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"lacunarity_width": (
"STRING",
{
"default": DEFAULTS.get_commasep("lacunarity", 1),
"tooltip": "Lacunarity controls the frequency multiplier between successive octaves. Only has an effect when octaves is greater than one. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"lacunarity_depth": (
"STRING",
{
"default": DEFAULTS.get_commasep("lacunarity", 2),
"tooltip": "Lacunarity controls the frequency multiplier between successive octaves. Only has an effect when depth is non-zero and octaves is greater than one. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"res_height": (
"STRING",
{
"default": DEFAULTS.get_commasep("res", 0),
"tooltip": "Number of periods of noise to generate along an axis. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"res_width": (
"STRING",
{
"default": DEFAULTS.get_commasep("res", 1),
"tooltip": "Number of periods of noise to generate along an axis. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"res_depth": (
"STRING",
{
"default": DEFAULTS.get_commasep("res", 2),
"tooltip": "Number of periods of noise to generate along an axis. Only has an effect when depth is non-zero. Comma-separated list, multiple items will apply to octaves in sequence.",
},
),
"break_pattern": (
"FLOAT",
{
"default": DEFAULTS.break_pattern,
"tooltip": "Applies a function to break the Perlin pattern, making it more like normal noise. The value is the blend strength, where 1.0 indicates 100% pattern broken noise and 0.5 indicates 50% raw noise and 50% pattern broken noise. Generally should be at least 0.9 unless you want to generate colorful blobs.",
},
),
"initial_depth": (
"INT",
{
"default": DEFAULTS.initial_depth,
"tooltip": "First zero-based depth index the noise generator will return. Only has an effect when depth is non-zero.",
},
),
"wrap_depth": (
"INT",
{
"default": DEFAULTS.wrap_depth,
"tooltip": "If non-zero, instead of generating a new chunk of noise when the last slice is used will instead jump back to the specified zero-based depth index. Only has an effect when depth is non-zero.",
},
),
"max_depth": (
"INT",
{
"default": DEFAULTS.max_depth,
"tooltip": "Basically crops the depth dimension to the specified value (inclusive). Negative values start from the end, the default of -1 does no cropping. Only has an effect when depth is non-zero.",
},
),
"tileable_height": (
"BOOLEAN",
{
"default": DEFAULTS.tileable[0],
"tooltip": "Makes the specified dimension tileable.",
},
),
"tileable_width": (
"BOOLEAN",
{
"default": DEFAULTS.tileable[1],
"tooltip": "Makes the specified dimension tileable.",
},
),
"tileable_depth": (
"BOOLEAN",
{
"default": DEFAULTS.tileable[2],
"tooltip": "Makes the specified dimension tileable. Only has an effect when depth is non-zero.",
},
),
"blend": (
tuple(BLENDING_MODES.keys()),
{
"default": "lerp",
"tooltip": "Blending function used when generating Perlin noise. When set to values other than LERP may not work at all or may not actually generate Perlin noise.",
},
),
"pattern_break_blend": (
tuple(BLENDING_MODES.keys()),
{
"default": "lerp",
"tooltip": "Blending function used to blend pattern broken noise with raw noise.",
},
),
"depth_over_channels": (
"BOOLEAN",
{
"default": DEFAULTS.depth_over_channels,
"tooltip": "When disabled, each channel will have its own separate 3D noise pattern. When enabled, depth is multiplied by the number of channels and each channel is a slice of depth. Only has an effect when depth is non-zero.",
},
),
"pad_height": (
"INT",
{
"default": DEFAULTS.pad[0],
"min": 0,
"tooltip": "Pads the specified dimension by the size. Equal padding will be added on both sides and cropped out after generation.",
},
),
"pad_width": (
"INT",
{
"default": DEFAULTS.pad[1],
"min": 0,
"tooltip": "Pads the specified dimension by the size. Equal padding will be added on both sides and cropped out after generation.",
},
),
"pad_depth": (
"INT",
{
"default": DEFAULTS.pad[2],
"min": 0,
"tooltip": "Pads the specified dimension by the size. Equal padding will be added on both sides and cropped out after generation. Only has an effect when depth is non-zero.",
},
),
"initial_amplitude": (
"FLOAT",
{
"default": DEFAULTS.initial_amplitude,
"tooltip": "Controls the amplitude for the first octave.",
},
),
"initial_frequency_height": (
"FLOAT",
{
"default": DEFAULTS.initial_frequency[0],
"tooltip": "Controls the frequency for the first octave for the this axis.",
},
),
"initial_frequency_width": (
"FLOAT",
{
"default": DEFAULTS.initial_frequency[1],
"tooltip": "Controls the frequency for the first octave for the this axis.",
},
),
"initial_frequency_depth": (
"FLOAT",
{
"default": DEFAULTS.initial_frequency[2],
"tooltip": "Controls the frequency for the first octave for the this axis.",
},
),
"normalize": (
("default", "forced", "off"),
{
"tooltip": "Controls whether the output noise is normalized after generation.",
},
),
"device": (
("default", "cpu", "gpu"),
{
"default": "default",
"tooltip": "Controls what device is used to generate the noise. GPU noise may be slightly faster but you will get different results on different GPUs.",
},
),
}
return result
@classmethod
def get_item_class(cls):
return PerlinItem
class PerlinSimpleNode(PerlinAdvancedNode):
DESCRIPTION = "Simplified Perlin noise generator, allows generating 2D or 3D Perlin noise. See the OCSNoise PerlinAdvanced node for more tuneable parameters."
_COPY_KEYS = {
"factor",
"rescale",
"depth",
"detail_level",
"octaves",
"persistence",
"break_pattern",
}
@classmethod
def INPUT_TYPES(cls):
result = super().INPUT_TYPES()
orig_reqs = result["required"]
reqs = {k: v for k, v in orig_reqs.items() if k in cls._COPY_KEYS}
reqs["lacunarity"] = orig_reqs["lacunarity_height"]
reqs["res"] = orig_reqs["res_height"]
result["required"] = reqs
return result
@classmethod
def get_item_class(cls):
def wrapper(factor, *, lacunarity, res, **kwargs):
return PerlinItem(
factor,
lacunarity_height=lacunarity,
lacunarity_width=lacunarity,
lacunarity_depth=lacunarity,
res_height=res,
res_width=res,
res_depth=res,
**kwargs,
)
return wrapper
+2 -2
View File
@@ -91,8 +91,8 @@ class NoiseSamplerCache:
normalize_noise=True, normalize_noise=True,
cpu_noise=True, cpu_noise=True,
batch_size=32, batch_size=32,
caching=True, caching=False,
cache_reset_interval=1, cache_reset_interval=9999,
set_seed=False, set_seed=False,
scale=1.0, scale=1.0,
normalize_dims=(-3, -2, -1), normalize_dims=(-3, -2, -1),
+28 -6
View File
@@ -376,6 +376,7 @@ class MinSigmaStepMixin:
class EulerStep(SingleStepSampler): class EulerStep(SingleStepSampler):
name = "euler" name = "euler"
allow_cfgpp = True allow_cfgpp = True
allow_alt_cfgpp = True
step = SingleStepSampler.euler_step step = SingleStepSampler.euler_step
@@ -669,6 +670,7 @@ class ReversibleHeun1SStep(ReversibleSingleStepSampler):
class RESStep(SingleStepSampler): class RESStep(SingleStepSampler):
name = "res" name = "res"
model_calls = 1 model_calls = 1
allow_alt_cfgpp = True # May not be implemented correctly.
def __init__(self, *, res_simple_phi=False, res_c2=0.5, **kwargs): def __init__(self, *, res_simple_phi=False, res_c2=0.5, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -689,13 +691,18 @@ class RESStep(SingleStepSampler):
c2_h = 0.5 * h c2_h = 0.5 * h
x_2 = math.exp(-c2_h) * x + a2_1 * h * denoised eff_x = (
x
if self.alt_cfgpp_scale == 0 or ss.hcur.denoised_uncond is None
else x + (ss.denoised - ss.hcur.denoised_uncond) * self.alt_cfgpp_scale
)
x_2 = math.exp(-c2_h) * eff_x + a2_1 * h * denoised
lam_2 = lam + c2_h lam_2 = lam + c2_h
sigma_2 = lam_2.neg().exp() sigma_2 = lam_2.neg().exp()
denoised2 = ss.model(x_2, sigma_2, ss=ss, call_index=1).denoised denoised2 = ss.model(x_2, sigma_2, ss=ss, call_index=1).denoised
x = math.exp(-h) * x + h * (b1 * denoised + b2 * denoised2) x = math.exp(-h) * eff_x + h * (b1 * denoised + b2 * denoised2)
yield from self.result(ss, x, sigma_up) yield from self.result(ss, x, sigma_up)
@@ -1044,9 +1051,11 @@ class EulerDancingStep(SingleStepSampler):
# return result, noise_scale # return result, noise_scale
# Alt CFG++ approach referenced from https://github.com/comfyanonymous/ComfyUI/pull/3871 - thanks!
class DPMPP2SStep(SingleStepSampler, DPMPPStepMixin): class DPMPP2SStep(SingleStepSampler, DPMPPStepMixin):
name = "dpmpp_2s" name = "dpmpp_2s"
model_calls = 1 model_calls = 1
allow_alt_cfgpp = True
def step(self, x, ss): def step(self, x, ss):
t_fn, sigma_fn = self.t_fn, self.sigma_fn t_fn, sigma_fn = self.t_fn, self.sigma_fn
@@ -1056,9 +1065,14 @@ class DPMPP2SStep(SingleStepSampler, DPMPPStepMixin):
r = 1 / 2 r = 1 / 2
h = t_next - t h = t_next - t
s = t + r * h s = t + r * h
x_2 = (sigma_fn(s) / sigma_fn(t)) * x - (-h * r).expm1() * ss.denoised eff_x = (
x
if self.alt_cfgpp_scale == 0 or ss.hcur.denoised_uncond is None
else x + (ss.denoised - ss.hcur.denoised_uncond) * self.alt_cfgpp_scale
)
x_2 = (sigma_fn(s) / sigma_fn(t)) * eff_x - (-h * r).expm1() * ss.denoised
denoised_2 = ss.model(x_2, sigma_fn(s), ss=ss, call_index=1).denoised denoised_2 = ss.model(x_2, sigma_fn(s), ss=ss, call_index=1).denoised
x = (sigma_fn(t_next) / sigma_fn(t)) * x - (-h).expm1() * denoised_2 x = (sigma_fn(t_next) / sigma_fn(t)) * eff_x - (-h).expm1() * denoised_2
yield from self.result(ss, x, sigma_up) yield from self.result(ss, x, sigma_up)
@@ -1066,6 +1080,7 @@ class DPMPPSDEStep(SingleStepSampler, DPMPPStepMixin):
name = "dpmpp_sde" name = "dpmpp_sde"
self_noise = 1 self_noise = 1
model_calls = 1 model_calls = 1
allow_alt_cfgpp = True # Implementation may not be correct.
def __init__(self, *args, r=1 / 2, **kwargs): def __init__(self, *args, r=1 / 2, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -1083,7 +1098,12 @@ class DPMPPSDEStep(SingleStepSampler, DPMPPStepMixin):
# Step 1 # Step 1
sd, su = get_ancestral_step(sigma_fn(t), sigma_fn(s), eta) sd, su = get_ancestral_step(sigma_fn(t), sigma_fn(s), eta)
s_ = t_fn(sd) s_ = t_fn(sd)
x_2 = (sigma_fn(s_) / sigma_fn(t)) * x - (t - s_).expm1() * ss.denoised eff_x = (
x
if self.alt_cfgpp_scale == 0 or ss.hcur.denoised_uncond is None
else x + (ss.denoised - ss.hcur.denoised_uncond) * self.alt_cfgpp_scale
)
x_2 = (sigma_fn(s_) / sigma_fn(t)) * eff_x - (t - s_).expm1() * ss.denoised
x_2 = yield from self.result( x_2 = yield from self.result(
ss, x_2, su, sigma=sigma_fn(t), sigma_next=sigma_fn(s), final=False ss, x_2, su, sigma=sigma_fn(t), sigma_next=sigma_fn(s), final=False
) )
@@ -1093,7 +1113,9 @@ class DPMPPSDEStep(SingleStepSampler, DPMPPStepMixin):
sd, su = get_ancestral_step(sigma_fn(t), sigma_fn(t_next), eta) sd, su = get_ancestral_step(sigma_fn(t), sigma_fn(t_next), eta)
t_next_ = t_fn(sd) t_next_ = t_fn(sd)
denoised_d = (1 - fac) * ss.denoised + fac * denoised_2 denoised_d = (1 - fac) * ss.denoised + fac * denoised_2
x = (sigma_fn(t_next_) / sigma_fn(t)) * x - (t - t_next_).expm1() * denoised_d x = (sigma_fn(t_next_) / sigma_fn(t)) * eff_x - (
t - t_next_
).expm1() * denoised_d
yield from self.result(ss, x, su) yield from self.result(ss, x, su)