Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8aadb2359 | ||
|
|
9b7e6b6083 | ||
|
|
7fc26d0488 | ||
|
|
7418974f6b |
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -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,)
|
||||||
@@ -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
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user