Skip to content

[BUG]: sCAM assumes a white point that contradicts with the IPT LMS matrix that is being used #1363

@facelessuser

Description

@facelessuser

Description

I was looking over the implementation paper of sCAM, and was verifying my results against Colour Science's, and I am a little confused about one point.

sCAM uses the IPT LMS transformation matrix. The sCAM paper states that the color should be in D65 when converting to Iab, but does not state what D65 is. What is interesting is that it states the HPE matrix is used, but what is listed is the modified one that IPT uses, which was specifically adapted for a white point of [0.9504, 1.0, 1.0889] (which is specifically stated in the IPT paper).

TO_LMS = [
    [0.4002, 0.7075, -0.0807],
    [-0.2280, 1.1500, 0.06124],
    [0.0, 0.0, 0.9184]
]

FROM_LMS = LMS_TO_XYZ = [
    [1.8502, -1.1383, 0.2385],
    [0.3668, 0.6439, -0.0107],
    [0.0, 0.0, 1.0889]
]

I will note that the IPT paper specifies 1.8501 in the inverse matrix, but this appears to be a mistake, as the inverse clearly yields a 1.8502.

>>> import numpy as np
>>> m = [
...     [0.4002, 0.7075, -0.0807],
...     [-0.2280, 1.1500, 0.06124],
...     [0.0, 0.0, 0.9184]
... ]
>>> np.linalg.inv(m)
array([[ 1.85024294, -1.13830164,  0.23848454],
       [ 0.36683078,  0.64388454, -0.01070149],
       [ 0.        ,  0.        ,  1.08885017]])

Now the forward transform creates noise in anything greater than 16-bit due to rounding.

>>> w = [0.9504, 1.0, 1.0889]
>>> np.matmul(m, w)
array([0.99997585, 0.99999304, 1.00004576])

But if we used the inverse transform, we can see that it yields a good LMS transform of [1, 1, 1] to the expected white point past 16-bit. It is clear that the transformation matrix was tailored to [0.9504, 1.0, 1.0889].

>>> mi = [
...     [1.8502, -1.1383, 0.2385],
...     [0.3668, 0.6439, -0.0107],
...     [0.0, 0.0, 1.0889]
... ]
>>> np.matmul(mi, [1, 1, 1])
array([0.9504, 1.    , 1.0889])
>>> np.matmul(np.linalg.inv(mi), [0.9504, 1.0, 1.0889])
array([1., 1., 1.])

Colour Science seems to assume a white point of:

TVS_D65_sCAM = np.array([0.95047, 1.00000, 1.08883])

And while I am aware this is a commonly used white point, and am aware of how it is derived, it seems to be an odd choice for the LMS matrix being used.

Is [0.95047, 1.00000, 1.08883] truly the white point that should be used? Or should it be [0.9504, 1.0, 1.0889]?

Currently, even when inputting [0.95047, 1.00000, 1.08883] into the sCAM conversion, you will not get 100% lightness in sCAM 1) due to the reverse LMS transform being generated from the forward transform which generates noise in anything greater than 16-bit, and 2) the LMS transform is not meant for the white point being used (unless it was adapted to the D65 variant being used).

If the appropriate white point (in the context of the IPT LMS matrix) was used, and the forward transform was generated from the reverse transform, you would then yield a clean 100% lightness in sCAM for the same white if it was the input.

Anyway, I thought it was worth bringing up the topic.

Code for Reproduction

Exception Message

Environment Information

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions