Coverage for quality/cri.py: 61%
101 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2Colour Rendering Index
3======================
5Define the *Colour Rendering Index* (CRI) computation objects.
7- :class:`colour.quality.ColourRendering_Specification_CRI`
8- :func:`colour.colour_rendering_index`
10References
11----------
12- :cite:`Ohno2008a` : Ohno, Yoshiro, & Davis, W. (2008). NIST CQS simulation
13 (Version 7.4) [Computer software].
14 https://drive.google.com/file/d/1PsuU6QjUJjCX6tQyCud6ul2Tbs8rYWW9/view?\
15usp=sharing
16"""
18from __future__ import annotations
20import typing
21from dataclasses import dataclass
23import numpy as np
25from colour.algebra import euclidean_distance, sdiv, sdiv_mode, spow
26from colour.colorimetry import (
27 MSDS_CMFS,
28 SPECTRAL_SHAPE_DEFAULT,
29 MultiSpectralDistributions,
30 SpectralDistribution,
31 reshape_msds,
32 reshape_sd,
33 sd_blackbody,
34 sd_CIE_illuminant_D_series,
35 sd_to_XYZ,
36)
38if typing.TYPE_CHECKING:
39 from colour.hints import Dict, Literal, NDArrayFloat, Tuple
41from colour.hints import cast
42from colour.models import UCS_to_uv, XYZ_to_UCS, XYZ_to_xyY
43from colour.quality.datasets.tcs import INDEXES_TO_NAMES_TCS, SDS_TCS
44from colour.temperature import CCT_to_xy_CIE_D, uv_to_CCT_Robertson1968
45from colour.utilities import domain_range_scale, validate_method
46from colour.utilities.documentation import DocstringTuple, is_documentation_building
48__author__ = "Colour Developers"
49__copyright__ = "Copyright 2013 Colour Developers"
50__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
51__maintainer__ = "Colour Developers"
52__email__ = "colour-developers@colour-science.org"
53__status__ = "Production"
55__all__ = [
56 "DataColorimetry_TCS",
57 "DataColourQualityScale_TCS",
58 "ColourRendering_Specification_CRI",
59 "COLOUR_RENDERING_INDEX_METHODS",
60 "colour_rendering_index",
61 "tcs_colorimetry_data",
62 "colour_rendering_indexes",
63]
66@dataclass
67class DataColorimetry_TCS:
68 """
69 Store colorimetric data for *test colour samples* used in colour
70 rendering index calculations.
72 This dataclass encapsulates the colorimetric properties of test colour
73 samples, including their tristimulus values, chromaticity coordinates,
74 and colour appearance attributes required for evaluating light source
75 colour rendering performance.
77 Attributes
78 ----------
79 name
80 Identifier for the test colour sample.
81 XYZ
82 *CIE XYZ* tristimulus values of the test colour sample.
83 uv
84 *CIE 1960 UCS* chromaticity coordinates of the test colour sample.
85 UVW
86 *CIE 1964 U*V*W** colour space coordinates of the test colour
87 sample.
88 """
90 name: str
91 XYZ: NDArrayFloat
92 uv: NDArrayFloat
93 UVW: NDArrayFloat
96@dataclass
97class DataColourQualityScale_TCS:
98 """
99 Store colour rendering index quality scale data for individual *test
100 colour samples*.
102 Attributes
103 ----------
104 name
105 Identifier of the test colour sample.
106 Q_a
107 Colour rendering index :math:`Q_a` value for the test colour sample.
108 """
110 name: str
111 Q_a: float
114@dataclass()
115class ColourRendering_Specification_CRI:
116 """
117 Define the *Colour Rendering Index* (CRI) colour quality specification.
119 This dataclass represents the colour quality assessment results using
120 the CRI method, which evaluates how accurately a light source renders
121 colours compared to a reference illuminant.
123 Parameters
124 ----------
125 name
126 Name of the test spectral distribution.
127 Q_a
128 *Colour Rendering Index* (CRI) :math:`Q_a` general index value.
129 Q_as
130 Individual *colour rendering indexes* data for each test colour
131 sample.
132 colorimetry_data
133 Colorimetry data for the test and reference illuminant
134 computations.
136 References
137 ----------
138 :cite:`Ohno2008a`
139 """
141 name: str
142 Q_a: float
143 Q_as: Dict[int, DataColourQualityScale_TCS]
144 colorimetry_data: Tuple[
145 Tuple[DataColorimetry_TCS, ...], Tuple[DataColorimetry_TCS, ...]
146 ]
149COLOUR_RENDERING_INDEX_METHODS: tuple = ("CIE 1995", "CIE 2024")
150if is_documentation_building(): # pragma: no cover
151 COLOUR_RENDERING_INDEX_METHODS = DocstringTuple(COLOUR_RENDERING_INDEX_METHODS)
152 COLOUR_RENDERING_INDEX_METHODS.__doc__ = """
153Supported *Colour Rendering Index* (CRI) computation methods.
155References
156----------
157:cite:`Ohno2008a`
158"""
161@typing.overload
162def colour_rendering_index(
163 sd_test: SpectralDistribution,
164 additional_data: Literal[True] = True,
165 method: Literal["CIE 1995", "CIE 2024"] | str = ...,
166) -> ColourRendering_Specification_CRI: ...
169@typing.overload
170def colour_rendering_index(
171 sd_test: SpectralDistribution,
172 *,
173 additional_data: Literal[False],
174 method: Literal["CIE 1995", "CIE 2024"] | str = ...,
175) -> float: ...
178@typing.overload
179def colour_rendering_index(
180 sd_test: SpectralDistribution,
181 additional_data: Literal[False],
182 method: Literal["CIE 1995", "CIE 2024"] | str = ...,
183) -> float: ...
186def colour_rendering_index(
187 sd_test: SpectralDistribution,
188 additional_data: bool = False,
189 method: Literal["CIE 1995", "CIE 2024"] | str = "CIE 1995",
190) -> float | ColourRendering_Specification_CRI:
191 """
192 Compute the *Colour Rendering Index* (CRI) :math:`Q_a` of the specified
193 spectral distribution.
195 Parameters
196 ----------
197 sd_test
198 Test spectral distribution.
199 additional_data
200 Whether to output additional data.
201 method
202 Computation method.
204 Returns
205 -------
206 :class:`float` or :class:`colour.quality.ColourRendering_Specification_CRI`
207 *Colour Rendering Index* (CRI).
209 References
210 ----------
211 :cite:`Ohno2008a`
213 Examples
214 --------
215 >>> from colour import SDS_ILLUMINANTS
216 >>> sd = SDS_ILLUMINANTS["FL2"]
217 >>> colour_rendering_index(sd) # doctest: +ELLIPSIS
218 64.2337241...
219 """
221 method = validate_method(method, tuple(COLOUR_RENDERING_INDEX_METHODS))
223 cmfs = reshape_msds(
224 MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
225 SPECTRAL_SHAPE_DEFAULT,
226 copy=False,
227 )
229 shape = cmfs.shape
230 sd_test = reshape_sd(sd_test, shape, copy=False)
231 sds_tcs = SDS_TCS[method]
232 tcs_sds = {sd.name: reshape_sd(sd, shape, copy=False) for sd in sds_tcs.values()}
234 with domain_range_scale("1"):
235 XYZ = sd_to_XYZ(sd_test, cmfs)
237 uv = UCS_to_uv(XYZ_to_UCS(XYZ))
238 CCT, _D_uv = uv_to_CCT_Robertson1968(uv)
240 if CCT < 5000:
241 sd_reference = sd_blackbody(CCT, shape)
242 else:
243 xy = CCT_to_xy_CIE_D(CCT)
244 sd_reference = sd_CIE_illuminant_D_series(xy)
245 sd_reference.align(shape)
247 test_tcs_colorimetry_data = tcs_colorimetry_data(
248 sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True, method=method
249 )
251 reference_tcs_colorimetry_data = tcs_colorimetry_data(
252 sd_reference, sd_reference, tcs_sds, cmfs, method=method
253 )
255 Q_as = colour_rendering_indexes(
256 test_tcs_colorimetry_data, reference_tcs_colorimetry_data
257 )
259 Q_a = cast(
260 "float",
261 np.average([v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)]),
262 )
264 if additional_data:
265 return ColourRendering_Specification_CRI(
266 sd_test.name,
267 Q_a,
268 Q_as,
269 (test_tcs_colorimetry_data, reference_tcs_colorimetry_data),
270 )
272 return Q_a
275def tcs_colorimetry_data(
276 sd_t: SpectralDistribution,
277 sd_r: SpectralDistribution,
278 sds_tcs: Dict[str, SpectralDistribution],
279 cmfs: MultiSpectralDistributions,
280 chromatic_adaptation: bool = False,
281 method: Literal["CIE 1995", "CIE 2024"] | str = "CIE 1995",
282) -> Tuple[DataColorimetry_TCS, ...]:
283 """
284 Compute the *test colour samples* colorimetry data.
286 Parameters
287 ----------
288 sd_t
289 Test spectral distribution.
290 sd_r
291 Reference spectral distribution.
292 sds_tcs
293 *Test colour samples* spectral reflectance distributions.
294 cmfs
295 Standard observer colour matching functions.
296 chromatic_adaptation
297 Perform chromatic adaptation.
299 Returns
300 -------
301 :class:`tuple`
302 *Test colour samples* colorimetry data.
303 """
305 method = validate_method(method, tuple(COLOUR_RENDERING_INDEX_METHODS))
307 XYZ_t = sd_to_XYZ(sd_t, cmfs)
308 uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t))
309 u_t, v_t = uv_t[0], uv_t[1]
311 XYZ_r = sd_to_XYZ(sd_r, cmfs)
312 uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r))
313 u_r, v_r = uv_r[0], uv_r[1]
315 tcs_data = []
316 for _key, value in sorted(INDEXES_TO_NAMES_TCS[method].items()):
317 if value not in sds_tcs:
318 continue
320 sd_tcs = sds_tcs[value]
321 XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t)
322 xyY_tcs = XYZ_to_xyY(XYZ_tcs)
323 uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs))
324 u_tcs, v_tcs = uv_tcs[0], uv_tcs[1]
326 if chromatic_adaptation:
328 def c(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat:
329 """Compute the :math:`c` term."""
331 with sdiv_mode():
332 return sdiv(4 - x - 10 * y, y)
334 def d(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat:
335 """Compute the :math:`d` term."""
337 with sdiv_mode():
338 return sdiv(1.708 * y + 0.404 - 1.481 * x, y)
340 c_t, d_t = c(u_t, v_t), d(u_t, v_t)
341 c_r, d_r = c(u_r, v_r), d(u_r, v_r)
342 tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs)
344 with sdiv_mode():
345 c_r_c_t = sdiv(c_r, c_t)
346 d_r_d_t = sdiv(d_r, d_t)
348 u_tcs = (10.872 + 0.404 * c_r_c_t * tcs_c - 4 * d_r_d_t * tcs_d) / (
349 16.518 + 1.481 * c_r_c_t * tcs_c - d_r_d_t * tcs_d
350 )
351 v_tcs = 5.52 / (16.518 + 1.481 * c_r_c_t * tcs_c - d_r_d_t * tcs_d)
353 W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17
354 U_tcs = 13 * W_tcs * (u_tcs - u_r)
355 V_tcs = 13 * W_tcs * (v_tcs - v_r)
357 tcs_data.append(
358 DataColorimetry_TCS(
359 sd_tcs.name, XYZ_tcs, uv_tcs, np.array([U_tcs, V_tcs, W_tcs])
360 )
361 )
363 return tuple(tcs_data)
366def colour_rendering_indexes(
367 test_data: Tuple[DataColorimetry_TCS, ...],
368 reference_data: Tuple[DataColorimetry_TCS, ...],
369) -> Dict[int, DataColourQualityScale_TCS]:
370 """
371 Compute the *test colour samples* rendering indexes :math:`Q_a`.
373 Parameters
374 ----------
375 test_data
376 Test data colorimetry for the *test colour samples*.
377 reference_data
378 Reference data colorimetry for the *test colour samples*.
380 Returns
381 -------
382 :class:`dict`
383 *Test colour samples* *Colour Rendering Index* (CRI) values
384 mapped by sample number.
385 """
387 Q_as = {}
388 for i in range(len(test_data)):
389 Q_as[i + 1] = DataColourQualityScale_TCS(
390 test_data[i].name,
391 100
392 - 4.6
393 * cast(
394 "float",
395 euclidean_distance(reference_data[i].UVW, test_data[i].UVW),
396 ),
397 )
399 return Q_as