"""
:math:`IC_TC_P` Colour Encoding
===============================
Define the :math:`IC_TC_P` colour encoding related transformations:
- :func:`colour.RGB_to_ICtCp`
- :func:`colour.ICtCp_to_RGB`
- :func:`colour.XYZ_to_ICtCp`
- :func:`colour.ICtCp_to_XYZ`
References
----------
- :cite:`Dolby2016a` : Dolby. (2016). WHAT IS ICtCp? - INTRODUCTION.
https://www.dolby.com/us/en/technologies/dolby-vision/ICtCp-white-paper.pdf
- :cite:`InternationalTelecommunicationUnion2018` : International
Telecommunication Union. (2018). Recommendation ITU-R BT.2100-2 - Image
parameter values for high dynamic range television for use in production
and international programme exchange.
https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
R-REC-BT.2100-2-201807-I!!PDF-E.pdf
- :cite:`Lu2016c` : Lu, T., Pu, F., Yin, P., Chen, T., Husak, W., Pytlarz,
J., Atkins, R., Froehlich, J., & Su, G.-M. (2016). ITP Colour Space and Its
Compression Performance for High Dynamic Range and Wide Colour Gamut Video
Distribution. ZTE Communications, 14(1), 32-38.
"""
from __future__ import annotations
import typing
import numpy as np
from colour.algebra import vecmul
from colour.colorimetry import CCS_ILLUMINANTS
if typing.TYPE_CHECKING:
from colour.hints import (
ArrayLike,
Literal,
LiteralChromaticAdaptationTransform,
NDArrayFloat,
)
from colour.models.rgb import RGB_COLOURSPACES, RGB_to_XYZ, XYZ_to_RGB
from colour.models.rgb.transfer_functions import (
eotf_inverse_ST2084,
eotf_ST2084,
oetf_BT2100_HLG,
oetf_inverse_BT2100_HLG,
)
from colour.utilities import as_float_array, domain_range_scale, 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__ = [
"MATRIX_ICTCP_RGB_TO_LMS",
"MATRIX_ICTCP_LMS_TO_RGB",
"MATRIX_ICTCP_LMS_P_TO_ICTCP",
"MATRIX_ICTCP_ICTCP_TO_LMS_P",
"MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2",
"MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2",
"RGB_to_ICtCp",
"ICtCp_to_RGB",
"XYZ_to_ICtCp",
"ICtCp_to_XYZ",
]
MATRIX_ICTCP_RGB_TO_LMS: NDArrayFloat = (
np.array(
[
[1688, 2146, 262],
[683, 2951, 462],
[99, 309, 3688],
]
)
/ 4096
)
"""*ITU-R BT.2020* colourspace to normalised cone responses matrix."""
MATRIX_ICTCP_LMS_TO_RGB: NDArrayFloat = np.linalg.inv(MATRIX_ICTCP_RGB_TO_LMS)
"""
:math:`IC_TC_P` colourspace normalised cone responses to *ITU-R BT.2020*
colourspace matrix.
"""
MATRIX_ICTCP_LMS_P_TO_ICTCP: NDArrayFloat = (
np.array(
[
[2048, 2048, 0],
[6610, -13613, 7003],
[17933, -17390, -543],
]
)
/ 4096
)
"""
:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to
:math:`IC_TC_P` colour encoding matrix.
"""
MATRIX_ICTCP_ICTCP_TO_LMS_P: NDArrayFloat = np.linalg.inv(MATRIX_ICTCP_LMS_P_TO_ICTCP)
"""
:math:`IC_TC_P` colour encoding to :math:`LMS_p` *SMPTE ST 2084:2014* encoded
normalised cone responses matrix.
"""
MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2: NDArrayFloat = (
np.array(
[
[2048, 2048, 0],
[3625, -7465, 3840],
[9500, -9212, -288],
]
)
/ 4096
)
"""
:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to
:math:`IC_TC_P` colour encoding matrix as given in *ITU-R BT.2100-2*.
"""
MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2: NDArrayFloat = np.linalg.inv(
MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2
)
"""
:math:`IC_TC_P` colour encoding to :math:`LMS_p` *SMPTE ST 2084:2014* encoded
normalised cone responses matrix as given in *ITU-R BT.2100-2*.
"""
[docs]
def RGB_to_ICtCp(
RGB: ArrayLike,
method: (
Literal[
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
]
| str
) = "Dolby 2016",
L_p: float = 10000,
) -> NDArrayFloat:
"""
Convert from *ITU-R BT.2020* colourspace to :math:`IC_TC_P` colour
encoding.
Parameters
----------
RGB
*ITU-R BT.2020* colourspace array.
method
Computation method. *Recommendation ITU-R BT.2100* defines multiple
variants of the :math:`IC_TC_P` colour encoding:
- *ITU-R BT.2100-1*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and the :math:`IC_TC_P` matrix
from :cite:`Dolby2016a`: *ITU-R BT.2100-1 HLG* method.
- *ITU-R BT.2100-2*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and a custom :math:`IC_TC_P`
matrix from :cite:`InternationalTelecommunicationUnion2018`:
*ITU-R BT.2100-2 HLG* method.
L_p
Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
non-linear encoding. This parameter should stay at its default
:math:`10000 cd/m^2` value for practical applications. It is exposed so
that the definition can be used as a fitting function.
Returns
-------
:class:`numpy.ndarray`
:math:`IC_TC_P` colour encoding array.
Warnings
--------
The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function.
Notes
-----
- The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are aliases
for the *Dolby 2016* method.
- The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function, thus the domain and range values for the *Reference*
and *1* scales are only indicative that the data is not affected by
scale transformations. The effective domain of *SMPTE ST 2084:2014*
inverse electro-optical transfer function (EOTF) is
[0.0001, 10000].
+------------+-----------------------+------------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``RGB`` | ``UN`` | ``UN`` |
+------------+-----------------------+------------------+
+------------+-----------------------+------------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``ICtCp`` | ``I`` : [0, 1] | ``I`` : [0, 1] |
| | | |
| | ``CT`` : [-1, 1] | ``CT`` : [-1, 1] |
| | | |
| | ``CP`` : [-1, 1] | ``CP`` : [-1, 1] |
+------------+-----------------------+------------------+
References
----------
:cite:`Dolby2016a`, :cite:`Lu2016c`
Examples
--------
>>> RGB = np.array([0.45620519, 0.03081071, 0.04091952])
>>> RGB_to_ICtCp(RGB) # doctest: +ELLIPSIS
array([ 0.0735136..., 0.0047525..., 0.0935159...])
>>> RGB_to_ICtCp(RGB, method="ITU-R BT.2100-2 HLG") # doctest: +ELLIPSIS
array([ 0.6256789..., -0.0198449..., 0.3591125...])
"""
RGB = as_float_array(RGB)
method = validate_method(
method,
(
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
),
)
is_hlg_method = "hlg" in method
is_BT2100_2_method = "2100-2" in method
LMS = vecmul(MATRIX_ICTCP_RGB_TO_LMS, RGB)
with domain_range_scale("ignore"):
LMS_p = oetf_BT2100_HLG(LMS) if is_hlg_method else eotf_inverse_ST2084(LMS, L_p)
return (
vecmul(MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2, LMS_p)
if (is_hlg_method and is_BT2100_2_method)
else vecmul(MATRIX_ICTCP_LMS_P_TO_ICTCP, LMS_p)
)
[docs]
def ICtCp_to_RGB(
ICtCp: ArrayLike,
method: (
Literal[
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
]
| str
) = "Dolby 2016",
L_p: float = 10000,
) -> NDArrayFloat:
"""
Convert from :math:`IC_TC_P` colour encoding to *ITU-R BT.2020*
colourspace.
Parameters
----------
ICtCp
:math:`IC_TC_P` colour encoding array.
method
Computation method. *Recommendation ITU-R BT.2100* defines multiple
variants of the :math:`IC_TC_P` colour encoding:
- *ITU-R BT.2100-1*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and the :math:`IC_TC_P` matrix
from :cite:`Dolby2016a`: *ITU-R BT.2100-1 HLG* method.
- *ITU-R BT.2100-2*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and a custom :math:`IC_TC_P`
matrix from :cite:`InternationalTelecommunicationUnion2018`:
*ITU-R BT.2100-2 HLG* method.
L_p
Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
non-linear encoding. This parameter should stay at its default
:math:`10000 cd/m^2` value for practical applications. It is exposed so
that the definition can be used as a fitting function.
Returns
-------
:class:`numpy.ndarray`
*ITU-R BT.2020* colourspace array.
Warnings
--------
The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function.
Notes
-----
- The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are aliases
for the *Dolby 2016* method.
- The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function, 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** |
+============+=======================+==================+
| ``ICtCp`` | ``I`` : [0, 1] | ``I`` : [0, 1] |
| | | |
| | ``CT`` : [-1, 1] | ``CT`` : [-1, 1] |
| | | |
| | ``CP`` : [-1, 1] | ``CP`` : [-1, 1] |
+------------+-----------------------+------------------+
+------------+-----------------------+------------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``RGB`` | ``UN`` | ``UN`` |
+------------+-----------------------+------------------+
References
----------
:cite:`Dolby2016a`, :cite:`Lu2016c`
Examples
--------
>>> ICtCp = np.array([0.07351364, 0.00475253, 0.09351596])
>>> ICtCp_to_RGB(ICtCp) # doctest: +ELLIPSIS
array([ 0.4562052..., 0.0308107..., 0.0409195...])
>>> ICtCp = np.array([0.62567899, -0.01984490, 0.35911259])
>>> ICtCp_to_RGB(ICtCp, method="ITU-R BT.2100-2 HLG") # doctest: +ELLIPSIS
array([ 0.4562052..., 0.0308107..., 0.0409195...])
"""
ICtCp = as_float_array(ICtCp)
method = validate_method(
method,
(
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
),
)
is_hlg_method = "hlg" in method
is_BT2100_2_method = "2100-2" in method
LMS_p = (
vecmul(MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2, ICtCp)
if (is_hlg_method and is_BT2100_2_method)
else vecmul(MATRIX_ICTCP_ICTCP_TO_LMS_P, ICtCp)
)
with domain_range_scale("ignore"):
LMS = (
oetf_inverse_BT2100_HLG(LMS_p) if is_hlg_method else eotf_ST2084(LMS_p, L_p)
)
return vecmul(MATRIX_ICTCP_LMS_TO_RGB, LMS)
[docs]
def XYZ_to_ICtCp(
XYZ: ArrayLike,
illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
"D65"
],
chromatic_adaptation_transform: (
LiteralChromaticAdaptationTransform | str | None
) = "CAT02",
method: (
Literal[
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
]
| str
) = "Dolby 2016",
L_p: float = 10000,
) -> NDArrayFloat:
"""
Convert from *CIE XYZ* tristimulus values to :math:`IC_TC_P` colour
encoding.
Parameters
----------
XYZ
*CIE XYZ* tristimulus values.
illuminant
Source illuminant chromaticity coordinates.
chromatic_adaptation_transform
*Chromatic adaptation* transform.
method
Computation method. *Recommendation ITU-R BT.2100* defines multiple
variants of the :math:`IC_TC_P` colour encoding:
- *ITU-R BT.2100-1*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and the :math:`IC_TC_P` matrix
from :cite:`Dolby2016a`: *ITU-R BT.2100-1 HLG* method.
- *ITU-R BT.2100-2*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and a custom :math:`IC_TC_P`
matrix from :cite:`InternationalTelecommunicationUnion2018`:
*ITU-R BT.2100-2 HLG* method.
L_p
Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
non-linear encoding. This parameter should stay at its default
:math:`10000 cd/m^2` value for practical applications. It is exposed so
that the definition can be used as a fitting function.
Returns
-------
:class:`numpy.ndarray`
:math:`IC_TC_P` colour encoding array.
Warnings
--------
The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function.
Notes
-----
- The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function, thus the domain and range values for the *Reference*
- The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are aliases
for the *Dolby 2016* method.
and *1* scales are only indicative that the data is not affected by
scale transformations. The effective domain of *SMPTE ST 2084:2014*
inverse electro-optical transfer function (EOTF) is
[0.0001, 10000].
+------------+-----------------------+------------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``XYZ`` | ``UN`` | ``UN`` |
+------------+-----------------------+------------------+
+------------+-----------------------+------------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``ICtCp`` | ``I`` : [0, 1] | ``I`` : [0, 1] |
| | | |
| | ``CT`` : [-1, 1] | ``CT`` : [-1, 1] |
| | | |
| | ``CP`` : [-1, 1] | ``CP`` : [-1, 1] |
+------------+-----------------------+------------------+
References
----------
:cite:`Dolby2016a`, :cite:`Lu2016c`
Examples
--------
>>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
>>> XYZ_to_ICtCp(XYZ) # doctest: +ELLIPSIS
array([ 0.0685809..., -0.0028384..., 0.0602098...])
>>> XYZ_to_ICtCp(XYZ, method="ITU-R BT.2100-2 HLG") # doctest: +ELLIPSIS
array([ 0.5924279..., -0.0374073..., 0.2512267...])
"""
RGB = XYZ_to_RGB(
XYZ,
RGB_COLOURSPACES["ITU-R BT.2020"],
illuminant,
chromatic_adaptation_transform,
)
return RGB_to_ICtCp(RGB, method, L_p)
[docs]
def ICtCp_to_XYZ(
ICtCp: ArrayLike,
illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
"D65"
],
chromatic_adaptation_transform: (
LiteralChromaticAdaptationTransform | str | None
) = "CAT02",
method: (
Literal[
"Dolby 2016",
"ITU-R BT.2100-1 HLG",
"ITU-R BT.2100-1 PQ",
"ITU-R BT.2100-2 HLG",
"ITU-R BT.2100-2 PQ",
]
| str
) = "Dolby 2016",
L_p: float = 10000,
) -> NDArrayFloat:
"""
Convert from :math:`IC_TC_P` colour encoding to *CIE XYZ* tristimulus
values.
Parameters
----------
ICtCp
:math:`IC_TC_P` colour encoding array.
illuminant
Source illuminant chromaticity coordinates.
chromatic_adaptation_transform
*Chromatic adaptation* transform.
method
Computation method. *Recommendation ITU-R BT.2100* defines multiple
variants of the :math:`IC_TC_P` colour encoding:
- *ITU-R BT.2100-1*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and the :math:`IC_TC_P` matrix
from :cite:`Dolby2016a`: *ITU-R BT.2100-1 HLG* method.
- *ITU-R BT.2100-2*
- *SMPTE ST 2084:2014* inverse electro-optical transfer
function (EOTF) and the :math:`IC_TC_P` matrix from
:cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
*ITU-R BT.2100-2 PQ* methods.
- *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
transfer function (OETF) and a custom :math:`IC_TC_P`
matrix from :cite:`InternationalTelecommunicationUnion2018`:
*ITU-R BT.2100-2 HLG* method.
L_p
Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
non-linear encoding. This parameter should stay at its default
:math:`10000 cd/m^2` value for practical applications. It is exposed so
that the definition can be used as a fitting function.
Returns
-------
:class:`numpy.ndarray`
*CIE XYZ* tristimulus values.
Warnings
--------
The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function.
Notes
-----
- The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are aliases
for the *Dolby 2016* method.
- The underlying *SMPTE ST 2084:2014* transfer function is an absolute
transfer function, 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** |
+============+=======================+==================+
| ``ICtCp`` | ``I`` : [0, 1] | ``I`` : [0, 1] |
| | | |
| | ``CT`` : [-1, 1] | ``CT`` : [-1, 1] |
| | | |
| | ``CP`` : [-1, 1] | ``CP`` : [-1, 1] |
+------------+-----------------------+------------------+
+------------+-----------------------+------------------+
| **Range** | **Scale - Reference** | **Scale - 1** |
+============+=======================+==================+
| ``XYZ`` | ``UN`` | ``UN`` |
+------------+-----------------------+------------------+
References
----------
:cite:`Dolby2016a`, :cite:`Lu2016c`
Examples
--------
>>> ICtCp = np.array([0.06858097, -0.00283842, 0.06020983])
>>> ICtCp_to_XYZ(ICtCp) # doctest: +ELLIPSIS
array([ 0.2065400..., 0.1219722..., 0.0513695...])
>>> ICtCp = np.array([0.59242792, -0.03740730, 0.25122675])
>>> ICtCp_to_XYZ(ICtCp, method="ITU-R BT.2100-2 HLG") # doctest: +ELLIPSIS
array([ 0.2065400..., 0.1219722..., 0.0513695...])
"""
RGB = ICtCp_to_RGB(ICtCp, method, L_p)
return RGB_to_XYZ(
RGB,
RGB_COLOURSPACES["ITU-R BT.2020"],
illuminant,
chromatic_adaptation_transform,
)