Nodes/ComfyUI_PhaseCongruencyCorner/Phase Congruency Corner
ComfyUI Node

Phase Congruency Corner

The corner detector that doesn't care about lighting

By bemoregt·Created 7 months ago·Updated 7 months ago· 1
Phase Congruency Corner
  • image
  • phase_congruency_corner
nscale4
norient6
min_wavelength3
mult2.1
sigma_on_f0.55
k2.0
cutoff0.50

This isn't a generation node, and it makes no apology for that. Phase Congruency Corner takes any image and hands back a grayscale map of where the corners are - not edges, corners - using a detector that's deliberately immune to lighting and contrast. If you've never heard of phase congruency, you're in the majority; it's a computer-vision classic (Kovesi, 1999) that mostly lives in academic code. This is one of the few places it lives inside ComfyUI.

Why you'd reach for it

Every corner detector you already know - Harris, Shi-Tomasi, FAST - works off intensity gradients, so its output shifts with brightness and local contrast. Photograph the same scene under two different lights and you get two different corner maps. Phase congruency doesn't play that game: it measures the coherence of Fourier phase components, which has nothing to do with how bright things are. A corner is where frequency components line up in phase across every orientation at once. Edges are phase-coherent in one direction; corners are phase-coherent in all of them. That's the whole trick, and it's a genuinely different approach from the gradient crowd.

Honest framing: there's no canonical ComfyUI workflow that needs this, and the pack sits at roughly zero community attention. You reach for it if you're doing real image analysis on your renders, or if you want an edge/corner-style conditioning map - say, as a custom ControlNet preprocessor - that stays stable when lighting changes. It's also just interesting to run on a render and check whether the geometry actually has sharp, plausible corners.

How it works

The node builds log-Gabor filters at nscale scales and norient orientations, applies them in the frequency domain via FFT, and computes a phase congruency score per orientation (with a Rayleigh-based noise threshold controlled by k). Then it accumulates those per-orientation scores into a 2×2 structure matrix at every pixel - the same trick Harris uses, but built from phase congruency instead of gradients - and takes the minimum eigenvalue. A high value in every direction at once is the geometric signature of a corner.

The output, phase_congruency_corner, is a grayscale IMAGE normalized to 0–1, so white blobs are strong corners. Preview it, threshold it, save it, or wire it into whatever's next in your graph.

The inputs that matter

image is the only thing you must plug in; everything else has a sane default. The few worth touching:

  • nscale (default 4) - number of log-Gabor filter scales. More = richer multi-scale info and slower.
  • min_wavelength (default 3 px) - smaller finds finer, more localized corners; larger finds broad junctions.
  • k (default 2.0) - noise threshold in standard deviations. Lower (1.0) is more sensitive and noisier; 4–6 gives cleaner output with fewer false corners.
  • norient (default 6) - filter orientations; push to 8 for better response at all angles.

Installing it

Easiest is ComfyUI Manager - search "ComfyUI_PhaseCongruencyCorner". Or:

cd ComfyUI/custom_nodes
git clone https://github.com/bemoregt/ComfyUI_PhaseCongruencyCorner

Restart ComfyUI and it shows up under image/filters → Phase Congruency Corner. Dependencies are just numpy and torch - both already in any working ComfyUI. No model files, nothing to download, which is refreshing in this ecosystem.

Gotchas worth knowing

Two things I checked in the code because the README didn't tell me. First, cutoff is accepted but not actually used - the internal Butterworth low-pass is hardcoded to 0.45, so twiddling that slider changes nothing on the current release. Second, keep sigma_on_f below 1.0: the filter bandwidth divides by log(σ/f₀), and at exactly 1.0 that's log(1) = 0, which means divide-by-zero NaNs. The default 0.55 is safe.

Also be aware it runs in numpy on CPU, converting to grayscale first and doing nscale × norient FFTs per image in a loop over the batch. One image or a small preview is fine; a batch of big renders will chug. And a quick README trap: the README's own install command says ComfyUI_PCPoint.git, which is a typo for this repo - use the clone above, not the README's. Since custom nodes run arbitrary code on load and this one is tiny and single-authored, it's worth a skim of __init__.py before you install if you're the cautious type. It's a short file, one node, pure math.

Categoryimage/filters

Inputs (8)

NameTypeDefaultDescription
imageIMAGE
nscaleINT42–8Number of log-Gabor filter scales
norientINT62–8Number of filter orientations
min_wavelengthINT32–20Minimum wavelength of log-Gabor filter (pixels)
multFLOAT2.11.5–4Scaling factor between successive filter scales
sigma_on_fFLOAT0.550.1–1Bandwidth of log-Gabor filter (σ/f₀)
kFLOAT2.00.5–10Noise threshold in std devs above mean (Rayleigh)
cutoffFLOAT0.500.1–0.9Butterworth low-pass filter cutoff frequency

Outputs (1)

NameTypeDescription
phase_congruency_cornerIMAGE