Example #1
0
def XYZ_to_RGB_LLAB(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to normalised cone responses.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        Normalised cone responses.

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_to_RGB_LLAB(XYZ)  # doctest: +ELLIPSIS
    array([ 0.9414279...,  1.0404012...,  1.0897088...])
    """

    _X, Y, _Z = tsplit(XYZ)

    Y = tstack([Y, Y, Y])
    XYZ_n = XYZ / Y

    return vector_dot(MATRIX_XYZ_TO_RGB_LLAB, XYZ_n)
Example #2
0
    def test_vector_dot(self):
        """Test :func:`colour.utilities.array.vector_dot` definition."""

        m = np.array(
            [
                [0.7328, 0.4296, -0.1624],
                [-0.7036, 1.6975, 0.0061],
                [0.0030, 0.0136, 0.9834],
            ]
        )
        m = np.reshape(np.tile(m, (6, 1)), (6, 3, 3))

        v = np.array([0.20654008, 0.12197225, 0.05136952])
        v = np.tile(v, (6, 1))

        np.testing.assert_almost_equal(
            vector_dot(m, v),
            np.array(
                [
                    [0.19540944, 0.06203965, 0.05279523],
                    [0.19540944, 0.06203965, 0.05279523],
                    [0.19540944, 0.06203965, 0.05279523],
                    [0.19540944, 0.06203965, 0.05279523],
                    [0.19540944, 0.06203965, 0.05279523],
                    [0.19540944, 0.06203965, 0.05279523],
                ]
            ),
            decimal=7,
        )
Example #3
0
def XYZ_to_LMS_ATD95(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to *LMS* cone responses.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        *LMS* cone responses.

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_to_LMS_ATD95(XYZ)  # doctest: +ELLIPSIS
    array([ 6.2283272...,  7.4780666...,  3.8859772...])
    """

    LMS = vector_dot(
        [
            [0.2435, 0.8524, -0.0516],
            [-0.3954, 1.1642, 0.0837],
            [0.0000, 0.0400, 0.6225],
        ],
        XYZ,
    )
    LMS *= np.array([0.66, 1.0, 0.43])

    LMS_p = spow(LMS, 0.7)
    LMS_p += np.array([0.024, 0.036, 0.31])

    return as_float_array(LMS_p)
Example #4
0
def RGB_to_rgb(RGB: ArrayLike) -> NDArray:
    """
    Convert given *RGB* array to *Hunt-Pointer-Estevez*
    :math:`\\rho\\gamma\\beta` colourspace.

    Parameters
    ----------
    RGB
        *RGB* array.

    Returns
    -------
    :class:`numpy.ndarray`
        *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array.

    Examples
    --------
    >>> RGB = np.array([19.99370783, 20.00393634, 20.01326387])
    >>> RGB_to_rgb(RGB)  # doctest: +ELLIPSIS
    array([ 19.9969397...,  20.0018612...,  20.0135053...])
    """

    rgb = vector_dot(matrix_dot(MATRIX_XYZ_TO_HPE, CAT_INVERSE_CAT02), RGB)

    return rgb
Example #5
0
def rgb_to_RGB(rgb: ArrayLike) -> NDArray:
    """
    Convert given *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta`
    colourspace array to *RGB* array.

    Parameters
    ----------
    rgb
        *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array.

    Returns
    -------
    :class:`numpy.ndarray`
        *RGB* array.

    Examples
    --------
    >>> rgb = np.array([19.99693975, 20.00186123, 20.01350530])
    >>> rgb_to_RGB(rgb)  # doctest: +ELLIPSIS
    array([ 19.9937078...,  20.0039363...,  20.0132638...])
    """

    RGB = vector_dot(matrix_dot(CAT_CAT02, MATRIX_HPE_TO_XYZ), rgb)

    return RGB
Example #6
0
def RGB_to_YCoCg(RGB: ArrayLike) -> NDArray:
    """
    Convert an array of *R'G'B'* values to the corresponding *YCoCg* colour
    encoding values array.

    Parameters
    ----------
    RGB
        Input *R'G'B'* array.

    Returns
    -------
    :class:`numpy.ndarray`
        *YCoCg* colour encoding array.

    References
    ----------
    :cite:`Malvar2003`

    Examples
    --------
    >>> RGB_to_YCoCg(np.array([1.0, 1.0, 1.0]))
    array([ 1.,  0.,  0.])
    >>> RGB_to_YCoCg(np.array([0.75, 0.5, 0.5]))
    array([ 0.5625,  0.125 , -0.0625])
    """

    return vector_dot(MATRIX_RGB_TO_YCOCG, RGB)
Example #7
0
def YCoCg_to_RGB(YCoCg: ArrayLike) -> NDArray:
    """
    Convert an array of *YCoCg* colour encoding values to the corresponding
    *R'G'B'* values array.

    Parameters
    ----------
    YCoCg
        *YCoCg* colour encoding array.

    Returns
    -------
    :class:`numpy.ndarray`
        Output *R'G'B'* array.

    References
    ----------
    :cite:`Malvar2003`

    Examples
    --------
    >>> YCoCg_to_RGB(np.array([1.0, 0.0, 0.0]))
    array([ 1.,  1.,  1.])
    >>> YCoCg_to_RGB(np.array([0.5625, 0.125, -0.0625]))
    array([ 0.75,  0.5 ,  0.5 ])
    """

    return vector_dot(MATRIX_YCOCG_TO_RGB, YCoCg)
Example #8
0
def chromatic_adaptation(
    RGB: ArrayLike,
    RGB_0: ArrayLike,
    RGB_0r: ArrayLike,
    Y: FloatingOrArrayLike,
    D: FloatingOrArrayLike = 1,
) -> NDArray:
    """
    Apply chromatic adaptation to given *RGB* normalised cone responses
    array.

    Parameters
    ----------
    RGB
        *RGB* normalised cone responses array of test sample / stimulus.
    RGB_0
        *RGB* normalised cone responses array of reference white.
    RGB_0r
        *RGB* normalised cone responses array of reference illuminant
        *CIE Standard Illuminant D Series* *D65*.
    Y
        Tristimulus values :math:`Y` of the stimulus.
    D
         *Discounting-the-Illuminant* factor normalised to domain [0, 1].

    Returns
    -------
    :class:`numpy.ndarray`
        Adapted *CIE XYZ* tristimulus values.

    Examples
    --------
    >>> RGB = np.array([0.94142795, 1.04040120, 1.08970885])
    >>> RGB_0 = np.array([0.94146023, 1.04039386, 1.08950293])
    >>> RGB_0r = np.array([0.94146023, 1.04039386, 1.08950293])
    >>> Y = 20.0
    >>> chromatic_adaptation(RGB, RGB_0, RGB_0r, Y)  # doctest: +ELLIPSIS
    array([ 19.01,  20.  ,  21.78])
    """

    R, G, B = tsplit(RGB)
    R_0, G_0, B_0 = tsplit(RGB_0)
    R_0r, G_0r, B_0r = tsplit(RGB_0r)
    Y = as_float_array(Y)
    D = as_float_array(D)

    beta = spow(B_0 / B_0r, 0.0834)

    R_r = (D * (R_0r / R_0) + 1 - D) * R
    G_r = (D * (G_0r / G_0) + 1 - D) * G
    B_r = (D * (B_0r / spow(B_0, beta)) + 1 - D) * spow(B, beta)

    RGB_r = tstack([R_r, G_r, B_r])

    Y = tstack([Y, Y, Y])

    XYZ_r = vector_dot(MATRIX_RGB_TO_XYZ_LLAB, RGB_r * Y)

    return XYZ_r
Example #9
0
def RGB_to_IHLS(RGB: ArrayLike) -> NDArray:
    """
    Convert from *RGB* colourspace to *IHLS* (Improved HLS) colourspace.

    Parameters
    ----------
    RGB
       *RGB* colourspace array.

    Returns
    -------
    :class:`numpy.ndarray`
        *HYS* colourspace array.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``RGB``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``HYS``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Hanbury2003`

    Examples
    --------
    >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245])
    >>> RGB_to_IHLS(RGB)  # doctest: +ELLIPSIS
    array([ 6.2616051...,  0.1216271...,  0.4255586...])
    """

    RGB = to_domain_1(RGB)
    R, G, B = tsplit(RGB)

    Y, C_1, C_2 = tsplit(vector_dot(MATRIX_RGB_TO_YC_1_C_2, RGB))

    C = np.sqrt(C_1**2 + C_2**2)

    acos_C_1_C_2 = zeros(C.shape)
    acos_C_1_C_2[C != 0] = np.arccos(C_1[C != 0] / C[C != 0])
    H = np.where(C_2 <= 0, acos_C_1_C_2, (np.pi * 2) - acos_C_1_C_2)

    S = np.maximum(np.maximum(R, G), B) - np.minimum(np.minimum(R, G), B)

    HYS = tstack([H, Y, S])

    return from_range_1(HYS)
Example #10
0
def IHLS_to_RGB(HYS: ArrayLike) -> NDArray:
    """
    Convert from *IHLS* (Improved HLS) colourspace to *RGB* colourspace.

    Parameters
    ----------
    HYS
        *IHLS* colourspace array.

    Returns
    -------
    :class:`numpy.ndarray`
        *RGB* colourspace array.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``HYS``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``RGB``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Hanbury2003`

    Examples
    --------
    >>> HYS = np.array([6.26160518, 0.12162712, 0.42555869])
    >>> IHLS_to_RGB(HYS)  # doctest: +ELLIPSIS
    array([ 0.4559557...,  0.0303970...,  0.0408724...])
    """

    H, Y, S = tsplit(to_domain_1(HYS))

    pi_3 = np.pi / 3

    k = np.floor(H / (pi_3))
    H_s = H - k * (pi_3)
    C = (np.sqrt(3) * S) / (2 * np.sin((2 * pi_3) - H_s))

    C_1 = C * np.cos(H)
    C_2 = -C * np.sin(H)

    RGB = vector_dot(MATRIX_YC_1_C_2_TO_RGB, tstack([Y, C_1, C_2]))

    return from_range_1(RGB)
Example #11
0
    def apply(self, RGB: ArrayLike, *args: Any, **kwargs: Any) -> NDArray:
        """
        Apply the *LUT* operator to given *RGB* array.

        Parameters
        ----------
        RGB
            *RGB* array to apply the *LUT* operator transform to.

        Other Parameters
        ----------------
        apply_offset_first
            Whether to apply the offset first and then the matrix.

        Returns
        -------
        :class:`numpy.ndarray`
            Transformed *RGB* array.

        Examples
        --------
        >>> matrix = np.array([[ 1.45143932, -0.23651075, -0.21492857],
        ...                    [-0.07655377,  1.1762297 , -0.09967593],
        ...                    [ 0.00831615, -0.00603245,  0.9977163 ]])
        >>> M = LUTOperatorMatrix(matrix)
        >>> RGB = np.array([0.3, 0.4, 0.5])
        >>> M.apply(RGB)  # doctest: +ELLIPSIS
        array([ 0.2333632...,  0.3976877...,  0.4989400...])
        """

        RGB = as_float_array(RGB)
        apply_offset_first = kwargs.get("apply_offset_first", False)

        has_alpha_channel = RGB.shape[-1] == 4
        M = self._matrix
        offset = self._offset

        if not has_alpha_channel:
            M = M[:3, :3]
            offset = offset[:3]

        if apply_offset_first:
            RGB += offset

        RGB = vector_dot(M, RGB)

        if not apply_offset_first:
            RGB += offset

        return RGB
Example #12
0
def RGB_10_degree_cmfs_to_LMS_10_degree_cmfs(
    wavelength: FloatingOrArrayLike, ) -> NDArray:
    """
    Convert *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching
    functions into the *Stockman & Sharpe 10 Degree Cone Fundamentals*
    spectral sensitivity functions.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` in nm.

    Returns
    -------
    :class:`numpy.ndarray`
        *Stockman & Sharpe 10 Degree Cone Fundamentals* spectral tristimulus
        values.

    Notes
    -----
    -   Data for the *Stockman & Sharpe 10 Degree Cone Fundamentals* already
        exists, this definition is intended for educational purpose.

    References
    ----------
    :cite:`CIETC1-362006a`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     RGB_10_degree_cmfs_to_LMS_10_degree_cmfs(700)  # doctest: +ELLIPSIS
    array([ 0.0052860...,  0.0003252...,  0.        ])
    """

    cmfs = MSDS_CMFS_RGB["Stiles & Burch 1959 10 Degree RGB CMFs"]

    rgb_bar = cmfs[wavelength]

    M = np.array([
        [0.1923252690, 0.749548882, 0.0675726702],
        [0.0192290085, 0.940908496, 0.113830196],
        [0.0000000000, 0.0105107859, 0.991427669],
    ])

    lms_bar = vector_dot(M, rgb_bar)
    lms_bar[..., -1][np.asarray(np.asarray(wavelength) > 505)] = 0

    return lms_bar
Example #13
0
def LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs(
    wavelength: FloatingOrArrayLike, ) -> NDArray:
    """
    Convert *Stockman & Sharpe 2 Degree Cone Fundamentals* colour matching
    functions into the *CIE 2012 2 Degree Standard Observer* colour matching
    functions.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` in nm.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE 2012 2 Degree Standard Observer* spectral tristimulus values.

    Notes
    -----
    -   Data for the *CIE 2012 2 Degree Standard Observer* already exists,
        this definition is intended for educational purpose.

    References
    ----------
    :cite:`CVRLv`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs(700)  # doctest: +ELLIPSIS
    array([ 0.0109677...,  0.0041959...,  0.        ])
    """

    cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 2 Degree Cone Fundamentals"]

    lms_bar = cmfs[wavelength]

    M = np.array([
        [1.94735469, -1.41445123, 0.36476327],
        [0.68990272, 0.34832189, 0.00000000],
        [0.00000000, 0.00000000, 1.93485343],
    ])

    xyz_bar = vector_dot(M, lms_bar)

    return xyz_bar
Example #14
0
def RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs(
    wavelength: FloatingOrArrayLike, ) -> NDArray:
    """
    Convert *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching
    functions into the *CIE 1964 10 Degree Standard Observer* colour matching
    functions.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` in nm.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE 1964 10 Degree Standard Observer* spectral tristimulus values.

    Notes
    -----
    -   Data for the *CIE 1964 10 Degree Standard Observer* already exists,
        this definition is intended for educational purpose.

    References
    ----------
    :cite:`Wyszecki2000be`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs(700)  # doctest: +ELLIPSIS
    array([ 0.0096432...,  0.0037526..., -0.0000041...])
    """

    cmfs = MSDS_CMFS_RGB["Stiles & Burch 1959 10 Degree RGB CMFs"]

    rgb_bar = cmfs[wavelength]

    M = np.array([
        [0.341080, 0.189145, 0.387529],
        [0.139058, 0.837460, 0.073316],
        [0.000000, 0.039553, 2.026200],
    ])

    xyz_bar = vector_dot(M, rgb_bar)

    return xyz_bar
Example #15
0
def LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs(
    wavelength: FloatingOrArrayLike, ) -> NDArray:
    """
    Convert *Stockman & Sharpe 10 Degree Cone Fundamentals* colour matching
    functions into the *CIE 2012 10 Degree Standard Observer* colour matching
    functions.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` in nm.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE 2012 10 Degree Standard Observer* spectral tristimulus values.

    Notes
    -----
    -   Data for the *CIE 2012 10 Degree Standard Observer* already exists,
        this definition is intended for educational purpose.

    References
    ----------
    :cite:`CVRLp`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs(700)  # doctest: +ELLIPSIS
    array([ 0.0098162...,  0.0037761...,  0.        ])
    """

    cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 10 Degree Cone Fundamentals"]

    lms_bar = cmfs[wavelength]

    M = np.array([
        [1.93986443, -1.34664359, 0.43044935],
        [0.69283932, 0.34967567, 0.00000000],
        [0.00000000, 0.00000000, 2.14687945],
    ])

    xyz_bar = vector_dot(M, lms_bar)

    return xyz_bar
Example #16
0
def matrix_post_adaptation_non_linear_response_compression(
        P_2: FloatingOrArrayLike, a: FloatingOrArrayLike,
        b: FloatingOrArrayLike) -> NDArray:
    """
    Apply the post-adaptation non-linear-response compression matrix.

    Parameters
    ----------
    P_2
        Point :math:`P_2`.
    a
        Opponent colour dimension :math:`a`.
    b
        Opponent colour dimension :math:`b`.

    Returns
    -------
    :class:`numpy.ndarray`
        Points :math:`P`.

    Examples
    --------
    >>> P_2 = 24.2372054671
    >>> a = -0.000624112068243
    >>> b = -0.000506270106773
    >>> matrix_post_adaptation_non_linear_response_compression(P_2, a, b)
    ... # doctest: +ELLIPSIS
    array([ 7.9463202...,  7.9471152...,  7.9489959...])
    """

    P_2 = as_float_array(P_2)
    a = as_float_array(a)
    b = as_float_array(b)

    RGB_a = (vector_dot(
        [
            [460, 451, 288],
            [460, -891, -261],
            [460, -220, -6300],
        ],
        tstack([P_2, a, b]),
    ) / 1403)

    return RGB_a
Example #17
0
def RGB_to_XYZ_Fairchild1990(RGB: ArrayLike) -> NDArray:
    """
    Convert from cone responses to *CIE XYZ* tristimulus values.

    Parameters
    ----------
    RGB
        Cone responses.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values.

    Examples
    --------
    >>> RGB = np.array([22.12319350, 23.60542240, 22.92795340])
    >>> RGB_to_XYZ_Fairchild1990(RGB)  # doctest: +ELLIPSIS
    array([ 19.53,  23.07,  24.97])
    """

    return vector_dot(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, RGB)
Example #18
0
def XYZ_to_RGB_CIE1994(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to cone responses.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        Cone responses.

    Examples
    --------
    >>> XYZ = np.array([28.00, 21.26, 5.27])
    >>> XYZ_to_RGB_CIE1994(XYZ)  # doctest: +ELLIPSIS
    array([ 25.8244273...,  18.6791422...,   4.8390194...])
    """

    return vector_dot(MATRIX_XYZ_TO_RGB_CIE1994, XYZ)
Example #19
0
def XYZ_to_RGB_Fairchild1990(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to cone responses.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        Cone responses.

    Examples
    --------
    >>> XYZ = np.array([19.53, 23.07, 24.97])
    >>> XYZ_to_RGB_Fairchild1990(XYZ)  # doctest: +ELLIPSIS
    array([ 22.1231935...,  23.6054224...,  22.9279534...])
    """

    return vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ)
Example #20
0
def XYZ_to_RGB_Nayatani95(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to cone responses.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        Cone responses.

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_to_RGB_Nayatani95(XYZ)  # doctest: +ELLIPSIS
    array([ 20.0005206...,  19.999783 ...,  19.9988316...])
    """

    return vector_dot(MATRIX_XYZ_TO_RGB_NAYATANI95, XYZ)
Example #21
0
def RGB_to_XYZ_CIE1994(RGB: ArrayLike) -> NDArray:
    """
    Convert from cone responses to *CIE XYZ* tristimulus values.

    Parameters
    ----------
    RGB
        Cone responses.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values.

    Examples
    --------
    >>> RGB = np.array([25.82442730, 18.67914220, 4.83901940])
    >>> RGB_to_XYZ_CIE1994(RGB)  # doctest: +ELLIPSIS
    array([ 28.  ,  21.26,   5.27])
    """

    return vector_dot(MATRIX_RGB_TO_XYZ_CIE1994, RGB)
Example #22
0
def XYZ_to_rgb(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to *Hunt-Pointer-Estevez*
    :math:`\\rho\\gamma\\beta` colourspace.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace.

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_to_rgb(XYZ)  # doctest: +ELLIPSIS
    array([ 19.4743367...,  20.3101217...,  21.78     ])
    """

    return vector_dot(MATRIX_XYZ_TO_HPE, XYZ)
Example #23
0
def chromatic_adaptation_Zhai2018(
    XYZ_b: ArrayLike,
    XYZ_wb: ArrayLike,
    XYZ_wd: ArrayLike,
    D_b: FloatingOrArrayLike = 1,
    D_d: FloatingOrArrayLike = 1,
    XYZ_wo: ArrayLike = np.array([1, 1, 1]),
    transform: Union[Literal["CAT02", "CAT16"], str] = "CAT02",
) -> NDArray:
    """
    Adapt given sample colour :math:`XYZ_{\\beta}` tristimulus values from
    input viewing conditions under :math:`\\beta` illuminant to output viewing
    conditions under :math:`\\delta` illuminant using *Zhai and Luo (2018)*
    chromatic adaptation model.

    According to the definition of :math:`D`, a one-step CAT such as CAT02 can
    only be used to transform colors from an incomplete adapted field into a
    complete adapted field. When CAT02 are used to transform an incomplete to
    incomplete case, :math:`D` has no baseline level to refer to.
    *Smet et al. (2017)* proposed a new concept of two-step CAT to replace the
    present CATs such as CAT02 with only one-step transform in order to define
    :math:`D` more clearly. A two-step CAT involves an illuminant representing
    the baseline states between the test and reference illuminants for the
    calculation. In the first step the test color is transformed from test
    illuminant to the baseline illuminant (:math:`BI`), and it is then
    transformed to the reference illuminant Degrees of adaptation under the
    other illuminants should be calculated relative to the adaptation under the
    :math:`BI`. When :math:`D` becomes lower towards zero, the adaptation point
    of the observer moves towards the :math:`BI`. Therefore, the chromaticity
    of the :math:`BI` should be an intrinsic property of the human vision
    system.

    Parameters
    ----------
    XYZ_b
        Sample colour :math:`XYZ_{\\beta}` under input illuminant
        :math:`\\beta`.
    XYZ_wb
        Input illuminant :math:`\\beta`.
    XYZ_wd
        Output illuminant :math:`\\delta`.
    D_b
        Degree of adaptation :math:`D_{\\beta}` of input illuminant
        :math:`\\beta`.
    D_d
        Degree of adaptation :math:`D_{\\delta}` of output illuminant
        :math:`\\delta`.
    XYZ_wo
        Baseline illuminant (:math:`BI`) :math:`o`.
    transform
        Chromatic adaptation transform.

    Returns
    -------
    :class:`numpy.ndarray`
        Sample corresponding colour :math:`XYZ_{\\delta}` tristimulus values
        under output illuminant :math:`D_{\\delta}`.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_b``  | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_wb`` | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_wd`` | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_wo`` | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_d``  | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Zhai2018`

    Examples
    --------
    >>> XYZ_b = np.array([48.900, 43.620, 6.250])
    >>> XYZ_wb = np.array([109.850, 100, 35.585])
    >>> XYZ_wd = np.array([95.047, 100, 108.883])
    >>> D_b = 0.9407
    >>> D_d = 0.9800
    >>> XYZ_wo = np.array([100, 100, 100])
    >>> chromatic_adaptation_Zhai2018(
    ...     XYZ_b, XYZ_wb, XYZ_wd, D_b, D_d, XYZ_wo)  # doctest: +ELLIPSIS
    array([ 39.1856164...,  42.1546179...,  19.2367203...])
    >>> XYZ_d = np.array([39.18561644, 42.15461798, 19.23672036])
    >>> chromatic_adaptation_Zhai2018(
    ...     XYZ_d, XYZ_wd, XYZ_wb, D_d, D_b, XYZ_wo)  # doctest: +ELLIPSIS
    array([ 48.9 ,  43.62,   6.25])
    """

    XYZ_b = to_domain_100(XYZ_b)
    XYZ_wb = to_domain_100(XYZ_wb)
    XYZ_wd = to_domain_100(XYZ_wd)
    XYZ_wo = to_domain_100(XYZ_wo)
    D_b = as_float_array(D_b)
    D_d = as_float_array(D_d)

    Y_wb = XYZ_wb[..., 1][..., np.newaxis]
    Y_wd = XYZ_wd[..., 1][..., np.newaxis]
    Y_wo = XYZ_wo[..., 1][..., np.newaxis]

    transform = validate_method(transform, ["CAT02", "CAT16"])
    M = CHROMATIC_ADAPTATION_TRANSFORMS[transform]

    RGB_b = vector_dot(M, XYZ_b)
    RGB_wb = vector_dot(M, XYZ_wb)
    RGB_wd = vector_dot(M, XYZ_wd)
    RGB_wo = vector_dot(M, XYZ_wo)

    D_RGB_b = D_b * (Y_wb / Y_wo) * (RGB_wo / RGB_wb) + 1 - D_b
    D_RGB_d = D_d * (Y_wd / Y_wo) * (RGB_wo / RGB_wd) + 1 - D_d

    D_RGB = D_RGB_b / D_RGB_d

    RGB_d = D_RGB * RGB_b

    XYZ_d = vector_dot(np.linalg.inv(M), RGB_d)

    return from_range_100(XYZ_d)
Example #24
0
def CIECAM02_to_XYZ(
    specification: CAM_Specification_CIECAM02,
    XYZ_w: ArrayLike,
    L_A: FloatingOrArrayLike,
    Y_b: FloatingOrArrayLike,
    surround: InductionFactors_CIECAM02 = VIEWING_CONDITIONS_CIECAM02[
        "Average"],
    discount_illuminant: Boolean = False,
) -> NDArray:
    """
    Convert from *CIECAM02* specification to *CIE XYZ* tristimulus values.

    Parameters
    ----------
    specification
        *CIECAM02* colour appearance model specification. Correlate of
        *Lightness* :math:`J`, correlate of *chroma* :math:`C` or correlate of
        *colourfulness* :math:`M` and *hue* angle :math:`h` in degrees must be
        specified, e.g. :math:`JCh` or :math:`JMh`.
    XYZ_w
        *CIE XYZ* tristimulus values of reference white.
    L_A
        Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken
        to be 20% of the luminance of a white object in the scene).
    Y_b
        Luminous factor of background :math:`Y_b` such as
        :math:`Y_b = 100 x L_b / L_w` where :math:`L_w` is the luminance of the
        light source and :math:`L_b` is the luminance of the background. For
        viewing images, :math:`Y_b` can be the average :math:`Y` value for the
        pixels in the entire image, or frequently, a :math:`Y` value of 20,
        approximate an :math:`L^*` of 50 is used.
    surround
        Surround viewing conditions.
    discount_illuminant
        Discount the illuminant.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values.

    Raises
    ------
    ValueError
        If neither *C* or *M* correlates have been defined in the
        ``CAM_Specification_CIECAM02`` argument.

    Notes
    -----
    +----------------------------------+-----------------------\
+---------------+
    | **Domain**                       | **Scale - Reference** \
| **Scale - 1** |
    +==================================+=======================\
+===============+
    | ``CAM_Specification_CIECAM02.J`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.C`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.h`` | [0, 360]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.s`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.Q`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.M`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.H`` | [0, 360]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``XYZ_w``                        | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Fairchild2004c`, :cite:`Luo2013`, :cite:`Moroneya`,
    :cite:`Wikipedia2007a`

    Examples
    --------
    >>> specification = CAM_Specification_CIECAM02(J=41.731091132513917,
    ...                                            C=0.104707757171031,
    ...                                            h=219.048432658311780)
    >>> XYZ_w = np.array([95.05, 100.00, 108.88])
    >>> L_A = 318.31
    >>> Y_b = 20.0
    >>> CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b)  # doctest: +ELLIPSIS
    array([ 19.01...,  20...  ,  21.78...])
    """

    J, C, h, _s, _Q, M, _H, _HC = astuple(specification)

    J = to_domain_100(J)
    C = to_domain_100(C)
    h = to_domain_degrees(h)
    M = to_domain_100(M)
    L_A = as_float_array(L_A)
    XYZ_w = to_domain_100(XYZ_w)
    _X_w, Y_w, _Z_w = tsplit(XYZ_w)

    n, F_L, N_bb, N_cb, z = viewing_condition_dependent_parameters(
        Y_b, Y_w, L_A)

    if has_only_nan(C) and not has_only_nan(M):
        C = M / spow(F_L, 0.25)
    elif has_only_nan(C):
        raise ValueError('Either "C" or "M" correlate must be defined in '
                         'the "CAM_Specification_CIECAM02" argument!')

    # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform
    # sharpened *RGB* values.
    RGB_w = vector_dot(CAT_CAT02, XYZ_w)

    # Computing degree of adaptation :math:`D`.
    D = (degree_of_adaptation(surround.F, L_A)
         if not discount_illuminant else ones(L_A.shape))

    # Computing full chromatic adaptation.
    RGB_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D)

    # Converting to *Hunt-Pointer-Estevez* colourspace.
    RGB_pw = RGB_to_rgb(RGB_wc)

    # Applying post-adaptation non-linear response compression.
    RGB_aw = post_adaptation_non_linear_response_compression_forward(
        RGB_pw, F_L)

    # Computing achromatic response for the whitepoint.
    A_w = achromatic_response_forward(RGB_aw, N_bb)

    # Computing temporary magnitude quantity :math:`t`.
    t = temporary_magnitude_quantity_inverse(C, J, n)

    # Computing eccentricity factor *e_t*.
    e_t = eccentricity_factor(h)

    # Computing achromatic response :math:`A` for the stimulus.
    A = achromatic_response_inverse(A_w, J, surround.c, z)

    # Computing *P_1* to *P_3*.
    P_n = P(surround.N_c, N_cb, e_t, t, A, N_bb)
    _P_1, P_2, _P_3 = tsplit(P_n)

    # Computing opponent colour dimensions :math:`a` and :math:`b`.
    a, b = tsplit(opponent_colour_dimensions_inverse(P_n, h))

    # Applying post-adaptation non-linear response compression matrix.
    RGB_a = matrix_post_adaptation_non_linear_response_compression(P_2, a, b)

    # Applying inverse post-adaptation non-linear response compression.
    RGB_p = post_adaptation_non_linear_response_compression_inverse(RGB_a, F_L)

    # Converting to *Hunt-Pointer-Estevez* colourspace.
    RGB_c = rgb_to_RGB(RGB_p)

    # Applying inverse full chromatic adaptation.
    RGB = full_chromatic_adaptation_inverse(RGB_c, RGB_w, Y_w, D)

    # Converting *CMCCAT2000* transform sharpened *RGB* values to *CIE XYZ*
    # tristimulus values.
    XYZ = vector_dot(CAT_INVERSE_CAT02, RGB)

    return from_range_100(XYZ)
Example #25
0
def XYZ_to_OSA_UCS(XYZ: ArrayLike) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values under the
    *CIE 1964 10 Degree Standard Observer* to *OSA UCS* colourspace.

    The lightness axis, *L* is usually in range [-9, 5] and centered around
    middle gray (Munsell N/6). The yellow-blue axis, *j* is usually in range
    [-15, 15]. The red-green axis, *g* is usually in range [-20, 15].

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values under the
        *CIE 1964 10 Degree Standard Observer*.

    Returns
    -------
    :class:`numpy.ndarray`
        *OSA UCS* :math:`Ljg` lightness, jaune (yellowness), and greenness.

    Notes
    -----
    +------------+-----------------------+--------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**      |
    +============+=======================+====================+
    | ``XYZ``    | [0, 100]              | [0, 1]             |
    +------------+-----------------------+--------------------+

    +------------+-----------------------+--------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**      |
    +============+=======================+====================+
    | ``Ljg``    | ``L`` : [-100, 100]   | ``L`` : [-1, 1]    |
    |            |                       |                    |
    |            | ``j`` : [-100, 100]   | ``j`` : [-1, 1]    |
    |            |                       |                    |
    |            | ``g`` : [-100, 100]   | ``g`` : [-1, 1]    |
    +------------+-----------------------+--------------------+

    -   *OSA UCS* uses the *CIE 1964 10 Degree Standard Observer*.

    References
    ----------
    :cite:`Cao2013`, :cite:`Moroney2003`

    Examples
    --------
    >>> import numpy as np
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100
    >>> XYZ_to_OSA_UCS(XYZ)  # doctest: +ELLIPSIS
    array([-3.0049979...,  2.9971369..., -9.6678423...])
    """

    XYZ = to_domain_100(XYZ)
    x, y, Y = tsplit(XYZ_to_xyY(XYZ))

    Y_0 = Y * (4.4934 * x**2 + 4.3034 * y**2 - 4.276 * x * y - 1.3744 * x -
               2.5643 * y + 1.8103)

    o_3 = 1 / 3
    Y_0_es = spow(Y_0, o_3) - 2 / 3
    # Gracefully handles Y_0 < 30.
    Y_0_s = Y_0 - 30
    Lambda = 5.9 * (Y_0_es + 0.042 * spow(Y_0_s, o_3))

    RGB = vector_dot(MATRIX_XYZ_TO_RGB_OSA_UCS, XYZ)
    RGB_3 = spow(RGB, 1 / 3)

    C = Lambda / (5.9 * Y_0_es)
    L = (Lambda - 14.4) / spow(2, 1 / 2)
    j = C * np.dot(RGB_3, np.array([1.7, 8, -9.7]))
    g = C * np.dot(RGB_3, np.array([-13.7, 17.7, -4]))

    Ljg = tstack([L, j, g])

    return from_range_100(Ljg)
Example #26
0
def degrees_of_adaptation(
    LMS: ArrayLike,
    Y_n: FloatingOrArrayLike,
    v: FloatingOrArrayLike = 1 / 3,
    discount_illuminant: Boolean = False,
) -> NDArray:
    """
    Compute the degrees of adaptation :math:`p_L`, :math:`p_M` and
    :math:`p_S`.

    Parameters
    ----------
    LMS
        Cone responses.
    Y_n
        Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`.
    v
        Exponent :math:`v`.
    discount_illuminant
        Truth value indicating if the illuminant should be discounted.

    Returns
    -------
    :class:`numpy.ndarray`
        Degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`.

    Examples
    --------
    >>> LMS = np.array([20.00052060, 19.99978300, 19.99883160])
    >>> Y_n = 31.83
    >>> degrees_of_adaptation(LMS, Y_n)  # doctest: +ELLIPSIS
    array([ 0.9799324...,  0.9960035...,  1.0233041...])
    >>> degrees_of_adaptation(LMS, Y_n, 1 / 3, True)
    array([ 1.,  1.,  1.])
    """

    LMS = as_float_array(LMS)
    if discount_illuminant:
        return ones(LMS.shape)

    Y_n = as_float_array(Y_n)
    v = as_float_array(v)

    L, M, S = tsplit(LMS)

    # E illuminant.
    LMS_E = vector_dot(CAT_VON_KRIES, ones(LMS.shape))
    L_E, M_E, S_E = tsplit(LMS_E)

    Ye_n = spow(Y_n, v)

    def m_E(x: NDArray, y: NDArray) -> NDArray:
        """Compute the :math:`m_E` term."""

        return (3 * (x / y)) / (L / L_E + M / M_E + S / S_E)

    def P_c(x: NDArray) -> NDArray:
        """Compute the :math:`P_L`, :math:`P_M` or :math:`P_S` terms."""

        return (1 + Ye_n + x) / (1 + Ye_n + 1 / x)

    p_L = P_c(m_E(L, L_E))
    p_M = P_c(m_E(M, M_E))
    p_S = P_c(m_E(S, S_E))

    p_LMS = tstack([p_L, p_M, p_S])

    return p_LMS
Example #27
0
def matrix_RGB_to_WSYBRG(cmfs: LMS_ConeFundamentals,
                         primaries: RGB_DisplayPrimaries) -> NDArray:
    """
    Compute the matrix transforming from *RGB* colourspace to opponent-colour
    space using *Machado et al. (2009)* method.

    Parameters
    ----------
    cmfs
        *LMS* cone fundamentals colour matching functions.
    primaries
        *RGB* display primaries tri-spectral distributions.

    Returns
    -------
    :class:`numpy.ndarray`
        Matrix transforming from *RGB* colourspace to opponent-colour space.

    Examples
    --------
    >>> from colour.characterisation import MSDS_DISPLAY_PRIMARIES
    >>> from colour.colorimetry import MSDS_CMFS_LMS
    >>> cmfs = MSDS_CMFS_LMS['Stockman & Sharpe 2 Degree Cone Fundamentals']
    >>> d_LMS = np.array([15, 0, 0])
    >>> primaries = MSDS_DISPLAY_PRIMARIES['Apple Studio Display']
    >>> matrix_RGB_to_WSYBRG(  # doctest: +ELLIPSIS
    ...     cmfs, primaries)
    array([[  0.2126535...,   0.6704626...,   0.1168838...],
           [  4.7095295...,  12.4862869..., -16.1958165...],
           [-11.1518474...,  15.2534789...,  -3.1016315...]])
    """

    wavelengths = cmfs.wavelengths
    WSYBRG = vector_dot(MATRIX_LMS_TO_WSYBRG, cmfs.values)
    WS, YB, RG = tsplit(WSYBRG)

    # pylint: disable=E1102
    primaries = reshape_msds(
        primaries,  # type: ignore[assignment]
        cmfs.shape,
        extrapolator_kwargs={
            "method": "Constant",
            "left": 0,
            "right": 0
        },
    )

    R, G, B = tsplit(primaries.values)

    WS_R = np.trapz(R * WS, wavelengths)
    WS_G = np.trapz(G * WS, wavelengths)
    WS_B = np.trapz(B * WS, wavelengths)

    YB_R = np.trapz(R * YB, wavelengths)
    YB_G = np.trapz(G * YB, wavelengths)
    YB_B = np.trapz(B * YB, wavelengths)

    RG_R = np.trapz(R * RG, wavelengths)
    RG_G = np.trapz(G * RG, wavelengths)
    RG_B = np.trapz(B * RG, wavelengths)

    M_G = np.array([
        [WS_R, WS_G, WS_B],
        [YB_R, YB_G, YB_B],
        [RG_R, RG_G, RG_B],
    ])

    PWS = 1 / (WS_R + WS_G + WS_B)
    PYB = 1 / (YB_R + YB_G + YB_B)
    PRG = 1 / (RG_R + RG_G + RG_B)

    M_G *= np.array([PWS, PYB, PRG])[:, np.newaxis]

    return M_G
Example #28
0
def XYZ_to_RLAB(
    XYZ: ArrayLike,
    XYZ_n: ArrayLike,
    Y_n: FloatingOrArrayLike,
    sigma: FloatingOrArrayLike = VIEWING_CONDITIONS_RLAB["Average"],
    D: FloatingOrArrayLike = D_FACTOR_RLAB["Hard Copy Images"],
) -> CAM_Specification_RLAB:
    """
    Compute the *RLAB* model color appearance correlates.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values of test sample / stimulus.
    XYZ_n
        *CIE XYZ* tristimulus values of reference white.
    Y_n
        Absolute adapting luminance in :math:`cd/m^2`.
    sigma
        Relative luminance of the surround, see
        :attr:`colour.VIEWING_CONDITIONS_RLAB` for reference.
    D
        *Discounting-the-Illuminant* factor normalised to domain [0, 1].

    Returns
    -------
    CAM_Specification_RLAB
        *RLAB* colour appearance model specification.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_n``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------------------------+-----------------------\
+---------------+
    | **Range**                    | **Scale - Reference** \
| **Scale - 1** |
    +==============================+=======================\
+===============+
    | ``CAM_Specification_RLAB.h`` | [0, 360]              \
| [0, 1]        |
    +------------------------------+-----------------------\
+---------------+

    References
    ----------
    :cite:`Fairchild1996a`, :cite:`Fairchild2013w`

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_n = np.array([109.85, 100, 35.58])
    >>> Y_n = 31.83
    >>> sigma = VIEWING_CONDITIONS_RLAB['Average']
    >>> D = D_FACTOR_RLAB['Hard Copy Images']
    >>> XYZ_to_RLAB(XYZ, XYZ_n, Y_n, sigma, D)  # doctest: +ELLIPSIS
    CAM_Specification_RLAB(J=49.8347069..., C=54.8700585..., \
h=286.4860208..., s=1.1010410..., HC=None, a=15.5711021..., \
b=-52.6142956...)
    """

    XYZ = to_domain_100(XYZ)
    XYZ_n = to_domain_100(XYZ_n)
    Y_n = as_float_array(Y_n)
    D = as_float_array(D)
    sigma = as_float_array(sigma)

    # Converting to cone responses.
    LMS_n = XYZ_to_rgb(XYZ_n)

    # Computing the :math:`A` matrix.
    LMS_l_E = (3 * LMS_n) / np.sum(LMS_n, axis=-1)[..., np.newaxis]
    LMS_p_L = (1 + spow(Y_n[..., np.newaxis], 1 / 3) +
               LMS_l_E) / (1 + spow(Y_n[..., np.newaxis], 1 / 3) +
                           (1 / LMS_l_E))
    LMS_a_L = (LMS_p_L + D[..., np.newaxis] * (1 - LMS_p_L)) / LMS_n

    M = matrix_dot(matrix_dot(MATRIX_R, row_as_diagonal(LMS_a_L)),
                   MATRIX_XYZ_TO_HPE)
    XYZ_ref = vector_dot(M, XYZ)

    X_ref, Y_ref, Z_ref = tsplit(XYZ_ref)

    # Computing the correlate of *Lightness* :math:`L^R`.
    LR = 100 * spow(Y_ref, sigma)

    # Computing opponent colour dimensions :math:`a^R` and :math:`b^R`.
    aR = as_float(430 * (spow(X_ref, sigma) - spow(Y_ref, sigma)))
    bR = as_float(170 * (spow(Y_ref, sigma) - spow(Z_ref, sigma)))

    # Computing the *hue* angle :math:`h^R`.
    hR = np.degrees(np.arctan2(bR, aR)) % 360
    # TODO: Implement hue composition computation.

    # Computing the correlate of *chroma* :math:`C^R`.
    CR = np.hypot(aR, bR)

    # Computing the correlate of *saturation* :math:`s^R`.
    sR = CR / LR

    return CAM_Specification_RLAB(
        LR,
        CR,
        as_float(from_range_degrees(hR)),
        sR,
        None,
        aR,
        bR,
    )
Example #29
0
def chromatic_adaptation_Fairchild1990(
    XYZ_1: ArrayLike,
    XYZ_n: ArrayLike,
    XYZ_r: ArrayLike,
    Y_n: FloatingOrArrayLike,
    discount_illuminant: Boolean = False,
) -> NDArray:
    """
    Adapt given stimulus *CIE XYZ_1* tristimulus values from test viewing
    conditions to reference viewing conditions using *Fairchild (1990)*
    chromatic adaptation model.

    Parameters
    ----------
    XYZ_1
        *CIE XYZ_1* tristimulus values of test sample / stimulus.
    XYZ_n
        Test viewing condition *CIE XYZ_n* tristimulus values of whitepoint.
    XYZ_r
        Reference viewing condition *CIE XYZ_r* tristimulus values of
        whitepoint.
    Y_n
        Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`.
    discount_illuminant
        Truth value indicating if the illuminant should be discounted.

    Returns
    -------
    :class:`numpy.ndarray`
        Adapted *CIE XYZ_2* tristimulus values of stimulus.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_1``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_n``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_r``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_2``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Fairchild1991a`, :cite:`Fairchild2013s`

    Examples
    --------
    >>> XYZ_1 = np.array([19.53, 23.07, 24.97])
    >>> XYZ_n = np.array([111.15, 100.00, 35.20])
    >>> XYZ_r = np.array([94.81, 100.00, 107.30])
    >>> Y_n = 200
    >>> chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n)
    ... # doctest: +ELLIPSIS
    array([ 23.3252634...,  23.3245581...,  76.1159375...])
    """

    XYZ_1 = to_domain_100(XYZ_1)
    XYZ_n = to_domain_100(XYZ_n)
    XYZ_r = to_domain_100(XYZ_r)
    Y_n = as_float_array(Y_n)

    LMS_1 = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_1)
    LMS_n = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_n)
    LMS_r = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_r)

    p_LMS = degrees_of_adaptation(
        LMS_1, Y_n, discount_illuminant=discount_illuminant
    )

    a_LMS_1 = p_LMS / LMS_n
    a_LMS_2 = p_LMS / LMS_r

    A_1 = row_as_diagonal(a_LMS_1)
    A_2 = row_as_diagonal(a_LMS_2)

    LMSp_1 = vector_dot(A_1, LMS_1)

    c = 0.219 - 0.0784 * np.log10(Y_n)
    C = row_as_diagonal(tstack([c, c, c]))

    LMS_a = vector_dot(C, LMSp_1)
    LMSp_2 = vector_dot(np.linalg.inv(C), LMS_a)

    LMS_c = vector_dot(np.linalg.inv(A_2), LMSp_2)
    XYZ_c = vector_dot(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, LMS_c)

    return from_range_100(XYZ_c)
Example #30
0
def XYZ_to_CIECAM02(
    XYZ: ArrayLike,
    XYZ_w: ArrayLike,
    L_A: FloatingOrArrayLike,
    Y_b: FloatingOrArrayLike,
    surround: InductionFactors_CIECAM02 = VIEWING_CONDITIONS_CIECAM02[
        "Average"],
    discount_illuminant: Boolean = False,
) -> CAM_Specification_CIECAM02:
    """
    Compute the *CIECAM02* colour appearance model correlates from given
    *CIE XYZ* tristimulus values.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values of test sample / stimulus.
    XYZ_w
        *CIE XYZ* tristimulus values of reference white.
    L_A
        Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken
        to be 20% of the luminance of a white object in the scene).
    Y_b
        Luminous factor of background :math:`Y_b` such as
        :math:`Y_b = 100 x L_b / L_w` where :math:`L_w` is the luminance of the
        light source and :math:`L_b` is the luminance of the background. For
        viewing images, :math:`Y_b` can be the average :math:`Y` value for the
        pixels in the entire image, or frequently, a :math:`Y` value of 20,
        approximate an :math:`L^*` of 50 is used.
    surround
        Surround viewing conditions induction factors.
    discount_illuminant
        Truth value indicating if the illuminant should be discounted.

    Returns
    -------
    :class:`colour.CAM_Specification_CIECAM02`
        *CIECAM02* colour appearance model specification.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``XYZ_w``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +----------------------------------+-----------------------\
+---------------+
    | **Range**                        | **Scale - Reference** \
| **Scale - 1** |
    +==================================+=======================\
+===============+
    | ``CAM_Specification_CIECAM02.J`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.C`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.h`` | [0, 360]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.s`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.Q`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.M`` | [0, 100]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+
    | ``CAM_Specification_CIECAM02.H`` | [0, 400]              \
| [0, 1]        |
    +----------------------------------+-----------------------\
+---------------+

    References
    ----------
    :cite:`Fairchild2004c`, :cite:`Luo2013`, :cite:`Moroneya`,
    :cite:`Wikipedia2007a`

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_w = np.array([95.05, 100.00, 108.88])
    >>> L_A = 318.31
    >>> Y_b = 20.0
    >>> surround = VIEWING_CONDITIONS_CIECAM02['Average']
    >>> XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround)  # doctest: +ELLIPSIS
    CAM_Specification_CIECAM02(J=41.7310911..., C=0.1047077..., \
h=219.0484326..., s=2.3603053..., Q=195.3713259..., M=0.1088421..., \
H=278.0607358..., HC=None)
    """

    XYZ = to_domain_100(XYZ)
    XYZ_w = to_domain_100(XYZ_w)
    _X_w, Y_w, _Z_w = tsplit(XYZ_w)
    L_A = as_float_array(L_A)
    Y_b = as_float_array(Y_b)

    n, F_L, N_bb, N_cb, z = viewing_condition_dependent_parameters(
        Y_b, Y_w, L_A)

    # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform
    # sharpened *RGB* values.
    RGB = vector_dot(CAT_CAT02, XYZ)
    RGB_w = vector_dot(CAT_CAT02, XYZ_w)

    # Computing degree of adaptation :math:`D`.
    D = (degree_of_adaptation(surround.F, L_A)
         if not discount_illuminant else ones(L_A.shape))

    # Computing full chromatic adaptation.
    RGB_c = full_chromatic_adaptation_forward(RGB, RGB_w, Y_w, D)
    RGB_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D)

    # Converting to *Hunt-Pointer-Estevez* colourspace.
    RGB_p = RGB_to_rgb(RGB_c)
    RGB_pw = RGB_to_rgb(RGB_wc)

    # Applying forward post-adaptation non-linear response compression.
    RGB_a = post_adaptation_non_linear_response_compression_forward(RGB_p, F_L)
    RGB_aw = post_adaptation_non_linear_response_compression_forward(
        RGB_pw, F_L)

    # Converting to preliminary cartesian coordinates.
    a, b = tsplit(opponent_colour_dimensions_forward(RGB_a))

    # Computing the *hue* angle :math:`h`.
    h = hue_angle(a, b)

    # Computing hue :math:`h` quadrature :math:`H`.
    H = hue_quadrature(h)
    # TODO: Compute hue composition.

    # Computing eccentricity factor *e_t*.
    e_t = eccentricity_factor(h)

    # Computing achromatic responses for the stimulus and the whitepoint.
    A = achromatic_response_forward(RGB_a, N_bb)
    A_w = achromatic_response_forward(RGB_aw, N_bb)

    # Computing the correlate of *Lightness* :math:`J`.
    J = lightness_correlate(A, A_w, surround.c, z)

    # Computing the correlate of *brightness* :math:`Q`.
    Q = brightness_correlate(surround.c, J, A_w, F_L)

    # Computing the correlate of *chroma* :math:`C`.
    C = chroma_correlate(J, n, surround.N_c, N_cb, e_t, a, b, RGB_a)

    # Computing the correlate of *colourfulness* :math:`M`.
    M = colourfulness_correlate(C, F_L)

    # Computing the correlate of *saturation* :math:`s`.
    s = saturation_correlate(M, Q)

    return CAM_Specification_CIECAM02(
        as_float(from_range_100(J)),
        as_float(from_range_100(C)),
        as_float(from_range_degrees(h)),
        as_float(from_range_100(s)),
        as_float(from_range_100(Q)),
        as_float(from_range_100(M)),
        as_float(from_range_degrees(H, 400)),
        None,
    )