Coverage for colour/volume/macadam_limits.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2Optimal Colour Stimuli - MacAdam Limits 

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

4 

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

6""" 

7 

8from __future__ import annotations 

9 

10import typing 

11 

12import numpy as np 

13 

14from colour.constants import EPSILON 

15 

16if typing.TYPE_CHECKING: 

17 from colour.hints import ArrayLike, Literal, NDArrayFloat 

18 

19from colour.models import xyY_to_XYZ 

20from colour.utilities import ( 

21 CACHE_REGISTRY, 

22 is_caching_enabled, 

23 required, 

24 validate_method, 

25) 

26from 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 

48def _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") 

87def 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)