SDR to HDR Prepare
The pre-VAE route, and whether you still need it
- image
- clip_mask
- image
- mask
- stats_json
- peak_linear
Most people who land here wanted a normal SDR-to-HDR conversion and should be using SDR → HDR Universal instead. Prepare is the older, narrower thing: it gets an SDR image ready to be pushed through a VAE and a sampler so a diffusion model can invent the highlight detail that a clipped 8-bit file threw away. If you don't have a VAE in your graph, you're on the wrong node.
What it's actually for
SDR-to-HDR without a model is a curve - you stretch the highlights you have and call it done. There is nothing above code value 1.0 to stretch, because the camera or the screenshot clipped it. So the interesting version of this job is generative: encode the SDR image into latent space with above-white energy already present, let a sampler fill in plausible specular detail, then decode back to linear. Prepare is the first half of the second path.
The author's own docstring spells the four steps out: decode the display transfer curve to scene-linear, push clipped regions above 1.0 with a power-curve extrapolation, compress that back into 0–1 so a VAE will accept it, and emit per-channel normalisation stats the decoder inverts afterwards. Output image is VAE-ready. Output mask is the feathered inpainting mask - the region the model is allowed to make up.
That mask is the whole trick, and it's why clip_mask is a required input: it comes from Clip Detector, and only inside it do highlights get boosted. Everything outside is left alone.
The inputs that matter
clip_mask (MASK) - run Clip Detector first. Same size as the image, or you get a size error at queue time rather than a warning.
compression_ratio (FLOAT, 0–1, default 0.5) - the author's tooltip: "Must match RadianceHDRDecoder. Wire from preset or LoRALoader." This is the soft-knee compression applied to fit the expanded values into a VAE-friendly range, so encoder and decoder have to agree on it. Mismatch it and your highlights come back shifted - not obviously broken, just wrong.
inverse_eotf - the transfer curve of your source. Get this right or everything downstream is off: sRGB for web images, Rec.709 for video stills, Gamma 2.2, or Linear (no-op) if the pixels are already linear.
Then the two taste controls. highlight_boost (default 4, up to 32) is how far above white you push, in stops: 4.0 is 2 stops. boost_gamma (default 1.5) sharpens the ramp into specular peaks. And mask_feather (default 16 px) softens the inpainting edge so the seam doesn't show.
Outputs are image, mask, stats_json (the norm stats the decoder needs) and peak_linear (the estimated scene-linear peak after extrapolation - useful for sanity-checking your boost before you wait on a sampler).
Install
The pack ships 147 visible nodes, GPL-3.0, by FXTD Studios.
- ComfyUI Manager → search Radiance → Install → restart → refresh the browser.
- Manual:
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: run that pip line with python_embeded\python.exe. The requirements are not trivial - OpenEXR, OpenImageIO, opencolorio, transformers, scipy, tifffile - and the pack's install.py re-checks OpenEXR, OpenColorIO, diffusers and accelerate after a Manager install. One real trap: the Comfy Registry currently serves an older release (2.3.3) than the README's 3.5.0, so if you want the current behaviour use Update in Manager or git pull inside the folder. Python 3.10–3.13; on 3.14 there's no OpenEXR wheel and EXR I/O falls back to OpenImageIO, then OpenCV.
Where people get burned
You may simply not need this node. Radiance 3.5.0 retired the latent-space RUDRA decoders - the ones this node's stats_json was written to feed - and the direct pixel model now handles SDR-to-HDR without a VAE at all. Prepare survives for the VAE/sampler/inpaint route; the current default path is Universal.
If you do run it: a mismatch between compression_ratio here and in the decoder is the classic silent failure, and the per-channel stats output exists precisely so you don't have to remember the numbers. Keep mask_feather off zero unless you like hard seams, and check peak_linear before sampling - if your boost is absurd, you'll see it in that number rather than after a five-minute render.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Display-encoded SDR image, decoded to linear with inverse_eotf. | |
| clip_mask | MASK | Clip mask from Clip Detector, same size as image. Highlights are boosted inside it and it becomes the feathered inpainting mask output. | |
| compression_ratio | FLOAT | 0.500–1 | Must match RadianceHDRDecoder. Wire from preset or LoRALoader. |
| inverse_eotfopt | COMBO | sRGB | Transfer curve of the input, decoded to linear before the highlight boost. Linear (no-op) only clamps negatives. |
| highlight_boostopt | FLOAT | 4.01–32 | How bright to push extrapolated highlights in scene-linear. 4.0 = 2 stops above white. Higher = more vivid reconstructed highlights. |
| boost_gammaopt | FLOAT | 1.50.5–4 | Power curve for highlight boost ramp. Higher = sharper specular peaks. |
| mask_featheropt | INT | 160–128 | Gaussian feather radius on the inpainting mask edge (pixels). |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| stats_json | STRING | — |
| peak_linear | FLOAT | — |