Source code for colour.quality.cri

"""
Colour Rendering Index
======================

Defines the *Colour Rendering Index* (CRI) computation objects:

-   :class:`colour.quality.ColourRendering_Specification_CRI`
-   :func:`colour.colour_rendering_index`

References
----------
-   :cite:`Ohno2008a` : Ohno, Yoshiro, & Davis, W. (2008). NIST CQS simulation
    (Version 7.4) [Computer software].
    https://drive.google.com/file/d/1PsuU6QjUJjCX6tQyCud6ul2Tbs8rYWW9/view?\
usp=sharing
"""

from __future__ import annotations

from dataclasses import dataclass

import numpy as np

from colour.algebra import euclidean_distance, sdiv, sdiv_mode, spow
from colour.colorimetry import (
    MSDS_CMFS,
    SPECTRAL_SHAPE_DEFAULT,
    MultiSpectralDistributions,
    SpectralDistribution,
    reshape_msds,
    reshape_sd,
    sd_blackbody,
    sd_CIE_illuminant_D_series,
    sd_to_XYZ,
)
from colour.hints import Dict, NDArrayFloat, Tuple, cast
from colour.models import UCS_to_uv, XYZ_to_UCS, XYZ_to_xyY
from colour.quality.datasets.tcs import INDEXES_TO_NAMES_TCS, SDS_TCS
from colour.temperature import CCT_to_xy_CIE_D, uv_to_CCT_Robertson1968
from colour.utilities import domain_range_scale

__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__ = [
    "DataColorimetry_TCS",
    "DataColourQualityScale_TCS",
    "ColourRendering_Specification_CRI",
    "colour_rendering_index",
    "tcs_colorimetry_data",
    "colour_rendering_indexes",
]


@dataclass
class DataColorimetry_TCS:
    """Define the class storing *test colour samples* colorimetry data."""

    name: str
    XYZ: NDArrayFloat
    uv: NDArrayFloat
    UVW: NDArrayFloat


@dataclass
class DataColourQualityScale_TCS:
    """
    Define the class storing *test colour samples* colour rendering index data.
    """

    name: str
    Q_a: float


[docs] @dataclass() class ColourRendering_Specification_CRI: """ Define the *Colour Rendering Index* (CRI) colour quality specification. Parameters ---------- name Name of the test spectral distribution. Q_a *Colour Rendering Index* (CRI) :math:`Q_a`. Q_as Individual *colour rendering indexes* data for each sample. colorimetry_data Colorimetry data for the test and reference computations. References ---------- :cite:`Ohno2008a` """ name: str Q_a: float Q_as: Dict[int, DataColourQualityScale_TCS] colorimetry_data: Tuple[ Tuple[DataColorimetry_TCS, ...], Tuple[DataColorimetry_TCS, ...] ]
[docs] def colour_rendering_index( sd_test: SpectralDistribution, additional_data: bool = False ) -> float | ColourRendering_Specification_CRI: """ Return the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral distribution. Parameters ---------- sd_test Test spectral distribution. additional_data Whether to output additional data. Returns ------- :class:`float` or \ :class:`colour.quality.ColourRendering_Specification_CRI` *Colour Rendering Index* (CRI). References ---------- :cite:`Ohno2008a` Examples -------- >>> from colour import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS["FL2"] >>> colour_rendering_index(sd) # doctest: +ELLIPSIS 64.2337241... """ cmfs = reshape_msds( MSDS_CMFS["CIE 1931 2 Degree Standard Observer"], SPECTRAL_SHAPE_DEFAULT, copy=False, ) shape = cmfs.shape sd_test = reshape_sd(sd_test, shape, copy=False) tcs_sds = {sd.name: reshape_sd(sd, shape, copy=False) for sd in SDS_TCS.values()} with domain_range_scale("1"): XYZ = sd_to_XYZ(sd_test, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, _D_uv = uv_to_CCT_Robertson1968(uv) if CCT < 5000: sd_reference = sd_blackbody(CCT, shape) else: xy = CCT_to_xy_CIE_D(CCT) sd_reference = sd_CIE_illuminant_D_series(xy) sd_reference.align(shape) test_tcs_colorimetry_data = tcs_colorimetry_data( sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True ) reference_tcs_colorimetry_data = tcs_colorimetry_data( sd_reference, sd_reference, tcs_sds, cmfs ) Q_as = colour_rendering_indexes( test_tcs_colorimetry_data, reference_tcs_colorimetry_data ) Q_a = cast( float, np.average([v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)]), ) if additional_data: return ColourRendering_Specification_CRI( sd_test.name, Q_a, Q_as, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data), ) else: return Q_a
def tcs_colorimetry_data( sd_t: SpectralDistribution, sd_r: SpectralDistribution, sds_tcs: Dict[str, SpectralDistribution], cmfs: MultiSpectralDistributions, chromatic_adaptation: bool = False, ) -> Tuple[DataColorimetry_TCS, ...]: """ Return the *test colour samples* colorimetry data. Parameters ---------- sd_t Test spectral distribution. sd_r Reference spectral distribution. sds_tcs *Test colour samples* spectral reflectance distributions. cmfs Standard observer colour matching functions. chromatic_adaptation Perform chromatic adaptation. Returns ------- :class:`tuple` *Test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_t, cmfs) uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t)) u_t, v_t = uv_t[0], uv_t[1] XYZ_r = sd_to_XYZ(sd_r, cmfs) uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r)) u_r, v_r = uv_r[0], uv_r[1] tcs_data = [] for _key, value in sorted(INDEXES_TO_NAMES_TCS.items()): sd_tcs = sds_tcs[value] XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t) xyY_tcs = XYZ_to_xyY(XYZ_tcs) uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs)) u_tcs, v_tcs = uv_tcs[0], uv_tcs[1] if chromatic_adaptation: def c(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat: """Compute the :math:`c` term.""" with sdiv_mode(): return sdiv(4 - x - 10 * y, y) def d(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat: """Compute the :math:`d` term.""" with sdiv_mode(): return sdiv(1.708 * y + 0.404 - 1.481 * x, y) c_t, d_t = c(u_t, v_t), d(u_t, v_t) c_r, d_r = c(u_r, v_r), d(u_r, v_r) tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs) with sdiv_mode(): c_r_c_t = sdiv(c_r, c_t) d_r_d_t = sdiv(d_r, d_t) u_tcs = (10.872 + 0.404 * c_r_c_t * tcs_c - 4 * d_r_d_t * tcs_d) / ( 16.518 + 1.481 * c_r_c_t * tcs_c - d_r_d_t * tcs_d ) v_tcs = 5.52 / (16.518 + 1.481 * c_r_c_t * tcs_c - d_r_d_t * tcs_d) W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17 U_tcs = 13 * W_tcs * (u_tcs - u_r) V_tcs = 13 * W_tcs * (v_tcs - v_r) tcs_data.append( DataColorimetry_TCS( sd_tcs.name, XYZ_tcs, uv_tcs, np.array([U_tcs, V_tcs, W_tcs]) ) ) return tuple(tcs_data) def colour_rendering_indexes( test_data: Tuple[DataColorimetry_TCS, ...], reference_data: Tuple[DataColorimetry_TCS, ...], ) -> Dict[int, DataColourQualityScale_TCS]: """ Return the *test colour samples* rendering indexes :math:`Q_a`. Parameters ---------- test_data Test data. reference_data Reference data. Returns ------- :class:`dict` *Test colour samples* *Colour Rendering Index* (CRI). """ Q_as = {} for i in range(len(test_data)): Q_as[i + 1] = DataColourQualityScale_TCS( test_data[i].name, 100 - 4.6 * cast( float, euclidean_distance(reference_data[i].UVW, test_data[i].UVW), ), ) return Q_as