Coverage for quality/cri.py: 61%

101 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2Colour Rendering Index 

3====================== 

4 

5Define the *Colour Rendering Index* (CRI) computation objects. 

6 

7- :class:`colour.quality.ColourRendering_Specification_CRI` 

8- :func:`colour.colour_rendering_index` 

9 

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""" 

17 

18from __future__ import annotations 

19 

20import typing 

21from dataclasses import dataclass 

22 

23import numpy as np 

24 

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) 

37 

38if typing.TYPE_CHECKING: 

39 from colour.hints import Dict, Literal, NDArrayFloat, Tuple 

40 

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 

47 

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" 

54 

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] 

64 

65 

66@dataclass 

67class DataColorimetry_TCS: 

68 """ 

69 Store colorimetric data for *test colour samples* used in colour 

70 rendering index calculations. 

71 

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. 

76 

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 """ 

89 

90 name: str 

91 XYZ: NDArrayFloat 

92 uv: NDArrayFloat 

93 UVW: NDArrayFloat 

94 

95 

96@dataclass 

97class DataColourQualityScale_TCS: 

98 """ 

99 Store colour rendering index quality scale data for individual *test 

100 colour samples*. 

101 

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 """ 

109 

110 name: str 

111 Q_a: float 

112 

113 

114@dataclass() 

115class ColourRendering_Specification_CRI: 

116 """ 

117 Define the *Colour Rendering Index* (CRI) colour quality specification. 

118 

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. 

122 

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. 

135 

136 References 

137 ---------- 

138 :cite:`Ohno2008a` 

139 """ 

140 

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 ] 

147 

148 

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. 

154 

155References 

156---------- 

157:cite:`Ohno2008a` 

158""" 

159 

160 

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: ... 

167 

168 

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: ... 

176 

177 

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: ... 

184 

185 

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. 

194 

195 Parameters 

196 ---------- 

197 sd_test 

198 Test spectral distribution. 

199 additional_data 

200 Whether to output additional data. 

201 method 

202 Computation method. 

203 

204 Returns 

205 ------- 

206 :class:`float` or :class:`colour.quality.ColourRendering_Specification_CRI` 

207 *Colour Rendering Index* (CRI). 

208 

209 References 

210 ---------- 

211 :cite:`Ohno2008a` 

212 

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 """ 

220 

221 method = validate_method(method, tuple(COLOUR_RENDERING_INDEX_METHODS)) 

222 

223 cmfs = reshape_msds( 

224 MSDS_CMFS["CIE 1931 2 Degree Standard Observer"], 

225 SPECTRAL_SHAPE_DEFAULT, 

226 copy=False, 

227 ) 

228 

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()} 

233 

234 with domain_range_scale("1"): 

235 XYZ = sd_to_XYZ(sd_test, cmfs) 

236 

237 uv = UCS_to_uv(XYZ_to_UCS(XYZ)) 

238 CCT, _D_uv = uv_to_CCT_Robertson1968(uv) 

239 

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) 

246 

247 test_tcs_colorimetry_data = tcs_colorimetry_data( 

248 sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True, method=method 

249 ) 

250 

251 reference_tcs_colorimetry_data = tcs_colorimetry_data( 

252 sd_reference, sd_reference, tcs_sds, cmfs, method=method 

253 ) 

254 

255 Q_as = colour_rendering_indexes( 

256 test_tcs_colorimetry_data, reference_tcs_colorimetry_data 

257 ) 

258 

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 ) 

263 

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 ) 

271 

272 return Q_a 

273 

274 

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. 

285 

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. 

298 

299 Returns 

300 ------- 

301 :class:`tuple` 

302 *Test colour samples* colorimetry data. 

303 """ 

304 

305 method = validate_method(method, tuple(COLOUR_RENDERING_INDEX_METHODS)) 

306 

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] 

310 

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] 

314 

315 tcs_data = [] 

316 for _key, value in sorted(INDEXES_TO_NAMES_TCS[method].items()): 

317 if value not in sds_tcs: 

318 continue 

319 

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] 

325 

326 if chromatic_adaptation: 

327 

328 def c(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat: 

329 """Compute the :math:`c` term.""" 

330 

331 with sdiv_mode(): 

332 return sdiv(4 - x - 10 * y, y) 

333 

334 def d(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat: 

335 """Compute the :math:`d` term.""" 

336 

337 with sdiv_mode(): 

338 return sdiv(1.708 * y + 0.404 - 1.481 * x, y) 

339 

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) 

343 

344 with sdiv_mode(): 

345 c_r_c_t = sdiv(c_r, c_t) 

346 d_r_d_t = sdiv(d_r, d_t) 

347 

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) 

352 

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) 

356 

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 ) 

362 

363 return tuple(tcs_data) 

364 

365 

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`. 

372 

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*. 

379 

380 Returns 

381 ------- 

382 :class:`dict` 

383 *Test colour samples* *Colour Rendering Index* (CRI) values 

384 mapped by sample number. 

385 """ 

386 

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 ) 

398 

399 return Q_as