Source code for colour.adaptation.cmccat2000

"""
CMCCAT2000 Chromatic Adaptation Model
=====================================

Defines the *CMCCAT2000* chromatic adaptation model objects:

-   :class:`colour.adaptation.InductionFactors_CMCCAT2000`
-   :class:`colour.VIEWING_CONDITIONS_CMCCAT2000`
-   :func:`colour.adaptation.chromatic_adaptation_forward_CMCCAT2000`
-   :func:`colour.adaptation.chromatic_adaptation_inverse_CMCCAT2000`
-   :func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`

References
----------
-   :cite:`Li2002a` : Li, C., Luo, M. R., Rigg, B., & Hunt, R. W. G. (2002).
    CMC 2000 chromatic adaptation transform: CMCCAT2000. Color Research &
    Application, 27(1), 49-58. doi:10.1002/col.10005
-   :cite:`Westland2012k` : Westland, S., Ripamonti, C., & Cheung, V. (2012).
    CMCCAT2000. In Computational Colour Science Using MATLAB (2nd ed., pp.
    83-86). ISBN:978-0-470-66569-5
"""

from __future__ import annotations

from typing import NamedTuple

import numpy as np

from colour.adaptation import CAT_CMCCAT2000
from colour.algebra import vector_dot
from colour.hints import ArrayLike, Literal, NDArrayFloat
from colour.utilities import (
    CanonicalMapping,
    as_float_array,
    from_range_100,
    to_domain_100,
    validate_method,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "CAT_INVERSE_CMCCAT2000",
    "InductionFactors_CMCCAT2000",
    "VIEWING_CONDITIONS_CMCCAT2000",
    "chromatic_adaptation_forward_CMCCAT2000",
    "chromatic_adaptation_inverse_CMCCAT2000",
    "chromatic_adaptation_CMCCAT2000",
]

CAT_INVERSE_CMCCAT2000: NDArrayFloat = np.linalg.inv(CAT_CMCCAT2000)
"""
Inverse *CMCCAT2000* chromatic adaptation transform.

CAT_INVERSE_CMCCAT2000
"""


[docs] class InductionFactors_CMCCAT2000(NamedTuple): """ *CMCCAT2000* chromatic adaptation model induction factors. Parameters ---------- F :math:`F` surround condition. References ---------- :cite:`Li2002a`, :cite:`Westland2012k` """ F: float
VIEWING_CONDITIONS_CMCCAT2000: CanonicalMapping = CanonicalMapping( { "Average": InductionFactors_CMCCAT2000(1), "Dim": InductionFactors_CMCCAT2000(0.8), "Dark": InductionFactors_CMCCAT2000(0.8), } ) VIEWING_CONDITIONS_CMCCAT2000.__doc__ = """ Reference *CMCCAT2000* chromatic adaptation model viewing conditions. References ---------- :cite:`Li2002a`, :cite:`Westland2012k` """
[docs] def chromatic_adaptation_forward_CMCCAT2000( XYZ: ArrayLike, XYZ_w: ArrayLike, XYZ_wr: ArrayLike, L_A1: ArrayLike, L_A2: ArrayLike, surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000[ "Average" ], ) -> NDArrayFloat: """ Adapt given stimulus *CIE XYZ* tristimulus values from test viewing conditions to reference viewing conditions using *CMCCAT2000* forward chromatic adaptation model. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the stimulus to adapt. XYZ_w Test viewing condition *CIE XYZ* tristimulus values of the whitepoint. XYZ_wr Reference viewing condition *CIE XYZ* tristimulus values of the whitepoint. L_A1 Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`. L_A2 Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`. surround Surround viewing conditions induction factors. Returns ------- :class:`numpy.ndarray` *CIE XYZ_c* tristimulus values of the stimulus corresponding colour. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_c`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Li2002a`, :cite:`Westland2012k` Examples -------- >>> XYZ = np.array([22.48, 22.74, 8.54]) >>> XYZ_w = np.array([111.15, 100.00, 35.20]) >>> XYZ_wr = np.array([94.81, 100.00, 107.30]) >>> L_A1 = 200 >>> L_A2 = 200 >>> chromatic_adaptation_forward_CMCCAT2000(XYZ, XYZ_w, XYZ_wr, L_A1, L_A2) ... # doctest: +ELLIPSIS array([ 19.5269832..., 23.0683396..., 24.9717522...]) """ XYZ = to_domain_100(XYZ) XYZ_w = to_domain_100(XYZ_w) XYZ_wr = to_domain_100(XYZ_wr) L_A1 = as_float_array(L_A1) L_A2 = as_float_array(L_A2) RGB = vector_dot(CAT_CMCCAT2000, XYZ) RGB_w = vector_dot(CAT_CMCCAT2000, XYZ_w) RGB_wr = vector_dot(CAT_CMCCAT2000, XYZ_wr) D = surround.F * ( 0.08 * np.log10(0.5 * (L_A1 + L_A2)) + 0.76 - 0.45 * (L_A1 - L_A2) / (L_A1 + L_A2) ) D = np.clip(D, 0, 1) a = D * XYZ_w[..., 1] / XYZ_wr[..., 1] RGB_c = RGB * (a[..., None] * (RGB_wr / RGB_w) + 1 - D[..., None]) XYZ_c = vector_dot(CAT_INVERSE_CMCCAT2000, RGB_c) return from_range_100(XYZ_c)
[docs] def chromatic_adaptation_inverse_CMCCAT2000( XYZ_c: ArrayLike, XYZ_w: ArrayLike, XYZ_wr: ArrayLike, L_A1: ArrayLike, L_A2: ArrayLike, surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000[ "Average" ], ) -> NDArrayFloat: """ Adapt given stimulus corresponding colour *CIE XYZ* tristimulus values from reference viewing conditions to test viewing conditions using *CMCCAT2000* inverse chromatic adaptation model. Parameters ---------- XYZ_c *CIE XYZ* tristimulus values of the stimulus to adapt. XYZ_w Test viewing condition *CIE XYZ* tristimulus values of the whitepoint. XYZ_wr Reference viewing condition *CIE XYZ* tristimulus values of the whitepoint. L_A1 Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`. L_A2 Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`. surround Surround viewing conditions induction factors. Returns ------- :class:`numpy.ndarray` *CIE XYZ_c* tristimulus values of the adapted stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_c`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Li2002a`, :cite:`Westland2012k` Examples -------- >>> XYZ_c = np.array([19.53, 23.07, 24.97]) >>> XYZ_w = np.array([111.15, 100.00, 35.20]) >>> XYZ_wr = np.array([94.81, 100.00, 107.30]) >>> L_A1 = 200 >>> L_A2 = 200 >>> chromatic_adaptation_inverse_CMCCAT2000( ... XYZ_c, XYZ_w, XYZ_wr, L_A1, L_A2 ... ) ... # doctest: +ELLIPSIS array([ 22.4839876..., 22.7419485..., 8.5393392...]) """ XYZ_c = to_domain_100(XYZ_c) XYZ_w = to_domain_100(XYZ_w) XYZ_wr = to_domain_100(XYZ_wr) L_A1 = as_float_array(L_A1) L_A2 = as_float_array(L_A2) RGB_c = vector_dot(CAT_CMCCAT2000, XYZ_c) RGB_w = vector_dot(CAT_CMCCAT2000, XYZ_w) RGB_wr = vector_dot(CAT_CMCCAT2000, XYZ_wr) D = surround.F * ( 0.08 * np.log10(0.5 * (L_A1 + L_A2)) + 0.76 - 0.45 * (L_A1 - L_A2) / (L_A1 + L_A2) ) D = np.clip(D, 0, 1) a = D * XYZ_w[..., 1] / XYZ_wr[..., 1] RGB = RGB_c / (a[..., None] * (RGB_wr / RGB_w) + 1 - D[..., None]) XYZ = vector_dot(CAT_INVERSE_CMCCAT2000, RGB) return from_range_100(XYZ)
[docs] def chromatic_adaptation_CMCCAT2000( XYZ: ArrayLike, XYZ_w: ArrayLike, XYZ_wr: ArrayLike, L_A1: ArrayLike, L_A2: ArrayLike, surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000[ "Average" ], direction: Literal["Forward", "Inverse"] | str = "Forward", ) -> NDArrayFloat: """ Adapt given stimulus *CIE XYZ* tristimulus values using given viewing conditions. This definition is a convenient wrapper around :func:`colour.adaptation.chromatic_adaptation_forward_CMCCAT2000` and :func:`colour.adaptation.chromatic_adaptation_inverse_CMCCAT2000`. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the stimulus to adapt. XYZ_w Source viewing condition *CIE XYZ* tristimulus values of the whitepoint. XYZ_wr Target viewing condition *CIE XYZ* tristimulus values of the whitepoint. L_A1 Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`. L_A2 Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`. surround Surround viewing conditions induction factors. direction Chromatic adaptation direction. Returns ------- :class:`numpy.ndarray` Adapted stimulus *CIE XYZ* tristimulus values. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Li2002a`, :cite:`Westland2012k` Examples -------- >>> XYZ = np.array([22.48, 22.74, 8.54]) >>> XYZ_w = np.array([111.15, 100.00, 35.20]) >>> XYZ_wr = np.array([94.81, 100.00, 107.30]) >>> L_A1 = 200 >>> L_A2 = 200 >>> chromatic_adaptation_CMCCAT2000( ... XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, direction="Forward" ... ) ... # doctest: +ELLIPSIS array([ 19.5269832..., 23.0683396..., 24.9717522...]) Using the *CMCCAT2000* inverse model: >>> XYZ = np.array([19.52698326, 23.06833960, 24.97175229]) >>> XYZ_w = np.array([111.15, 100.00, 35.20]) >>> XYZ_wr = np.array([94.81, 100.00, 107.30]) >>> L_A1 = 200 >>> L_A2 = 200 >>> chromatic_adaptation_CMCCAT2000( ... XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, direction="Inverse" ... ) ... # doctest: +ELLIPSIS array([ 22.48, 22.74, 8.54]) """ direction = validate_method( direction, ("Forward", "Inverse"), '"{0}" direction is invalid, it must be one of {1}!', ) if direction == "forward": return chromatic_adaptation_forward_CMCCAT2000( XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, surround ) else: return chromatic_adaptation_inverse_CMCCAT2000( XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, surround )