Nodes/ComfyUI-HappNodeSet/Contour Gap Closer
ComfyUI Node

Contour Gap Closer

Reconnecting broken contours by their endpoints — with a direction check so it doesn't bridge the wrong things

By mikemojen·Created 8 months ago·Updated 3 months ago· 0
Contour Gap Closer
  • image
  • image
  • contour_mask
max_gap20
threshold0.50
inverttrue
morph_close_radius2
re_skeletonizefalse
direction_weight0.30
iterations3
output_mode
dilation_radius1

Broken contours are the reason a laser cutter or engraver cuts a shape twice, or not at all: the path just isn't closed. Contour Gap Closer detects those breaks - the open endpoints in a skeletonized drawing - and draws connecting lines between nearby ones to produce closed, continuous paths. It's the "repair" node in this pack's contour toolset, and it pairs with Contour Endpoint Visualizer (which shows you the endpoints first) and LaserPathTracer (which needs clean closed features to trace).

How it works

The algorithm is refreshingly mechanical. Binarize the image at threshold (0–1, default 0.5) with invert handling polarity (default on = dark lines on light). Skeletonize down to single-pixel lines. Then find endpoints - pixels with exactly one neighbor in the skeleton. For each endpoint, find the nearest other endpoint within max_gap pixels (default 20) and draw a straight line between them. Repeat for iterations (default 3) passes so multi-gap chains get closed progressively.

The part that makes it not-dumb: direction_weight (0–1, default 0.3). Before connecting two endpoints, the node estimates each line's tangent direction by walking a few pixels back along the skeleton. When it scores candidate pairs, it biases toward pairs whose directions are roughly collinear - i.e. it prefers bridging a gap in the same line over fusing the end of one line to the middle of an unrelated one. At 0 it's pure nearest-neighbor (fast, sloppy); at 1.0 it's strict collinearity (won't bridge a sharp corner). The default is a reasonable middle ground; raise it if your drawings have dense, nearby, unrelated lines.

Before the endpoint search, morph_close_radius (default 2) optionally applies a morphological closing to heal micro-gaps, so tiny breaks don't even reach the endpoint logic.

Outputs and modes

  • image (IMAGE) - the repaired drawing, rendered per output_mode: original_thickness (keep line weight), skeleton (thin to 1px), or dilated (thicken to dilation_radius px). For feeding back into measurement nodes, skeleton is usually right.
  • contour_mask (MASK) - a binary mask of the closed contours, handy for masking downstream or compositing.

re_skeletonize (default off) runs skeletonization on the output so the repairs stay single-pixel-wide - turn it on if you're feeding a measurement node that assumes thin lines.

Where it fits

Extract Black → ContourEndpointVisualizer (check the breaks) → ContourGapCloserLaserPathTracer. The visualizer exists specifically to tell you what max_gap and direction_weight should be before you run the closer, and skipping that check is how you end up with paths that cut through unrelated geometry.

Install

Part of ComfyUI-HappNodeSet (mikemojen). Manager: search HappNodeSet. Manual:

cd ComfyUI/custom_nodes
git clone https://github.com/mikemojen/ComfyUI-HappNodeSet.git
pip install -r ComfyUI-HappNodeSet/requirements.txt

Restart ComfyUI. Dependencies here are the scipy ndimage convolution and KDTree for the nearest-endpoint search, plus skimage for skeletonization - all in the pack's standard requirements.

Common issues

If max_gap is too large, the closer starts bridging genuinely separate lines - especially with direction_weight low. If the repaired paths cut across the drawing at weird angles, raise direction_weight. And remember this node only knows endpoints after skeletonization: thick, blobby linework gives you fewer clean endpoints and messier repairs, so thin the input first. Preview the image output before trusting any downstream trace.

Categoryimage/contour

Inputs (10)

NameTypeDefaultDescription
imageIMAGE
max_gapINT201–200Maximum pixel distance between endpoints to bridge
thresholdFLOAT0.500.01–0.99Binarization threshold (0-1)
invertBOOLEANtrueTrue = dark lines on light background
morph_close_radiusINT20–20Morphological closing radius to heal micro-gaps first (0=skip)
re_skeletonizeBOOLEANfalseSkeletonize the output to single-pixel lines
direction_weightFLOAT0.300–1Direction matching strictness (0=any, 1=strict collinear)
iterationsINT31–10Repeat endpoint-detection + connection N times
output_modeCOMBO3 options: original_thickness, skeleton, dilated
dilation_radiusINT11–10Line thickness when output_mode='dilated'

Outputs (2)

NameTypeDescription
imageIMAGE
contour_maskMASK