Coverage for appearance/llab.py: 51%

123 statements  

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

1""" 

2:math:`LLAB(l:c)` Colour Appearance Model 

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

4 

5Define the *:math:`LLAB(l:c)`* colour appearance model for predicting 

6perceptual colour attributes under varying viewing conditions. 

7 

8- :class:`colour.appearance.InductionFactors_LLAB` 

9- :attr:`colour.VIEWING_CONDITIONS_LLAB` 

10- :class:`colour.CAM_Specification_LLAB` 

11- :func:`colour.XYZ_to_LLAB` 

12 

13References 

14---------- 

15- :cite:`Fairchild2013x` : Fairchild, M. D. (2013). LLAB Model. In Color 

16 Appearance Models (3rd ed., pp. 6025-6178). Wiley. ISBN:B00DAYO8E2 

17- :cite:`Luo1996b` : Luo, Ming Ronnier, Lo, M.-C., & Kuo, W.-G. (1996). The 

18 LLAB (l:c) colour model. Color Research & Application, 21(6), 412-429. 

19 doi:10.1002/(SICI)1520-6378(199612)21:6<412::AID-COL4>3.0.CO;2-Z 

20- :cite:`Luo1996c` : Luo, Ming Ronnier, & Morovic, J. (1996). Two Unsolved 

21 Issues in Colour Management - Colour Appearance and Gamut Mapping. 

22 Conference: 5th International Conference on High Technology: Imaging 

23 Science and Technology - Evolution & Promise, 136-147. 

24 http://www.researchgate.net/publication/\ 

25236348295_Two_Unsolved_Issues_in_Colour_Management__\ 

26Colour_Appearance_and_Gamut_Mapping 

27""" 

28 

29from __future__ import annotations 

30 

31from dataclasses import dataclass, field 

32 

33import numpy as np 

34 

35from colour.algebra import polar_to_cartesian, sdiv, sdiv_mode, spow, vecmul 

36from colour.hints import Annotated, ArrayLike, Domain100, NDArrayFloat # noqa: TC001 

37from colour.utilities import ( 

38 CanonicalMapping, 

39 MixinDataclassArithmetic, 

40 MixinDataclassIterable, 

41 as_float, 

42 as_float_array, 

43 from_range_degrees, 

44 to_domain_100, 

45 tsplit, 

46 tstack, 

47) 

48 

49__author__ = "Colour Developers" 

50__copyright__ = "Copyright 2013 Colour Developers" 

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

52__maintainer__ = "Colour Developers" 

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

54__status__ = "Production" 

55 

56__all__ = [ 

57 "InductionFactors_LLAB", 

58 "VIEWING_CONDITIONS_LLAB", 

59 "MATRIX_XYZ_TO_RGB_LLAB", 

60 "MATRIX_RGB_TO_XYZ_LLAB", 

61 "CAM_ReferenceSpecification_LLAB", 

62 "CAM_Specification_LLAB", 

63 "XYZ_to_LLAB", 

64 "XYZ_to_RGB_LLAB", 

65 "chromatic_adaptation", 

66 "f", 

67 "opponent_colour_dimensions", 

68 "hue_angle", 

69 "chroma_correlate", 

70 "colourfulness_correlate", 

71 "saturation_correlate", 

72 "final_opponent_signals", 

73] 

74 

75 

76@dataclass(frozen=True) 

77class InductionFactors_LLAB(MixinDataclassIterable): 

78 """ 

79 Define the *:math:`LLAB(l:c)`* colour appearance model induction factors. 

80 

81 Parameters 

82 ---------- 

83 D 

84 *Discounting-the-Illuminant* factor :math:`D`. 

85 F_S 

86 Surround induction factor :math:`F_S`. 

87 F_L 

88 *Lightness* induction factor :math:`F_L`. 

89 F_C 

90 *Chroma* induction factor :math:`F_C`. 

91 

92 References 

93 ---------- 

94 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` 

95 """ 

96 

97 D: float 

98 F_S: float 

99 F_L: float 

100 F_C: float 

101 

102 

103VIEWING_CONDITIONS_LLAB: CanonicalMapping = CanonicalMapping( 

104 { 

105 "Reference Samples & Images, Average Surround, Subtending > 4": ( 

106 InductionFactors_LLAB(1, 3, 0, 1) 

107 ), 

108 "Reference Samples & Images, Average Surround, Subtending < 4": ( 

109 InductionFactors_LLAB(1, 3, 1, 1) 

110 ), 

111 "Television & VDU Displays, Dim Surround": ( 

112 InductionFactors_LLAB(0.7, 3.5, 1, 1) 

113 ), 

114 "Cut Sheet Transparency, Dim Surround": (InductionFactors_LLAB(1, 5, 1, 1.1)), 

115 "35mm Projection Transparency, Dark Surround": ( 

116 InductionFactors_LLAB(0.7, 4, 1, 1) 

117 ), 

118 } 

119) 

120VIEWING_CONDITIONS_LLAB.__doc__ = """ 

121Define the reference :math:`LLAB(l:c)` colour appearance model viewing 

122conditions. 

123 

124References 

125---------- 

126:cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` 

127 

128Aliases: 

129 

130- 'ref_average_4_plus': 

131 'Reference Samples & Images, Average Surround, Subtending > 4' 

132- 'ref_average_4_minus': 

133 'Reference Samples & Images, Average Surround, Subtending < 4' 

134- 'tv_dim': 'Television & VDU Displays, Dim Surround' 

135- 'sheet_dim': 'Cut Sheet Transparency, Dim Surround' 

136- 'projected_dark': '35mm Projection Transparency, Dark Surround' 

137""" 

138VIEWING_CONDITIONS_LLAB["ref_average_4_plus"] = VIEWING_CONDITIONS_LLAB[ 

139 "Reference Samples & Images, Average Surround, Subtending > 4" 

140] 

141VIEWING_CONDITIONS_LLAB["ref_average_4_minus"] = VIEWING_CONDITIONS_LLAB[ 

142 "Reference Samples & Images, Average Surround, Subtending < 4" 

143] 

144VIEWING_CONDITIONS_LLAB["tv_dim"] = VIEWING_CONDITIONS_LLAB[ 

145 "Television & VDU Displays, Dim Surround" 

146] 

147VIEWING_CONDITIONS_LLAB["sheet_dim"] = VIEWING_CONDITIONS_LLAB[ 

148 "Cut Sheet Transparency, Dim Surround" 

149] 

150VIEWING_CONDITIONS_LLAB["projected_dark"] = VIEWING_CONDITIONS_LLAB[ 

151 "35mm Projection Transparency, Dark Surround" 

152] 

153 

154MATRIX_XYZ_TO_RGB_LLAB: NDArrayFloat = np.array( 

155 [ 

156 [0.8951, 0.2664, -0.1614], 

157 [-0.7502, 1.7135, 0.0367], 

158 [0.0389, -0.0685, 1.0296], 

159 ] 

160) 

161""" 

162LLAB(l:c) colour appearance model *CIE XYZ* tristimulus values to normalised 

163cone responses matrix. 

164""" 

165 

166MATRIX_RGB_TO_XYZ_LLAB: NDArrayFloat = np.linalg.inv(MATRIX_XYZ_TO_RGB_LLAB) 

167""" 

168LLAB(l:c) colour appearance model normalised cone responses to *CIE XYZ* 

169tristimulus values matrix. 

170""" 

171 

172 

173@dataclass 

174class CAM_ReferenceSpecification_LLAB(MixinDataclassArithmetic): 

175 """ 

176 Define the *:math:`LLAB(l:c)`* colour appearance model reference 

177 specification. 

178 

179 This specification contains field names consistent with the *Fairchild 

180 (2013)* reference. 

181 

182 Parameters 

183 ---------- 

184 L_L 

185 Correlate of *Lightness* :math:`L_L`. 

186 Ch_L 

187 Correlate of *chroma* :math:`Ch_L`. 

188 h_L 

189 *Hue* angle :math:`h_L` in degrees. 

190 s_L 

191 Correlate of *saturation* :math:`s_L`. 

192 C_L 

193 Correlate of *colourfulness* :math:`C_L`. 

194 HC 

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

196 A_L 

197 Opponent signal :math:`A_L`. 

198 B_L 

199 Opponent signal :math:`B_L`. 

200 

201 References 

202 ---------- 

203 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` 

204 """ 

205 

206 L_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

207 Ch_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

208 h_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

209 s_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

210 C_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

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

212 A_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

213 B_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

214 

215 

216@dataclass 

217class CAM_Specification_LLAB(MixinDataclassArithmetic): 

218 """ 

219 Define the *:math:`LLAB(l:c)`* colour appearance model specification. 

220 

221 This specification provides a standardized interface for the *LLAB(l:c)* 

222 model with field names consistent across all colour appearance models in 

223 :mod:`colour.appearance`. While the field names differ from the original 

224 *Fairchild (2013)* reference notation, they map directly to the model's 

225 perceptual correlates. 

226 

227 Parameters 

228 ---------- 

229 J 

230 Correlate of *lightness* :math:`L_L`. 

231 C 

232 Correlate of *chroma* :math:`Ch_L`. 

233 h 

234 *Hue* angle :math:`h_L` in degrees. 

235 s 

236 Correlate of *saturation* :math:`s_L`. 

237 M 

238 Correlate of *colourfulness* :math:`C_L`. 

239 HC 

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

241 a 

242 Opponent signal :math:`A_L`. 

243 b 

244 Opponent signal :math:`B_L`. 

245 

246 Notes 

247 ----- 

248 - This specification is the one used in the current model implementation. 

249 

250 References 

251 ---------- 

252 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` 

253 """ 

254 

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

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

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

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

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

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

261 a: float | NDArrayFloat | None = field(default_factory=lambda: None) 

262 b: float | NDArrayFloat | None = field(default_factory=lambda: None) 

263 

264 

265def XYZ_to_LLAB( 

266 XYZ: Domain100, 

267 XYZ_0: Domain100, 

268 Y_b: ArrayLike, 

269 L: ArrayLike, 

270 surround: InductionFactors_LLAB = VIEWING_CONDITIONS_LLAB[ 

271 "Reference Samples & Images, Average Surround, Subtending < 4" 

272 ], 

273) -> Annotated[CAM_Specification_LLAB, 360]: 

274 """ 

275 Compute the *:math:`LLAB(l:c)`* colour appearance model correlates from 

276 the specified *CIE XYZ* tristimulus values. 

277 

278 Parameters 

279 ---------- 

280 XYZ 

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

282 XYZ_0 

283 *CIE XYZ* tristimulus values of reference white. 

284 Y_b 

285 Luminance factor of the background in :math:`cd/m^2`. 

286 L 

287 Absolute luminance :math:`L` of reference white in 

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

289 surround 

290 Surround viewing conditions induction factors. 

291 

292 Returns 

293 ------- 

294 :class:`colour.CAM_Specification_LLAB` 

295 *:math:`LLAB(l:c)`* colour appearance model specification. 

296 

297 Notes 

298 ----- 

299 +---------------------+-----------------------+---------------+ 

300 | **Domain** | **Scale - Reference** | **Scale - 1** | 

301 +=====================+=======================+===============+ 

302 | ``XYZ`` | 100 | 1 | 

303 +---------------------+-----------------------+---------------+ 

304 | ``XYZ_0`` | 100 | 1 | 

305 +---------------------+-----------------------+---------------+ 

306 

307 +---------------------+-----------------------+---------------+ 

308 | **Range** | **Scale - Reference** | **Scale - 1** | 

309 +=====================+=======================+===============+ 

310 | ``specification.h`` | 360 | 1 | 

311 +---------------------+-----------------------+---------------+ 

312 

313 References 

314 ---------- 

315 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` 

316 

317 Examples 

318 -------- 

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

320 >>> XYZ_0 = np.array([95.05, 100.00, 108.88]) 

321 >>> Y_b = 20.0 

322 >>> L = 318.31 

323 >>> surround = VIEWING_CONDITIONS_LLAB["ref_average_4_minus"] 

324 >>> XYZ_to_LLAB(XYZ, XYZ_0, Y_b, L, surround) # doctest: +ELLIPSIS 

325 CAM_Specification_LLAB(J=37.3668650..., C=0.0089496..., h=270..., \ 

326s=0.0002395..., M=0.0190185..., HC=None, a=..., b=-0.0190185...) 

327 """ 

328 

329 _X, Y, _Z = tsplit(to_domain_100(XYZ)) 

330 RGB = XYZ_to_RGB_LLAB(to_domain_100(XYZ)) 

331 RGB_0 = XYZ_to_RGB_LLAB(to_domain_100(XYZ_0)) 

332 

333 # Reference illuminant *CIE Standard Illuminant D Series* *D65*. 

334 XYZ_0r = np.array([95.05, 100.00, 108.88]) 

335 RGB_0r = XYZ_to_RGB_LLAB(XYZ_0r) 

336 

337 # Computing chromatic adaptation. 

338 XYZ_r = chromatic_adaptation(RGB, RGB_0, RGB_0r, Y, surround.D) 

339 

340 # ------------------------------------------------------------------------- 

341 # Computing the correlate of *Lightness* :math:`L_L`. 

342 # ------------------------------------------------------------------------- 

343 # Computing opponent colour dimensions. 

344 L_L, a, b = tsplit( 

345 opponent_colour_dimensions(XYZ_r, Y_b, surround.F_S, surround.F_L) 

346 ) 

347 

348 # Computing perceptual correlates. 

349 # ------------------------------------------------------------------------- 

350 # Computing the correlate of *chroma* :math:`Ch_L`. 

351 # ------------------------------------------------------------------------- 

352 Ch_L = chroma_correlate(a, b) 

353 

354 # ------------------------------------------------------------------------- 

355 # Computing the correlate of *colourfulness* :math:`C_L`. 

356 # ------------------------------------------------------------------------- 

357 C_L = colourfulness_correlate(L, L_L, Ch_L, surround.F_C) 

358 

359 # ------------------------------------------------------------------------- 

360 # Computing the correlate of *saturation* :math:`s_L`. 

361 # ------------------------------------------------------------------------- 

362 s_L = saturation_correlate(Ch_L, L_L) 

363 

364 # ------------------------------------------------------------------------- 

365 # Computing the *hue* angle :math:`h_L`. 

366 # ------------------------------------------------------------------------- 

367 h_L = hue_angle(a, b) 

368 # TODO: Implement hue composition computation. 

369 

370 # ------------------------------------------------------------------------- 

371 # Computing final opponent signals. 

372 # ------------------------------------------------------------------------- 

373 A_L, B_L = tsplit(final_opponent_signals(C_L, h_L)) 

374 

375 return CAM_Specification_LLAB( 

376 J=L_L, 

377 C=Ch_L, 

378 h=as_float(from_range_degrees(h_L)), 

379 s=s_L, 

380 M=C_L, 

381 HC=None, 

382 a=A_L, 

383 b=B_L, 

384 ) 

385 

386 

387def XYZ_to_RGB_LLAB(XYZ: ArrayLike) -> NDArrayFloat: 

388 """ 

389 Convert from *CIE XYZ* tristimulus values to normalised cone responses. 

390 

391 Parameters 

392 ---------- 

393 XYZ 

394 *CIE XYZ* tristimulus values. 

395 

396 Returns 

397 ------- 

398 :class:`numpy.ndarray` 

399 Normalised cone responses. 

400 

401 Examples 

402 -------- 

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

404 >>> XYZ_to_RGB_LLAB(XYZ) # doctest: +ELLIPSIS 

405 array([ 0.9414279..., 1.0404012..., 1.0897088...]) 

406 """ 

407 

408 XYZ = as_float_array(XYZ) 

409 

410 with sdiv_mode(): 

411 return vecmul(MATRIX_XYZ_TO_RGB_LLAB, sdiv(XYZ, XYZ[..., 1, None])) 

412 

413 

414def chromatic_adaptation( 

415 RGB: ArrayLike, 

416 RGB_0: ArrayLike, 

417 RGB_0r: ArrayLike, 

418 Y: ArrayLike, 

419 D: ArrayLike = 1, 

420) -> NDArrayFloat: 

421 """ 

422 Apply chromatic adaptation to the specified *RGB* normalised cone 

423 responses array. 

424 

425 Parameters 

426 ---------- 

427 RGB 

428 *RGB* normalised cone responses array of the test sample / stimulus. 

429 RGB_0 

430 *RGB* normalised cone responses array of the reference white. 

431 RGB_0r 

432 *RGB* normalised cone responses array of the reference illuminant 

433 *CIE Standard Illuminant D Series* *D65*. 

434 Y 

435 Tristimulus value :math:`Y` of the stimulus. 

436 D 

437 *Discounting-the-Illuminant* factor normalised to domain [0, 1]. 

438 Default is 1. 

439 

440 Returns 

441 ------- 

442 :class:`numpy.ndarray` 

443 Adapted *CIE XYZ* tristimulus values. 

444 

445 Examples 

446 -------- 

447 >>> RGB = np.array([0.94142795, 1.04040120, 1.08970885]) 

448 >>> RGB_0 = np.array([0.94146023, 1.04039386, 1.08950293]) 

449 >>> RGB_0r = np.array([0.94146023, 1.04039386, 1.08950293]) 

450 >>> Y = 20.0 

451 >>> chromatic_adaptation(RGB, RGB_0, RGB_0r, Y) # doctest: +ELLIPSIS 

452 array([ 19.01, 20. , 21.78]) 

453 """ 

454 

455 R, G, B = tsplit(RGB) 

456 R_0, G_0, B_0 = tsplit(RGB_0) 

457 R_0r, G_0r, B_0r = tsplit(RGB_0r) 

458 Y = as_float_array(Y) 

459 D = as_float_array(D) 

460 

461 beta = spow(B_0 / B_0r, 0.0834) 

462 

463 R_r = (D * R_0r / R_0 + 1 - D) * R 

464 G_r = (D * G_0r / G_0 + 1 - D) * G 

465 B_r = (D * B_0r / spow(B_0, beta) + 1 - D) * spow(B, beta) 

466 

467 RGB_r = tstack([R_r, G_r, B_r]) 

468 

469 Y = tstack([Y, Y, Y]) 

470 

471 return vecmul(MATRIX_RGB_TO_XYZ_LLAB, RGB_r * Y) 

472 

473 

474def f(x: ArrayLike, F_S: ArrayLike) -> NDArrayFloat: 

475 """ 

476 Model the nonlinear response function of the *:math:`LLAB(l:c)`* colour 

477 appearance model to simulate the nonlinear behaviour of various visual 

478 responses. 

479 

480 Parameters 

481 ---------- 

482 x 

483 Visual response variable :math:`x`. 

484 F_S 

485 Surround induction factor :math:`F_S`. 

486 

487 Returns 

488 ------- 

489 :class:`numpy.ndarray` 

490 Modeled visual response variable :math:`x`. 

491 

492 Examples 

493 -------- 

494 >>> x = np.array([0.23350512, 0.23351103, 0.23355179]) 

495 >>> f(0.200009186234000, 3) # doctest: +ELLIPSIS 

496 0.5848125... 

497 """ 

498 

499 x = as_float_array(x) 

500 F_S = as_float_array(F_S) 

501 

502 one_F_s = 1 / F_S 

503 

504 x_m = np.where( 

505 x > 0.008856, 

506 spow(x, one_F_s), 

507 ((spow(0.008856, one_F_s) - (16 / 116)) / 0.008856) * x + (16 / 116), 

508 ) 

509 

510 return as_float(x_m) 

511 

512 

513def opponent_colour_dimensions( 

514 XYZ: ArrayLike, 

515 Y_b: ArrayLike, 

516 F_S: ArrayLike, 

517 F_L: ArrayLike, 

518) -> NDArrayFloat: 

519 r""" 

520 Compute opponent colour dimensions from the specified adapted *CIE XYZ* 

521 tristimulus values. 

522 

523 The opponent colour dimensions are based on a modified *CIE L\*a\*b\** 

524 colourspace formulae. 

525 

526 Parameters 

527 ---------- 

528 XYZ 

529 Adapted *CIE XYZ* tristimulus values. 

530 Y_b 

531 Luminance factor of the background in :math:`cd/m^2`. 

532 F_S 

533 Surround induction factor :math:`F_S`. 

534 F_L 

535 Lightness induction factor :math:`F_L`. 

536 

537 Returns 

538 ------- 

539 :class:`numpy.ndarray` 

540 Opponent colour dimensions. 

541 

542 Examples 

543 -------- 

544 >>> XYZ = np.array([19.00999572, 20.00091862, 21.77993863]) 

545 >>> Y_b = 20.0 

546 >>> F_S = 3.0 

547 >>> F_L = 1.0 

548 >>> opponent_colour_dimensions(XYZ, Y_b, F_S, F_L) # doctest: +ELLIPSIS 

549 array([ 3.7368047...e+01, -4.4986443...e-03, -5.2604647...e-03]) 

550 """ 

551 

552 X, Y, Z = tsplit(XYZ) 

553 Y_b = as_float_array(Y_b) 

554 F_S = as_float_array(F_S) 

555 F_L = as_float_array(F_L) 

556 

557 # Account for background lightness contrast. 

558 z = 1 + F_L * spow(Y_b / 100, 0.5) 

559 

560 # Computing modified *CIE L\\*a\\*b\\** colourspace array. 

561 L = 116 * spow(f(Y / 100, F_S), z) - 16 

562 a = 500 * (f(X / 95.05, F_S) - f(Y / 100, F_S)) 

563 b = 200 * (f(Y / 100, F_S) - f(Z / 108.88, F_S)) 

564 

565 return tstack([L, a, b]) 

566 

567 

568def hue_angle(a: ArrayLike, b: ArrayLike) -> NDArrayFloat: 

569 """ 

570 Compute the *hue* angle :math:`h_L` in degrees from the specified 

571 opponent colour dimensions. 

572 

573 Parameters 

574 ---------- 

575 a 

576 Opponent colour dimension :math:`a`. 

577 b 

578 Opponent colour dimension :math:`b`. 

579 

580 Returns 

581 ------- 

582 :class:`numpy.ndarray` 

583 *Hue* angle :math:`h_L` in degrees. 

584 

585 Examples 

586 -------- 

587 >>> hue_angle(-4.49864756e-03, -5.26046353e-03) # doctest: +ELLIPSIS 

588 229.4635727... 

589 """ 

590 

591 a = as_float_array(a) 

592 b = as_float_array(b) 

593 

594 h_L = np.degrees(np.arctan2(b, a)) % 360 

595 

596 return as_float(h_L) 

597 

598 

599def chroma_correlate(a: ArrayLike, b: ArrayLike) -> NDArrayFloat: 

600 """ 

601 Compute the correlate of *chroma* :math:`Ch_L` from the specified 

602 opponent colour dimensions. 

603 

604 Parameters 

605 ---------- 

606 a 

607 Opponent colour dimension :math:`a`. 

608 b 

609 Opponent colour dimension :math:`b`. 

610 

611 Returns 

612 ------- 

613 :class:`numpy.ndarray` 

614 Correlate of *chroma* :math:`Ch_L`. 

615 

616 Examples 

617 -------- 

618 >>> a = -4.49864756e-03 

619 >>> b = -5.26046353e-03 

620 >>> chroma_correlate(a, b) # doctest: +ELLIPSIS 

621 0.0086506... 

622 """ 

623 

624 a = as_float_array(a) 

625 b = as_float_array(b) 

626 

627 c = spow(a**2 + b**2, 0.5) 

628 Ch_L = 25 * np.log1p(0.05 * c) 

629 

630 return as_float(Ch_L) 

631 

632 

633def colourfulness_correlate( 

634 L: ArrayLike, 

635 L_L: ArrayLike, 

636 Ch_L: ArrayLike, 

637 F_C: ArrayLike, 

638) -> NDArrayFloat: 

639 """ 

640 Compute the correlate of *colourfulness* :math:`C_L`. 

641 

642 Parameters 

643 ---------- 

644 L 

645 Absolute luminance :math:`L` of the reference white in 

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

647 L_L 

648 Correlate of *Lightness* :math:`L_L`. 

649 Ch_L 

650 Correlate of *chroma* :math:`Ch_L`. 

651 F_C 

652 Chroma induction factor :math:`F_C`. 

653 

654 Returns 

655 ------- 

656 :class:`numpy.ndarray` 

657 Correlate of *colourfulness* :math:`C_L`. 

658 

659 Examples 

660 -------- 

661 >>> L = 318.31 

662 >>> L_L = 37.368047493928195 

663 >>> Ch_L = 0.008650662051714 

664 >>> F_C = 1.0 

665 >>> colourfulness_correlate(L, L_L, Ch_L, F_C) # doctest: +ELLIPSIS 

666 0.0183832... 

667 """ 

668 

669 L = as_float_array(L) 

670 L_L = as_float_array(L_L) 

671 Ch_L = as_float_array(Ch_L) 

672 F_C = as_float_array(F_C) 

673 

674 S_C = 1 + 0.47 * np.log10(L) - 0.057 * np.log10(L) ** 2 

675 S_M = 0.7 + 0.02 * L_L - 0.0002 * L_L**2 

676 C_L = Ch_L * S_M * S_C * F_C 

677 

678 return as_float(C_L) 

679 

680 

681def saturation_correlate(Ch_L: ArrayLike, L_L: ArrayLike) -> NDArrayFloat: 

682 """ 

683 Compute the correlate of *saturation* :math:`S_L`. 

684 

685 Parameters 

686 ---------- 

687 Ch_L 

688 Correlate of *chroma* :math:`Ch_L`. 

689 L_L 

690 Correlate of *lightness* :math:`L_L`. 

691 

692 Returns 

693 ------- 

694 :class:`numpy.ndarray` 

695 Correlate of *saturation* :math:`S_L`. 

696 

697 Examples 

698 -------- 

699 >>> Ch_L = 0.008650662051714 

700 >>> L_L = 37.368047493928195 

701 >>> saturation_correlate(Ch_L, L_L) # doctest: +ELLIPSIS 

702 0.0002314... 

703 """ 

704 

705 Ch_L = as_float_array(Ch_L) 

706 L_L = as_float_array(L_L) 

707 

708 return Ch_L / L_L 

709 

710 

711def final_opponent_signals(C_L: ArrayLike, h_L: ArrayLike) -> NDArrayFloat: 

712 """ 

713 Compute the final opponent signals :math:`A_L` and :math:`B_L`. 

714 

715 Parameters 

716 ---------- 

717 C_L 

718 Correlate of *colourfulness* :math:`C_L`. 

719 h_L 

720 Correlate of *hue* :math:`h_L` in degrees. 

721 

722 Returns 

723 ------- 

724 :class:`numpy.ndarray` 

725 Final opponent signals :math:`A_L` and :math:`B_L`. 

726 

727 Examples 

728 -------- 

729 >>> C_L = 0.0183832899143 

730 >>> h_L = 229.46357270858391 

731 >>> final_opponent_signals(C_L, h_L) # doctest: +ELLIPSIS 

732 array([-0.0119478..., -0.0139711...]) 

733 """ 

734 

735 return polar_to_cartesian(tstack([as_float_array(C_L), np.radians(h_L)]))