ACES 2.0 CAM DRT Development

My hope is that all these dark blue/cyan blue/magenta sharp transitions are reduced once we have the gamut mapping as an approximation with smoothing. There’s many examples in the old ZCAM thread where this is being discussed. Latest CAM DRT is significantly better than the old transfrom but it’s still visible in many images. The lightsaber image is one good example where one can clearly see a sharp line in the background as it transitions from purple to blue/cyan.

From ZCAM thread for example. I think these are pretty much the same issue.

1 Like

I’ve managed to get my interactive cusp plotting working in a Colab.

It is rather slow compared to running it locally, but at least it means anybody can try it out.

It does not include the SDF approximation @priikone showed in meeting #86. Pekka, feel free to add that if you have time, and feel it is useful.

(Note that as mentioned in my write-up of the meeting, the gamma used in the approximation not matching the Nuke implementation was due to different surround settings. This plot uses a gamma value calculated from the surround parameters – set to “Dim” by default – rather than having a gamma slider like the version shown in the meeting.)

To get the plot to run interactively, it seems you need to “Run all” and then possibly “Restart runtime and run all”. I would be grateful if somebody could confirm to me if they can do this successfully.

1 Like

Can confirm, it runs for me after Restart runtime and run all. (Windows 10, Chrome) Slow indeed, but nice to be able to analyze and play with it.

1 Like

Nick, Will also confirm on Windows10 and Google Chrome after Runtime/Restart and run all. Thanks.

I still am wondering about the issue at lower brightness/exposure (banding in gradients at lower levels)(the bands/ lines in the neon of frame 0032 above)(and what was shown last meeting in the Nuke 3D example showing the gamut compression. The banding seems to be in the lower exposure region and none or very little to discern in higher levels.

I do understand the precision and why it should be satisfactory (from the Dolby information showing signal above the noise.) Certainly the 10-bit should be plenty. However, I sill cannot be totally comfortable when the issues seem to be occurring in only the lower exposure range.

If the calculations are 10-bit now and there is no need for negative numbers can the calculations be made using 11-bits and see if the results show any less banding. The 11-bits should be doable with half-float and not bring a major change in computation. And if there is no difference between using 10-bit or 11-bit that could further establish the precision as not an issue. I just can’t shake the feeling that moving along the cusp at low exposures may not have enough precision at certain places causing this jump/banding. I hope my feeling is wrong, but then what is causing this?

We talked about this in the meeting, but I’ll try and show what we think is the issue here.

As you point out, at normal exposure, the gradient appears smooth:

But at 1 stop under, the transition starts for form a V shaped discontinuity in brightness:

If we look at the gradient in Yxy space, we can see there are a mix of in and out of gamut colours, with the cyan drifting outside of the 709 boundry:

Post gamut compressor, we can see those values have been pulled back inside the target gamut. Whilst this does seem for for a bit of a hook shape in 2D, this doesn’t seem to be what’s actually causing the issue:

If we look at this 3D visualisation in JMh space, we can see the same values represented a form that represents the pre and post gamut mapped values. Where the white the line is thin, the pre and post gamut mapped values are the same (all in gamut), but where it’s thicker, we can see the connection between the pre and post gamut mapped values.

The out of gamut cyan section is here:

If we look at it directly from the side, the issue becomes clearer.
The further the gamut compressor is pulling the value is, the more it’s boostin the J value, increasing perceptual brightness.

It also explains why the effect is exposure dependant, as the direction of the focus point varies depending on it’s position relative to the gamut cusp.

This is sweep, the outer dotted line is input JMh
The outside of the white form is post tonemapped - post chroma compression JMh
And the inside of the white form is post gamut compressor:

The same sweep can be seen from the top here:

My half considered reading of this is we perhaps need to be a bit more conservative with how far we deviate from flat with the gamut compressor J focal point. We know we need some at the extreme top nd bottom of the range, but perhaps we should be steering closer to flat in the middle.

For example, the top half this image is fully focused on the cusp
Whilst the bottom is fully focused on the SSTS midpoint, producing a much flatter projection.


I think there is one additional piece, which is that the compression happens along the perceptual hue line, which skews the color to even more cyan in the chromaticity space, making it also appear brighter (that hook in the 2D diagram). @bottosson explained why this happens, which I think is what’s also going on here.

I guess the claim would be that this is perceptually correct, but it sure doesn’t look correct. We get similar looking image with the current DRT with dark blue/cyan transition, but not so extreme, it’s much better.

We can try to improve this by changing the lightness mapping (J projection) in the gamut mapper. But the perceptual hue lines around blue (perceptually accurate or not) will have big impact as well.

1 Like

Pekka and Alex as well, thank you for this information especially the OKlab reflections which seem quite illuminating.

Something else comes to mind in that since it seems we have thus far been looking at a rec.709 target, so will this issue of non-smooth/banding become worse as we then move to targets of P3 and rec.2020? Or might those results be better?

Sent pull request for @alexfry for v29, also available in my fork . It brings the following:

  • Replaces the iterative gamut intersection finder with an approximation to rounded and, optionally, smoothened triangle. Currently the cusp smoothing is set to 0, ie. it’s disabled.

  • Removes the eccentricity factor from Hellwig2022 model. Mostly color rendering doesn’t change, except for highly saturated blue and magenta, making them slightly lighter. Custom primaries were adjusted slightly because of this. This change is done to simplify the model.

  • Changes the default viewing conditions to Dim. Rendering changes a bit.

  • Removes the focusDistanceMin and focusDistanceMax introduced in v28 and brings back the old focusDistance with single value, and sets the value to the old 0.5. Rendering changes a bit because of this.

  • Removes the old tonescales.


v29 Rec.709 inverse:

v28 Rec.709 inverse:

The new gamut intersection has cusp smoothing but it’s disabled by default. The smoothing tries to avoid reducing the gamut. However, there is a small reduction with some colors that shows up when doing gamma 2.4 inverse, but not so much with linear inverse. Following images compare cusp smoothing set to 1.0 and 0.0, in that order:


So does this mean that the only iteration now happens in the init() function to populate the 360 entry look-up of the cusp position against hue? So the gamut intersection is just approximated using the values in this table?

If so, I can start working on a DCTL implementation that has the table from the Blink init() function pre-calculated, and simply declared as an array in the code. Highly unlikely I will have anything by tomorrow’s meeting though!

Yes. But, I think we can later look at approximating the cusp as well. I think the next thing to do is to try and see if we can simplify the lightness mapping so that we could have inverse without iterative solve for the original lightness. Unless people think it’s not a problem to have iteration in the inverse?

I don’t know if it’s relevant to the issue with the DRT in any way, so just in case:
I found a similar sharp transition using RGC on Alexa V3 footage with a defocused face (a chin) in front of a TV that was displaying a logo with magenta-blue solid color. I’m not allowed to post it except for a crop.

When I turn off RGC, the logo on TV changes its color (that feels more correct, but that’s only feeling, not sure if it is actually), and the transition looks smoother.

I made the images about a week ago, but when I found it’s RGC related I decided not to post it (until I watched the latest meeting recording where gamut compression was mentioned), so I’m not sure, but most likely it’s with v28 DRT if it matters in this case at all.


Without RGC:

And here is the circle in linear AP0

Academy’s image set has a perfect test image as well, frame #420.



I’ve updated the LUT repo with v029.

I’ve left v028 in there in parralel to make it easier for people to compare both of them if needed.


Started working up some tooling to visualise the compression vectors better.

In this first visualisation, we see a 0 → 360 h sweep.
Red is post tonemapped JMh
Green is post chromaCompression JMh
Blue is post gamut compressor JMh

Most of the sweep looks good, but it does seem like the chromaCompression around green is pre-compressing the gamut to inside the target gamut, leaving green values on the table.

This graphic uses a fixed h of 140.1 (max green in 709) whilst ramping chromeCompression from 0 → 1


1 Like

That’s nice. I’m not sure what the issue with the green is, though. The chroma compression is pulling all colors into gamut because if we don’t the interior of the gamut isn’t smooth. That’s the main reason for doing that step. Gamut mapper can’t do that. It does pull highly saturated colors less than less saturated colors, but everything is being compressed.

It has two steps. 1. path-to-white/path-to-black which is global hue-independent compression. All colors are compressed the same amount. 2. hue-dependent compression of different levels of colorfulness. Less saturated colors are compressed more than highly saturated colors. Gamut boundary is not considered. Now that it’s possible to get the boundary efficiently (since v29), it perhaps could be considered - somehow.

EDIT: did you use v28 or v29? My plots below are v29.

Here’s example of green with chroma compress set to 0 and 1 (step 2 still happens with it set to 0, but step 1 doesn’t), with gamut mapping:

chroma compress 0:

chroma compress 1:

(ignore the discontinuities in the lines. The input is just HSV hue sweep.)

1 Like

Is the solid green line the actual boundary? Does that mean we can’t quite hit the green primary? Or at least that we need an M value >100 to do so?

Those images are just for illustrative purposes for what happens in the interior of the gamut, they can’t be used to judge the exact limits. The input is not perceptual hue, it’s HSV ramp.

The Python in my Colab and repo, can now show the actual position of the focus point. This makes it clear that the focusM value is unaffected by the source M value, but does vary with source J.

The images below show (at least for this example) by displaying the focus point for a source – JM = (37.0, 62.0) – and then the focus point derived from the compressed values from that source – JM = (42.0, 36.0) – that although the focus point differs, the compression direction is not that different.

I was wondering if it might be possible to modify the calculation for the focus point, so it incorporated source M in the calculation, such that the focus point for a JM value after compression was the same as that for the value before compression. If this were the case, inversion would be simple.

But this is just me thinking aloud. I don’t know if it would even be possible.

(I do realise that I may just be saying “If the compression were trivially invertible, then it would be trivially invertible”!)


I downloaded the CAM_DRT_v029 config during the last meeting and played around the the red_star image again together with an exposure node.

Plus I compared the output to a simple EOTF.
I figured I should have used the P3-config for my iMac when doing a screen-recording?

—> linear_sRGB 0-1 values with gamma 2.2

—> ACESCG 0-1 values over CAM DRT v029 for Rec.709

—> linear-sRGB 0-1 values in ACESCG over CAM DRT v029 for Rec.709

I wonder about the dip in the center of the red star in the waveform.
The displayed values are getting lower in the center of the star.
I could imagine it’s complicated to map the ACEScg red to the display, but a sRGB red?

To illustrate this better, I made a screen recording from within Nuke and a Histogram. I do not understand what is happening, but it looks strange to me that the red channel moves so different from the green and blue channel. The link to the video is here: HiDrive

1 Like

Following on from my previous comment about making the compression vector the same for any point along the line, I wondered if it might be possible to calculate the vector not from the J value of the pixel, but rather work backwards and calculate the vector from the J intersect value, and solve for what J intersect passes through the (M, J) coordinates of the current pixel.

This Desmos graph I hope illustrates the point.

I have not managed to work out the maths for solving for the right J intersect (or even if it is possible). My gut feeling is that it could possibly be rearranged to a quadratic equation, so the solution is given by the quadratic formula.

An iterative solve might be a temporary way to see if this idea is even worth pursuing. Maybe an inverted vector calculation like this will not give an appealing image.

And of course, as has been discussed, there is the trade-off between adding complexity to the forward transform and the possible benefit of an exact inverse.