Skip to content

Commit d664d5f

Browse files
committed
Add is_within_visible_xy implementation
1 parent 8981236 commit d664d5f

File tree

2 files changed

+104
-6
lines changed

2 files changed

+104
-6
lines changed

colour/volume/spectrum.py

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from __future__ import annotations
2424

2525
import numpy as np
26-
2726
from colour.colorimetry import (
2827
MultiSpectralDistributions,
2928
SpectralDistribution,
@@ -38,6 +37,7 @@
3837
Literal,
3938
NDArrayFloat,
4039
)
40+
from colour.models.cie_xyy import XYZ_to_xy
4141
from colour.utilities import CACHE_REGISTRY, validate_method, zeros
4242
from colour.volume import is_within_mesh_volume
4343

@@ -386,14 +386,20 @@ def is_within_visible_spectrum(
386386
**kwargs: Any,
387387
) -> NDArrayFloat:
388388
"""
389-
Return whether given *CIE XYZ* tristimulus values are within the visible
390-
spectrum volume, i.e. *Rösch-MacAdam* colour solid, for given colour
391-
matching functions and illuminant.
389+
Return whether given *CIE XYZ* tristimulus values or *CIE xy* chromaticity
390+
values are within the visible spectrum volume, i.e. *Rösch-MacAdam* colour
391+
solid, for given colour matching functions and illuminant.
392+
393+
For the brightness invariant *CIE xy* check, the limit is determined by the
394+
spectral locus and "line of purples" depending on the precision of the
395+
spectral arguments.
392396
393397
Parameters
394398
----------
395399
XYZ
396-
*CIE XYZ* tristimulus values.
400+
*CIE XYZ* or xy tristimulus values. xy chromaticity mode will be
401+
selected if the size of the last dimension is 2. i.e. if XYZ.shape[-1]
402+
== 2
397403
cmfs
398404
Standard observer colour matching functions, default to the
399405
*CIE 1931 2 Degree Standard Observer*.
@@ -432,6 +438,16 @@ def is_within_visible_spectrum(
432438
array([ True, False], dtype=bool)
433439
"""
434440

441+
XYZ = np.asarray(XYZ)
442+
if XYZ.shape[-1] == 2:
443+
return _is_within_visible_xy(
444+
XYZ,
445+
cmfs=cmfs,
446+
illuminant=illuminant,
447+
tolerance=tolerance,
448+
**kwargs,
449+
)
450+
435451
cmfs, illuminant = handle_spectral_arguments(
436452
cmfs,
437453
illuminant,
@@ -449,3 +465,76 @@ def is_within_visible_spectrum(
449465
)
450466

451467
return is_within_mesh_volume(XYZ, vertices, tolerance)
468+
469+
470+
def _is_within_visible_xy(
471+
xy: ArrayLike,
472+
cmfs: MultiSpectralDistributions | None = None,
473+
illuminant: SpectralDistribution | None = None,
474+
tolerance: float = 100 * EPSILON,
475+
**kwargs: Any,
476+
) -> NDArrayFloat:
477+
"""
478+
Return whether given *CIE XYZ* tristimulus values are within the visible
479+
spectrum volume, i.e. *Rösch-MacAdam* colour solid, for given colour
480+
matching functions and illuminant.
481+
482+
Parameters
483+
----------
484+
XYZ
485+
*CIE XYZ* tristimulus values.
486+
cmfs
487+
Standard observer colour matching functions, default to the
488+
*CIE 1931 2 Degree Standard Observer*.
489+
illuminant
490+
Illuminant spectral distribution, default to *CIE Illuminant E*.
491+
tolerance
492+
Tolerance allowed in the inside-triangle check.
493+
494+
Other Parameters
495+
----------------
496+
kwargs
497+
{:func:`colour.msds_to_XYZ`},
498+
See the documentation of the previously listed definition.
499+
500+
Returns
501+
-------
502+
:class:`numpy.ndarray`
503+
Are *CIE XYZ* tristimulus values within the visible spectrum volume,
504+
i.e. *Rösch-MacAdam* colour solid.
505+
506+
Notes
507+
-----
508+
+------------+-----------------------+---------------+
509+
| **Domain** | **Scale - Reference** | **Scale - 1** |
510+
+============+=======================+===============+
511+
| ``XYZ`` | [0, 1] | [0, 1] |
512+
+------------+-----------------------+---------------+
513+
514+
Examples
515+
--------
516+
>>> import numpy as np
517+
>>> is_within_visible_spectrum(np.array([0.33,0.33]))
518+
array(True, dtype=bool)
519+
>>> a = np.array([[.33,.33], [.1,.1]])
520+
>>> is_within_visible_spectrum(a)
521+
array([ True, False], dtype=bool)
522+
"""
523+
524+
cmfs, illuminant = handle_spectral_arguments(
525+
cmfs,
526+
illuminant,
527+
"CIE 1931 2 Degree Standard Observer",
528+
"E",
529+
SPECTRAL_SHAPE_OUTER_SURFACE_XYZ,
530+
)
531+
532+
key = (hash(cmfs), hash(illuminant), str(kwargs))
533+
vertices = _CACHE_OUTER_SURFACE_XYZ_POINTS.get(key)
534+
535+
if vertices is None:
536+
_CACHE_OUTER_SURFACE_XYZ_POINTS[key] = vertices = XYZ_to_xy(
537+
cmfs.values
538+
)
539+
540+
return is_within_mesh_volume(xy, vertices, tolerance)

colour/volume/tests/test_spectrum.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from itertools import product
66

77
import numpy as np
8-
98
from colour.colorimetry import (
109
MSDS_CMFS,
1110
SPECTRAL_SHAPE_DEFAULT,
@@ -196,6 +195,16 @@ class TestIsWithinVisibleSpectrum(unittest.TestCase):
196195
definition unit tests methods.
197196
"""
198197

198+
def test_is_within_visible_spectrum_xy(self):
199+
"""Test :func:`colour.volume.spectrum.is_within_visible_spectrum`
200+
detection and use of xy chromaticity.
201+
"""
202+
203+
samples = [[0.1, 0.1], [0.3, 0.3]]
204+
results = is_within_visible_spectrum(samples)
205+
206+
np.testing.assert_array_equal(results, (False, True))
207+
199208
def test_is_within_visible_spectrum(self):
200209
"""
201210
Test :func:`colour.volume.spectrum.is_within_visible_spectrum`

0 commit comments

Comments
 (0)