From 574ade726f22e093576a6fc6ef2ab3144c032b2f Mon Sep 17 00:00:00 2001 From: Ken Simpson Date: Mon, 29 Sep 2025 14:49:40 -0700 Subject: [PATCH] Add latent frequency split node --- README.md | 5 ++++ __init__.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/README.md b/README.md index 037322f..9e06944 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Runs the rectified-flow ODE backwards to re-noise an encoded latent. Supports de Applies a Gaussian blur directly in latent space. `Spatial Only` blurs each channel independently; `Spatial and Channel` performs a joint 3D blur across channels for stronger smoothing. +### Latent Frequency Split + +Separates a latent into low-pass structure and high-frequency detail bands using a Gaussian crossover so you can process each side differently before recombining. + ### Add Latent Noise (Seeded) Adds seeded Gaussian noise scaled by the input latent's standard deviation. Useful whenever a latent needs a controlled bump in noise without touching the schedule. @@ -50,6 +54,7 @@ Image exploration rarely means chasing a single "correct" answer. These nodes ar - Follow the noise with `Conditioning (Gaussian Blur)` to soften abrupt emphasis changes or to merge multi-prompt blends into a single vibe. Blurring after noise often produces dreamlike, painterly shifts instead of chaotic drift. - Use `Conditioning (Scale)` as a volume knob while iterating: dial the factor down to 0.3–0.5 when you want the image to respond mostly to the recovered latent, then crank it above 1.5 when the textual guidance should take the lead. - Pair `Latent Gaussian Blur` with `Conditioning (Gaussian Blur)` for holistic smoothing—latent blur calms texture while conditioning blur calms prompt pacing. Reintroduce energy by sprinkling in `Add Latent Noise` or ramping the conditioning scale afterwards. +- Split a latent with `Latent Frequency Split`, sharpen or noise the high band while blurring the low band, then recombine via `Latent Mixer`/`Add` nodes to weave sharp detail onto soft composition. - Swap seeds between the inverter, forward diffusion, and noise nodes as you iterate. Small tweaks here can shift the interplay between latent detail and prompt guidance, rewarding playful experimentation. Treat each slider as a brushstroke: push a setting until it breaks, back off to the sweet spot, and capture the happy accidents along the way. diff --git a/__init__.py b/__init__.py index 189d304..c0afb2d 100644 --- a/__init__.py +++ b/__init__.py @@ -302,6 +302,73 @@ class LatentGaussianBlur: out["samples"] = blurred_tensor.cpu() return (out,) + +class LatentFrequencySplit: + """Splits a latent into low- and high-frequency bands via Gaussian smoothing.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "latent": ("LATENT",), + "sigma": ("FLOAT", { + "default": 1.5, + "min": 0.0, + "max": 20.0, + "step": 0.1, + "tooltip": "Radius of the Gaussian used for the low-pass. Higher values move detail into the high band." + }), + } + } + + RETURN_TYPES = ("LATENT", "LATENT") + RETURN_NAMES = ("low_pass", "high_pass") + FUNCTION = "split" + CATEGORY = "Latent/Filter" + + def split(self, latent, sigma): + device = comfy.model_management.get_torch_device() + latent_tensor = latent["samples"].clone().to(device) + + if sigma <= 0.0: + low_samples = latent_tensor + high_samples = torch.zeros_like(latent_tensor) + else: + low_samples = self._gaussian_blur(latent_tensor, sigma) + high_samples = latent_tensor - low_samples + + low_latent = latent.copy() + low_latent["samples"] = low_samples.cpu() + + high_latent = latent.copy() + high_latent["samples"] = high_samples.cpu() + + return (low_latent, high_latent) + + @staticmethod + def _gaussian_blur(latent_tensor, sigma): + tensor = latent_tensor + is_5d = tensor.dim() == 5 + + if is_5d: + b, c, t, h, w = tensor.shape + tensor_4d = tensor.permute(0, 2, 1, 3, 4).reshape(b * t, c, h, w) + else: + tensor_4d = tensor + + kernel_size = int(sigma * 6) + 1 + if kernel_size % 2 == 0: + kernel_size += 1 + + blurred_4d = TF.gaussian_blur(tensor_4d, kernel_size=kernel_size, sigma=sigma) + + if is_5d: + blurred = blurred_4d.view(b, t, c, h, w).permute(0, 2, 1, 3, 4) + else: + blurred = blurred_4d + + return blurred + class LatentAddNoise: """ Adds a configurable amount of seeded random noise to a latent tensor. @@ -602,6 +669,7 @@ class ConditioningScale: NODE_CLASS_MAPPINGS = { "QwenRectifiedFlowInverter": QwenRectifiedFlowInverter, "LatentGaussianBlur": LatentGaussianBlur, + "LatentFrequencySplit": LatentFrequencySplit, "LatentAddNoise": LatentAddNoise, "LatentForwardDiffusion": LatentForwardDiffusion, "LatentHybridInverter": LatentHybridInverter, @@ -613,6 +681,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "QwenRectifiedFlowInverter": "Qwen Rectified Flow Inverter", "LatentGaussianBlur": "Latent Gaussian Blur", + "LatentFrequencySplit": "Latent Frequency Split", "LatentAddNoise": "Add Latent Noise (Seeded)", "LatentForwardDiffusion": "Forward Diffusion (Add Scheduled Noise)", "LatentHybridInverter": "Latent Hybrid Inverter (Qwen)",