colour: [BUG]: Numerical precision issues with CIECAM02 and CIECAM16 for L_A = 0 and Y_b = 0

Description

When L_A = 0 and/or Y_b = 0, CIECAM02 and CIECAM16 models give incorrect results

Code for Reproduction

import colour
import pandas as pd
sRGB = [
    (0, 0, 0), # black
    (0, 1, 0), # green
    (1, 1, 1)  # white
]
cam = colour.convert(sRGB, 'sRGB', 'CIECAM16',
    XYZ_w=colour.TVS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]/100,
    L_A=0, Y_b=20,
)
pd.DataFrame(cam, columns=['J', 'C', 'h', 's', 'Q', 'M', 'H', 'HC'])
C:\Users\yy51\AppData\Local\Programs\Miniconda3\Lib\site-packages\colour\algebra\common.py:488: RuntimeWarning: divide by zero encountered in power
  a_p = np.sign(a) * np.abs(a) ** p
C:\Users\yy51\AppData\Local\Programs\Miniconda3\Lib\site-packages\colour\algebra\common.py:488: RuntimeWarning: invalid value encountered in scalar multiply
  a_p = np.sign(a) * np.abs(a) ** p
     J             C    h    s    Q    M         H  HC
0  1.0  3.446651e-14  0.5  0.0  0.0  0.0  0.561821 NaN
1  1.0  3.446651e-14  0.5  0.0  0.0  0.0  0.561821 NaN
2  1.0  3.446651e-14  0.5  0.0  0.0  0.0  0.561821 NaN

All outputs are identical no matter the input. Looking a the output at L_A = 1e-30 suggests J, h, H should reach a limit, only C, s, Q, M should go to zero.

cam = colour.convert(sRGB, 'sRGB', 'CIECAM16',
    XYZ_w=colour.TVS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]/100,
    L_A=1e-30, Y_b=20,
)
pd.DataFrame(cam, columns=['J', 'C', 'h', 's', 'Q', 'M', 'H', 'HC'])
              J             C         h             s             Q             M         H  HC
0  8.734361e-08  1.018622e-17  0.500000  3.855345e-07  2.167138e-12  3.221165e-25  0.561821 NaN
1  7.875197e-01  3.343898e-10  0.395617  4.031120e-05  6.507311e-09  1.057433e-17  0.443577 NaN
2  1.000007e+00  1.354441e-11  0.583176  7.642647e-06  7.332842e-09  4.283117e-19  0.666321 NaN

Similarly for Y_b=0

cam = colour.convert(sRGB, 'sRGB', 'CIECAM16',
    XYZ_w=colour.TVS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]/100,
    L_A=64*0.2/np.pi, Y_b=0,
)
pd.DataFrame(cam, columns=['J', 'C', 'h', 's', 'Q', 'M', 'H', 'HC'])
     J    C         h    s    Q    M         H  HC
0  0.0  0.0  0.500000  0.0  0.0  0.0  0.561821 NaN
1  0.0  0.0  0.395067  0.0  0.0  0.0  0.443016 NaN
2  0.0  0.0  0.583308  0.0  0.0  0.0  0.666476 NaN

Testing Y_b=1e-30 suggests, J, h, H should reach a limit, s should go to zero, and C, Q, M should go to infinity.

cam = colour.convert(sRGB, 'sRGB', 'CIECAM16',
    XYZ_w=colour.TVS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]/100,
    L_A=64*0.2/np.pi, Y_b=1e-30,
)
pd.DataFrame(cam, columns=['J', 'C', 'h', 's', 'Q', 'M', 'H', 'HC'])
              J             C         h             s             Q             M         H  HC
0  9.187890e-19  1.147554e-17  0.500000  6.667251e-08  1.866260e-03  8.295944e-18  0.561821 NaN
1  8.352566e-01  3.861502e+05  0.395067  3.960840e-01  1.779403e+06  2.791572e+05  0.443016 NaN
2  1.000007e+00  1.070548e+04  0.583308  6.304728e-02  1.947000e+06  7.739247e+03  0.666476 NaN

Environment Information

===============================================================================
*                                                                             *
*   Interpreter :                                                             *
*       python : 3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023,    *
*   13:26:23) [MSC v.1916 64 bit (AMD64)]                                     *
*                                                                             *
*   colour-science.org :                                                      *
*       colour : 0.4.3                                                        *
*                                                                             *
*   Runtime :                                                                 *
*       imageio : 2.31.4                                                      *
*       matplotlib : 3.8.0                                                    *
*       networkx : 3.1                                                        *
*       numpy : 1.25.2                                                        *
*       pandas : 2.1.1                                                        *
*       scipy : 1.11.3                                                        *
*       tqdm : 4.65.0                                                         *
*                                                                             *
===============================================================================

defaultdict(dict,
            {'Interpreter': {'python': '3.11.5 | packaged by Anaconda, Inc. | (main, Sep 11 2023, 13:26:23) [MSC v.1916 64 bit (AMD64)]'},
             'colour-science.org': {'colour': '0.4.3'},
             'Runtime': {'imageio': '2.31.4',
              'matplotlib': '3.8.0',
              'networkx': '3.1',
              'numpy': '1.25.2',
              'pandas': '2.1.1',
              'scipy': '1.11.3',
              'tqdm': '4.65.0'}})

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments

Sorry one last comment here. “Not an exact science” is one of my least favorite phrases to here about color science. Actually, it is an exact science modeling phenomenon that have a lot of noise or a lot of variance and a lot of independent variables. The methods of psychophysics are objective and based on centuries of scientific work. Work that also founded the fields of psychology, neuroscience, and many aspects of neurobiology. All imaging systems were first conceived relative to color science. It’s just a complicated and small (number of researchers) field and filled with 1000x as many authors writing nonsense about color than there are researchers. Almost like the amount of noise in the field is similar to the underlying topic. 😃

Small pet peeve. 😁

Hi @mesvam

In order to understand the model’s instability for these inputs / outputs it’s important to understand what they are modeling. If you aren’t familiar with it I highly recommend Mark Fairchild’s “Color Appearance Models” 3rd ed.

Let’s get into the rabbit hole a bit…

First L_A and Y_b are two ways of controlling the same underlying parameter, what is the luminance of J = 100? Or what is often called “diffuse white”. “J” is the correlate of “lightness”, the same as L in CIELAB. A white piece of paper with no fluorescent whiteners has a “Lightness” of about 100. A grey patch that reflects about 20% of all light has a J value of around 50. This is due to the non-linear response of the eye.

So to transform from radiometric measurements, we need an anchor point. In the 70s and 80s when R.W.G. Hunt was first doing color appearance modeling at Kodak he used the grey surrounding card as the reference point. Y_b denotes the reflectance % of the surrounding card, typically 20%. L_A is the measured luminance of the surround. My desk, the luminance of 20% grey is is 6.8 nits this morning. If Y_b is 20 and L_A is 6.8 this implies a diffuse white point or adapting white point of 34 nits.

It’s more common today to directly measure diffuse white with a spectrometer and use that measurement to derive the L_A value by dividing by 5. If that isn’t an option, other experimenters will measure the illuminance using an illuminance meter, then divide by 5*pi.

If you are working on already normalized XYZ data already, then leaving L_A = 20 and Y_b = 20 is the correct input.

As you decrease L_a, that is the same as saying there is no light at all. In which case, there is no color.

Now we should also look at the “Colorfulness” and “Chroma” correlates, M and C respectively. First, I’ll talk about the definitions of these.

Chroma is the colorfulness of a stimulus relative to the brightness of the scene / adaptation. Colorfulness is absolute in that it is not scaled by adaptation to the brightness of the scene. So again, as L_A goes towards zero colorfulness also goes towards 0. There is no light, or the amount of light is decreasing and so the stimulus must appear less colorful also. Chroma, being relative to adaptation, is more relevant in our visual system.

If you sit in a dark room, M might decrease to very low values because there is not a lot of chromatic appearance on an absolute scale, compared to a brighter room. But relative to sitting there in that room with your adapted visual system, there might still be enough chromatic stuff for you to see colors still. Chroma accounts for this. colorfulness (M) does not.

For appearance manipulation it’s much more likely that you will have some success using J and C. Hopefully this also explains the usage of L_a better.