Nodes/ComfyUI-NKD-Basic-Tools/😺NKD Mask Ops
ComfyUI Node

😺NKD Mask Ops

The whole mask pipeline in one GPU pass — levels, specks, holes, blockify, temporal

By Nekodificador·Created about a month ago·Updated 3 days ago· 71
😺NKD Mask Ops
  • mask
  • vae
  • model
  • mask
  • mask_inverted
  • latent_mask
â—„invertfalseâ–º
â—„black_point0.00â–º
â—„white_point1.00â–º
â—„despeckle0â–º
â—„fill_holesfalseâ–º
â—„close_gaps0â–º
â—„temporal_expand0â–º
â—„temporal_smooth0â–º
â—„expand0â–º
â—„blockify0â–º
â—„blockify_threshold0.50â–º
â—„feather0â–º
â—„edge_low0.00â–º
â—„edge_high1.00â–º

Mask cleanup is the boring, essential step nobody wants to spend render time on - and in a video workflow it quietly becomes the slowest part, because the usual approach grows or feathers a mask frame by frame on the CPU. 😺NKD Mask Ops puts the whole pipeline in one node, runs it on the GPU over the entire batch at once, and claims a full pipeline on 81 frames of 1080p in about a third of a second - with expand around 50× faster than the node you're probably using. The author credits the mask-to-latent idea to the MaskVidExperiments conversation and sets the bar as KJNodes; this node is the "everything, tuned" version.

The pipeline, in fixed order

Everything sits in one panel, and anything left at 0 is skipped. Steps run in a fixed order - clean, stabilize, shape, soften - so a feathered edge is never re-hardened by a later step. In order:

  • Levels - black_point / white_point cut the faint halo a segmentation model leaves around the subject. Set both to the same value for a hard threshold with no soft edge left.
  • Speck removal - despeckle drops blobs thinner than about twice the width you give it, while what survives keeps its exact shape - fingers and hair don't get shaved off like a normal cleanup pass.
  • Fill holes / close gaps - solid shapes and bridged cracks (close_gaps bridges without growing the mask overall).
  • Temporal - for video: temporal_expand makes each frame also cover what the mask covered a few frames before and after (so a segmentation that lags motion still covers it), and temporal_smooth averages across frames to stop the edge flickering.
  • Expand / contract - one signed expand value.
  • Blockify - snaps the mask to a grid of squares so it survives the trip into latent space without bleeding into neighboring blocks.
  • Feather - runs last, so nothing hardens it again.

The two inputs that make it latent-aware

  • vae - connect the VAE you're sampling with and Blockify turns on with that VAE's own grid, including, on a video VAE, the frames it collapses into a single latent. The mask then lands on latent boundaries exactly.
  • model - alongside the VAE. A few models (MiniMax H3) read the mask themselves and regenerate a whole token the moment any part of it is covered, so the block that must stay clean is bigger than one latent. The node asks the model rather than assuming - and changes nothing for models where the latent is the unit.

With a VAE connected you also get the latent_mask output: the same mask already reduced to latent resolution, on the VAE's real grouping. Feed that one to Set Latent Noise Mask for video. A mask still in pixels gets resampled on the way in, spread evenly across frames - and a video VAE doesn't group them evenly, which on a MiniMax H3 grid can make a one-frame latent's mask vanish outright. Reduced here, it arrives exactly as you built it.

Outputs: mask, mask_inverted, and latent_mask.

Install and gotchas

Install via ComfyUI Manager (search ComfyUI-NKD-Basic-Tools) or:

cd ComfyUI/custom_nodes
git clone https://github.com/Nekodificador/ComfyUI-NKD-Basic-Tools

Restart after. Pack dependencies are onnxruntime and huggingface_hub; neither is needed here. The edge_low / edge_high pair at the bottom is the subtle one: some video models only react to a narrow band of mask values, so a feather drawn across the full range behaves like a hard edge - set that band and the whole feather becomes the transition, while fully black and fully white are left alone. And if you only need fill holes + expand + feather, the pack's own 😺NKD Mask Ops Lean is the same engine with three widgets - don't haul this panel around for a stills composite.

Category😺NKD Nodes/Masking

Inputs (17)

NameTypeDefaultDescription
maskMASKMask or mask batch (one per video frame).
invertBOOLEANfalseSwap masked and unmasked before anything else runs.
black_pointFLOAT0.000–1Mask values at or below this become empty. Raise it to cut the faint halo a segmentation model leaves around the subject.
white_pointFLOAT1.000–1Mask values at or above this become fully masked. Set it to the same value as Black Point for a hard threshold with no soft edge left.
despeckleINT00–256Remove blobs thinner than about twice this many pixels. What survives keeps its exact shape — thin details like fingers or hair are not shaved off.
fill_holesBOOLEANfalseFill gaps fully enclosed by the mask, so each area becomes a solid shape.
close_gapsINT00–256Bridge cracks and notches narrower than about twice this many pixels without growing the mask overall.
temporal_expandINT00–64Video only. Each frame also covers what the mask covered this many frames before and after, so a mask that lags the motion still covers it.
temporal_smoothINT00–64Video only. Average each frame's mask with this many frames either side to stop the edge from flickering.
expandINT0-512–512Grow the mask outward by this many pixels, or shrink it with a negative value.
blockifyINT00–256Snap the mask to a grid of squares this many pixels wide, so it survives the trip into latent space with no bleeding into neighbouring blocks. Connect a VAE and the size comes from it; otherwise set it yourself (8 or 16 for most models). Any value above 0 turns it on.
blockify_thresholdFLOAT0.500–1How much of a block must be covered for it to turn on fully. 0 keeps each block's average as a gray value instead, for a pixelated look.
featherINT00–256Soften the edge by this many pixels. Runs last, so nothing hardens it again.
edge_lowFLOAT0.000–1Where the soft part of a feathered edge starts. Leave 0 and 1 and nothing happens. Some video models only react to a narrow band of mask values, so a feather drawn across the full range behaves like a hard edge; set that band here and the whole feather becomes the transition. Fully black and fully white are left alone, so what the mask protects stays protected.
edge_highFLOAT1.000–1The other end of that band.
vaeoptVAEOptional. Connect the VAE you are sampling with and Blockify turns on with that VAE's own grid — including, on a video VAE, the frames it collapses into a single latent. The mask then lands on latent boundaries exactly, with nothing bleeding into the neighbouring blocks.
modeloptMODELOptional, alongside the VAE. A few models (MiniMax H3) read the mask themselves and regenerate a whole token the moment any part of it is covered, so the block that has to stay clean is bigger than one latent. Connect the model and Blockify uses whichever of the two applies — it asks the model rather than assuming, and changes nothing for the models where the latent is the unit.

Outputs (3)

NameTypeDescription
maskMASK—
mask_invertedMASK—
latent_maskMASKWith a VAE connected, the same mask already reduced to latent resolution — feed this one to Set Latent Noise Mask. It arrives exactly as built: a mask still in pixels gets resampled on the way in, evenly across the frames, which is not how a video VAE groups them. Same as the mask output when no VAE is connected.