ACES 2.0 Tonescale
The curve that decides what 'correct' looks like
- image
- image
- curve_info
What it is
The ACES 2.0 tonescale operator, on its own, with the knobs exposed. Not a full output transform - no gamut mapping, no primaries conversion, no encoding. Just the curve that turns scene-linear luminance into display-referred luminance using the ACES 2.0 reference form.
Why would you want a piece of an output transform as a standalone node? Because it's the part that does all the visible work. If you're building a custom display chain, or you've got an HDR image and you want the ACES 2.0 rolloff without the rest of the pipeline's opinions, this is the only thing you need. It's also the fastest way to see what the ACES 2.0 curve does before you commit a whole graph to it.
It's worth being clear about what it isn't: this is not a "make my image look nicer" tone mapper. It's a specification. Reaching for it to fix a washed-out generation is like using a torque wrench as a hammer.
The mechanism
The curve is a Hill / Michaelis-Menten rational function, which is the analytical form the Academy's reference implementation uses - a smooth S with no hard clip anywhere except the final normalisation. It's parameterised to put 18% scene grey at 10% of peak display output, which is the anchor that makes the whole thing land where a colourist expects it. The curve_info string it outputs includes the computed K constant so you can see it actually solved for your settings.
Two modes, and the difference is real:
luminance_preserving(default) tone-maps luma and then scales RGB by the same factor. Hue and saturation survive. This is the one to use unless you have a reason.per_channelruns the curve independently on R, G and B. That can dodge a colour cast on difficult footage but it will shift hues, because three separately-compressed channels no longer sum to the same colour.
Inputs you'll actually touch
image - scene-linear ACEScg (AP1). Same rule as every ACES node in this pack: if you hand it a display-encoded image, you'll get mud.
peak_nits - the display peak in cd/m². 100 is SDR, 1000/2000/4000 are the HDR tiers. This is the single most important setting, because it defines what "white" means for everything the curve does.
mode - as above.
Then the three optional parameters, which are the interesting ones:
contrast_g(default 1.15) is the contrast exponent. The ACES 2.0 reference value is 1.15; lower flattens, higher snaps. Note the Full output transform node ties this to thesurroundchoice - Dark 1.10, Dim 1.15, Average 1.20 - so if you're matching a look, this is the dial that decides it.grey_target(default 0.10) is where 18% grey lands as a fraction of peak. Standard is 10 nits out of 100 for SDR. Raise it and mid-tones lift.toe_scene(default 0.04) is the scene luminance below which a linear toe kicks in, which keeps deep shadows from getting lifted by the curve's gamma. If your blacks look foggy, this is why.
Outputs
image and curve_info. Note that the output is normalised and clamped to 0–1 - this is a display-referred result, not scene-linear you can keep grading. It belongs at the end of a chain. If you need to keep working, you're looking for a different node.
curve_info is a one-line summary (peak, g, grey target, toe, K). Useful for logging; not an image.
Install
ComfyUI Manager → search Radiance → install → restart → refresh the browser. Manually:
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: use the bundled python_embeded\python.exe for pip. Dependencies are light for this node - torch and numpy do the work; the heavy libraries in requirements.txt (OpenEXR, OpenImageIO, opencolorio, diffusers) are for the file I/O, colour management and model-driven nodes elsewhere in the pack. No model download is involved here.
Gotchas
- Feeding it display-encoded input. It'll run, and it'll look wrong. Linearise first.
- Expecting scene-linear out. It clamps to 0–1. Anything downstream that treats values above 1.0 as valid will be quietly misled.
- Stacking it with a full output transform. You've tone-mapped twice; highlights will look beige.
- Reading
per_channelas "more accurate". It's not - it's a different trade, and on colourful footage it shifts hue. The tooltip says as much. - Forgetting
toe_sceneexists. Deep-shadow lift is the most common complaint about any tone curve, and this parameter is the fix.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Scene-linear ACEScg (AP1) image batch. | |
| peak_nits | FLOAT | 10048–10000 | Display peak luminance in cd/m². 100 = SDR, 1000/2000/4000 = HDR. |
| mode | COMBO | luminance_preserving | luminance_preserving: tone-map luma then scale RGB — preserves hue/saturation. per_channel: apply curve independently to R, G, B — may introduce hue shifts but avoids colour casts. |
| contrast_gopt | FLOAT | 1.150.8–1.6 | Contrast exponent g. ACES 2.0 reference = 1.15. |
| grey_targetopt | FLOAT | 0.1000.05–0.3 | Display grey target as fraction of peak. Standard = 0.10 (10 nits of 100 SDR). |
| toe_sceneopt | FLOAT | 0.0400.005–0.2 | Scene luminance below which a linear toe is applied. Prevents gamma lift in deep shadows. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| curve_info | STRING | — |