DOGMA Object Crops v27 — Whole Objects, Never Tile
Cut the car out whole instead of tiling it
- image
- reference_image
- masks
- crops
- reference_crops
- crop_masks
- stitch
- info
Every "detailer" pipeline eventually hits the same wall: a 70-pixel car in a 4K frame is a smear, and the fix is to crop it out, re-render it at a size the model can actually resolve, and paste it back. Fine. The thing that ruins the result is how you crop. Cut a rectangle that slices the car in half and the model invents a second bumper at the seam. Tile the scene into a grid and you spend sampler calls on sky.
DOGMAObjectClusterCropsV27 is the version that refuses to tile. Its own header comment says the quiet part out loud: "Keeps each object whole… NEVER splits a crop into tiles… If a crop is too large, the whole crop is downscaled as one image." If you have a mask stack from SAM and you want per-object jobs, this is the node that builds them.
How it works
You feed it a master image, a second image to sample at identical coordinates (reference_image), a stack of masks, and a category string. It thresholds each mask at mask_threshold, drops anything under 6 pixels, and - this is the useful bit - throws away any mask that covers more than 55% of the frame, because a discrete-object pass should never be handed the sky.
Surviving boxes are converted from mask coordinates to master coordinates, sorted by area, and deduplicated at ≥0.72 bbox IoU. That's how a SAM run that returned car and automobile for the same car becomes one job instead of two.
Then it clusters, greedily and conservatively. group_radius is a maximum bounding-box gap in pixels; only objects that close get merged, and never more than a hard per-category cap (3 for vehicles, 2 for people, faces, architectural details and street furniture, 2 as the fallback). Miss that and you'd think group_radius=512 welds the whole street into one crop. It doesn't. Groups are ranked by total mask area, truncated to max_crops, then re-sorted top-left so your list order is stable run to run.
Each group becomes a crop with context_px of margin around the whole group, snapped to multiples of 16, plus a mask rebuilt from just that group's members. Resize is whole-image bicubic down to target_long_side long side, capped at 3.8 MP, with max_source_side acting as a second conservative ceiling rather than a split trigger.
The inputs you'll actually touch
category- comes from a planner/audit node upstream. It picks the grouping cap.max_crops- the number of crops, which is the number of downstream sampler runs. This is your render-time dial.group_radius/context_px- how much a "cluster" is allowed to be, and how much neighbourhood the model sees. Raise context for faces in crowds; keep it tight for street furniture.
Two outputs are lists, and that matters: crops and crop_masks each emit one item per group, reference_crops is the same rectangle sampled from reference_image, and stitch is a DOGMA_STITCH list - a plain list of dicts carrying x, y, width, height, source_width, source_height, group_id. That's the pack's own context object: one wire instead of four loose numbers per crop. info is a readable per-group log.
Install
ComfyUI Manager → search DOGMA Nodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/axior/ComfyUI-DOGMA-Nodes
# restart ComfyUI
No pip dependencies - requirements.txt literally says so, and the pack only uses PyTorch plus ComfyUI's own APIs. The README documents the WAN VACE and sampler nodes, not this one; the semantic family is undocumented there, which is why the node tooltips are empty.
Where people get burned
The batch is ignored. The node reads image[0:1]. Feed it eight frames and you get frame one, eight times not at all. Run it per image.
A safe no-op is not an error. With no usable detections it returns a centre 768px crop with a zero mask and noop: True in the stitch dict, and info says so. Downstream stitches skip no-ops, so the pipeline survives - but your image is untouched, and if you didn't read info you'll think the local pass did nothing.
One giant mask can eat the run. SAM returning one mask over the whole scene trips the 55% rejection and you get the no-op. Fix the segmentation, not the threshold.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| reference_image | IMAGE | — | |
| masks | MASK | — | |
| category | STRING | — | |
| group_radius | INT | 800–512 | — |
| context_px | INT | 1600–768 | — |
| max_source_side | INT | 4096768–8192 | — |
| target_long_side | INT | 3072768–4096 | — |
| max_crops | INT | 81–16 | — |
| mask_threshold | FLOAT | 0.500.01–0.99 | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| crops | IMAGE | — |
| reference_crops | IMAGE | — |
| crop_masks | MASK | — |
| stitch | DOGMA_STITCH | — |
| info | STRING | — |