Coverage for adaptation/cie1994.py: 69%

1"""

2 CIE 1994 Chromatic Adaptation Model

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

4

5 Define the *CIE 1994* chromatic adaptation model for predicting corresponding

6 colours under different viewing conditions.

7

8- :func:`colour.adaptation.chromatic_adaptation_CIE1994`

9

10 References

11----------

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

13 Predicting Corresponding Colours under Different Chromatic and Illuminance

14 Adaptations. Commission Internationale de l'Eclairage.

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

16"""

17

18 from __future__ import annotations

19

20 import typing

21

22 import numpy as np

23

24 from colour.adaptation import CAT_VON_KRIES

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

26

27 if typing.TYPE_CHECKING:

28 from colour.hints import DTypeFloat, NDArray

29

30 from colour.hints import ( # noqa: TC001

31 ArrayLike,

32 Domain100,

33 NDArrayFloat,

34 Range100,

35)

36 from colour.utilities import (

37 as_float_array,

38 from_range_100,

39 to_domain_100,

40 tsplit,

41 tstack,

42 usage_warning,

43)

44

45 __author__ = "Colour Developers"

46 __copyright__ = "Copyright 2013 Colour Developers"

47 __license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"

48 __maintainer__ = "Colour Developers"

49 __email__ = "colour-developers@colour-science.org"

50 __status__ = "Production"

51

52 __all__ = [

53 "MATRIX_XYZ_TO_RGB_CIE1994",

54 "MATRIX_RGB_TO_XYZ_CIE1994",

55 "chromatic_adaptation_CIE1994",

56 "XYZ_to_RGB_CIE1994",

57 "RGB_to_XYZ_CIE1994",

58 "intermediate_values",

59 "effective_adapting_responses",

60 "beta_1",

61 "beta_2",

62 "exponential_factors",

63 "K_coefficient",

64 "corresponding_colour",

65]

66

67 MATRIX_XYZ_TO_RGB_CIE1994: NDArrayFloat = CAT_VON_KRIES

68"""

69*CIE 1994* colour appearance model *CIE XYZ* tristimulus values to cone

70 responses matrix.

71"""

72

73 MATRIX_RGB_TO_XYZ_CIE1994: NDArrayFloat = np.linalg.inv(MATRIX_XYZ_TO_RGB_CIE1994)

74"""

75*CIE 1994* colour appearance model cone responses to *CIE XYZ* tristimulus

76 values matrix.

77"""

78

79

80 def chromatic_adaptation_CIE1994(

81 XYZ_1: Domain100,

82 xy_o1: ArrayLike,

83 xy_o2: ArrayLike,

84 Y_o: Domain100,

85 E_o1: ArrayLike,

86 E_o2: ArrayLike,

87 n: ArrayLike = 1,

88) -> Range100:

89 """

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

91 viewing conditions to reference viewing conditions using the

92 *CIE 1994* chromatic adaptation model.

93

94 Parameters

95 ----------

96 XYZ_1

97 *CIE XYZ* tristimulus values of test sample / stimulus.

98 xy_o1

99 Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test

100 illuminant and background.

101 xy_o2

102 Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of

103 reference illuminant and background.

104 Y_o

105 Luminance factor :math:`Y_o` of achromatic background as percentage

106 normalised to domain [18, 100] in **'Reference'** domain-range scale.

107 E_o1

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

109 E_o2

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

111 n

112 Noise component in fundamental primary system.

113

114 Returns

115 -------

116 :class:`numpy.ndarray`

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

118

119 Notes

120 -----

121 +------------+-----------------------+---------------+

122 | **Domain** | **Scale - Reference** | **Scale - 1** |

123 +============+=======================+===============+

124 | ``XYZ_1`` | 100 | 1 |

125 +------------+-----------------------+---------------+

126 | ``Y_o`` | 100 | 1 |

127 +------------+-----------------------+---------------+

128

129 +------------+-----------------------+---------------+

130 | **Range** | **Scale - Reference** | **Scale - 1** |

131 +============+=======================+===============+

132 | ``XYZ_2`` | 100 | 1 |

133 +------------+-----------------------+---------------+

134

135 References

136 ----------

137 :cite:`CIETC1-321994b`

138

139 Examples

140 --------

141 >>> XYZ_1 = np.array([28.00, 21.26, 5.27])

142 >>> xy_o1 = np.array([0.4476, 0.4074])

143 >>> xy_o2 = np.array([0.3127, 0.3290])

144 >>> Y_o = 20

145 >>> E_o1 = 1000

146 >>> E_o2 = 1000

147 >>> chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2)

148 ... # doctest: +ELLIPSIS

149 array([ 24.0337952..., 21.1562121..., 17.6430119...])

150 """

151

152 XYZ_1 = to_domain_100(XYZ_1)

153 Y_o = as_float_array(to_domain_100(Y_o))

154 E_o1 = as_float_array(E_o1)

155 E_o2 = as_float_array(E_o2)

156

157 if np.any(Y_o < 18) or np.any(Y_o > 100):

158 usage_warning(

159 '"Y_o" luminance factor must be in [18, 100] domain, '

160 "unpredictable results may occur!"

161 )

162

163 RGB_1 = XYZ_to_RGB_CIE1994(XYZ_1)

164

165 xez_1 = intermediate_values(xy_o1)

166 xez_2 = intermediate_values(xy_o2)

167

168 RGB_o1 = effective_adapting_responses(xez_1, Y_o, E_o1)

169 RGB_o2 = effective_adapting_responses(xez_2, Y_o, E_o2)

170

171 bRGB_o1 = exponential_factors(RGB_o1)

172 bRGB_o2 = exponential_factors(RGB_o2)

173

174 K = K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n)

175

176 RGB_2 = corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K, n)

177 XYZ_2 = RGB_to_XYZ_CIE1994(RGB_2)

178

179 return from_range_100(XYZ_2)

180

181

182 def XYZ_to_RGB_CIE1994(XYZ: ArrayLike) -> NDArrayFloat:

183 """

184 Convert from *CIE XYZ* tristimulus values to cone responses using the

185 *CIE 1994* colour appearance model transformation.

186

187 Parameters

188 ----------

189 XYZ

190 *CIE XYZ* tristimulus values.

191

192 Returns

193 -------

194 :class:`numpy.ndarray`

195 Cone responses.

196

197 Examples

198 --------

199 >>> XYZ = np.array([28.00, 21.26, 5.27])

200 >>> XYZ_to_RGB_CIE1994(XYZ) # doctest: +ELLIPSIS

201 array([ 25.8244273..., 18.6791422..., 4.8390194...])

202 """

203

204 return vecmul(MATRIX_XYZ_TO_RGB_CIE1994, XYZ)

205

206

207 def RGB_to_XYZ_CIE1994(RGB: ArrayLike) -> NDArrayFloat:

208 """

209 Convert from cone responses to *CIE XYZ* tristimulus values using the

210 *CIE 1994* colour appearance model inverse transformation.

211

212 Parameters

213 ----------

214 RGB

215 Cone responses.

216

217 Returns

218 -------

219 :class:`numpy.ndarray`

220 *CIE XYZ* tristimulus values.

221

222 Examples

223 --------

224 >>> RGB = np.array([25.82442730, 18.67914220, 4.83901940])

225 >>> RGB_to_XYZ_CIE1994(RGB) # doctest: +ELLIPSIS

226 array([ 28. , 21.26, 5.27])

227 """

228

229 return vecmul(MATRIX_RGB_TO_XYZ_CIE1994, RGB)

230

231

232 def intermediate_values(xy_o: ArrayLike) -> NDArrayFloat:

233 """

234 Compute the intermediate values :math:`\\xi`, :math:`\\eta`, and

235 :math:`\\zeta`.

236

237 Parameters

238 ----------

239 xy_o

240 Chromaticity coordinates :math:`x_o` and :math:`y_o` of the

241 whitepoint.

242

243 Returns

244 -------

245 :class:`numpy.ndarray`

246 Intermediate values :math:`\\xi`, :math:`\\eta`, and

247 :math:`\\zeta`.

248

249 Examples

250 --------

251 >>> xy_o = np.array([0.4476, 0.4074])

252 >>> intermediate_values(xy_o) # doctest: +ELLIPSIS

253 array([ 1.1185719..., 0.9329553..., 0.3268087...])

254 """

255

256 x_o, y_o = tsplit(xy_o)

257

258 # Computing :math:`\\xi` :math:`\\eta`, :math:`\\zeta` values.

259 xi = (0.48105 * x_o + 0.78841 * y_o - 0.08081) / y_o

260 eta = (-0.27200 * x_o + 1.11962 * y_o + 0.04570) / y_o

261 zeta = (0.91822 * (1 - x_o - y_o)) / y_o

262

263 return tstack([xi, eta, zeta])

264

265

266 def effective_adapting_responses(

267 xez: ArrayLike, Y_o: ArrayLike, E_o: ArrayLike

268) -> NDArrayFloat:

269 """

270 Compute the effective adapting responses in the fundamental primary system

271 of the test or reference field.

272

273 Parameters

274 ----------

275 xez

276 Intermediate values :math:`\\xi`, :math:`\\eta`, :math:`\\zeta`.

277 Y_o

278 Luminance factor :math:`Y_o` of achromatic background as percentage

279 normalised to domain [18, 100] in **'Reference'** domain-range scale.

280 E_o

281 Test or reference illuminance :math:`E_o` in lux.

282

283 Returns

284 -------

285 :class:`numpy.ndarray`

286 Effective adapting responses.

287

288 Examples

289 --------

290 >>> xez = np.array([1.11857195, 0.93295530, 0.32680879])

291 >>> E_o = 1000

292 >>> Y_o = 20

293 >>> effective_adapting_responses(xez, Y_o, E_o) # doctest: +ELLIPSIS

294 array([ 71.2105020..., 59.3937790..., 20.8052937...])

295 """

296

297 xez = as_float_array(xez)

298 Y_o = as_float_array(Y_o)

299 E_o = as_float_array(E_o)

300

301 return ((Y_o[..., None] * E_o[..., None]) / (100 * np.pi)) * xez

302

303

304@typing.overload

305 def beta_1(x: float | DTypeFloat) -> DTypeFloat: ...

306@typing.overload

307 def beta_1(x: NDArray) -> NDArrayFloat: ...

308@typing.overload

309 def beta_1(x: ArrayLike) -> DTypeFloat | NDArrayFloat: ...

310 def beta_1(x: ArrayLike) -> DTypeFloat | NDArrayFloat:

311 """

312 Compute the exponent :math:`\\beta_1` for the middle and long-wavelength

313 sensitive cones.

314

315 Parameters

316 ----------

317 x

318 Middle and long-wavelength sensitive cone response.

319

320 Returns

321 -------

322 :class:`numpy.ndarray`

323 Exponent :math:`\\beta_1`.

324

325 Examples

326 --------

327 >>> beta_1(318.323316315) # doctest: +ELLIPSIS

328 4.6106222...

329 """

330

331 x_p = spow(x, 0.4495)

332

333 return (x_p * 6.362 + 6.469) / (x_p + 6.469)

334

335

336@typing.overload

337 def beta_2(x: float | DTypeFloat) -> DTypeFloat: ...

338@typing.overload

339 def beta_2(x: NDArray) -> NDArrayFloat: ...

340@typing.overload

341 def beta_2(x: ArrayLike) -> DTypeFloat | NDArrayFloat: ...

342 def beta_2(x: ArrayLike) -> DTypeFloat | NDArrayFloat:

343 """

344 Compute the exponent :math:`\\beta_2` for the short-wavelength sensitive

345 cones.

346

347 Parameters

348 ----------

349 x

350 Short-wavelength sensitive cone response.

351

352 Returns

353 -------

354 :class:`numpy.ndarray`

355 Exponent :math:`\\beta_2`.

356

357 Examples

358 --------

359 >>> beta_2(318.323316315) # doctest: +ELLIPSIS

360 4.6522416...

361 """

362

363 x_p = spow(x, 0.5128)

364

365 return (x_p * 8.091 + 8.414) * 0.7844 / (x_p + 8.414)

366

367

368 def exponential_factors(RGB_o: ArrayLike) -> NDArrayFloat:

369 """

370 Compute the chromatic adaptation exponential factors

371 :math:`\\beta_1(R_o)`, :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)` of

372 the specified cone responses.

373

374 Parameters

375 ----------

376 RGB_o

377 Cone responses.

378

379 Returns

380 -------

381 :class:`numpy.ndarray`

382 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,

383 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.

384

385 Examples

386 --------

387 >>> RGB_o = np.array([318.32331631, 318.30352317, 318.23283482])

388 >>> exponential_factors(RGB_o) # doctest: +ELLIPSIS

389 array([ 4.6106222..., 4.6105892..., 4.6520698...])

390 """

391

392 R_o, G_o, B_o = tsplit(RGB_o)

393

394 bR_o = beta_1(R_o)

395 bG_o = beta_1(G_o)

396 bB_o = beta_2(B_o)

397

398 return tstack([bR_o, bG_o, bB_o])

399

400

401 def K_coefficient(

402 xez_1: ArrayLike,

403 xez_2: ArrayLike,

404 bRGB_o1: ArrayLike,

405 bRGB_o2: ArrayLike,

406 Y_o: ArrayLike,

407 n: ArrayLike = 1,

408) -> NDArrayFloat:

409 """

410 Compute the coefficient :math:`K` for correcting the difference between

411 the test and reference illuminances.

412

413 Parameters

414 ----------

415 xez_1

416 Intermediate values :math:`\\xi_1`, :math:`\\eta_1`, :math:`\\zeta_1`

417 for the test illuminant and background.

418 xez_2

419 Intermediate values :math:`\\xi_2`, :math:`\\eta_2`, :math:`\\zeta_2`

420 for the reference illuminant and background.

421 bRGB_o1

422 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`,

423 :math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample.

424 bRGB_o2

425 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`,

426 :math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference

427 sample.

428 Y_o

429 Luminance factor :math:`Y_o` of achromatic background as percentage

430 normalised to domain [18, 100] in **'Reference'** domain-range scale.

431 n

432 Noise component in fundamental primary system.

433

434 Returns

435 -------

436 :class:`numpy.ndarray`

437 Coefficient :math:`K`.

438

439 Examples

440 --------

441 >>> xez_1 = np.array([1.11857195, 0.93295530, 0.32680879])

442 >>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461])

443 >>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811])

444 >>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351])

445 >>> Y_o = 20

446 >>> K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o)

447 1.0

448 """

449

450 xi_1, eta_1, _zeta_1 = tsplit(xez_1)

451 xi_2, eta_2, _zeta_2 = tsplit(xez_2)

452 bR_o1, bG_o1, _bB_o1 = tsplit(bRGB_o1)

453 bR_o2, bG_o2, _bB_o2 = tsplit(bRGB_o2)

454 Y_o = as_float_array(Y_o)

455 n = as_float_array(n)

456

457 K = spow((Y_o * xi_1 + n) / (20 * xi_1 + n), (2 / 3) * bR_o1) / spow(

458 (Y_o * xi_2 + n) / (20 * xi_2 + n), (2 / 3) * bR_o2

459 )

460

461 K *= spow((Y_o * eta_1 + n) / (20 * eta_1 + n), (1 / 3) * bG_o1) / spow(

462 (Y_o * eta_2 + n) / (20 * eta_2 + n), (1 / 3) * bG_o2

463 )

464

465 return K

466

467

468 def corresponding_colour(

469 RGB_1: ArrayLike,

470 xez_1: ArrayLike,

471 xez_2: ArrayLike,

472 bRGB_o1: ArrayLike,

473 bRGB_o2: ArrayLike,

474 Y_o: ArrayLike,

475 K: ArrayLike,

476 n: ArrayLike = 1,

477) -> NDArrayFloat:

478 """

479 Compute corresponding colour cone responses of the specified test sample cone

480 responses :math:`RGB_1` under chromatic adaptation.

481

482 Parameters

483 ----------

484 RGB_1

485 Test sample cone responses :math:`RGB_1`.

486 xez_1

487 Intermediate values :math:`\\xi_1`, :math:`\\eta_1`, :math:`\\zeta_1`

488 for test illuminant and background.

489 xez_2

490 Intermediate values :math:`\\xi_2`, :math:`\\eta_2`, :math:`\\zeta_2`

491 for reference illuminant and background.

492 bRGB_o1

493 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`,

494 :math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test

495 sample.

496 bRGB_o2

497 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`,

498 :math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference

499 sample.

500 Y_o

501 Luminance factor :math:`Y_o` of achromatic background as percentage

502 normalised to domain [18, 100] in **'Reference'** domain-range

503 scale.

504 K

505 Coefficient :math:`K`.

506 n

507 Noise component in fundamental primary system.

508

509 Returns

510 -------

511 :class:`numpy.ndarray`

512 Corresponding colour cone responses of the specified test sample cone

513 responses.

514

515 Examples

516 --------

517 >>> RGB_1 = np.array([25.82442730, 18.67914220, 4.83901940])

518 >>> xez_1 = np.array([1.11857195, 0.93295530, 0.32680879])

519 >>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461])

520 >>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811])

521 >>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351])

522 >>> Y_o = 20

523 >>> K = 1

524 >>> corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K)

525 ... # doctest: +ELLIPSIS

526 array([ 23.1636901..., 20.0211948..., 16.2001664...])

527 """

528

529 R_1, G_1, B_1 = tsplit(RGB_1)

530 xi_1, eta_1, zeta_1 = tsplit(xez_1)

531 xi_2, eta_2, zeta_2 = tsplit(xez_2)

532 bR_o1, bG_o1, bB_o1 = tsplit(bRGB_o1)

533 bR_o2, bG_o2, bB_o2 = tsplit(bRGB_o2)

534 Y_o = as_float_array(Y_o)

535 K = as_float_array(K)

536 n = as_float_array(n)

537

538 def RGB_c(

539 x_1: NDArrayFloat,

540 x_2: NDArrayFloat,

541 y_1: NDArrayFloat,

542 y_2: NDArrayFloat,

543 z: NDArrayFloat,

544 n: NDArrayFloat,

545 ) -> NDArrayFloat:

546 """Compute the corresponding colour cone responses component."""

547

548 with sdiv_mode():

549 return (Y_o * x_2 + n) * spow(K, sdiv(1, y_2)) * spow(

550 (z + n) / (Y_o * x_1 + n), sdiv(y_1, y_2)

551 ) - n

552

553 R_2 = RGB_c(xi_1, xi_2, bR_o1, bR_o2, R_1, n)

554 G_2 = RGB_c(eta_1, eta_2, bG_o1, bG_o2, G_1, n)

555 B_2 = RGB_c(zeta_1, zeta_2, bB_o1, bB_o2, B_1, n)

556

557 return tstack([R_2, G_2, B_2])