DOGMA Global Refine Mask v14 — Protect Text
DOGMA Global Refine Mask v14
- reference_image
- mask_1
- mask_2
- mask_3
- mask_4
- mask_5
- mask_6
- refine_mask
- protected_text_mask
- summary
Here's a failure you have probably met: you run a restoration pass over an old street photo, the buildings come back beautifully, and the shop sign now says something else. Or says the same thing but with the letters slightly wrong. Text is the thing diffusion models are worst at preserving and most confident about inventing.
This node builds the mask that stops it. Full-frame refinement, minus every pixel of signage, plus a margin.
What it is
Most of the DOGMA pipeline is about where to edit. This one is about where not to, and it inverts the usual question. You already have a set of semantic masks - six of them, in the pack's standard layout. Whatever the pipeline decided to detect, this node takes those masks, picks out the ones whose category is a text-bearing family, and subtracts them from the frame. What's left is your refinement mask: everything in the photograph that is fair game.
The docstring says the intent plainly - "this lets the global visual-restoration pass change the photograph everywhere except the original sign/text pixels, which remain exact source pixels." Exact, not blended, not feathered. The sign pixels in the output are literally the source.
That's a stronger guarantee than a prompt can give you. Telling a model "don't change the text" is a request; subtracting the text from the mask is a geometry.
How it works
Each of the six mask_i inputs is first reduced to a single-channel union: a 2-D mask gets a batch dimension, and a batch gets max-collapsed across it. All six are then conformed to the largest canvas size present, so mismatched SAM outputs from different passes don't blow up.
Then the categories matter. Each category_i string is run through the pack's category-to-family mapping, and any mask whose family is signage is accumulated into a protected union. Everything else is ignored - this node does not care what people, vehicles or vegetation masks contain. They influence nothing.
The protected union is then dilated by protect_radius pixels and clamped, and refine_mask is 1 - protected. That's the whole algorithm.
The dilation is doing real work. A SAM mask that hugs the glyphs exactly will still leave the generative pass touching antialiased edge pixels and the sign's frame; 20px of margin at SAM resolution is the author's answer, and it's adjustable from 0 to 128 because archival plates are scanned at wildly different resolutions.
Inputs and outputs
Thirteen inputs, all required, which is why this node reads as intimidating at first: reference_image (IMAGE), then six pairs of mask_1/category_1 through mask_6/category_6, then protect_radius (INT, default 20, 0–128).
reference_image exists to establish the frame size rather than to be modified - it passes nothing through. The category_i inputs are forced STRING inputs, so they come off your plan or inventory nodes. The mask_i inputs are MASK.
Three outputs. refine_mask (MASK) is the one you feed into the global refinement pass - a txt2img- or img2img-style whole-frame node, typically masking the sampler's latent. protected_text_mask (MASK) is the signage union after dilation, which is useful on its own: wire it into whatever "keep this region untouched" input your sampler exposes, or into a mask preview to confirm you caught the right thing. summary (STRING) tells you which categories were treated as protected, phrased as a sentence about radii and pixel counts. When summary says no signage category was detected, the full frame has been left eligible - read it, because that's a silent difference in behaviour, not an error.
Install
ComfyUI Manager → search DOGMA Nodes (publisher axior), install, restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/axior/ComfyUI-DOGMA-Nodes
# restart ComfyUI
No external Python packages - the pack's requirements.txt is a single comment - and no model files. It's mask arithmetic on torch tensors.
Common issues
The dependency you cannot skip is upstream: this node only protects signage if one of your six slots actually detected signage. If your plan has no sign category, or its threshold was high enough that the shop sign never registered, you get a full-frame mask and a cheerful summary telling you so. Check summary before blaming the sampler.
Second, and specific to this node: because it protects text rather than repairing it, a genuine restoration job leaves you with a frame where the building looks 2020 and the sign still looks 1950. That's an accurate preservation of source pixels and often exactly right - you do not want a model guessing at letterforms. But it's a visible tonal mismatch if the sign covers a chunk of frame, and there's no dial here to soften it beyond lowering protect_radius. Protecting less means blending more, and blending more means risking the wording.
Last, version drift: this is a v14 node, from a much earlier generation of the pipeline than the v54–v56 crop and mask nodes. Its six-slot assumption and its category_i strings are compatible with the fixed plans, less so with planners that emit a different number of slots. Wire the ones you have, and if you only have four categories, that's fine - unused mask inputs still need something connected, which is what an empty mask from a null-output placeholder is for.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| reference_image | IMAGE | — | |
| mask_1 | MASK | — | |
| category_1 | STRING | — | |
| mask_2 | MASK | — | |
| category_2 | STRING | — | |
| mask_3 | MASK | — | |
| category_3 | STRING | — | |
| mask_4 | MASK | — | |
| category_4 | STRING | — | |
| mask_5 | MASK | — | |
| category_5 | STRING | — | |
| mask_6 | MASK | — | |
| category_6 | STRING | — | |
| protect_radius | INT | 200–128 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| refine_mask | MASK | — |
| protected_text_mask | MASK | — |
| summary | STRING | — |