Mask Hold Until Stable
Stop your animated crops from jittering
- mask
- mask
Here's the failure mode this node exists for. You run a face-detection node over a video, you get a mask per frame, and you feed those masks to a crop node to make an animated talking-head crop. The crop shudders - every frame the detector's box wiggles by a few pixels, and the whole result looks like a shaky handheld cam with the stabilizer off. Mask Hold Until Stable takes that batch of masks and outputs a single held mask, locked to the first frame whose mask is big enough (or the biggest frame in the batch), then backfills everything before it and forward-fills everything after it. Same mask, every frame, zero jitter.
It's one node in Enigmatic Nodes, a small utility pack by the individual dev enigmatice. Pure local Python, no models, nothing to download. The whole category of "mask hygiene" nodes is thin in ComfyUI, and this is the one that solves the temporal problem rather than the spatial one.
How it works
The mechanism is refreshingly simple, and the source spells it out. The node computes the area (sum of mask values) of every frame in the batch, then picks an anchor start: by default the first frame whose area crosses min_area; in peak_mode, the frame with the largest mask above min_area. It also scans backward from the end to find the last frame above a separate min_area_end threshold. Then, if hold_start is on, every frame before the anchor gets the anchor's mask; if hold_end is on, every frame after the end anchor gets that frame's mask. If no frame ever qualifies, it passes the batch through unchanged rather than guessing.
The key design choice is that start and end thresholds are independent. Detection often locks in reliably at the start of a clip but the face shrinks or blurs near the end - so you raise min_area to wait for a solid start, and keep min_area_end low to hold until the mask has nearly disappeared.
The inputs that matter
- mask - the batch of masks (e.g. per-frame face masks from a detection node).
- min_area (default
500) - start anchor threshold, in pixels. Raise it for higher-resolution masks; a 1280-wide face mask has far more than 500 pixels, so don't treat the default as universal. - min_area_end (default
500) - end anchor threshold, tuned independently. - peak_mode - off: first stable frame; on: lock to the largest mask in the whole batch (handy when detection is weak at the start and strongest mid-clip).
- hold_start / hold_end - backfill before the anchor and forward-fill after the end.
The output
One mask batch with the same frame count, held stable across the clip. Wire it straight into your crop/segment node where you'd previously wired the raw detection output.
Installing it
Same for every node in the pack:
- ComfyUI Manager - search Enigmatic Nodes, install, restart.
- Manual -
cd ComfyUI/custom_nodes && git clone https://github.com/enigmatice/comfyui-enigmatic-nodes, then restart.
No model files. The pack declares scipy, opencv-python, numpy, Pillow, torch; ComfyUI core already ships everything except opencv-python (which you almost certainly have from another pack), and Manager installs deps for you.
Common issues
- Nothing changes. If no frame ever crosses
min_area, the node passes the batch through untouched. Lowermin_areauntil the anchor locks in - and remember it's in pixels, so a 1024px mask needs a much bigger number than the 500 default. - The crop holds in the wrong place. That's
peak_modedoing its job based on the biggest mask, or a lowmin_arealocking on too early. Tune the threshold, or flip to the other mode. - The held crop doesn't follow movement. That's the whole point - this node freezes a mask, it doesn't track. If your subject moves a lot, pair it with something that re-detects periodically rather than expecting one mask to ride along.
One honest caveat: the pack is brand new (mid-2026, zero community footprint yet), so there's no pile of shared workflows to copy from. But for the specific agony of jittery animated crops on talking-head footage, this is the right tool, and the threshold logic is well thought out.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| mask | MASK | — | |
| min_area | INT | 5001–500000 | Minimum mask area for the START anchor. Raise this to wait for a larger masked region before locking. In peak mode this is a noise floor; the largest frame above it is used. |
| min_area_end | INT | 5001–500000 | Minimum mask area for the END anchor. Keep this low to hold until the masked region has nearly disappeared. Independent of min_area so start and end can be tuned separately. |
| peak_mode | BOOLEAN | false | Off: lock start to the first frame that crosses min_area. On: lock start to the frame with the biggest mask in the whole batch. |
| hold_start | BOOLEAN | true | Backfill all frames before the anchor with the anchor mask. |
| hold_end | BOOLEAN | true | Forward-fill all frames after the last stable frame with that frame's mask. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |