Nodes/radiance/HDR Per-Channel Denorm
ComfyUI Node

HDR Per-Channel Denorm

Undo the normalisation without re-deriving the math

By FXTD-Studios·Created 8 months ago·Updated about 18 hours ago· 246
HDR Per-Channel Denorm
  • image
  • image_linear
◄stats_json—►

This one is simple and it's the kind of simple that saves a render. When you normalise an HDR image per channel before a VAE - mean and standard deviation, re-centred into 0–1 - the decode side has to invert that. You could recompute the statistics yourself. You shouldn't. HDR Per-Channel Denorm takes the stats_json that the norm node already emitted and undoes the operation exactly.

It's a two-input, one-output utility, and its whole value is not having to keep mean/std/norm_center in a text file next to your workflow.

How it works

The math is stated in the source and it's just arithmetic:

x_linear = x_norm * σ + μ
         = ((x_vae * 2 * norm_center) - norm_center) * σ + μ

Read that second form: the decoded 0–1 image is first stretched back out of the norm_center window (the norm node mapped ±norm_center σ into 0–1, so multiplying by 2 × norm_center and subtracting norm_center recovers the standardised values), then rescaled by σ and re-offset by μ. That's the full inverse. The reason it matters that this is exact is that the norm step exists to stop HDR frames with strong colour casts - golden hour, fire, neon - from saturating the VAE's latent distribution and decoding with banding. An approximate inverse just reintroduces a colour error on top of the banding you were trying to avoid.

Inputs and outputs

Required: image - the normalised, 0–1 image, typically the VAE-decoded result of something that went through HDR Per-Channel Norm. The tooltip is specific about the expectation, and it's the right expectation.

Required: stats_json - a STRING that must be wired, not typed. It carries the per-channel mean, std, and the norm_center value that was used, which is why it's a forced input on the node.

One output: image_linear.

That's it. No knobs, no tricks. If you're looking for a widget to fiddle with, there isn't one - and that's the point.

Install

Manager → search Radiance → Install → restart ComfyUI → refresh your browser.

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 should use ComfyUI's bundled python_embeded\python.exe. No models.

Where people get burned

Mismatched stats. The stats describe one specific image - a per-frame mean and std. Wire stats from frame A into a decode of frame B and you'll get a plausible-looking image with the wrong exposure and a colour cast, and nothing will warn you. In a batch pipeline this usually means keeping the stats paired with the latents through the whole round trip, which is fiddly but not optional.

Forgetting norm_center travels with the stats. It does, in the JSON - that's deliberate. But if you hand-write or edit a stats string, that value has to stay consistent with the window the norm node actually used, or the stretch step is wrong.

Using the wrong normalisation entirely. This inverts per-channel normalisation (the LTX-Video-style behaviour). It is not a tool for undoing an arbitrary scale you applied somewhere else - if the numbers came from a different transform, feed it the matching one or do the math yourself.

And a sanity note: because these are plain tensors, nothing here clamps or remaps afterwards. If your recovered linear values look extreme, that's usually correct - the HDR was extreme, that's why it got normalised.

CategoryFXTD STUDIOS/Radiance/HDR

Inputs (2)

NameTypeDefaultDescription
imageIMAGENormalised image (0 to 1), typically the VAE-decoded result of an image that went through HDR Per-Channel Norm.
stats_jsonSTRINGstats_json output of HDR Per-Channel Norm (per-channel mean, std and norm_center). Used to undo that normalisation.

Outputs (1)

NameTypeDescription
image_linearIMAGE—