Hole Counter (Robust)
How many holes are in this part? Now with annotations so you can argue with the answer
- image
- output_image
- hole_count
- hole_info
The "Robust" in the display name is doing real work. Hole Counter counts the internal holes in a black-and-white cutting diagram - and it's built to survive the three things that wreck naive hole counters: broken outlines, specks of noise, and scans with uneven lighting. If you're quoting a job, verifying a CAD export against a drawing, or just need to know "is it 48 holes or 49?", this is the node, and it annotates the image so you can see exactly which blobs it counted.
How it works
It shares the pack's laser-path detection machinery with LaserPathTracer: binarize at binary_threshold (default 128), run morphological closing with morph_close_size (default 5) to bridge gaps in hole outlines, dilate with morph_dilate_size (0 = off) to thicken faint strokes, filter by area (min_hole_area default 50 px²; max_hole_area_pct default 0.4 of the external boundary), and merge near-duplicates within dedup_radius (default 15 px). invert_image flips polarity, and use_adaptive_threshold (with adaptive_block_size and adaptive_c) rescues noisy or uneven scans where a fixed threshold fails.
Two outputs-related toggles: annotate_image (default on) draws numbered markers on the detected holes, and show_debug_binary (default off) swaps the output to the raw binarized/debug view instead - invaluable when you're trying to figure out why it sees what it sees.
What comes out
output_image(IMAGE) - the annotated drawing, holes numbered.hole_count(INT) - the headline number.hole_info(STRING) - a text list:Holes detected: Nfollowed by each hole's rank and(x, y)center coordinates, sorted by position. Wire it to Show Text for a printable count sheet.
Where it fits
The workflow is Load Image → Extract Black → HoleCounter → Show Text. It composes with LaserPathTracer (same detection core, so the features it counts are the features the tracer will route) and ContourGapCloser (run it first if outlines are broken, since closing heals them before detection). For a QC pass on a drawing, this is the node that turns "I think it's forty-something" into "48, and here are their coordinates."
Install
Part of ComfyUI-HappNodeSet (mikemojen). ComfyUI 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. OpenCV + numpy; pack-standard deps only.
Common issues
The classic failure is double-counting: a single hole with a broken outline gets detected as two arc-shaped features before closing heals it - so if the count looks high, raise morph_close_size or dedup_radius. Missing holes usually mean min_hole_area is too high or the outline never closed. And max_hole_area_pct is your guard against counting the outer boundary or large shapes as "holes" - if the count includes one giant thing, the boundary leaked through and this setting needs to come down. Read the annotation output, not just the integer; the annotations exist to be checked.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| binary_threshold | INT | 1280–255 | — |
| min_hole_area | INT | 501–50000 | — |
| max_hole_area_pct | FLOAT | 0.400.01–0.99 | — |
| morph_close_size | INT | 50–31 | — |
| morph_dilate_size | INT | 00–15 | — |
| dedup_radius | INT | 150–100 | — |
| invert_image | BOOLEAN | false | — |
| use_adaptive_threshold | BOOLEAN | false | — |
| adaptive_block_size | INT | 513–201 | — |
| adaptive_c | INT | 10-30–60 | — |
| annotate_image | BOOLEAN | true | — |
| show_debug_binary | BOOLEAN | false | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| output_image | IMAGE | — |
| hole_count | INT | — |
| hole_info | STRING | — |