GROW Watermark Detect
Finds the watermark without rerunning the model — one VAE encode is all it takes
- image
- vae
- config
- decoded_message
- ecc_valid
- corrected_symbols
- min_vote_margin
- raw_codeword_hex
The usual way to check an image for a watermark is to reverse the generation process - diffusion inversion, or re-running the model with the payload as conditioning. That's slow and fragile. GROW Watermark Detect does neither: it VAE-encodes the image once and reads the watermark straight out of the latent's frequency signs. No diffusion model loaded, no inversion, no sampling. For batch verification of thousands of images, that speed difference is the whole point.
It's the detection half of the comfyui-dit-watermark pack. The clever part is that it's blind: it doesn't need to know the watermark content in advance. Each payload is stored as a self-describing frame - 1 byte of UTF-8 length, the payload, and 4 Reed–Solomon parity bytes (N+5 total) - so the detector tries candidate lengths up to your limit and accepts the first frame whose length, parity, and UTF-8 all validate. The RS code corrects up to two corrupted bytes, which is what keeps the scheme alive through JPEG q50, AVIF, HEIF, JXL, and crops.
The inputs that matter
- image / vae - the watermarked image and the same VAE used to embed it. If your detector uses a different VAE, you're comparing frequency domains from different spaces and it will usually fail.
- config - the
GROW_CONFIGoutput fromGROW Watermark Config. This is non-negotiable: the detector rebuilds the exact keyed frequency layout from it, and any mismatch meansecc_valid=False. - max_watermark_bytes (default 64) - how far the blind length search goes. Larger is slower; 64 covers the pack's own watermarks plus the legacy 32-byte frame with room to spare.
- robust_mode -
noneis fastest and correct for clean images.rotation,crop_scale, orrotation_crop_scalebrute-force a bounded set of affine candidates (identity first, then angles/scales) and keep whichever decodes. Crops recover cleanly in testing; rotation still leaves residual errors, so don't promise it.
What comes out
Five outputs, and honestly you mostly care about two:
- decoded_message - the payload text, if any.
- ecc_valid - the authoritative check. True means a frame passed RS validation and you have a genuine GROW watermark. A high
min_vote_marginwithecc_valid=Falsemeans someone misconfigured the layout, not that the watermark is fine. - corrected_symbols - how many corrupted bytes RS had to fix (0 is cleanest).
- min_vote_margin - the weakest bit's majority margin, 0–1; useful for judging how much headroom you have left.
- raw_codeword_hex - the raw decoded frame in hex, for debugging your own experiments.
The node also prints a summary line to the UI with all of it plus which alignment candidate won.
Installing
Same pack as the other three nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/zhangp365/comfyui-dit-watermark
Restart ComfyUI (or use Manager and search comfyui-dit-watermark). No models to download and no extra Python packages - the RS codec is bundled, and detection only needs PyTorch.
Where people get burned
- Wrong VAE or wrong config. Both must match the embed side exactly. This is the #1 cause of
ecc_valid=Falseon an image you know is watermarked. ecc_valid=Falsedoesn't mean "no watermark". It means the frame didn't validate - the image may be damaged beyond two symbols, un-watermarked, or from a different config. Checkraw_codeword_hexand the margin before blaming the image.- Over-attacked images. The README's own numbers are honest: Flux2 Klein images recovered 43 of 48 attack samples (89.58%), Qwen 12 of 16 (75%). JPEG below ~q50 and aggressive rotation are where it falls over. Build error-correction headroom in at embed time if you know images will be roughed up.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| vae | VAE | — | |
| config | GROW_CONFIG | — | |
| max_watermark_bytes | INT | 641–250 | Blind detection checks candidate UTF-8 byte lengths up to this value; a larger limit is slower. |
| robust_mode | COMBO | none | 4 options: none, rotation, crop_scale, rotation_crop_scale |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| decoded_message | STRING | — |
| ecc_valid | BOOLEAN | — |
| corrected_symbols | INT | — |
| min_vote_margin | FLOAT | — |
| raw_codeword_hex | STRING | — |