Coverage for appearance/kim2009.py: 68%

1"""

2 Kim, Weyrich and Kautz (2009) Colour Appearance Model

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

4

5 Define the *Kim, Weyrich and Kautz (2009)* colour appearance model for

6 predicting perceptual colour attributes under varying viewing conditions.

7

8 This model extends *CIECAM02* to handle high dynamic range viewing conditions

9 by introducing media-specific parameters that modulate lightness prediction.

10

11- :class:`colour.appearance.InductionFactors_Kim2009`

12- :attr:`colour.VIEWING_CONDITIONS_KIM2009`

13- :class:`colour.appearance.MediaParameters_Kim2009`

14- :attr:`colour.MEDIA_PARAMETERS_KIM2009`

15- :class:`colour.CAM_Specification_Kim2009`

16- :func:`colour.XYZ_to_Kim2009`

17- :func:`colour.Kim2009_to_XYZ`

18

19 References

20----------

21- :cite:`Kim2009` : Kim, M., Weyrich, T., & Kautz, J. (2009). Modeling Human

22 Color Perception under Extended Luminance Levels. ACM Transactions on

23 Graphics, 28(3), 27:1--27:9. doi:10.1145/1531326.1531333

24"""

25

26 from __future__ import annotations

27

28 from dataclasses import astuple, dataclass, field

29

30 import numpy as np

31

32 from colour.adaptation import CAT_CAT02

33 from colour.algebra import spow, vecmul

34 from colour.appearance.ciecam02 import (

35 CAT_INVERSE_CAT02,

36 VIEWING_CONDITIONS_CIECAM02,

37 RGB_to_rgb,

38 degree_of_adaptation,

39 full_chromatic_adaptation_forward,

40 full_chromatic_adaptation_inverse,

41 hue_quadrature,

42 rgb_to_RGB,

43)

44 from colour.hints import ( # noqa: TC001

45 Annotated,

46 ArrayLike,

47 Domain100,

48 NDArrayFloat,

49 Range100,

50)

51 from colour.utilities import (

52 CanonicalMapping,

53 MixinDataclassArithmetic,

54 MixinDataclassIterable,

55 as_float,

56 as_float_array,

57 from_range_100,

58 from_range_degrees,

59 has_only_nan,

60 ones,

61 to_domain_100,

62 to_domain_degrees,

63 tsplit,

64 tstack,

65)

66

67 __author__ = "Colour Developers"

68 __copyright__ = "Copyright 2013 Colour Developers"

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

70 __maintainer__ = "Colour Developers"

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

72 __status__ = "Production"

73

74 __all__ = [

75 "InductionFactors_Kim2009",

76 "VIEWING_CONDITIONS_KIM2009",

77 "MediaParameters_Kim2009",

78 "MEDIA_PARAMETERS_KIM2009",

79 "CAM_Specification_Kim2009",

80 "XYZ_to_Kim2009",

81 "Kim2009_to_XYZ",

82]

83

84

85@dataclass(frozen=True)

86 class InductionFactors_Kim2009(MixinDataclassIterable):

87 """

88 Define the *Kim, Weyrich and Kautz (2009)* colour appearance model

89 surround induction factors.

90

91 Parameters

92 ----------

93 F

94 Maximum degree of adaptation :math:`F`.

95 c

96 Exponential non-linearity :math:`c`.

97 N_c

98 Chromatic induction factor :math:`N_c`.

99

100 Notes

101 -----

102 - The *Kim, Weyrich and Kautz (2009)* colour appearance model induction

103 factors are the same as the *CIECAM02* colour appearance model.

104 - The *Kim, Weyrich and Kautz (2009)* colour appearance model separates

105 the surround modelled by the

106 :class:`colour.appearance.InductionFactors_Kim2009` class instance

107 from the media, modelled with the

108 :class:`colour.appearance.MediaParameters_Kim2009` class instance.

109

110 References

111 ----------

112 :cite:`Kim2009`

113 """

114

115 F: float

116 c: float

117 N_c: float

118

119

120 VIEWING_CONDITIONS_KIM2009: CanonicalMapping = CanonicalMapping(

121 VIEWING_CONDITIONS_CIECAM02

122)

123 VIEWING_CONDITIONS_KIM2009.__doc__ = """

124 Define the reference *Kim, Weyrich and Kautz (2009)* colour appearance model

125 viewing conditions inherited from *CIECAM02*.

126

127 References

128----------

129:cite:`Kim2009`

130"""

131

132

133@dataclass(frozen=True)

134 class MediaParameters_Kim2009:

135 """

136 Define the media parameters for the *Kim, Weyrich and Kautz (2009)* colour

137 appearance model.

138

139 Parameters

140 ----------

141 E

142 Lightness prediction modulating parameter :math:`E`.

143

144 References

145 ----------

146 :cite:`Kim2009`

147 """

148

149 E: float

150

151

152 MEDIA_PARAMETERS_KIM2009: CanonicalMapping = CanonicalMapping(

153 {

154 "High-luminance LCD Display": MediaParameters_Kim2009(1),

155 "Transparent Advertising Media": MediaParameters_Kim2009(1.2175),

156 "CRT Displays": MediaParameters_Kim2009(1.4572),

157 "Reflective Paper": MediaParameters_Kim2009(1.7526),

158 }

159)

160 MEDIA_PARAMETERS_KIM2009.__doc__ = """

161 Define the reference *Kim, Weyrich and Kautz (2009)* colour appearance model

162 media parameters.

163

164 References

165----------

166:cite:`Kim2009`

167

168 Aliases:

169

170- 'bright_lcd_display': 'High-luminance LCD Display'

171- 'advertising_transparencies': 'Transparent Advertising Media'

172- 'crt': 'CRT Displays'

173- 'paper': 'Reflective Paper'

174"""

175 MEDIA_PARAMETERS_KIM2009["bright_lcd_display"] = MEDIA_PARAMETERS_KIM2009[

176 "High-luminance LCD Display"

177]

178 MEDIA_PARAMETERS_KIM2009["advertising_transparencies"] = MEDIA_PARAMETERS_KIM2009[

179 "Transparent Advertising Media"

180]

181 MEDIA_PARAMETERS_KIM2009["crt"] = MEDIA_PARAMETERS_KIM2009["CRT Displays"]

182 MEDIA_PARAMETERS_KIM2009["paper"] = MEDIA_PARAMETERS_KIM2009["Reflective Paper"]

183

184

185@dataclass

186 class CAM_Specification_Kim2009(MixinDataclassArithmetic):

187 """

188 Represent the *Kim, Weyrich and Kautz (2009)* colour appearance model

189 output specification.

190

191 Parameters

192 ----------

193 J

194 Correlate of *Lightness* :math:`J`.

195 C

196 Correlate of *chroma* :math:`C`.

197 h

198 *Hue* angle :math:`h` in degrees.

199 s

200 Correlate of *saturation* :math:`s`.

201 Q

202 Correlate of *brightness* :math:`Q`.

203 M

204 Correlate of *colourfulness* :math:`M`.

205 H

206 *Hue* :math:`h` quadrature :math:`H`.

207 HC

208 *Hue* :math:`h` composition :math:`H^C`.

209

210 References

211 ----------

212 :cite:`Kim2009`

213 """

214

215 J: float | NDArrayFloat | None = field(default_factory=lambda: None)

216 C: float | NDArrayFloat | None = field(default_factory=lambda: None)

217 h: float | NDArrayFloat | None = field(default_factory=lambda: None)

218 s: float | NDArrayFloat | None = field(default_factory=lambda: None)

219 Q: float | NDArrayFloat | None = field(default_factory=lambda: None)

220 M: float | NDArrayFloat | None = field(default_factory=lambda: None)

221 H: float | NDArrayFloat | None = field(default_factory=lambda: None)

222 HC: float | NDArrayFloat | None = field(default_factory=lambda: None)

223

224

225 def XYZ_to_Kim2009(

226 XYZ: Domain100,

227 XYZ_w: Domain100,

228 L_A: ArrayLike,

229 media: MediaParameters_Kim2009 = MEDIA_PARAMETERS_KIM2009["CRT Displays"],

230 surround: InductionFactors_Kim2009 = VIEWING_CONDITIONS_KIM2009["Average"],

231 n_c: float = 0.57,

232 discount_illuminant: bool = False,

233 compute_H: bool = True,

234) -> Annotated[CAM_Specification_Kim2009, (100, 100, 360, 100, 100, 100, 400)]:

235 """

236 Compute the *Kim, Weyrich and Kautz (2009)* colour appearance model

237 correlates from the specified *CIE XYZ* tristimulus values.

238

239 Parameters

240 ----------

241 XYZ

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

243 XYZ_w

244 *CIE XYZ* tristimulus values of reference white.

245 L_A

246 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often

247 taken to be 20% of the luminance of a white object in the scene).

248 media

249 Media parameters.

250 surround

251 Surround viewing conditions induction factors.

252 discount_illuminant

253 Truth value indicating if the illuminant should be discounted.

254 compute_H

255 Whether to compute *Hue* :math:`h` quadrature :math:`H`.

256 :math:`H` is rarely used, and expensive to compute.

257 n_c

258 Cone response sigmoidal curve modulating factor :math:`n_c`.

259

260 Returns

261 -------

262 :class:`colour.CAM_Specification_Kim2009`

263 *Kim, Weyrich and Kautz (2009)* colour appearance model

264 specification.

265

266 Notes

267 -----

268 +---------------------+-----------------------+---------------+

269 | **Domain** | **Scale - Reference** | **Scale - 1** |

270 +=====================+=======================+===============+

271 | ``XYZ`` | 100 | 1 |

272 +---------------------+-----------------------+---------------+

273 | ``XYZ_w`` | 100 | 1 |

274 +---------------------+-----------------------+---------------+

275

276 +---------------------+-----------------------+---------------+

277 | **Range** | **Scale - Reference** | **Scale - 1** |

278 +=====================+=======================+===============+

279 | ``specification.J`` | 100 | 1 |

280 +---------------------+-----------------------+---------------+

281 | ``specification.C`` | 100 | 1 |

282 +---------------------+-----------------------+---------------+

283 | ``specification.h`` | 360 | 1 |

284 +---------------------+-----------------------+---------------+

285 | ``specification.s`` | 100 | 1 |

286 +---------------------+-----------------------+---------------+

287 | ``specification.Q`` | 100 | 1 |

288 +---------------------+-----------------------+---------------+

289 | ``specification.M`` | 100 | 1 |

290 +---------------------+-----------------------+---------------+

291 | ``specification.H`` | 400 | 1 |

292 +---------------------+-----------------------+---------------+

293

294 References

295 ----------

296 :cite:`Kim2009`

297

298 Examples

299 --------

300 >>> XYZ = np.array([19.01, 20.00, 21.78])

301 >>> XYZ_w = np.array([95.05, 100.00, 108.88])

302 >>> L_A = 318.31

303 >>> media = MEDIA_PARAMETERS_KIM2009["CRT Displays"]

304 >>> surround = VIEWING_CONDITIONS_KIM2009["Average"]

305 >>> XYZ_to_Kim2009(XYZ, XYZ_w, L_A, media, surround)

306 ... # doctest: +ELLIPSIS

307 CAM_Specification_Kim2009(J=28.8619089..., C=0.5592455..., \

308 h=219.0480667..., s=9.3837797..., Q=52.7138883..., M=0.4641738..., \

309 H=278.0602824..., HC=None)

310 """

311

312 XYZ = to_domain_100(XYZ)

313 XYZ_w = to_domain_100(XYZ_w)

314 _X_w, Y_w, _Z_w = tsplit(XYZ_w)

315 L_A = as_float_array(L_A)

316

317 # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform

318 # sharpened *RGB* values.

319 RGB = vecmul(CAT_CAT02, XYZ)

320 RGB_w = vecmul(CAT_CAT02, XYZ_w)

321

322 # Computing degree of adaptation :math:`D`.

323 D = (

324 degree_of_adaptation(surround.F, L_A)

325 if not discount_illuminant

326 else ones(L_A.shape)

327 )

328

329 # Computing full chromatic adaptation.

330 XYZ_c = full_chromatic_adaptation_forward(RGB, RGB_w, Y_w, D)

331 XYZ_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D)

332

333 # Converting to *Hunt-Pointer-Estevez* colourspace.

334 LMS = RGB_to_rgb(XYZ_c)

335 LMS_w = RGB_to_rgb(XYZ_wc)

336

337 # Cones absolute response.

338 LMS_n_c = spow(LMS, n_c)

339 LMS_w_n_c = spow(LMS_w, n_c)

340 L_A_n_c = spow(L_A, n_c)

341 LMS_p = LMS_n_c / (LMS_n_c + L_A_n_c)

342 LMS_wp = LMS_w_n_c / (LMS_w_n_c + L_A_n_c)

343

344 # Achromatic signal :math:`A` and :math:`A_w`.

345 v_A = np.array([40, 20, 1])

346 A = np.sum(v_A * LMS_p, axis=-1) / 61

347 A_w = np.sum(v_A * LMS_wp, axis=-1) / 61

348

349 # Perceived *Lightness* :math:`J_p`.

350 a_j, b_j, o_j, n_j = 0.89, 0.24, 0.65, 3.65

351 A_A_w = A / A_w

352 J_p = spow((-(A_A_w - b_j) * spow(o_j, n_j)) / (A_A_w - b_j - a_j), 1 / n_j)

353

354 # Computing the media dependent *Lightness* :math:`J`.

355 J = 100 * (media.E * (J_p - 1) + 1)

356

357 # Computing the correlate of *brightness* :math:`Q`.

358 n_q = 0.1308

359 Q = J * spow(Y_w, n_q)

360

361 # Opponent signals :math:`a` and :math:`b`.

362 a = (1 / 11) * np.sum(np.array([11, -12, 1]) * LMS_p, axis=-1)

363 b = (1 / 9) * np.sum(np.array([1, 1, -2]) * LMS_p, axis=-1)

364

365 # Computing the correlate of *chroma* :math:`C`.

366 a_k, n_k = 456.5, 0.62

367 C = a_k * spow(np.hypot(a, b), n_k)

368

369 # Computing the correlate of *colourfulness* :math:`M`.

370 a_m, b_m = 0.11, 0.61

371 M = C * (a_m * np.log10(Y_w) + b_m)

372

373 # Computing the correlate of *saturation* :math:`s`.

374 s = 100 * np.sqrt(M / Q)

375

376 # Computing the *hue* angle :math:`h`.

377 h = np.degrees(np.arctan2(b, a)) % 360

378

379 # Computing hue :math:`h` quadrature :math:`H`.

380 H = hue_quadrature(h) if compute_H else np.full(h.shape, np.nan)

381

382 return CAM_Specification_Kim2009(

383 J=as_float(from_range_100(J)),

384 C=as_float(from_range_100(C)),

385 h=as_float(from_range_degrees(h)),

386 s=as_float(from_range_100(s)),

387 Q=as_float(from_range_100(Q)),

388 M=as_float(from_range_100(M)),

389 H=as_float(from_range_degrees(H, 400)),

390 HC=None,

391 )

392

393

394 def Kim2009_to_XYZ(

395 specification: Annotated[

396 CAM_Specification_Kim2009, (100, 100, 360, 100, 100, 100, 400)

397 ],

398 XYZ_w: Domain100,

399 L_A: ArrayLike,

400 media: MediaParameters_Kim2009 = MEDIA_PARAMETERS_KIM2009["CRT Displays"],

401 surround: InductionFactors_Kim2009 = VIEWING_CONDITIONS_KIM2009["Average"],

402 n_c: float = 0.57,

403 discount_illuminant: bool = False,

404) -> Range100:

405 """

406 Convert the *Kim, Weyrich and Kautz (2009)* colour appearance model

407 specification to *CIE XYZ* tristimulus values.

408

409 Parameters

410 ----------

411 specification

412 *Kim, Weyrich and Kautz (2009)* colour appearance model specification.

413 Correlate of *Lightness* :math:`J`, correlate of *chroma* :math:`C` or

414 correlate of *colourfulness* :math:`M` and *hue* angle :math:`h` in

415 degrees must be specified, e.g., :math:`JCh` or :math:`JMh`.

416 XYZ_w

417 *CIE XYZ* tristimulus values of reference white.

418 L_A

419 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken

420 to be 20% of the luminance of a white object in the scene).

421 media

422 Media parameters.

423 surround

424 Surround viewing conditions induction factors.

425 n_c

426 Cone response sigmoidal curve modulating factor :math:`n_c`.

427 discount_illuminant

428 Truth value indicating if the illuminant should be discounted.

429

430 Returns

431 -------

432 :class:`numpy.ndarray`

433 *CIE XYZ* tristimulus values.

434

435 Raises

436 ------

437 ValueError

438 If neither :math:`C` nor :math:`M` correlates have been defined in the

439 ``specification`` argument.

440

441 Notes

442 -----

443 +---------------------+-----------------------+---------------+

444 | **Domain** | **Scale - Reference** | **Scale - 1** |

445 +=====================+=======================+===============+

446 | ``specification.J`` | 100 | 1 |

447 +---------------------+-----------------------+---------------+

448 | ``specification.C`` | 100 | 1 |

449 +---------------------+-----------------------+---------------+

450 | ``specification.h`` | 360 | 1 |

451 +---------------------+-----------------------+---------------+

452 | ``specification.s`` | 100 | 1 |

453 +---------------------+-----------------------+---------------+

454 | ``specification.Q`` | 100 | 1 |

455 +---------------------+-----------------------+---------------+

456 | ``specification.M`` | 100 | 1 |

457 +---------------------+-----------------------+---------------+

458 | ``specification.H`` | 360 | 1 |

459 +---------------------+-----------------------+---------------+

460 | ``XYZ_w`` | 100 | 1 |

461 +---------------------+-----------------------+---------------+

462

463 +---------------------+-----------------------+---------------+

464 | **Range** | **Scale - Reference** | **Scale - 1** |

465 +=====================+=======================+===============+

466 | ``XYZ`` | 100 | 1 |

467 +---------------------+-----------------------+---------------+

468

469 References

470 ----------

471 :cite:`Kim2009`

472

473 Examples

474 --------

475 >>> specification = CAM_Specification_Kim2009(

476 ... J=28.861908975839647, C=0.5592455924373706, h=219.04806677662953

477 ... )

478 >>> XYZ_w = np.array([95.05, 100.00, 108.88])

479 >>> L_A = 318.31

480 >>> media = MEDIA_PARAMETERS_KIM2009["CRT Displays"]

481 >>> surround = VIEWING_CONDITIONS_KIM2009["Average"]

482 >>> Kim2009_to_XYZ(specification, XYZ_w, L_A, media, surround)

483 ... # doctest: +ELLIPSIS

484 array([ 19.0099995..., 19.9999999..., 21.7800000...])

485 """

486

487 J, C, h, _s, _Q, M, _H, _HC = astuple(specification)

488

489 J = to_domain_100(J)

490 C = to_domain_100(C)

491 h = to_domain_degrees(h)

492 M = to_domain_100(M)

493 L_A = as_float_array(L_A)

494 XYZ_w = to_domain_100(XYZ_w)

495 _X_w, Y_w, _Z_w = tsplit(XYZ_w)

496

497 # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform

498 # sharpened *RGB* values.

499 RGB_w = vecmul(CAT_CAT02, XYZ_w)

500

501 # Computing degree of adaptation :math:`D`.

502 D = (

503 degree_of_adaptation(surround.F, L_A)

504 if not discount_illuminant

505 else ones(L_A.shape)

506 )

507

508 # Computing full chromatic adaptation.

509 XYZ_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D)

510

511 # Converting to *Hunt-Pointer-Estevez* colourspace.

512 LMS_w = RGB_to_rgb(XYZ_wc)

513

514 # n_q = 0.1308

515 # J = Q / spow(Y_w, n_q)

516 if has_only_nan(C) and not has_only_nan(M):

517 a_m, b_m = 0.11, 0.61

518 C = M / (a_m * np.log10(Y_w) + b_m)

519 elif has_only_nan(C):

520 error = (

521 'Either "C" or "M" correlate must be defined in '

522 'the "CAM_Specification_Kim2009" argument!'

523 )

524

525 raise ValueError(error)

526

527 # Cones absolute response.

528 LMS_w_n_c = spow(LMS_w, n_c)

529 L_A_n_c = spow(L_A, n_c)

530 LMS_wp = LMS_w_n_c / (LMS_w_n_c + L_A_n_c)

531

532 # Achromatic signal :math:`A_w`

533 v_A = np.array([40, 20, 1])

534 A_w = np.sum(v_A * LMS_wp, axis=-1) / 61

535

536 # Perceived *Lightness* :math:`J_p`.

537 J_p = (J / 100 - 1) / media.E + 1

538

539 # Achromatic signal :math:`A`.

540 a_j, b_j, n_j, o_j = 0.89, 0.24, 3.65, 0.65

541 J_p_n_j = spow(J_p, n_j)

542 A = A_w * ((a_j * J_p_n_j) / (J_p_n_j + spow(o_j, n_j)) + b_j)

543

544 # Opponent signals :math:`a` and :math:`b`.

545 a_k, n_k = 456.5, 0.62

546 C_a_k_n_k = spow(C / a_k, 1 / n_k)

547 hr = np.radians(h)

548 a, b = np.cos(hr) * C_a_k_n_k, np.sin(hr) * C_a_k_n_k

549

550 # Cones absolute response.

551 M = np.array(

552 [

553 [1.0000, 0.3215, 0.2053],

554 [1.0000, -0.6351, -0.1860],

555 [1.0000, -0.1568, -4.4904],

556 ]

557 )

558 LMS_p = vecmul(M, tstack([A, a, b]))

559 LMS = spow((-spow(L_A, n_c) * LMS_p) / (LMS_p - 1), 1 / n_c)

560

561 # Converting to *Hunt-Pointer-Estevez* colourspace.

562 RGB_c = rgb_to_RGB(LMS)

563

564 # Applying inverse full chromatic adaptation.

565 RGB = full_chromatic_adaptation_inverse(RGB_c, RGB_w, Y_w, D)

566

567 XYZ = vecmul(CAT_INVERSE_CAT02, RGB)

568

569 return from_range_100(XYZ)