About ST2084 and PQ Implementation. #712
-
The current implementation of the ST2084 EOTF and OETF doesn't seem to behave correctly (at least in the applications I'm aware of). A true PQ curve is an absolute cv-nit scale. The normalized CVs of a few different nit levels are: 100 nit = 0.5081 Here's Steve Shaw's write-up on the matter which further backs up my concerns: https://www.lightillusion.com/uhdtv.html This behavior is how reference displays should work (notwithstanding HDR metadata and consumer-grade secret-sauce), and I can confirm is the way the Dolby Pulsar, Sony x300, and x550 all work. When fed RGB(.5081, .5081, .5081) on all three displays set to D65 ST2084, you should measure 100 nits. If you feed a linear ramp to all the displays, you will see a hard clip of all values above the associated code value for that display's peak luminance. With that said, in order to take an RGB value and convert it to a an XYZ value, such as trying to measure a display's accuracy to tracking a P3 D65 ST2084 colorspace, the transfer function should simply linearize a normalized RGB value, scale it by the constant 10000, hard clip at the peak luminance of the display, scale it back down to the normalized code value, and return that value. This value can then be used in the RGB_to_XYZ function to calculate target XYZ values. Now I know this may be specific to calibrating displays, and I am still fairly new to this package, so I'm aware there are probably many other applications. But the current implementation does a scale by the L_p input, which should really only be used as a clipping point. Even if not clipping the values when converting a normalized CV with the PQ curve, the final nits level should always be scaled by 10000. I'd be curious to see other applications of the PQ curve in which doing a relative scale necessary or accurate, as I am not familiar with any. I'd love to hear some comments. |
Beta Was this translation helpful? Give feedback.
Replies: 15 comments
-
Hi @codycuellar, I'm not sure to follow you exactly, here is what I get trying to match your code values / output: import colour
colour.models.oetf_ST2084([100, 600, 1000, 4000, 10000])
# array([ 0.50807842, 0.69629409, 0.7518271 , 0.90257239, 1. ]) And the next above figure from Steve Shaw: import numpy as np
np.round(colour.models.oetf_ST2084([100, 400, 1000, 2000, 5000, 10000]) * 1023).astype(np.int_)
# array([ 520, 668, 769, 846, 948, 1023]) As you can see, we are bang on, so maybe I misunderstood something, which could be because I'm at work and very busy atm :) |
Beta Was this translation helpful? Give feedback.
-
Yes, I was confused too. Colour simply implements the transfer functions per spec (with the PQ EOTF returning absolute cd/m^2). Any scaling or clamping is up to the user. In general Colour doesn't clamp. |
Beta Was this translation helpful? Give feedback.
-
The clamping isn't really the problem. That would just be a nice option, but can easily be done by the user. I'm specifically referring to the Peak luminance parameter of the function which I'm not understanding. >>> colour.models.oetf_ST2084([100, 600, 1000, 4000, 10000], L_p=1000)
array([ 0.7518271 , 0.94602856, 1. , 1.14043269, 1.22712711]) Here when you specify a peak luminance, the results are no longer absolute, they are scaled relative to the input peak luminance. But without implementing clipping, the L_p parameter doesn't make sense to me why it would be there. Does this make sense? |
Beta Was this translation helpful? Give feedback.
-
Ahhh right, I get it, well the |
Beta Was this translation helpful? Give feedback.
-
Gotcha, that makes sense, then perhaps just the documentation is slightly misleading?
Since the display peak luminance will only affect clipping point, then it doesn't behave as documented. What do you think? The second thing I pointed out on Gitter is the return value. I've explored some of the other transfer functions and most of them appear to take an encoded normalized float, and return a linear float. I suppose as long as it's documented what is returned, it isn't really a big issue, but it seems for the sake of consistency, the transfer functions should return the linearized rgb and let the user do the scaling, or use a parameter to enable/disable it. |
Beta Was this translation helpful? Give feedback.
-
SMPTE ST 2084:2014 says:
and further away:
So this is really imaging system / display peak Luminance we are talking about here. It is not very different to ACES theoretical display in that sense. Good point about the normalisation! I changed a few ones this past week but those were mostly transfer functions working in integer domain by default, e.g. RIMM-ROMM, DCDM, etc... I did not think about PQ though. @nick-shaw : What is your take on this one? |
Beta Was this translation helpful? Give feedback.
-
I suppose the confusion is that e.g. for Might it be clearer to say "system peak luminance" or something similar? As in 10,000 cd/m² is the peak luminance of the PQ "system". I think it's the word "display" which could lead people to think it means the actual display being used. Obviously you can get a normalised (in the sense @codycuellar means) output from My own code (from before I found Colour) returns a value normalised so 100 cd/m² is represented by 1.0. But I'm not sure if that's any more logical than anything else! |
Beta Was this translation helpful? Give feedback.
-
I'd avoid the 100cd/m^2 scaling. it is either normalised or absolute, if you want something custom, pass in a parameter. My thought would be stick with absolute by default. This is a little confusing as Rec 2100 suggestions for the floating point representation... Normalization for display-referred signals Normalization for scene-referred signals PQ is absolute display referred in my book. Kevin |
Beta Was this translation helpful? Give feedback.
-
I read the table in BT.2100 as referring not specifically to PQ, but to float HDR in general. So because HLG is notionally scene referred, they are defining both scene referred and display referred float codings for HDR, not just display referred for PQ. |
Beta Was this translation helpful? Give feedback.
-
Some good points here. @nick-shaw, I think you hit it on the head, perhaps I was the only one confused but the term display peak luminance, but I am trying to actually calculate XYZ values from pq displays at various peak luminance levels, so maybe something like you suggested 'system peak luminance' would make more sense for clarity. Another thing that makes it a little bit grey-area is between the two SMPTE documents I found, from 2012 and 2014. They each have different constant names and are calculating different results. The 2012 document shows multiplying by the L constant 10000 to calculate the absolute Y value, whereas the 2014 document shows only calculating the linearized RGB float, which is is written as L, so there seems to be a little inconsistency in the publishing. Anyway, I guess I would just expect the 10,000 value to be stored in the |
Beta Was this translation helpful? Give feedback.
-
Playing devil's advocate, one argument for normalisation would be that because the PQ EOTF is applied to the three channels, the output is not actually absolute cd/m^2.
That is not "1000nits of red". It is "the red value which would produce 1000nits of white if both the other channels had the same value"! In BT.2020, that triple would produce a luminance of 262.7 cd/m^2. |
Beta Was this translation helpful? Give feedback.
-
I'm following, but for the sake of documentation for novices (and playing Devil's Advocate), it might be good to highlight how you came up with 262.7 cd/m^2! :) |
Beta Was this translation helpful? Give feedback.
-
>>> RGB_to_XYZ_matrix = colour.BT2020_COLOURSPACE.RGB_to_XYZ_matrix >>> colour.dot_vector(RGB_to_XYZ_matrix, [1000, 0, 0]) array([ 636.9580483 , 262.70021201, 0. ]) So Y = 262.7 (the second component of XYZ) |
Beta Was this translation helpful? Give feedback.
-
Thanks! :) |
Beta Was this translation helpful? Give feedback.
Hi @codycuellar,
I'm not sure to follow you exactly, here is what I get trying to match your code values / output:
And the next above figure from Steve Shaw:
As you can see, we are bang on, so maybe I misunderstood something, which could be because I'm at work and very busy atm :)