Superside Portrait Sections
Build a portrait mask by checking boxes — eyes, lips, hair, clothing, merged
- image
- mask
- info
- color_preview
The face-parsing problem, solved by checkboxes. When you want to retouch a portrait's skin but not touch the eyes, lips, or hair, you need a mask of everything to protect - and this node builds that mask from a list of facial sections you toggle on and off: skin, nose, eyes, eyebrows, ears, mouth, lips, hair, hat, glasses, earrings, neck, necklace, clothing. Each active section gets segmented and the results merge into one MASK output. It's the Superside in-house replacement for a local face-parsing model's per-region checklist, and it runs on fal's SAM 3 (fal-ai/sam-3/image) instead of local weights.
The canonical use case is an exclusion mask for a re-skin pass: retouch the skin, then composite the original back over everything this mask covers so eyes and hair come through untouched. The README is explicit about the trade-off vs. a dedicated local face-parsing network - SAM 3 is promptable and open-vocabulary rather than a fixed pixel-labeled taxonomy, so boundaries can be slightly less crisp, and you pay one extra fal call per active section. Keep your toggle count low and the cost stays low.
The inputs that matter
image,api_key- the essentials. Then the section toggles. The defaults mirror the original re-skin workflow's exclusion set (nose/eyes/ears/mouth/lips/hair/hat on, the rest off), which is a sane starting point: you generally do want to protect facial features and hair from a skin retouch.- Every section also has a
<section>_opacity(0–1, default 1.0). This is the sophisticated bit: 1.0 writes the section fully white in the mask (fully protected - original wins in the composite); 0.5 writes gray, which blends 50% original / 50% generated - i.e. partial retouch on that section.clothing_opacity = 0.5, for instance, gets you a half-strength retouch on clothing. At all-defaults it reproduces the old binary mask exactly, so upgrading a saved workflow is safe. padding_percent- grow the merged mask, useful when a section's edge is slightly loose.partial_feather_percent/partial_contract_percent- for the gray partial sections only: feather softens the partial region's edge so the blend has no hard line, and contract erodes it inward first so the soft edge sits inside the region instead of bleeding a faint ring onto the surrounding skin. If you're using partial opacity, set contract near or above feather.glasses_prompt_override- the SAM 3 text query for glasses (default targets just the frame/temples, not the lens). Tune it if the glasses mask keeps swallowing the lens.glasses_box_center_x/y+glasses_box_width/height- an optional GroundingDINO-style box (e.g. from a Florence-2 region selector) so SAM 3 refines a known box instead of searching by text alone. The classic "text finds it, box nails it" two-stage pattern.
Three outputs: mask (MASK, merged), info (STRING, JSON of which sections were used), and color_preview (IMAGE) - a color-coded preview where each section is tinted so you can see the mask's coverage at a glance.
How it works
For each active toggle it makes one SAM 3 call with that section's text prompt, downloads the mask, applies opacity/feather/contract, and OR-merges everything into one mask. That means a section that fails its SAM call is skipped with a warning rather than killing the run - the info output is where you see that. Cost is directly proportional to active toggles, so disable the sections you don't need.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/Superside/comfyui-superside-nodes
pip install -r requirements.txt
Restart ComfyUI, find it under Superside, paste a fal key into api_key (blank falls back to a FAL_KEY env var).
One real gotcha
For partial-opacity gray to survive into the composite, the downstream mask nodes must not binarize it - the README calls out that the pack's Grow Mask With Blur and Resize To Match both preserve gray, so stick to those if you're using partial sections. And remember: every active toggle is a billable fal call, so this node's cost adds up fast on a busy face with everything switched on.
Inputs (38)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| api_key | STRING | — | |
| skinopt | BOOLEAN | false | — |
| noseopt | BOOLEAN | true | — |
| eyesopt | BOOLEAN | true | — |
| eyebrowsopt | BOOLEAN | false | — |
| earsopt | BOOLEAN | true | — |
| mouthopt | BOOLEAN | true | — |
| lipsopt | BOOLEAN | true | — |
| hairopt | BOOLEAN | true | — |
| hatopt | BOOLEAN | true | — |
| glassesopt | BOOLEAN | true | — |
| earringsopt | BOOLEAN | false | — |
| neckopt | BOOLEAN | false | — |
| necklaceopt | BOOLEAN | false | — |
| clothingopt | BOOLEAN | false | — |
| skin_opacityopt | FLOAT | 1.000–1 | Opacity of 'skin' in the exclusion mask (only when 'skin' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| nose_opacityopt | FLOAT | 1.000–1 | Opacity of 'nose' in the exclusion mask (only when 'nose' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| eyes_opacityopt | FLOAT | 1.000–1 | Opacity of 'eyes' in the exclusion mask (only when 'eyes' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| eyebrows_opacityopt | FLOAT | 1.000–1 | Opacity of 'eyebrows' in the exclusion mask (only when 'eyebrows' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| ears_opacityopt | FLOAT | 1.000–1 | Opacity of 'ears' in the exclusion mask (only when 'ears' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| mouth_opacityopt | FLOAT | 1.000–1 | Opacity of 'mouth' in the exclusion mask (only when 'mouth' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| lips_opacityopt | FLOAT | 1.000–1 | Opacity of 'lips' in the exclusion mask (only when 'lips' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| hair_opacityopt | FLOAT | 1.000–1 | Opacity of 'hair' in the exclusion mask (only when 'hair' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| hat_opacityopt | FLOAT | 1.000–1 | Opacity of 'hat' in the exclusion mask (only when 'hat' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| glasses_opacityopt | FLOAT | 1.000–1 | Opacity of 'glasses' in the exclusion mask (only when 'glasses' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| earrings_opacityopt | FLOAT | 1.000–1 | Opacity of 'earrings' in the exclusion mask (only when 'earrings' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| neck_opacityopt | FLOAT | 1.000–1 | Opacity of 'neck' in the exclusion mask (only when 'neck' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| necklace_opacityopt | FLOAT | 1.000–1 | Opacity of 'necklace' in the exclusion mask (only when 'necklace' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| clothing_opacityopt | FLOAT | 1.000–1 | Opacity of 'clothing' in the exclusion mask (only when 'clothing' is ON). 1.0 = fully preserve the original here (no retouch). 0.5 = partial (50% original / 50% generated in the final composite). 0.0 = no exclusion. |
| glasses_prompt_overrideopt | STRING | eyeglasses frame only - the metal or plastic rim and temples, not the lens or the skin behind it | SAM 3 text query used for the 'glasses' toggle above. Edit this to tune how tightly it targets just the frame/temples vs. the whole glasses silhouette (frame+lens) - no code changes needed. Only used when 'glasses' is ON. |
| glasses_box_center_xopt | INT | -1-1–1000000 | Optional grounding box for the 'glasses' section, in the old GroundingDINO+SAM style: wire in a Florence-2 Smart Region Selector's center_x/center_y/crop_width/crop_height (query e.g. 'eyeglasses frame') and SAM 3 will refine that box into a precise mask instead of relying on text alone. Leave at -1 (unconnected) to use text-only prompting. |
| glasses_box_center_yopt | INT | -1-1–1000000 | — |
| glasses_box_widthopt | INT | -1-1–1000000 | — |
| glasses_box_heightopt | INT | -1-1–1000000 | — |
| padding_percentopt | FLOAT | 0.00–50 | — |
| partial_feather_percentopt | FLOAT | 0.00–25 | Feather (soft edge) applied ONLY to sections whose opacity is below 1.0 (e.g. clothing at 0.5). Softens the partial region's border so the downstream composite blends it gradually instead of showing a hard line / doubled edge. Full-opacity (1.0) sections are untouched here - they keep their crisp edge for the downstream grow/blur to handle. 0 = off. |
| partial_contract_percentopt | FLOAT | 0.00–25 | Contract (erode inward) the mask of partial-opacity sections BEFORE feathering, so the soft edge sits INSIDE the region instead of bleeding outward past its true border (which shows as a faint ring/edge on the surrounding skin). Set it near partial_feather_percent to pull the feathered ramp back to the real edge. Only affects partial (<1.0 opacity) sections. 0 = off. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |
| info | STRING | — |
| color_preview | IMAGE | — |