Phase Congruency Edge
Phase Congruency Edge
- image
- phase_congruency_edge
You've got a batch of images where Canny fires perfectly on half of them and goes blind on the rest, because one was shot in shadow and another blew out its highlights. That's the exact problem this node exists for. Phase Congruency Edge is an edge detector that keys on phase, not brightness, so a gradient or a shadow doesn't move the edge - only the actual edge does.
It's a direct port of Peter Kovesi's 1999 phase congruency algorithm, which has quietly lived in computer vision papers for decades without ever getting a friendly UI. This node is that friendly UI. The trade: it's slower and needs a few knobs understood, but when you need edges that survive lighting changes, Canny doesn't even compete.
What it does under the hood
Classic detectors (Sobel, Canny, Laplacian) look at intensity gradients - which means their output changes when the light does. Phase congruency flips the question. It looks at the image in the frequency domain (via FFT) and asks where the Fourier components of different scales line up in phase with each other. At a real edge, all the wave components hit their peak together. In a flat region or noise, they don't. So the detector finds locations where components are "maximally in phase" - regardless of their amplitude. That's what makes it illumination- and contrast-invariant.
The node builds log-Gabor filters at nscale scales and norient orientations, applies them in the frequency domain, sums the complex responses into an energy vector per orientation, estimates a noise threshold from Rayleigh statistics, and computes max(energy − T, 0) / (sum of amplitudes). The final map is the mean across orientations, normalized to 0–1. White = strong edge. It feeds a grayscale (actually RGB-replicated) phase_congruency_edge IMAGE out.
The inputs that matter
There are eight inputs and you'll touch maybe three. The rest are defaults that work:
k- noise threshold in standard deviations. This is your main dial. Default 2.0. Lower it toward 1.0 when edges are getting missed (risk: speckle noise). Raise it to 4–6 when the output looks dirty.min_wavelength- fineness of the finest scale filter, in pixels. Lower (2–3) for fine detail, higher (8–20) for coarse, broad edges. Default 3.norient- orientations to check. Default 6 catches all directions; crank to 8 if diagonal edges look weak.
Leave nscale (4), mult (2.1), sigma_on_f (0.55), and cutoff (0.5) alone until you've got a feel for the first three.
Where it fits
In ComfyUI terms, this is a ControlNet preprocessor that ships outside the usual preprocessor pack - a hand-rolled replacement for Canny or lineart when those frustrate you. Wire any IMAGE into it, take the output into a ControlNet Apply (or straight to a save node), and you have a lighting-robust line map for architecture, documents, OCR prep, or stylized edge rendering. It's CPU-only numpy FFT, so on large images expect it to be much slower than Canny - fine for a single image, painful if you feed it a whole video frame batch.
Installing it
ComfyUI Manager is easiest: search ComfyUI_PhaseCongruencyEdge. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/bemoregt/ComfyUI_PhaseCongruencyEdge
Restart ComfyUI and it appears under image/filters → Phase Congruency Edge. No model downloads, no GPU deps - just numpy, pillow, and torch, which ComfyUI already ships. One gotcha: the README's own clone command points at a stale repo name that doesn't exist anymore; the URL above is the live one. That kind of rot is why you check the code, not just the docs.
If output comes out noisy, raise k first - that's the single most common fix. If edges look too heavy or too faint, that's min_wavelength. This is a niche node from a small repo, so don't expect community threads to save you - the math is the spec, and it's the only edge detector you'll meet that genuinely doesn't care what time of day the photo was taken.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| nscale | INT | 42–8 | Number of wavelet scales |
| norient | INT | 62–8 | Number of filter orientations |
| min_wavelength | INT | 32–20 | Minimum wavelength of log-Gabor filter |
| mult | FLOAT | 2.11.5–4 | Scaling factor between successive filter scales |
| sigma_on_f | FLOAT | 0.550.1–1 | Bandwidth of log-Gabor filter |
| k | FLOAT | 2.00.5–10 | Noise threshold (std devs above mean) |
| cutoff | FLOAT | 0.500.1–0.9 | Fractional measure of frequency spread below which phase congruency values get penalized |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| phase_congruency_edge | IMAGE | — |