"""
Luminance :math:`Y`
===================
Define the *luminance* :math:`Y` computation objects.
The following methods are available:
- :func:`colour.colorimetry.luminance_Newhall1943`: *luminance* :math:`Y`
computation of given *Munsell* value :math:`V` using
*Newhall, Nickerson and Judd (1943)* method.
- :func:`colour.colorimetry.luminance_ASTMD1535`: *luminance* :math:`Y`
computation of given *Munsell* value :math:`V` using *ASTM D1535-08e1*
method.
- :func:`colour.colorimetry.luminance_CIE1976`: *luminance* :math:`Y`
computation of given *Lightness* :math:`L^*` as per *CIE 1976*
recommendation.
- :func:`colour.colorimetry.luminance_Fairchild2010`: *luminance* :math:`Y`
computation of given *Lightness* :math:`L_{hdr}` using
*Fairchild and Wyble (2010)* method.
- :func:`colour.colorimetry.luminance_Fairchild2011`: *luminance* :math:`Y`
computation of given *Lightness* :math:`L_{hdr}` using
*Fairchild and Chen (2011)* method.
- :func:`colour.colorimetry.luminance_Abebe2017`: *Luminance* :math:`Y`
computation of given *Lightness* :math:`L` using
*Abebe, Pouli, Larabi and Reinhard (2017)* method.
- :attr:`colour.LUMINANCE_METHODS`: Supported *luminance* :math:`Y`
computation methods.
- :func:`colour.luminance`: *Luminance* :math:`Y` computation of given
*Lightness* :math:`L^*` or given *Munsell* value :math:`V` using given
method.
References
----------
- :cite:`Abebe2017` : Abebe, M. A., Pouli, T., Larabi, M.-C., & Reinhard,
E. (2017). Perceptual Lightness Modeling for High-Dynamic-Range Imaging.
ACM Transactions on Applied Perception, 15(1), 1-19. doi:10.1145/3086577
- :cite:`ASTMInternational2008a` : ASTM International. (2008). ASTM
D1535-08e1 - Standard Practice for Specifying Color by the Munsell System.
doi:10.1520/D1535-08E01
- :cite:`CIETC1-482004m` : CIE TC 1-48. (2004). CIE 1976 uniform colour
spaces. In CIE 015:2004 Colorimetry, 3rd Edition (p. 24).
ISBN:978-3-901906-33-6
- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010). hdr-CIELAB
and hdr-IPT: Simple Models for Describing the Color of High-Dynamic-Range
and Wide-Color-Gamut Images. Proc. of Color and Imaging Conference,
322-326. ISBN:978-1-62993-215-6
- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011). Brightness,
lightness, and specifying color in high-dynamic-range scenes and images. In
S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image Quality and
System Performance VIII (p. 78670O). doi:10.1117/12.872075
- :cite:`Newhall1943a` : Newhall, S. M., Nickerson, D., & Judd, D. B. (1943).
Final Report of the OSA Subcommittee on the Spacing of the Munsell Colors.
Journal of the Optical Society of America, 33(7), 385.
doi:10.1364/JOSA.33.000385
- :cite:`Wikipedia2001b` : Wikipedia. (2001). Luminance. Retrieved February
10, 2018, from https://en.wikipedia.org/wiki/Luminance
- :cite:`Wyszecki2000bd` : Wyszecki, Günther, & Stiles, W. S. (2000). CIE
1976 (L*u*v*)-Space and Color-Difference Formula. In Color Science:
Concepts and Methods, Quantitative Data and Formulae (p. 167). Wiley.
ISBN:978-0-471-39918-6
"""
from __future__ import annotations
import numpy as np
from colour.algebra import spow
from colour.biochemistry import (
substrate_concentration_MichaelisMenten_Abebe2017,
substrate_concentration_MichaelisMenten_Michaelis1913,
)
from colour.hints import Any, ArrayLike, Literal, NDArrayFloat
from colour.utilities import (
CanonicalMapping,
as_float,
as_float_array,
filter_kwargs,
from_range_1,
from_range_100,
get_domain_range_scale,
to_domain_10,
to_domain_100,
validate_method,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"luminance_Newhall1943",
"luminance_ASTMD1535",
"intermediate_luminance_function_CIE1976",
"luminance_CIE1976",
"luminance_Fairchild2010",
"luminance_Fairchild2011",
"luminance_Abebe2017",
"LUMINANCE_METHODS",
"luminance",
]
[docs]
def luminance_Newhall1943(V: ArrayLike) -> NDArrayFloat:
"""
Return the *luminance* :math:`R_Y` of given *Munsell* value :math:`V`
using *Newhall et al. (1943)* method.
Parameters
----------
V
*Munsell* value :math:`V`.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`R_Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``V`` | [0, 10] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``R_Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Newhall1943a`
Examples
--------
>>> luminance_Newhall1943(4.08244375) # doctest: +ELLIPSIS
12.5500788...
"""
V = to_domain_10(V)
R_Y = (
1.2219 * V
- 0.23111 * (V * V)
+ 0.23951 * (V**3)
- 0.021009 * (V**4)
+ 0.0008404 * (V**5)
)
return as_float(from_range_100(R_Y))
[docs]
def luminance_ASTMD1535(V: ArrayLike) -> NDArrayFloat:
"""
Return the *luminance* :math:`Y` of given *Munsell* value :math:`V` using
*ASTM D1535-08e1* method.
Parameters
----------
V
*Munsell* value :math:`V`.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``V`` | [0, 10] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`ASTMInternational2008a`
Examples
--------
>>> luminance_ASTMD1535(4.08244375) # doctest: +ELLIPSIS
12.2363426...
"""
V = to_domain_10(V)
Y = (
1.1914 * V
- 0.22533 * (V**2)
+ 0.23352 * (V**3)
- 0.020484 * (V**4)
+ 0.00081939 * (V**5)
)
return as_float(from_range_100(Y))
[docs]
def luminance_CIE1976(L_star: ArrayLike, Y_n: ArrayLike = 100) -> NDArrayFloat:
"""
Return the *luminance* :math:`Y` of given *Lightness* :math:`L^*` with
given reference white *luminance* :math:`Y_n`.
Parameters
----------
L_star
*Lightness* :math:`L^*`
Y_n
White reference *luminance* :math:`Y_n`.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_star`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
Examples
--------
>>> luminance_CIE1976(41.527875844653451) # doctest: +ELLIPSIS
12.1972253...
>>> luminance_CIE1976(41.527875844653451, 95) # doctest: +ELLIPSIS
11.5873640...
"""
L_star = to_domain_100(L_star)
Y_n = to_domain_100(Y_n)
f_Y_Y_n = (L_star + 16) / 116
Y = intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n)
return as_float(from_range_100(Y))
[docs]
def luminance_Fairchild2010(
L_hdr: ArrayLike, epsilon: ArrayLike = 1.836
) -> NDArrayFloat:
"""
Compute *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using
*Fairchild and Wyble (2010)* method according to *Michaelis-Menten*
kinetics.
Parameters
----------
L_hdr
*Lightness* :math:`L_{hdr}`.
epsilon
:math:`\\epsilon` exponent.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_hdr`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 1] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Fairchild2010`
Examples
--------
>>> luminance_Fairchild2010(31.996390226262736, 1.836)
... # doctest: +ELLIPSIS
0.1219722...
"""
L_hdr = to_domain_100(L_hdr)
Y = np.exp(
np.log(
substrate_concentration_MichaelisMenten_Michaelis1913(
L_hdr - 0.02, 100, spow(0.184, epsilon)
)
)
/ epsilon
)
return as_float(from_range_1(Y))
[docs]
def luminance_Fairchild2011(
L_hdr: ArrayLike,
epsilon: ArrayLike = 0.474,
method: Literal["hdr-CIELAB", "hdr-IPT"] | str = "hdr-CIELAB",
) -> NDArrayFloat:
"""
Compute *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using
*Fairchild and Chen (2011)* method according to *Michaelis-Menten*
kinetics.
Parameters
----------
L_hdr
*Lightness* :math:`L_{hdr}`.
epsilon
:math:`\\epsilon` exponent.
method
*Lightness* :math:`L_{hdr}` computation method.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_hdr`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 1] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Fairchild2011`
Examples
--------
>>> luminance_Fairchild2011(51.852958445912506) # doctest: +ELLIPSIS
0.1219722...
>>> luminance_Fairchild2011(51.643108411718522, method="hdr-IPT")
... # doctest: +ELLIPSIS
0.1219722...
"""
L_hdr = to_domain_100(L_hdr)
method = validate_method(method, ("hdr-CIELAB", "hdr-IPT"))
maximum_perception = 247 if method == "hdr-cielab" else 246
Y = np.exp(
np.log(
substrate_concentration_MichaelisMenten_Michaelis1913(
L_hdr - 0.02, maximum_perception, spow(2, epsilon)
)
)
/ epsilon
)
return as_float(from_range_1(Y))
def luminance_Abebe2017(
L: ArrayLike,
Y_n: ArrayLike = 100,
method: Literal["Michaelis-Menten", "Stevens"] | str = "Michaelis-Menten",
) -> NDArrayFloat:
"""
Compute *luminance* :math:`Y` of *Lightness* :math:`L` using
*Abebe, Pouli, Larabi and Reinhard (2017)* method according to
*Michaelis-Menten* kinetics or *Stevens's Power Law*.
Parameters
----------
L
*Lightness* :math:`L`.
Y_n
Adapting luminance :math:`Y_n` in :math:`cd/m^2`.
method
*Luminance* :math:`Y` computation method.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y` in :math:`cd/m^2`.
Notes
-----
- *Abebe, Pouli, Larabi and Reinhard (2017)* method uses absolute
luminance levels, thus the domain and range values for the *Reference*
and *1* scales are only indicative that the data is not affected by
scale transformations.
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
| ``Y_n`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
References
----------
:cite:`Abebe2017`
Examples
--------
>>> luminance_Abebe2017(0.486955571109229) # doctest: +ELLIPSIS
12.1972253...
>>> luminance_Abebe2017(0.474544792145434, method="Stevens")
... # doctest: +ELLIPSIS
12.1972253...
"""
L = as_float_array(L)
Y_n = as_float_array(Y_n)
method = validate_method(method, ("Michaelis-Menten", "Stevens"))
if method == "stevens":
Y = np.where(
Y_n <= 100,
spow((L + 0.226) / 1.226, 1 / 0.266),
spow((L + 0.127) / 1.127, 1 / 0.230),
)
else:
Y = np.where(
Y_n <= 100,
spow(
substrate_concentration_MichaelisMenten_Abebe2017(
L, 1.448, 0.635, 0.813
),
1 / 0.582,
),
spow(
substrate_concentration_MichaelisMenten_Abebe2017(
L, 1.680, 1.584, 0.096
),
1 / 0.293,
),
)
Y = Y * Y_n
return as_float(Y)
LUMINANCE_METHODS: CanonicalMapping = CanonicalMapping(
{
"Newhall 1943": luminance_Newhall1943,
"ASTM D1535": luminance_ASTMD1535,
"CIE 1976": luminance_CIE1976,
"Fairchild 2010": luminance_Fairchild2010,
"Fairchild 2011": luminance_Fairchild2011,
"Abebe 2017": luminance_Abebe2017,
}
)
LUMINANCE_METHODS.__doc__ = """
Supported *luminance* computation methods.
References
----------
:cite:`ASTMInternational2008a`, :cite:`CIETC1-482004m`, :cite:`Fairchild2010`,
:cite:`Fairchild2011`, :cite:`Newhall1943a`, :cite:`Wyszecki2000bd`
Aliases:
- 'astm2008': 'ASTM D1535'
- 'cie1976': 'CIE 1976'
"""
LUMINANCE_METHODS["astm2008"] = LUMINANCE_METHODS["ASTM D1535"]
LUMINANCE_METHODS["cie1976"] = LUMINANCE_METHODS["CIE 1976"]
[docs]
def luminance(
LV: ArrayLike,
method: (
Literal[
"Abebe 2017",
"CIE 1976",
"Glasser 1958",
"Fairchild 2010",
"Fairchild 2011",
"Wyszecki 1963",
]
| str
) = "CIE 1976",
**kwargs: Any,
) -> NDArrayFloat:
"""
Return the *luminance* :math:`Y` of given *Lightness* :math:`L^*` or given
*Munsell* value :math:`V`.
Parameters
----------
LV
*Lightness* :math:`L^*` or *Munsell* value :math:`V`.
method
Computation method.
Other Parameters
----------------
Y_n
{:func:`colour.colorimetry.luminance_Abebe2017`,
:func:`colour.colorimetry.luminance_CIE1976`},
White reference *luminance* :math:`Y_n`.
epsilon
{:func:`colour.colorimetry.lightness_Fairchild2010`,
:func:`colour.colorimetry.lightness_Fairchild2011`},
:math:`\\epsilon` exponent.
Returns
-------
:class:`numpy.ndarray`
*Luminance* :math:`Y`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``LV`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Abebe2017`, :cite:`ASTMInternational2008a`, :cite:`CIETC1-482004m`,
:cite:`Fairchild2010`, :cite:`Fairchild2011`, :cite:`Newhall1943a`,
:cite:`Wikipedia2001b`, :cite:`Wyszecki2000bd`
Examples
--------
>>> luminance(41.527875844653451) # doctest: +ELLIPSIS
12.1972253...
>>> luminance(41.527875844653451, Y_n=100) # doctest: +ELLIPSIS
12.1972253...
>>> luminance(42.51993072812094, Y_n=95) # doctest: +ELLIPSIS
12.1972253...
>>> luminance(4.08244375 * 10, method="Newhall 1943")
... # doctest: +ELLIPSIS
12.5500788...
>>> luminance(4.08244375 * 10, method="ASTM D1535")
... # doctest: +ELLIPSIS
12.2363426...
>>> luminance(29.829510892279330, epsilon=0.710, method="Fairchild 2011")
... # doctest: +ELLIPSIS
12.1972253...
"""
LV = as_float_array(LV)
method = validate_method(method, tuple(LUMINANCE_METHODS))
function = LUMINANCE_METHODS[method]
# NOTE: "Abebe et al. (2017)" uses absolute luminance levels and has
# undefined domain-range scale, yet we modify its behaviour consistency
# with the other methods.
domain_range_reference = get_domain_range_scale() == "reference"
domain_range_1 = get_domain_range_scale() == "1"
domain_1 = (luminance_Fairchild2010, luminance_Fairchild2011)
domain_10 = (luminance_Newhall1943, luminance_ASTMD1535)
domain_undefined = (luminance_Abebe2017,)
if function in domain_10 and domain_range_reference:
LV = LV / 10
if function in domain_undefined and domain_range_1:
LV = LV * 100
kwargs["Y_n"] = kwargs.get("Y_n", 100) * 100
Y_V = function(LV, **filter_kwargs(function, **kwargs))
if function in domain_1 and domain_range_reference:
Y_V *= 100
if function in domain_undefined and domain_range_1:
Y_V /= 100
return Y_V