Coverage for colour/adaptation/__init__.py: 100%

51 statements  

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

1""" 

2Provide chromatic adaptation models that predict how colours appear under 

3different illumination conditions. 

4 

5References 

6---------- 

7- :cite:`CIETC1-321994b` : CIE TC 1-32. (1994). CIE 109-1994 A Method of 

8 Predicting Corresponding Colours under Different Chromatic and Illuminance 

9 Adaptations. Commission Internationale de l'Eclairage. 

10 ISBN:978-3-900734-51-0 

11- :cite:`Fairchild1991a` : Fairchild, M. D. (1991). Formulation and testing 

12 of an incomplete-chromatic-adaptation model. Color Research & Application, 

13 16(4), 243-250. doi:10.1002/col.5080160406 

14- :cite:`Fairchild2013s` : Fairchild, M. D. (2013). FAIRCHILD'S 1990 MODEL. 

15 In Color Appearance Models (3rd ed., pp. 4418-4495). Wiley. ISBN:B00DAYO8E2 

16- :cite:`Fairchild2013t` : Fairchild, M. D. (2013). Chromatic Adaptation 

17 Models. In Color Appearance Models (3rd ed., pp. 4179-4252). Wiley. 

18 ISBN:B00DAYO8E2 

19- :cite:`Fairchild2020` : Fairchild, M. D. (2020). Von Kries 2020: Evolution 

20 of degree of chromatic adaptation. Color and Imaging Conference, 28(1), 

21 252-257. doi:10.2352/issn.2169-2629.2020.28.40 

22- :cite:`Li2002a` : Li, C., Luo, M. R., Rigg, B., & Hunt, R. W. G. (2002). 

23 CMC 2000 chromatic adaptation transform: CMCCAT2000. Color Research & 

24 Application, 27(1), 49-58. doi:10.1002/col.10005 

25- :cite:`Li2025` : Li, M. (2025). One Step CAT16 Chromatic Adaptation 

26 Transform. https://github.com/colour-science/colour/pull/1349\ 

27#issuecomment-3058339414 

28- :cite:`Westland2012k` : Westland, S., Ripamonti, C., & Cheung, V. (2012). 

29 CMCCAT2000. In Computational Colour Science Using MATLAB (2nd ed., pp. 

30 83-86). ISBN:978-0-470-66569-5 

31- :cite:`Zhai2018` : Zhai, Q., & Luo, M. R. (2018). Study of chromatic 

32 adaptation via neutral white matches on different viewing media. Optics 

33 Express, 26(6), 7724. doi:10.1364/OE.26.007724 

34""" 

35 

36from __future__ import annotations 

37 

38import typing 

39 

40if typing.TYPE_CHECKING: 

41 from colour.hints import Any, ArrayLike, Literal, NDArrayFloat 

42 

43# isort: split 

44 

45from colour.utilities import ( 

46 CanonicalMapping, 

47 as_float_array, 

48 filter_kwargs, 

49 get_domain_range_scale, 

50 validate_method, 

51) 

52 

53# isort: split 

54 

55from .datasets import ( 

56 CAT_BIANCO2010, 

57 CAT_BRADFORD, 

58 CAT_CAT02, 

59 CAT_CAT02_BRILL2008, 

60 CAT_CAT16, 

61 CAT_CMCCAT97, 

62 CAT_CMCCAT2000, 

63 CAT_FAIRCHILD, 

64 CAT_PC_BIANCO2010, 

65 CAT_SHARP, 

66 CAT_VON_KRIES, 

67 CAT_XYZ_SCALING, 

68 CHROMATIC_ADAPTATION_TRANSFORMS, 

69) 

70 

71# isort: split 

72 

73from .vonkries import ( 

74 chromatic_adaptation_VonKries, 

75 matrix_chromatic_adaptation_VonKries, 

76) 

77 

78# isort: split 

79 

80from .fairchild1990 import chromatic_adaptation_Fairchild1990 

81from .fairchild2020 import ( 

82 CONDITIONS_DEGREE_OF_ADAPTATION_VK20, 

83 chromatic_adaptation_vK20, 

84 matrix_chromatic_adaptation_vk20, 

85) 

86 

87# isort: split 

88 

89from .cie1994 import chromatic_adaptation_CIE1994 

90from .cmccat2000 import ( 

91 VIEWING_CONDITIONS_CMCCAT2000, 

92 InductionFactors_CMCCAT2000, 

93 chromatic_adaptation_CMCCAT2000, 

94 chromatic_adaptation_forward_CMCCAT2000, 

95 chromatic_adaptation_inverse_CMCCAT2000, 

96) 

97from .zhai2018 import chromatic_adaptation_Zhai2018 

98 

99# isort: split 

100 

101from .li2025 import CAT_CAT16_INVERSE, chromatic_adaptation_Li2025 

102 

103__all__ = [ 

104 "CAT_BIANCO2010", 

105 "CAT_BRADFORD", 

106 "CAT_CAT02", 

107 "CAT_CAT02_BRILL2008", 

108 "CAT_CAT16", 

109 "CAT_CMCCAT97", 

110 "CAT_CMCCAT2000", 

111 "CAT_FAIRCHILD", 

112 "CAT_PC_BIANCO2010", 

113 "CAT_SHARP", 

114 "CAT_VON_KRIES", 

115 "CAT_XYZ_SCALING", 

116 "CHROMATIC_ADAPTATION_TRANSFORMS", 

117] 

118__all__ += [ 

119 "chromatic_adaptation_VonKries", 

120 "matrix_chromatic_adaptation_VonKries", 

121] 

122__all__ += [ 

123 "chromatic_adaptation_Fairchild1990", 

124] 

125__all__ += [ 

126 "CONDITIONS_DEGREE_OF_ADAPTATION_VK20", 

127 "chromatic_adaptation_vK20", 

128 "matrix_chromatic_adaptation_vk20", 

129] 

130__all__ += [ 

131 "chromatic_adaptation_CIE1994", 

132] 

133__all__ += [ 

134 "VIEWING_CONDITIONS_CMCCAT2000", 

135 "InductionFactors_CMCCAT2000", 

136 "chromatic_adaptation_CMCCAT2000", 

137 "chromatic_adaptation_forward_CMCCAT2000", 

138 "chromatic_adaptation_inverse_CMCCAT2000", 

139] 

140__all__ += [ 

141 "chromatic_adaptation_Zhai2018", 

142] 

143__all__ += [ 

144 "CAT_CAT16_INVERSE", 

145 "chromatic_adaptation_Li2025", 

146] 

147 

148CHROMATIC_ADAPTATION_METHODS: CanonicalMapping = CanonicalMapping( 

149 { 

150 "CIE 1994": chromatic_adaptation_CIE1994, 

151 "CMCCAT2000": chromatic_adaptation_CMCCAT2000, 

152 "Fairchild 1990": chromatic_adaptation_Fairchild1990, 

153 "Li 2025": chromatic_adaptation_Li2025, 

154 "Von Kries": chromatic_adaptation_VonKries, 

155 "Zhai 2018": chromatic_adaptation_Zhai2018, 

156 "vK20": chromatic_adaptation_vK20, 

157 } 

158) 

159CHROMATIC_ADAPTATION_METHODS.__doc__ = """ 

160Supported chromatic adaptation methods. 

161 

162References 

163---------- 

164:cite:`CIETC1-321994b`, :cite:`Fairchild1991a`, :cite:`Fairchild2013s`, 

165:cite:`Fairchild2013t`, :cite:`Fairchild2020`, :cite:`Li2002a`, 

166:cite:`Li2025`, :cite:`Westland2012k`, :cite:`Zhai2018` 

167""" 

168 

169 

170def chromatic_adaptation( 

171 XYZ: ArrayLike, 

172 XYZ_w: ArrayLike, 

173 XYZ_wr: ArrayLike, 

174 method: ( 

175 Literal[ 

176 "CIE 1994", 

177 "CMCCAT2000", 

178 "Fairchild 1990", 

179 "Li 2025", 

180 "Von Kries", 

181 "Zhai 2018", 

182 "vK20", 

183 ] 

184 | str 

185 ) = "Von Kries", 

186 **kwargs: Any, 

187) -> NDArrayFloat: 

188 """ 

189 Adapt the specified stimulus *CIE XYZ* tristimulus values from test 

190 viewing conditions to reference viewing conditions using the specified 

191 chromatic adaptation method. 

192 

193 Parameters 

194 ---------- 

195 XYZ 

196 *CIE XYZ* tristimulus values of stimulus to adapt. 

197 XYZ_w 

198 Test viewing condition *CIE XYZ* tristimulus values of the 

199 whitepoint. 

200 XYZ_wr 

201 Reference viewing condition *CIE XYZ* tristimulus values of the 

202 whitepoint. 

203 method 

204 Computation method. 

205 

206 Other Parameters 

207 ---------------- 

208 E_o1 

209 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, 

210 Test illuminance :math:`E_{o1}` in :math:`lux`. 

211 E_o2 

212 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, 

213 Reference illuminance :math:`E_{o2}` in :math:`lux`. 

214 n 

215 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, 

216 Noise component in fundamental primary system. 

217 Y_o 

218 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, 

219 Luminance factor :math:`Y_o` of achromatic background normalised 

220 to domain [0.18, 1] in **'Reference'** domain-range scale. 

221 direction 

222 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, 

223 Chromatic adaptation direction. 

224 L_A1 

225 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, 

226 Luminance of test adapting field :math:`L_{A1}` in 

227 :math:`cd/m^2`. 

228 L_A2 

229 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, 

230 Luminance of reference adapting field :math:`L_{A2}` in 

231 :math:`cd/m^2`. 

232 surround 

233 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, 

234 Surround viewing conditions induction factors. 

235 discount_illuminant 

236 {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`}, 

237 Truth value indicating if the illuminant should be discounted. 

238 Y_n 

239 {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`}, 

240 Luminance :math:`Y_n` of test adapting stimulus in 

241 :math:`cd/m^2`. 

242 L_A 

243 {:func:`colour.adaptation.chromatic_adaptation_Li2025`}, 

244 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`. 

245 F_surround 

246 {:func:`colour.adaptation.chromatic_adaptation_Li2025`}, 

247 Maximum degree of adaptation :math:`F` from surround viewing 

248 conditions. 

249 discount_illuminant 

250 {:func:`colour.adaptation.chromatic_adaptation_Li2025`}, 

251 Truth value indicating if the illuminant should be discounted. 

252 D_b 

253 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`}, 

254 Degree of adaptation :math:`D_\\beta` of input illuminant 

255 :math:`\\beta`. 

256 D_d 

257 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`}, 

258 Degree of adaptation :math:`D_\\Delta` of output illuminant 

259 :math:`\\Delta`. 

260 XYZ_r 

261 {:func:`colour.adaptation.chromatic_adaptation_vK20`}, 

262 Reference viewing conditions *CIE XYZ* tristimulus values of 

263 whitepoint. 

264 coefficients 

265 {:func:`colour.adaptation.chromatic_adaptation_vK20`}, 

266 *vK20* degree of adaptation coefficients. 

267 transform 

268 {:func:`colour.adaptation.chromatic_adaptation_VonKries`, 

269 :func:`colour.adaptation.chromatic_adaptation_vK20`, 

270 :func:`colour.adaptation.chromatic_adaptation_Zhai2018`}, 

271 Chromatic adaptation transform. 

272 XYZ_wo 

273 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`}, 

274 Baseline illuminant (:math:`BI`) :math:`o`. 

275 

276 Returns 

277 ------- 

278 :class:`numpy.ndarray` 

279 *CIE XYZ* tristimulus values of the stimulus corresponding colour. 

280 

281 Notes 

282 ----- 

283 +------------+-----------------------+---------------+ 

284 | **Domain** | **Scale - Reference** | **Scale - 1** | 

285 +============+=======================+===============+ 

286 | ``XYZ`` | 1 | 1 | 

287 +------------+-----------------------+---------------+ 

288 | ``XYZ_w`` | 1 | 1 | 

289 +------------+-----------------------+---------------+ 

290 | ``XYZ_wr`` | 1 | 1 | 

291 +------------+-----------------------+---------------+ 

292 | ``XYZ_wo`` | 1 | 1 | 

293 +------------+-----------------------+---------------+ 

294 | ``Y_o`` | 1 | 1 | 

295 +------------+-----------------------+---------------+ 

296 

297 +------------+-----------------------+---------------+ 

298 | **Range** | **Scale - Reference** | **Scale - 1** | 

299 +============+=======================+===============+ 

300 | ``XYZ_c`` | 1 | 1 | 

301 +------------+-----------------------+---------------+ 

302 

303 References 

304 ---------- 

305 :cite:`CIETC1-321994b`, :cite:`Fairchild1991a`, 

306 :cite:`Fairchild2013s`, :cite:`Fairchild2013t`, :cite:`Li2002a`, 

307 :cite:`Li2025`, :cite:`Westland2012k` 

308 

309 Examples 

310 -------- 

311 *Von Kries* chromatic adaptation: 

312 

313 >>> import numpy as np 

314 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

315 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) 

316 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) 

317 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr) 

318 ... # doctest: +ELLIPSIS 

319 array([ 0.2163881..., 0.1257 , 0.0384749...]) 

320 

321 *vK2020* chromatic adaptation: 

322 

323 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) 

324 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) 

325 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="vK20") 

326 ... # doctest: +ELLIPSIS 

327 array([ 0.2146884..., 0.1245616..., 0.0466255...]) 

328 

329 *CIE 1994* chromatic adaptation, requires extra *kwargs*: 

330 

331 >>> XYZ = np.array([0.2800, 0.2126, 0.0527]) 

332 >>> XYZ_w = np.array([1.09867452, 1.00000000, 0.35591556]) 

333 >>> XYZ_wr = np.array([0.95045593, 1.00000000, 1.08905775]) 

334 >>> Y_o = 0.20 

335 >>> E_o = 1000 

336 >>> chromatic_adaptation( 

337 ... XYZ, XYZ_w, XYZ_wr, method="CIE 1994", Y_o=Y_o, E_o1=E_o, E_o2=E_o 

338 ... ) 

339 ... # doctest: +ELLIPSIS 

340 array([ 0.2403379..., 0.2115621..., 0.1764301...]) 

341 

342 *CMCCAT2000* chromatic adaptation, requires extra *kwargs*: 

343 

344 >>> XYZ = np.array([0.2248, 0.2274, 0.0854]) 

345 >>> XYZ_w = np.array([1.1115, 1.0000, 0.3520]) 

346 >>> XYZ_wr = np.array([0.9481, 1.0000, 1.0730]) 

347 >>> L_A = 200 

348 >>> chromatic_adaptation( 

349 ... XYZ, XYZ_w, XYZ_wr, method="CMCCAT2000", L_A1=L_A, L_A2=L_A 

350 ... ) 

351 ... # doctest: +ELLIPSIS 

352 array([ 0.1952698..., 0.2306834..., 0.2497175...]) 

353 

354 *Fairchild (1990)* chromatic adaptation, requires extra *kwargs*: 

355 

356 >>> XYZ = np.array([0.1953, 0.2307, 0.2497]) 

357 >>> Y_n = 200 

358 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Fairchild 1990", Y_n=Y_n) 

359 ... # doctest: +ELLIPSIS 

360 array([ 0.2332526..., 0.2332455..., 0.7611593...]) 

361 

362 *Li (2025)* chromatic adaptation: 

363 

364 >>> XYZ = np.array([0.1953, 0.2307, 0.2497]) 

365 >>> L_A = 64 

366 >>> F_surround = 1.0 

367 >>> chromatic_adaptation( 

368 ... XYZ, XYZ_w, XYZ_wr, method="Li 2025", L_A=L_A, F_surround=F_surround 

369 ... ) 

370 ... # doctest: +ELLIPSIS 

371 array([ 0.2039701..., 0.2304747..., 0.6783065...]) 

372 

373 *Zhai and Luo (2018)* chromatic adaptation: 

374 

375 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

376 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) 

377 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) 

378 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Zhai 2018") 

379 ... # doctest: +ELLIPSIS 

380 array([ 0.2163881..., 0.1257 , 0.0384749...]) 

381 >>> chromatic_adaptation( 

382 ... XYZ, 

383 ... XYZ_w, 

384 ... XYZ_wr, 

385 ... method="Zhai 2018", 

386 ... D_b=0.9, 

387 ... XYZ_wo=np.array([100, 100, 100]), 

388 ... ) 

389 ... # doctest: +ELLIPSIS 

390 array([ 0.2152436..., 0.1253522..., 0.0388406...]) 

391 """ 

392 

393 method = validate_method(method, tuple(CHROMATIC_ADAPTATION_METHODS)) 

394 

395 function = CHROMATIC_ADAPTATION_METHODS[method] 

396 

397 domain_range_reference = get_domain_range_scale() == "reference" 

398 domain_100 = ( 

399 chromatic_adaptation_CIE1994, 

400 chromatic_adaptation_CMCCAT2000, 

401 chromatic_adaptation_Fairchild1990, 

402 chromatic_adaptation_Li2025, 

403 chromatic_adaptation_Zhai2018, 

404 ) 

405 

406 if function in domain_100 and domain_range_reference: 

407 XYZ = as_float_array(XYZ) * 100 

408 XYZ_w = as_float_array(XYZ_w) * 100 

409 XYZ_wr = as_float_array(XYZ_wr) * 100 

410 

411 if "Y_o" in kwargs: 

412 kwargs["Y_o"] = kwargs["Y_o"] * 100 

413 

414 if "XYZ_wo" in kwargs: 

415 kwargs["XYZ_wo"] = kwargs["XYZ_wo"] * 100 

416 

417 kwargs.update({"XYZ_w": XYZ_w, "XYZ_wr": XYZ_wr}) 

418 

419 if function is chromatic_adaptation_CIE1994: 

420 from colour import XYZ_to_xy # noqa: PLC0415 

421 

422 kwargs.update({"xy_o1": XYZ_to_xy(XYZ_w), "xy_o2": XYZ_to_xy(XYZ_wr)}) 

423 elif function is chromatic_adaptation_Fairchild1990: 

424 kwargs.update({"XYZ_n": XYZ_w, "XYZ_r": XYZ_wr}) 

425 elif function is chromatic_adaptation_Li2025: 

426 kwargs.update({"XYZ_ws": XYZ_w, "XYZ_wd": XYZ_wr}) 

427 elif function is chromatic_adaptation_Zhai2018: 

428 kwargs.update({"XYZ_wb": XYZ_w, "XYZ_wd": XYZ_wr}) 

429 elif function is chromatic_adaptation_vK20: 

430 kwargs.update({"XYZ_p": XYZ_w, "XYZ_n": XYZ_wr}) 

431 

432 XYZ_c = function(XYZ, **filter_kwargs(function, **kwargs)) 

433 

434 if function in domain_100 and domain_range_reference: 

435 XYZ_c /= 100 

436 

437 return XYZ_c 

438 

439 

440__all__ += [ 

441 "CHROMATIC_ADAPTATION_METHODS", 

442 "chromatic_adaptation", 

443]