Coverage for colour/models/icacb.py: 100%
31 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2:math:`IC_AC_B` Colourspace
3===========================
5Define the :math:`IC_AC_B` colourspace transformations.
7- :func:`colour.XYZ_to_ICaCb`
8- :func:`colour.ICaCb_to_XYZ`
10References
11----------
12- :cite:`Frohlich2017` : Frohlich, J. (2017). Encoding high dynamic range
13 and wide color gamut imagery. doi:10.18419/OPUS-9664
14"""
16from __future__ import annotations
18import numpy as np
20from colour.hints import ( # noqa: TC001
21 ArrayLike,
22 Domain1,
23 NDArrayFloat,
24 Range1,
25)
26from colour.models import Iab_to_XYZ, XYZ_to_Iab
27from colour.models.rgb.transfer_functions import eotf_inverse_ST2084, eotf_ST2084
28from colour.utilities import domain_range_scale
30__author__ = "Colour Developers"
31__copyright__ = "Copyright 2013 Colour Developers"
32__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
33__maintainer__ = "Colour Developers"
34__email__ = "colour-developers@colour-science.org"
35__status__ = "Production"
37__all__ = [
38 "MATRIX_ICACB_XYZ_TO_LMS",
39 "MATRIX_ICACB_LMS_TO_XYZ",
40 "MATRIX_ICACB_XYZ_TO_LMS_2",
41 "MATRIX_ICACB_LMS_TO_XYZ_2",
42 "XYZ_to_ICaCb",
43 "ICaCb_to_XYZ",
44]
46MATRIX_ICACB_XYZ_TO_LMS: NDArrayFloat = np.array(
47 [
48 [0.37613, 0.70431, -0.05675],
49 [-0.21649, 1.14744, 0.05356],
50 [0.02567, 0.16713, 0.74235],
51 ]
52)
53"""*CIE XYZ* tristimulus values to normalised cone responses matrix."""
55MATRIX_ICACB_LMS_TO_XYZ: NDArrayFloat = np.linalg.inv(MATRIX_ICACB_XYZ_TO_LMS)
56"""Normalised cone responses to *CIE XYZ* tristimulus values matrix."""
58MATRIX_ICACB_XYZ_TO_LMS_2: NDArrayFloat = np.array(
59 [
60 [0.4949, 0.5037, 0.0015],
61 [4.2854, -4.5462, 0.2609],
62 [0.3605, 1.1499, -1.5105],
63 ]
64)
65"""Normalised non-linear cone responses to :math:`IC_AC_B` colourspace matrix."""
67MATRIX_ICACB_LMS_TO_XYZ_2: NDArrayFloat = np.linalg.inv(MATRIX_ICACB_XYZ_TO_LMS_2)
68""":math:`IC_AC_B` to normalised non-linear cone responses colourspace matrix."""
71def XYZ_to_ICaCb(XYZ: Domain1) -> Range1:
72 """
73 Convert from *CIE XYZ* tristimulus values to :math:`IC_AC_B` colourspace.
75 Parameters
76 ----------
77 XYZ
78 *CIE XYZ* tristimulus values.
80 Returns
81 -------
82 :class:`numpy.ndarray`
83 :math:`IC_AC_B` colourspace array.
85 Notes
86 -----
87 +------------+-----------------------+-----------------+
88 | **Domain** | **Scale - Reference** | **Scale - 1** |
89 +============+=======================+=================+
90 | ``XYZ`` | 1 | 1 |
91 +------------+-----------------------+-----------------+
93 +------------+-----------------------+-----------------+
94 | **Range** | **Scale - Reference** | **Scale - 1** |
95 +============+=======================+=================+
96 | ``ICaCb`` | 1 | 1 |
97 +------------+-----------------------+-----------------+
99 - Input *CIE XYZ* tristimulus values must be adapted to
100 *CIE Standard Illuminant D Series* *D65*.
102 References
103 ----------
104 :cite:`Frohlich2017`
106 Examples
107 --------
108 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
109 >>> XYZ_to_ICaCb(XYZ) # doctest: +ELLIPSIS
110 array([ 0.0687529..., 0.0575335..., 0.0208154...])
111 """
113 def LMS_to_LMS_p_callable(LMS: ArrayLike) -> NDArrayFloat:
114 """
115 Callable applying the forward non-linearity to the :math:`LMS`
116 colourspace array.
117 """
119 with domain_range_scale("ignore"):
120 return eotf_inverse_ST2084(LMS)
122 return XYZ_to_Iab(
123 XYZ,
124 LMS_to_LMS_p_callable,
125 MATRIX_ICACB_XYZ_TO_LMS,
126 MATRIX_ICACB_XYZ_TO_LMS_2,
127 )
130def ICaCb_to_XYZ(ICaCb: Domain1) -> Range1:
131 """
132 Convert from :math:`IC_AC_B` colourspace to *CIE XYZ* tristimulus values.
134 Parameters
135 ----------
136 ICaCb
137 :math:`IC_AC_B` colourspace array.
139 Returns
140 -------
141 :class:`numpy.ndarray`
142 *CIE XYZ* tristimulus values.
144 Notes
145 -----
146 +------------+-----------------------+-----------------+
147 | **Domain** | **Scale - Reference** | **Scale - 1** |
148 +============+=======================+=================+
149 | ``ICaCb`` | 1 | 1 |
150 +------------+-----------------------+-----------------+
152 +------------+-----------------------+-----------------+
153 | **Range** | **Scale - Reference** | **Scale - 1** |
154 +============+=======================+=================+
155 | ``XYZ`` | 1 | 1 |
156 +------------+-----------------------+-----------------+
158 - Output *CIE XYZ* tristimulus values are adapted to
159 *CIE Standard Illuminant D Series* *D65*.
161 References
162 ----------
163 :cite:`Frohlich2017`
165 Examples
166 --------
167 >>> ICaCb = np.array([0.06875297, 0.05753352, 0.02081548])
168 >>> ICaCb_to_XYZ(ICaCb) # doctest: +ELLIPSIS
169 array([ 0.2065400..., 0.1219722..., 0.0513695...])
170 """
172 def LMS_p_to_LMS_callable(LMS_p: ArrayLike) -> NDArrayFloat:
173 """
174 Callable applying the reverse non-linearity to the :math:`LMS_p`
175 colourspace array.
176 """
178 with domain_range_scale("ignore"):
179 return eotf_ST2084(LMS_p)
181 return Iab_to_XYZ(
182 ICaCb,
183 LMS_p_to_LMS_callable,
184 MATRIX_ICACB_LMS_TO_XYZ_2,
185 MATRIX_ICACB_LMS_TO_XYZ,
186 )