Coverage for quality/cri.py: 61%

1"""

2 Colour Rendering Index

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

4

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

6

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

8- :func:`colour.colour_rendering_index`

9

10 References

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?\

15 usp=sharing

16"""

17

18 from __future__ import annotations

19

20 import typing

21 from dataclasses import dataclass

22

23 import numpy as np

24

25 from colour.algebra import euclidean_distance, sdiv, sdiv_mode, spow

26 from 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

38 if typing.TYPE_CHECKING:

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

40

41 from colour.hints import cast

42 from colour.models import UCS_to_uv, XYZ_to_UCS, XYZ_to_xyY

43 from colour.quality.datasets.tcs import INDEXES_TO_NAMES_TCS, SDS_TCS

44 from colour.temperature import CCT_to_xy_CIE_D, uv_to_CCT_Robertson1968

45 from colour.utilities import domain_range_scale, validate_method

46 from 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

67 class 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

97 class 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()

115 class 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

149 COLOUR_RENDERING_INDEX_METHODS: tuple = ("CIE 1995", "CIE 2024")

150 if is_documentation_building(): # pragma: no cover

151 COLOUR_RENDERING_INDEX_METHODS = DocstringTuple(COLOUR_RENDERING_INDEX_METHODS)

152 COLOUR_RENDERING_INDEX_METHODS.__doc__ = """

153 Supported *Colour Rendering Index* (CRI) computation methods.

154

155 References

156----------

157:cite:`Ohno2008a`

158"""

159

160

161@typing.overload

162 def 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

170 def 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

179 def colour_rendering_index(

180 sd_test: SpectralDistribution,

181 additional_data: Literal[False],

182 method: Literal["CIE 1995", "CIE 2024"] | str = ...,

183) -> float: ...

184

185

186 def 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

275 def 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

366 def 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