Coverage for appearance/kim2009.py: 68%

116 statements  

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

1""" 

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

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

4 

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

6predicting perceptual colour attributes under varying viewing conditions. 

7 

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

9by 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 

19References 

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 

26from __future__ import annotations 

27 

28from dataclasses import astuple, dataclass, field 

29 

30import numpy as np 

31 

32from colour.adaptation import CAT_CAT02 

33from colour.algebra import spow, vecmul 

34from 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) 

44from colour.hints import ( # noqa: TC001 

45 Annotated, 

46 ArrayLike, 

47 Domain100, 

48 NDArrayFloat, 

49 Range100, 

50) 

51from 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) 

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

120VIEWING_CONDITIONS_KIM2009: CanonicalMapping = CanonicalMapping( 

121 VIEWING_CONDITIONS_CIECAM02 

122) 

123VIEWING_CONDITIONS_KIM2009.__doc__ = """ 

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

125viewing conditions inherited from *CIECAM02*. 

126 

127References 

128---------- 

129:cite:`Kim2009` 

130""" 

131 

132 

133@dataclass(frozen=True) 

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

152MEDIA_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) 

160MEDIA_PARAMETERS_KIM2009.__doc__ = """ 

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

162media parameters. 

163 

164References 

165---------- 

166:cite:`Kim2009` 

167 

168Aliases: 

169 

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

171- 'advertising_transparencies': 'Transparent Advertising Media' 

172- 'crt': 'CRT Displays' 

173- 'paper': 'Reflective Paper' 

174""" 

175MEDIA_PARAMETERS_KIM2009["bright_lcd_display"] = MEDIA_PARAMETERS_KIM2009[ 

176 "High-luminance LCD Display" 

177] 

178MEDIA_PARAMETERS_KIM2009["advertising_transparencies"] = MEDIA_PARAMETERS_KIM2009[ 

179 "Transparent Advertising Media" 

180] 

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

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

183 

184 

185@dataclass 

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

225def 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..., \ 

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

309H=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 

394def 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)