Nodes/BrainDead Nodes/BD Fill Mask Holes
ComfyUI Node

BD Fill Mask Holes

When SAM3 nails the outline but forgets the inside

By BizaNator·Created 8 months ago·Updated 3 days ago· 15
BD Fill Mask Holes
  • mask
  • filled_mask
  • status
threshold0.50
closing_radius4
smooth_edges0

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:

  1. Binarize at threshold (default 0.5) - soft mask edges become a hard on/off boundary.
  2. 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.
  3. Fill with scipy.ndimage.binary_fill_holes - the actual flood-fill of any pixel region fully enclosed by the boundary.
  4. 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_radius toward 8–16.
  • Edges look jagged - raise smooth_edges to 2–4.
  • The mask changed shape unexpectedly - lowering closing_radius back 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).
Category🧠BrainDead/Segmentation

Inputs (4)

NameTypeDefaultDescription
maskMASK
thresholdoptFLOAT0.500–1Binarize the input mask at this value before filling. 0.5 = standard binary threshold.
closing_radiusoptINT40–64Morphological 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_edgesoptINT00–16Gaussian blur radius applied to the filled mask for soft edges. 0 = hard binary output. 2–4 = slight feathering at boundaries.

Outputs (2)

NameTypeDescription
filled_maskMASK
statusSTRING