Source code for colour.models.rgb.derivation

"""
RGB Colourspace Derivation
==========================

Define objects for *RGB* colourspace derivation, primarily focused on
calculating the normalised primary matrix from the specified *RGB* colourspace
primaries and whitepoint.

-   :func:`colour.normalised_primary_matrix`
-   :func:`colour.chromatically_adapted_primaries`
-   :func:`colour.primaries_whitepoint`
-   :func:`colour.RGB_luminance_equation`
-   :func:`colour.RGB_luminance`

References
----------
-   :cite:`SocietyofMotionPictureandTelevisionEngineers1993a` : Society of
    Motion Picture and Television Engineers. (1993). RP 177:1993 - Derivation
    of Basic Television Color Equations. In RP 177:1993: Vol. RP 177:199. The
    Society of Motion Picture and Television Engineers.
    doi:10.5594/S9781614821915
-   :cite:`Trieu2015a` : Borer, T. (2017). Private Discussion with Mansencal,
    T. and Shaw, N.
"""

from __future__ import annotations

import typing

from colour.adaptation import chromatic_adaptation_VonKries

if typing.TYPE_CHECKING:
    from colour.hints import LiteralChromaticAdaptationTransform, Tuple

from colour.hints import ArrayLike, NDArrayFloat, Range1  # noqa: TC001
from colour.models import XYZ_to_xy, XYZ_to_xyY, xy_to_XYZ
from colour.utilities import (
    array_namespace,
    as_float,
    as_float_array,
    ones,
    tsplit,
    xp_as_float_array,
    xp_create_diagonal,
    xp_matrix_transpose,
    xp_reshape,
)

__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__ = [
    "xy_to_z",
    "normalised_primary_matrix",
    "chromatically_adapted_primaries",
    "primaries_whitepoint",
    "RGB_luminance_equation",
    "RGB_luminance",
]


def xy_to_z(xy: ArrayLike) -> float:
    """
    Return the *z* coordinate using the specified :math:`xy` chromaticity
    coordinates.

    Parameters
    ----------
    xy
        :math:`xy` chromaticity coordinates.

    Returns
    -------
    :class:`float`
        *z* coordinate.

    Examples
    --------
    >>> import numpy as np
    >>> xy_to_z(np.array([0.25, 0.25]))
    np.float64(0.5)
    """

    x, y = tsplit(xy)
    return 1 - x - y


[docs] def normalised_primary_matrix( primaries: ArrayLike, whitepoint: ArrayLike ) -> NDArrayFloat: """ Compute the *Normalised Primary Matrix* (NPM) converting a *RGB* colourspace array to *CIE XYZ* tristimulus values using the specified *primaries* and *whitepoint* :math:`xy` chromaticity coordinates. Parameters ---------- primaries Primaries :math:`xy` chromaticity coordinates. whitepoint Illuminant / whitepoint :math:`xy` chromaticity coordinates. Returns ------- :class:`numpy.ndarray` *Normalised Primary Matrix* (NPM). References ---------- :cite:`SocietyofMotionPictureandTelevisionEngineers1993a` Examples -------- >>> import numpy as np >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) >>> w = np.array([0.32168, 0.33767]) >>> normalised_primary_matrix(p, w) # doctest: +ELLIPSIS array([[ 9.5255239...e-01, 0.0000000...e+00, 9.3678631...e-05], [ 3.4396645...e-01, 7.2816609...e-01, -7.2132546...e-02], [ 0.0000000...e+00, 0.0000000...e+00, 1.0088251...e+00]]) """ primaries = as_float_array(primaries) xp = array_namespace(primaries) primaries = xp_reshape(primaries, (3, 2), xp=xp) z = as_float_array(xy_to_z(primaries))[..., None] primaries = xp_matrix_transpose(xp.concat([primaries, z], axis=1), xp=xp) whitepoint = xp_as_float_array(xy_to_XYZ(whitepoint), xp=xp, like=primaries) coefficients = xp.matmul(xp.linalg.inv(primaries), whitepoint) coefficients = xp_create_diagonal(coefficients, xp=xp) return xp.matmul(primaries, coefficients)
[docs] def chromatically_adapted_primaries( primaries: ArrayLike, whitepoint_t: ArrayLike, whitepoint_r: ArrayLike, chromatic_adaptation_transform: ( LiteralChromaticAdaptationTransform | str ) = "CAT02", ) -> NDArrayFloat: """ Chromatically adapt specified *primaries* :math:`xy` chromaticity coordinates from test ``whitepoint_t`` to reference ``whitepoint_r``. Parameters ---------- primaries Primaries :math:`xy` chromaticity coordinates. whitepoint_t Test illuminant / whitepoint :math:`xy` chromaticity coordinates. whitepoint_r Reference illuminant / whitepoint :math:`xy` chromaticity coordinates. chromatic_adaptation_transform *Chromatic adaptation* transform. Returns ------- :class:`numpy.ndarray` Chromatically adapted primaries :math:`xy` chromaticity coordinates. Examples -------- >>> import numpy as np >>> p = np.array([0.64, 0.33, 0.30, 0.60, 0.15, 0.06]) >>> w_t = np.array([0.31270, 0.32900]) >>> w_r = np.array([0.34570, 0.35850]) >>> chromatic_adaptation_transform = "Bradford" >>> chromatically_adapted_primaries(p, w_t, w_r, chromatic_adaptation_transform) ... # doctest: +ELLIPSIS array([[0.6484414..., 0.3308533...], [0.3211951..., 0.5978443...], [0.1558932..., 0.0660492...]]) """ primaries = as_float_array(primaries) xp = array_namespace(primaries) primaries = xp_reshape(primaries, (3, 2), xp=xp) XYZ_a = chromatic_adaptation_VonKries( xy_to_XYZ(primaries), xy_to_XYZ(whitepoint_t), xy_to_XYZ(whitepoint_r), chromatic_adaptation_transform, ) return XYZ_to_xyY(XYZ_a)[..., 0:2]
[docs] def primaries_whitepoint(npm: ArrayLike) -> Tuple[NDArrayFloat, NDArrayFloat]: """ Compute the *primaries* and *whitepoint* :math:`xy` chromaticity coordinates using the specified *Normalised Primary Matrix* (NPM). Parameters ---------- npm *Normalised Primary Matrix*. Returns ------- :class:`tuple` *Primaries* and *whitepoint* :math:`xy` chromaticity coordinates. References ---------- :cite:`Trieu2015a` Examples -------- >>> import numpy as np >>> npm = np.array( ... [ ... [9.52552396e-01, 0.00000000e00, 9.36786317e-05], ... [3.43966450e-01, 7.28166097e-01, -7.21325464e-02], ... [0.00000000e00, 0.00000000e00, 1.00882518e00], ... ] ... ) >>> p, w = primaries_whitepoint(npm) >>> p # doctest: +ELLIPSIS array([[ 7.3470000...e-01, 2.6530000...e-01], [ 0.0000000...e+00, 1.0000000...e+00], [ 1.0000000...e-04, -7.7000000...e-02]]) >>> w # doctest: +ELLIPSIS array([0.32168, 0.33767]) """ npm = as_float_array(npm) xp = array_namespace(npm) npm = xp_reshape(npm, (3, 3), xp=xp) primaries = XYZ_to_xy(xp_matrix_transpose(npm, xp=xp)) whitepoint = xp_reshape( XYZ_to_xy( xp_matrix_transpose( xp.matmul(npm, xp_as_float_array(ones((3, 1)), xp=xp, like=npm)), xp=xp, ) ), (-1,), xp=xp, ) # TODO: Investigate if we return an ndarray here with primaries and # whitepoint stacked together. return primaries, whitepoint
[docs] def RGB_luminance_equation(primaries: ArrayLike, whitepoint: ArrayLike) -> str: """ Return the *luminance equation* from the specified *primaries* and *whitepoint*. Parameters ---------- primaries Primaries chromaticity coordinates. whitepoint Illuminant / whitepoint chromaticity coordinates. Returns ------- :class:`str` *Luminance* equation. Examples -------- >>> import numpy as np >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) >>> whitepoint = np.array([0.32168, 0.33767]) >>> RGB_luminance_equation(p, whitepoint) # doctest: +ELLIPSIS 'Y = 0.3439664...(R) + 0.7281660...(G) + -0.0721325...(B)' """ primaries = as_float_array(primaries) xp = array_namespace(primaries) return "Y = {}(R) + {}(G) + {}(B)".format( *xp_reshape(normalised_primary_matrix(primaries, whitepoint), (-1,), xp=xp)[3:6] )
[docs] def RGB_luminance( RGB: ArrayLike, primaries: ArrayLike, whitepoint: ArrayLike ) -> Range1: """ Calculate the *luminance* :math:`Y` of the specified *RGB* components using the specified *primaries* and *whitepoint* chromaticity coordinates. Parameters ---------- RGB *RGB* colour components array. primaries Primaries :math:`xy` chromaticity coordinates array. whitepoint Illuminant / whitepoint :math:`xy` chromaticity coordinates. Returns ------- :class:`numpy.ndarray` *Luminance* :math:`Y`. Notes ----- +-----------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===========+=======================+===============+ | ``Y`` | 1 | 1 | +-----------+-----------------------+---------------+ Examples -------- >>> import numpy as np >>> RGB = np.array([0.21959402, 0.06986677, 0.04703877]) >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) >>> whitepoint = np.array([0.32168, 0.33767]) >>> RGB_luminance(RGB, p, whitepoint) # doctest: +ELLIPSIS np.float64(0.1230145...) """ RGB = as_float_array(RGB) xp = array_namespace(RGB) Y = xp.sum( xp_as_float_array( normalised_primary_matrix(primaries, whitepoint)[1], xp=xp, like=RGB ) * RGB, axis=-1, ) return as_float(Y)