Coverage for colorimetry/luminance.py: 66%
76 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"""
2Luminance :math:`Y`
3===================
5Define *luminance* :math:`Y` computation methods.
7- :func:`colour.colorimetry.luminance_Newhall1943`: Compute *luminance*
8 :math:`Y` from *Munsell* value :math:`V` using *Newhall, Nickerson and
9 Judd (1943)* polynomial approximation.
10- :func:`colour.colorimetry.luminance_ASTMD1535`: Compute *luminance*
11 :math:`Y` from *Munsell* value :math:`V` using *ASTM D1535-08e1*
12 standard polynomial.
13- :func:`colour.colorimetry.luminance_CIE1976`: Compute *luminance*
14 :math:`Y` from *CIE 1976* *Lightness* :math:`L^*` using the inverse
15 of the standard lightness function.
16- :func:`colour.colorimetry.luminance_Fairchild2010`: Compute *luminance*
17 :math:`Y` from *lightness* :math:`L_{hdr}` using *Fairchild and
18 Wyble (2010)* method according to *Michaelis-Menten* kinetics.
19- :func:`colour.colorimetry.luminance_Fairchild2011`: Compute *luminance*
20 :math:`Y` from *lightness* :math:`L_{hdr}` using *Fairchild and
21 Chen (2011)* method according to *Michaelis-Menten* kinetics.
22- :func:`colour.colorimetry.luminance_Abebe2017`: Compute *luminance*
23 :math:`Y` from *lightness* :math:`L` using *Abebe, Pouli, Larabi and
24 Reinhard (2017)* adaptive method for high-dynamic-range imaging.
25- :attr:`colour.LUMINANCE_METHODS`: Supported *luminance* :math:`Y`
26 computation methods registry.
27- :func:`colour.luminance`: Compute *luminance* :math:`Y` from
28 *Lightness* :math:`L^*` or *Munsell* value :math:`V` using the specified
29 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:`ASTMInternational2008a` : ASTM International. (2008). ASTM
37 D1535-08e1 - Standard Practice for Specifying Color by the Munsell System.
38 doi:10.1520/D1535-08E01
39- :cite:`CIETC1-482004m` : CIE TC 1-48. (2004). CIE 1976 uniform colour
40 spaces. In CIE 015:2004 Colorimetry, 3rd Edition (p. 24).
41 ISBN:978-3-901906-33-6
42- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010). hdr-CIELAB
43 and hdr-IPT: Simple Models for Describing the Color of High-Dynamic-Range
44 and Wide-Color-Gamut Images. Proc. of Color and Imaging Conference,
45 322-326. ISBN:978-1-62993-215-6
46- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011). Brightness,
47 lightness, and specifying color in high-dynamic-range scenes and images. In
48 S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image Quality and
49 System Performance VIII (p. 78670O). doi:10.1117/12.872075
50- :cite:`Newhall1943a` : Newhall, S. M., Nickerson, D., & Judd, D. B. (1943).
51 Final Report of the OSA Subcommittee on the Spacing of the Munsell Colors.
52 Journal of the Optical Society of America, 33(7), 385.
53 doi:10.1364/JOSA.33.000385
54- :cite:`Wikipedia2001b` : Wikipedia. (2001). Luminance. Retrieved February
55 10, 2018, from https://en.wikipedia.org/wiki/Luminance
56- :cite:`Wyszecki2000bd` : Wyszecki, Günther, & Stiles, W. S. (2000). CIE
57 1976 (L*u*v*)-Space and Color-Difference Formula. In Color Science:
58 Concepts and Methods, Quantitative Data and Formulae (p. 167). Wiley.
59 ISBN:978-0-471-39918-6
60"""
62from __future__ import annotations
64import typing
66import numpy as np
68from colour.algebra import spow
69from colour.biochemistry import (
70 substrate_concentration_MichaelisMenten_Abebe2017,
71 substrate_concentration_MichaelisMenten_Michaelis1913,
72)
74if typing.TYPE_CHECKING:
75 from colour.hints import Any, Literal
77from colour.hints import ( # noqa: TC001
78 ArrayLike,
79 Domain10,
80 Domain100,
81 NDArrayFloat,
82 Range1,
83 Range100,
84)
85from colour.utilities import (
86 CanonicalMapping,
87 as_float,
88 as_float_array,
89 filter_kwargs,
90 from_range_1,
91 from_range_100,
92 get_domain_range_scale,
93 optional,
94 to_domain_10,
95 to_domain_100,
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 "luminance_Newhall1943",
108 "luminance_ASTMD1535",
109 "intermediate_luminance_function_CIE1976",
110 "luminance_CIE1976",
111 "luminance_Fairchild2010",
112 "luminance_Fairchild2011",
113 "luminance_Abebe2017",
114 "LUMINANCE_METHODS",
115 "luminance",
116]
119def luminance_Newhall1943(V: Domain10) -> Range100:
120 """
121 Compute the *luminance* :math:`R_Y` from the specified *Munsell* value
122 :math:`V` using *Newhall et al. (1943)* method.
124 Parameters
125 ----------
126 V
127 *Munsell* value :math:`V`.
129 Returns
130 -------
131 :class:`numpy.ndarray`
132 *Luminance* :math:`R_Y`.
134 Notes
135 -----
136 +------------+-----------------------+---------------+
137 | **Domain** | **Scale - Reference** | **Scale - 1** |
138 +============+=======================+===============+
139 | ``V`` | 10 | 1 |
140 +------------+-----------------------+---------------+
142 +------------+-----------------------+---------------+
143 | **Range** | **Scale - Reference** | **Scale - 1** |
144 +============+=======================+===============+
145 | ``R_Y`` | 100 | 1 |
146 +------------+-----------------------+---------------+
148 References
149 ----------
150 :cite:`Newhall1943a`
152 Examples
153 --------
154 >>> luminance_Newhall1943(4.08244375) # doctest: +ELLIPSIS
155 12.5500788...
156 """
158 V = to_domain_10(V)
160 R_Y = (
161 1.2219 * V
162 - 0.23111 * (V * V)
163 + 0.23951 * (V**3)
164 - 0.021009 * (V**4)
165 + 0.0008404 * (V**5)
166 )
168 return as_float(from_range_100(R_Y))
171def luminance_ASTMD1535(V: Domain10) -> Range100:
172 """
173 Compute *luminance* :math:`Y` from the specified *Munsell* value :math:`V`
174 using *ASTM D1535-08e1* method.
176 Parameters
177 ----------
178 V
179 *Munsell* value :math:`V`.
181 Returns
182 -------
183 :class:`numpy.ndarray`
184 *Luminance* :math:`Y`.
186 Notes
187 -----
188 +------------+-----------------------+---------------+
189 | **Domain** | **Scale - Reference** | **Scale - 1** |
190 +============+=======================+===============+
191 | ``V`` | 10 | 1 |
192 +------------+-----------------------+---------------+
194 +------------+-----------------------+---------------+
195 | **Range** | **Scale - Reference** | **Scale - 1** |
196 +============+=======================+===============+
197 | ``Y`` | 100 | 1 |
198 +------------+-----------------------+---------------+
200 References
201 ----------
202 :cite:`ASTMInternational2008a`
204 Examples
205 --------
206 >>> luminance_ASTMD1535(4.08244375) # doctest: +ELLIPSIS
207 12.2363426...
208 """
210 V = to_domain_10(V)
212 Y = (
213 1.1914 * V
214 - 0.22533 * (V**2)
215 + 0.23352 * (V**3)
216 - 0.020484 * (V**4)
217 + 0.00081939 * (V**5)
218 )
220 return as_float(from_range_100(Y))
223def intermediate_luminance_function_CIE1976(
224 f_Y_Y_n: ArrayLike, Y_n: ArrayLike = 100
225) -> NDArrayFloat:
226 """
227 Compute *luminance* :math:`Y` from the specified intermediate value
228 :math:`f(Y/Y_n)` using the specified reference white *luminance* :math:`Y_n`
229 as per *CIE 1976* recommendation.
231 Parameters
232 ----------
233 f_Y_Y_n
234 Intermediate value :math:`f(Y/Y_n)`.
235 Y_n
236 White reference *luminance* :math:`Y_n`.
238 Returns
239 -------
240 :class:`numpy.ndarray`
241 *Luminance* :math:`Y`.
243 Notes
244 -----
245 +-------------+-----------------------+---------------+
246 | **Domain** | **Scale - Reference** | **Scale - 1** |
247 +=============+=======================+===============+
248 | ``f_Y_Y_n`` | 1 | 1 |
249 +-------------+-----------------------+---------------+
251 +-------------+-----------------------+---------------+
252 | **Range** | **Scale - Reference** | **Scale - 1** |
253 +=============+=======================+===============+
254 | ``Y`` | 100 | 100 |
255 +-------------+-----------------------+---------------+
257 References
258 ----------
259 :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
261 Examples
262 --------
263 >>> intermediate_luminance_function_CIE1976(0.495929964178047)
264 ... # doctest: +ELLIPSIS
265 12.1972253...
266 >>> intermediate_luminance_function_CIE1976(0.504482161449319, 95)
267 ... # doctest: +ELLIPSIS
268 12.1972253...
269 """
271 f_Y_Y_n = as_float_array(f_Y_Y_n)
272 Y_n = as_float_array(Y_n)
274 Y = np.where(
275 f_Y_Y_n > 24 / 116,
276 Y_n * f_Y_Y_n**3,
277 Y_n * (f_Y_Y_n - 16 / 116) * (108 / 841),
278 )
280 return as_float(Y)
283def luminance_CIE1976(L_star: Domain100, Y_n: ArrayLike | None = None) -> Range100:
284 """
285 Compute the *luminance* :math:`Y` from the specified *lightness* :math:`L^*`
286 with the specified reference white *luminance* :math:`Y_n`.
288 Parameters
289 ----------
290 L_star
291 *Lightness* :math:`L^*`.
292 Y_n
293 White reference *luminance* :math:`Y_n`.
295 Returns
296 -------
297 :class:`numpy.ndarray`
298 *Luminance* :math:`Y`.
300 Notes
301 -----
302 +------------+-----------------------+---------------+
303 | **Domain** | **Scale - Reference** | **Scale - 1** |
304 +============+=======================+===============+
305 | ``L_star`` | 100 | 1 |
306 +------------+-----------------------+---------------+
308 +------------+-----------------------+---------------+
309 | **Range** | **Scale - Reference** | **Scale - 1** |
310 +============+=======================+===============+
311 | ``Y`` | 100 | 1 |
312 +------------+-----------------------+---------------+
314 References
315 ----------
316 :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
318 Examples
319 --------
320 >>> luminance_CIE1976(41.527875844653451) # doctest: +ELLIPSIS
321 12.1972253...
322 >>> luminance_CIE1976(41.527875844653451, 95) # doctest: +ELLIPSIS
323 11.5873640...
324 """
326 L_star = to_domain_100(L_star)
327 Y_n = to_domain_100(
328 optional(Y_n, 100 if get_domain_range_scale() == "reference" else 1)
329 )
331 f_Y_Y_n = (L_star + 16) / 116
333 Y = intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n)
335 return as_float(from_range_100(Y))
338def luminance_Fairchild2010(L_hdr: Domain100, epsilon: ArrayLike = 1.836) -> Range1:
339 """
340 Compute *luminance* :math:`Y` from the specified *lightness* :math:`L_{hdr}`
341 using *Fairchild and Wyble (2010)* method according to *Michaelis-Menten*
342 kinetics.
344 Parameters
345 ----------
346 L_hdr
347 *Lightness* :math:`L_{hdr}`.
348 epsilon
349 :math:`\\epsilon` exponent.
351 Returns
352 -------
353 :class:`numpy.ndarray`
354 *Luminance* :math:`Y`.
356 Notes
357 -----
358 +------------+-----------------------+---------------+
359 | **Domain** | **Scale - Reference** | **Scale - 1** |
360 +============+=======================+===============+
361 | ``L_hdr`` | 100 | 1 |
362 +------------+-----------------------+---------------+
364 +------------+-----------------------+---------------+
365 | **Range** | **Scale - Reference** | **Scale - 1** |
366 +============+=======================+===============+
367 | ``Y`` | 1 | 1 |
368 +------------+-----------------------+---------------+
370 References
371 ----------
372 :cite:`Fairchild2010`
374 Examples
375 --------
376 >>> luminance_Fairchild2010(31.996390226262736, 1.836)
377 ... # doctest: +ELLIPSIS
378 0.1219722...
379 """
381 L_hdr = to_domain_100(L_hdr)
383 Y = np.exp(
384 np.log(
385 substrate_concentration_MichaelisMenten_Michaelis1913(
386 L_hdr - 0.02, 100, spow(0.184, epsilon)
387 )
388 )
389 / epsilon
390 )
392 return as_float(from_range_1(Y))
395def luminance_Fairchild2011(
396 L_hdr: Domain100,
397 epsilon: ArrayLike = 0.474,
398 method: Literal["hdr-CIELAB", "hdr-IPT"] | str = "hdr-CIELAB",
399) -> Range1:
400 """
401 Compute *luminance* :math:`Y` from the specified *lightness* :math:`L_{hdr}`
402 using *Fairchild and Chen (2011)* method according to *Michaelis-Menten*
403 kinetics.
405 Parameters
406 ----------
407 L_hdr
408 *Lightness* :math:`L_{hdr}`.
409 epsilon
410 :math:`\\epsilon` exponent.
411 method
412 *Lightness* :math:`L_{hdr}` computation method.
414 Returns
415 -------
416 :class:`numpy.ndarray`
417 *Luminance* :math:`Y`.
419 Notes
420 -----
421 +------------+-----------------------+---------------+
422 | **Domain** | **Scale - Reference** | **Scale - 1** |
423 +============+=======================+===============+
424 | ``L_hdr`` | 100 | 1 |
425 +------------+-----------------------+---------------+
427 +------------+-----------------------+---------------+
428 | **Range** | **Scale - Reference** | **Scale - 1** |
429 +============+=======================+===============+
430 | ``Y`` | 1 | 1 |
431 +------------+-----------------------+---------------+
433 References
434 ----------
435 :cite:`Fairchild2011`
437 Examples
438 --------
439 >>> luminance_Fairchild2011(51.852958445912506) # doctest: +ELLIPSIS
440 0.1219722...
441 >>> luminance_Fairchild2011(51.643108411718522, method="hdr-IPT")
442 ... # doctest: +ELLIPSIS
443 0.1219722...
444 """
446 L_hdr = to_domain_100(L_hdr)
447 method = validate_method(method, ("hdr-CIELAB", "hdr-IPT"))
449 maximum_perception = 247 if method == "hdr-cielab" else 246
451 Y = np.exp(
452 np.log(
453 substrate_concentration_MichaelisMenten_Michaelis1913(
454 L_hdr - 0.02, maximum_perception, spow(2, epsilon)
455 )
456 )
457 / epsilon
458 )
460 return as_float(from_range_1(Y))
463def luminance_Abebe2017(
464 L: ArrayLike,
465 Y_n: ArrayLike | None = None,
466 method: Literal["Michaelis-Menten", "Stevens"] | str = "Michaelis-Menten",
467) -> NDArrayFloat:
468 """
469 Compute *luminance* :math:`Y` from *lightness* :math:`L` using
470 *Abebe, Pouli, Larabi and Reinhard (2017)* adaptive method for
471 high-dynamic-range imaging according to *Michaelis-Menten* kinetics or
472 *Stevens's Power Law*.
474 Parameters
475 ----------
476 L
477 *Lightness* :math:`L`.
478 Y_n
479 Adapting luminance :math:`Y_n` in :math:`cd/m^2`.
480 method
481 *Luminance* :math:`Y` computation method.
483 Returns
484 -------
485 :class:`numpy.ndarray`
486 *Luminance* :math:`Y` in :math:`cd/m^2`.
488 Notes
489 -----
490 - *Abebe, Pouli, Larabi and Reinhard (2017)* method uses absolute
491 luminance levels, thus the domain and range values for the
492 *Reference* and *1* scales are only indicative that the data is not
493 affected by scale transformations.
495 +------------+-----------------------+---------------+
496 | **Domain** | **Scale - Reference** | **Scale - 1** |
497 +============+=======================+===============+
498 | ``L`` | ``UN`` | ``UN`` |
499 +------------+-----------------------+---------------+
500 | ``Y_n`` | ``UN`` | ``UN`` |
501 +------------+-----------------------+---------------+
503 +------------+-----------------------+---------------+
504 | **Range** | **Scale - Reference** | **Scale - 1** |
505 +============+=======================+===============+
506 | ``Y`` | ``UN`` | ``UN`` |
507 +------------+-----------------------+---------------+
509 References
510 ----------
511 :cite:`Abebe2017`
513 Examples
514 --------
515 >>> luminance_Abebe2017(0.486955571109229) # doctest: +ELLIPSIS
516 12.1972253...
517 >>> luminance_Abebe2017(0.474544792145434, method="Stevens")
518 ... # doctest: +ELLIPSIS
519 12.1972253...
520 """
522 L = as_float_array(L)
523 Y_n = as_float_array(optional(Y_n, 100))
524 method = validate_method(method, ("Michaelis-Menten", "Stevens"))
526 if method == "stevens":
527 Y = np.where(
528 Y_n <= 100,
529 spow((L + 0.226) / 1.226, 1 / 0.266),
530 spow((L + 0.127) / 1.127, 1 / 0.230),
531 )
532 else:
533 Y = np.where(
534 Y_n <= 100,
535 spow(
536 substrate_concentration_MichaelisMenten_Abebe2017(
537 L, 1.448, 0.635, 0.813
538 ),
539 1 / 0.582,
540 ),
541 spow(
542 substrate_concentration_MichaelisMenten_Abebe2017(
543 L, 1.680, 1.584, 0.096
544 ),
545 1 / 0.293,
546 ),
547 )
548 Y = Y * Y_n
550 return as_float(Y)
553LUMINANCE_METHODS: CanonicalMapping = CanonicalMapping(
554 {
555 "Newhall 1943": luminance_Newhall1943,
556 "ASTM D1535": luminance_ASTMD1535,
557 "CIE 1976": luminance_CIE1976,
558 "Fairchild 2010": luminance_Fairchild2010,
559 "Fairchild 2011": luminance_Fairchild2011,
560 "Abebe 2017": luminance_Abebe2017,
561 }
562)
563LUMINANCE_METHODS.__doc__ = """
564Supported *luminance* computation methods.
566References
567----------
568:cite:`ASTMInternational2008a`, :cite:`CIETC1-482004m`,
569:cite:`Fairchild2010`, :cite:`Fairchild2011`, :cite:`Newhall1943a`,
570:cite:`Wyszecki2000bd`
572Aliases:
574- 'astm2008': 'ASTM D1535'
575- 'cie1976': 'CIE 1976'
576"""
577LUMINANCE_METHODS["astm2008"] = LUMINANCE_METHODS["ASTM D1535"]
578LUMINANCE_METHODS["cie1976"] = LUMINANCE_METHODS["CIE 1976"]
581def luminance(
582 LV: Domain100,
583 method: (
584 Literal[
585 "Abebe 2017",
586 "CIE 1976",
587 "Glasser 1958",
588 "Fairchild 2010",
589 "Fairchild 2011",
590 "Wyszecki 1963",
591 ]
592 | str
593 ) = "CIE 1976",
594 **kwargs: Any,
595) -> Range100:
596 """
597 Compute the *luminance* :math:`Y` from the specified *lightness*
598 :math:`L^*` or *Munsell* value :math:`V`.
600 Parameters
601 ----------
602 LV
603 *Lightness* :math:`L^*` or *Munsell* value :math:`V`.
604 method
605 Computation method.
607 Other Parameters
608 ----------------
609 Y_n
610 {:func:`colour.colorimetry.luminance_Abebe2017`,
611 :func:`colour.colorimetry.luminance_CIE1976`},
612 White reference *luminance* :math:`Y_n`.
613 epsilon
614 {:func:`colour.colorimetry.luminance_Fairchild2010`,
615 :func:`colour.colorimetry.luminance_Fairchild2011`},
616 :math:`\\epsilon` exponent.
618 Returns
619 -------
620 :class:`numpy.ndarray`
621 *Luminance* :math:`Y`.
623 Notes
624 -----
625 +------------+-----------------------+---------------+
626 | **Domain** | **Scale - Reference** | **Scale - 1** |
627 +============+=======================+===============+
628 | ``LV`` | 100 | 1 |
629 +------------+-----------------------+---------------+
631 +------------+-----------------------+---------------+
632 | **Range** | **Scale - Reference** | **Scale - 1** |
633 +============+=======================+===============+
634 | ``Y`` | 100 | 1 |
635 +------------+-----------------------+---------------+
637 References
638 ----------
639 :cite:`Abebe2017`, :cite:`ASTMInternational2008a`,
640 :cite:`CIETC1-482004m`, :cite:`Fairchild2010`, :cite:`Fairchild2011`,
641 :cite:`Newhall1943a`, :cite:`Wikipedia2001b`, :cite:`Wyszecki2000bd`
643 Examples
644 --------
645 >>> luminance(41.527875844653451) # doctest: +ELLIPSIS
646 12.1972253...
647 >>> luminance(41.527875844653451, Y_n=100) # doctest: +ELLIPSIS
648 12.1972253...
649 >>> luminance(42.51993072812094, Y_n=95) # doctest: +ELLIPSIS
650 12.1972253...
651 >>> luminance(4.08244375 * 10, method="Newhall 1943")
652 ... # doctest: +ELLIPSIS
653 12.5500788...
654 >>> luminance(4.08244375 * 10, method="ASTM D1535")
655 ... # doctest: +ELLIPSIS
656 12.2363426...
657 >>> luminance(29.829510892279330, epsilon=0.710, method="Fairchild 2011")
658 ... # doctest: +ELLIPSIS
659 12.1972253...
660 >>> luminance(48.695557110922894, method="Abebe 2017")
661 ... # doctest: +ELLIPSIS
662 12.1972253...
663 """
665 LV = as_float_array(LV)
666 method = validate_method(method, tuple(LUMINANCE_METHODS))
668 function = LUMINANCE_METHODS[method]
670 domain_range_reference = get_domain_range_scale() == "reference"
671 domain_range_1 = get_domain_range_scale() == "1"
672 domain_range_100 = get_domain_range_scale() == "100"
674 # Newhall/ASTM methods expect V in [0, 10].
675 if (
676 function in (luminance_Newhall1943, luminance_ASTMD1535)
677 and domain_range_reference
678 ):
679 LV = LV / 10
681 # Abebe expects L in [0, 1] and Y_n in cd/m².
682 if function in (luminance_Abebe2017,):
683 if domain_range_reference or domain_range_100:
684 LV = LV / 100
685 if domain_range_1 and "Y_n" in kwargs:
686 kwargs["Y_n"] = kwargs["Y_n"] * 100
688 Y_V = function(LV, **filter_kwargs(function, **kwargs))
690 # Fairchild methods output Y in [0, 1], scale to [0, 100] in reference.
691 if (
692 function in (luminance_Fairchild2010, luminance_Fairchild2011)
693 and domain_range_reference
694 ):
695 Y_V = Y_V * 100
697 # Abebe outputs absolute cd/m², scale to [0, 1] in scale 1.
698 if function in (luminance_Abebe2017,) and domain_range_1:
699 Y_V = Y_V / 100
701 return Y_V