Nodes/ComfyUI-Spectral-Preprocessing-Nodes/Radial Stratified Histogram Match
ComfyUI Node

Radial Stratified Histogram Match

Match the spectrum per radial band, not all at once

By EdoardoGuerriero·Created 2 months ago·Updated 2 months ago· 0
Radial Stratified Histogram Match
  • image_source
  • image_target
  • image
strength0.75
n_bands16
preserve_low_freq0.05
tile_size0
tile_overlap64

If you've ever done pixel-space histogram matching, you know the idea: take the brightness distribution of one image and remap another image's pixels so its histogram matches. RadialStratifiedHistogramMatch does the frequency-domain version of that - it remaps the FFT magnitude distribution of one image to match another's - but with a crucial upgrade: it does it independently within each radial frequency band. That makes it the spectral analogue of CLAHE, and the README calls it the most expressive spectral matching node in the pack.

Here's why the per-band approach matters. A global histogram match treats all FFT coefficients as one population, but low-frequency and high-frequency coefficients follow completely different statistical distributions. Matching them with a single cumulative-distribution curve smears the correction across scales - you end up pulling low-frequency structure toward high-frequency statistics and vice versa. Splitting the spectrum into radial annuli and giving each ring its own CDF mapping respects the per-scale statistics, which is exactly what you want when the goal is transferring a texture's character rather than its overall brightness.

What it's for

The pack frames it as the two-way texture workhorse:

  • Pre-processing: make a synthetic or AI-generated image spectrally indistinguishable from a natural photograph before VAE encoding - the README's demo shows matched spectra converging on the target's.
  • Post-processing: give a generated image the same grain and texture character as a reference, without altering its colours or content. Phase is always preserved, so edges and layout survive intact.

The inputs that matter

  • image_source - the image whose per-band spectral distribution gets remapped.
  • image_target - the reference; its per-band distribution is the target.
  • strength (default 0.75) - blend between "no change" and "fully matched." Start around 0.5–0.75.
  • n_bands (default 16) - number of radial annuli. The tooltip's sweet spot is 8–24; more bands mean finer per-scale control but noisier statistics.
  • preserve_low_freq (default 0.05) - radius below which bands are skipped entirely, protecting global brightness and colour. The tooltip has a genuinely useful split: keep it at 0.05–0.1 for post-processing, but raise to 0.15–0.2 for pre-processing so you don't match away the source's actual structure.

One genuine quirk in this pack: tile_size's tooltip says leave it at 0 - tiling changes the band statistics and defeats the per-band logic. This is the one node where the usual "tile big images" advice doesn't apply.

Installing it

Ships in ComfyUI-Spectral-Preprocessing-Nodes; one install gives you all sixteen nodes under Spectral Preprocessing. ComfyUI Manager (search "ComfyUI-Spectral-Preprocessing-Nodes"), or:

cd ComfyUI/custom_nodes
git clone https://github.com/EdoardoGuerriero/ComfyUI-Spectral-Preprocessing-Nodes

Restart. Only numpy and scipy (both already present), no models to download.

Where people get burned

The two-input design is the first thing that trips people up - it needs both a source and a target image, and the output is source-content-with-target-spectrum, not some average of the two. If you only have one image, this isn't the node for you (that's SpectralHistogramMatch's global variant - also two inputs, actually - or RadialSpectrumNormalizer if you just want to nudge toward natural without a reference). Second, over-matching: at strength 1.0 with a highly textured target you can push the source's spectrum onto the target's so hard that fine detail flattens. The README's own comparison (natural target / AI source / global match / stratified match) is the best illustration of what you're buying here - the stratified result converges on the target's spectrum while keeping the source's content, and a partial strength keeps it honest.

CategorySpectral Preprocessing

Inputs (7)

NameTypeDefaultDescription
image_sourceIMAGEImage whose per-band spectral distribution will be remapped.
image_targetIMAGEReference — its per-band magnitude distribution is the target.
strengthFLOAT0.750–10 = no change, 1 = fully match target per-band distribution.
n_bandsINT162–64Number of radial frequency bands. More bands = finer per-scale control but more sensitive to noise in the statistics. 8–24 is a good range.
preserve_low_freqFLOAT0.050–0.5Normalised radius below which bands are skipped entirely. Protects global brightness and colour from being matched away. Keep at 0.05–0.1 for post-processing; raise to 0.15–0.2 for pre-processing.
tile_sizeINT00–2048Tile size for large images. 0 = whole image (recommended for this node — tiling changes the band statistics).
tile_overlapINT640–512Tile overlap in pixels (used only when tile_size > 0).

Outputs (1)

NameTypeDescription
imageIMAGE