"""
Lightness :math:`L`
===================
Defines the *Lightness* :math:`L` computation objects.
The following methods are available:
- :func:`colour.colorimetry.lightness_Glasser1958`: *Lightness* :math:`L`
computation of given *luminance* :math:`Y` using
*Glasser, Mckinney, Reilly and Schnelle (1958)* method.
- :func:`colour.colorimetry.lightness_Wyszecki1963`: *Lightness* :math:`W`
computation of given *luminance* :math:`Y` using *Wyszecki (1963)* method.
- :func:`colour.colorimetry.lightness_CIE1976`: *Lightness* :math:`L^*`
computation of given *luminance* :math:`Y` as per *CIE 1976*
recommendation.
- :func:`colour.colorimetry.lightness_Fairchild2010`: *Lightness*
:math:`L_{hdr}` computation of given *luminance* :math:`Y` using
*Fairchild and Wyble (2010)* method.
- :func:`colour.colorimetry.lightness_Fairchild2011`: *Lightness*
:math:`L_{hdr}` computation of given *luminance* :math:`Y` using
*Fairchild and Chen (2011)* method.
- :func:`colour.colorimetry.lightness_Abebe2017`: *Lightness* :math:`L`
computation of given *luminance* :math:`Y` using
*Abebe, Pouli, Larabi and Reinhard (2017)* method.
- :attr:`colour.LIGHTNESS_METHODS`: Supported *Lightness* :math:`L`
computation methods.
- :func:`colour.lightness`: *Lightness* :math:`L` computation of given
*luminance* :math:`Y` 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:`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:`Glasser1958a` : Glasser, L. G., McKinney, A. H., Reilly, C. D., &
Schnelle, P. D. (1958). Cube-Root Color Coordinate System. Journal of the
Optical Society of America, 48(10), 736. doi:10.1364/JOSA.48.000736
- :cite:`Wikipedia2007c` : Nayatani, Y., Sobagaki, H., & Yano, K. H. T.
(1995). Lightness dependency of chroma scales of a nonlinear
color-appearance model and its latest formulation. Color Research &
Application, 20(3), 156-167. doi:10.1002/col.5080200305
- :cite:`Wyszecki1963b` : Wyszecki, Günter. (1963). Proposal for a New
Color-Difference Formula. Journal of the Optical Society of America,
53(11), 1318. doi:10.1364/JOSA.53.001318
- :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 (
reaction_rate_MichaelisMenten_Michaelis1913,
reaction_rate_MichaelisMenten_Abebe2017,
)
from colour.hints import Any, ArrayLike, NDArrayFloat, Literal
from colour.utilities import (
CanonicalMapping,
as_float,
as_float_array,
filter_kwargs,
from_range_100,
get_domain_range_scale,
to_domain_1,
to_domain_100,
usage_warning,
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__ = [
"lightness_Glasser1958",
"lightness_Wyszecki1963",
"intermediate_lightness_function_CIE1976",
"lightness_CIE1976",
"lightness_Fairchild2010",
"lightness_Fairchild2011",
"lightness_Abebe2017",
"LIGHTNESS_METHODS",
"lightness",
]
[docs]
def lightness_Glasser1958(Y: ArrayLike) -> NDArrayFloat:
"""
Return the *Lightness* :math:`L` of given *luminance* :math:`Y` using
*Glasser et al. (1958)* method.
Parameters
----------
Y
*Luminance* :math:`Y`.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`L`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Glasser1958a`
Examples
--------
>>> lightness_Glasser1958(12.19722535) # doctest: +ELLIPSIS
39.8351264...
"""
Y = to_domain_100(Y)
L = 25.29 * spow(Y, 1 / 3) - 18.38
return as_float(from_range_100(L))
[docs]
def lightness_Wyszecki1963(Y: ArrayLike) -> NDArrayFloat:
"""
Return the *Lightness* :math:`W` of given *luminance* :math:`Y` using
*Wyszecki (1963)* method.
Parameters
----------
Y
*Luminance* :math:`Y`.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`W`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``W`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Wyszecki1963b`
Examples
--------
>>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS
40.5475745...
"""
Y = to_domain_100(Y)
if np.any(Y < 1) or np.any(Y > 98):
usage_warning(
'"W*" Lightness computation is only applicable for '
'1% < "Y" < 98%, unpredictable results may occur!'
)
W = 25 * spow(Y, 1 / 3) - 17
return as_float(from_range_100(W))
[docs]
def lightness_CIE1976(Y: ArrayLike, Y_n: ArrayLike = 100) -> NDArrayFloat:
"""
Return the *Lightness* :math:`L^*` of given *luminance* :math:`Y` using
given reference white *luminance* :math:`Y_n` as per *CIE 1976*
recommendation.
Parameters
----------
Y
*Luminance* :math:`Y`.
Y_n
White reference *luminance* :math:`Y_n`.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`L^*`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_star`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd`
Examples
--------
>>> lightness_CIE1976(12.19722535) # doctest: +ELLIPSIS
41.5278758...
"""
Y = to_domain_100(Y)
Y_n = as_float_array(Y_n)
L_star = 116 * intermediate_lightness_function_CIE1976(Y, Y_n) - 16
return as_float(from_range_100(L_star))
[docs]
def lightness_Fairchild2010(
Y: ArrayLike, epsilon: ArrayLike = 1.836
) -> NDArrayFloat:
"""
Compute *Lightness* :math:`L_{hdr}` of given *luminance* :math:`Y` using
*Fairchild and Wyble (2010)* method according to *Michaelis-Menten*
kinetics.
Parameters
----------
Y
*Luminance* :math:`Y`.
epsilon
:math:`\\epsilon` exponent.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`L_{hdr}`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 1] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_hdr`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Fairchild2010`
Examples
--------
>>> lightness_Fairchild2010(12.19722535 / 100) # doctest: +ELLIPSIS
31.9963902...
"""
Y = to_domain_1(Y)
maximum_perception = 100
L_hdr = (
reaction_rate_MichaelisMenten_Michaelis1913(
spow(Y, epsilon), maximum_perception, spow(0.184, epsilon)
)
+ 0.02
)
return as_float(from_range_100(L_hdr))
[docs]
def lightness_Fairchild2011(
Y: ArrayLike,
epsilon: ArrayLike = 0.474,
method: Literal["hdr-CIELAB", "hdr-IPT"] | str = "hdr-CIELAB",
) -> NDArrayFloat:
"""
Compute *Lightness* :math:`L_{hdr}` of given *luminance* :math:`Y` using
*Fairchild and Chen (2011)* method according to *Michaelis-Menten*
kinetics.
Parameters
----------
Y
*Luminance* :math:`Y`.
epsilon
:math:`\\epsilon` exponent.
method
*Lightness* :math:`L_{hdr}` computation method.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`L_{hdr}`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 1] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L_hdr`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Fairchild2011`
Examples
--------
>>> lightness_Fairchild2011(12.19722535 / 100) # doctest: +ELLIPSIS
51.8529584...
>>> lightness_Fairchild2011(12.19722535 / 100, method="hdr-IPT")
... # doctest: +ELLIPSIS
51.6431084...
"""
Y = to_domain_1(Y)
method = validate_method(method, ("hdr-CIELAB", "hdr-IPT"))
maximum_perception = 247 if method == "hdr-cielab" else 246
L_hdr = (
reaction_rate_MichaelisMenten_Michaelis1913(
spow(Y, epsilon), maximum_perception, spow(2, epsilon)
)
+ 0.02
)
return as_float(from_range_100(L_hdr))
[docs]
def lightness_Abebe2017(
Y: ArrayLike,
Y_n: ArrayLike = 100,
method: Literal["Michaelis-Menten", "Stevens"] | str = "Michaelis-Menten",
) -> NDArrayFloat:
"""
Compute *Lightness* :math:`L` of given *luminance* :math:`Y` using
*Abebe, Pouli, Larabi and Reinhard (2017)* method according to
*Michaelis-Menten* kinetics or *Stevens's Power Law*.
Parameters
----------
Y
*Luminance* :math:`Y` in :math:`cd/m^2`.
Y_n
Adapting luminance :math:`Y_n` in :math:`cd/m^2`.
method
*Lightness* :math:`L` computation method.
Returns
-------
:class:`numpy.ndarray`
*Lightness* :math:`L`.
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** |
+============+=======================+===============+
| ``Y`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
| ``Y_n`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L`` | ``UN`` | ``UN`` |
+------------+-----------------------+---------------+
References
----------
:cite:`Abebe2017`
Examples
--------
>>> lightness_Abebe2017(12.19722535) # doctest: +ELLIPSIS
0.4869555...
>>> lightness_Abebe2017(12.19722535, method="Stevens")
... # doctest: +ELLIPSIS
0.4745447...
"""
Y = as_float_array(Y)
Y_n = as_float_array(Y_n)
method = validate_method(method, ("Michaelis-Menten", "Stevens"))
Y_Y_n = Y / Y_n
if method == "stevens":
L = np.where(
Y_n <= 100,
1.226 * spow(Y_Y_n, 0.266) - 0.226,
1.127 * spow(Y_Y_n, 0.230) - 0.127,
)
else:
L = np.where(
Y_n <= 100,
reaction_rate_MichaelisMenten_Abebe2017(
spow(Y_Y_n, 0.582), 1.448, 0.635, 0.813
),
reaction_rate_MichaelisMenten_Abebe2017(
spow(Y_Y_n, 0.293), 1.680, 1.584, 0.096
),
)
return as_float(L)
LIGHTNESS_METHODS: CanonicalMapping = CanonicalMapping(
{
"Glasser 1958": lightness_Glasser1958,
"Wyszecki 1963": lightness_Wyszecki1963,
"CIE 1976": lightness_CIE1976,
"Fairchild 2010": lightness_Fairchild2010,
"Fairchild 2011": lightness_Fairchild2011,
"Abebe 2017": lightness_Abebe2017,
}
)
LIGHTNESS_METHODS.__doc__ = """
Supported *Lightness* computation methods.
References
----------
:cite:`CIETC1-482004m`, :cite:`Fairchild2010`, :cite:`Fairchild2011`,
:cite:`Glasser1958a`, :cite:`Wyszecki1963b`, :cite:`Wyszecki2000bd`
Aliases:
- 'Lstar1976': 'CIE 1976'
"""
LIGHTNESS_METHODS["Lstar1976"] = LIGHTNESS_METHODS["CIE 1976"]
[docs]
def lightness(
Y: ArrayLike,
method: Literal[
"Abebe 2017",
"CIE 1976",
"Glasser 1958",
"Fairchild 2010",
"Fairchild 2011",
"Wyszecki 1963",
]
| str = "CIE 1976",
**kwargs: Any,
) -> NDArrayFloat:
"""
Return the *Lightness* :math:`L` of given *luminance* :math:`Y` using
given method.
Parameters
----------
Y
*Luminance* :math:`Y`.
method
Computation method.
Other Parameters
----------------
Y_n
{:func:`colour.colorimetry.lightness_Abebe2017`,
:func:`colour.colorimetry.lightness_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`
*Lightness* :math:`L`.
Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``Y`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
+------------+-----------------------+---------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``L`` | [0, 100] | [0, 1] |
+------------+-----------------------+---------------+
References
----------
:cite:`Abebe2017`, :cite:`CIETC1-482004m`, :cite:`Fairchild2010`,
:cite:`Fairchild2011`, :cite:`Glasser1958a`, :cite:`Wikipedia2007c`,
:cite:`Wyszecki1963b`, :cite:`Wyszecki2000bd`
Examples
--------
>>> lightness(12.19722535) # doctest: +ELLIPSIS
41.5278758...
>>> lightness(12.19722535, Y_n=100) # doctest: +ELLIPSIS
41.5278758...
>>> lightness(12.19722535, Y_n=95) # doctest: +ELLIPSIS
42.5199307...
>>> lightness(12.19722535, method="Glasser 1958") # doctest: +ELLIPSIS
39.8351264...
>>> lightness(12.19722535, method="Wyszecki 1963") # doctest: +ELLIPSIS
40.5475745...
>>> lightness(12.19722535, epsilon=0.710, method="Fairchild 2011")
... # doctest: +ELLIPSIS
29.8295108...
>>> lightness(12.19722535, epsilon=0.710, method="Fairchild 2011")
... # doctest: +ELLIPSIS
29.8295108...
>>> lightness(12.19722535, method="Abebe 2017")
... # doctest: +ELLIPSIS
48.6955571...
"""
Y = as_float_array(Y)
method = validate_method(method, tuple(LIGHTNESS_METHODS))
function = LIGHTNESS_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_range_100 = get_domain_range_scale() == "100"
domain_1 = (lightness_Fairchild2010, lightness_Fairchild2011)
domain_undefined = (lightness_Abebe2017,)
if function in domain_1 and domain_range_reference:
Y = Y / 100
if function in domain_undefined and domain_range_1:
Y = Y * 100
L = function(Y, **filter_kwargs(function, **kwargs))
if function in domain_undefined and (
domain_range_reference or domain_range_100
):
L *= 100
return L