Hermitian Symmetry Enforcer
The 'actually, your FFT has been lying to you' cleanup node
- image
- image
- residual
This is the most technical node in ComfyUI-Spectral-Preprocessing-Nodes, and the honest framing is that it's the one you may never strictly need. But if you've been hand-editing FFTs, running images through several lossy codec rounds, or just want the frequency-domain equivalent of a cleanup pass before VAE encoding, it fixes a bug you can't see - and for most images, the fix is genuinely invisible.
Here's the bug. For any real image, its discrete Fourier transform must satisfy a constraint called Hermitian symmetry: F[k,l] = conj(F[-k,-l]). The spectrum of a real image isn't arbitrary - half of it is mathematically forced to be the mirror-conjugate of the other half. Lossy codec cycles, certain interpolation methods, and manual frequency-domain edits can subtly violate that constraint. When you IFFT a spectrum that's broken this way, you get a nonzero imaginary component - which every np.fft.ifft2(...).real call then silently throws away. Energy that ought to be in the image just vanishes. Nothing errors, nothing looks obviously wrong; you just lose a little spectral energy you were owed.
How it works
The fix is elegant and deterministic: replace the spectrum with the nearest Hermitian matrix, in the minimum-L2 sense:
F_corrected = (F + conj(F_flipped)) / 2
There are no trainable parameters and nothing to tune beyond one strength slider (default 1.0 = fully enforce, 0.0 = no change). For clean natural images the residual is tiny - the node's own docs say below 1e-6 in pixel values - which is why the tooltip can promise "for most images the difference is invisible at strength=1."
Inputs and outputs
The only input besides the image is strength (default 1.0). Keep it there. The interesting part is the two outputs:
image- the corrected image, wire this onward.residual- a diagnostic view of the imaginary part of the IFFT of the original spectrum. Nearly black means "no problem here"; visibly structured content means the input genuinely had broken symmetry and the node just earned its keep.
The recommended placement, per the source, is after any node that does manual magnitude/phase editing, or as a cleanup step right before VAE encode. If you're only running the pack's own nodes in sequence, they all preserve phase and produce valid spectra, so this is belt-and-suspenders. If you've been editing FFTs with external tools or feeding heavily recompressed images, it's cheap insurance.
Installing it
Ships with the whole pack - one install gives you all sixteen nodes under Spectral Preprocessing in the node browser. ComfyUI Manager (search "ComfyUI-Spectral-Preprocessing-Nodes") or:
cd ComfyUI/custom_nodes
git clone https://github.com/EdoardoGuerriero/ComfyUI-Spectral-Preprocessing-Nodes
Then restart. Only numpy and scipy are required - no models, no downloads.
The honest take
This is the node you add when you've read enough of the pack's philosophy to get paranoid about spectral integrity, and then forget about. It costs almost nothing (one FFT pair, NumPy-vectorized), changes nothing perceptible on healthy images, and catches a real silent failure on mangled ones. If you're assembling the full preprocessing pipeline the README recommends, throwing it on the end before VAE Encode is a sensible, zero-risk default. If you're a beginner who just wants Flux img2img to stop sparking, skip it for now - the Spike Suppressor and Noise Floor Lifter are the ones doing the heavy lifting, and this node is the cleanup crew, not the firefighter.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| strength | FLOAT | 1.000–1 | 1.0 = fully enforce Hermitian symmetry. 0.0 = no change. For most images the difference is invisible at strength=1. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| residual | IMAGE | — |