Nodes/comfyui-jz/jz Image Sanity
ComfyUI Node

jz Image Sanity

The bouncer your batch workflows are missing

By j-zhang19·Created 28 days ago·Updated about 23 hours ago· 2
jz Image Sanity
  • image
  • alpha
  • image
  • ok
  • reason
  • std
  • mean
  • report
  • all_ok
check_flattrue
min_std0.010
check_darktrue
min_mean0.020
check_brighttrue
max_mean0.980
check_transparenttrue
invert_alphatrue

jz Image Sanity is a quality gate for generated frames, and the more you lean on API image models or long batches, the more you'll want one. Fire off enough generations through Gemini or an upscaler running over a folder and you'll eventually get a frame back that is black, blown out to white, a single flat colour, or a fully transparent nothing. Most of the time you don't notice until you're staring at a finished grid. This node catches them at the moment they happen - and, crucially, it never raises. It measures each frame, reports what's wrong, and lets you decide whether that means retry, substitute, or skip.

That last bit is the whole design. This is from the comfyui-jz pack, which lives and dies by the pattern of branching on results with jz Switch and jz Fallback - lazy if/else nodes where the unused branch never executes. Image Sanity is the sensor those branches read. Wire its ok output into a jz Fallback alongside a retry path and you've built a loop that quietly discards garbage without a human in it.

How it works

Per frame, it runs a handful of cheap tensor checks. The interesting one is flatness, measured per channel - a flat red frame has per-channel standard deviations of 0, but a single global std across the whole tensor comes out around 0.47 because red vs. black is huge variation. So it computes std for each RGB channel and takes the largest; if that's below min_std (0.01 by default), the frame is flat. Brightness uses the BT.601 luma-weighted mean, so "too dark" and "too bright" are perceptual, not raw pixel averages. Transparency is separate: it looks at the 4th image channel and an optional wired alpha mask, and fails a frame whose alpha is zero everywhere - nothing visible, however textured the RGB is. An empty 0px frame is always a fail, no toggle.

The inputs that matter

  • check_flat / check_dark / check_bright / check_transparent - the toggles. All on by default.
  • min_std, min_mean, max_mean - the thresholds. Defaults are tuned for "the API returned literal garbage," so they're tight: 0.98 for max_mean means a legitimately bright scene can trip it. If your workflow deliberately makes dark images, lower min_mean (too dark means mean luma below it); if it runs bright and airy, raise max_mean. Watch the std and mean outputs while you tune.
  • alpha + invert_alpha - the one real gotcha. LoadImage's MASK output is 1 = transparent, so invert_alpha defaults to on to compensate. If you wire a mask from somewhere that's 1 = opaque, flip it.

Outputs, and how to wire them

The per-frame outputs - image, ok, reason, std, mean - are all lists, one verdict per frame in a batch. reason is a plain string ("flat #ff0000 (std 0.0000 < 0.0100)") that tells you exactly which check fired; report is a single JSON blob covering the whole batch and feeds jz Display JSON if you like reading the autopsy; all_ok is the one scalar you use for whole-batch decisions. The image output is a pass-through, untouched, so you can chain this inline without mangling anything.

Install

It ships inside comfyui-jz, so install the pack, not the node:

cd ComfyUI/custom_nodes
git clone https://github.com/j-zhang19/comfyui-jz

then restart ComfyUI, or use ComfyUI Manager and search "comfyui-jz". Dependencies are just requests, pillow, numpy - no model downloads, nothing heavy.

Gotchas

A wired alpha mask must match the frame dimensions or it raises (one mask broadcasts over a whole batch, fine). And remember the defaults are strict: this is a bouncer, not a critic. It's designed to flag anything a downstream model could choke on, so don't be offended when your artsy near-black frame gets thrown out - that's the feature.

Categoryjz/image

Inputs (10)

NameTypeDefaultDescription
imageIMAGE
check_flatBOOLEANtruefail frames with no variation — flat black, flat white, any single fill colour
min_stdFLOAT0.0100–1flat when the largest per-channel std falls below this; watch the std output to tune it
check_darkBOOLEANtruefail near-black frames that carry just enough noise to pass the flat check
min_meanFLOAT0.0200–1too dark when mean luma is below this
check_brightBOOLEANtruefail blown-out near-white frames
max_meanFLOAT0.9800–1too bright when mean luma is above this
check_transparentBOOLEANtruefail frames whose alpha is zero everywhere — nothing visible, however textured the rgb is
alphaoptMASKtransparency for a 3-channel image; LoadImage's MASK output goes here
invert_alphaoptBOOLEANtrueLoadImage masks are 1 = transparent; keep on when wiring that, turn off if your mask is 1 = opaque

Outputs (7)

NameTypeDescription
imageIMAGE
okBOOLEAN
reasonSTRING
stdFLOAT
meanFLOAT
reportSTRING
all_okBOOLEAN