Nodes/ComfyUI-SMPL/Union SMPL Part Masks
ComfyUI Node

Union SMPL Part Masks

You Already Know Where the Hands Are — So Just Mask Them

By corentineuvrard·Created 4 days ago·Updated about 24 hours ago· 0
Union SMPL Part Masks
  • part_masks
  • mask
◄presetnone►
◄hipsfalse►
◄leftUpLegfalse►
◄rightUpLegfalse►
◄spinefalse►
◄leftLegfalse►
◄rightLegfalse►
◄spine1false►
◄leftFootfalse►
◄rightFootfalse►
◄spine2false►
◄leftToeBasefalse►
◄rightToeBasefalse►
◄neckfalse►
◄leftShoulderfalse►
◄rightShoulderfalse►
◄headfalse►
◄leftArmfalse►
◄rightArmfalse►
◄leftForeArmfalse►
◄rightForeArmfalse►
◄leftHandfalse►
◄rightHandfalse►
◄leftHandIndex1false►
◄rightHandIndex1false►
◄grow_px0►

Every hands-and-faces cleanup pass in ComfyUI starts with a detector. Render the image, run a face/hand detector, inpaint whatever it found. That works right up until the detector misses a hand, or finds one you didn't want, or finds one twice.

If your subject came out of Generate SMPL Avatar you can skip detection entirely. You built the body, so you know exactly which pixels are the hands. Union SMPL Part Masks turns those 24 per-part masks into the single MASK you actually wanted, with a couple of pixels of dilation on top.

What it does

Two ways to select, and they're additive:

  • preset - thirteen canned groups: none, head, head and neck, torso, arms, hands, arms and hands, legs, feet, legs and feet, upper body, lower body, full body.
  • The 24 toggles - one boolean per SMPL joint (hips, spine, spine1, spine2, neck, head, leftShoulder, leftArm, leftForeArm, leftHand, leftHandIndex1, the right-side mirror, and legs). Each carries the author's plain-English gloss as its tooltip: spine2 is "chest and upper back", leftShoulder is "left collarbone / trapezius area", leftHandIndex1 is "fingers".

Then grow_px dilates the result with a positive number and erodes it with a negative one, up to 64 pixels. One output: mask.

Additive is the trap worth internalising: the selection is the union of preset and toggles, not an intersection and not "toggles override the preset". Leave preset on torso, switch on head, get torso plus head. Driving the toggles by hand? Set the preset back to none first.

How it works

part_masks arrives as one tensor of shape [24·N, H, W] - for each of N avatars, 24 masks in canonical SMPL joint order, hips at 0, rightHandIndex1 at 23. The node maps your preset to part names, adds any enabled toggles, converts the names to indices and does a max over those channels. Max rather than sum because the input masks are already disjoint: the pack multiplies each part label map by the silhouette, so no pixel belongs to two parts. Selecting nothing returns zeros rather than an error - deliberate, and occasionally confusing.

Batching is the quietly useful bit: one avatar gives one mask, two avatars give two. A workflow rendering a male and a female avatar in one node still lines up downstream, mask for image.

grow_px is a square-kernel max_pool2d, so corners grow diagonally - fine at 8 px, blocky at 40. The erosion path pads with zeros explicitly, because max_pool2d pads with negative infinity and negating it would count everything outside the image as inside the mask and glue it to the frame edge. Someone hit that.

Where it goes

Wire the output into anything that takes a mask: InpaintModelConditioning for a proper inpaint, VAEEncode with a mask, ImageCompositeMasked, Set Latent Noise Mask. Keep a Preview Mask on it while you tune - you can't judge a selection from the finished image.

What the parts unlock, in rough order of trouble saved:

  • hands with grow_px around 6–8, then a hand-fix inpaint. No detector, no double-detection, and the mask is identical every run because the body is deterministic.
  • head or head and neck for a higher-resolution face pass - same idea as a face-only second pass, except you never had to find the face.
  • torso for a clothing swap, legs and feet for shoes, arms for sleeves.
  • full body as a person-shaped silhouette, handy for compositing and for outpainting everything that isn't the subject.

SMPL and SMPL-H share mesh topology, so one segmentation serves both - but pick model_type = SMPL-H on the generator if hands are the point, since that model has articulated hands.

Installing it

Same pack, same install, because this node doesn't exist on its own:

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

Manager search "ComfyUI-SMPL" works too. Then the body models: download from smpl.is.tue.mpg.de and mano.is.tue.mpg.de (registration, non-commercial research licence) and run python scripts/prepare_models.py --source <downloads>. Both nodes appear under SMPL Avatar.

Troubleshooting

The mask is empty and the node warned about it. The node logs when every input part mask is empty. Two causes, both upstream on the generator: compute_part_masks was off (then part_masks is all zeros by design), or the body fell outside the view under framing = fixed. This node can only union what it's given.

"expected part masks of shape (24*N, H, W)". You wired something that isn't part_masks from Generate SMPL Avatar - a plain silhouette, say. The channel count is how the node knows which parts it's looking at.

Seams when you select one part. Parts are disjoint by construction: a vertex on the neck/head boundary is listed in both, but each face gets one label. So a single-part mask stops dead at the seam. Need a continuous region? Union the neighbours - that's what head and neck and arms and hands are for - or add a pixel or two of grow_px.

The licence. The pack's code is MIT; smplx and the SMPL model files are Max Planck non-commercial research licences, with commercial licensing through Meshcapade. Worth knowing before you build a paid pipeline on these masks.

CategorySMPL Avatar

Inputs (27)

NameTypeDefaultDescription
part_masksMASKThe 'part_masks' output of 'Generate SMPL Avatar'.
presetCOMBOnoneGroup of parts to include.
hipsBOOLEANfalsepelvis
leftUpLegBOOLEANfalseleft thigh
rightUpLegBOOLEANfalseright thigh
spineBOOLEANfalselower torso (belly / lower back)
leftLegBOOLEANfalseleft shin and calf
rightLegBOOLEANfalseright shin and calf
spine1BOOLEANfalsemid torso
leftFootBOOLEANfalseleft foot
rightFootBOOLEANfalseright foot
spine2BOOLEANfalsechest and upper back
leftToeBaseBOOLEANfalseleft toes
rightToeBaseBOOLEANfalseright toes
neckBOOLEANfalseneck
leftShoulderBOOLEANfalseleft collarbone / trapezius area
rightShoulderBOOLEANfalseright collarbone / trapezius area
headBOOLEANfalsehead
leftArmBOOLEANfalseleft upper arm
rightArmBOOLEANfalseright upper arm
leftForeArmBOOLEANfalseleft forearm
rightForeArmBOOLEANfalseright forearm
leftHandBOOLEANfalseleft hand (palm)
rightHandBOOLEANfalseright hand (palm)
leftHandIndex1BOOLEANfalseleft fingers
rightHandIndex1BOOLEANfalseright fingers
grow_pxoptINT0-64–64Dilate (positive) or erode (negative) the result by this many pixels.

Outputs (1)

NameTypeDescription
maskMASKUnion of the selected parts (1 = selected).