Coverage for appearance/llab.py: 51%

1"""

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

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

4

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

6 perceptual 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

13 References

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/\

25 236348295_Two_Unsolved_Issues_in_Colour_Management__\

26 Colour_Appearance_and_Gamut_Mapping

27"""

28

29 from __future__ import annotations

30

31 from dataclasses import dataclass, field

32

33 import numpy as np

34

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

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

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

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

103 VIEWING_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)

120 VIEWING_CONDITIONS_LLAB.__doc__ = """

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

122 conditions.

123

124 References

125----------

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

127

128 Aliases:

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"""

138 VIEWING_CONDITIONS_LLAB["ref_average_4_plus"] = VIEWING_CONDITIONS_LLAB[

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

140]

141 VIEWING_CONDITIONS_LLAB["ref_average_4_minus"] = VIEWING_CONDITIONS_LLAB[

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

143]

144 VIEWING_CONDITIONS_LLAB["tv_dim"] = VIEWING_CONDITIONS_LLAB[

145 "Television & VDU Displays, Dim Surround"

146]

147 VIEWING_CONDITIONS_LLAB["sheet_dim"] = VIEWING_CONDITIONS_LLAB[

148 "Cut Sheet Transparency, Dim Surround"

149]

150 VIEWING_CONDITIONS_LLAB["projected_dark"] = VIEWING_CONDITIONS_LLAB[

151 "35mm Projection Transparency, Dark Surround"

152]

153

154 MATRIX_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"""

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

163 cone responses matrix.

164"""

165

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

167"""

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

169 tristimulus values matrix.

170"""

171

172

173@dataclass

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

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

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

326 s=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

387 def 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

414 def 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

474 def 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

513 def 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

568 def 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

599 def 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

633 def 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

681 def 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

711 def 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)]))