BD Fill Mask Holes
When SAM3 nails the outline but forgets the inside
- mask
- filled_mask
- status
Here's a classic failure you'll meet the first time you run SAM3 on a mouth: the lips and teeth perimeter comes back perfect, but the inside of the mask is a Swiss cheese of holes, because "teeth" and "tongue" got picked up as separate regions. You don't need a better segmenter - you need a fill. BD_FillMaskHoles is exactly that: it takes a binary mask and flood-fills every enclosed region inside its boundary, so an outline becomes a solid blob.
It's a small node with a boring, reliable job, and it sits in a specific spot in ComfyUI-BrainDead's segmentation flow: after BD_SAM3MultiPrompt, before BD_MaskResolver. But nothing about it is BrainDead-specific - if you have a mask from any source with holes you want gone, it works standalone too.
How it works
Three steps, each an input:
- Binarize at
threshold(default 0.5) - soft mask edges become a hard on/off boundary. - Morphological closing (optional,
closing_radius, default 4) - dilate then erode with an elliptical kernel. This is the clever bit: it bridges near-touching edges of the mask perimeter before filling, so an almost-sealed hole gets properly sealed and then filled. The tooltip's steer is right: 4–8px is the sweet spot for SAM3 lip/teeth gaps. 0 skips closing entirely. - Fill with
scipy.ndimage.binary_fill_holes- the actual flood-fill of any pixel region fully enclosed by the boundary. - Smooth (optional,
smooth_edges, default 0) - a Gaussian blur on the filled result if you want feathered edges instead of a hard binary edge. 2–4 for a gentle boundary.
The only required input is mask. Outputs are filled_mask and a status string that reports how many pixels (as a percentage) the fill added across your batch - a handy sanity check that the node actually did something.
When to reach for it
The README's canonical case: SAM3 detects a perimeter but misses interior pixels - think lips/teeth/tongue, glasses frames, or clothing where a buckle reads as a separate object. One fill node and the mask is solid.
The one thing to be careful about: binary_fill_holes fills enclosed regions only. If your mask boundary has a genuine opening to the outside, nothing gets filled - which is what closing_radius is for. Bump it when holes stubbornly refuse to fill and you'll see the difference immediately.
Install
Part of the BrainDead pack:
cd ComfyUI/custom_nodes
git clone https://github.com/BizaNator/ComfyUI-BrainDead
cd ComfyUI-BrainDead
pip install -r requirements.txt
Or search "BrainDead" in ComfyUI Manager and restart. This node needs scipy and opencv at runtime (both imported lazily), which most ComfyUI installs already have; if you hit an import error, pip install scipy opencv-python-headless fixes it.
Troubleshooting
- Holes still open after fill - the boundary isn't sealed. Raise
closing_radiustoward 8–16. - Edges look jagged - raise
smooth_edgesto 2–4. - The mask changed shape unexpectedly - lowering
closing_radiusback toward 0 keeps the fill closer to the original outline; high closing radii aggressively bridge gaps that may have been intentional (like a thin gap between teeth you wanted to keep).
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| mask | MASK | — | |
| thresholdopt | FLOAT | 0.500–1 | Binarize the input mask at this value before filling. 0.5 = standard binary threshold. |
| closing_radiusopt | INT | 40–64 | Morphological closing radius (pixels) applied BEFORE fill_holes. Closing = dilate then erode — it bridges small gaps in the mask perimeter so that enclosed regions are properly sealed before filling. 0 = skip closing (only fill_holes). 4–8 = good for SAM3 lip/teeth gaps. |
| smooth_edgesopt | INT | 00–16 | Gaussian blur radius applied to the filled mask for soft edges. 0 = hard binary output. 2–4 = slight feathering at boundaries. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| filled_mask | MASK | — |
| status | STRING | — |