Nodes/radiance/Policy Guard
ComfyUI Node

Policy Guard

Turn delivery specs into a pass/fail you can automate

By FXTD-Studios·Created 8 months ago·Updated about 18 hours ago· 246
Policy Guard
  • image
  • image
  • passed
  • data1
  • data2
  • score
◄modeGuard►
◄presetBroadcast SDR►
◄policy_file►
◄custom_max_peak_nits1000►
◄custom_max_clipping0.010►
◄custom_max_black_crush0.050►
◄custom_max_saturation1.00►
◄policy—►
◄max_clipping0.010►
◄max_black_crush0.050►
◄max_saturation1.00►
◄max_peak_nits1000►
◄require_metadata►
◄metadata_present►
◄signalDisplay SDR (1.0 = 100 nits)►

Every delivery has a spec sheet - peak nits, how much clipping is tolerable, how much black crush, a required metadata set. Policy Guard turns that sheet into something a graph can check. Feed it frames, get back a boolean, a score, the worst frame's numbers, and the image itself so you can keep chaining. That's a delivery QC gate you can put in front of a render queue instead of a person with a waveform.

Its scope is deliberately narrow, and the node says so: picture only. There's no audio input, so no loudness check. If you need loudness, that's a different tool.

The two modes are the trick

mode has Preset and Guard, and the workflow the author intends uses both.

In Preset mode the image is ignored and the node emits a policy instead: data1 is a policy JSON and data2 is a human-readable description of it. You pick from Broadcast SDR, Cinema HDR (P3-PQ), OTT HDR10, Social Media or Custom, and Custom uses the five custom_* fields (max peak nits, max clipping, max black crush, max saturation). Optionally you can point policy_file at a JSON file and it replaces the preset; a failed load logs a warning and falls back rather than killing the run.

In Guard mode you wire that data1 into the policy input and the node checks the image against it. When policy is connected it replaces every max_* and require_metadata value - so the preset path and the guard path can't disagree about what the spec is. If you don't connect a policy, the node falls back to the inline max_clipping, max_black_crush, max_saturation, max_peak_nits and require_metadata widgets, which is fine for a quick one-off.

The defaults in the box are worth reading because they're honest about what they measure. max_clipping 0.01 means at most 1% of pixels with luma above 0.99. max_black_crush 0.05 means at most 5% below 0.01. max_saturation at 1.0 is effectively "only fail on negative pixel values", because mean (max - min) / max saturation can't exceed 1 on legal values - so if you want a real saturation gate, set it lower on purpose.

The check that actually bites

signal is how pixel values are read as light for the peak test, and getting it wrong makes the node lie to you. Display SDR (1.0 = 100 nits) for BT.1886 white; Scene-linear (1.0 = 203 nits) for Radiance's HDR convention (BT.2408); PQ (ST 2084), absolute code values; HLG (BT.2100, 1000-nit display), HLG code values on the reference display. Clipping, black crush and luma are measured on the values as they are, but peak nits depend entirely on this one setting - pass a PQ master with signal still on Display SDR and your 1000-nit highlights read as something like 10 nits. Nothing errors. You just get a pass on a spec violation.

metadata_present takes the metadata your deliverable actually carries, as comma-separated keys or key=value pairs (only the keys are checked), and require_metadata lists the keys that must appear there - colorspace, eotf. That's how "did the deliverable ship tagged" becomes a machine check instead of a footnote in an email.

Outputs: image passes through, passed is the boolean, data1 and data2 are the policy JSON and its description (in Guard mode they describe the run), and score is an INT you can sort or threshold. The report is for the worst frame in the batch, which is the right call - you get the failure, not an average that hides it.

Install

Ships with Radiance; install via ComfyUI Manager (search Radiance, then restart and refresh) or:

cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt

No models, no GPU - it's arithmetic over the tensor. Fast enough to gate every delivery.

Where people get burned

  • Wrong signal. Covered above; it's the single most common way to get a green light you didn't earn.
  • Checking a display-referred image as scene-linear. The peak test will scream. Set signal to match what's actually on the wire.
  • Expecting a per-frame list. You get the worst frame's numbers in the report. If you need per-frame detail, that's what the QC node's JSON report is for.
  • Using it as an artistic judgement. It checks numbers, not whether the shot looks good. Nothing here replaces a human looking at the frame - it just stops the obviously broken ones from reaching them.
CategoryFXTD STUDIOS/Radiance/Review

Inputs (16)

NameTypeDefaultDescription
modeCOMBOGuardPreset: output a policy JSON (data1) and its description (data2); the image is not checked. Guard: check the image against a policy.
imageIMAGEFrames to check. Peak nits are read as set by signal; clipping, black crush and luma are measured on the values as they are. Ignored in Preset mode.
presetoptCOMBOBroadcast SDRDelivery policy to output in Preset mode. Custom uses the custom_* values.
policy_fileoptSTRINGOptional path to a policy JSON file; when it loads, it replaces the preset. A failed load logs a warning and falls back to the preset. Preset mode only.
custom_max_peak_nitsoptFLOAT10000–10000Custom preset: highest allowed peak, in nits (read as set by signal in Guard mode). Preset mode only.
custom_max_clippingoptFLOAT0.0100–1Custom preset: highest allowed fraction of pixels with luma above 0.99 (0.01 = 1%). Preset mode only.
custom_max_black_crushoptFLOAT0.0500–1Custom preset: highest allowed fraction of pixels with luma below 0.01 (0.05 = 5%). Preset mode only.
custom_max_saturationoptFLOAT1.000–2Custom preset: highest allowed mean HSV-style saturation, (max - min) / max per pixel. 1.0 only fails on negative pixel values. Preset mode only.
policyoptSTRINGPolicy JSON, usually data1 of a Preset-mode Policy Guard. When connected it replaces all max_* and require_metadata values. Guard mode only.
max_clippingoptFLOAT0.0100–1Highest allowed fraction of pixels with luma above 0.99, worst frame. Guard mode, used only when policy is empty.
max_black_crushoptFLOAT0.0500–1Highest allowed fraction of pixels with luma below 0.01, worst frame. Guard mode, used only when policy is empty.
max_saturationoptFLOAT1.000–2Highest allowed mean saturation, (max - min) / max per pixel, worst frame. 1.0 only fails on negative pixel values. Guard mode, used only when policy is empty.
max_peak_nitsoptFLOAT10000–10000Highest allowed peak in nits, the brightest channel read as set by signal. Guard mode, used only when policy is empty.
require_metadataoptSTRINGComma-separated metadata keys that must appear in metadata_present, for example colorspace, eotf. Guard mode, used only when policy is empty.
metadata_presentoptSTRINGComma-separated metadata the deliverable carries, as keys or key=value pairs. Only the keys are checked. Guard mode only.
signaloptCOMBODisplay SDR (1.0 = 100 nits)How pixel values are read as light for the peak check. Display SDR: 1.0 = 100 nits (BT.1886 white). Scene-linear: Radiance's convention, 1.0 = 203 nits (BT.2408). PQ: ST 2084 code values, absolute. HLG: BT.2100 code values on the 1000-nit reference display. Guard mode only.

Outputs (5)

NameTypeDescription
imageIMAGE—
passedBOOLEAN—
data1STRING—
data2STRING—
scoreINT—