Nodes/ComfyUI_zura_nodes/Zura · Fill Small Mask Holes
ComfyUI Node

Zura · Fill Small Mask Holes

The five-second fix for a mask full of pinholes

By ZURAVFX·Created about a month ago·Updated a day ago· 2
Zura · Fill Small Mask Holes
  • masks
  • cleaned_mask
◄max_hole_area128►

Segmentation masks come back with holes. A face mask cut by SAM will have stray background pixels punched through it - a nostril, a gap between glasses and cheek, a couple of noisy pixels on a jacket. Those holes are individually invisible and collectively ruinous, because in a masked video edit every hole is a place the sampler is told "do not change this."

Why you'd reach for it

The author's own description is refreshingly honest about the scope: "Fill tiny enclosed gaps in each mask frame. Border-connected areas and larger gaps stay untouched; inspect the mask before generation." That's the whole node. It is not a mask refiner, it doesn't close real holes, and it doesn't touch the subject's outline.

Where it earns its keep is the step right before a masked H3 or Wan render, after you've detected and tracked a performer. You look at the mask preview and it's 98% solid with a sprinkle of one-pixel gaps. This node cleans that up in one pass and you move on.

How it works

Per frame, it thresholds the mask at 0.5 and treats everything below that as background. It then runs cv2.connectedComponentsWithStats with four-way connectivity - four-way, not eight, so diagonal gaps count as separate regions - and collects every background component whose pixel area is at or under max_hole_area.

The second half of the trick is the border check. Any component that touches the edge of the image is discarded, because "background that reaches the frame edge" is the actual background, not a hole. Only fully enclosed components get filled. That's what makes this safe to run blind: it can't bleed your mask out to the frame boundary.

It runs frame by frame, transferring one frame to CPU at a time, so it carries no temporal memory. That's a feature for a per-frame mask - and a limitation if your holes are actually tracking failures that span 30 frames, which this will never fill.

Inputs and outputs

  • masks - any MASK, but it wants a properly shaped [frames, height, width] float batch, not a single 2D mask.
  • max_hole_area - default 128, range 0–256. The tooltip is the whole manual: "Fill enclosed gaps up to this many pixels. Set to 0 to pass the mask through." Start at 128. Push it toward 256 if you're on a high-res raster where a "tiny" gap is more pixels than it used to be.
  • cleaned_mask - same shape, same dtype, holes filled. Feeds your sampler's mask input or the mask-check preview.

Install

One pack, one install. In ComfyUI Manager search Zura Nodes, or:

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

Restart. No model weights are involved in this node at all. It does need OpenCV at runtime, which you almost certainly have already - the pack's ultralytics dependency pulls it in.

Common issues

Nothing changed. Either there genuinely are no enclosed holes under the threshold, or the gaps you're looking at touch the image border. Raise max_hole_area a notch and compare.

A real hole didn't get filled. Above 256 pixels, that's out of scope by design. Use a morphological close from KJNodes (or a proper inpaint pass) for anything bigger - this node is deliberately conservative because a mouse that fills large gaps will happily swallow the gap between an arm and a torso and hand you a blob.

"Provide a floating-point MASK batch with shape [frames, height, width]." You passed a single 2D mask. Run it through a batch helper first, or check whether your upstream node dropped a dimension.

Very long clips. There are hard ceilings - 4096 frames, 16.7M pixels per frame, 134M pixels in the batch. A 5-second 24 fps clip at 1080p is nowhere near any of them, but a full-length 4K source will trip it.

CategoryZura/Artist Studio/Mask

Inputs (2)

NameTypeDefaultDescription
masksMASK—
max_hole_areaINT1280–256Fill enclosed gaps up to this many pixels. Set to 0 to pass the mask through.

Outputs (1)

NameTypeDescription
cleaned_maskMASK—