X2HDR Dynamic Range QA
Is your 'HDR' actually HDR? This node answers instead of guessing
- hdr_image
- qa_json
- exposure_strip
The most common failure in an X2HDR workflow isn't the decode - it's that the decode produced an SDR-range image wearing an HDR costume. A float tensor can hold values above 1.0 while all the actual energy sits in the SDR band, and then you save an EXR that's technically HDR and practically a flat PNG. X2HDR Dynamic Range QA catches that before you commit.
How it works
Per frame it computes luminance and RGB statistics - percentiles, max RGB, black fraction, how much sits above the SDR reference - and runs three checks:
- Exceeds SDR:
max_rgborlum_p995abovesdr_reference(default 1.0). - Highlight headroom:
log2(lum_p995 / lum_p50)≥headroom_threshold_stops(default 3). - Useful dynamic range:
log2(lum_p995 / positive_lum_p01)≥dynamic_range_threshold_stops(default 10).
verdict = pass only if every frame passes all three. Otherwise it's review, with per-frame review_reasons naming the failed check. Pure-black pixels are reported separately as black_fraction instead of polluting the dynamic-range number - a nice touch, since a single dead pixel would otherwise tank the whole measurement.
The second half is visual: it builds a -4/-2/0/+2/+4 EV exposure preview strip so you can see recoverability. Lower exposure should reveal highlight detail; higher exposure should reveal shadow detail. The README's own framing says it best: bright numbers alone are not enough.
Inputs
- hdr_image - decoded linear HDR; wire the
hdr_imageoutput of any X2HDR decode here. - sdr_reference (1.0), headroom_threshold_stops (3), dynamic_range_threshold_stops (10) - the pass/fail bar. Tune them if your pipeline's target differs, but the defaults are sensible for a ~10-stop HDR check.
- preview_method (
aces/reinhard/log), white_percentile, gamma - control how the strip looks, not the verdict. - save_preview (true) and filename_prefix - whether the strip gets saved as a temporary preview image in the UI.
Outputs
- qa_json (
STRING) - the full report; wire to a text-display node. - exposure_strip (
IMAGE) - the five-EV strip; wire to aPreview Image.
Installing it
Part of facok/comfyui-x2hdr. ComfyUI Manager → search comfyui-x2hdr → install, restart, nodes under image/HDR/X2HDR. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/facok/comfyui-x2hdr
then restart. The one extra dependency is PyAV (av), imported at load time; pip install av in ComfyUI's Python environment if the pack errors.
Where people get burned
- It's the fastest wrong-decode detector in the pack. If QA says
reviewbecause nothing exceeds the SDR reference, your model wasn't trained with the encoding you decoded - you've wired a PU21 decode to a LogC-trained model, or vice versa. - Pass means range, not beauty. A technically-HDR image can still look bad tone-mapped. QA answers "is the range there?", not "will this win a colorist award."
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| hdr_image | IMAGE | — | |
| sdr_reference | FLOAT | 1.000.001–100000 | Reference SDR ceiling. A frame must have max_rgb or lum_p995 above this value to prove it exceeds SDR/VAE range. |
| headroom_threshold_stops | FLOAT | 3.00–32 | Minimum highlight headroom in stops: log2(lum_p995 / lum_p50). Higher means highlights carry more recoverable range above midtones. |
| dynamic_range_threshold_stops | FLOAT | 10.00–32 | Minimum useful dynamic range in stops: log2(lum_p995 / positive_lum_p01). Pure black pixels are reported separately as black_fraction. |
| preview_method | COMBO | aces | Tone mapper used for the exposure preview strip. |
| white_percentile | FLOAT | 99.50–100 | Luminance percentile used as the preview white point when white_point is automatic. |
| gamma | FLOAT | 2.200.1–8 | Display gamma for the LDR exposure preview strip. |
| save_preview | BOOLEAN | true | Save the -4/-2/0/+2/+4 EV strip as a temporary preview image in the node UI. |
| filename_prefix | STRING | x2hdr_dr_qa | Filename prefix for saved temporary QA preview strips. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| qa_json | STRING | — |
| exposure_strip | IMAGE | — |