Coverage for utilities/tests/test_array.py: 100%
645 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""Define the unit tests for the :mod:`colour.utilities.array` module."""
3from __future__ import annotations
5import typing
6import unittest
7from copy import deepcopy
8from dataclasses import dataclass, field, fields
9from functools import partial
11import numpy as np
12import pytest
14from colour.constants import (
15 DTYPE_COMPLEX_DEFAULT,
16 DTYPE_FLOAT_DEFAULT,
17 DTYPE_INT_DEFAULT,
18 TOLERANCE_ABSOLUTE_TESTS,
19)
21if typing.TYPE_CHECKING:
22 from colour.hints import (
23 Annotated,
24 Any,
25 ArrayLike,
26 Domain1,
27 Domain10,
28 Domain100,
29 Domain100_100_360,
30 Domain360,
31 DType,
32 NDArray,
33 NDArrayFloat,
34 Range1,
35 Range10,
36 Range100,
37 Range100_100_360,
38 Range360,
39 Type,
40 )
41else:
42 # Import Annotated at runtime for test helper function signatures
43 # get_domain_range_scale_metadata() needs to access Annotated.__metadata__
44 from colour.hints import ( # noqa: TC001
45 Annotated,
46 Any,
47 ArrayLike,
48 Domain1,
49 Domain10,
50 Domain100,
51 Domain360,
52 Domain100_100_360,
53 NDArrayFloat,
54 Range1,
55 Range10,
56 Range100,
57 Range360,
58 Range100_100_360,
59 )
61from colour.utilities import (
62 MixinDataclassArithmetic,
63 MixinDataclassArray,
64 MixinDataclassFields,
65 MixinDataclassIterable,
66 as_array,
67 as_complex_array,
68 as_float,
69 as_float_array,
70 as_float_scalar,
71 as_int,
72 as_int_array,
73 as_int_scalar,
74 centroid,
75 closest,
76 closest_indexes,
77 domain_range_scale,
78 fill_nan,
79 format_array_as_row,
80 from_range_1,
81 from_range_10,
82 from_range_100,
83 from_range_degrees,
84 from_range_int,
85 full,
86 get_domain_range_scale,
87 get_domain_range_scale_metadata,
88 has_only_nan,
89 in_array,
90 index_along_last_axis,
91 interval,
92 is_ndarray_copy_enabled,
93 is_networkx_installed,
94 is_scipy_installed,
95 is_uniform,
96 ndarray_copy,
97 ndarray_copy_enable,
98 ndarray_write,
99 ones,
100 orient,
101 row_as_diagonal,
102 set_default_float_dtype,
103 set_default_int_dtype,
104 set_domain_range_scale,
105 set_ndarray_copy_enable,
106 to_domain_1,
107 to_domain_10,
108 to_domain_100,
109 to_domain_degrees,
110 to_domain_int,
111 tsplit,
112 tstack,
113 zeros,
114)
116__author__ = "Colour Developers"
117__copyright__ = "Copyright 2013 Colour Developers"
118__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
119__maintainer__ = "Colour Developers"
120__email__ = "colour-developers@colour-science.org"
121__status__ = "Production"
123__all__ = [
124 "TestMixinDataclassFields",
125 "TestMixinDataclassIterable",
126 "TestMixinDataclassArray",
127 "TestMixinDataclassArithmetic",
128 "TestAsArray",
129 "TestAsInt",
130 "TestAsFloat",
131 "TestAsIntArray",
132 "TestAsFloatArray",
133 "TestAsComplexArray",
134 "TestAsIntScalar",
135 "TestAsFloatScalar",
136 "TestSetDefaultIntegerDtype",
137 "TestSetDefaultFloatDtype",
138 "TestGetDomainRangeScale",
139 "TestSetDomainRangeScale",
140 "TestDomainRangeScale",
141 "TestGetDomainRangeScaleMetadata",
142 "TestToDomain1",
143 "TestToDomain10",
144 "TestToDomain100",
145 "TestToDomainDegrees",
146 "TestToDomainInt",
147 "TestFromRange1",
148 "TestFromRange10",
149 "TestFromRange100",
150 "TestFromRangeDegrees",
151 "TestFromRangeInt",
152 "TestIsNdarrayCopyEnabled",
153 "TestSetNdarrayCopyEnabled",
154 "TestNdarrayCopyEnable",
155 "TestNdarrayCopy",
156 "TestClosestIndexes",
157 "TestClosest",
158 "TestInterval",
159 "TestIsUniform",
160 "TestInArray",
161 "TestTstack",
162 "TestTsplit",
163 "TestRowAsDiagonal",
164 "TestOrient",
165 "TestCentroid",
166 "TestFillNan",
167 "TestHasNanOnly",
168 "TestNdarrayWrite",
169 "TestZeros",
170 "TestOnes",
171 "TestFull",
172 "TestIndexAlongLastAxis",
173]
176class TestMixinDataclassFields(unittest.TestCase):
177 """
178 Define :class:`colour.utilities.array.MixinDataclassFields` class unit
179 tests methods.
180 """
182 def setUp(self) -> None:
183 """Initialise the common tests attributes."""
185 @dataclass
186 class Data(MixinDataclassFields):
187 a: str
188 b: str
189 c: str
191 self._data: Data = Data(a="Foo", b="Bar", c="Baz")
193 def test_required_attributes(self) -> None:
194 """Test the presence of required attributes."""
196 required_attributes = ("fields",)
198 for method in required_attributes:
199 assert method in dir(MixinDataclassFields)
201 def test_fields(self) -> None:
202 """
203 Test :meth:`colour.utilities.array.MixinDataclassIterable._fields`
204 method.
205 """
207 assert self._data.fields == fields(self._data)
210class TestMixinDataclassIterable(unittest.TestCase):
211 """
212 Define :class:`colour.utilities.array.MixinDataclassIterable` class unit
213 tests methods.
214 """
216 def setUp(self) -> None:
217 """Initialise the common tests attributes."""
219 @dataclass
220 class Data(MixinDataclassIterable):
221 a: str
222 b: str
223 c: str
225 self._data: Data = Data(a="Foo", b="Bar", c="Baz")
227 def test_required_attributes(self) -> None:
228 """Test the presence of required attributes."""
230 required_attributes = (
231 "keys",
232 "values",
233 "items",
234 )
236 for method in required_attributes:
237 assert method in dir(MixinDataclassIterable)
239 def test_required_methods(self) -> None:
240 """Test the presence of required methods."""
242 required_methods = ("__iter__",)
244 for method in required_methods:
245 assert method in dir(MixinDataclassIterable)
247 def test__iter__(self) -> None:
248 """
249 Test :meth:`colour.utilities.array.MixinDataclassIterable.__iter__`
250 method.
251 """
253 assert {key: value for key, value in self._data} == (
254 {"a": "Foo", "b": "Bar", "c": "Baz"}
255 )
257 def test_keys(self) -> None:
258 """
259 Test :meth:`colour.utilities.array.MixinDataclassIterable.keys`
260 method.
261 """
263 assert tuple(self._data.keys) == ("a", "b", "c")
265 def test_values(self) -> None:
266 """
267 Test :meth:`colour.utilities.array.MixinDataclassIterable.values`
268 method.
269 """
271 assert tuple(self._data.values) == ("Foo", "Bar", "Baz")
273 def test_items(self) -> None:
274 """
275 Test :meth:`colour.utilities.array.MixinDataclassIterable.items`
276 method.
277 """
279 assert tuple(self._data.items) == (("a", "Foo"), ("b", "Bar"), ("c", "Baz"))
282class TestMixinDataclassArray(unittest.TestCase):
283 """
284 Define :class:`colour.utilities.array.MixinDataclassArray` class unit
285 tests methods.
286 """
288 def setUp(self) -> None:
289 """Initialise the common tests attributes."""
291 @dataclass
292 class Data(MixinDataclassArray):
293 a: float | list | tuple | np.ndarray | None = field(
294 default_factory=lambda: None
295 )
297 b: float | list | tuple | np.ndarray | None = field(
298 default_factory=lambda: None
299 )
301 c: float | list | tuple | np.ndarray | None = field(
302 default_factory=lambda: None
303 )
305 self._data: Data = Data(
306 b=np.array([0.1, 0.2, 0.3]), c=np.array([0.4, 0.5, 0.6])
307 )
308 self._array: NDArray = np.array(
309 [
310 [np.nan, 0.1, 0.4],
311 [np.nan, 0.2, 0.5],
312 [np.nan, 0.3, 0.6],
313 ]
314 )
316 def test_required_methods(self) -> None:
317 """Test the presence of required methods."""
319 required_methods = ("__array__",)
321 for method in required_methods:
322 assert method in dir(MixinDataclassArray)
324 def test__array__(self) -> None:
325 """
326 Test :meth:`colour.utilities.array.MixinDataclassArray.__array__`
327 method.
328 """
330 np.testing.assert_array_equal(self._data, self._array)
332 assert np.array(self._data, dtype=DTYPE_INT_DEFAULT).dtype == DTYPE_INT_DEFAULT
335class TestMixinDataclassArithmetic(unittest.TestCase):
336 """
337 Define :class:`colour.utilities.array.MixinDataclassArithmetic` class unit
338 tests methods.
339 """
341 def setUp(self) -> None:
342 """Initialise the common tests attributes."""
344 @dataclass
345 class Data(MixinDataclassArithmetic):
346 a: float | list | tuple | np.ndarray | None = field(
347 default_factory=lambda: None
348 )
350 b: float | list | tuple | np.ndarray | None = field(
351 default_factory=lambda: None
352 )
354 c: float | list | tuple | np.ndarray | None = field(
355 default_factory=lambda: None
356 )
358 self._factory: Type[Data] = Data
359 self._data: Data = Data(
360 b=np.array([0.1, 0.2, 0.3]), c=np.array([0.4, 0.5, 0.6])
361 )
362 self._array: NDArray = np.array(
363 [
364 [np.nan, 0.1, 0.4],
365 [np.nan, 0.2, 0.5],
366 [np.nan, 0.3, 0.6],
367 ]
368 )
370 def test_required_methods(self) -> None:
371 """Test the presence of required methods."""
373 required_methods = (
374 "__iadd__",
375 "__add__",
376 "__isub__",
377 "__sub__",
378 "__imul__",
379 "__mul__",
380 "__idiv__",
381 "__div__",
382 "__ipow__",
383 "__pow__",
384 "arithmetical_operation",
385 )
387 for method in required_methods:
388 assert method in dir(MixinDataclassArithmetic)
390 def test_arithmetical_operation(self) -> None:
391 """
392 Test :meth:`colour.utilities.array.MixinDataclassArithmetic.\
393arithmetical_operation` method.
394 """
396 np.testing.assert_allclose(
397 self._data.arithmetical_operation(10, "+", False),
398 self._array + 10,
399 atol=TOLERANCE_ABSOLUTE_TESTS,
400 )
402 np.testing.assert_allclose(
403 self._data.arithmetical_operation(10, "-", False),
404 self._array - 10,
405 atol=TOLERANCE_ABSOLUTE_TESTS,
406 )
408 np.testing.assert_allclose(
409 self._data.arithmetical_operation(10, "*", False),
410 self._array * 10,
411 atol=TOLERANCE_ABSOLUTE_TESTS,
412 )
414 np.testing.assert_allclose(
415 self._data.arithmetical_operation(10, "/", False),
416 self._array / 10,
417 atol=TOLERANCE_ABSOLUTE_TESTS,
418 )
420 np.testing.assert_allclose(
421 self._data.arithmetical_operation(10, "**", False),
422 self._array**10,
423 atol=TOLERANCE_ABSOLUTE_TESTS,
424 )
426 np.testing.assert_allclose(
427 self._data + 10,
428 self._array + 10,
429 atol=TOLERANCE_ABSOLUTE_TESTS,
430 )
432 np.testing.assert_allclose(
433 self._data - 10,
434 self._array - 10,
435 atol=TOLERANCE_ABSOLUTE_TESTS,
436 )
438 np.testing.assert_allclose(
439 self._data * 10,
440 self._array * 10,
441 atol=TOLERANCE_ABSOLUTE_TESTS,
442 )
444 np.testing.assert_allclose(
445 self._data / 10,
446 self._array / 10,
447 atol=TOLERANCE_ABSOLUTE_TESTS,
448 )
450 np.testing.assert_allclose(
451 self._data**10,
452 self._array**10,
453 atol=TOLERANCE_ABSOLUTE_TESTS,
454 )
456 data = deepcopy(self._data)
458 np.testing.assert_allclose(
459 data.arithmetical_operation(10, "+", True),
460 self._array + 10,
461 atol=TOLERANCE_ABSOLUTE_TESTS,
462 )
464 np.testing.assert_allclose(
465 data.arithmetical_operation(10, "-", True),
466 self._array,
467 atol=TOLERANCE_ABSOLUTE_TESTS,
468 )
470 np.testing.assert_allclose(
471 data.arithmetical_operation(10, "*", True),
472 self._array * 10,
473 atol=TOLERANCE_ABSOLUTE_TESTS,
474 )
476 np.testing.assert_allclose(
477 data.arithmetical_operation(10, "/", True),
478 self._array,
479 atol=TOLERANCE_ABSOLUTE_TESTS,
480 )
482 np.testing.assert_allclose(
483 data.arithmetical_operation(10, "**", True),
484 self._array**10,
485 atol=TOLERANCE_ABSOLUTE_TESTS,
486 )
488 data = deepcopy(self._data)
490 np.testing.assert_allclose(
491 data.arithmetical_operation(self._array, "+", False),
492 data + self._array,
493 atol=TOLERANCE_ABSOLUTE_TESTS,
494 )
496 np.testing.assert_allclose(
497 data.arithmetical_operation(data, "+", False),
498 data + data,
499 atol=TOLERANCE_ABSOLUTE_TESTS,
500 )
502 data = self._factory(1, 2, 3)
504 data += 1
505 assert data.a == 2
507 data -= 1
508 assert data.a == 1
510 data *= 2
511 assert data.a == 2
513 data /= 2
514 assert data.a == 1
516 data **= 0.5
517 assert data.a == 1
520class TestAsArray(unittest.TestCase):
521 """
522 Define :func:`colour.utilities.array.as_array` definition unit tests
523 methods.
524 """
526 def test_as_array(self) -> None:
527 """Test :func:`colour.utilities.array.as_array` definition."""
529 np.testing.assert_equal(as_array([1, 2, 3]), np.array([1, 2, 3]))
531 assert as_array([1, 2, 3], DTYPE_FLOAT_DEFAULT).dtype == DTYPE_FLOAT_DEFAULT
533 assert as_array([1, 2, 3], DTYPE_INT_DEFAULT).dtype == DTYPE_INT_DEFAULT
535 np.testing.assert_equal(
536 as_array(dict(zip("abc", [1, 2, 3], strict=True)).values()),
537 np.array([1, 2, 3]),
538 )
541class TestAsInt(unittest.TestCase):
542 """
543 Define :func:`colour.utilities.array.as_int` definition unit tests
544 methods.
545 """
547 def test_as_int(self) -> None:
548 """Test :func:`colour.utilities.array.as_int` definition."""
550 assert as_int(1) == 1
552 assert as_int(np.array([1])).ndim == 1
554 assert as_int(np.array([[1]])).ndim == 2
556 np.testing.assert_array_equal(
557 as_int(np.array([1.0, 2.0, 3.0])), np.array([1, 2, 3])
558 )
560 assert as_int(np.array([1.0, 2.0, 3.0])).dtype == DTYPE_INT_DEFAULT
562 assert isinstance(as_int(1), DTYPE_INT_DEFAULT)
565class TestAsFloat(unittest.TestCase):
566 """
567 Define :func:`colour.utilities.array.as_float` definition unit tests
568 methods.
569 """
571 def test_as_float(self) -> None:
572 """Test :func:`colour.utilities.array.as_float` definition."""
574 assert as_float(1) == 1.0
576 assert as_float(np.array([1])).ndim == 1
578 assert as_float(np.array([[1]])).ndim == 2
580 np.testing.assert_allclose(
581 as_float(np.array([1, 2, 3])),
582 np.array([1.0, 2.0, 3.0]),
583 atol=TOLERANCE_ABSOLUTE_TESTS,
584 )
586 assert as_float(np.array([1, 2, 3])).dtype == DTYPE_FLOAT_DEFAULT
588 assert isinstance(as_float(1), DTYPE_FLOAT_DEFAULT)
591class TestAsIntArray(unittest.TestCase):
592 """
593 Define :func:`colour.utilities.array.as_int_array` definition unit tests
594 methods.
595 """
597 def test_as_int_array(self) -> None:
598 """Test :func:`colour.utilities.array.as_int_array` definition."""
600 np.testing.assert_equal(as_int_array([1.0, 2.0, 3.0]), np.array([1, 2, 3]))
602 assert as_int_array([1, 2, 3]).dtype == DTYPE_INT_DEFAULT
605class TestAsFloatArray(unittest.TestCase):
606 """
607 Define :func:`colour.utilities.array.as_float_array` definition unit tests
608 methods.
609 """
611 def test_as_float_array(self) -> None:
612 """Test :func:`colour.utilities.array.as_float_array` definition."""
614 np.testing.assert_equal(as_float_array([1, 2, 3]), np.array([1, 2, 3]))
616 assert as_float_array([1, 2, 3]).dtype == DTYPE_FLOAT_DEFAULT
619class TestAsComplexArray(unittest.TestCase):
620 """
621 Define :func:`colour.utilities.array.as_complex_array` definition unit tests
622 methods.
623 """
625 def test_as_complex_array(self) -> None:
626 """Test :func:`colour.utilities.array.as_complex_array` definition."""
628 np.testing.assert_equal(
629 as_complex_array([1, 2, 3]), np.array([1 + 0j, 2 + 0j, 3 + 0j])
630 )
632 np.testing.assert_equal(
633 as_complex_array([1 + 2j, 3 + 4j]), np.array([1 + 2j, 3 + 4j])
634 )
636 assert as_complex_array([1, 2, 3]).dtype == DTYPE_COMPLEX_DEFAULT
638 assert as_complex_array([1, 2, 3], np.complex64).dtype == np.complex64
641class TestAsIntScalar(unittest.TestCase):
642 """
643 Define :func:`colour.utilities.array.as_int_scalar` definition unit tests
644 methods.
645 """
647 def test_as_int_scalar(self) -> None:
648 """Test :func:`colour.utilities.array.as_int_scalar` definition."""
650 assert as_int_scalar(1.0) == 1
652 assert as_int_scalar(1.0).dtype == DTYPE_INT_DEFAULT # pyright: ignore
655class TestAsFloatScalar(unittest.TestCase):
656 """
657 Define :func:`colour.utilities.array.as_float_scalar` definition unit
658 tests methods.
659 """
661 def test_as_float_scalar(self) -> None:
662 """Test :func:`colour.utilities.array.as_float_scalar` definition."""
664 assert as_float_scalar(1) == 1.0
666 assert as_float_scalar(1).dtype == DTYPE_FLOAT_DEFAULT # pyright: ignore
669class TestSetDefaultIntegerDtype(unittest.TestCase):
670 """
671 Define :func:`colour.utilities.array.set_default_int_dtype` definition unit
672 tests methods.
673 """
675 def test_set_default_int_dtype(self) -> None:
676 """
677 Test :func:`colour.utilities.array.set_default_int_dtype` definition.
678 """
680 assert as_int_array(np.ones(3)).dtype == np.int64
682 set_default_int_dtype(np.int32)
684 assert as_int_array(np.ones(3)).dtype == np.int32
686 set_default_int_dtype(np.int64)
688 assert as_int_array(np.ones(3)).dtype == np.int64
690 def tearDown(self) -> None:
691 """After tests actions."""
693 set_default_int_dtype(np.int64)
696class TestSetDefaultFloatDtype(unittest.TestCase):
697 """
698 Define :func:`colour.utilities.array.set_default_float_dtype` definition unit
699 tests methods.
700 """
702 def test_set_default_float_dtype(self) -> None:
703 """
704 Test :func:`colour.utilities.array.set_default_float_dtype`
705 definition.
706 """
708 try:
709 assert as_float_array(np.ones(3)).dtype == np.float64
711 set_default_float_dtype(np.float16)
713 assert as_float_array(np.ones(3)).dtype == np.float16
715 set_default_float_dtype(np.float64)
717 assert as_float_array(np.ones(3)).dtype == np.float64
718 finally:
719 set_default_float_dtype(np.float64)
721 def test_set_default_float_dtype_enforcement(self) -> None:
722 """
723 Test whether :func:`colour.utilities.array.set_default_float_dtype`
724 effect is applied through most of *Colour* public API.
725 """
727 if not is_scipy_installed(): # pragma: no cover
728 return
730 if not is_networkx_installed(): # pragma: no cover
731 return
733 from colour.appearance import ( # noqa: PLC0415
734 CAM_Specification_CAM16,
735 CAM_Specification_CIECAM02,
736 CAM_Specification_CIECAM16,
737 CAM_Specification_Hellwig2022,
738 CAM_Specification_Kim2009,
739 CAM_Specification_sCAM,
740 CAM_Specification_ZCAM,
741 )
742 from colour.graph.conversion import ( # noqa: PLC0415
743 CONVERSION_SPECIFICATIONS_DATA,
744 convert,
745 )
747 try:
748 dtype = np.float32
749 set_default_float_dtype(dtype)
751 for source, target, _callable in CONVERSION_SPECIFICATIONS_DATA:
752 if target in ("Hexadecimal", "Munsell Colour"):
753 continue
755 # Spectral distributions are instantiated with float64 data and
756 # spectral up-sampling optimization fails.
757 if (
758 "Spectral Distribution" in (source, target) # noqa: PLR1714
759 or target == "Complementary Wavelength"
760 or target == "Dominant Wavelength"
761 ):
762 continue
764 a = np.array([(0.25, 0.5, 0.25), (0.25, 0.5, 0.25)])
766 if source == "CAM16":
767 a = CAM_Specification_CAM16(J=0.25, M=0.5, h=0.25)
769 if source == "CIECAM02":
770 a = CAM_Specification_CIECAM02(J=0.25, M=0.5, h=0.25)
772 if source == "CIECAM16":
773 a = CAM_Specification_CIECAM16(J=0.25, M=0.5, h=0.25)
775 if source == "Hellwig 2022":
776 a = CAM_Specification_Hellwig2022(J=0.25, M=0.5, h=0.25)
778 if source == "Kim 2009":
779 a = CAM_Specification_Kim2009(J=0.25, M=0.5, h=0.25)
781 if source == "sCAM":
782 a = CAM_Specification_sCAM(J=0.25, M=0.5, h=0.25)
784 if source == "ZCAM":
785 a = CAM_Specification_ZCAM(J=0.25, M=0.5, h=0.25)
787 if source == "CMYK":
788 a = np.array([(0.25, 0.5, 0.25, 0.5), (0.25, 0.5, 0.25, 0.5)])
790 if source == "Hexadecimal":
791 a = np.array(["#FFFFFF", "#FFFFFF"])
793 if source == "CSS Color 3":
794 a = "aliceblue"
796 if source == "Munsell Colour":
797 a = ["4.2YR 8.1/5.3", "4.2YR 8.1/5.3"]
799 if source == "Wavelength":
800 a = 555
802 if (
803 source.startswith("CCT") # noqa: PIE810
804 or source.endswith(" xy")
805 or source.endswith(" uv")
806 ):
807 a = np.array([(0.25, 0.5), (0.25, 0.5)])
809 def dtype_getter(x: NDArray) -> DType:
810 """Dtype getter callable."""
812 for specification in (
813 "ATD95",
814 "CIECAM02",
815 "CAM16",
816 "Hellwig 2022",
817 "Hunt",
818 "Kim 2009",
819 "LLAB",
820 "Nayatani95",
821 "RLAB",
822 "sCAM",
823 "ZCAM",
824 ):
825 if target.endswith(specification): # noqa: B023
826 return getattr(x, fields(x)[0].name).dtype # pyright: ignore
828 return x.dtype # pyright: ignore
830 assert dtype_getter(convert(a, source, target)) == dtype
831 finally:
832 set_default_float_dtype(np.float64)
835class TestGetDomainRangeScale(unittest.TestCase):
836 """
837 Define :func:`colour.utilities.common.get_domain_range_scale` definition
838 unit tests methods.
839 """
841 def test_get_domain_range_scale(self) -> None:
842 """
843 Test :func:`colour.utilities.common.get_domain_range_scale`
844 definition.
845 """
847 with domain_range_scale("Reference"):
848 assert get_domain_range_scale() == "reference"
850 with domain_range_scale("1"):
851 assert get_domain_range_scale() == "1"
853 with domain_range_scale("100"):
854 assert get_domain_range_scale() == "100"
857class TestSetDomainRangeScale(unittest.TestCase):
858 """
859 Define :func:`colour.utilities.common.set_domain_range_scale` definition
860 unit tests methods.
861 """
863 def test_set_domain_range_scale(self) -> None:
864 """
865 Test :func:`colour.utilities.common.set_domain_range_scale`
866 definition.
867 """
869 with domain_range_scale("Reference"):
870 set_domain_range_scale("1")
871 assert get_domain_range_scale() == "1"
873 with domain_range_scale("Reference"):
874 set_domain_range_scale("100")
875 assert get_domain_range_scale() == "100"
877 with domain_range_scale("1"):
878 set_domain_range_scale("Reference")
879 assert get_domain_range_scale() == "reference"
881 with pytest.raises(ValueError):
882 set_domain_range_scale("Invalid")
885class TestDomainRangeScale(unittest.TestCase):
886 """
887 Define :func:`colour.utilities.common.domain_range_scale` definition
888 unit tests methods.
889 """
891 def test_domain_range_scale(self) -> None:
892 """
893 Test :func:`colour.utilities.common.domain_range_scale`
894 definition.
895 """
897 assert get_domain_range_scale() == "reference"
899 with domain_range_scale("Reference"):
900 assert get_domain_range_scale() == "reference"
902 assert get_domain_range_scale() == "reference"
904 with domain_range_scale("1"):
905 assert get_domain_range_scale() == "1"
907 assert get_domain_range_scale() == "reference"
909 with domain_range_scale("100"):
910 assert get_domain_range_scale() == "100"
912 assert get_domain_range_scale() == "reference"
914 def fn_a(a: ArrayLike) -> NDArrayFloat:
915 """Change the domain-range scale for unit testing."""
917 b = to_domain_10(a)
919 b *= 2
921 return from_range_100(b)
923 with domain_range_scale("Reference"):
924 with domain_range_scale("1"):
925 with domain_range_scale("100"):
926 with domain_range_scale("Ignore"):
927 assert get_domain_range_scale() == "ignore"
928 assert fn_a(4) == 8
930 assert get_domain_range_scale() == "100"
931 assert fn_a(40) == 8
933 assert get_domain_range_scale() == "1"
934 assert fn_a(0.4) == 0.08
936 assert get_domain_range_scale() == "reference"
937 assert fn_a(4) == 8
939 assert get_domain_range_scale() == "reference"
941 @domain_range_scale("1")
942 def fn_b(a: ArrayLike) -> NDArrayFloat:
943 """Change the domain-range scale for unit testing."""
945 b = to_domain_10(a)
947 b *= 2
949 return from_range_100(b)
951 assert fn_b(10) == 2.0
954class TestGetDomainRangeScaleMetadata(unittest.TestCase):
955 """
956 Define :func:`colour.utilities.array.get_domain_range_scale_metadata`
957 definition unit tests methods.
958 """
960 def test_get_domain_range_scale_metadata(self) -> None:
961 """
962 Test :func:`colour.utilities.array.get_domain_range_scale_metadata`
963 definition.
964 """
966 # Pattern 1: Uniform parameter scaling
967 def function_a(
968 XYZ: Annotated[ArrayLike, 1],
969 illuminant: ArrayLike = None, # type: ignore
970 ) -> Annotated[NDArrayFloat, 100]: # type: ignore
971 """Test uniform parameter scaling."""
973 metadata = get_domain_range_scale_metadata(function_a)
974 assert metadata["domain"] == {"XYZ": 1}
975 assert metadata["range"] == 100
977 # Pattern 2: Per-parameter scaling (only some params scaled)
978 def function_b(
979 uv: ArrayLike,
980 illuminant: ArrayLike = None, # type: ignore
981 L: Annotated[ArrayLike, 100] = 100,
982 ) -> Annotated[NDArrayFloat, 100]: # type: ignore
983 """Test per-parameter scaling."""
985 metadata = get_domain_range_scale_metadata(function_b)
986 assert metadata["domain"] == {"L": 100}
987 assert metadata["range"] == 100
989 # Pattern 3: Per-component tuple scaling (CAM models)
990 def function_c(
991 XYZ: Annotated[ArrayLike, 100],
992 ) -> Annotated[tuple, (100, 100, 360, 100, 100, 100, 400)]: # type: ignore
993 """Test tuple return scaling."""
995 metadata = get_domain_range_scale_metadata(function_c)
996 assert metadata["domain"] == {"XYZ": 100}
997 assert metadata["range"] == (100, 100, 360, 100, 100, 100, 400)
999 # Multiple domain parameters
1000 def function_d(
1001 XYZ: Annotated[ArrayLike, 100],
1002 XYZ_w: Annotated[ArrayLike, 100],
1003 illuminant: ArrayLike = None, # type: ignore
1004 ) -> Annotated[NDArrayFloat, 100]: # type: ignore
1005 """Test multiple domain parameters."""
1007 metadata = get_domain_range_scale_metadata(function_d)
1008 assert metadata["domain"] == {"XYZ": 100, "XYZ_w": 100}
1009 assert metadata["range"] == 100
1011 # No annotations (backward compatibility)
1012 def function_e(XYZ: Any, illuminant: Any = None) -> None:
1013 """Test backward compatibility."""
1015 metadata = get_domain_range_scale_metadata(function_e)
1016 assert metadata["domain"] == {}
1017 assert metadata["range"] is None
1019 # Only domain scaling, no range
1020 def function_f(
1021 XYZ: Annotated[ArrayLike, 1],
1022 ) -> NDArrayFloat: # type: ignore
1023 """Test domain-only scaling."""
1025 metadata = get_domain_range_scale_metadata(function_f)
1026 assert metadata["domain"] == {"XYZ": 1}
1027 assert metadata["range"] is None
1029 # Only range scaling, no domain
1030 def function_g(
1031 XYZ: ArrayLike,
1032 ) -> Annotated[NDArrayFloat, 100]: # type: ignore
1033 """Test range-only scaling."""
1035 metadata = get_domain_range_scale_metadata(function_g)
1036 assert metadata["domain"] == {}
1037 assert metadata["range"] == 100
1039 # Type aliases: Domain1/Range1
1040 def function_h(XYZ: Domain1, XYZ_w: Domain1 = 1) -> Range1: # type: ignore
1041 """Test Domain1/Range1 type aliases."""
1043 metadata = get_domain_range_scale_metadata(function_h)
1044 assert metadata["domain"] == {"XYZ": 1, "XYZ_w": 1}
1046 # Union with Annotated types
1047 def function_i(
1048 value: Annotated[int, 100] | Annotated[float, 200],
1049 ) -> NDArrayFloat: # type: ignore
1050 """Test Union with Annotated members."""
1052 metadata = get_domain_range_scale_metadata(function_i)
1053 assert metadata["domain"] == {"value": 100}
1054 assert metadata["range"] is None
1056 # Type aliases: Domain100/Range100
1057 def function_j(Y: Domain100, Y_n: Domain100 = 100) -> Range100: # type: ignore
1058 """Test Domain100/Range100 type aliases."""
1060 metadata = get_domain_range_scale_metadata(function_j)
1061 assert metadata["domain"] == {"Y": 100, "Y_n": 100}
1062 assert metadata["range"] == 100
1064 # Type aliases: Domain10/Range10
1065 def function_k(L: Domain10) -> Range10: # type: ignore
1066 """Test Domain10/Range10 type aliases."""
1068 metadata = get_domain_range_scale_metadata(function_k)
1069 assert metadata["domain"] == {"L": 10}
1070 assert metadata["range"] == 10
1072 # Type aliases: Domain360/Range360
1073 def function_l(hue: Domain360) -> Range360: # type: ignore
1074 """Test Domain360/Range360 type aliases."""
1076 metadata = get_domain_range_scale_metadata(function_l)
1077 assert metadata["domain"] == {"hue": 360}
1078 assert metadata["range"] == 360
1080 # Type aliases: Domain100_100_360/Range100_100_360
1081 def function_m(Lab: Domain100_100_360) -> Range100_100_360: # type: ignore
1082 """Test Domain100_100_360/Range100_100_360 type aliases."""
1084 metadata = get_domain_range_scale_metadata(function_m)
1085 assert metadata["domain"] == {"Lab": (100, 100, 360)}
1086 assert metadata["range"] == (100, 100, 360)
1088 # Mixed: type aliases and explicit Annotated
1089 def function_n(
1090 XYZ: Domain1, L: Domain100, custom: Annotated[ArrayLike, 50]
1091 ) -> Range100: # type: ignore
1092 """Test mixed type aliases and Annotated."""
1094 metadata = get_domain_range_scale_metadata(function_n)
1095 assert metadata["domain"] == {"XYZ": 1, "L": 100, "custom": 50}
1096 assert metadata["range"] == 100
1098 # functools.partial with type aliases
1099 def function_o(
1100 XYZ: Domain1,
1101 colourspace: str,
1102 illuminant: ArrayLike | None = None,
1103 ) -> Range1: # type: ignore
1104 """Test function for partial wrapping."""
1106 partial_func = partial(function_o, colourspace="sRGB")
1107 metadata = get_domain_range_scale_metadata(partial_func)
1108 assert metadata["domain"] == {"XYZ": 1}
1109 assert metadata["range"] == 1
1111 # functools.partial with explicit Annotated
1112 def function_p(
1113 Lab: Annotated[ArrayLike, 100],
1114 illuminant: ArrayLike | None = None,
1115 method: str = "CIE 1976",
1116 ) -> Annotated[NDArrayFloat, 100]: # type: ignore
1117 """Test function for partial wrapping with Annotated."""
1119 partial_func2 = partial(function_p, method="CIE 2000")
1120 metadata = get_domain_range_scale_metadata(partial_func2)
1121 assert metadata["domain"] == {"Lab": 100}
1122 assert metadata["range"] == 100
1124 # Test string annotation with unevaluable scale (triggers exception handler)
1125 # This simulates what happens with `from __future__ import annotations`
1126 # when the annotation contains an undefined variable
1127 def function_q(x: Any) -> Any:
1128 """Test function with mock string annotation."""
1130 # Manually set __annotations__ to simulate string annotation with undefined var
1131 function_q.__annotations__ = {
1132 "x": "Annotated[float, undefined_variable]",
1133 "return": "Annotated[float, another_undefined]",
1134 }
1136 metadata = get_domain_range_scale_metadata(function_q)
1137 # The eval will fail, so it falls back to the string itself
1138 assert metadata["domain"] == {"x": "undefined_variable"}
1139 assert metadata["range"] == "another_undefined"
1142class TestToDomain1(unittest.TestCase):
1143 """
1144 Define :func:`colour.utilities.common.to_domain_1` definition unit
1145 tests methods.
1146 """
1148 def test_to_domain_1(self) -> None:
1149 """Test :func:`colour.utilities.common.to_domain_1` definition."""
1151 with domain_range_scale("Reference"):
1152 assert to_domain_1(1) == 1
1154 with domain_range_scale("1"):
1155 assert to_domain_1(1) == 1
1157 with domain_range_scale("100"):
1158 assert to_domain_1(1) == 0.01
1160 with domain_range_scale("100"):
1161 assert to_domain_1(1, np.pi) == 1 / np.pi
1163 with domain_range_scale("100"):
1164 assert to_domain_1(1, dtype=np.float16).dtype == np.float16
1167class TestToDomain10(unittest.TestCase):
1168 """
1169 Define :func:`colour.utilities.common.to_domain_10` definition unit
1170 tests methods.
1171 """
1173 def test_to_domain_10(self) -> None:
1174 """Test :func:`colour.utilities.common.to_domain_10` definition."""
1176 with domain_range_scale("Reference"):
1177 assert to_domain_10(1) == 1
1179 with domain_range_scale("1"):
1180 assert to_domain_10(1) == 10
1182 with domain_range_scale("100"):
1183 assert to_domain_10(1) == 0.1
1185 with domain_range_scale("100"):
1186 assert to_domain_10(1, np.pi) == 1 / np.pi
1188 with domain_range_scale("100"):
1189 assert to_domain_10(1, dtype=np.float16).dtype == np.float16
1192class TestToDomain100(unittest.TestCase):
1193 """
1194 Define :func:`colour.utilities.common.to_domain_100` definition unit
1195 tests methods.
1196 """
1198 def test_to_domain_100(self) -> None:
1199 """Test :func:`colour.utilities.common.to_domain_100` definition."""
1201 with domain_range_scale("Reference"):
1202 assert to_domain_100(1) == 1
1204 with domain_range_scale("1"):
1205 assert to_domain_100(1) == 100
1207 with domain_range_scale("100"):
1208 assert to_domain_100(1) == 1
1210 with domain_range_scale("1"):
1211 assert to_domain_100(1, np.pi) == np.pi
1213 with domain_range_scale("100"):
1214 assert to_domain_100(1, dtype=np.float16).dtype == np.float16
1217class TestToDomainDegrees(unittest.TestCase):
1218 """
1219 Define :func:`colour.utilities.common.to_domain_degrees` definition unit
1220 tests methods.
1221 """
1223 def test_to_domain_degrees(self) -> None:
1224 """Test :func:`colour.utilities.common.to_domain_degrees` definition."""
1226 with domain_range_scale("Reference"):
1227 assert to_domain_degrees(1) == 1
1229 with domain_range_scale("1"):
1230 assert to_domain_degrees(1) == 360
1232 with domain_range_scale("100"):
1233 assert to_domain_degrees(1) == 3.6
1235 with domain_range_scale("100"):
1236 assert to_domain_degrees(1, np.pi) == np.pi / 100
1238 with domain_range_scale("100"):
1239 assert to_domain_degrees(1, dtype=np.float16).dtype == np.float16
1242class TestToDomainInt(unittest.TestCase):
1243 """
1244 Define :func:`colour.utilities.common.to_domain_int` definition unit
1245 tests methods.
1246 """
1248 def test_to_domain_int(self) -> None:
1249 """Test :func:`colour.utilities.common.to_domain_int` definition."""
1251 with domain_range_scale("Reference"):
1252 assert to_domain_int(1) == 1
1254 with domain_range_scale("1"):
1255 assert to_domain_int(1) == 255
1257 with domain_range_scale("100"):
1258 assert to_domain_int(1) == 2.55
1260 with domain_range_scale("100"):
1261 assert to_domain_int(1, 10) == 10.23
1263 with domain_range_scale("100"):
1264 assert to_domain_int(1, dtype=np.float16).dtype == np.float16
1267class TestFromRange1(unittest.TestCase):
1268 """
1269 Define :func:`colour.utilities.common.from_range_1` definition unit
1270 tests methods.
1271 """
1273 def test_from_range_1(self) -> None:
1274 """Test :func:`colour.utilities.common.from_range_1` definition."""
1276 with domain_range_scale("Reference"):
1277 assert from_range_1(1) == 1
1279 with domain_range_scale("1"):
1280 assert from_range_1(1) == 1
1282 with domain_range_scale("100"):
1283 assert from_range_1(1) == 100
1285 with domain_range_scale("100"):
1286 assert from_range_1(1, np.pi) == 1 * np.pi
1289class TestFromRange10(unittest.TestCase):
1290 """
1291 Define :func:`colour.utilities.common.from_range_10` definition unit
1292 tests methods.
1293 """
1295 def test_from_range_10(self) -> None:
1296 """Test :func:`colour.utilities.common.from_range_10` definition."""
1298 with domain_range_scale("Reference"):
1299 assert from_range_10(1) == 1
1301 with domain_range_scale("1"):
1302 assert from_range_10(1) == 0.1
1304 with domain_range_scale("100"):
1305 assert from_range_10(1) == 10
1307 with domain_range_scale("100"):
1308 assert from_range_10(1, np.pi) == 1 * np.pi
1311class TestFromRange100(unittest.TestCase):
1312 """
1313 Define :func:`colour.utilities.common.from_range_100` definition unit
1314 tests methods.
1315 """
1317 def test_from_range_100(self) -> None:
1318 """Test :func:`colour.utilities.common.from_range_100` definition."""
1320 with domain_range_scale("Reference"):
1321 assert from_range_100(1) == 1
1323 with domain_range_scale("1"):
1324 assert from_range_100(1) == 0.01
1326 with domain_range_scale("100"):
1327 assert from_range_100(1) == 1
1329 with domain_range_scale("1"):
1330 assert from_range_100(1, np.pi) == 1 / np.pi
1333class TestFromRangeDegrees(unittest.TestCase):
1334 """
1335 Define :func:`colour.utilities.common.from_range_degrees` definition unit
1336 tests methods.
1337 """
1339 def test_from_range_degrees(self) -> None:
1340 """Test :func:`colour.utilities.common.from_range_degrees` definition."""
1342 with domain_range_scale("Reference"):
1343 assert from_range_degrees(1) == 1
1345 with domain_range_scale("1"):
1346 assert from_range_degrees(1) == 1 / 360
1348 with domain_range_scale("100"):
1349 assert from_range_degrees(1) == 1 / 3.6
1351 with domain_range_scale("100"):
1352 assert from_range_degrees(1, np.pi) == 1 / (np.pi / 100)
1355class TestFromRangeInt(unittest.TestCase):
1356 """
1357 Define :func:`colour.utilities.common.from_range_int` definition unit
1358 tests methods.
1359 """
1361 def test_from_range_int(self) -> None:
1362 """Test :func:`colour.utilities.common.from_range_int` definition."""
1364 with domain_range_scale("Reference"):
1365 assert from_range_int(1) == 1
1367 with domain_range_scale("1"):
1368 assert from_range_int(1) == 1 / 255
1370 with domain_range_scale("100"):
1371 assert from_range_int(1) == 1 / 2.55
1373 with domain_range_scale("100"):
1374 assert from_range_int(1, 10) == 1 / (1023 / 100)
1376 with domain_range_scale("100"):
1377 assert from_range_int(1, dtype=np.float16).dtype == np.float16
1380class TestIsNdarrayCopyEnabled(unittest.TestCase):
1381 """
1382 Define :func:`colour.utilities.array.is_ndarray_copy_enabled` definition
1383 unit tests methods.
1384 """
1386 def test_is_ndarray_copy_enabled(self) -> None:
1387 """
1388 Test :func:`colour.utilities.array.is_ndarray_copy_enabled` definition.
1389 """
1391 with ndarray_copy_enable(True):
1392 assert is_ndarray_copy_enabled()
1394 with ndarray_copy_enable(False):
1395 assert not is_ndarray_copy_enabled()
1398class TestSetNdarrayCopyEnabled(unittest.TestCase):
1399 """
1400 Define :func:`colour.utilities.array.set_ndarray_copy_enable` definition
1401 unit tests methods.
1402 """
1404 def test_set_ndarray_copy_enable(self) -> None:
1405 """
1406 Test :func:`colour.utilities.array.set_ndarray_copy_enable` definition.
1407 """
1409 with ndarray_copy_enable(is_ndarray_copy_enabled()):
1410 set_ndarray_copy_enable(True)
1411 assert is_ndarray_copy_enabled()
1413 with ndarray_copy_enable(is_ndarray_copy_enabled()):
1414 set_ndarray_copy_enable(False)
1415 assert not is_ndarray_copy_enabled()
1418class TestNdarrayCopyEnable(unittest.TestCase):
1419 """
1420 Define :func:`colour.utilities.array.ndarray_copy_enable` definition unit
1421 tests methods.
1422 """
1424 def test_ndarray_copy_enable(self) -> None:
1425 """
1426 Test :func:`colour.utilities.array.ndarray_copy_enable` definition.
1427 """
1429 with ndarray_copy_enable(True):
1430 assert is_ndarray_copy_enabled()
1432 with ndarray_copy_enable(False):
1433 assert not is_ndarray_copy_enabled()
1435 @ndarray_copy_enable(True)
1436 def fn_a() -> None:
1437 """:func:`ndarray_copy_enable` unit tests :func:`fn_a` definition."""
1439 assert is_ndarray_copy_enabled()
1441 fn_a()
1443 @ndarray_copy_enable(False)
1444 def fn_b() -> None:
1445 """:func:`ndarray_copy_enable` unit tests :func:`fn_b` definition."""
1447 assert not is_ndarray_copy_enabled()
1449 fn_b()
1452class TestNdarrayCopy(unittest.TestCase):
1453 """
1454 Define :func:`colour.utilities.array.ndarray_copy` definition unit
1455 tests methods.
1456 """
1458 def test_ndarray_copy(self) -> None:
1459 """Test :func:`colour.utilities.array.ndarray_copy` definition."""
1461 a = np.linspace(0, 1, 10)
1462 with ndarray_copy_enable(True):
1463 assert id(ndarray_copy(a)) != id(a)
1465 with ndarray_copy_enable(False):
1466 assert id(ndarray_copy(a)) == id(a)
1469class TestClosestIndexes(unittest.TestCase):
1470 """
1471 Define :func:`colour.utilities.array.closest_indexes` definition unit
1472 tests methods.
1473 """
1475 def test_closest_indexes(self) -> None:
1476 """Test :func:`colour.utilities.array.closest_indexes` definition."""
1478 a = np.array(
1479 [
1480 24.31357115,
1481 63.62396289,
1482 55.71528816,
1483 62.70988028,
1484 46.84480573,
1485 25.40026416,
1486 ]
1487 )
1489 assert closest_indexes(a, 63.05) == 3
1491 assert closest_indexes(a, 51.15) == 4
1493 assert closest_indexes(a, 24.90) == 5
1495 np.testing.assert_array_equal(
1496 closest_indexes(a, np.array([63.05, 51.15, 24.90])),
1497 np.array([3, 4, 5]),
1498 )
1501class TestClosest(unittest.TestCase):
1502 """
1503 Define :func:`colour.utilities.array.closest` definition unit tests
1504 methods.
1505 """
1507 def test_closest(self) -> None:
1508 """Test :func:`colour.utilities.array.closest` definition."""
1510 a = np.array(
1511 [
1512 24.31357115,
1513 63.62396289,
1514 55.71528816,
1515 62.70988028,
1516 46.84480573,
1517 25.40026416,
1518 ]
1519 )
1521 assert closest(a, 63.05) == 62.70988028
1523 assert closest(a, 51.15) == 46.84480573
1525 assert closest(a, 24.90) == 25.40026416
1527 np.testing.assert_allclose(
1528 closest(a, np.array([63.05, 51.15, 24.90])),
1529 np.array([62.70988028, 46.84480573, 25.40026416]),
1530 atol=TOLERANCE_ABSOLUTE_TESTS,
1531 )
1534class TestInterval(unittest.TestCase):
1535 """
1536 Define :func:`colour.utilities.array.interval` definition unit tests
1537 methods.
1538 """
1540 def test_interval(self) -> None:
1541 """Test :func:`colour.utilities.array.interval` definition."""
1543 np.testing.assert_array_equal(interval(range(0, 10, 2)), np.array([2]))
1545 np.testing.assert_array_equal(
1546 interval(range(0, 10, 2), False), np.array([2, 2, 2, 2])
1547 )
1549 np.testing.assert_allclose(
1550 interval([1, 2, 3, 4, 6, 6.5]),
1551 np.array([0.5, 1.0, 2.0]),
1552 atol=TOLERANCE_ABSOLUTE_TESTS,
1553 )
1555 np.testing.assert_allclose(
1556 interval([1, 2, 3, 4, 6, 6.5], False),
1557 np.array([1.0, 1.0, 1.0, 2.0, 0.5]),
1558 atol=TOLERANCE_ABSOLUTE_TESTS,
1559 )
1562class TestIsUniform(unittest.TestCase):
1563 """
1564 Define :func:`colour.utilities.array.is_uniform` definition unit tests
1565 methods.
1566 """
1568 def test_is_uniform(self) -> None:
1569 """Test :func:`colour.utilities.array.is_uniform` definition."""
1571 assert is_uniform(range(0, 10, 2))
1573 assert not is_uniform([1, 2, 3, 4, 6])
1576class TestInArray(unittest.TestCase):
1577 """
1578 Define :func:`colour.utilities.array.in_array` definition unit tests
1579 methods.
1580 """
1582 def test_in_array(self) -> None:
1583 """Test :func:`colour.utilities.array.in_array` definition."""
1585 assert np.array_equal(
1586 in_array(np.array([0.50, 0.60]), np.linspace(0, 10, 101)),
1587 np.array([True, True]),
1588 )
1590 assert not np.array_equal(
1591 in_array(np.array([0.50, 0.61]), np.linspace(0, 10, 101)),
1592 np.array([True, True]),
1593 )
1595 assert np.array_equal(
1596 in_array(np.array([[0.50], [0.60]]), np.linspace(0, 10, 101)),
1597 np.array([[True], [True]]),
1598 )
1600 def test_n_dimensional_in_array(self) -> None:
1601 """
1602 Test :func:`colour.utilities.array.in_array` definition n-dimensional
1603 support.
1604 """
1606 np.testing.assert_array_equal(
1607 in_array(np.array([0.50, 0.60]), np.linspace(0, 10, 101)).shape,
1608 np.array([2]),
1609 )
1611 np.testing.assert_array_equal(
1612 in_array(np.array([[0.50, 0.60]]), np.linspace(0, 10, 101)).shape,
1613 np.array([1, 2]),
1614 )
1616 np.testing.assert_array_equal(
1617 in_array(np.array([[0.50], [0.60]]), np.linspace(0, 10, 101)).shape,
1618 np.array([2, 1]),
1619 )
1622class TestTstack(unittest.TestCase):
1623 """
1624 Define :func:`colour.utilities.array.tstack` definition unit tests
1625 methods.
1626 """
1628 def test_tstack(self) -> None:
1629 """Test :func:`colour.utilities.array.tstack` definition."""
1631 a = 0
1632 np.testing.assert_array_equal(tstack([a, a, a]), np.array([0, 0, 0]))
1634 a = np.arange(0, 6)
1635 np.testing.assert_array_equal(
1636 tstack([a, a, a]),
1637 np.array(
1638 [
1639 [0, 0, 0],
1640 [1, 1, 1],
1641 [2, 2, 2],
1642 [3, 3, 3],
1643 [4, 4, 4],
1644 [5, 5, 5],
1645 ]
1646 ),
1647 )
1649 a = np.reshape(a, (1, 6))
1650 np.testing.assert_array_equal(
1651 tstack([a, a, a]),
1652 np.array(
1653 [
1654 [
1655 [0, 0, 0],
1656 [1, 1, 1],
1657 [2, 2, 2],
1658 [3, 3, 3],
1659 [4, 4, 4],
1660 [5, 5, 5],
1661 ]
1662 ]
1663 ),
1664 )
1666 a = np.reshape(a, (1, 2, 3))
1667 np.testing.assert_array_equal(
1668 tstack([a, a, a]),
1669 np.array(
1670 [
1671 [
1672 [[0, 0, 0], [1, 1, 1], [2, 2, 2]],
1673 [[3, 3, 3], [4, 4, 4], [5, 5, 5]],
1674 ]
1675 ]
1676 ),
1677 )
1679 # Ensuring that contiguity is maintained.
1680 a = np.array([0, 1, 2], dtype=DTYPE_FLOAT_DEFAULT)
1681 b = tstack([a, a, a])
1682 assert b.flags.contiguous
1684 # Ensuring that independence is maintained.
1685 a *= 2
1686 np.testing.assert_array_equal(
1687 b,
1688 np.array(
1689 [
1690 [0, 0, 0],
1691 [1, 1, 1],
1692 [2, 2, 2],
1693 ],
1694 ),
1695 )
1697 a = np.array([0, 1, 2], dtype=DTYPE_FLOAT_DEFAULT)
1698 b = tstack([a, a, a])
1700 b[1] *= 2
1701 np.testing.assert_array_equal(
1702 a,
1703 np.array([0, 1, 2]),
1704 )
1707class TestTsplit(unittest.TestCase):
1708 """
1709 Define :func:`colour.utilities.array.tsplit` definition unit tests
1710 methods.
1711 """
1713 def test_tsplit(self) -> None:
1714 """Test :func:`colour.utilities.array.tsplit` definition."""
1716 a = np.array([0, 0, 0])
1717 np.testing.assert_array_equal(tsplit(a), np.array([0, 0, 0]))
1718 a = np.array(
1719 [
1720 [0, 0, 0],
1721 [1, 1, 1],
1722 [2, 2, 2],
1723 [3, 3, 3],
1724 [4, 4, 4],
1725 [5, 5, 5],
1726 ]
1727 )
1728 np.testing.assert_array_equal(
1729 tsplit(a),
1730 np.array(
1731 [
1732 [0, 1, 2, 3, 4, 5],
1733 [0, 1, 2, 3, 4, 5],
1734 [0, 1, 2, 3, 4, 5],
1735 ]
1736 ),
1737 )
1739 a = np.array(
1740 [
1741 [
1742 [0, 0, 0],
1743 [1, 1, 1],
1744 [2, 2, 2],
1745 [3, 3, 3],
1746 [4, 4, 4],
1747 [5, 5, 5],
1748 ],
1749 ]
1750 )
1751 np.testing.assert_array_equal(
1752 tsplit(a),
1753 np.array(
1754 [
1755 [[0, 1, 2, 3, 4, 5]],
1756 [[0, 1, 2, 3, 4, 5]],
1757 [[0, 1, 2, 3, 4, 5]],
1758 ]
1759 ),
1760 )
1762 a = np.array(
1763 [
1764 [
1765 [[0, 0, 0], [1, 1, 1], [2, 2, 2]],
1766 [[3, 3, 3], [4, 4, 4], [5, 5, 5]],
1767 ]
1768 ]
1769 )
1770 np.testing.assert_array_equal(
1771 tsplit(a),
1772 np.array(
1773 [
1774 [[[0, 1, 2], [3, 4, 5]]],
1775 [[[0, 1, 2], [3, 4, 5]]],
1776 [[[0, 1, 2], [3, 4, 5]]],
1777 ]
1778 ),
1779 )
1781 # Ensuring that contiguity is maintained.
1782 a = np.array(
1783 [
1784 [0, 0, 0],
1785 [1, 1, 1],
1786 [2, 2, 2],
1787 ],
1788 dtype=DTYPE_FLOAT_DEFAULT,
1789 )
1790 b = tsplit(a)
1791 assert b.flags.contiguous
1793 # Ensuring that independence is maintained.
1794 a *= 2
1795 np.testing.assert_array_equal(
1796 b,
1797 np.array(
1798 [
1799 [0, 1, 2],
1800 [0, 1, 2],
1801 [0, 1, 2],
1802 ]
1803 ),
1804 )
1806 a = np.array(
1807 [
1808 [0, 0, 0],
1809 [1, 1, 1],
1810 [2, 2, 2],
1811 ],
1812 dtype=DTYPE_FLOAT_DEFAULT,
1813 )
1814 b = tsplit(a)
1816 b[1] *= 2
1817 np.testing.assert_array_equal(
1818 a,
1819 np.array(
1820 [
1821 [0, 0, 0],
1822 [1, 1, 1],
1823 [2, 2, 2],
1824 ]
1825 ),
1826 )
1829class TestRowAsDiagonal(unittest.TestCase):
1830 """
1831 Define :func:`colour.utilities.array.row_as_diagonal` definition unit
1832 tests methods.
1833 """
1835 def test_row_as_diagonal(self) -> None:
1836 """Test :func:`colour.utilities.array.row_as_diagonal` definition."""
1838 np.testing.assert_allclose(
1839 row_as_diagonal(
1840 np.array(
1841 [
1842 [0.25891593, 0.07299478, 0.36586996],
1843 [0.30851087, 0.37131459, 0.16274825],
1844 [0.71061831, 0.67718718, 0.09562581],
1845 [0.71588836, 0.76772047, 0.15476079],
1846 [0.92985142, 0.22263399, 0.88027331],
1847 ]
1848 )
1849 ),
1850 np.array(
1851 [
1852 [
1853 [0.25891593, 0.00000000, 0.00000000],
1854 [0.00000000, 0.07299478, 0.00000000],
1855 [0.00000000, 0.00000000, 0.36586996],
1856 ],
1857 [
1858 [0.30851087, 0.00000000, 0.00000000],
1859 [0.00000000, 0.37131459, 0.00000000],
1860 [0.00000000, 0.00000000, 0.16274825],
1861 ],
1862 [
1863 [0.71061831, 0.00000000, 0.00000000],
1864 [0.00000000, 0.67718718, 0.00000000],
1865 [0.00000000, 0.00000000, 0.09562581],
1866 ],
1867 [
1868 [0.71588836, 0.00000000, 0.00000000],
1869 [0.00000000, 0.76772047, 0.00000000],
1870 [0.00000000, 0.00000000, 0.15476079],
1871 ],
1872 [
1873 [0.92985142, 0.00000000, 0.00000000],
1874 [0.00000000, 0.22263399, 0.00000000],
1875 [0.00000000, 0.00000000, 0.88027331],
1876 ],
1877 ]
1878 ),
1879 atol=TOLERANCE_ABSOLUTE_TESTS,
1880 )
1883class TestOrient(unittest.TestCase):
1884 """
1885 Define :func:`colour.utilities.array.orient` definition unit tests
1886 methods.
1887 """
1889 def test_orient(self) -> None:
1890 """Test :func:`colour.utilities.array.orient` definition."""
1892 a = np.tile(np.arange(5), (5, 1))
1894 np.testing.assert_array_equal(
1895 orient(a, "Flip"),
1896 np.array(
1897 [
1898 [4, 3, 2, 1, 0],
1899 [4, 3, 2, 1, 0],
1900 [4, 3, 2, 1, 0],
1901 [4, 3, 2, 1, 0],
1902 [4, 3, 2, 1, 0],
1903 ]
1904 ),
1905 )
1907 np.testing.assert_array_equal(
1908 orient(a, "Flop"),
1909 np.array(
1910 [
1911 [0, 1, 2, 3, 4],
1912 [0, 1, 2, 3, 4],
1913 [0, 1, 2, 3, 4],
1914 [0, 1, 2, 3, 4],
1915 [0, 1, 2, 3, 4],
1916 ]
1917 ),
1918 )
1920 np.testing.assert_array_equal(
1921 orient(a, "90 CW"),
1922 np.array(
1923 [
1924 [0, 0, 0, 0, 0],
1925 [1, 1, 1, 1, 1],
1926 [2, 2, 2, 2, 2],
1927 [3, 3, 3, 3, 3],
1928 [4, 4, 4, 4, 4],
1929 ]
1930 ),
1931 )
1933 np.testing.assert_array_equal(
1934 orient(a, "90 CCW"),
1935 np.array(
1936 [
1937 [4, 4, 4, 4, 4],
1938 [3, 3, 3, 3, 3],
1939 [2, 2, 2, 2, 2],
1940 [1, 1, 1, 1, 1],
1941 [0, 0, 0, 0, 0],
1942 ]
1943 ),
1944 )
1946 np.testing.assert_array_equal(
1947 orient(a, "180"),
1948 np.array(
1949 [
1950 [4, 3, 2, 1, 0],
1951 [4, 3, 2, 1, 0],
1952 [4, 3, 2, 1, 0],
1953 [4, 3, 2, 1, 0],
1954 [4, 3, 2, 1, 0],
1955 ]
1956 ),
1957 )
1959 np.testing.assert_array_equal(orient(a), a)
1962class TestCentroid(unittest.TestCase):
1963 """
1964 Define :func:`colour.utilities.array.centroid` definition unit tests
1965 methods.
1966 """
1968 def test_centroid(self) -> None:
1969 """Test :func:`colour.utilities.array.centroid` definition."""
1971 a = np.arange(5)
1972 np.testing.assert_array_equal(centroid(a), np.array([3]))
1974 a = np.tile(a, (5, 1))
1975 np.testing.assert_array_equal(centroid(a), np.array([2, 3]))
1977 a = np.tile(np.linspace(0, 1, 10), (10, 1))
1978 np.testing.assert_array_equal(centroid(a), np.array([4, 6]))
1980 a = tstack([a, a, a])
1981 np.testing.assert_array_equal(centroid(a), np.array([4, 6, 1]))
1984class TestFillNan(unittest.TestCase):
1985 """
1986 Define :func:`colour.utilities.array.fill_nan` definition unit tests
1987 methods.
1988 """
1990 def test_fill_nan(self) -> None:
1991 """Test :func:`colour.utilities.array.fill_nan` definition."""
1993 a = np.array([0.1, 0.2, np.nan, 0.4, 0.5])
1994 np.testing.assert_allclose(
1995 fill_nan(a),
1996 np.array([0.1, 0.2, 0.3, 0.4, 0.5]),
1997 atol=TOLERANCE_ABSOLUTE_TESTS,
1998 )
2000 np.testing.assert_allclose(
2001 fill_nan(a, method="Constant", default=8.0),
2002 np.array([0.1, 0.2, 8.0, 0.4, 0.5]),
2003 atol=TOLERANCE_ABSOLUTE_TESTS,
2004 )
2007class TestHasNanOnly(unittest.TestCase):
2008 """
2009 Define :func:`colour.utilities.array.has_only_nan` definition unit tests
2010 methods.
2011 """
2013 def test_has_only_nan(self) -> None:
2014 """Test :func:`colour.utilities.array.has_only_nan` definition."""
2016 assert has_only_nan(None) # pyright: ignore
2018 assert has_only_nan([None, None]) # pyright: ignore
2020 assert not has_only_nan([True, None]) # pyright: ignore
2022 assert not has_only_nan([0.1, np.nan, 0.3])
2025class TestNdarrayWrite(unittest.TestCase):
2026 """
2027 Define :func:`colour.utilities.array.ndarray_write` definition unit tests
2028 methods.
2029 """
2031 def test_ndarray_write(self) -> None:
2032 """Test :func:`colour.utilities.array.ndarray_write` definition."""
2034 a = np.linspace(0, 1, 10)
2035 a.setflags(write=False)
2037 with pytest.raises(ValueError):
2038 a += 1
2040 with ndarray_write(a):
2041 a += 1
2044class TestZeros(unittest.TestCase):
2045 """
2046 Define :func:`colour.utilities.array.zeros` definition unit tests
2047 methods.
2048 """
2050 def test_zeros(self) -> None:
2051 """Test :func:`colour.utilities.array.zeros` definition."""
2053 np.testing.assert_equal(zeros(3), np.zeros(3))
2056class TestOnes(unittest.TestCase):
2057 """
2058 Define :func:`colour.utilities.array.ones` definition unit tests
2059 methods.
2060 """
2062 def test_ones(self) -> None:
2063 """Test :func:`colour.utilities.array.ones` definition."""
2065 np.testing.assert_equal(ones(3), np.ones(3))
2068class TestFull(unittest.TestCase):
2069 """
2070 Define :func:`colour.utilities.array.full` definition unit tests
2071 methods.
2072 """
2074 def test_full(self) -> None:
2075 """Test :func:`colour.utilities.array.full` definition."""
2077 np.testing.assert_equal(full(3, 0.5), np.full(3, 0.5))
2080class TestIndexAlongLastAxis(unittest.TestCase):
2081 """
2082 Define :func:`colour.utilities.array.index_along_last_axis` definition
2083 unit tests methods.
2084 """
2086 def test_index_along_last_axis(self) -> None:
2087 """Test :func:`colour.utilities.array.index_along_last_axis` definition."""
2088 a = np.array(
2089 [
2090 [
2091 [
2092 [0.51090627, 0.86191718, 0.8687926],
2093 [0.82738158, 0.80587656, 0.28285687],
2094 ],
2095 [
2096 [0.84085977, 0.03851814, 0.06057988],
2097 [0.94659267, 0.79308353, 0.30870888],
2098 ],
2099 ],
2100 [
2101 [
2102 [0.50758436, 0.24066455, 0.20199051],
2103 [0.4507304, 0.84189245, 0.81160878],
2104 ],
2105 [
2106 [0.75421871, 0.88187494, 0.01612045],
2107 [0.38777511, 0.58905552, 0.32970469],
2108 ],
2109 ],
2110 [
2111 [
2112 [0.99285824, 0.738076, 0.0716432],
2113 [0.35847844, 0.0367514, 0.18586322],
2114 ],
2115 [
2116 [0.72674561, 0.0822759, 0.9771182],
2117 [0.90644279, 0.09689787, 0.93483977],
2118 ],
2119 ],
2120 ]
2121 )
2123 indexes = np.array([[[0, 1], [0, 1]], [[2, 1], [2, 1]], [[2, 1], [2, 0]]])
2125 np.testing.assert_equal(
2126 index_along_last_axis(a, indexes),
2127 np.array(
2128 [
2129 [[0.51090627, 0.80587656], [0.84085977, 0.79308353]],
2130 [[0.20199051, 0.84189245], [0.01612045, 0.58905552]],
2131 [[0.0716432, 0.0367514], [0.9771182, 0.90644279]],
2132 ]
2133 ),
2134 )
2136 def test_compare_with_argmin_argmax(self) -> None:
2137 """
2138 Test :func:`colour.utilities.array.index_along_last_axis` definition
2139 by comparison with :func:`argmin` and :func:`argmax`.
2140 """
2142 a = np.random.random((2, 3, 4, 5, 6, 7))
2144 np.testing.assert_equal(
2145 index_along_last_axis(a, np.argmin(a, axis=-1)), np.min(a, axis=-1)
2146 )
2148 np.testing.assert_equal(
2149 index_along_last_axis(a, np.argmax(a, axis=-1)), np.max(a, axis=-1)
2150 )
2152 def test_exceptions(self) -> None:
2153 """
2154 Test :func:`colour.utilities.array.index_along_last_axis` definition
2155 handling of invalid inputs.
2156 """
2158 a = as_float_array([[11, 12], [21, 22]])
2160 # Bad shape
2161 with pytest.raises(ValueError):
2162 indexes = np.array([0])
2163 index_along_last_axis(a, indexes)
2165 # Indexes out of range
2166 with pytest.raises(IndexError):
2167 indexes = np.array([123, 456])
2168 index_along_last_axis(a, indexes)
2170 # Non-int indexes
2171 with pytest.raises(IndexError):
2172 indexes = np.array([0.0, 0.0])
2173 index_along_last_axis(a, indexes)
2176class TestFormatArrayAsRow(unittest.TestCase):
2177 """
2178 Define :func:`colour.utilities.array.format_array_as_row` definition unit
2179 tests methods.
2180 """
2182 def test_format_array_as_row(self) -> None:
2183 """Test :func:`colour.utilities.array.format_array_as_row` definition."""
2185 assert format_array_as_row([1.25, 2.5, 3.75]) == "1.2500000 2.5000000 3.7500000"
2187 assert format_array_as_row([1.25, 2.5, 3.75], 3) == "1.250 2.500 3.750"
2189 assert format_array_as_row([1.25, 2.5, 3.75], 3, ", ") == "1.250, 2.500, 3.750"