Mask: Cleanup
Fix the Mask by Pixel Count, Not by Brush Radius
- mask
- mask
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_areaare removed. 0 turns it off. - Holes smaller than
max_hole_areaare 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_areais 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_areais 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.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| mask | MASK | Mask to clean, e.g. from SAM / rembg. | |
| threshold | FLOAT | 0.500–1 | Pixels above this value count as foreground. |
| min_island_area | INT | 640–1000000 | Remove foreground blobs under this many pixels. 0 = off. |
| max_hole_area | INT | 640–1000000 | Fill enclosed holes smaller than this many pixels (background touching the image border is never filled). 0 = off. |
| feather_px | FLOAT | 0.00–256 | Soft edge distance in pixels, measured from the cleaned edge. 0 = hard black/white mask. |
| connectivity | COMBO | 8 | Foreground connectivity: 8 = diagonal pixels are connected, 4 = only side neighbours. Holes use the complement (8 -> 4). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |