Creating shapers using basic operators

(Scott Dyer) #1

In the last VWG meeting, we discussed how the halfDomain attribute allows for 1D LUTs where one can specify exactly how each value should be mapped. There was discussion about creating a tool to assist with making such LUTs, since it tends to be confusing for inexperienced people to do.

It was also discussed that we should determine the basic grammar operators that would be needed to manipulate LUTs as shapers. Are there one or more base parametric forms that would allows us to represent the majority of log-lin conversions? There was discussion about being able to use OCIO as a guideline and @doug_walker volunteered to try to add the various log things that we were looking at (“log w/ linear”) and we were also aware that PQ could be problematic with fitting into these forms.

What are the basic operators we need to allow for a parametric process node that will cover the majority of needs for lin-log conversions?

Unique cases that don’t fit the parametric forms or those LUT creators who want to would still be able to use the halfDomain attribute for exact 1D LUT coverage.

Meeting - CLF Spec / Code Review - 2/6/19 9am pst
(doug_walker) #2

JD / Scott,

Following up on my action item from the previous meeting, here is a proposal for adding a Log operator to CLF that could be used for a variety of lin-to-log type transforms.

There are three classes of these transforms that I think we should support. First there is the basic logarithm. OCIO has this and it is regularly used (along with an affine transform) to create shaper transforms when needing to apply linear values to LUTs. Second, there is the classic Cineon type lin-to-log transform and the related Josh Pines type transform (the latter being similar to Cineon but is more fun at parties, also it handles blacks better!). These can be implemented as an affine transform (a multiply and add), followed by a logarithm, followed by another affine transform. Third, there is a Cineon type curve with the addition of a linear section near black. This handles most of the digital camera log curves including Alexa LogC (for typical EIs), Sony SLog3, Panasonic VLog, and Red Log3G10, as well as ACEScct.

So here is a rough proposal for an XML syntax that could represent these three types of log curves.

Basic logarithm examples (base 10 and 2 allowed):

 <Log inBitDepth="16f" outBitDepth="12i" style="log10" />
 <Log inBitDepth="12i" outBitDepth="16f" style="antiLog10" />
 <Log inBitDepth="16f" outBitDepth="12i" style="log2" />
 <Log inBitDepth="12i" outBitDepth="16f" style="antiLog2" />

Cineon style logarithm examples:

<Log inBitDepth="32f" outBitDepth="10i" style="linToLog">
    <LogParams gamma="0.6" refBlack=" 95." refWhite="685." highlight="1.0" shadow="0.0" />
</Log>
<Log inBitDepth="10i" outBitDepth="32f" style="logToLin">
    <LogParams gamma="0.6" refBlack=" 95." refWhite="685." highlight="1.0" shadow="0.0" />
</Log>

If the parameters are different per channel, they may be expressed this way:

  <LogParams channel="R" gamma="0.5" refWhite="681" refBlack="95" highlight="0.9" shadow="0.0045" />
  <LogParams channel="G" gamma="0.6" refWhite="682" refBlack="94" highlight="1" shadow="0.0025" />
  <LogParams channel="B" gamma="0.65" refWhite="683" refBlack="93" highlight="0.8" shadow="0.0035" />

In order to be more in line with OCIO, we should also allow specifying the LogParams as follows:

<Log inBitDepth="32f" outBitDepth="10i" style="linToLog">
    <LogParams linSideSlope="1.0" linSideOffset="0.0" logSideSlope="1.0" logSideOffset="0.0" base="10.0"  />
</Log>

The implied lin-to-log equation is:
y = logSideSlope * log( linSideSlope * x + linSideOffset, base ) + logSideOffset;

(If the traditional refBlack/refWhite/gamma syntax above is used, those parameters may be converted to utilize this same equation too.)

Finally, the Camera Log style transforms would be written as follows:

<Log inBitDepth="32f" outBitDepth="10i" style="cameraLinToLog">
    <LogParams linSideSlope="1.0" linSideOffset="0.0" logSideSlope="1.0" logSideOffset="0.0" linSideBreak="0.0" linearGain="1.0" base="10.0"  />
</Log>
<Log inBitDepth="10i" outBitDepth="32f" style="cameraLogToLin">
    <LogParams linSideSlope="1.0" linSideOffset="0.0" logSideSlope="1.0" logSideOffset="0.0" linSideBreak="0.0" linearGain="1.0" base="10.0"  />
</Log>

The implied lin-to-log equation is:

if (x <= linSideBreak)
  y = x * linearGain + k;
else
  y = logSideSlope * log( linSideSlope * x + linSideOffset, base ) + logSideOffset;

And where k is calculated so the two pieces are continuous at the break point. The linearGain parameter is optional. If it is missing, the default is to calculate it so that the two pieces are also continuous in slope (e.g., like ACEScct).

There are some details to be filled in such as more detailed pseudo-code and the details around bit-depth, and I could add these in a more formal proposal later if there is interest in proceeding. Also, there are some other log-like curves such as PQ and HLG that are not covered here. That said, hopefully the above will at least serve as a good strawman to continue the discussion.

Doug

p.s. If you receive this as an email and the formatting is difficult to read, please look at the message on ACESCentral. Thx

1 Like
(Dennis Adams) #3

Scott, et. al.,

I suggested this in the OpenColorIO Slack since they are implementing half-domain 1D LUTs, and they suggested I also mention it in ACESCentral, so I’m putting it here. Is it a good location to mention it?

I’ve been thinking about the awesomeness of 16f / half-float indexed LUTs and they bring a lot to the table, especially for conversions from linear. However, I believe they are fixed size, and so quite large (64K elements * sizeof(float) = 262K bytes) and on a GPU with limited memory, they could start to add up and compete with other large textures. By comparison, for our dozen currently shipping LUTs that take linear input (pre-shaped with a lg2 AllocationTransform) we use a mixture of 8K, 16K, 32K, and 64K element LUTs. It would be nice to have the same option for float-indexed LUTs. Is it possible to right-shift off the lower bits of the 16f by 1 to 4 (or more) places and get 32K, 16K, 8K, and 4K element LUTs? Then the shifted-off bits could be used to interpolate between output values, similar to how integer-indexed LUTs work today. To me, this would be the best of both worlds.

Doug Walker replied that his OCIOv2 half-domain Lut1D apply functions they recently added already interpolate in the table (assuming the inputs are 32f), so it would be a fairly small amount of code to modify there in order to do what I propose.

Will you consider it as well?

1 Like
(JD Vandenberg) #4

I would definitely consider it. I hope you can join the next call (not scheduled as of now) so we can discuss it.

(Nick Shaw) #5

One camera log which wouldn’t be fully covered by this form is Canon Log 3, due to the three part nature of the curve, mirroring about linear zero.

How often it would actually be problematic to treat the curve as if the linear segment continued for ever into negative values, rather than changing back to an inverted log curve, I am not sure.

1 Like
(Greg Cotten) #6

I’m all for finding clever optimizations, but does a 0.5MB (65536 entries x sizeof(half) 2 bytes per channel x 4 channels for simplicity’s sake) texture really break the bank? A 1920x1080 16-bit float texture is 16MB.

(Dennis Adams) #7

Maybe not. It’s probably half that size (65536 x sizeof(float)) but 200 of them equals 5% of a 1 GB GPU. It just seems strange to go from LUT size flexibility to fixed-size.

(Walter Arrighetti) #8

However, I don’t think we should “fix” a size of any half-float 1D LUT in the CLF Specifications. As GPU power ramps up, so could the used bit-depths and, therefore, the LUT sizes.

1 Like
(Nick Shaw) #9

And in the majority of cases it is only going to be one channel in the table, as conversions such as lin to log are identical for all channels.

(Greg Cotten) #10

Walter,

I think we’re all talking about half-domain 1D LUTs, which have a fixed size of 65536 points.

(Walter Arrighetti) #11

Hi Greg,
I see your argument, but that’s exactly what I’m saying: since that is for now a hard technical constrain due to both 16bpc color-depth on one side and on GPU technology based on 16b half-floats on the other, I don’t think that, today, anyone would implement 1D LUTs with more than 64ki entries.

On the other hand, should either of the above improve in the near or distant future, I don’t think having contrained that on CLF spec would be of any help. This is my point.

(James Eggleton) #12

Canon also retroactively applied the mirroring feature to the Canon Log 1 and 2 curves.
In principle the mirroring feature only requires one more optional parameter - linSideMirror. I think it would be worthwhile supporting the pure log curve, the linear cut extension, and the mirror extension:

  1. Pure log: (linSideSlope, linSideOffset, logSideSlope, logSideOffset, base)
  2. with optional cut (linSideBreak, linearGain)
  3. with optional mirror (linSideMirror)

Note that it’s possible to refactor the log segment so that the base is implicit (most obvious choices being 2, 10, E), and one of the linSideX or logSideX variables can be eliminated. Such a canonical form only requires 3 parameters to describe the log segment.

e.g. log->lin functions:
RedLog3G10: v<0.0 ? (v - 0.15192689) / 15.192689 : exp((v - 0.49186296) / 0.097404435) - 0.0164112704
SonyLog1_8bit: v<0.088510854 ? (v - 0.088510854) / 4.7712418 : exp((v - 0.63506099) / 0.16138908) - 0.0338256
SonyLog1_12bit: v<0.08818664 ? (v - 0.08818664) / 4.7537648 : exp((v - 0.6327348) / 0.16079791) - 0.0338256
SonyLog3: v<0.16736099 ? (v - 0.092864125) / 6.6219437 : exp((v - 0.5949227) / 0.11101467) - 0.01

1 Like