Mask to Mask Batch
One painted mask in, one mask per blob out
- mask
- masks
You painted three separate faces into one mask because that's how MaskEditor works. Now you want to fix each one on its own, with its own seed, and you can't, because a single mask is a single job. Mask to Mask Batch is the splitter: it finds every disconnected region in your mask and hands you one full-canvas mask per region as a normal MASK batch.
How it works
OpenCV contour detection, one pass per plane. For each plane it runs cv2.findContours with the tree-retrieval mode, then skips contours that have a parent - those are holes, so if you painted a donut the middle doesn't become its own region. Each surviving contour gets its own blank canvas, and what lands in that canvas depends on two switches:
- Default (both off): the original mask's values masked by that contour -
mask * separated. So a feathered, gradient-y region keeps its soft values and only its own pixels. This is the setting you want most of the time. contour_fillon: the contour is drawn solid, giving you a hard-edged region regardless of the source mask's falloff.bbox_fillon: the output for that region also becomes a filled rectangle over the contour's bounding box. Useful when you know the region and want a guaranteed clean rectangle; risky when the blob is a diagonal streak, because you're now also masking its corners.drop_size(default 10) discards any contour whose bounding box is at most that many pixels in width or height. Note the inclusive comparison - a blob exactly 10px wide is dropped at the default. Turn it down to 1 to keep everything that survived detection.
One pass quirk worth knowing: results are quantized to 8-bit before stacking, so soft values come back in 1/255 steps. Irrelevant for hard masks; visible if you were relying on a smooth ramp.
Inputs and output
mask in, masks out - a full-canvas MASK batch, one plane per region, each the same size as the source canvas. Because each output plane is canvas-sized, it drops into anything expecting a mask for the original image without further geometry. If nothing survives (empty mask, or everything below drop_size), you get one blank plane rather than an empty batch.
Where this earns its place
The whole detection-and-detailing stack is built on treating regions as independent jobs, and this is the manual version of that: paint a mask by hand, split it, then run a per-region pass with a different seed per region. That last part matters more than it sounds. The classic detailing failure - and it's a documented one - is a crowd of separate people converging into a crowd of the same person, because each region re-renders on the same base seed. Splitting the mask is what lets you vary it.
The other use is bookkeeping: split first, then send each plane off to get its own crop, prompt, denoise, or upsample. Masks become items you can count, order, and select from - which is what the rest of the pack's mask nodes are for.
Install
Manager: search ComfyUI Utility Suite (publisher tom-m), install, restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/tom-m-2020/ComfyUI-Utility-Suite
No models. This one does need OpenCV - the pack's requirements.txt is a single line, opencv-python-headless, and if you installed by hand or Manager skipped the pip step, a ModuleNotFoundError: cv2 here means install that into ComfyUI's Python. The pack is written against ComfyUI's V3 node API, so an outdated install will load the folder and show no nodes at all.
Troubleshooting
A region I painted is missing from the output. Check drop_size first - anything at or below 10px wide or tall is discarded by default. Second suspect: the region is enclosed by another one, so OpenCV sees it as a hole and this node deliberately ignores holes.
The split came out as one plane. Your "three separate" regions are actually connected, usually by a thin anti-aliased bridge the brush left behind. The mask edits that look separate on screen are frequently one component to findContours.
The plane count doesn't match the visual blob count. Very common with noisy or dithered masks: speckle becomes its own contour. drop_size is the filter for exactly this.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| mask | MASK | — | |
| bbox_fill | BOOLEAN | false | — |
| drop_size | INT | 101–16384 | — |
| contour_fill | BOOLEAN | false | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| masks | MASK | — |