ComfyUI Node

Mask: Cleanup

Fix the Mask by Pixel Count, Not by Brush Radius

By jetthuangai·Created 6 months ago·Updated a day ago· 6
Mask: Cleanup
  • mask
  • mask
◄threshold0.50►
◄min_island_area64►
◄max_hole_area64►
◄feather_px0.0►
◄connectivity8►

Every segmentation node leaves the same two messes. Floating islands of "subject" out in the background, and pin-holes inside the subject where the model got confused. The usual fix is Mask Morphology - grow, shrink, open, close - and it's the wrong tool, because morphology works by radius and radius doesn't know what you meant. Shrink by 4 px to kill a speck and you've also eaten 4 px off every thin strap and fingertip.

Mask: Cleanup (JHPixelProMaskCleanup) works by connected component size in pixels instead, which is far more predictable. A blob is either big enough to be your subject or it isn't.

What it does

The mask is binarised at threshold (default 0.5, pixels above count as foreground). Components are labelled with cv2.connectedComponentsWithStats. Then:

  • Islands smaller than min_island_area are removed. 0 turns it off.
  • Holes smaller than max_hole_area are filled. 0 turns it off. A hole touching the image border is never filled, because that's background, not a hole.
  • Feather is a signed distance transform, not a blur: alpha = clip(0.5 + sd / (2 × feather_px), 0, 1).

That feather is the clever bit. It's an even distance from the cleaned edge, and the 0.5 line sits exactly on the original outline - so a feathered mask still covers the same area. A Gaussian blur feather shrinks thin parts of the subject and pulls the half-way line inward. If you've ever feathered a mask and watched a necklace disappear, that's why.

connectivity (8 / 4) sets foreground connectivity: 8 means diagonal pixels are connected, 4 means only side neighbours. Holes use the complement (8 → 4), which is the standard pairing that stops a hole from leaking diagonally out through a one-pixel-wide diagonal gap.

Inputs and outputs

mask - anything, typically straight from SAM, rembg or a segmenter. threshold (0–1, default 0.5). min_island_area (default 64 px), max_hole_area (default 64 px) - both up to 1,000,000. feather_px (0–256, default 0 = hard black/white). connectivity (default 8).

One output: mask.

Use it first, not last

This goes at the head of a mask chain: clean up the raw segmentation, then hand it to Edge-Aware Mask Refiner or Sub-Pixel Mask Refiner to snap or soften the edge against the photo. Snapping a speck-riddled mask to real image edges just gives you speck-shaped edge detail.

The two failure modes are both threshold mistakes, and both are easy to diagnose:

  • A real part of the subject vanished (a small earring, a button) - min_island_area is too high. It's a pixel count, so a small object at low resolution is fewer pixels than you'd guess.
  • A real hole disappeared (the gap between an arm and the body) - max_hole_area is too high.

Set both to 0 for a first run to see the raw mask, then raise them until the junk goes and the real details stay.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/ComfyUI-JH-PixelPro.git
cd ComfyUI-JH-PixelPro
pip install -r requirements.txt   # the Python that runs ComfyUI

ComfyUI Manager: search ComfyUI-JH-PixelPro → Install → restart → Add Node → ComfyUI-JH-PixelPro → mask. Deps: kornia>=0.7, mediapipe>=0.10.33, opencv-python-headless>=4.8, scipy>=1.10; PyTorch from ComfyUI. No model downloads. It runs on the CPU, one batch item at a time - which for a mask cleanup is the right call, since the work is a labelling pass, not a convolution.

One practical pairing: this is the node that makes Decontaminate Colors useful. Decontamination needs a soft matte to un-mix; this node gives you a clean, evenly feathered one to feed it. Clean, then decontaminate, then composite with the same mask.

Pack: Apache-2.0, maintained by JH (@jetthuangai), v1.10.0 with 64 nodes and EN/VI docs. Essentially no Reddit presence - the pack name returns one unrelated browser extension in the corpus - but for a component-labelling utility, honestly, nobody needs a tutorial. Read the two tooltips about island and hole area, run it once with both at 0, and you've learned the node.

CategoryComfyUI-JH-PixelPro/mask

Inputs (6)

NameTypeDefaultDescription
maskMASKMask to clean, e.g. from SAM / rembg.
thresholdFLOAT0.500–1Pixels above this value count as foreground.
min_island_areaINT640–1000000Remove foreground blobs under this many pixels. 0 = off.
max_hole_areaINT640–1000000Fill enclosed holes smaller than this many pixels (background touching the image border is never filled). 0 = off.
feather_pxFLOAT0.00–256Soft edge distance in pixels, measured from the cleaned edge. 0 = hard black/white mask.
connectivityCOMBO8Foreground connectivity: 8 = diagonal pixels are connected, 4 = only side neighbours. Holes use the complement (8 -> 4).

Outputs (1)

NameTypeDescription
maskMASK—