Coverage for colour/appearance/llab.py: 100%
123 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2:math:`LLAB(l:c)` Colour Appearance Model
3=========================================
5Define the *:math:`LLAB(l:c)`* colour appearance model for predicting
6perceptual colour attributes under varying viewing conditions.
8- :class:`colour.appearance.InductionFactors_LLAB`
9- :attr:`colour.VIEWING_CONDITIONS_LLAB`
10- :class:`colour.CAM_Specification_LLAB`
11- :func:`colour.XYZ_to_LLAB`
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"""
29from __future__ import annotations
31from dataclasses import dataclass, field
33import numpy as np
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)
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"
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]
76@dataclass(frozen=True)
77class InductionFactors_LLAB(MixinDataclassIterable):
78 """
79 Define the *:math:`LLAB(l:c)`* colour appearance model induction factors.
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`.
92 References
93 ----------
94 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c`
95 """
97 D: float
98 F_S: float
99 F_L: float
100 F_C: float
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.
124References
125----------
126:cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c`
128Aliases:
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]
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"""
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"""
173@dataclass
174class CAM_ReferenceSpecification_LLAB(MixinDataclassArithmetic):
175 """
176 Define the *:math:`LLAB(l:c)`* colour appearance model reference
177 specification.
179 This specification contains field names consistent with the *Fairchild
180 (2013)* reference.
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`.
201 References
202 ----------
203 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c`
204 """
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)
216@dataclass
217class CAM_Specification_LLAB(MixinDataclassArithmetic):
218 """
219 Define the *:math:`LLAB(l:c)`* colour appearance model specification.
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.
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`.
246 Notes
247 -----
248 - This specification is the one used in the current model implementation.
250 References
251 ----------
252 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c`
253 """
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)
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.
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.
292 Returns
293 -------
294 :class:`colour.CAM_Specification_LLAB`
295 *:math:`LLAB(l:c)`* colour appearance model specification.
297 Notes
298 -----
299 +---------------------+-----------------------+---------------+
300 | **Domain** | **Scale - Reference** | **Scale - 1** |
301 +=====================+=======================+===============+
302 | ``XYZ`` | 100 | 1 |
303 +---------------------+-----------------------+---------------+
304 | ``XYZ_0`` | 100 | 1 |
305 +---------------------+-----------------------+---------------+
307 +---------------------+-----------------------+---------------+
308 | **Range** | **Scale - Reference** | **Scale - 1** |
309 +=====================+=======================+===============+
310 | ``specification.h`` | 360 | 1 |
311 +---------------------+-----------------------+---------------+
313 References
314 ----------
315 :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c`
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 """
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))
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)
337 # Computing chromatic adaptation.
338 XYZ_r = chromatic_adaptation(RGB, RGB_0, RGB_0r, Y, surround.D)
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 )
348 # Computing perceptual correlates.
349 # -------------------------------------------------------------------------
350 # Computing the correlate of *chroma* :math:`Ch_L`.
351 # -------------------------------------------------------------------------
352 Ch_L = chroma_correlate(a, b)
354 # -------------------------------------------------------------------------
355 # Computing the correlate of *colourfulness* :math:`C_L`.
356 # -------------------------------------------------------------------------
357 C_L = colourfulness_correlate(L, L_L, Ch_L, surround.F_C)
359 # -------------------------------------------------------------------------
360 # Computing the correlate of *saturation* :math:`s_L`.
361 # -------------------------------------------------------------------------
362 s_L = saturation_correlate(Ch_L, L_L)
364 # -------------------------------------------------------------------------
365 # Computing the *hue* angle :math:`h_L`.
366 # -------------------------------------------------------------------------
367 h_L = hue_angle(a, b)
368 # TODO: Implement hue composition computation.
370 # -------------------------------------------------------------------------
371 # Computing final opponent signals.
372 # -------------------------------------------------------------------------
373 A_L, B_L = tsplit(final_opponent_signals(C_L, h_L))
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 )
387def XYZ_to_RGB_LLAB(XYZ: ArrayLike) -> NDArrayFloat:
388 """
389 Convert from *CIE XYZ* tristimulus values to normalised cone responses.
391 Parameters
392 ----------
393 XYZ
394 *CIE XYZ* tristimulus values.
396 Returns
397 -------
398 :class:`numpy.ndarray`
399 Normalised cone responses.
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 """
408 XYZ = as_float_array(XYZ)
410 with sdiv_mode():
411 return vecmul(MATRIX_XYZ_TO_RGB_LLAB, sdiv(XYZ, XYZ[..., 1, None]))
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.
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.
440 Returns
441 -------
442 :class:`numpy.ndarray`
443 Adapted *CIE XYZ* tristimulus values.
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 """
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)
461 beta = spow(B_0 / B_0r, 0.0834)
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)
467 RGB_r = tstack([R_r, G_r, B_r])
469 Y = tstack([Y, Y, Y])
471 return vecmul(MATRIX_RGB_TO_XYZ_LLAB, RGB_r * Y)
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.
480 Parameters
481 ----------
482 x
483 Visual response variable :math:`x`.
484 F_S
485 Surround induction factor :math:`F_S`.
487 Returns
488 -------
489 :class:`numpy.ndarray`
490 Modeled visual response variable :math:`x`.
492 Examples
493 --------
494 >>> x = np.array([0.23350512, 0.23351103, 0.23355179])
495 >>> f(0.200009186234000, 3) # doctest: +ELLIPSIS
496 0.5848125...
497 """
499 x = as_float_array(x)
500 F_S = as_float_array(F_S)
502 one_F_s = 1 / F_S
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 )
510 return as_float(x_m)
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.
523 The opponent colour dimensions are based on a modified *CIE L\*a\*b\**
524 colourspace formulae.
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`.
537 Returns
538 -------
539 :class:`numpy.ndarray`
540 Opponent colour dimensions.
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 """
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)
557 # Account for background lightness contrast.
558 z = 1 + F_L * spow(Y_b / 100, 0.5)
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))
565 return tstack([L, a, b])
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.
573 Parameters
574 ----------
575 a
576 Opponent colour dimension :math:`a`.
577 b
578 Opponent colour dimension :math:`b`.
580 Returns
581 -------
582 :class:`numpy.ndarray`
583 *Hue* angle :math:`h_L` in degrees.
585 Examples
586 --------
587 >>> hue_angle(-4.49864756e-03, -5.26046353e-03) # doctest: +ELLIPSIS
588 229.4635727...
589 """
591 a = as_float_array(a)
592 b = as_float_array(b)
594 h_L = np.degrees(np.arctan2(b, a)) % 360
596 return as_float(h_L)
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.
604 Parameters
605 ----------
606 a
607 Opponent colour dimension :math:`a`.
608 b
609 Opponent colour dimension :math:`b`.
611 Returns
612 -------
613 :class:`numpy.ndarray`
614 Correlate of *chroma* :math:`Ch_L`.
616 Examples
617 --------
618 >>> a = -4.49864756e-03
619 >>> b = -5.26046353e-03
620 >>> chroma_correlate(a, b) # doctest: +ELLIPSIS
621 0.0086506...
622 """
624 a = as_float_array(a)
625 b = as_float_array(b)
627 c = spow(a**2 + b**2, 0.5)
628 Ch_L = 25 * np.log1p(0.05 * c)
630 return as_float(Ch_L)
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`.
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`.
654 Returns
655 -------
656 :class:`numpy.ndarray`
657 Correlate of *colourfulness* :math:`C_L`.
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 """
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)
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
678 return as_float(C_L)
681def saturation_correlate(Ch_L: ArrayLike, L_L: ArrayLike) -> NDArrayFloat:
682 """
683 Compute the correlate of *saturation* :math:`S_L`.
685 Parameters
686 ----------
687 Ch_L
688 Correlate of *chroma* :math:`Ch_L`.
689 L_L
690 Correlate of *lightness* :math:`L_L`.
692 Returns
693 -------
694 :class:`numpy.ndarray`
695 Correlate of *saturation* :math:`S_L`.
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 """
705 Ch_L = as_float_array(Ch_L)
706 L_L = as_float_array(L_L)
708 return Ch_L / L_L
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`.
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.
722 Returns
723 -------
724 :class:`numpy.ndarray`
725 Final opponent signals :math:`A_L` and :math:`B_L`.
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 """
735 return polar_to_cartesian(tstack([as_float_array(C_L), np.radians(h_L)]))