The sigmoid quantile norm mode was renamed to sigmoid_keepsign since that's what it was doing. There is a replacement sigmoid quantile norm mode that doesn't care about sign.
Quantile normalization can now take a negative quantile to consider values closest to zero the "extremes". Note: Experimental feature that may not be implemented correctly/subject to change. `SonarShuffledNoise` node reworked. Unfortunately, this will break workflows. If anyone has a burning need for the old version, let me know and I can bring it back as a separate node. The new approach should be better in general though. Fixed momentum sampler init parameter passing. Added more Voronoi noise octave modes. Added replace_2pt/3pt (and variants) quantile norm result modes that use multiple replacement values. Internal cleanups/refactoring.
This commit is contained in:
@@ -2,6 +2,17 @@
|
||||
|
||||
Note, only relatively significant changes to user-visible functionality will be included here. Most recent changes at the top.
|
||||
|
||||
## 20250808
|
||||
|
||||
Aside from the `sigmoid` quantile mode change and `SonarShuffledNoise`, these changes should not break workflows. Let me know if you experience anything unusual.
|
||||
|
||||
* The `sigmoid` quantile norm mode was renamed to `sigmoid_keepsign` since that's what it was doing. There is a replacement `sigmoid` quantile norm mode that doesn't care about sign.
|
||||
* Quantile normalization can now take a negative quantile to consider values closest to zero the "extremes". Note: Experimental feature that may not be implemented correctly/subject to change.
|
||||
* `SonarShuffledNoise` node reworked. Unfortunately, this will break workflows. If anyone has a burning need for the old version, let me know and I can bring it back as a separate node. The new approach should be better in general though.
|
||||
* Fixed momentum sampler init parameter passing.
|
||||
* Added more Voronoi noise octave modes.
|
||||
* Added replace_2pt/3pt (and variants) quantile norm result modes that use multiple replacement values.
|
||||
|
||||
## 20250805
|
||||
|
||||
Once again, large set of changes/internal reorganization which may break stuff. If you run into problems or experience anything weird, please create an issue.
|
||||
|
||||
@@ -461,7 +461,7 @@ The node has many options for calculating the distance between the feature point
|
||||
* You can enter a `+` (plus symbol) separated list of modes. The modes will be calculated and the result will be the average. Distance modes all have the common parameter `dscale` which defaults to 1 and can be overridden. Result modes use `rscale`. See below for a description on passing parameters.
|
||||
* It's possible to pass parameters to distance and result modes. Example with a result mode: `diff:idx1=0:idx2=1:rscale=0.5`
|
||||
|
||||
**Note**: Some modes act as wrappers for other modes. Unfortunately, there isn't currently a good way to escape parameters. Modes ignore parameters they don't understand and the wrapper modes will pass through any parameters they don't use themselves so you _can_ pass parameters to the submodes as long as they don't conflict (and only up to one level).
|
||||
Some modes act as wrappers to other modes. All modes will just ignore parameters they don't understand. Each time a submode is called, one level of "_" at the beginning of parameter names is stripped off. It's not user-friendly but this does allow passing parameters to submodes. Since unknown parameters are ignored, you only need to bother with this if the mode that's calling the submode will use that parameter. Dumb example: `gradient_magnitude:name1=diff:name2=gradient_magnitude:_name1=f4:_name2=f4`. `gradient_magnitude` takes two submodes that it calls (specified with `name1` and `name2`). The top-level `gradient_magnitude` will consume the `name1` and `name2` parameters, strip one level of underscores off the parameter names and call the submodes.
|
||||
|
||||
#### Distance Modes
|
||||
|
||||
@@ -475,7 +475,9 @@ Modes listed with the defaults for parameters they support. These modes also sup
|
||||
* `angle:idx=2` - idxs here range from 0 to 2.
|
||||
* `angle_tanh:idx=2` - Same as `angle` but scales the result with tanh.
|
||||
* `angle_sigmoid:idx=2` - Same as `angle` but scales the result with the sigmoid function.
|
||||
* `fuzz:name=euclidean:fuzz=0.25` - Acts as a wrapper for another result mode (specified with `name`). Will perturb the result by `fuzz` percent of the absolute maximum value. Or more simply, randomizes values by +/- `fuzz` percentage so if you set `fuzz=1` you will essentially get pure noise.
|
||||
* `fuzz:name=euclidean:fuzz=0.25` - Acts as a wrapper for another mode (specified with `name`). Will perturb the result by `fuzz` percent of the absolute maximum value. Or more simply, randomizes values by +/- `fuzz` percentage so if you set `fuzz=1` you will essentially get pure noise.
|
||||
* `fractal_norm:scale=0.1:multiplier=10.0:mode=sin:name=euclidean` - This acts as a wrapper for another mode. `mode` may be one of `sin`, `cos`. It will adjust the input to the mode it wraps by `scale * sin(input * multiplier)` (assuming `mode=sin`).
|
||||
* `weight:h=1.0:w=1.0:z=0.25:name=euclidean` - This acts as a wrapper for another mode and allows you to scale height/width/z (depth) before calling it.
|
||||
|
||||
#### Result Modes
|
||||
|
||||
@@ -491,6 +493,10 @@ Modes listed with the defaults for parameters they support. These modes also sup
|
||||
* `cellid` - Returns a discrete value for the area of each cell (diffusion models hate this). You will need to dilute the Voronoi noise a lot to actually use this. It could also possibly be used for masking.
|
||||
* `median_distance`
|
||||
* `fuzz:name=f1:fuzz=0.25` - Works the same as `fuzz` in distance modes. See the description there.
|
||||
* `fractal_norm:scale=0.1:multiplier=10.0:mode=sin:name=diff` - This acts as a wrapper for another mode. `mode` may be one of `sin`, `cos`. It will adjust the input to the mode it wraps by `scale * sin(input * multiplier)` (assuming `mode=sin`).
|
||||
* `ridge:name=diff:exp=-1.0` - Wraps another mode and may enhance cell borders (doesn't seem super useful).
|
||||
* `softmin:temperature=50.0` - Passes the result through softmin and can be used to smooth the output from distance modes like `angle` that may experience abrupt changes as you move through `z`. It also supports a `use_sorted` parameter that will apply this adjustment to the sorted values as well if it's present and set to anything. Higher temperatures will result in less of a smoothing effect.
|
||||
* `gradient_magnitude:name1=f4:name2=f4:padding_mode=replicate` - Wraps two other modes. Seems pretty nice for adding detail when using low numbers of feature points. `padding_mode` can be set to modes that PyTorch's `pad` function supports.
|
||||
|
||||
#### Depth
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class SamplerNodeSonarBase:
|
||||
max=15.0,
|
||||
tooltip="Multiplier applied to the result of normal sampling.",
|
||||
)
|
||||
.req_field_init_noise_type(
|
||||
.req_field_rand_init_noise_type(
|
||||
tuple(NoiseType.get_names(skip=(NoiseType.BROWNIAN,))),
|
||||
default="gaussian",
|
||||
tooltip="Noise type to use when momentum_init is set to RANDOM.",
|
||||
|
||||
+26
-15
@@ -689,11 +689,11 @@ class SonarQuantileFilteredNoiseNode(SonarCustomNoiseNodeBase):
|
||||
)
|
||||
.req_float_quantile(
|
||||
default=0.85,
|
||||
min=0.0,
|
||||
min=-1.0,
|
||||
max=1.0,
|
||||
step=0.001,
|
||||
round=False,
|
||||
tooltip="When enabled, will normalize generated noise to this quantile (i.e. 0.75 means outliers >75% will be clipped). Set to 1.0 or 0.0 to disable quantile normalization. A value like 0.75 or 0.85 should be reasonable, it really depends on the input and how many of the values are extreme.",
|
||||
tooltip="When enabled, will normalize generated noise to this quantile (i.e. 0.75 means outliers >75% will be clipped). Set to 1.0 or 0.0 to disable quantile normalization. A value like 0.75 or 0.85 should be reasonable, it really depends on the input and how many of the values are extreme. (Experimental) You can also use a negative quantile to consider values closest to 0 to be 'extreme'.",
|
||||
)
|
||||
.req_field_dim(
|
||||
("global", "0", "1", "2", "3", "4"),
|
||||
@@ -766,23 +766,28 @@ class SonarQuantileFilteredNoiseNode(SonarCustomNoiseNodeBase):
|
||||
|
||||
|
||||
class SonarShuffledNoiseNode(SonarCustomNoiseNodeBase):
|
||||
DESCRIPTION = "Custom noise type that allows shuffling noise along some dimension"
|
||||
DESCRIPTION = (
|
||||
"Custom noise type that allows shuffling noise along dimensions you specify."
|
||||
)
|
||||
|
||||
INPUT_TYPES = SonarLazyInputTypes(
|
||||
lambda: NoiseNoChainInputTypes()
|
||||
.req_customnoise_custom_noise(tooltip="Custom noise type to filter.")
|
||||
.req_string_dims(
|
||||
default="-1",
|
||||
default="1,-2,-1",
|
||||
tooltip="Comma separated list of dimensions to shuffle. May be negative to count from the end.",
|
||||
)
|
||||
.req_bool_flatten(
|
||||
tooltip="Controls whether to flatten starting from the dimension before the shuffle operation. May be slow as this requires flattening and then reshaping the tensor back to the correct shape. Flattening will occur between the lowest and highest dimension in the list, other dimensions will be ignored. If they are the same, then it will just flatten from the lowest dimension.",
|
||||
.req_string_percentages(
|
||||
default="1.0,0.25,0.25",
|
||||
tooltip="Comma separated list of percentages (0.0 to 1.0 for 100%) of elements to shuffle. Paired with the list of dimensions and wrap if it's shorter. For example, if you specified three dimensions and two percentages, the third dimension in the list would use the first percentage again.",
|
||||
)
|
||||
.req_float_percentage(
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
tooltip="Percentage of elements to shuffle in the specified dimensions.",
|
||||
.req_bool_fork_rng(
|
||||
default=True,
|
||||
tooltip="When enabled, the RNG state will be forked to generate the shuffle values.",
|
||||
)
|
||||
.req_bool_no_identity(
|
||||
default=True,
|
||||
tooltip="When enabled, ensures shuffle never ends up selecting the original element.",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -795,18 +800,24 @@ class SonarShuffledNoiseNode(SonarCustomNoiseNodeBase):
|
||||
*,
|
||||
factor: float,
|
||||
dims: str,
|
||||
flatten: bool,
|
||||
percentage: float,
|
||||
percentages: str,
|
||||
fork_rng: bool,
|
||||
no_identity: bool,
|
||||
custom_noise: object,
|
||||
):
|
||||
dims = dims.strip()
|
||||
dims = () if not dims else tuple(int(i) for i in dims.split(","))
|
||||
percentages = percentages.strip()
|
||||
percentages = (
|
||||
() if not percentages else tuple(float(p) for p in percentages.split(","))
|
||||
)
|
||||
return super().go(
|
||||
factor,
|
||||
noise=custom_noise,
|
||||
dims=dims,
|
||||
flatten=flatten,
|
||||
percentage=percentage,
|
||||
percentages=percentages,
|
||||
fork_rng=fork_rng,
|
||||
no_identity=no_identity,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+13
-4
@@ -404,11 +404,11 @@ class SonarAdvancedDistroNoiseNode(SonarCustomNoiseNodeBase):
|
||||
"FLOAT",
|
||||
{
|
||||
"default": 0.85,
|
||||
"min": 0.0,
|
||||
"min": -1.0,
|
||||
"max": 1.0,
|
||||
"step": 0.001,
|
||||
"round": False,
|
||||
"tooltip": "When enabled, will normalize generated noise to this quantile (i.e. 0.75 means outliers >75% will be clipped). Set to 1.0 or 0.0 to disable quantile normalization. A value like 0.75 or 0.85 should be reasonable, it really depends on the distribution and how many of the values are extreme.",
|
||||
"tooltip": "When enabled, will normalize generated noise to this quantile (i.e. 0.75 means outliers >75% will be clipped). Set to 1.0 or 0.0 to disable quantile normalization. A value like 0.75 or 0.85 should be reasonable, it really depends on the distribution and how many of the values are extreme. (Experimental) You can use a negative quantile to consider the values closest to zero as extreme.",
|
||||
},
|
||||
),
|
||||
"quantile_norm_mode": (
|
||||
@@ -657,9 +657,18 @@ class SonarAdvancedVoronoiNoiseNode(SonarCustomNoiseNodeBase):
|
||||
f"Possible result modes: {_pretty_result_modes}",
|
||||
)
|
||||
.req_field_octave_mode(
|
||||
("same_features", "new_features"),
|
||||
(
|
||||
"same_features",
|
||||
"new_features",
|
||||
"same_invert_odd",
|
||||
"same_invert_even",
|
||||
"same_roll_chan_up",
|
||||
"same_roll_chan_down",
|
||||
"same_roll_dir_up",
|
||||
"same_roll_dir_down",
|
||||
),
|
||||
default="new_features",
|
||||
tooltip="Only relevant when generating multiple octaves. Controls whether octaves share a set of feature points or if they are different for each octave (note that this is slower).",
|
||||
tooltip="Only relevant when generating multiple octaves. Controls whether octaves share a set of feature points or if they are different for each octave (note that this is slower). Modes starting with 'same' will use the same feature points per octave but may transform them.",
|
||||
)
|
||||
.req_int_octaves(
|
||||
default=3,
|
||||
|
||||
+48
@@ -1907,6 +1907,54 @@ class ShuffledNoise(CustomNoiseItemBase):
|
||||
*args,
|
||||
normalized=True,
|
||||
**kwargs,
|
||||
):
|
||||
factor = self.factor
|
||||
dims = tuple(d if d >= 0 else x.ndim + d for d in self.dims)
|
||||
if not all(d >= 0 and d < x.ndim for d in dims):
|
||||
raise ValueError("Dimension out of range")
|
||||
percentages = self.percentages
|
||||
if not all(0.0 <= p <= 1.0 for p in percentages):
|
||||
raise ValueError("Percentage out of range, must be between 0 and 1")
|
||||
ns = self.noise.make_noise_sampler(
|
||||
x,
|
||||
*args,
|
||||
sigma_min=sigma_min,
|
||||
sigma_max=sigma_max,
|
||||
normalized=normalized,
|
||||
**kwargs,
|
||||
)
|
||||
if not percentages or not dims or all(p == 0 for p in percentages):
|
||||
return ns
|
||||
n_percentages = len(percentages)
|
||||
fork_rng = self.fork_rng
|
||||
no_identity = self.no_identity
|
||||
|
||||
def noise_sampler(sigma, sigma_next):
|
||||
noise = scale_noise(
|
||||
ns(sigma, sigma_next),
|
||||
factor,
|
||||
normalized=normalized,
|
||||
)
|
||||
with torch.random.fork_rng(enabled=fork_rng, devices=(noise.device,)):
|
||||
for idx, dim in enumerate(dims):
|
||||
noise = utils.elementwise_shuffle_by_dim(
|
||||
noise,
|
||||
dim=dim,
|
||||
prob=percentages[idx % n_percentages],
|
||||
no_identity=no_identity,
|
||||
)
|
||||
return noise
|
||||
|
||||
return noise_sampler
|
||||
|
||||
def make_noise_sampler_(
|
||||
self,
|
||||
x,
|
||||
sigma_min,
|
||||
sigma_max,
|
||||
*args,
|
||||
normalized=True,
|
||||
**kwargs,
|
||||
):
|
||||
factor = self.factor
|
||||
dims = {x.ndim + d if d < 0 else d for d in self.dims}
|
||||
|
||||
+242
-83
@@ -6,6 +6,7 @@ from enum import Enum, auto
|
||||
from typing import TYPE_CHECKING, Callable, ClassVar, NamedTuple
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F # noqa: N812
|
||||
from comfy.k_diffusion import sampling
|
||||
from comfy.model_management import throw_exception_if_processing_interrupted
|
||||
from torch import FloatTensor, Generator, Tensor
|
||||
@@ -1292,6 +1293,42 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
MIN_DIMS = 4
|
||||
MAX_DIMS = 4
|
||||
|
||||
voronoi_distance_modes = frozenset((
|
||||
"angle_sigmoid",
|
||||
"angle_tanh",
|
||||
"angle",
|
||||
"chebyshev",
|
||||
"euclidean",
|
||||
"fractal_norm",
|
||||
"fuzz",
|
||||
"manhatten",
|
||||
"minkowski",
|
||||
"quadratic",
|
||||
"weight",
|
||||
))
|
||||
|
||||
voronoi_result_modes = frozenset((
|
||||
"cellid",
|
||||
"diff",
|
||||
"diff2",
|
||||
"f",
|
||||
"f1",
|
||||
"f2",
|
||||
"f3",
|
||||
"f4",
|
||||
"fractal_norm",
|
||||
"fuzz",
|
||||
"inv_f",
|
||||
"inv_f1",
|
||||
"inv_f2",
|
||||
"inv_f3",
|
||||
"inv_f4",
|
||||
"gradient_magnitude",
|
||||
"median_distance",
|
||||
"ridge",
|
||||
"softmin",
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def ng_params(cls, *, no_super: bool = False):
|
||||
result = {
|
||||
@@ -1388,7 +1425,26 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
)
|
||||
|
||||
def get_feature_points(self, octave: int) -> torch.Tensor:
|
||||
return self.feature_points[octave % len(self.feature_points)]
|
||||
result = self.feature_points[octave % len(self.feature_points)]
|
||||
odd_octave = (octave % 2) == 1
|
||||
om = self.octave_mode
|
||||
if (om == "same_invert_odd" and odd_octave) or (
|
||||
om == "same_invert_even" and not odd_octave
|
||||
):
|
||||
return 1.0 - result
|
||||
if octave > 0 and om in {"same_roll_chan_up", "same_roll_chan_down"}:
|
||||
return torch.roll(
|
||||
result,
|
||||
(-1 if om == "same_roll_chan_up" else 1) * (octave % 3),
|
||||
dims=(1,),
|
||||
)
|
||||
if octave > 0 and om in {"same_roll_dir_up", "same_roll_dir_down"}:
|
||||
return torch.roll(
|
||||
result,
|
||||
(-1 if om == "same_roll_dir_up" else 1) * (octave % 3),
|
||||
dims=(3,),
|
||||
)
|
||||
return result
|
||||
|
||||
def get_distance_mode(self, octave: int) -> torch.Tensor:
|
||||
return self.distance_mode[octave % len(self.distance_mode)]
|
||||
@@ -1396,17 +1452,29 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
def get_result_mode(self, octave: int) -> torch.Tensor:
|
||||
return self.result_mode[octave % len(self.result_mode)]
|
||||
|
||||
voronoi_distance_modes = frozenset((
|
||||
"euclidean",
|
||||
"manhatten",
|
||||
"chebyshev",
|
||||
"minkowski",
|
||||
"quadratic",
|
||||
"angle",
|
||||
"angle_tanh",
|
||||
"angle_sigmoid",
|
||||
"fuzz",
|
||||
))
|
||||
def voronoi_call_mode(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
result: bool,
|
||||
args: list | tuple = (),
|
||||
kwargs: dict | None = None,
|
||||
) -> torch.Tensor:
|
||||
name = name.strip().lower()
|
||||
modes = self.voronoi_result_modes if result else self.voronoi_distance_modes
|
||||
mode_label = "result" if result else "distance"
|
||||
if name not in modes:
|
||||
errstr = f"Bad Voronoi {mode_label} mode {name}"
|
||||
raise ValueError(errstr)
|
||||
kwargs = (
|
||||
{}
|
||||
if kwargs is None
|
||||
else {
|
||||
k[1:] if k.startswith("_") and len(k) > 1 else k: v
|
||||
for k, v in kwargs.items()
|
||||
}
|
||||
)
|
||||
return getattr(self, f"_voronoi_{mode_label}_{name}")(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _voronoi_distance_euclidean(d: torch.Tensor, **_kwargs) -> torch.Tensor:
|
||||
@@ -1471,19 +1539,59 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
.acos_()
|
||||
)
|
||||
|
||||
def _voronoi_distance_weight(
|
||||
self,
|
||||
d: torch.Tensor,
|
||||
*args,
|
||||
name: str = "euclidean",
|
||||
h: float | str = 1.0,
|
||||
w: float | str = 1.0,
|
||||
z: float | str = 0.25,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
weights = d.new_tensor((float(h), float(w), float(z)))
|
||||
return self.voronoi_call_mode(
|
||||
name,
|
||||
result=False,
|
||||
args=(d * weights, *args),
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
def _voronoi_distance_fractal_norm(
|
||||
self,
|
||||
d: torch.Tensor,
|
||||
*args,
|
||||
name: str = "euclidean",
|
||||
mode: str = "sin",
|
||||
scale: str | float = 0.1,
|
||||
multiplier: str | float = 10.0,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
if mode == "sin":
|
||||
fun = torch.sin
|
||||
elif mode == "cos":
|
||||
fun = torch.cos
|
||||
else:
|
||||
raise ValueError(
|
||||
"Bad mode parameter for fractal_norm distance mode, must be one of: sin, cos",
|
||||
)
|
||||
adjustment = float(scale) * fun(d * float(multiplier))
|
||||
return self.voronoi_call_mode(
|
||||
name,
|
||||
result=False,
|
||||
args=(d + adjustment, *args),
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
def _voronoi_distance_fuzz(
|
||||
self,
|
||||
*args,
|
||||
name: str = "f1",
|
||||
name: str = "euclidean",
|
||||
fuzz: float | str = 0.25,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
fuzz = float(fuzz)
|
||||
name = name.strip().lower()
|
||||
if name not in self.voronoi_distance_modes:
|
||||
errstr = f"Bad voronoi fuzz distance mode name: {name}"
|
||||
raise ValueError(errstr)
|
||||
result = getattr(self, f"_voronoi_distance_{name}")(*args, **kwargs)
|
||||
result = self.voronoi_call_mode(name, result=False, args=args, kwargs=kwargs)
|
||||
rmin, rmax = result.aminmax()
|
||||
fuzz = max(abs(rmin.item()), abs(rmax.item())) * fuzz
|
||||
result += (
|
||||
@@ -1494,48 +1602,6 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
)
|
||||
return utils.normalize_to_scale(result, rmin.item(), rmax.item(), dim=(-2, -1))
|
||||
|
||||
def voronoi_distance(self, d: torch.Tensor, octave: int) -> torch.Tensor:
|
||||
modes = self.get_distance_mode(octave).split("+")
|
||||
result_scale_base = 1.0 / len(modes)
|
||||
result = None
|
||||
for mode in modes:
|
||||
if ":" in mode:
|
||||
mode_name, *mode_rest = mode.split(":")
|
||||
mode_kwargs = dict(
|
||||
tuple(v.strip() for v in di.split("=", 1)) for di in mode_rest
|
||||
)
|
||||
result_scale = result_scale_base * float(mode_kwargs.pop("dscale", 1.0))
|
||||
else:
|
||||
mode_name = mode
|
||||
mode_kwargs = {}
|
||||
result_scale = result_scale_base
|
||||
if mode_name not in self.voronoi_distance_modes:
|
||||
errstr = f"Bad distance mode {mode}"
|
||||
raise ValueError(errstr)
|
||||
handler = getattr(self, f"_voronoi_distance_{mode_name}")
|
||||
curr_result = handler(d, **mode_kwargs).mul_(result_scale)
|
||||
result = curr_result if result is None else result.add_(curr_result)
|
||||
return result
|
||||
|
||||
voronoi_result_modes = frozenset((
|
||||
"f",
|
||||
"f1",
|
||||
"f2",
|
||||
"f3",
|
||||
"f4",
|
||||
"diff",
|
||||
"diff2",
|
||||
"inv_f",
|
||||
"inv_f1",
|
||||
"inv_f2",
|
||||
"inv_f3",
|
||||
"inv_f4",
|
||||
"cellid",
|
||||
"ridge",
|
||||
"median_distance",
|
||||
"fuzz",
|
||||
))
|
||||
|
||||
@staticmethod
|
||||
def _voronoi_result_f(
|
||||
_d: torch.Tensor,
|
||||
@@ -1609,12 +1675,9 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
exp: float | str = -10.0,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
name = name.strip().lower()
|
||||
if name not in self.voronoi_result_modes:
|
||||
errstr = f"Bad voronoi ridge result mode name: {name}"
|
||||
raise ValueError(errstr)
|
||||
return 1.0 - (
|
||||
float(exp) * getattr(self, f"_voronoi_result_{name}")(*args, **kwargs)
|
||||
float(exp)
|
||||
* self.voronoi_call_mode(name, result=True, args=args, kwargs=kwargs)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -1625,6 +1688,76 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
) -> torch.Tensor:
|
||||
return get_sorted().median(dim=-1).values
|
||||
|
||||
@staticmethod
|
||||
def _voronoi_result_softmin(
|
||||
d: torch.Tensor,
|
||||
*_args,
|
||||
temperature=50.0,
|
||||
use_sorted=None,
|
||||
d_orig: torch.Tensor,
|
||||
get_sorted: Callable,
|
||||
**_kwargs,
|
||||
) -> torch.Tensor:
|
||||
d_norm = d_orig.norm(dim=-1)
|
||||
soft_weights = F.softmax(-d_norm * float(temperature), dim=-1)
|
||||
eff_d = get_sorted() if use_sorted is not None else d
|
||||
return (eff_d * soft_weights).sum(dim=-1)
|
||||
|
||||
def _voronoi_result_gradient_magnitude(
|
||||
self,
|
||||
*args,
|
||||
name1: str = "f4",
|
||||
name2: str = "f4",
|
||||
pad_mode: str = "replicate",
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
r1 = self.voronoi_call_mode(name1, result=True, args=args, kwargs=kwargs)
|
||||
r1_padded = F.pad(r1, (1, 1, 1, 1), mode=pad_mode)
|
||||
if name2 != name1:
|
||||
r2 = self.voronoi_call_mode(name2, result=True, args=args, kwargs=kwargs)
|
||||
r2_padded = F.pad(r2, (1, 1, 1, 1), mode=pad_mode)
|
||||
else:
|
||||
r2 = r1
|
||||
r2_padded = r1_padded
|
||||
dx = r1_padded[..., 1:-1, 2:] - r2_padded[..., 1:-1, :-2]
|
||||
dy = r1_padded[..., 2:, 1:-1] - r2_padded[..., :-2, 1:-1]
|
||||
return (dx**2 + dy**2).sqrt_()
|
||||
|
||||
def _voronoi_result_fractal_norm(
|
||||
self,
|
||||
d: torch.Tensor,
|
||||
*args,
|
||||
name: str = "diff",
|
||||
mode: str = "sin",
|
||||
scale: str | float = 0.1,
|
||||
multiplier: str | float = 10.0,
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
if mode == "sin":
|
||||
fun = torch.sin
|
||||
elif mode == "cos":
|
||||
fun = torch.cos
|
||||
else:
|
||||
raise ValueError(
|
||||
"Bad mode parameter for fractal_norm result mode, must be one of: sin, cos",
|
||||
)
|
||||
d_adjusted = float(scale) * fun(d * float(multiplier))
|
||||
my_d_sorted = None
|
||||
|
||||
def my_get_sorted():
|
||||
nonlocal my_d_sorted
|
||||
if my_d_sorted is not None:
|
||||
return my_d_sorted
|
||||
my_d_sorted = d_adjusted.sort(dim=-1).values
|
||||
return my_d_sorted
|
||||
|
||||
return self.voronoi_call_mode(
|
||||
name,
|
||||
result=True,
|
||||
args=(d_adjusted, *args),
|
||||
kwargs=kwargs | {"get_sorted": my_get_sorted},
|
||||
)
|
||||
|
||||
def _voronoi_result_fuzz(
|
||||
self,
|
||||
*args,
|
||||
@@ -1633,11 +1766,7 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
**kwargs,
|
||||
) -> torch.Tensor:
|
||||
fuzz = float(fuzz)
|
||||
name = name.strip().lower()
|
||||
if name not in self.voronoi_result_modes:
|
||||
errstr = f"Bad voronoi fuzz result mode name: {name}"
|
||||
raise ValueError(errstr)
|
||||
result = getattr(self, f"_voronoi_result_{name}")(*args, **kwargs)
|
||||
result = self.voronoi_call_mode(name, result=True, args=args, kwargs=kwargs)
|
||||
rmin, rmax = result.aminmax()
|
||||
fuzz = max(abs(rmin.item()), abs(rmax.item())) * fuzz
|
||||
result += (
|
||||
@@ -1648,7 +1777,37 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
)
|
||||
return utils.normalize_to_scale(result, rmin.item(), rmax.item(), dim=(-2, -1))
|
||||
|
||||
def voronoi_result(self, d: torch.Tensor, octave: int) -> torch.Tensor:
|
||||
def voronoi_distance(self, d: torch.Tensor, octave: int) -> torch.Tensor:
|
||||
modes = self.get_distance_mode(octave).split("+")
|
||||
result_scale_base = 1.0 / len(modes)
|
||||
result = None
|
||||
for mode in modes:
|
||||
if ":" in mode:
|
||||
mode_name, *mode_rest = mode.split(":")
|
||||
mode_kwargs = dict(
|
||||
tuple(val.strip() for val in di.split("=", 1)) for di in mode_rest
|
||||
)
|
||||
result_scale = result_scale_base * float(mode_kwargs.pop("dscale", 1.0))
|
||||
else:
|
||||
mode_name = mode
|
||||
mode_kwargs = {}
|
||||
result_scale = result_scale_base
|
||||
curr_result = self.voronoi_call_mode(
|
||||
mode_name,
|
||||
result=False,
|
||||
args=(d,),
|
||||
kwargs=mode_kwargs,
|
||||
).mul_(result_scale)
|
||||
result = curr_result if result is None else result.add_(curr_result)
|
||||
return result
|
||||
|
||||
def voronoi_result(
|
||||
self,
|
||||
d: torch.Tensor,
|
||||
d_orig: torch.Tensor,
|
||||
*,
|
||||
octave: int,
|
||||
) -> torch.Tensor:
|
||||
modes = self.get_result_mode(octave).split("+")
|
||||
result_scale_base = 1.0 / len(modes)
|
||||
result = None
|
||||
@@ -1661,6 +1820,10 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
d_sorted = d.sort(dim=-1).values
|
||||
return d_sorted
|
||||
|
||||
base_kwargs = {
|
||||
"d_orig": d_orig,
|
||||
"get_sorted": get_sorted,
|
||||
}
|
||||
for mode in modes:
|
||||
if ":" in mode:
|
||||
mode_name, *mode_rest = mode.split(":")
|
||||
@@ -1672,14 +1835,11 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
result_scale = result_scale_base
|
||||
mode_name = mode
|
||||
mode_kwargs = {}
|
||||
if mode_name not in self.voronoi_result_modes:
|
||||
errstr = f"Bad result mode {mode}"
|
||||
raise ValueError(errstr)
|
||||
handler = getattr(self, f"_voronoi_result_{mode_name}")
|
||||
curr_result = handler(
|
||||
d,
|
||||
get_sorted=get_sorted,
|
||||
**mode_kwargs,
|
||||
curr_result = self.voronoi_call_mode(
|
||||
mode_name,
|
||||
result=True,
|
||||
args=(d,),
|
||||
kwargs=mode_kwargs | base_kwargs,
|
||||
).mul_(result_scale)
|
||||
result = curr_result if result is None else result.add_(curr_result)
|
||||
return result
|
||||
@@ -1704,10 +1864,9 @@ class VoronoiNoiseGenerator(NoiseGenerator):
|
||||
fp = (fp * scale) % 1.0
|
||||
|
||||
# Toroidal wrapped difference
|
||||
d = (grid_3d - fp + 0.5) % 1.0 - 0.5 # Wrap to [-0.5, 0.5)
|
||||
|
||||
d = self.voronoi_distance(d, octave=octave)
|
||||
return self.voronoi_result(d, octave=octave)
|
||||
d_orig = d = (grid_3d - fp + 0.5) % 1.0 - 0.5 # Wrap to [-0.5, 0.5)
|
||||
d = self.voronoi_distance(d.clone(), octave=octave)
|
||||
return self.voronoi_result(d, d_orig, octave=octave)
|
||||
|
||||
def generate(self, *args):
|
||||
if self.grid_xyz is None or self.feature_points is None or self.z_max == 0:
|
||||
|
||||
+176
-49
@@ -181,13 +181,32 @@ def _quantile_norm_replace(
|
||||
*,
|
||||
keep_sign: bool = False,
|
||||
avoid_sign: bool = False,
|
||||
count: int = 1,
|
||||
count_flipping: bool = False,
|
||||
**_kwargs: dict,
|
||||
) -> torch.Tensor:
|
||||
mask = noise.abs() <= nq
|
||||
candidates = noise[mask].flatten()
|
||||
candidates = candidates[torch.arange(noise.numel()) % candidates.numel()].reshape(
|
||||
noise.shape,
|
||||
)
|
||||
n_candidates = candidates.numel()
|
||||
idxs = torch.arange(noise.numel()) % n_candidates
|
||||
cresult = candidates[idxs]
|
||||
if count < 2:
|
||||
candidates = cresult
|
||||
else:
|
||||
multiplier = 1.0 / count
|
||||
cresult = cresult * multiplier # noqa: PLR6104
|
||||
for i in range(1, count):
|
||||
cresult += (
|
||||
candidates[
|
||||
torch.roll(
|
||||
idxs,
|
||||
i if not count_flipping or (i % 2) == 0 else -i,
|
||||
dims=(-1,),
|
||||
)
|
||||
]
|
||||
* multiplier
|
||||
)
|
||||
candidates = cresult.reshape(noise.shape)
|
||||
if keep_sign or avoid_sign:
|
||||
candidates = candidates.copysign_(noise.neg() if avoid_sign else noise)
|
||||
return torch.where(mask, noise, candidates)
|
||||
@@ -202,9 +221,12 @@ quantile_handlers = {
|
||||
noise.tanh().mul_(nq.abs()),
|
||||
noise,
|
||||
),
|
||||
"sigmoid": lambda noise, nq, **_kwargs: noise.sigmoid()
|
||||
"sigmoid_keepsign": lambda noise, nq, **_kwargs: noise.sigmoid()
|
||||
.mul_(nq.abs())
|
||||
.copysign(noise),
|
||||
"sigmoid": lambda noise, nq, **_kwargs: noise.sigmoid()
|
||||
.mul_(nq.abs() * 2)
|
||||
.sub_(nq.abs()),
|
||||
"sigmoid_outliers": lambda noise, nq, **_kwargs: torch.where(
|
||||
noise.abs() > nq,
|
||||
noise.sigmoid().mul_(nq.abs()).copysign(noise),
|
||||
@@ -290,6 +312,54 @@ quantile_handlers = {
|
||||
"replace": _quantile_norm_replace,
|
||||
"replace_keepsign": partial(_quantile_norm_replace, keep_sign=True),
|
||||
"replace_avoidsign": partial(_quantile_norm_replace, avoid_sign=True),
|
||||
"replace_2pt": partial(_quantile_norm_replace, count=2),
|
||||
"replace_3pt": partial(_quantile_norm_replace, count=3),
|
||||
"replace_2pt_flip": partial(_quantile_norm_replace, count=2, count_flipping=True),
|
||||
"replace_3pt_flip": partial(_quantile_norm_replace, count=3, count_flipping=True),
|
||||
"replace_2pt_keepsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=2,
|
||||
keep_sign=True,
|
||||
),
|
||||
"replace_3pt_keepsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=3,
|
||||
keep_sign=True,
|
||||
),
|
||||
"replace_2pt_flip_keepsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=2,
|
||||
count_flipping=True,
|
||||
keep_sign=True,
|
||||
),
|
||||
"replace_3pt_flip_keepsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=3,
|
||||
count_flipping=True,
|
||||
keep_sign=True,
|
||||
),
|
||||
"replace_2pt_avoidsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=2,
|
||||
avoid_sign=True,
|
||||
),
|
||||
"replace_3pt_avoidsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=3,
|
||||
avoid_sign=True,
|
||||
),
|
||||
"replace_2pt_flip_avoidsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=2,
|
||||
count_flipping=True,
|
||||
avoid_sign=True,
|
||||
),
|
||||
"replace_3pt_flip_avoidsign": partial(
|
||||
_quantile_norm_replace,
|
||||
count=3,
|
||||
count_flipping=True,
|
||||
avoid_sign=True,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -297,14 +367,14 @@ quantile_handlers = {
|
||||
def quantile_normalize(
|
||||
noise: torch.Tensor,
|
||||
*,
|
||||
quantile: float = 0.75,
|
||||
quantile: float | tuple | list = 0.75,
|
||||
dim: int | None = 1,
|
||||
flatten: bool = True,
|
||||
nq_fac: float = 1.0,
|
||||
pow_fac: float = 0.5,
|
||||
strategy: str = "clamp",
|
||||
strategy_handler=None,
|
||||
use_abs: bool = True,
|
||||
eps=1e-08,
|
||||
) -> torch.Tensor:
|
||||
if noise.numel() == 0:
|
||||
return noise
|
||||
@@ -319,51 +389,18 @@ def quantile_normalize(
|
||||
pow_fac=pow_fac,
|
||||
strategy=strategy,
|
||||
strategy_handler=strategy_handler,
|
||||
use_abs=use_abs,
|
||||
)
|
||||
return noise
|
||||
if quantile is None or quantile <= 0 or quantile >= 1:
|
||||
if quantile is None or quantile >= 1 or quantile <= -1:
|
||||
return noise
|
||||
if not use_abs:
|
||||
pos_mask = noise >= 0
|
||||
neg_mask = ~pos_mask
|
||||
result = torch.zeros_like(noise)
|
||||
result[pos_mask] = quantile_normalize(
|
||||
noise=noise[pos_mask],
|
||||
quantile=quantile,
|
||||
dim=dim,
|
||||
flatten=flatten,
|
||||
nq_fac=nq_fac,
|
||||
pow_fac=pow_fac,
|
||||
strategy=strategy,
|
||||
strategy_handler=strategy_handler,
|
||||
use_abs=True,
|
||||
)
|
||||
result[neg_mask] = quantile_normalize(
|
||||
noise=noise[neg_mask],
|
||||
quantile=quantile,
|
||||
dim=dim,
|
||||
flatten=flatten,
|
||||
nq_fac=nq_fac,
|
||||
pow_fac=pow_fac,
|
||||
strategy=strategy,
|
||||
strategy_handler=strategy_handler,
|
||||
use_abs=True,
|
||||
)
|
||||
return result
|
||||
centered = quantile < 0
|
||||
absquantile = abs(quantile)
|
||||
orig_shape = noise.shape
|
||||
if noise.ndim > 1 and flatten:
|
||||
flatnoise = noise.flatten(start_dim=dim)
|
||||
else:
|
||||
flatten = False
|
||||
flatnoise = noise
|
||||
nq = torch.quantile(
|
||||
flatnoise.abs(),
|
||||
quantile,
|
||||
dim=-1 if flatten else dim,
|
||||
keepdim=True,
|
||||
)
|
||||
nq = nq.mul_(nq_fac)
|
||||
handler = (
|
||||
quantile_handlers.get(strategy)
|
||||
if strategy_handler is None
|
||||
@@ -371,13 +408,42 @@ def quantile_normalize(
|
||||
)
|
||||
if handler is None:
|
||||
raise ValueError("Unknown strategy")
|
||||
noise = handler(
|
||||
flatnoise,
|
||||
nq,
|
||||
orig_noise=noise,
|
||||
dim=dim,
|
||||
flatten=flatten,
|
||||
)
|
||||
if not centered:
|
||||
nq = torch.quantile(
|
||||
flatnoise.abs(),
|
||||
quantile,
|
||||
dim=-1 if flatten else dim,
|
||||
keepdim=True,
|
||||
)
|
||||
nq = nq.mul_(nq_fac).add_(eps)
|
||||
# print(f"\nNQ: {nq}")
|
||||
noise = handler(
|
||||
flatnoise,
|
||||
nq,
|
||||
orig_noise=noise,
|
||||
dim=dim,
|
||||
flatten=flatten,
|
||||
)
|
||||
else:
|
||||
absnoise = flatnoise.abs()
|
||||
maxabs = absnoise.amax(dim=-1 if flatten else dim, keepdim=True)
|
||||
proxy = flatnoise.sign().mul_(maxabs - absnoise)
|
||||
nq_proxy = torch.quantile(
|
||||
proxy.abs(),
|
||||
absquantile,
|
||||
dim=-1 if flatten else dim,
|
||||
keepdim=True,
|
||||
)
|
||||
nq_proxy = nq_proxy.mul_(nq_fac).add_(eps)
|
||||
# print(f"\nNQ proxy: {nq_proxy}")
|
||||
out_proxy = handler(
|
||||
proxy,
|
||||
nq_proxy,
|
||||
orig_noise=noise,
|
||||
dim=dim,
|
||||
flatten=flatten,
|
||||
)
|
||||
noise = out_proxy.sign().mul_(maxabs - out_proxy.abs())
|
||||
if pow_fac not in {0.0, 1.0}:
|
||||
noise = noise.abs().pow_(pow_fac).copysign(noise)
|
||||
return noise if noise.shape == orig_shape else noise.reshape(orig_shape)
|
||||
@@ -530,6 +596,67 @@ def pattern_break(
|
||||
return blend_function(noise, result, percentage).to(dtype=orig_dtype)
|
||||
|
||||
|
||||
def elementwise_shuffle_by_dim(
|
||||
t: torch.Tensor,
|
||||
*,
|
||||
dim: int = -1,
|
||||
prob: float = 1.0,
|
||||
no_identity: bool = False,
|
||||
generator=None,
|
||||
) -> torch.Tensor:
|
||||
orig_shape = t.shape
|
||||
device = t.device
|
||||
|
||||
num_positions = math.prod(orig_shape[:dim] + orig_shape[dim + 1 :])
|
||||
num_elements = orig_shape[dim]
|
||||
|
||||
tensor_2d = t.permute(
|
||||
*tuple(d for d in range(t.dim()) if d != dim),
|
||||
dim,
|
||||
).reshape(-1, num_elements)
|
||||
|
||||
rand_perms = (
|
||||
torch.arange(num_elements, device=device).expand(num_positions, -1).clone()
|
||||
)
|
||||
|
||||
if prob < 1.0:
|
||||
mask = torch.rand(num_positions, device=device, generator=generator) < prob
|
||||
else:
|
||||
mask = torch.ones(num_positions, device=device, dtype=torch.bool)
|
||||
|
||||
if no_identity:
|
||||
offsets = torch.randint(
|
||||
1,
|
||||
num_elements,
|
||||
(num_positions,),
|
||||
device=device,
|
||||
generator=generator,
|
||||
)
|
||||
rand_perms[mask] = (
|
||||
torch.arange(num_elements, device=device) + offsets[mask][:, None]
|
||||
) % num_elements
|
||||
else:
|
||||
rand_perms[mask] = torch.rand(
|
||||
num_positions,
|
||||
num_elements,
|
||||
device=device,
|
||||
generator=generator,
|
||||
)[mask].argsort(dim=1)
|
||||
|
||||
shuffled_2d = torch.gather(tensor_2d, 1, rand_perms)
|
||||
|
||||
shuffled = shuffled_2d.reshape(
|
||||
*orig_shape[:dim],
|
||||
*orig_shape[dim + 1 :],
|
||||
orig_shape[dim],
|
||||
)
|
||||
return shuffled.permute(
|
||||
*tuple(d for d in range(t.dim() - 1) if d < dim),
|
||||
t.dim() - 1,
|
||||
*tuple(d for d in range(t.dim() - 1) if d >= dim),
|
||||
).contiguous()
|
||||
|
||||
|
||||
def trunc_decimals(x: torch.Tensor, decimals: int = 3) -> torch.Tensor:
|
||||
x_i = x.trunc()
|
||||
x_f = x - x_i
|
||||
|
||||
Reference in New Issue
Block a user