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
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Optimal Colour Stimuli - MacAdam Limits
3=======================================
5Define objects for computing *Optimal Colour Stimuli* and *MacAdam Limits*.
6"""
8from __future__ import annotations
10import typing
12import numpy as np
14from colour.constants import EPSILON
16if typing.TYPE_CHECKING:
17 from colour.hints import ArrayLike, Literal, NDArrayFloat
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
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"
35__all__ = [
36 "is_within_macadam_limits",
37]
39_CACHE_OPTIMAL_COLOUR_STIMULI_XYZ: dict = CACHE_REGISTRY.register_cache(
40 f"{__name__}._CACHE_OPTIMAL_COLOUR_STIMULI_XYZ"
41)
43_CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS: dict = CACHE_REGISTRY.register_cache(
44 f"{__name__}._CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS"
45)
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.
55 Parameters
56 ----------
57 illuminant
58 Illuminant name.
60 Returns
61 -------
62 :class:`numpy.ndarray`
63 *Optimal Colour Stimuli* for the specified illuminant.
64 """
66 illuminant = validate_method(
67 illuminant,
68 tuple(OPTIMAL_COLOUR_STIMULI_ILLUMINANTS),
69 '"{0}" illuminant is invalid, it must be one of {1}!',
70 )
72 optimal_colour_stimuli = OPTIMAL_COLOUR_STIMULI_ILLUMINANTS[illuminant]
74 vertices = _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ.get(illuminant)
76 if is_caching_enabled() and vertices is not None:
77 return vertices
79 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ[illuminant] = vertices = (
80 xyY_to_XYZ(optimal_colour_stimuli) / 100
81 )
83 return vertices
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.
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.
105 Returns
106 -------
107 :class:`numpy.ndarray`
108 Boolean array indicating whether the specified *CIE xyY*
109 colourspace array is within MacAdam limits.
111 Notes
112 -----
113 +------------+-----------------------+---------------+
114 | **Domain** | **Scale - Reference** | **Scale - 1** |
115 +============+=======================+===============+
116 | ``xyY`` | 1 | 1 |
117 +------------+-----------------------+---------------+
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 """
128 from scipy.spatial import Delaunay # noqa: PLC0415
130 optimal_colour_stimuli = _XYZ_optimal_colour_stimuli(illuminant)
131 triangulation = _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS.get(illuminant)
133 if triangulation is None:
134 _CACHE_OPTIMAL_COLOUR_STIMULI_XYZ_TRIANGULATIONS[illuminant] = triangulation = (
135 Delaunay(optimal_colour_stimuli)
136 )
138 simplex = triangulation.find_simplex(xyY_to_XYZ(xyY), tol=tolerance)
140 return np.where(simplex >= 0, True, False)