Coverage for models/hdr_ipt.py: 62%
52 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2hdr-IPT Colourspace
3===================
5Define the *hdr-IPT* colourspace transformations.
7- :attr:`colour.HDR_IPT_METHODS`
8- :func:`colour.XYZ_to_hdr_IPT`
9- :func:`colour.hdr_IPT_to_XYZ`
11References
12----------
13- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010). hdr-CIELAB
14 and hdr-IPT: Simple Models for Describing the Color of High-Dynamic-Range
15 and Wide-Color-Gamut Images. Proc. of Color and Imaging Conference,
16 322-326. ISBN:978-1-62993-215-6
17- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011). Brightness,
18 lightness, and specifying color in high-dynamic-range scenes and images. In
19 S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image Quality and
20 System Performance VIII (p. 78670O). doi:10.1117/12.872075
21"""
23from __future__ import annotations
25import typing
27import numpy as np
29from colour.algebra import vecmul
30from colour.colorimetry import (
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.ipt import (
49 MATRIX_IPT_IPT_TO_LMS_P,
50 MATRIX_IPT_LMS_P_TO_IPT,
51 MATRIX_IPT_LMS_TO_XYZ,
52 MATRIX_IPT_XYZ_TO_LMS,
53)
54from colour.utilities import (
55 as_float_array,
56 domain_range_scale,
57 from_range_1,
58 from_range_100,
59 to_domain_1,
60 to_domain_100,
61 validate_method,
62)
63from colour.utilities.documentation import DocstringTuple, is_documentation_building
65__author__ = "Colour Developers"
66__copyright__ = "Copyright 2013 Colour Developers"
67__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
68__maintainer__ = "Colour Developers"
69__email__ = "colour-developers@colour-science.org"
70__status__ = "Production"
72__all__ = [
73 "HDR_IPT_METHODS",
74 "exponent_hdr_IPT",
75 "XYZ_to_hdr_IPT",
76 "hdr_IPT_to_XYZ",
77]
79HDR_IPT_METHODS: tuple = ("Fairchild 2010", "Fairchild 2011")
80if is_documentation_building(): # pragma: no cover
81 HDR_IPT_METHODS = DocstringTuple(HDR_IPT_METHODS)
82 HDR_IPT_METHODS.__doc__ = """
83Supported *hdr-IPT* colourspace computation methods.
85References
86----------
87:cite:`Fairchild2010`, :cite:`Fairchild2011`
88"""
91def exponent_hdr_IPT(
92 Y_s: Domain1,
93 Y_abs: ArrayLike,
94 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011",
95) -> NDArrayFloat:
96 """
97 Compute the *hdr-IPT* colourspace *Lightness* :math:`\\epsilon` exponent
98 using the *Fairchild and Wyble (2010)* or *Fairchild and Chen (2011)*
99 methods.
101 Parameters
102 ----------
103 Y_s
104 Relative luminance :math:`Y_s` of the surround.
105 Y_abs
106 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
107 :math:`cd/m^2`.
108 method
109 Computation method.
111 Returns
112 -------
113 :class:`numpy.ndarray`
114 *hdr-IPT* colourspace *Lightness* :math:`\\epsilon` exponent.
116 Notes
117 -----
118 +------------+-----------------------+---------------+
119 | **Domain** | **Scale - Reference** | **Scale - 1** |
120 +============+=======================+===============+
121 | ``Y_s`` | 1 | 1 |
122 +------------+-----------------------+---------------+
124 Examples
125 --------
126 >>> exponent_hdr_IPT(0.2, 100) # doctest: +ELLIPSIS
127 0.4820209...
128 >>> exponent_hdr_IPT(0.2, 100, method="Fairchild 2010")
129 ... # doctest: +ELLIPSIS
130 1.6891383...
131 """
133 Y_s = to_domain_1(Y_s)
134 Y_abs = as_float_array(Y_abs)
135 method = validate_method(method, HDR_IPT_METHODS)
137 epsilon = 1.38 if method == "fairchild 2010" else 0.59
139 lf = np.log(318) / np.log(Y_abs)
140 sf = 1.25 - 0.25 * (Y_s / 0.184)
141 if method == "fairchild 2010":
142 epsilon *= sf * lf
143 else:
144 epsilon /= sf * lf
146 return epsilon
149def XYZ_to_hdr_IPT(
150 XYZ: Domain1,
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-IPT* colourspace.
158 Parameters
159 ----------
160 XYZ
161 *CIE XYZ* tristimulus values.
162 Y_s
163 Relative luminance :math:`Y_s` of the surround.
164 Y_abs
165 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
166 :math:`cd/m^2`.
167 method
168 Computation method.
170 Returns
171 -------
172 :class:`numpy.ndarray`
173 *hdr-IPT* colourspace array.
175 Notes
176 -----
177 +-------------+-------------------------+---------------------+
178 | **Domain** | **Scale - Reference** | **Scale - 1** |
179 +=============+=========================+=====================+
180 | ``XYZ`` | 1 | 1 |
181 +-------------+-------------------------+---------------------+
182 | ``Y_s`` | 1 | 1 |
183 +-------------+-------------------------+---------------------+
185 +-------------+-------------------------+---------------------+
186 | **Range** | **Scale - Reference** | **Scale - 1** |
187 +=============+=========================+=====================+
188 | ``IPT_hdr`` | 100 | 1 |
189 +-------------+-------------------------+---------------------+
191 - Input *CIE XYZ* tristimulus values must be adapted to
192 *CIE Standard Illuminant D Series* *D65*.
194 References
195 ----------
196 :cite:`Fairchild2010`, :cite:`Fairchild2011`
198 Examples
199 --------
200 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
201 >>> XYZ_to_hdr_IPT(XYZ) # doctest: +ELLIPSIS
202 array([ 48.3937634..., 42.4499020..., 22.0195403...])
203 >>> XYZ_to_hdr_IPT(XYZ, method="Fairchild 2010") # doctest: +ELLIPSIS
204 array([ 30.0287314..., 83.9384506..., 34.9028738...])
205 """
207 XYZ = to_domain_1(XYZ)
208 method = validate_method(method, HDR_IPT_METHODS)
210 if method == "fairchild 2010":
211 lightness_callable = lightness_Fairchild2010
212 else:
213 lightness_callable = lightness_Fairchild2011
215 e = exponent_hdr_IPT(Y_s, Y_abs, method)[..., None]
217 LMS = vecmul(MATRIX_IPT_XYZ_TO_LMS, XYZ)
219 # Domain and range scaling has already been handled.
220 with domain_range_scale("ignore"):
221 LMS_prime = np.sign(LMS) * np.abs(lightness_callable(LMS, e))
223 IPT_hdr = vecmul(MATRIX_IPT_LMS_P_TO_IPT, LMS_prime)
225 return from_range_100(IPT_hdr)
228def hdr_IPT_to_XYZ(
229 IPT_hdr: Domain100,
230 Y_s: Domain1 = 0.2,
231 Y_abs: ArrayLike = 100,
232 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011",
233) -> Range1:
234 """
235 Convert from *hdr-IPT* colourspace to *CIE XYZ* tristimulus values.
237 Parameters
238 ----------
239 IPT_hdr
240 *hdr-IPT* colourspace array.
241 Y_s
242 Relative luminance :math:`Y_s` of the surround.
243 Y_abs
244 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in
245 :math:`cd/m^2`.
246 method
247 Computation method.
249 Returns
250 -------
251 :class:`numpy.ndarray`
252 *CIE XYZ* tristimulus values.
254 Notes
255 -----
256 +-------------+-------------------------+---------------------+
257 | **Domain** | **Scale - Reference** | **Scale - 1** |
258 +=============+=========================+=====================+
259 | ``IPT_hdr`` | 100 | 1 |
260 +-------------+-------------------------+---------------------+
261 | ``Y_s`` | 1 | 1 |
262 +-------------+-------------------------+---------------------+
264 +-------------+-------------------------+---------------------+
265 | **Range** | **Scale - Reference** | **Scale - 1** |
266 +=============+=========================+=====================+
267 | ``XYZ`` | 1 | 1 |
268 +-------------+-------------------------+---------------------+
270 References
271 ----------
272 :cite:`Fairchild2010`, :cite:`Fairchild2011`
274 Examples
275 --------
276 >>> IPT_hdr = np.array([48.39376346, 42.44990202, 22.01954033])
277 >>> hdr_IPT_to_XYZ(IPT_hdr) # doctest: +ELLIPSIS
278 array([ 0.2065400..., 0.1219722..., 0.0513695...])
279 >>> IPT_hdr = np.array([30.02873147, 83.93845061, 34.90287382])
280 >>> hdr_IPT_to_XYZ(IPT_hdr, method="Fairchild 2010")
281 ... # doctest: +ELLIPSIS
282 array([ 0.2065400..., 0.1219722..., 0.0513695...])
283 """
285 IPT_hdr = to_domain_100(IPT_hdr)
286 method = validate_method(method, HDR_IPT_METHODS)
288 if method == "fairchild 2010":
289 luminance_callable = luminance_Fairchild2010
290 else:
291 luminance_callable = luminance_Fairchild2011
293 e = exponent_hdr_IPT(Y_s, Y_abs, method)[..., None]
295 LMS = vecmul(MATRIX_IPT_IPT_TO_LMS_P, IPT_hdr)
297 # Domain and range scaling has already be handled.
298 with domain_range_scale("ignore"):
299 LMS_prime = np.sign(LMS) * np.abs(luminance_callable(LMS, e))
301 XYZ = vecmul(MATRIX_IPT_LMS_TO_XYZ, LMS_prime)
303 return from_range_1(XYZ)