Coverage for colour/models/hdr_cie_lab.py: 100%
55 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"""
2hdr-CIELAB Colourspace
3======================
5Define the *hdr-CIELAB* colourspace transformations.
7- :attr:`colour.HDR_CIELAB_METHODS`
8- :func:`colour.XYZ_to_hdr_CIELab`
9- :func:`colour.hdr_CIELab_to_XYZ`
11References
12----------
13- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010).
14 hdr-CIELAB and hdr-IPT: Simple Models for Describing the Color of
15 High-Dynamic-Range and Wide-Color-Gamut Images. Proc. of Color and
16 Imaging Conference, 322-326. ISBN:978-1-62993-215-6
17- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011).
18 Brightness, lightness, and specifying color in high-dynamic-range scenes
19 and images. In S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image
20 Quality and System Performance VIII (p. 78670O). doi:10.1117/12.872075
21"""
23from __future__ import annotations
25import typing
27import numpy as np
29from colour.colorimetry import (
30 CCS_ILLUMINANTS,
31 lightness_Fairchild2010,
32 lightness_Fairchild2011,
33 luminance_Fairchild2010,
34 luminance_Fairchild2011,
35)
37if typing.TYPE_CHECKING:
38 from colour.hints import Literal
40from colour.hints import ( # noqa: TC001
41 ArrayLike,
42 Domain1,
43 Domain100,
44 NDArrayFloat,
45 Range1,
46 Range100,
47)
48from colour.models import xy_to_xyY, xyY_to_XYZ
49from colour.utilities import (
50 as_float_array,
51 domain_range_scale,
52 from_range_1,
53 from_range_100,
54 to_domain_1,
55 to_domain_100,
56 tsplit,
57 tstack,
58 validate_method,
59)
60from colour.utilities.documentation import DocstringTuple, is_documentation_building
62__author__ = "Colour Developers"
63__copyright__ = "Copyright 2013 Colour Developers"
64__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
65__maintainer__ = "Colour Developers"
66__email__ = "colour-developers@colour-science.org"
67__status__ = "Production"
69__all__ = [
70 "HDR_CIELAB_METHODS",
71 "exponent_hdr_CIELab",
72 "XYZ_to_hdr_CIELab",
73 "hdr_CIELab_to_XYZ",
74]
76HDR_CIELAB_METHODS: tuple = ("Fairchild 2010", "Fairchild 2011")
77if is_documentation_building(): # pragma: no cover
78 HDR_CIELAB_METHODS = DocstringTuple(HDR_CIELAB_METHODS)
79 HDR_CIELAB_METHODS.__doc__ = """
80Supported *hdr-CIELAB* colourspace computation methods.
82References
83----------
84:cite:`Fairchild2010`, :cite:`Fairchild2011`
85"""
88def exponent_hdr_CIELab(
89 Y_s: Domain1,
90 Y_abs: ArrayLike,
91 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011",
92) -> NDArrayFloat:
93 """
94 Compute the *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon`
95 exponent using the *Fairchild and Wyble (2010)* or *Fairchild and Chen
96 (2011)* methods.
98 Parameters
99 ----------
100 Y_s
101 Relative luminance :math:`Y_s` of the surround.
102 Y_abs
103 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
104 :math:`cd/m^2`.
105 method
106 Computation method.
108 Returns
109 -------
110 :class:`numpy.ndarray`
111 *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon` exponent.
113 Notes
114 -----
115 +------------+-----------------------+---------------+
116 | **Domain** | **Scale - Reference** | **Scale - 1** |
117 +============+=======================+===============+
118 | ``Y_s`` | 1 | 1 |
119 +------------+-----------------------+---------------+
121 Examples
122 --------
123 >>> exponent_hdr_CIELab(0.2, 100) # doctest: +ELLIPSIS
124 0.4738510...
125 >>> exponent_hdr_CIELab(0.2, 100, method="Fairchild 2010")
126 ... # doctest: +ELLIPSIS
127 1.8360198...
128 """
130 Y_s = to_domain_1(Y_s)
131 Y_abs = as_float_array(Y_abs)
132 method = validate_method(method, HDR_CIELAB_METHODS)
134 epsilon = 1.5 if method == "fairchild 2010" else 0.58
136 sf = 1.25 - 0.25 * (Y_s / 0.184)
137 lf = np.log(318) / np.log(Y_abs)
138 if method == "fairchild 2010":
139 epsilon *= sf * lf
140 else:
141 epsilon /= sf * lf
143 return epsilon
146def XYZ_to_hdr_CIELab(
147 XYZ: Domain1,
148 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
149 "D65"
150 ],
151 Y_s: Domain1 = 0.2,
152 Y_abs: ArrayLike = 100,
153 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011",
154) -> Range100:
155 """
156 Convert from *CIE XYZ* tristimulus values to *hdr-CIELAB* colourspace.
158 Parameters
159 ----------
160 XYZ
161 *CIE XYZ* tristimulus values.
162 illuminant
163 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
164 colourspace array.
165 Y_s
166 Relative luminance :math:`Y_s` of the surround.
167 Y_abs
168 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
169 :math:`cd/m^2`.
170 method
171 Computation method.
173 Returns
174 -------
175 :class:`numpy.ndarray`
176 *hdr-CIELAB* colourspace array.
178 Notes
179 -----
180 +----------------+-------------------------+---------------------+
181 | **Domain** | **Scale - Reference** | **Scale - 1** |
182 +================+=========================+=====================+
183 | ``XYZ`` | 1 | 1 |
184 +----------------+-------------------------+---------------------+
185 | ``illuminant`` | 1 | 1 |
186 +----------------+-------------------------+---------------------+
187 | ``Y_s`` | 1 | 1 |
188 +----------------+-------------------------+---------------------+
190 +----------------+-------------------------+---------------------+
191 | **Range** | **Scale - Reference** | **Scale - 1** |
192 +================+=========================+=====================+
193 | ``Lab_hdr`` | 100 | 1 |
194 +----------------+-------------------------+---------------------+
196 - Conversion to polar coordinates to compute the *chroma*
197 :math:`C_{hdr}` and *hue* :math:`h_{hdr}` correlates can be
198 safely performed with :func:`colour.Lab_to_LCHab` definition.
199 - Conversion to cartesian coordinates from the *Lightness*
200 :math:`L_{hdr}`, *chroma* :math:`C_{hdr}` and *hue*
201 :math:`h_{hdr}` correlates can be safely performed with
202 :func:`colour.LCHab_to_Lab` definition.
204 References
205 ----------
206 :cite:`Fairchild2010`, :cite:`Fairchild2011`
208 Examples
209 --------
210 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
211 >>> XYZ_to_hdr_CIELab(XYZ) # doctest: +ELLIPSIS
212 array([ 51.8700206..., 60.4763385..., 32.1455191...])
213 >>> XYZ_to_hdr_CIELab(XYZ, method="Fairchild 2010") # doctest: +ELLIPSIS
214 array([ 31.9962111..., 128.0076303..., 48.7695230...])
215 """
217 X, Y, Z = tsplit(to_domain_1(XYZ))
218 method = validate_method(method, HDR_CIELAB_METHODS)
220 X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant)))
222 if method == "fairchild 2010":
223 lightness_callable = lightness_Fairchild2010
224 else:
225 lightness_callable = lightness_Fairchild2011
227 e = exponent_hdr_CIELab(Y_s, Y_abs, method)
229 # Domain and range scaling has already been handled.
230 with domain_range_scale("ignore"):
231 L_hdr = lightness_callable(Y / Y_n, e)
232 a_hdr = 5 * (lightness_callable(X / X_n, e) - L_hdr)
233 b_hdr = 2 * (L_hdr - lightness_callable(Z / Z_n, e))
235 Lab_hdr = tstack([L_hdr, a_hdr, b_hdr])
237 return from_range_100(Lab_hdr)
240def hdr_CIELab_to_XYZ(
241 Lab_hdr: Domain100,
242 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
243 "D65"
244 ],
245 Y_s: Domain1 = 0.2,
246 Y_abs: ArrayLike = 100,
247 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011",
248) -> Range1:
249 """
250 Convert from *hdr-CIELAB* colourspace to *CIE XYZ* tristimulus values.
252 Parameters
253 ----------
254 Lab_hdr
255 *hdr-CIELAB* colourspace array.
256 illuminant
257 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
258 colourspace array.
259 Y_s
260 Relative luminance :math:`Y_s` of the surround.
261 Y_abs
262 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
263 :math:`cd/m^2`.
264 method
265 Computation method.
267 Returns
268 -------
269 :class:`numpy.ndarray`
270 *CIE XYZ* tristimulus values.
272 Notes
273 -----
274 +----------------+-------------------------+---------------------+
275 | **Domain** | **Scale - Reference** | **Scale - 1** |
276 +================+=========================+=====================+
277 | ``Lab_hdr`` | 100 | 1 |
278 +----------------+-------------------------+---------------------+
279 | ``illuminant`` | 1 | 1 |
280 +----------------+-------------------------+---------------------+
281 | ``Y_s`` | 1 | 1 |
282 +----------------+-------------------------+---------------------+
284 +----------------+-------------------------+---------------------+
285 | **Range** | **Scale - Reference** | **Scale - 1** |
286 +================+=========================+=====================+
287 | ``XYZ`` | 1 | 1 |
288 +----------------+-------------------------+---------------------+
290 References
291 ----------
292 :cite:`Fairchild2010`, :cite:`Fairchild2011`
294 Examples
295 --------
296 >>> Lab_hdr = np.array([51.87002062, 60.4763385, 32.14551912])
297 >>> hdr_CIELab_to_XYZ(Lab_hdr) # doctest: +ELLIPSIS
298 array([ 0.2065400..., 0.1219722..., 0.0513695...])
299 >>> Lab_hdr = np.array([31.99621114, 128.00763036, 48.76952309])
300 >>> hdr_CIELab_to_XYZ(Lab_hdr, method="Fairchild 2010")
301 ... # doctest: +ELLIPSIS
302 array([ 0.2065400..., 0.1219722..., 0.0513695...])
303 """
305 L_hdr, a_hdr, b_hdr = tsplit(to_domain_100(Lab_hdr))
306 method = validate_method(method, HDR_CIELAB_METHODS)
308 X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant)))
310 if method == "fairchild 2010":
311 luminance_callable = luminance_Fairchild2010
312 else:
313 luminance_callable = luminance_Fairchild2011
315 e = exponent_hdr_CIELab(Y_s, Y_abs, method)
317 # Domain and range scaling has already been handled.
318 with domain_range_scale("ignore"):
319 Y = luminance_callable(L_hdr, e) * Y_n
320 X = luminance_callable((a_hdr + 5 * L_hdr) / 5, e) * X_n
321 Z = luminance_callable((-b_hdr + 2 * L_hdr) / 2, e) * Z_n
323 XYZ = tstack([X, Y, Z])
325 return from_range_1(XYZ)