Nodes/radiance/HDR Color Pipeline
ComfyUI Node

HDR Color Pipeline

Linearise, adapt, convert, compress — in one node

By FXTD-Studios·Created 8 months ago·Updated about 18 hours ago· 246
HDR Color Pipeline
  • image
  • vae_image
  • scene_linear
  • peak_linear
  • colorspace_json
◄encodingsRGB►
◄compression_ratio0.50►
◄source_primariesRec.709 (sRGB)►
◄target_primariesRec.709 (sRGB)►
◄chromatic_adaptationNone►
◄pq_peak_nits1000►

Wiring a proper scene-linear handoff usually looks like five nodes in a row: decode the transfer curve, adapt the white point, swap primaries, compress the highlights so a VAE can survive them, then output. HDR Color Pipeline is that whole chain in one box, and it emits both answers you need - a VAE-ready image and the untouched scene-linear one.

This is the node for the seam between "I have an image in some encoding" and "I have something a diffusion model can eat without banding." It's the colours department's version of a prep node.

How it works

Four stages, in order:

  1. Linearise. encoding names the input's transfer curve and gets decoded to linear light. PQ is decoded absolutely - 203 nits maps to 1.0 - because that's what ST.2084 is. Log curves decode to camera scene-linear. Linear (none) just clamps negatives.
  2. Adapt. chromatic_adaptation applies a Bradford white-point matrix (D65_to_D60, D60_to_D65, D65_to_D50, D50_to_D65). This step is skipped with a warning when the primaries conversion already changes the white point - i.e. to or from ACEScg, which is D60. That guard exists so you never apply the same adaptation twice, and it's the kind of thing most nodes make you discover the hard way.
  3. Primaries. source_primaries → target_primaries, any pair from Rec.709 (sRGB), BT.2020, ACEScg, DCI-P3 (D65), XYZ (D65). Same on both ends means no change.
  4. Compress. A soft-knee highlight compressor, strength set by compression_ratio (default 0.5). This is the part that keeps a VAE from choking on 60.0-valued sunsets.

Inputs and outputs

The four required inputs are image, encoding, compression_ratio and - practically - nothing else. Ten encodings are offered: sRGB, Rec.709 (OETF), BT.1886 (TV γ2.4), Gamma 2.2, Gamma 2.4, PQ (ST.2084), HLG, ARRI LogC4, Sony S-Log3, and Linear (none).

Then source_primaries and target_primaries (default both Rec.709, i.e. no primaries work), chromatic_adaptation (default None), and pq_peak_nits - which the author's own tooltip confirms has no effect on the decode, because PQ is absolute. It's there for compatibility.

Four outputs, and the split is the point:

  • vae_image - the compressed, VAE-ready result. This is what goes into your encoder.
  • scene_linear - the uncompressed linear version, for grading, analysis or EXR delivery.
  • peak_linear (FLOAT) - how hot the scene got before compression. Watch this while you tune compression_ratio.
  • colorspace_json (STRING) - a record of the conversion, for logging or metadata.

Install

Manager → search Radiance → Install → restart → refresh.

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

Windows portable users: swap python for python_embeded\python.exe. No models needed here.

Where people get burned

The double-adaptation skip is correct behaviour, but it means your graph can contain a chromatic_adaptation setting that silently does nothing because you're converting to or from ACEScg. Read the console warning instead of assuming the widget bit.

Second, don't treat vae_image as your deliverable. It's compressed on purpose - that's a loss the diffusion stack is designed to tolerate, not a look you want in an EXR. Save scene_linear and let the encode node do delivery.

Third, watch peak_linear. If it's enormous (hundreds, not tens), a 0.5 compression_ratio is doing heavy lifting and you'll see it as flat, milky highlights downstream. That's a signal that your HDR expansion upstream is too aggressive, not that this node is broken.

CategoryFXTD STUDIOS/Radiance/HDR

Inputs (7)

NameTypeDefaultDescription
imageIMAGEEncoded input image. Negatives are clamped to 0; alpha is passed through.
encodingCOMBOsRGBTransfer curve of the input, decoded to linear. PQ decodes so 203 nits = 1.0; log curves decode to camera scene-linear. Linear (none) only clamps negatives.
compression_ratioFLOAT0.500–1Compression ratio for soft-knee HDR highlight compression.
source_primariesoptCOMBORec.709 (sRGB)Primaries of the input. Any pair of the listed primaries converts, with a Bradford white-point adaptation where the whites differ (ACEScg is D60).
target_primariesoptCOMBORec.709 (sRGB)Primaries to convert to. Same as source leaves colours unchanged.
chromatic_adaptationoptCOMBONoneBradford white-point matrix applied to the linear RGB before the primaries step. Skipped, with a warning, when the primaries conversion already changes the white point (to or from ACEScg), so it is never applied twice.
pq_peak_nitsoptFLOAT1000100–10000Has no effect on the decode: PQ is absolute, so it always decodes with 203 nits = 1.0 whatever this is set to.

Outputs (4)

NameTypeDescription
vae_imageIMAGE—
scene_linearIMAGE—
peak_linearFLOAT—
colorspace_jsonSTRING—