Coverage for colour/models/cie_luv.py: 100%
79 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"""
2CIE L*u*v* Colourspace
3======================
5Define the *CIE L\\*u\\*v\\** colourspace transformations.
7- :func:`colour.XYZ_to_Luv`
8- :func:`colour.Luv_to_XYZ`
9- :func:`colour.Luv_to_uv`
10- :func:`colour.uv_to_Luv`
11- :func:`colour.Luv_uv_to_xy`
12- :func:`colour.xy_to_Luv_uv`
13- :func:`colour.XYZ_to_CIE1976UCS`
14- :func:`colour.CIE1976UCS_to_XYZ`
16References
17----------
18- :cite:`CIETC1-482004j` : CIE TC 1-48. (2004). CIE 1976 uniform
19 chromaticity scale diagram (UCS diagram). In CIE 015:2004 Colorimetry, 3rd
20 Edition (p. 24). ISBN:978-3-901906-33-6
21- :cite:`CIETC1-482004m` : CIE TC 1-48. (2004). CIE 1976 uniform colour
22 spaces. In CIE 015:2004 Colorimetry, 3rd Edition (p. 24).
23 ISBN:978-3-901906-33-6
24- :cite:`Wikipedia2007b` : Wikipedia. (2007). CIELUV. Retrieved February 24,
25 2014, from http://en.wikipedia.org/wiki/CIELUV
26- :cite:`Wikipedia2007d` : Wikipedia. (2007). The reverse transformation.
27 Retrieved February 24, 2014, from
28 http://en.wikipedia.org/wiki/CIELUV#The_reverse_transformation
29"""
31from __future__ import annotations
33import numpy as np
35from colour.algebra import sdiv, sdiv_mode
36from colour.colorimetry import CCS_ILLUMINANTS, lightness_CIE1976, luminance_CIE1976
37from colour.hints import ( # noqa: TC001
38 Annotated,
39 ArrayLike,
40 Domain1,
41 Domain100,
42 NDArrayFloat,
43 Range1,
44 Range100,
45)
46from colour.models import xy_to_xyY, xyY_to_XYZ
47from colour.utilities import (
48 domain_range_scale,
49 from_range_1,
50 from_range_100,
51 get_domain_range_scale,
52 optional,
53 to_domain_1,
54 to_domain_100,
55 tsplit,
56 tstack,
57)
59__author__ = "Colour Developers"
60__copyright__ = "Copyright 2013 Colour Developers"
61__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
62__maintainer__ = "Colour Developers"
63__email__ = "colour-developers@colour-science.org"
64__status__ = "Production"
66__all__ = [
67 "XYZ_to_Luv",
68 "Luv_to_XYZ",
69 "Luv_to_uv",
70 "uv_to_Luv",
71 "Luv_uv_to_xy",
72 "xy_to_Luv_uv",
73 "XYZ_to_CIE1976UCS",
74 "CIE1976UCS_to_XYZ",
75]
78def XYZ_to_Luv(
79 XYZ: Domain1,
80 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
81 "D65"
82 ],
83) -> Range100:
84 """
85 Convert from *CIE XYZ* tristimulus values to *CIE L\\*u\\*v\\**
86 colourspace.
88 Parameters
89 ----------
90 XYZ
91 *CIE XYZ* tristimulus values.
92 illuminant
93 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
94 colourspace array.
96 Returns
97 -------
98 :class:`numpy.ndarray`
99 *CIE L\\*u\\*v\\** colourspace array.
101 Notes
102 -----
103 +----------------+-----------------------+-----------------+
104 | **Domain** | **Scale - Reference** | **Scale - 1** |
105 +================+=======================+=================+
106 | ``XYZ`` | 1 | 1 |
107 +----------------+-----------------------+-----------------+
108 | ``illuminant`` | 1 | 1 |
109 +----------------+-----------------------+-----------------+
111 +----------------+-----------------------+-----------------+
112 | **Range** | **Scale - Reference** | **Scale - 1** |
113 +================+=======================+=================+
114 | ``Luv`` | 100 | 1 |
115 +----------------+-----------------------+-----------------+
117 References
118 ----------
119 :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b`
121 Examples
122 --------
123 >>> import numpy as np
124 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
125 >>> XYZ_to_Luv(XYZ) # doctest: +ELLIPSIS
126 array([ 41.5278752..., 96.8362605..., 17.7521014...])
127 """
129 X, Y, Z = tsplit(to_domain_1(XYZ))
131 X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant)))
133 with domain_range_scale("ignore"):
134 L = lightness_CIE1976(Y, Y_r)
136 X_Y_Z = X + 15 * Y + 3 * Z
137 X_r_Y_r_Z_r = X_r + 15 * Y_r + 3 * Z_r
139 with sdiv_mode():
140 u = 13 * L * ((4 * sdiv(X, X_Y_Z)) - (4 * sdiv(X_r, X_r_Y_r_Z_r)))
141 v = 13 * L * ((9 * sdiv(Y, X_Y_Z)) - (9 * sdiv(Y_r, X_r_Y_r_Z_r)))
143 Luv = tstack([L, u, v])
145 return from_range_100(Luv)
148def Luv_to_XYZ(
149 Luv: Domain100,
150 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
151 "D65"
152 ],
153) -> Range1:
154 """
155 Convert from *CIE L\\*u\\*v\\** colourspace to *CIE XYZ* tristimulus
156 values.
158 Parameters
159 ----------
160 Luv
161 *CIE L\\*u\\*v\\** colourspace array.
162 illuminant
163 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
164 colourspace array.
166 Returns
167 -------
168 :class:`numpy.ndarray`
169 *CIE XYZ* tristimulus values.
171 Notes
172 -----
173 +----------------+-----------------------+-----------------+
174 | **Domain** | **Scale - Reference** | **Scale - 1** |
175 +================+=======================+=================+
176 | ``Luv`` | 100 | 1 |
177 +----------------+-----------------------+-----------------+
178 | ``illuminant`` | 1 | 1 |
179 +----------------+-----------------------+-----------------+
181 +----------------+-----------------------+-----------------+
182 | **Range** | **Scale - Reference** | **Scale - 1** |
183 +================+=======================+=================+
184 | ``XYZ`` | 1 | 1 |
185 +----------------+-----------------------+-----------------+
187 References
188 ----------
189 :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b`
191 Examples
192 --------
193 >>> import numpy as np
194 >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149])
195 >>> Luv_to_XYZ(Luv) # doctest: +ELLIPSIS
196 array([ 0.2065400..., 0.1219722..., 0.0513695...])
197 """
199 L, u, v = tsplit(to_domain_100(Luv))
201 X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant)))
203 with domain_range_scale("ignore"):
204 Y = luminance_CIE1976(L, Y_r)
206 X_r_Y_r_Z_r = X_r + 15 * Y_r + 3 * Z_r
208 with sdiv_mode():
209 a_1 = u + 13 * L * (4 * sdiv(X_r, X_r_Y_r_Z_r))
210 d_1 = v + 13 * L * (9 * sdiv(Y_r, X_r_Y_r_Z_r))
212 a = 1 / 3 * (52 * sdiv(L, a_1) - 1)
213 b = -5 * Y
214 c = -1 / 3
215 d = Y * (39 * sdiv(L, d_1) - 5)
217 X = sdiv(d - b, a - c)
219 Z = X * a + b
221 XYZ = tstack([X, Y, Z])
223 return from_range_1(XYZ)
226def Luv_to_uv(
227 Luv: Domain100,
228 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
229 "D65"
230 ],
231) -> NDArrayFloat:
232 """
233 Convert from *CIE L\\*u\\*v\\** colourspace to :math:`uv^p` chromaticity
234 coordinates.
236 Parameters
237 ----------
238 Luv
239 *CIE L\\*u\\*v\\** colourspace array.
240 illuminant
241 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
242 colourspace array.
244 Returns
245 -------
246 :class:`numpy.ndarray`
247 :math:`uv^p` chromaticity coordinates.
249 Notes
250 -----
251 +----------------+-----------------------+-----------------+
252 | **Domain** | **Scale - Reference** | **Scale - 1** |
253 +================+=======================+=================+
254 | ``Luv`` | 100 | 1 |
255 +----------------+-----------------------+-----------------+
256 | ``illuminant`` | 1 | 1 |
257 +----------------+-----------------------+-----------------+
259 References
260 ----------
261 :cite:`CIETC1-482004j`
263 Examples
264 --------
265 >>> import numpy as np
266 >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149])
267 >>> Luv_to_uv(Luv) # doctest: +ELLIPSIS
268 array([ 0.3772021..., 0.5012026...])
269 """
271 Luv = to_domain_100(Luv)
273 X, Y, Z = tsplit(Luv_to_XYZ(Luv, illuminant))
275 X_Y_Z = X + 15 * Y + 3 * Z
277 with sdiv_mode():
278 return tstack([4 * sdiv(X, X_Y_Z), 9 * sdiv(Y, X_Y_Z)])
281def uv_to_Luv(
282 uv: ArrayLike,
283 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
284 "D65"
285 ],
286 L: Domain100 | None = None,
287) -> Range100:
288 """
289 Convert from :math:`uv^p` chromaticity coordinates to *CIE L\\*u\\*v\\**
290 colourspace by extending the array's last dimension with the specified
291 :math:`L^*` *Lightness*.
293 Parameters
294 ----------
295 uv
296 :math:`uv^p` chromaticity coordinates.
297 illuminant
298 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
299 colourspace array.
300 L
301 Optional :math:`L^*` *Lightness* value used to construct the
302 intermediate *CIE XYZ* colourspace array, the default :math:`L^*`
303 *Lightness* value is 100.
305 Returns
306 -------
307 :class:`numpy.ndarray`
308 *CIE L\\*u\\*v\\** colourspace array.
310 Notes
311 -----
312 +----------------+-----------------------+-----------------+
313 | **Range** | **Scale - Reference** | **Scale - 1** |
314 +================+=======================+=================+
315 | ``Luv`` | 100 | 1 |
316 +----------------+-----------------------+-----------------+
317 | ``illuminant`` | 100 | 1 |
318 +----------------+-----------------------+-----------------+
320 References
321 ----------
322 :cite:`CIETC1-482004j`
324 Examples
325 --------
326 >>> import numpy as np
327 >>> uv = np.array([0.37720213, 0.50120264])
328 >>> uv_to_Luv(uv, L=41.5278752) # doctest: +ELLIPSIS
329 array([ 41.5278752..., 96.8362609..., 17.7521029...])
330 """
332 u, v = tsplit(uv)
333 L = to_domain_100(
334 optional(L, 100 if get_domain_range_scale() == "reference" else 1)
335 )
337 _X_r, Y_r, _Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant)))
339 with domain_range_scale("ignore"):
340 Y = luminance_CIE1976(L, Y_r)
342 with sdiv_mode():
343 X = sdiv(9 * Y * u, 4 * v)
344 Z = sdiv(Y * (-3 * u - 20 * v + 12), 4 * v)
346 XYZ = tstack([X, np.resize(Y, u.shape), Z])
348 return XYZ_to_Luv(from_range_1(XYZ), illuminant)
351def Luv_uv_to_xy(uv: ArrayLike) -> NDArrayFloat:
352 """
353 Convert from *CIE L\\*u\\*v\\** colourspace :math:`u'v'` chromaticity
354 coordinates to *CIE xy* chromaticity coordinates.
356 Parameters
357 ----------
358 uv
359 *CIE L\\*u\\*v\\* u"v"* chromaticity coordinates.
361 Returns
362 -------
363 :class:`numpy.ndarray`
364 *CIE xy* chromaticity coordinates.
366 References
367 ----------
368 :cite:`Wikipedia2007d`
370 Examples
371 --------
372 >>> import numpy as np
373 >>> uv = np.array([0.37720213, 0.50120264])
374 >>> Luv_uv_to_xy(uv) # doctest: +ELLIPSIS
375 array([ 0.5436955..., 0.3210794...])
376 """
378 u, v = tsplit(uv)
380 d = 6 * u - 16 * v + 12
382 with sdiv_mode():
383 return tstack([sdiv(9 * u, d), sdiv(4 * v, d)])
386def xy_to_Luv_uv(xy: ArrayLike) -> NDArrayFloat:
387 """
388 Convert from *CIE xy* chromaticity coordinates to *CIE L\\*u\\*v\\**
389 colourspace :math:`u'v'` chromaticity coordinates.
391 Parameters
392 ----------
393 xy
394 *CIE xy* chromaticity coordinates.
396 Returns
397 -------
398 :class:`numpy.ndarray`
399 *CIE L\\*u\\*v\\* u"v"* chromaticity coordinates.
401 References
402 ----------
403 :cite:`Wikipedia2007b`
405 Examples
406 --------
407 >>> import numpy as np
408 >>> xy = np.array([0.54369558, 0.32107944])
409 >>> xy_to_Luv_uv(xy) # doctest: +ELLIPSIS
410 array([ 0.3772021..., 0.5012026...])
411 """
413 x, y = tsplit(xy)
415 d = -2 * x + 12 * y + 3
417 with sdiv_mode():
418 return tstack([sdiv(4 * x, d), sdiv(9 * y, d)])
421def XYZ_to_CIE1976UCS(
422 XYZ: Domain1,
423 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
424 "D65"
425 ],
426) -> Annotated[NDArrayFloat, (1, 1, 100)]:
427 """
428 Convert from *CIE XYZ* tristimulus values to :math:`uv^pL^*` colourspace.
430 This colourspace combines the :math:`uv^p` chromaticity
431 coordinates with the *Lightness* :math:`L^{*}` from the
432 *CIE L*u*v** colourspace.
434 It is a convenient definition for use with the
435 *CIE 1976 UCS Chromaticity Diagram*.
437 Parameters
438 ----------
439 XYZ
440 *CIE XYZ* tristimulus values.
441 illuminant
442 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
443 colourspace array.
445 Returns
446 -------
447 :class:`numpy.ndarray`
448 :math:`uv^pL^*` colourspace array.
450 Notes
451 -----
452 +----------------+-----------------------+-----------------+
453 | **Domain** | **Scale - Reference** | **Scale - 1** |
454 +================+=======================+=================+
455 | ``XYZ`` | 1 | 1 |
456 +----------------+-----------------------+-----------------+
457 | ``illuminant`` | 1 | 1 |
458 +----------------+-----------------------+-----------------+
460 +----------------+-----------------------+-----------------+
461 | **Range** | **Scale - Reference** | **Scale - 1** |
462 +================+=======================+=================+
463 | ``uvL`` | ``u`` : 1 | ``u`` : 1 |
464 | | | |
465 | | ``v`` : 1 | ``v`` : 1 |
466 | | | |
467 | | ``L`` : 100 | ``L`` : 1 |
468 +----------------+-----------------------+-----------------+
470 Examples
471 --------
472 >>> import numpy as np
473 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
474 >>> XYZ_to_CIE1976UCS(XYZ) # doctest: +ELLIPSIS
475 array([ 0.3772021..., 0.5012026..., 41.5278752...])
476 """
478 Luv = XYZ_to_Luv(XYZ, illuminant)
480 L, _u, _v = tsplit(Luv)
482 u, v = tsplit(Luv_to_uv(Luv, illuminant))
484 return tstack([u, v, L])
487def CIE1976UCS_to_XYZ(
488 uvL: Annotated[ArrayLike, (1, 1, 100)],
489 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
490 "D65"
491 ],
492) -> Range1:
493 """
494 Convert from :math:`uv^pL^*` colourspace to *CIE XYZ* tristimulus values.
496 This colourspace combines the :math:`uv^p` chromaticity
497 coordinates with the *Lightness* :math:`L^{*}` from the
498 *CIE L*u*v** colourspace.
500 It is a convenient definition for use with the
501 *CIE 1976 UCS Chromaticity Diagram*.
503 Parameters
504 ----------
505 uvL
506 :math:`uv^pL^*` colourspace array.
507 illuminant
508 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
509 colourspace array.
511 Returns
512 -------
513 :class:`numpy.ndarray`
514 *CIE XYZ* tristimulus values.
516 Notes
517 -----
518 +----------------+-----------------------+-----------------+
519 | **Domain** | **Scale - Reference** | **Scale - 1** |
520 +================+=======================+=================+
521 | ``uvL`` | ``u`` : 1 | ``u`` : 1 |
522 | | | |
523 | | ``v`` : 1 | ``v`` : 1 |
524 | | | |
525 | | ``L`` : 100 | ``L`` : 1 |
526 +----------------+-----------------------+-----------------+
527 | ``illuminant`` | 1 | 1 |
528 +----------------+-----------------------+-----------------+
530 +----------------+-----------------------+-----------------+
531 | **Range** | **Scale - Reference** | **Scale - 1** |
532 +================+=======================+=================+
533 | ``XYZ`` | 1 | 1 |
534 +----------------+-----------------------+-----------------+
536 Examples
537 --------
538 >>> import numpy as np
539 >>> uvL = np.array([0.37720213, 0.50120264, 41.52787529])
540 >>> CIE1976UCS_to_XYZ(uvL) # doctest: +ELLIPSIS
541 array([ 0.2065400..., 0.1219722..., 0.0513695...])
542 """
544 u, v, L = tsplit(uvL)
546 _L, u, v = tsplit(uv_to_Luv(tstack([u, v]), illuminant, L))
548 return Luv_to_XYZ(tstack([L, u, v]), illuminant)