Closed eonadler closed 5 years ago
Without getting to the bottom of the matter, let me first suggest to vectorize the code. Python loops are awefully slow, so you never want to use them. This
import numpy as np
import colorio
colorspace = colorio.SrgbLinear()
colorspace2 = colorio.JzAzBz()
X = np.linspace(0.0, 1.0, 256)
x, y, z = np.meshgrid(X, X, X)
pts = np.array([x, y, z])
jzazbz_test = colorspace2.from_xyz100(colorspace.to_xyz100(colorspace.from_srgb1(pts)))
print("min Jz={}".format(np.min(jzazbz_test[0])))
print("max Jz={}".format(np.max(jzazbz_test[0])))
print("min Az={}".format(np.min(jzazbz_test[1])))
print("max Az={}".format(np.max(jzazbz_test[1])))
print("min Bz={}".format(np.min(jzazbz_test[2])))
print("max Bz={}".format(np.max(jzazbz_test[2])))
is about 70 times faster.
Next, a picture of the SRGB gamut, created with
import colorio
colorio.show_srgb_gamut(colorio.JzAzBz(), "out.vtu", n=100)
Okay, this conincides with what you've measured.
In the article, they're referring to the Rec.2020 cube which is larger than sRGB, so there's one difference. The difference in the scales however seems too big to explain that. Perhaps I could add a function that returns the Rec.2020 gamut in any color space to confirm.
It'd be awesome if you could check the values of colorio against their MATLAB :frowning_face: reference implementation at https://figshare.com/articles/JzAzBz_m/5016299.
Okay now, here's the Rec.2020 (HDR) gamut. Doesn't look right either, so need to confirm with the MATLAB code.
The “XYZ” coordinates they are using are counted in nits, and range from 0 to 10000. It’s not clear if the best way to use the color space is to scale white to 10000, or instead to leave the maximum at whatever the actual luminance of the display is.
If you want to compare against another implementation I believe https://observablehq.com/@jrus/jzazbz is correct w/r/t the paper.
I've just checked with the Ebner-Fairchild and Hung-Berns plots of JzAzBz, and they look alright in the current implementation. The Rec2020 plot looks wrong.
When changing to nits, Ebner-Fairchild and Hung-Berns are out of whack while the SRGB (Rec. 709) blob looks like what they have as Rec 2020.
Something is wrong.
I've tried contacting the authors over a year ago, but only got back to M. Safdar who referred me to M.R. Luo who never replied.
I don’t know what specifically you are plotting or what “look alright” or “out of whack” mean.
I would recommend you try scaling white to 10,000 before converting to Jzazbz in every one of your desired plots and see what happens.
Okay, I've added some images to make the situation clearer:
On master: Ebner-Fairchild, Hung-Berns, and Munsell data seem okay.
With the suggested change, everything seems wrong, but the gamut resembles more closely what's given in the article.
article | master | with change |
---|---|---|
article | master | with change |
---|---|---|
article | master | with change |
---|---|---|
article | master | with change |
---|---|---|
Thank you both for your insightful comments.
My group is interested in JzAzBz because it is perceptually uniform under a Euclidean metric. So I'm not concerned about the specific coordinate ranges, even if they are different with respect to the paper, if they just reflect linear transformations of the coordinate system.
So, given that the change suggested by @jrus is a rescaling, do you think the coordinate range issue is just a matter of convenience? To me, the Rec. 2020 gamut in @nschloe 's final plot just looks rotated with respect to master, which isn't an issue for my use case.
I simply multiplied the JzAzBz output by 598.1807563064359 which is 100 divided by the Jz component corresponding to RGB triplet (255,255,255). I find the resulting numbers nice to compare with the output of the LAB/CAM02/CAM16:
RGB | LAB | CAM02UCS | CAM16UCS | JZAZBZ-SCALED
0 0 0 | 0.0000 0.0000 0.0000 | 0.0000 0.0000 0.0000 | 0.0000 0.0000 0.0000 | 0.0000 0.0000 0.0000
0 0 255 | 32.3026 79.2022 -107.8533 | 31.2239 -8.3659 -39.1590 | 36.2552 8.5896 -37.8669 | 41.4225 -18.4584 -93.4991
0 255 0 | 87.7370 -86.1777 83.1908 | 87.0057 -32.2317 30.4331 | 86.5506 -35.4870 27.5038 | 78.8833 -55.5455 60.1641
0 255 255 | 91.1165 -48.0717 -14.1263 | 90.2576 -28.4179 -9.2366 | 90.6384 -28.5505 -8.5056 | 86.2685 -36.2323 -15.9889
255 0 0 | 53.2329 80.0906 67.2008 | 60.0487 38.6798 24.3153 | 59.1734 40.8207 21.1519 | 59.1962 59.6086 54.5743
255 0 255 | 60.3199 98.2421 -60.8341 | 66.6790 36.4558 -21.1075 | 67.3853 40.2178 -19.1223 | 70.3016 56.2463 -45.9072
255 255 0 | 97.1382 -21.5641 94.4861 | 97.4097 -10.3133 35.6083 | 96.8014 -12.7831 33.0375 | 94.1758 -14.4070 68.9318
255 255 255 | 100.0000 0.0000 0.0000 | 100.0000 -1.9170 -1.1378 | 100.0000 -1.8976 -1.0728 | 100.0000 -0.0802 -0.0493
there seems to be an error in the reverse part of the model
y = (y_ + (self.g - 1) * x) / self.g
y = ((z_ * self.b - z_ + x_) * self.g + (y_ - z_) * self.b + z_ - x_) / (self.b * self.g)
i couldn't get the reverse transform to line up otherwise (arduous)
@daT4v1s Could you open a new issue for that?
@daT4v1s Forget about it, that's a non-issue. Your alternative expression is exactly the same as the original one. Open an issue if there still is something wrong.
I'm closing this, since based on the helpful diagnostic plots from @nschloe, I believe that the normalization/range issue is purely cosmetic, and does not affect the relations between JzAzBz coordinates.
@eonadler It'd be great to find out what's wrong; there seems something fishy about the Rec.2020 plots in the article.
normalization/range issue is purely cosmetic, and does not affect the relations between JzAzBz coordinates
This is a clearly false statement. But do what you like.
It would be nice to hear back from the authors of the paper, since their intended meaning is not very clear in the text.
Haven't fully read the thread yet but:
It’s not clear if the best way to use the color space is to scale white to 10000, or instead to leave the maximum at whatever the actual luminance of the display is.
Keep in mind that PQ is an absolute EOTF, there is no free parameter for the display peak luminance, it has been modeled with Barten (1999) CSF thus there is nothing relative about it.
There should be no reason for the slightly different constants used by Safdar et al. to modify this postulate. The resulting curve is not very different from PQ:
I don't think it was a good idea to change it because the resulting curve does not agree with the CSF anymore.
There should be no reason for the slightly different constants used by Safdar et al. to modify this postulate. The resulting curve is not very different from PQ:
The curve in Safdar & al.’s paper has a somewhat different purpose from the PQ curve as far as I can tell (they just used that one as a starting point for convenience). The parameters they changed seem like they were optimized so that the full resulting color model would best match their experimental data.
Safdar & al. also have another paper https://doi.org/10.2352/issn.2169-2629.2018.26.96 but I cannot get access to a copy to read it.
No it is the same, i.e. lightness prediction, they had to slightly modify it so that it fits best the data.
The “XYZ” coordinates they are using are counted in nits, and range from 0 to 10000. It’s not clear if the best way to use the color space is to scale white to 10000, or instead to leave the maximum at whatever the actual luminance of the display is.
I noticed that your implementation of Jzazbz scales sRGB white to 10,000 cd/m² which is very far from the correct value! Which explains why the results I got from your implementation bear no relation to the results from running the reference Safdar et al Matlab code.
Jzazbz uses a (slightly modified) Dolby PQ transfer function, which has enormous headroom for highlights. There is currently only one, research, display in the world which can actually produce a 10,000 cd/m² highlight (and then, only on a small part of the screen and only for a limited time). Commercial HDR grading monitors have a peak small-area luminance of 4,000or 2,000 cd/m². Commercial HDR TV conform to the VESA DisplayHDR standards, the most stringent grade of which requires a minimum of 1,400 cd/m² for a short-duration, 10% Center Patch test. Meanwhile the sustained, full-screen brightness test requires only 900 cd/m² (again, at the highest grade; the lowest grade requires only 320 cd/m².
Since the broadcast TV world has been producing HDR content for some time and has considered mixture of SDR and HDR content and grading monitors, there are already standards and informative reports which define how to map SDR (paper or media or diffuse reflector) white onto PQ-encoded HDR.
Rep. ITU-R BT.2390-8 section 5.3.1 describes a peak luminance for SDR (from BT.709 and BT.1886) of 100 cd/m² compared to the PQ peak of 10,000 cd/m².
Meanwhile Dolby, in Reference Level Guidelines for PQ (BT.2100), working from the PQ value of 0.34 for an 18% grey card, state that diffuse white at 0.54 PQ value corresponding to a luminance of 140 cd/m².
I find the Dolby analysis more detailed and convincing, and thus suggest that all SDR color spaces (sRGB, AdbeRGB, Display P3, etc) have their peak white, which is a full-screen long duration diffuse white, mapped to 140 cd/m² when converting into PQ-based colorspaces like BT.2100 (Bt.2020 primaries and PQ transfer curve) or Jzazbz.
Mapping it to 10,000 means you are asking for a blinding white which cannot be displayed. It also means you have zero headroom for specular highlights, defeating the entire point of HDR in general and PQ in particular.
@svgeesus Thanks for the thorough analysis. Could you run some "random" numbers through the upstream MATLAB script and post them here? I'd love to make sure that colorio gives the same output.
Edit: I ran the scripts with Octave and colorio.JzAzBz() returns the same results.
@svgeesus You’ll notice there is a line in there:
display_white_luminance = 10000
Which can trivially be changed (and which I have fiddled around in various experimenting; a previous published version of this notebook had this set to a value of 200
, and I have reset it to that). This only has an effect on my inessential added srgb_to_xyz_abs
function, and is outside the scope of the reference implementation. I changed it as part of a local experiment and never changed it back.
results I got from your implementation bear no relation to the results from running the reference Safdar et al Matlab code.
I would hope not. The implementation of the Jzazbz model and its inverse (from XYZ coordinates) should be exactly identical to the paper / Matlab / etc. If it is not, please tell me. The Matlab code provided by Safdar & al. does not provide any method at all for obtaining XYZ inputs, but if you put the same XYZ inputs into the Matlab code and into my Javascript you should get identical results.
What was not clear to me is precisely how Safdar & al. expect their model to be scaled for non-HDR displays, and how their data fitting of existing colorimetric data was handled in construction of their model. I found their paper quite unclear/sparse about this general topic, with a lot of details of their data fitting left implicit/underspecified.
If you set this parameter to a value of 140
or 200
, it’s not clear to me that the resulting gamut in a/b dimensions for an SDR sRGB display actually ends up particularly perceptually uniform. Go ahead and fiddle with it for yourself. For a couple particular uses I had in trying to use this tool to construct some color schemes in my own other experiments, I had better results when setting this to 10000
(it’s possible Jzazbz is just the wrong tool overall for my uses), but didn’t really mean to imply that it should be set that way for general use, and didn’t even intend to leave it set that way in a published notebook.
Jzazbz uses a (slightly modified) Dolby PQ transfer function
Dramatically changing the value of the exponent applied to the inputs makes a pretty big difference. But then they use this as just one step among several in their (inverse) model: the most important other one is a rational function applied to lightness. The result is that the shape is substantially different:
https://www.desmos.com/calculator/pu7ylakohj
P.S. if you don’t directly ping someone by name in a github discussion on someone else’s project, github doesn’t make a particularly obvious notification about it.
@svgeesus
results I got from your implementation bear no relation to the results from running the reference Safdar et al Matlab code.
I just double checked, and for every input I have tried results match to like 14 digits.
Here’s an example screenshot showing Matlab and Javascript:
Just faced to the same problem. Found another implementation where white luminance parameter is 1
https://github.com/colour-science/colour/blob/develop/colour/models/jzazbz.py#L436
We have encountered the following issue, which has been reproduced using a few independent RGB --> JzAzBz implementations (including colorio and https://github.com/quag/JzAzBz/blob/master/python3/srgb255ToJzAzBz.py) ---
The JzAzBz paper (https://doi.org/10.1364/OE.25.015131) seems to suggest that the coordinates should span roughly Jz in (0,1), Az in (-0.5, 0.5), Bz in (-0.5,0.5). However, when we explicitly map all RGB tuples in (0,255) x (0,255) x (0,255) to JzAzBz as follows, we find that (Jz, Az, Bz) span (0,0.167), (-0.09,1.09), (-0.156,0.115).
So we're wondering if the original paper renormalized their coordinates, or if there's a mistake on our end.
Extremely simplified code that demonstrates the basic issue: