Nodes/ComfyUI-BarCensor/Mask Merge (Combine Overlaps)
ComfyUI Node

Mask Merge (Combine Overlaps)

Glue Eight Detector Masks Into One Before You Censor Anything

By InfinityTechnology·Created a day ago·Updated a day ago· 0
Mask Merge (Combine Overlaps)
  • mask_a
  • mask_b
  • mask_c
  • mask_d
  • mask_e
  • mask_f
  • mask_g
  • mask_h
  • mask
  • info
◄operation▾►
◄batch_mode▾►
◄threshold0.00►

The problem it solves

Multiple detectors, one final mask. In ComfyUI that's the normal state of things - a face YOLO, a hand YOLO, a person segmentation model, a promptable SAM mask for the one weird object nobody trained a detector for. Each emits its own MASK, and every downstream consumer wants exactly one.

The obvious wrong answer is to chain the consumers: censor pass A, then censor pass B on top of it. That double-processes every pixel the two masks share, and with a censor node it also looks wrong, because the second pass re-estimates its region from an image that already has bars in it. Mask Merge is the boring fix - collapse the masks first, censor once. It's plumbing, in the good sense: it doesn't touch pixels, so the behavior is fixed by the node's arithmetic rather than by whatever model shipped this month.

It's usable outside censorship, too: two masks into one is the shape of half the mask workflows in ComfyUI - face plus hand for a single inpaint pass, subject plus shadow. The output is a plain MASK, so anything that takes one will take it.

Same disclaimer as its sibling node: this pack has near-zero discussion footprint, so treat a surprising result as your job to diagnose.

What "merge" actually computes

Each input is normalized to float32 [batch, height, width] in 0..1, with any stray channel dimension squeezed off. Then, in flatten mode, every frame of every input is stacked into one tall batch and reduced to a single frame:

  • union → max(). A pixel is masked if any input masks it. This is what you want 95% of the time.
  • intersection → min(). Only pixels masked by every input survive.
  • add → summed and clamped to 1.0. Overlaps get denser; coverage doesn't change.
  • average → mean. A soft blend, good for combining confidence-ish masks before you threshold.

Then threshold binarizes: any value >= threshold becomes 1.0, the rest 0.0. At its default of 0 it is off, so soft edges pass straight through.

batch_mode is the setting that trips people. flatten (default) throws away frame identity - three frames in, one frame out. elementwise keeps frame i with frame i, which is the video case; shorter inputs are padded by repeating their last frame, so a single mask broadcasts against a whole clip.

Inputs are also resampled to the largest incoming width/height with bilinear interpolation. Convenient, and slightly lossy: feed a 512px mask into a 1024px merge and you get a soft-edged 1024px mask out.

Inputs and outputs

mask_a is the one required input; mask_b through mask_h are seven optional slots. More than eight and you cascade: feed one Mask Merge's mask output into the mask_a of a second. Set operation and batch_mode first; they do most of the work.

Two outputs. mask is the merged mask - wire it into Bar Censor, into a mask composite, into Set Latent Noise Mask, anywhere a MASK goes. info is a string like:

merged 3 mask inputs [union / flatten] | frames [1, 1, 4] -> 1 | 1024x1024 | coverage 8.31%

That coverage number is the useful part. It's the sanity check between "my detectors found the subject" and "my detectors found half the frame" before anything downstream eats it.

Install

ComfyUI Manager: search ComfyUI-BarCensor. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/InfinityTechnology/ComfyUI-BarCensor.git

Restart. Nothing to download - no models, no requirements.txt, and the only declared dependency is numpy, which ComfyUI already has. Both nodes from this pack appear under 🧪AILab/▬BarCensor in the menu, which is inherited from the upstream mosaic pack's naming, so it's not somewhere you'd look by instinct.

Traps worth knowing

flatten plus intersection on video is a trap. In flatten mode, intersection takes the minimum across every frame of every input at once - so it keeps only pixels masked in all frames simultaneously. Moving subjects have none, and you get an empty matte. If you want a video mask, elementwise.

flatten outputs one frame. That's by design, but it means the consumer sees a single-frame mask. Bar Censor indexes its mask per image frame, so feeding a flattened mask alongside a 30-frame image batch produces a frame-count mismatch - and because that node catches its own exceptions, you get a silent passthrough instead of an error. For video, keep elementwise all the way through.

add is not union. It clamps at 1.0, so it never increases coverage - it only saturates overlaps. Without a threshold afterwards, the two look identical and you'll wonder why the setting exists.

Threshold 0 means off, not "threshold at zero". The code skips the binarize step unless the value is greater than zero, so soft masks stay soft. If you want a hard matte, 0.5 is the obvious starting point.

Failures are silent. The whole merge is wrapped in a try/except that passes mask_a through unchanged with an info string of failed, passthrough. Any time the output looks suspiciously like your first input, read info before you go hunting in the detector.

Category🧪AILab/▬BarCensor

Inputs (11)

NameTypeDefaultDescription
mask_aMASK—
operationCOMBO4 options: union, intersection, add, average
batch_modeCOMBO2 options: flatten, elementwise
mask_boptMASK—
mask_coptMASK—
mask_doptMASK—
mask_eoptMASK—
mask_foptMASK—
mask_goptMASK—
mask_hoptMASK—
thresholdoptFLOAT0.000–1—

Outputs (2)

NameTypeDescription
maskMASK—
infoSTRING—