Coverage for colour/appearance/nayatani95.py: 93%
183 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"""
2Nayatani (1995) Colour Appearance Model
3=======================================
5Define the *Nayatani (1995)* colour appearance model for predicting
6perceptual colour attributes under varying viewing conditions.
8- :class:`colour.CAM_Specification_Nayatani95`
9- :func:`colour.XYZ_to_Nayatani95`
11References
12----------
13- :cite:`Fairchild2013ba` : Fairchild, M. D. (2013). The Nayatani et al.
14 Model. In Color Appearance Models (3rd ed., pp. 4810-5085). Wiley.
15 ISBN:B00DAYO8E2
16- :cite:`Nayatani1995a` : Nayatani, Y., Sobagaki, H., & Yano, K. H. T.
17 (1995). Lightness dependency of chroma scales of a nonlinear
18 color-appearance model and its latest formulation. Color Research &
19 Application, 20(3), 156-167. doi:10.1002/col.5080200305
20"""
22from __future__ import annotations
24import typing
25from dataclasses import dataclass, field
27import numpy as np
29from colour.adaptation.cie1994 import (
30 MATRIX_XYZ_TO_RGB_CIE1994,
31 beta_1,
32 exponential_factors,
33 intermediate_values,
34)
35from colour.algebra import spow, vecmul
37if typing.TYPE_CHECKING:
38 from colour.hints import ArrayLike, Domain100
40from colour.hints import Annotated, NDArrayFloat, cast
41from colour.models import XYZ_to_xy
42from colour.utilities import (
43 MixinDataclassArithmetic,
44 as_float,
45 as_float_array,
46 from_range_degrees,
47 to_domain_100,
48 tsplit,
49 tstack,
50)
52__author__ = "Colour Developers"
53__copyright__ = "Copyright 2013 Colour Developers"
54__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
55__maintainer__ = "Colour Developers"
56__email__ = "colour-developers@colour-science.org"
57__status__ = "Production"
59__all__ = [
60 "MATRIX_XYZ_TO_RGB_NAYATANI95",
61 "CAM_ReferenceSpecification_Nayatani95",
62 "CAM_Specification_Nayatani95",
63 "XYZ_to_Nayatani95",
64 "illuminance_to_luminance",
65 "XYZ_to_RGB_Nayatani95",
66 "scaling_coefficient",
67 "achromatic_response",
68 "tritanopic_response",
69 "protanopic_response",
70 "brightness_correlate",
71 "ideal_white_brightness_correlate",
72 "achromatic_lightness_correlate",
73 "normalised_achromatic_lightness_correlate",
74 "hue_angle",
75 "saturation_components",
76 "saturation_correlate",
77 "chroma_components",
78 "chroma_correlate",
79 "colourfulness_components",
80 "colourfulness_correlate",
81 "chromatic_strength_function",
82]
84MATRIX_XYZ_TO_RGB_NAYATANI95: NDArrayFloat = MATRIX_XYZ_TO_RGB_CIE1994
85"""
86*Nayatani (1995)* colour appearance model *CIE XYZ* tristimulus values to cone
87responses matrix.
88"""
91@dataclass
92class CAM_ReferenceSpecification_Nayatani95(MixinDataclassArithmetic):
93 """
94 Define the *Nayatani (1995)* colour appearance model reference
95 specification.
97 This specification contains field names consistent with the *Fairchild
98 (2013)* reference.
100 Parameters
101 ----------
102 L_star_P
103 Correlate of *achromatic lightness* :math:`L_p^\\star`.
104 C
105 Correlate of *chroma* :math:`C`.
106 theta
107 *Hue* angle :math:`\\theta` in degrees.
108 S
109 Correlate of *saturation* :math:`S`.
110 B_r
111 Correlate of *brightness* :math:`B_r`.
112 M
113 Correlate of *colourfulness* :math:`M`.
114 H
115 *Hue* :math:`h` quadrature :math:`H`.
116 H_C
117 *Hue* :math:`h` composition :math:`H_C`.
118 L_star_N
119 Correlate of *normalised achromatic lightness* :math:`L_n^\\star`.
121 References
122 ----------
123 :cite:`Fairchild2013ba`, :cite:`Nayatani1995a`
124 """
126 L_star_P: float | NDArrayFloat | None = field(default_factory=lambda: None)
127 C: float | NDArrayFloat | None = field(default_factory=lambda: None)
128 theta: float | NDArrayFloat | None = field(default_factory=lambda: None)
129 S: float | NDArrayFloat | None = field(default_factory=lambda: None)
130 B_r: float | NDArrayFloat | None = field(default_factory=lambda: None)
131 M: float | NDArrayFloat | None = field(default_factory=lambda: None)
132 H: float | NDArrayFloat | None = field(default_factory=lambda: None)
133 H_C: float | NDArrayFloat | None = field(default_factory=lambda: None)
134 L_star_N: float | NDArrayFloat | None = field(default_factory=lambda: None)
137@dataclass
138class CAM_Specification_Nayatani95(MixinDataclassArithmetic):
139 """
140 Define the *Nayatani (1995)* colour appearance model specification.
142 This specification provides a standardized interface for the
143 *Nayatani (1995)* model with field names consistent across all colour
144 appearance models in :mod:`colour.appearance`. While the field names differ
145 from the original *Fairchild (2013)* reference notation, they map directly
146 to the model's perceptual correlates.
148 Parameters
149 ----------
150 L_star_P
151 Correlate of *achromatic lightness* :math:`L_p^\\star`.
152 C
153 Correlate of *chroma* :math:`C`.
154 h
155 *Hue* angle :math:`\\theta` in degrees.
156 s
157 Correlate of *saturation* :math:`S`.
158 Q
159 Correlate of *brightness* :math:`B_r`.
160 M
161 Correlate of *colourfulness* :math:`M`.
162 H
163 *Hue* :math:`h` quadrature :math:`H`.
164 HC
165 *Hue* :math:`h` composition :math:`H_C`.
166 L_star_N
167 Correlate of *normalised achromatic lightness* :math:`L_n^\\star`.
169 Notes
170 -----
171 - This specification is the one used in the current model
172 implementation.
174 References
175 ----------
176 :cite:`Fairchild2013ba`, :cite:`Nayatani1995a`
177 """
179 L_star_P: float | NDArrayFloat | None = field(default_factory=lambda: None)
180 C: float | NDArrayFloat | None = field(default_factory=lambda: None)
181 h: float | NDArrayFloat | None = field(default_factory=lambda: None)
182 s: float | NDArrayFloat | None = field(default_factory=lambda: None)
183 Q: float | NDArrayFloat | None = field(default_factory=lambda: None)
184 M: float | NDArrayFloat | None = field(default_factory=lambda: None)
185 H: float | NDArrayFloat | None = field(default_factory=lambda: None)
186 HC: float | NDArrayFloat | None = field(default_factory=lambda: None)
187 L_star_N: float | NDArrayFloat | None = field(default_factory=lambda: None)
190def XYZ_to_Nayatani95(
191 XYZ: Domain100,
192 XYZ_n: Domain100,
193 Y_o: ArrayLike,
194 E_o: ArrayLike,
195 E_or: ArrayLike,
196 n: ArrayLike = 1,
197) -> Annotated[CAM_Specification_Nayatani95, 360]:
198 """
199 Compute the *Nayatani (1995)* colour appearance model correlates from the
200 specified *CIE XYZ* tristimulus values.
202 Parameters
203 ----------
204 XYZ
205 *CIE XYZ* tristimulus values of test sample / stimulus.
206 XYZ_n
207 *CIE XYZ* tristimulus values of reference white.
208 Y_o
209 Luminance factor :math:`Y_o` of achromatic background as percentage
210 normalised to domain [0.18, 1.0] in **'Reference'** domain-range
211 scale.
212 E_o
213 Illuminance :math:`E_o` of the viewing field in lux.
214 E_or
215 Normalising illuminance :math:`E_{or}` in lux usually normalised to
216 domain [1000, 3000].
217 n
218 Noise term used in the non-linear chromatic adaptation model.
220 Returns
221 -------
222 :class:`colour.CAM_Specification_Nayatani95`
223 *Nayatani (1995)* colour appearance model specification.
225 Notes
226 -----
227 +---------------------+-----------------------+---------------+
228 | **Domain** | **Scale - Reference** | **Scale - 1** |
229 +=====================+=======================+===============+
230 | ``XYZ`` | 100 | 1 |
231 +---------------------+-----------------------+---------------+
232 | ``XYZ_n`` | 100 | 1 |
233 +---------------------+-----------------------+---------------+
235 +---------------------+-----------------------+---------------+
236 | **Range** | **Scale - Reference** | **Scale - 1** |
237 +=====================+=======================+===============+
238 | ``specification.h`` | 360 | 1 |
239 +---------------------+-----------------------+---------------+
241 References
242 ----------
243 :cite:`Fairchild2013ba`, :cite:`Nayatani1995a`
245 Examples
246 --------
247 >>> XYZ = np.array([19.01, 20.00, 21.78])
248 >>> XYZ_n = np.array([95.05, 100.00, 108.88])
249 >>> Y_o = 20.0
250 >>> E_o = 5000.0
251 >>> E_or = 1000.0
252 >>> XYZ_to_Nayatani95(XYZ, XYZ_n, Y_o, E_o, E_or) # doctest: +ELLIPSIS
253 CAM_Specification_Nayatani95(L_star_P=49.9998829..., C=0.0133550..., \
254h=257.5232268..., s=0.0133550..., Q=62.6266734..., M=0.0167262..., \
255H=None, HC=None, L_star_N=50.0039154...)
256 """
258 XYZ = to_domain_100(XYZ)
259 XYZ_n = to_domain_100(XYZ_n)
260 Y_o = as_float_array(Y_o)
261 E_o = as_float_array(E_o)
262 E_or = as_float_array(E_or)
264 # Computing adapting luminance :math:`L_o` and normalising luminance
265 # :math:`L_{or}` in in :math:`cd/m^2`.
266 # L_o = illuminance_to_luminance(E_o, Y_o)
267 L_or = illuminance_to_luminance(E_or, Y_o)
269 # Computing :math:`\\xi` :math:`\\eta`, :math:`\\zeta` values.
270 xez = intermediate_values(XYZ_to_xy(XYZ_n / 100))
271 xi, eta, _zeta = tsplit(xez)
273 # Computing adapting field cone responses.
274 RGB_o = ((Y_o[..., None] * E_o[..., None]) / (100 * np.pi)) * xez
276 # Computing stimulus cone responses.
277 RGB = XYZ_to_RGB_Nayatani95(XYZ)
278 R, G, _B = tsplit(RGB)
280 # Computing exponential factors of the chromatic adaptation.
281 bRGB_o = exponential_factors(RGB_o)
282 bL_or = beta_1(L_or)
284 # Computing scaling coefficients :math:`e(R)` and :math:`e(G)`
285 eR = scaling_coefficient(R, xi)
286 eG = scaling_coefficient(G, eta)
288 # Computing opponent colour dimensions.
289 # Computing achromatic response :math:`Q`:
290 Q_response = achromatic_response(RGB, bRGB_o, xez, bL_or, eR, eG, n)
292 # Computing tritanopic response :math:`t`:
293 t_response = tritanopic_response(RGB, bRGB_o, xez, n)
295 # Computing protanopic response :math:`p`:
296 p_response = protanopic_response(RGB, bRGB_o, xez, n)
298 # Computing the correlate of *brightness* :math:`B_r`.
299 B_r = brightness_correlate(bRGB_o, bL_or, Q_response)
301 # Computing *brightness* :math:`B_{rw}` of ideal white.
302 brightness_ideal_white = ideal_white_brightness_correlate(bRGB_o, xez, bL_or, n)
304 # Computing the correlate of achromatic *Lightness* :math:`L_p^\\star`.
305 L_star_P = achromatic_lightness_correlate(Q_response)
307 # Computing the correlate of normalised achromatic *Lightness*
308 # :math:`L_n^\\star`.
309 L_star_N = normalised_achromatic_lightness_correlate(B_r, brightness_ideal_white)
311 # Computing the *hue* angle :math:`\\theta`.
312 theta = hue_angle(p_response, t_response)
313 # TODO: Implement hue quadrature & composition computation.
315 # Computing the correlate of *saturation* :math:`S`.
316 S_RG, S_YB = tsplit(saturation_components(theta, bL_or, t_response, p_response))
317 S = saturation_correlate(S_RG, S_YB)
319 # Computing the correlate of *chroma* :math:`C`.
320 # C_RG, C_YB = tsplit(chroma_components(L_star_P, S_RG, S_YB))
321 C = chroma_correlate(L_star_P, S)
323 # Computing the correlate of *colourfulness* :math:`M`.
324 # TODO: Investigate components usage.
325 # M_RG, M_YB = tsplit(colourfulness_components(C_RG, C_YB,
326 # brightness_ideal_white))
327 M = colourfulness_correlate(C, brightness_ideal_white)
329 return CAM_Specification_Nayatani95(
330 L_star_P=L_star_P,
331 C=C,
332 h=as_float(from_range_degrees(theta)),
333 s=S,
334 Q=B_r,
335 M=M,
336 H=None,
337 HC=None,
338 L_star_N=L_star_N,
339 )
342def illuminance_to_luminance(E: ArrayLike, Y_f: ArrayLike) -> NDArrayFloat:
343 """
344 Convert the specified *illuminance* :math:`E` value in lux to *luminance*
345 :math:`Y` in :math:`cd/m^2`.
347 Parameters
348 ----------
349 E
350 *Illuminance* :math:`E` in lux.
351 Y_f
352 *Luminance* factor :math:`Y_f` in :math:`cd/m^2`.
354 Returns
355 -------
356 :class:`numpy.ndarray`
357 *Luminance* :math:`Y` in :math:`cd/m^2`.
359 Examples
360 --------
361 >>> illuminance_to_luminance(5000.0, 20.0) # doctest: +ELLIPSIS
362 318.3098861...
363 """
365 E = as_float_array(E)
366 Y_f = as_float_array(Y_f)
368 return Y_f * E / (100 * np.pi)
371def XYZ_to_RGB_Nayatani95(XYZ: ArrayLike) -> NDArrayFloat:
372 """
373 Convert *CIE XYZ* tristimulus values to cone responses.
375 Parameters
376 ----------
377 XYZ
378 *CIE XYZ* tristimulus values.
380 Returns
381 -------
382 :class:`numpy.ndarray`
383 Cone responses.
385 Examples
386 --------
387 >>> XYZ = np.array([19.01, 20.00, 21.78])
388 >>> XYZ_to_RGB_Nayatani95(XYZ) # doctest: +ELLIPSIS
389 array([ 20.0005206..., 19.999783 ..., 19.9988316...])
390 """
392 return vecmul(MATRIX_XYZ_TO_RGB_NAYATANI95, XYZ)
395def scaling_coefficient(x: ArrayLike, y: ArrayLike) -> NDArrayFloat:
396 """
397 Compute the scaling coefficient :math:`e(R)` or :math:`e(G)`.
399 Parameters
400 ----------
401 x
402 Cone response.
403 y
404 Intermediate value.
406 Returns
407 -------
408 :class:`numpy.ndarray`
409 Scaling coefficient :math:`e(R)` or :math:`e(G)`.
411 Examples
412 --------
413 >>> x = 20.000520600000002
414 >>> y = 1.000042192
415 >>> scaling_coefficient(x, y)
416 1.0
417 """
419 x = as_float_array(x)
420 y = as_float_array(y)
422 return as_float(np.where(x >= (20 * y), 1.758, 1))
425def achromatic_response(
426 RGB: ArrayLike,
427 bRGB_o: ArrayLike,
428 xez: ArrayLike,
429 bL_or: ArrayLike,
430 eR: ArrayLike,
431 eG: ArrayLike,
432 n: ArrayLike = 1,
433) -> NDArrayFloat:
434 """
435 Compute the achromatic response :math:`Q` from the specified stimulus
436 cone responses.
438 Parameters
439 ----------
440 RGB
441 Stimulus cone responses.
442 bRGB_o
443 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,
444 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.
445 xez
446 Intermediate values :math:`\\xi`, :math:`\\eta`, :math:`\\zeta`.
447 bL_or
448 Normalising chromatic adaptation exponential factor
449 :math:`\\beta_1(B_{or})`.
450 eR
451 Scaling coefficient :math:`e(R)`.
452 eG
453 Scaling coefficient :math:`e(G)`.
454 n
455 Noise term used in the non-linear chromatic adaptation model.
457 Returns
458 -------
459 :class:`numpy.ndarray`
460 Achromatic response :math:`Q`.
462 Examples
463 --------
464 >>> RGB = np.array([20.00052060, 19.99978300, 19.99883160])
465 >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986])
466 >>> xez = np.array([1.00004219, 0.99998001, 0.99975794])
467 >>> bL_or = 3.681021495604089
468 >>> eR = 1.0
469 >>> eG = 1.758
470 >>> n = 1.0
471 >>> achromatic_response(RGB, bRGB_o, xez, bL_or, eR, eG, n)
472 ... # doctest: +ELLIPSIS
473 -0.0001169...
474 """
476 R, G, _B = tsplit(RGB)
477 bR_o, bG_o, _bB_o = tsplit(bRGB_o)
478 xi, eta, _zeta = tsplit(xez)
479 bL_or = as_float_array(bL_or)
480 eR = as_float_array(eR)
481 eG = as_float_array(eG)
483 Q = (2 / 3) * bR_o * eR * np.log10((R + n) / (20 * xi + n))
484 Q += (1 / 3) * bG_o * eG * np.log10((G + n) / (20 * eta + n))
485 Q *= 41.69 / bL_or
487 return as_float(Q)
490def tritanopic_response(
491 RGB: ArrayLike, bRGB_o: ArrayLike, xez: ArrayLike, n: ArrayLike
492) -> NDArrayFloat:
493 """
494 Compute the tritanopic response :math:`t` from the specified stimulus cone
495 responses.
497 Parameters
498 ----------
499 RGB
500 Stimulus cone responses.
501 bRGB_o
502 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,
503 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.
504 xez
505 Intermediate values :math:`\\xi`, :math:`\\eta`, :math:`\\zeta`.
506 n
507 Noise term used in the non-linear chromatic adaptation model.
509 Returns
510 -------
511 :class:`numpy.ndarray`
512 Tritanopic response :math:`t`.
514 Examples
515 --------
516 >>> RGB = np.array([20.00052060, 19.99978300, 19.99883160])
517 >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986])
518 >>> xez = np.array([1.00004219, 0.99998001, 0.99975794])
519 >>> n = 1.0
520 >>> tritanopic_response(RGB, bRGB_o, xez, n) # doctest: +ELLIPSIS
521 -1.7703650...e-05
522 """
524 R, G, B = tsplit(RGB)
525 bR_o, bG_o, bB_o = tsplit(bRGB_o)
526 xi, eta, zeta = tsplit(xez)
528 t = bR_o * np.log10((R + n) / (20 * xi + n))
529 t += -(12 / 11) * bG_o * np.log10((G + n) / (20 * eta + n))
530 t += (1 / 11) * bB_o * np.log10((B + n) / (20 * zeta + n))
532 return as_float(t)
535def protanopic_response(
536 RGB: ArrayLike, bRGB_o: ArrayLike, xez: ArrayLike, n: ArrayLike
537) -> NDArrayFloat:
538 """
539 Compute the protanopic response :math:`p` from the specified stimulus cone
540 responses.
542 Parameters
543 ----------
544 RGB
545 Stimulus cone responses.
546 bRGB_o
547 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,
548 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.
549 xez
550 Intermediate values :math:`\\xi`, :math:`\\eta`, :math:`\\zeta`.
551 n
552 Noise term used in the non-linear chromatic adaptation model.
554 Returns
555 -------
556 :class:`numpy.ndarray`
557 Protanopic response :math:`p`.
559 Examples
560 --------
561 >>> RGB = np.array([20.00052060, 19.99978300, 19.99883160])
562 >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986])
563 >>> xez = np.array([1.00004219, 0.99998001, 0.99975794])
564 >>> n = 1.0
565 >>> protanopic_response(RGB, bRGB_o, xez, n) # doctest: +ELLIPSIS
566 -8.0021426...e-05
567 """
569 R, G, B = tsplit(RGB)
570 bR_o, bG_o, bB_o = tsplit(bRGB_o)
571 xi, eta, zeta = tsplit(xez)
573 p = (1 / 9) * bR_o * np.log10((R + n) / (20 * xi + n))
574 p += (1 / 9) * bG_o * np.log10((G + n) / (20 * eta + n))
575 p += -(2 / 9) * bB_o * np.log10((B + n) / (20 * zeta + n))
577 return as_float(p)
580def brightness_correlate(
581 bRGB_o: ArrayLike, bL_or: ArrayLike, Q: ArrayLike
582) -> NDArrayFloat:
583 """
584 Compute the *brightness* correlate :math:`B_r`.
586 Parameters
587 ----------
588 bRGB_o
589 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,
590 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.
591 bL_or
592 Normalising chromatic adaptation exponential factor
593 :math:`\\beta_1(B_{or})`.
594 Q
595 Achromatic response :math:`Q`.
597 Returns
598 -------
599 :class:`numpy.ndarray`
600 *Brightness* correlate :math:`B_r`.
602 Examples
603 --------
604 >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986])
605 >>> bL_or = 3.681021495604089
606 >>> Q = -0.000117024294955
607 >>> brightness_correlate(bRGB_o, bL_or, Q) # doctest: +ELLIPSIS
608 62.6266734...
609 """
611 bR_o, bG_o, _bB_o = tsplit(bRGB_o)
612 bL_or = as_float_array(bL_or)
613 Q = as_float_array(Q)
615 B_r = (50 / bL_or) * ((2 / 3) * bR_o + (1 / 3) * bG_o) + Q
617 return as_float(B_r)
620def ideal_white_brightness_correlate(
621 bRGB_o: ArrayLike,
622 xez: ArrayLike,
623 bL_or: ArrayLike,
624 n: ArrayLike,
625) -> NDArrayFloat:
626 """
627 Compute the ideal white *brightness* correlate :math:`B_{rw}`.
629 Parameters
630 ----------
631 bRGB_o
632 Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`,
633 :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`.
634 xez
635 Intermediate values :math:`\\xi`, :math:`\\eta`, :math:`\\zeta`.
636 bL_or
637 Normalising chromatic adaptation exponential factor
638 :math:`\\beta_1(B_{or})`.
639 n
640 Noise term used in the non-linear chromatic adaptation model.
642 Returns
643 -------
644 :class:`numpy.ndarray`
645 Ideal white *brightness* correlate :math:`B_{rw}`.
647 Examples
648 --------
649 >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986])
650 >>> xez = np.array([1.00004219, 0.99998001, 0.99975794])
651 >>> bL_or = 3.681021495604089
652 >>> n = 1.0
653 >>> ideal_white_brightness_correlate(bRGB_o, xez, bL_or, n)
654 ... # doctest: +ELLIPSIS
655 125.2435392...
656 """
658 bR_o, bG_o, _bB_o = tsplit(bRGB_o)
659 xi, eta, _zeta = tsplit(xez)
660 bL_or = as_float_array(bL_or)
662 B_rw = (2 / 3) * bR_o * 1.758 * np.log10((100 * xi + n) / (20 * xi + n))
663 B_rw += (1 / 3) * bG_o * 1.758 * np.log10((100 * eta + n) / (20 * eta + n))
664 B_rw *= 41.69 / bL_or
665 B_rw += (50 / bL_or) * (2 / 3) * bR_o
666 B_rw += (50 / bL_or) * (1 / 3) * bG_o
668 return as_float(B_rw)
671def achromatic_lightness_correlate(
672 Q: ArrayLike,
673) -> NDArrayFloat:
674 """
675 Compute the *achromatic lightness* correlate :math:`L_p^\\star`.
677 Parameters
678 ----------
679 Q
680 Achromatic response :math:`Q`.
682 Returns
683 -------
684 :class:`numpy.ndarray`
685 *Achromatic lightness* correlate :math:`L_p^\\star`.
687 Examples
688 --------
689 >>> Q = -0.000117024294955
690 >>> achromatic_lightness_correlate(Q) # doctest: +ELLIPSIS
691 49.9998829...
692 """
694 Q = as_float_array(Q)
696 return as_float(Q + 50)
699def normalised_achromatic_lightness_correlate(
700 B_r: ArrayLike, B_rw: ArrayLike
701) -> NDArrayFloat:
702 """
703 Compute the *normalised achromatic lightness* correlate
704 :math:`L_n^\\star`.
706 Parameters
707 ----------
708 B_r
709 *Brightness* correlate :math:`B_r`.
710 B_rw
711 Ideal white *brightness* correlate :math:`B_{rw}`.
713 Returns
714 -------
715 :class:`numpy.ndarray`
716 *Normalised achromatic lightness* correlate :math:`L_n^\\star`.
718 Examples
719 --------
720 >>> B_r = 62.626673467230766
721 >>> B_rw = 125.24353925846037
722 >>> normalised_achromatic_lightness_correlate(B_r, B_rw)
723 ... # doctest: +ELLIPSIS
724 50.0039154...
725 """
727 B_r = as_float_array(B_r)
728 B_rw = as_float_array(B_rw)
730 return as_float(100 * B_r / B_rw)
733def hue_angle(p: ArrayLike, t: ArrayLike) -> NDArrayFloat:
734 """
735 Compute the *hue* angle :math:`h` in degrees from the specified
736 protanopic and tritanopic responses.
738 Parameters
739 ----------
740 p
741 Protanopic response :math:`p`.
742 t
743 Tritanopic response :math:`t`.
745 Returns
746 -------
747 :class:`numpy.ndarray`
748 *Hue* angle :math:`h` in degrees.
750 Examples
751 --------
752 >>> p = -8.002142682085493e-05
753 >>> t = -0.000017703650669
754 >>> hue_angle(p, t) # doctest: +ELLIPSIS
755 257.5250300...
756 """
758 p = as_float_array(p)
759 t = as_float_array(t)
761 h_L = np.degrees(np.arctan2(p, t)) % 360
763 return as_float(h_L)
766def chromatic_strength_function(
767 theta: ArrayLike,
768) -> NDArrayFloat:
769 """
770 Define the chromatic strength function :math:`E_s(\\theta)` used to
771 correct saturation scale as a function of hue angle :math:`\\theta` in
772 degrees.
774 Parameters
775 ----------
776 theta
777 Hue angle :math:`\\theta` in degrees.
779 Returns
780 -------
781 :class:`numpy.ndarray`
782 Corrected saturation scale.
784 Examples
785 --------
786 >>> h = 257.52322689806243
787 >>> chromatic_strength_function(h) # doctest: +ELLIPSIS
788 1.2267869...
789 """
791 theta = np.radians(theta)
793 E_s = cast("NDArrayFloat", 0.9394)
794 E_s += -0.2478 * np.sin(1 * theta)
795 E_s += -0.0743 * np.sin(2 * theta)
796 E_s += +0.0666 * np.sin(3 * theta)
797 E_s += -0.0186 * np.sin(4 * theta)
798 E_s += -0.0055 * np.cos(1 * theta)
799 E_s += -0.0521 * np.cos(2 * theta)
800 E_s += -0.0573 * np.cos(3 * theta)
801 E_s += -0.0061 * np.cos(4 * theta)
803 return as_float(E_s)
806def saturation_components(
807 h: ArrayLike,
808 bL_or: ArrayLike,
809 t: ArrayLike,
810 p: ArrayLike,
811) -> NDArrayFloat:
812 """
813 Compute the *saturation* components :math:`S_{RG}` and :math:`S_{YB}`.
815 Parameters
816 ----------
817 h
818 Correlate of *hue* :math:`h` in degrees.
819 bL_or
820 Normalising chromatic adaptation exponential factor
821 :math:`\\beta_1(B_or)`.
822 t
823 Tritanopic response :math:`t`.
824 p
825 Protanopic response :math:`p`.
827 Returns
828 -------
829 :class:`numpy.ndarray`
830 *Saturation* components :math:`S_{RG}` and :math:`S_{YB}`.
832 Examples
833 --------
834 >>> h = 257.52322689806243
835 >>> bL_or = 3.681021495604089
836 >>> t = -0.000017706764677
837 >>> p = -0.000080023561356
838 >>> saturation_components(h, bL_or, t, p) # doctest: +ELLIPSIS
839 array([-0.0028852..., -0.0130396...])
840 """
842 h = as_float_array(h)
843 bL_or = as_float_array(bL_or)
844 t = as_float_array(t)
845 p = as_float_array(p)
847 E_s = chromatic_strength_function(h)
848 S_RG = 488.93 / bL_or * E_s * t
849 S_YB = 488.93 / bL_or * E_s * p
851 return tstack([S_RG, S_YB])
854def saturation_correlate(S_RG: ArrayLike, S_YB: ArrayLike) -> NDArrayFloat:
855 """
856 Compute the correlate of *saturation* :math:`S`.
858 Parameters
859 ----------
860 S_RG
861 *Saturation* component :math:`S_{RG}`.
862 S_YB
863 *Saturation* component :math:`S_{YB}`.
865 Returns
866 -------
867 :class:`numpy.ndarray`
868 Correlate of *saturation* :math:`S`.
870 Examples
871 --------
872 >>> S_RG = -0.002885271638197
873 >>> S_YB = -0.013039632941332
874 >>> saturation_correlate(S_RG, S_YB) # doctest: +ELLIPSIS
875 0.0133550...
876 """
878 S_RG = as_float_array(S_RG)
879 S_YB = as_float_array(S_YB)
881 S = np.hypot(S_RG, S_YB)
883 return as_float(S)
886def chroma_components(
887 L_star_P: ArrayLike,
888 S_RG: ArrayLike,
889 S_YB: ArrayLike,
890) -> NDArrayFloat:
891 """
892 Compute the *chroma* components :math:`C_{RG}` and :math:`C_{YB}`.
894 Parameters
895 ----------
896 L_star_P
897 *Achromatic lightness* correlate :math:`L_p^\\star`.
898 S_RG
899 *Saturation* component :math:`S_{RG}`.
900 S_YB
901 *Saturation* component :math:`S_{YB}`.
903 Returns
904 -------
905 :class:`numpy.ndarray`
906 *Chroma* components :math:`C_{RG}` and :math:`C_{YB}`.
908 Examples
909 --------
910 >>> L_star_P = 49.99988297570504
911 >>> S_RG = -0.002885271638197
912 >>> S_YB = -0.013039632941332
913 >>> chroma_components(L_star_P, S_RG, S_YB) # doctest: +ELLIPSIS
914 array([-0.00288527, -0.01303961])
915 """
917 L_star_P = as_float_array(L_star_P)
918 S_RG = as_float_array(S_RG)
919 S_YB = as_float_array(S_YB)
921 C_RG = spow(L_star_P / 50, 0.7) * S_RG
922 C_YB = spow(L_star_P / 50, 0.7) * S_YB
924 return tstack([C_RG, C_YB])
927def chroma_correlate(L_star_P: ArrayLike, S: ArrayLike) -> NDArrayFloat:
928 """
929 Compute the correlate of *chroma* :math:`C`.
931 Parameters
932 ----------
933 L_star_P
934 *Achromatic lightness* correlate :math:`L_p^\\star`.
935 S
936 Correlate of *saturation* :math:`S`.
938 Returns
939 -------
940 :class:`numpy.ndarray`
941 Correlate of *chroma* :math:`C`.
943 Examples
944 --------
945 >>> L_star_P = 49.99988297570504
946 >>> S = 0.013355029751778
947 >>> chroma_correlate(L_star_P, S) # doctest: +ELLIPSIS
948 0.0133550...
949 """
951 L_star_P = as_float_array(L_star_P)
952 S = as_float_array(S)
954 return spow(L_star_P / 50, 0.7) * S
957def colourfulness_components(
958 C_RG: ArrayLike,
959 C_YB: ArrayLike,
960 B_rw: ArrayLike,
961) -> NDArrayFloat:
962 """
963 Compute the *colourfulness* components :math:`M_{RG}` and :math:`M_{YB}`.
965 Parameters
966 ----------
967 C_RG
968 *Chroma* component :math:`C_{RG}`.
969 C_YB
970 *Chroma* component :math:`C_{YB}`.
971 B_rw
972 Ideal white *brightness* correlate :math:`B_{rw}`.
974 Returns
975 -------
976 :class:`numpy.ndarray`
977 *Colourfulness* components :math:`M_{RG}` and :math:`M_{YB}`.
979 Examples
980 --------
981 >>> C_RG = -0.002885271638197
982 >>> C_YB = -0.013039632941332
983 >>> B_rw = 125.24353925846037
984 >>> colourfulness_components(C_RG, C_YB, B_rw) # doctest: +ELLIPSIS
985 array([-0.0036136..., -0.0163313...])
986 """
988 C_RG = as_float_array(C_RG)
989 C_YB = as_float_array(C_YB)
990 B_rw = as_float_array(B_rw)
992 M_RG = C_RG * B_rw / 100
993 M_YB = C_YB * B_rw / 100
995 return tstack([M_RG, M_YB])
998def colourfulness_correlate(C: ArrayLike, B_rw: ArrayLike) -> NDArrayFloat:
999 """
1000 Compute the correlate of *colourfulness* :math:`M`.
1002 Parameters
1003 ----------
1004 C
1005 Correlate of *chroma* :math:`C`.
1006 B_rw
1007 Ideal white *brightness* correlate :math:`B_{rw}`.
1009 Returns
1010 -------
1011 :class:`numpy.ndarray`
1012 Correlate of *colourfulness* :math:`M`.
1014 Examples
1015 --------
1016 >>> C = 0.013355007871689
1017 >>> B_rw = 125.24353925846037
1018 >>> colourfulness_correlate(C, B_rw) # doctest: +ELLIPSIS
1019 0.0167262...
1020 """
1022 C = as_float_array(C)
1023 B_rw = as_float_array(B_rw)
1025 M = C * B_rw / 100
1027 return as_float(M)