Nodes/DOGMA Nodes/DOGMA v39 Safe Adaptive Crops
ComfyUI Node

DOGMA v39 Safe Adaptive Crops

The no-op fix that keeps a crop graph from dying on an empty sector

By axior·Created 4 months ago·Updated 3 days ago· 1
DOGMA v39 Safe Adaptive Crops
  • image
  • mask
  • crops
  • crop_masks
  • stitch
  • info
category
kind
max_groups8
context_px160
target_long_side1792

This node exists because of one bug class, and if you've run an automated detailer unattended you already know it: a sector finds nothing, returns an empty crop list, and everything downstream either errors or - worse - runs on a stale input.

DOGMAAdaptiveGroupedCropsV381 is DOGMAAdaptiveGroupedCropsV38 with a guard bolted on, and the display name says it: v39 Safe Adaptive Crops. Same inputs (image, mask, category, kind, max_groups, context_px, target_long_side), same outputs (crops, crop_masks, stitch, info). It calls the v38 build, and then looks at what came back.

What the guard does

If the upstream result says "no selected regions", or the category is literally none, the node returns a single synthetic crop instead of an empty list: the top-left corner of the image, with height and width being the smaller of 512 or the image dimension, rounded to a multiple of 16, plus an all-zero mask and a stitch record stamped empty: True. The info string becomes <category>: no selected regions - SAFE NO-OP.

Two things are deliberate about that. The 16-px rounding is because these crops are destined for a Flux-family latent grid that divides by 16, so a 500-pixel crop just gets padded or resampled downstream. The zero mask is because the point is for the pass to run and change nothing, so the stitch composites zero pixels and your output equals your input.

Why does a no-op pass matter more than skipping? Because in a real graph you can't cleanly skip. The Klein branch is already wired, the stitch node expects a list of the same length as its edited images, and the graph is evaluated as a unit. A synthetic no-op crop keeps all the lengths consistent. It's the same reasoning as the v40 cropper's centred no-op and the v39 detail band's fallback - this pack consistently prefers a harmless pass over an empty tensor.

Where it fits

It replaces V38 in a v38-era inventory pipeline: planner emits category + kind per group → this node makes context-padded crops (family mapping: OBJECT→vehicle-like, STRUCTURE→architecture, SURFACE→road/chunked) → Klein does a masked local pass per crop → a stitch node composites using the stitch records. Keep DOGMACategoryMaskGate on the mask input if you want unused slots to arrive as zero masks rather than as leftovers from the previous sector.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/axior/ComfyUI-DOGMA-Nodes

Or ComfyUI Manager → DOGMA Nodes, then restart - the node set registers at import, so a running ComfyUI won't pick it up. Nothing to pip install; the pack declares no dependencies. You do need a segmenter (SAM / Grounded SAM) for the masks, a VLM for the plan text, and FLUX.2 Klein for the pass itself.

Common issues

Still nothing happens on an active sector. Then it's not the "no regions" path - the cropper returned some region and the mask inside it was near enough empty. Check info: a real run reports <family>: N grouped/category crops.

The mask arrives from a previous sector. This is the classic silent wrong-edit: an unused slot keeps last run's mask. Put DOGMACategoryMaskGate in front, which zeroes the mask whenever the category is none/unused/n/a.

You expected per-instance masks in the crop output. This path emits one mask per crop, not one per object. If you need instance-level masks and grouping, that's the v40 cropper's job.

CategoryDOGMA/Semantic Detailer

Inputs (7)

NameTypeDefaultDescription
imageIMAGE
maskMASK
categorySTRING
kindSTRING
max_groupsINT81–16
context_pxINT16032–512
target_long_sideINT1792768–3072

Outputs (4)

NameTypeDescription
cropsIMAGE
crop_masksMASK
stitchDOGMA_STITCH
infoSTRING