Source code for colour.recovery.smits1999

"""
Smits (1999) - Reflectance Recovery
===================================

Define objects for reflectance recovery using the *Smits (1999)* method.

References
----------
-   :cite:`Smits1999a` : Smits, B. (1999). An RGB-to-Spectrum Conversion for
    Reflectances. Journal of Graphics Tools, 4(4), 11-22.
    doi:10.1080/10867651.1999.10487511
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from colour.colorimetry import (
    CCS_ILLUMINANTS,
    MultiSpectralDistributions,
    SpectralDistribution,
)
from colour.models import RGB_Colourspace, RGB_COLOURSPACE_sRGB, XYZ_to_RGB
from colour.recovery import MSDS_SMITS1999
from colour.utilities import (
    as_float_array,
    optional,
    to_domain_1,
    tsplit,
)

if TYPE_CHECKING:
    from colour.hints import ArrayLike, Domain1, NDArrayFloat, Range1

__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__ = [
    "PRIMARIES_SMITS1999",
    "WHITEPOINT_NAME_SMITS1999",
    "CCS_WHITEPOINT_SMITS1999",
    "RGB_COLOURSPACE_SMITS1999",
    "XYZ_to_RGB_Smits1999",
    "RGB_to_msds_Smits1999",
    "RGB_to_sd_Smits1999",
]

PRIMARIES_SMITS1999: NDArrayFloat = RGB_COLOURSPACE_sRGB.primaries
"""*Smits (1999)* method implementation colourspace primaries."""

WHITEPOINT_NAME_SMITS1999 = "E"
"""*Smits (1999)* method implementation colourspace whitepoint name."""

CCS_WHITEPOINT_SMITS1999: NDArrayFloat = CCS_ILLUMINANTS[
    "CIE 1931 2 Degree Standard Observer"
][WHITEPOINT_NAME_SMITS1999]
"""*Smits (1999)* method implementation colourspace whitepoint."""

RGB_COLOURSPACE_SMITS1999 = RGB_Colourspace(
    "Smits 1999",
    PRIMARIES_SMITS1999,
    CCS_WHITEPOINT_SMITS1999,
    WHITEPOINT_NAME_SMITS1999,
)
RGB_COLOURSPACE_SMITS1999.__doc__ = """
*Smits (1999)* colourspace.

References
----------
:cite:`Smits1999a`,
"""


def XYZ_to_RGB_Smits1999(XYZ: Domain1) -> Range1:
    """
    Convert from *CIE XYZ* tristimulus values to *RGB* colourspace using
    the conditions required by the current *Smits (1999)* method
    implementation.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.

    Returns
    -------
    :class:`numpy.ndarray`
        *RGB* colour array.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | 1                     | 1             |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``RGB``    | 1                     | 1             |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> import numpy as np
    >>> XYZ = np.array([0.21781186, 0.12541048, 0.04697113])
    >>> XYZ_to_RGB_Smits1999(XYZ)  # doctest: +ELLIPSIS
    array([0.4063959..., 0.0275289..., 0.0398219...])
    """

    return XYZ_to_RGB(XYZ, RGB_COLOURSPACE_SMITS1999)


[docs] def RGB_to_msds_Smits1999( RGB: ArrayLike, basis: MultiSpectralDistributions | None = None, ) -> NDArrayFloat: """ Recover spectral values from *RGB* colourspace array using the *Smits (1999)* decomposition algorithm. This is a vectorised implementation supporting multi-dimensional arrays. Parameters ---------- RGB *RGB* colourspace array to recover spectral values from. The last dimension must be size 3. basis Multi-spectral distributions basis with signals: white, cyan, magenta, yellow, red, green, blue. Defaults to :attr:`MSDS_SMITS1999`. Returns ------- :class:`numpy.ndarray` Recovered spectral values with shape ``(*RGB.shape[:-1], wavelengths)``. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`Smits1999a` Examples -------- >>> import numpy as np >>> RGB = np.array( ... [ ... [0.45623196, 0.03080455, 0.04093343], ... [0.05438271, 0.29877169, 0.07188444], ... [0.01863137, 0.05139773, 0.28887675], ... ] ... ) >>> RGB_to_msds_Smits1999(RGB).shape (3, 10) >>> float(RGB_to_msds_Smits1999(RGB)[0, 0]) # doctest: +ELLIPSIS 0.0829... """ basis = optional(basis, MSDS_SMITS1999) RGB = to_domain_1(as_float_array(RGB)) shape = RGB.shape RGB = np.atleast_2d(RGB.reshape(-1, 3)) R, G, B = tsplit(RGB) labels = list(basis.labels) white = basis.values[:, labels.index("white")] cyan = basis.values[:, labels.index("cyan")] magenta = basis.values[:, labels.index("magenta")] yellow = basis.values[:, labels.index("yellow")] red = basis.values[:, labels.index("red")] green = basis.values[:, labels.index("green")] blue = basis.values[:, labels.index("blue")] R = R[..., np.newaxis] G = G[..., np.newaxis] B = B[..., np.newaxis] # if R <= G and R <= B: # sd += white * R # if G <= B: # sd += cyan * (G - R) + blue * (B - G) # else: # sd += cyan * (B - R) + green * (G - B) R_le_G_le_B = (R <= G) & (R <= B) & (G <= B) R_le_B_lt_G = (R <= G) & (R <= B) & (G > B) # elif G <= R and G <= B: # sd += white * G # if R <= B: # sd += magenta * (R - G) + blue * (B - R) # else: # sd += magenta * (B - G) + red * (R - B) G_lt_R_le_B = (G < R) & (G <= B) & (R <= B) G_le_B_lt_R = (G < R) & (G <= B) & (R > B) # else: # B < R and B < G # sd += white * B # if R <= G: # sd += yellow * (R - B) + green * (G - R) # else: # sd += yellow * (G - B) + red * (R - G) B_lt_R_le_G = (B < R) & (B < G) & (R <= G) B_lt_G_lt_R = (B < R) & (B < G) & (R > G) spectra = np.select( [R_le_G_le_B, R_le_B_lt_G, G_lt_R_le_B, G_le_B_lt_R, B_lt_R_le_G, B_lt_G_lt_R], [ white * R + cyan * (G - R) + blue * (B - G), white * R + cyan * (B - R) + green * (G - B), white * G + magenta * (R - G) + blue * (B - R), white * G + magenta * (B - G) + red * (R - B), white * B + yellow * (R - B) + green * (G - R), white * B + yellow * (G - B) + red * (R - G), ], ) return np.reshape(spectra, [*list(shape[:-1]), len(white)])
[docs] def RGB_to_sd_Smits1999( RGB: Domain1, basis: MultiSpectralDistributions | None = None, name: str | None = None, ) -> SpectralDistribution: """ Generate a spectral distribution from *RGB* values using the *Smits (1999)* decomposition algorithm. Parameters ---------- RGB *RGB* colourspace array to recover the spectral distribution from. basis Multi-spectral distributions basis with signals: white, cyan, magenta, yellow, red, green, blue. Defaults to :attr:`MSDS_SMITS1999`. name Name for the resulting spectral distribution. Returns ------- :class:`colour.SpectralDistribution` Recovered spectral distribution. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`Smits1999a` Examples -------- >>> import numpy as np >>> from colour import MSDS_CMFS, SDS_ILLUMINANTS, SpectralShape >>> from colour.colorimetry import sd_to_XYZ_integration >>> from colour.utilities import numpy_print_options >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> RGB = XYZ_to_RGB_Smits1999(XYZ) >>> cmfs = ( ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] ... .copy() ... .align(SpectralShape(360, 780, 10)) ... ) >>> illuminant = SDS_ILLUMINANTS["E"].copy().align(cmfs.shape) >>> sd = RGB_to_sd_Smits1999(RGB) >>> with numpy_print_options(suppress=True): ... sd # doctest: +ELLIPSIS SpectralDistribution([[380. , 0.0787830...], [417.7778 , 0.0622018...], [455.5556 , 0.0446206...], [493.3333 , 0.0352220...], [531.1111 , 0.0324149...], [568.8889 , 0.0330105...], [606.6667 , 0.3207115...], [644.4444 , 0.3836164...], [682.2222 , 0.3836164...], [720. , 0.3835649...]], LinearInterpolator, {}, Extrapolator, {'method': 'Constant', 'left': None, 'right': None}) >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 # doctest: +ELLIPSIS array([0.1894770..., 0.1126470..., 0.0474420...]) """ basis = optional(basis, MSDS_SMITS1999) name = optional(name, f"Smits (1999) - {RGB!r}") values = RGB_to_msds_Smits1999(RGB, basis) return SpectralDistribution( values, basis.wavelengths, name=name, interpolator=basis.interpolator, interpolator_kwargs=basis.interpolator_kwargs, )