Coverage for volume/macadam_limits.py: 42%

1"""

2 Optimal Colour Stimuli - MacAdam Limits

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

4

5 Define objects for computing *Optimal Colour Stimuli* and *MacAdam Limits*.

6"""

7

8 from __future__ import annotations

9

10 import typing

11

12 import numpy as np

13

14 from colour.constants import EPSILON

15

16 if typing.TYPE_CHECKING:

17 from colour.hints import ArrayLike, Literal, NDArrayFloat

18

19 from colour.models import xyY_to_XYZ

20 from colour.utilities import (

21 CACHE_REGISTRY,

22 is_caching_enabled,

23 required,

24 validate_method,

25)

26 from colour.volume import OPTIMAL_COLOUR_STIMULI_ILLUMINANTS

27

28 __author__ = "Colour Developers"

29 __copyright__ = "Copyright 2013 Colour Developers"

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

31 __maintainer__ = "Colour Developers"

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

33 __status__ = "Production"

34

35 __all__ = [

36 "is_within_macadam_limits",

37]

38

39 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ: dict = CACHE_REGISTRY.register_cache(

40 f"{__name__}._CACHE_OPTIMAL_COLOUR_STIMULI_XYZ"

41)

42

43 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS: dict = CACHE_REGISTRY.register_cache(

44 f"{__name__}._CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS"

45)

46

47

48 def _XYZ_optimal_colour_stimuli(

49 illuminant: Literal["A", "C", "D65"] | str = "D65",

50) -> NDArrayFloat:

51 """

52 Return the *Optimal Colour Stimuli* for the specified illuminant in

53 *CIE XYZ* tristimulus values and cache it if not existing.

54

55 Parameters

56 ----------

57 illuminant

58 Illuminant name.

59

60 Returns

61 -------

62 :class:`numpy.ndarray`

63 *Optimal Colour Stimuli* for the specified illuminant.

64 """

65

66 illuminant = validate_method(

67 illuminant,

68 tuple(OPTIMAL_COLOUR_STIMULI_ILLUMINANTS),

69 '"{0}" illuminant is invalid, it must be one of {1}!',

70 )

71

72 optimal_colour_stimuli = OPTIMAL_COLOUR_STIMULI_ILLUMINANTS[illuminant]

73

74 vertices = _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ.get(illuminant)

75

76 if is_caching_enabled() and vertices is not None:

77 return vertices

78

79 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ[illuminant] = vertices = (

80 xyY_to_XYZ(optimal_colour_stimuli) / 100

81 )

82

83 return vertices

84

85

86@required("SciPy")

87 def is_within_macadam_limits(

88 xyY: ArrayLike,

89 illuminant: Literal["A", "C", "D65"] | str = "D65",

90 tolerance: float = 100 * EPSILON,

91) -> NDArrayFloat:

92 """

93 Determine whether the specified *CIE xyY* colourspace array are within

94 the MacAdam limits of the specified illuminant.

95

96 Parameters

97 ----------

98 xyY

99 *CIE xyY* colourspace array.

100 illuminant

101 Illuminant name.

102 tolerance

103 Tolerance allowed in the inside-triangle check.

104

105 Returns

106 -------

107 :class:`numpy.ndarray`

108 Boolean array indicating whether the specified *CIE xyY*

109 colourspace array is within MacAdam limits.

110

111 Notes

112 -----

113 +------------+-----------------------+---------------+

114 | **Domain** | **Scale - Reference** | **Scale - 1** |

115 +============+=======================+===============+

116 | ``xyY`` | 1 | 1 |

117 +------------+-----------------------+---------------+

118

119 Examples

120 --------

121 >>> is_within_macadam_limits(np.array([0.3205, 0.4131, 0.51]), "A")

122 array(True, dtype=bool)

123 >>> a = np.array([[0.3205, 0.4131, 0.51], [0.0005, 0.0031, 0.001]])

124 >>> is_within_macadam_limits(a, "A")

125 array([ True, False], dtype=bool)

126 """

127

128 from scipy.spatial import Delaunay # noqa: PLC0415

129

130 optimal_colour_stimuli = _XYZ_optimal_colour_stimuli(illuminant)

131 triangulation = _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS.get(illuminant)

132

133 if triangulation is None:

134 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS[illuminant] = triangulation = (

135 Delaunay(optimal_colour_stimuli)

136 )

137

138 simplex = triangulation.find_simplex(xyY_to_XYZ(xyY), tol=tolerance)

139

140 return np.where(simplex >= 0, True, False)