Coverage for adaptation/__init__.py: 55%

1"""

2 Provide chromatic adaptation models that predict how colours appear under

3 different illumination conditions.

4

5 References

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

36 from __future__ import annotations

37

38 import typing

39

40 if typing.TYPE_CHECKING:

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

42

43# isort: split

44

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

55 from .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

73 from .vonkries import (

74 chromatic_adaptation_VonKries,

75 matrix_chromatic_adaptation_VonKries,

76)

77

78# isort: split

79

80 from .fairchild1990 import chromatic_adaptation_Fairchild1990

81 from .fairchild2020 import (

82 CONDITIONS_DEGREE_OF_ADAPTATION_VK20,

83 chromatic_adaptation_vK20,

84 matrix_chromatic_adaptation_vk20,

85)

86

87# isort: split

88

89 from .cie1994 import chromatic_adaptation_CIE1994

90 from .cmccat2000 import (

91 VIEWING_CONDITIONS_CMCCAT2000,

92 InductionFactors_CMCCAT2000,

93 chromatic_adaptation_CMCCAT2000,

94 chromatic_adaptation_forward_CMCCAT2000,

95 chromatic_adaptation_inverse_CMCCAT2000,

96)

97 from .zhai2018 import chromatic_adaptation_Zhai2018

98

99# isort: split

100

101 from .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

148 CHROMATIC_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)

159 CHROMATIC_ADAPTATION_METHODS.__doc__ = """

160 Supported chromatic adaptation methods.

161

162 References

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

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