Coverage for colour/colorimetry/lightness.py: 100%
74 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"""
2Lightness :math:`L`
3===================
5Define the *lightness* :math:`L` computation methods.
7- :func:`colour.colorimetry.lightness_Glasser1958`: *Lightness* :math:`L`
8 computation of the specified *luminance* :math:`Y` using
9 *Glasser, Mckinney, Reilly and Schnelle (1958)* method.
10- :func:`colour.colorimetry.lightness_Wyszecki1963`: *Lightness*
11 :math:`W` computation of the specified *luminance* :math:`Y` using
12 *Wyszecki (1963)* method.
13- :func:`colour.colorimetry.lightness_CIE1976`: *Lightness* :math:`L^*`
14 computation of the specified *luminance* :math:`Y` as per *CIE 1976*
15 recommendation.
16- :func:`colour.colorimetry.lightness_Fairchild2010`: *Lightness*
17 :math:`L_{hdr}` computation of the specified *luminance* :math:`Y` using
18 *Fairchild and Wyble (2010)* method.
19- :func:`colour.colorimetry.lightness_Fairchild2011`: *Lightness*
20 :math:`L_{hdr}` computation of the specified *luminance* :math:`Y` using
21 *Fairchild and Chen (2011)* method.
22- :func:`colour.colorimetry.lightness_Abebe2017`: *Lightness* :math:`L`
23 computation of the specified *luminance* :math:`Y` using
24 *Abebe, Pouli, Larabi and Reinhard (2017)* adaptive method for
25 high-dynamic-range imaging.
26- :attr:`colour.LIGHTNESS_METHODS`: Supported *Lightness* :math:`L`
27 computation methods.
28- :func:`colour.lightness`: *Lightness* :math:`L` computation of
29 specified *luminance* :math:`Y` using the specified method.
31References
32----------
33- :cite:`Abebe2017` : Abebe, M. A., Pouli, T., Larabi, M.-C., & Reinhard,
34 E. (2017). Perceptual Lightness Modeling for High-Dynamic-Range Imaging.
35 ACM Transactions on Applied Perception, 15(1), 1-19. doi:10.1145/3086577
36- :cite:`CIETC1-482004m` : CIE TC 1-48. (2004). CIE 1976 uniform colour
37 spaces. In CIE 015:2004 Colorimetry, 3rd Edition (p. 24).
38 ISBN:978-3-901906-33-6
39- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010). hdr-CIELAB
40 and hdr-IPT: Simple Models for Describing the Color of High-Dynamic-Range
41 and Wide-Color-Gamut Images. Proc. of Color and Imaging Conference,
42 322-326. ISBN:978-1-62993-215-6
43- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011). Brightness,
44 lightness, and specifying color in high-dynamic-range scenes and images. In
45 S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image Quality and
46 System Performance VIII (p. 78670O). doi:10.1117/12.872075
47- :cite:`Glasser1958a` : Glasser, L. G., McKinney, A. H., Reilly, C. D., &
48 Schnelle, P. D. (1958). Cube-Root Color Coordinate System. Journal of the
49 Optical Society of America, 48(10), 736. doi:10.1364/JOSA.48.000736
50- :cite:`Wikipedia2007c` : Nayatani, Y., Sobagaki, H., & Yano, K. H. T.
51 (1995). Lightness dependency of chroma scales of a nonlinear
52 color-appearance model and its latest formulation. Color Research &
53 Application, 20(3), 156-167. doi:10.1002/col.5080200305
54- :cite:`Wyszecki1963b` : Wyszecki, Günter. (1963). Proposal for a New
55 Color-Difference Formula. Journal of the Optical Society of America,
56 53(11), 1318. doi:10.1364/JOSA.53.001318
57- :cite:`Wyszecki2000bd` : Wyszecki, Günther, & Stiles, W. S. (2000). CIE
58 1976 (L*u*v*)-Space and Color-Difference Formula. In Color Science:
59 Concepts and Methods, Quantitative Data and Formulae (p. 167). Wiley.
60 ISBN:978-0-471-39918-6
61"""
63from __future__ import annotations
65import typing
67import numpy as np
69from colour.algebra import spow
70from colour.biochemistry import (
71 reaction_rate_MichaelisMenten_Abebe2017,
72 reaction_rate_MichaelisMenten_Michaelis1913,
73)
75if typing.TYPE_CHECKING:
76 from colour.hints import Any, Literal
78from colour.hints import ( # noqa: TC001
79 ArrayLike,
80 Domain1,
81 Domain100,
82 NDArrayFloat,
83 Range100,
84)
85from colour.utilities import (
86 CanonicalMapping,
87 as_float,
88 as_float_array,
89 filter_kwargs,
90 from_range_100,
91 get_domain_range_scale,
92 optional,
93 to_domain_1,
94 to_domain_100,
95 usage_warning,
96 validate_method,
97)
99__author__ = "Colour Developers"
100__copyright__ = "Copyright 2013 Colour Developers"
101__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
102__maintainer__ = "Colour Developers"
103__email__ = "colour-developers@colour-science.org"
104__status__ = "Production"
106__all__ = [
107 "lightness_Glasser1958",
108 "lightness_Wyszecki1963",
109 "intermediate_lightness_function_CIE1976",
110 "lightness_CIE1976",
111 "lightness_Fairchild2010",
112 "lightness_Fairchild2011",
113 "lightness_Abebe2017",
114 "LIGHTNESS_METHODS",
115 "lightness",
116]
119def lightness_Glasser1958(Y: Domain100) -> Range100:
120 """
121 Compute *lightness* :math:`L` from the specified *luminance* :math:`Y` using
122 the *Glasser et al. (1958)* method.
124 Parameters
125 ----------
126 Y
127 *Luminance* :math:`Y`.
129 Returns
130 -------
131 :class:`numpy.ndarray`
132 *Lightness* :math:`L`.
134 Notes
135 -----
136 +------------+-----------------------+---------------+
137 | **Domain** | **Scale - Reference** | **Scale - 1** |
138 +============+=======================+===============+
139 | ``Y`` | 100 | 1 |
140 +------------+-----------------------+---------------+
142 +------------+-----------------------+---------------+
143 | **Range** | **Scale - Reference** | **Scale - 1** |
144 +============+=======================+===============+
145 | ``L`` | 100 | 1 |
146 +------------+-----------------------+---------------+
148 References
149 ----------
150 :cite:`Glasser1958a`
152 Examples
153 --------
154 >>> lightness_Glasser1958(12.19722535) # doctest: +ELLIPSIS
155 39.8351264...
156 """
158 Y = to_domain_100(Y)
160 L = 25.29 * spow(Y, 1 / 3) - 18.38
162 return as_float(from_range_100(L))
165def lightness_Wyszecki1963(
166 Y: Domain100,
167) -> Range100:
168 """
169 Compute *lightness* :math:`W` from the specified *luminance* :math:`Y`
170 using the *Wyszecki (1963)* method.
172 Parameters
173 ----------
174 Y
175 *Luminance* :math:`Y`.
177 Returns
178 -------
179 :class:`numpy.ndarray`
180 *Lightness* :math:`W`.
182 Notes
183 -----
184 +------------+-----------------------+---------------+
185 | **Domain** | **Scale - Reference** | **Scale - 1** |
186 +============+=======================+===============+
187 | ``Y`` | 100 | 1 |
188 +------------+-----------------------+---------------+
190 +------------+-----------------------+---------------+
191 | **Range** | **Scale - Reference** | **Scale - 1** |
192 +============+=======================+===============+
193 | ``W`` | 100 | 1 |
194 +------------+-----------------------+---------------+
196 References
197 ----------
198 :cite:`Wyszecki1963b`
200 Examples
201 --------
202 >>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS
203 40.5475745...
204 """
206 Y = to_domain_100(Y)
208 if np.any(Y < 1) or np.any(Y > 98):
209 usage_warning(
210 '"W*" Lightness computation is only applicable for '
211 '1% < "Y" < 98%, unpredictable results may occur!'
212 )
214 W = 25 * spow(Y, 1 / 3) - 17
216 return as_float(from_range_100(W))
219def intermediate_lightness_function_CIE1976(
220 Y: ArrayLike, Y_n: ArrayLike = 100
221) -> NDArrayFloat:
222 """
223 Compute the intermediate value :math:`f(Y/Y_n)` from the specified *luminance*
224 :math:`Y` using the specified reference white *luminance* :math:`Y_n` as per
225 *CIE 1976* recommendation.
227 Parameters
228 ----------
229 Y
230 *Luminance* :math:`Y`.
231 Y_n
232 White reference *luminance* :math:`Y_n`.
234 Returns
235 -------
236 :class:`numpy.ndarray`
237 Intermediate value :math:`f(Y/Y_n)`.
239 Notes
240 -----
241 +-------------+-----------------------+---------------+
242 | **Domain** | **Scale - Reference** | **Scale - 1** |
243 +=============+=======================+===============+
244 | ``Y`` | 100 | 100 |
245 +-------------+-----------------------+---------------+
247 +-------------+-----------------------+---------------+
248 | **Range** | **Scale - Reference** | **Scale - 1** |
249 +=============+=======================+===============+
250 | ``f_Y_Y_n`` | 1 | 1 |
251 +-------------+-----------------------+---------------+
253 References
254 ----------
255 :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
257 Examples
258 --------
259 >>> intermediate_lightness_function_CIE1976(12.19722535)
260 ... # doctest: +ELLIPSIS
261 0.4959299...
262 >>> intermediate_lightness_function_CIE1976(12.19722535, 95)
263 ... # doctest: +ELLIPSIS
264 0.5044821...
265 """
267 Y = as_float_array(Y)
268 Y_n = as_float_array(Y_n)
270 Y_Y_n = Y / Y_n
272 f_Y_Y_n = np.where(
273 Y_Y_n > (24 / 116) ** 3,
274 spow(Y_Y_n, 1 / 3),
275 (841 / 108) * Y_Y_n + 16 / 116,
276 )
278 return as_float(f_Y_Y_n)
281def lightness_CIE1976(Y: Domain100, Y_n: ArrayLike | None = None) -> Range100:
282 """
283 Compute the *lightness* :math:`L^*` of the specified *luminance* :math:`Y`
284 using the specified reference white *luminance* :math:`Y_n` as per *CIE 1976*
285 recommendation.
287 Parameters
288 ----------
289 Y
290 *Luminance* :math:`Y`.
291 Y_n
292 White reference *luminance* :math:`Y_n`.
294 Returns
295 -------
296 :class:`numpy.ndarray`
297 *Lightness* :math:`L^*`.
299 Notes
300 -----
301 +------------+-----------------------+---------------+
302 | **Domain** | **Scale - Reference** | **Scale - 1** |
303 +============+=======================+===============+
304 | ``Y`` | 100 | 1 |
305 +------------+-----------------------+---------------+
307 +------------+-----------------------+---------------+
308 | **Range** | **Scale - Reference** | **Scale - 1** |
309 +============+=======================+===============+
310 | ``L_star`` | 100 | 1 |
311 +------------+-----------------------+---------------+
313 References
314 ----------
315 :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
317 Examples
318 --------
319 >>> lightness_CIE1976(12.19722535) # doctest: +ELLIPSIS
320 41.5278758...
321 """
323 Y = to_domain_100(Y)
324 Y_n = to_domain_100(
325 optional(Y_n, 100 if get_domain_range_scale() == "reference" else 1)
326 )
328 L_star = 116 * intermediate_lightness_function_CIE1976(Y, Y_n) - 16
330 return as_float(from_range_100(L_star))
333def lightness_Fairchild2010(Y: Domain1, epsilon: ArrayLike = 1.836) -> Range100:
334 """
335 Compute *lightness* :math:`L_{hdr}` from the specified *luminance* :math:`Y`
336 using *Fairchild and Wyble (2010)* method according to *Michaelis-Menten*
337 kinetics.
339 Parameters
340 ----------
341 Y
342 *Luminance* :math:`Y`.
343 epsilon
344 :math:`\\epsilon` exponent.
346 Returns
347 -------
348 :class:`numpy.ndarray`
349 *Lightness* :math:`L_{hdr}`.
351 Notes
352 -----
353 +------------+-----------------------+---------------+
354 | **Domain** | **Scale - Reference** | **Scale - 1** |
355 +============+=======================+===============+
356 | ``Y`` | 1 | 1 |
357 +------------+-----------------------+---------------+
359 +------------+-----------------------+---------------+
360 | **Range** | **Scale - Reference** | **Scale - 1** |
361 +============+=======================+===============+
362 | ``L_hdr`` | 100 | 1 |
363 +------------+-----------------------+---------------+
365 References
366 ----------
367 :cite:`Fairchild2010`
369 Examples
370 --------
371 >>> lightness_Fairchild2010(12.19722535 / 100) # doctest: +ELLIPSIS
372 31.9963902...
373 """
375 Y = to_domain_1(Y)
377 maximum_perception = 100
379 L_hdr = (
380 reaction_rate_MichaelisMenten_Michaelis1913(
381 spow(Y, epsilon), maximum_perception, spow(0.184, epsilon)
382 )
383 + 0.02
384 )
386 return as_float(from_range_100(L_hdr))
389def lightness_Fairchild2011(
390 Y: Domain1,
391 epsilon: ArrayLike = 0.474,
392 method: Literal["hdr-CIELAB", "hdr-IPT"] | str = "hdr-CIELAB",
393) -> Range100:
394 """
395 Compute *lightness* :math:`L_{hdr}` from the specified *luminance* :math:`Y`
396 using *Fairchild and Chen (2011)* method according to *Michaelis-Menten*
397 kinetics.
399 Parameters
400 ----------
401 Y
402 *Luminance* :math:`Y`.
403 epsilon
404 :math:`\\epsilon` exponent.
405 method
406 *Lightness* :math:`L_{hdr}` computation method.
408 Returns
409 -------
410 :class:`numpy.ndarray`
411 *Lightness* :math:`L_{hdr}`.
413 Notes
414 -----
415 +------------+-----------------------+---------------+
416 | **Domain** | **Scale - Reference** | **Scale - 1** |
417 +============+=======================+===============+
418 | ``Y`` | 1 | 1 |
419 +------------+-----------------------+---------------+
421 +------------+-----------------------+---------------+
422 | **Range** | **Scale - Reference** | **Scale - 1** |
423 +============+=======================+===============+
424 | ``L_hdr`` | 100 | 1 |
425 +------------+-----------------------+---------------+
427 References
428 ----------
429 :cite:`Fairchild2011`
431 Examples
432 --------
433 >>> lightness_Fairchild2011(12.19722535 / 100) # doctest: +ELLIPSIS
434 51.8529584...
435 >>> lightness_Fairchild2011(12.19722535 / 100, method="hdr-IPT")
436 ... # doctest: +ELLIPSIS
437 51.6431084...
438 """
440 Y = to_domain_1(Y)
441 method = validate_method(method, ("hdr-CIELAB", "hdr-IPT"))
443 maximum_perception = 247 if method == "hdr-cielab" else 246
445 L_hdr = (
446 reaction_rate_MichaelisMenten_Michaelis1913(
447 spow(Y, epsilon), maximum_perception, spow(2, epsilon)
448 )
449 + 0.02
450 )
452 return as_float(from_range_100(L_hdr))
455def lightness_Abebe2017(
456 Y: ArrayLike,
457 Y_n: ArrayLike | None = None,
458 method: Literal["Michaelis-Menten", "Stevens"] | str = "Michaelis-Menten",
459) -> NDArrayFloat:
460 """
461 Compute *lightness* :math:`L` from the specified *luminance* :math:`Y` using
462 *Abebe, Pouli, Larabi and Reinhard (2017)* adaptive method for
463 high-dynamic-range imaging according to *Michaelis-Menten* kinetics or
464 *Stevens's Power Law*.
466 Parameters
467 ----------
468 Y
469 *Luminance* :math:`Y` in :math:`cd/m^2`.
470 Y_n
471 Adapting luminance :math:`Y_n` in :math:`cd/m^2`.
472 method
473 *Lightness* :math:`L` computation method.
475 Returns
476 -------
477 :class:`numpy.ndarray`
478 *Lightness* :math:`L`.
480 Notes
481 -----
482 - *Abebe, Pouli, Larabi and Reinhard (2017)* method uses absolute
483 luminance levels, thus the domain and range values for the
484 *Reference* and *1* scales are only indicative that the data is
485 not affected by scale transformations.
487 +------------+-----------------------+---------------+
488 | **Domain** | **Scale - Reference** | **Scale - 1** |
489 +============+=======================+===============+
490 | ``Y`` | ``UN`` | ``UN`` |
491 +------------+-----------------------+---------------+
492 | ``Y_n`` | ``UN`` | ``UN`` |
493 +------------+-----------------------+---------------+
495 +------------+-----------------------+---------------+
496 | **Range** | **Scale - Reference** | **Scale - 1** |
497 +============+=======================+===============+
498 | ``L`` | ``UN`` | ``UN`` |
499 +------------+-----------------------+---------------+
501 References
502 ----------
503 :cite:`Abebe2017`
505 Examples
506 --------
507 >>> lightness_Abebe2017(12.19722535) # doctest: +ELLIPSIS
508 0.4869555...
509 >>> lightness_Abebe2017(12.19722535, method="Stevens")
510 ... # doctest: +ELLIPSIS
511 0.4745447...
512 """
514 Y = as_float_array(Y)
515 Y_n = as_float_array(optional(Y_n, 100))
516 method = validate_method(method, ("Michaelis-Menten", "Stevens"))
518 Y_Y_n = Y / Y_n
519 if method == "stevens":
520 L = np.where(
521 Y_n <= 100,
522 1.226 * spow(Y_Y_n, 0.266) - 0.226,
523 1.127 * spow(Y_Y_n, 0.230) - 0.127,
524 )
525 else:
526 L = np.where(
527 Y_n <= 100,
528 reaction_rate_MichaelisMenten_Abebe2017(
529 spow(Y_Y_n, 0.582), 1.448, 0.635, 0.813
530 ),
531 reaction_rate_MichaelisMenten_Abebe2017(
532 spow(Y_Y_n, 0.293), 1.680, 1.584, 0.096
533 ),
534 )
536 return as_float(L)
539LIGHTNESS_METHODS: CanonicalMapping = CanonicalMapping(
540 {
541 "Glasser 1958": lightness_Glasser1958,
542 "Wyszecki 1963": lightness_Wyszecki1963,
543 "CIE 1976": lightness_CIE1976,
544 "Fairchild 2010": lightness_Fairchild2010,
545 "Fairchild 2011": lightness_Fairchild2011,
546 "Abebe 2017": lightness_Abebe2017,
547 }
548)
549LIGHTNESS_METHODS.__doc__ = """
550Supported *lightness* computation methods.
552References
553----------
554:cite:`CIETC1-482004m`, :cite:`Fairchild2010`, :cite:`Fairchild2011`,
555:cite:`Glasser1958a`, :cite:`Wyszecki1963b`, :cite:`Wyszecki2000bd`
557Aliases:
559- 'Lstar1976': 'CIE 1976'
560"""
561LIGHTNESS_METHODS["Lstar1976"] = LIGHTNESS_METHODS["CIE 1976"]
564def lightness(
565 Y: Domain100,
566 method: (
567 Literal[
568 "Abebe 2017",
569 "CIE 1976",
570 "Glasser 1958",
571 "Fairchild 2010",
572 "Fairchild 2011",
573 "Wyszecki 1963",
574 ]
575 | str
576 ) = "CIE 1976",
577 **kwargs: Any,
578) -> Range100:
579 """
580 Compute the *lightness* :math:`L` from the specified *luminance* :math:`Y`.
582 Parameters
583 ----------
584 Y
585 *Luminance* :math:`Y`.
586 method
587 Computation method.
589 Other Parameters
590 ----------------
591 Y_n
592 {:func:`colour.colorimetry.lightness_Abebe2017`,
593 :func:`colour.colorimetry.lightness_CIE1976`},
594 White reference *luminance* :math:`Y_n`.
595 epsilon
596 {:func:`colour.colorimetry.lightness_Fairchild2010`,
597 :func:`colour.colorimetry.lightness_Fairchild2011`},
598 :math:`\\epsilon` exponent.
600 Returns
601 -------
602 :class:`numpy.ndarray`
603 *Lightness* :math:`L`.
605 Notes
606 -----
607 +------------+-----------------------+---------------+
608 | **Domain** | **Scale - Reference** | **Scale - 1** |
609 +============+=======================+===============+
610 | ``Y`` | 100 | 1 |
611 +------------+-----------------------+---------------+
613 +------------+-----------------------+---------------+
614 | **Range** | **Scale - Reference** | **Scale - 1** |
615 +============+=======================+===============+
616 | ``L`` | 100 | 1 |
617 +------------+-----------------------+---------------+
619 References
620 ----------
621 :cite:`Abebe2017`, :cite:`CIETC1-482004m`, :cite:`Fairchild2010`,
622 :cite:`Fairchild2011`, :cite:`Glasser1958a`, :cite:`Wikipedia2007c`,
623 :cite:`Wyszecki1963b`, :cite:`Wyszecki2000bd`
625 Examples
626 --------
627 >>> lightness(12.19722535) # doctest: +ELLIPSIS
628 41.5278758...
629 >>> lightness(12.19722535, Y_n=100) # doctest: +ELLIPSIS
630 41.5278758...
631 >>> lightness(12.19722535, Y_n=95) # doctest: +ELLIPSIS
632 42.5199307...
633 >>> lightness(12.19722535, method="Glasser 1958") # doctest: +ELLIPSIS
634 39.8351264...
635 >>> lightness(12.19722535, method="Wyszecki 1963") # doctest: +ELLIPSIS
636 40.5475745...
637 >>> lightness(12.19722535, epsilon=0.710, method="Fairchild 2011")
638 ... # doctest: +ELLIPSIS
639 29.8295108...
640 >>> lightness(12.19722535, epsilon=0.710, method="Fairchild 2011")
641 ... # doctest: +ELLIPSIS
642 29.8295108...
643 >>> lightness(12.19722535, method="Abebe 2017")
644 ... # doctest: +ELLIPSIS
645 48.6955571...
646 """
648 Y = as_float_array(Y)
649 method = validate_method(method, tuple(LIGHTNESS_METHODS))
651 function = LIGHTNESS_METHODS[method]
653 domain_range_reference = get_domain_range_scale() == "reference"
654 domain_range_1 = get_domain_range_scale() == "1"
656 # Fairchild methods expect Y in [0, 1].
657 if (
658 function
659 in (
660 lightness_Fairchild2010,
661 lightness_Fairchild2011,
662 )
663 and domain_range_reference
664 ):
665 Y = Y / 100
667 # Abebe uses absolute luminance, scale inputs to cd/m² in scale 1.
668 if function in (lightness_Abebe2017,) and domain_range_1:
669 Y = Y * 100
670 if "Y_n" in kwargs:
671 kwargs["Y_n"] = kwargs["Y_n"] * 100
673 L = function(Y, **filter_kwargs(function, **kwargs))
675 # Scale Abebe output to [0, 100] for comparability (not in scale 1).
676 if function in (lightness_Abebe2017,) and not domain_range_1:
677 return L * 100
679 return L