DOGMA Clustered Mask Crops (2K)
Turn a mask dump into grouped 2K tiles
- image
- masks
- crops
- crop_masks
- stitch
Step two of the detailing loop is "cut each region out and scale it up." Everyone writes their own version of it and most of them do the same naive thing: one crop per mask, no grouping, no floor on how small a crop can be. Ten masks of one car and you get ten overlapping crops of one car, sampled ten times. This node is the version that groups them first.
What it does
Feed it an image and a batch of masks - the raw output of a segmentation pass, not tidied up - and it hands back a list of square crops, a matching list of crop masks, and a stitch list that tells the other end where each crop came from.
The grouping is the point. Every mask becomes a bounding box, anything smaller than min_mask_area is thrown away, and boxes closer than merge_distance pixels get union-found into a single cluster. Then clusters are sorted by how much mask area they contain, biggest first, and capped at max_groups. One crowded street becomes a handful of large crops instead of forty little ones. That matters for more than speed: each crop is a separate sampling pass, and a hundred passes on a 2K tile is a hundred chances for a tile to come back different from its neighbours.
The interesting part: how big is a crop
Each cluster gets a square crop whose side is ceil(longest_box_side × context_factor), floored at min_crop_side, centred on the cluster, then clamped inside the image. The crop is resampled to target_size - 2048 by default.
That clamping is where the node quietly lies to you. side = min(side, W, H), so on a 1920×1080 source no crop can exceed 1080px on a side, and the node then resamples it up to 2048. You are not gaining detail; you're buying 2048px of generation budget for a region that was upscaled to get there. On a 1080p plate that's still worth doing - the model gets its native working resolution instead of a corner of a huge frame - but don't expect the crop to contain 2K of real information. This is the same reason upscaling.md tells you to fix defects at native resolution before upscaling: interpolating first bakes in what was already there.
If no mask clears min_mask_area, you don't get an error. You get one centred square crop of the middle of the frame, a zero mask, and a stitch entry flagged noop: true that every downstream stitch node skips. Nice defensive design; occasionally confusing when you were expecting output.
Inputs worth setting
- image - one image. The node reads
image[0]; a batch of four gives you crops of the first frame. - masks - the whole mask batch from your segmenter.
- target_size (default 2048) - the resolution each crop gets resampled to.
- merge_distance (96) - raise it to weld a whole crowd into one crop, lower it to keep people separate. This is the knob that changes the personality of the run most.
- context_factor (1.45) - padding around the cluster, as a multiple of its bounding box. More context is better for coherence and worse for spending your resolution budget on background.
- min_mask_area (24), min_crop_side (160), max_groups (24), mask_threshold (0.5) - the filters and caps.
Outputs
crops (IMAGE), crop_masks (MASK), and stitch (DOGMA_STITCH) - all three as lists, not batches, so you'll get a type complaint if you wire crops into something expecting a normal IMAGE. stitch carries the geometry (x, y, width, height, plus the original source size and a group_id with the member mask indices) and is what the stitch nodes read to know where to paste. Send crop_masks through a mask-builder like DOGMAFastDualMaskV34 to get a generation mask and a tight stitch mask out of one SAM mask; send stitch straight to the stitch node.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/axior/ComfyUI-DOGMA-Nodes
Manager → DOGMA Nodes, or comfy node install comfyui-dogma-nodes. No dependencies: the repo's requirements.txt is a comment, pyproject.toml declares none, and the code only imports torch, numpy and comfy.utils. MIT. No models shipped - you supply the segmenter.
Where people get burned
The list-versus-batch thing is the number one stumble. crops is a list of single-image tensors; classic ComfyUI nodes expect a batch. DOGMAImageListToBatchV25 and its inverse exist in the same pack for exactly this reason, and the DOGMA stitch nodes are INPUT_IS_LIST=True so they want lists too.
Second: the default max_groups of 24 is generous, and every group is a sampling pass on a 2K tile. On a 12GB card with a Klein 9B checkpoint, 24 crops is a long coffee break and a plausible OOM. Drop max_groups to 6–8 while you're iterating, then raise it once you know the graph works.
Third: merge_distance is in source pixels, so it means different things at different resolutions. 96px welds most of a crowded sidewalk at 1080p and practically nothing at 4K.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| masks | MASK | — | |
| target_size | INT | 2048512–4096 | — |
| merge_distance | INT | 960–1024 | — |
| context_factor | FLOAT | 1.451–4 | — |
| min_mask_area | INT | 241–1000000 | — |
| min_crop_side | INT | 16032–4096 | — |
| max_groups | INT | 241–256 | — |
| mask_threshold | FLOAT | 0.500.01–0.99 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| crops | IMAGE | — |
| crop_masks | MASK | — |
| stitch | DOGMA_STITCH | — |