Coverage for colour/graph/tests/test_conversion.py: 100%
61 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"""Define the unit tests for the :mod:`colour.graph.conversion` module."""
3from __future__ import annotations
5import numpy as np
6import pytest
8from colour.characterisation import SDS_COLOURCHECKERS
9from colour.colorimetry import CCS_ILLUMINANTS, SDS_ILLUMINANTS
10from colour.constants import TOLERANCE_ABSOLUTE_TESTS
11from colour.graph import convert, describe_conversion_path
12from colour.models import COLOURSPACE_MODELS, RGB_COLOURSPACE_ACES2065_1, XYZ_to_Lab
13from colour.utilities import get_domain_range_scale_metadata
15__author__ = "Colour Developers"
16__copyright__ = "Copyright 2013 Colour Developers"
17__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
18__maintainer__ = "Colour Developers"
19__email__ = "colour-developers@colour-science.org"
20__status__ = "Production"
22__all__ = [
23 "TestDescribeConversionPath",
24 "TestConvert",
25]
28class TestDescribeConversionPath:
29 """
30 Define :func:`colour.graph.conversion.describe_conversion_path` definition
31 unit tests methods.
32 """
34 def test_describe_conversion_path(self) -> None:
35 """
36 Test :func:`colour.graph.conversion.describe_conversion_path`
37 definition.
38 """
40 describe_conversion_path("Spectral Distribution", "sRGB")
42 describe_conversion_path("Spectral Distribution", "sRGB", mode="Long")
44 describe_conversion_path(
45 "Spectral Distribution",
46 "sRGB",
47 mode="Extended",
48 sd_to_XYZ={
49 "illuminant": SDS_ILLUMINANTS["FL2"],
50 "return": np.array([0.47924575, 0.31676968, 0.17362725]),
51 },
52 )
55class TestConvert:
56 """
57 Define :func:`colour.graph.conversion.convert` definition unit tests
58 methods.
59 """
61 def test_convert(self) -> None:
62 """Test :func:`colour.graph.conversion.convert` definition."""
64 RGB_a = convert(
65 SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"],
66 "Spectral Distribution",
67 "sRGB",
68 )
69 np.testing.assert_allclose(
70 RGB_a,
71 np.array([0.49034776, 0.30185875, 0.23587685]),
72 atol=TOLERANCE_ABSOLUTE_TESTS,
73 )
75 Jpapbp = convert(RGB_a, "Output-Referred RGB", "CAM16UCS")
76 np.testing.assert_allclose(
77 Jpapbp,
78 np.array([0.40738741, 0.12046560, 0.09284385]),
79 atol=TOLERANCE_ABSOLUTE_TESTS,
80 )
82 RGB_b = convert(Jpapbp, "CAM16UCS", "sRGB", verbose={"mode": "Extended"})
83 # NOTE: The "CIE XYZ" tristimulus values to "sRGB" matrix is given
84 # rounded at 4 decimals as per "IEC 61966-2-1:1999" and thus preventing
85 # exact roundtrip.
86 np.testing.assert_allclose(RGB_a, RGB_b, atol=1e-4)
88 np.testing.assert_allclose(
89 convert("#808080", "Hexadecimal", "Scene-Referred RGB"),
90 np.array([0.21586050, 0.21586050, 0.21586050]),
91 atol=TOLERANCE_ABSOLUTE_TESTS,
92 )
94 np.testing.assert_allclose(
95 convert("#808080", "Hexadecimal", "RGB Luminance"),
96 0.21586050,
97 atol=TOLERANCE_ABSOLUTE_TESTS,
98 )
100 np.testing.assert_allclose(
101 convert(
102 convert(
103 np.array([0.5, 0.5, 0.5]),
104 "Output-Referred RGB",
105 "Scene-Referred RGB",
106 ),
107 "RGB",
108 "YCbCr",
109 ),
110 np.array([0.49215686, 0.50196078, 0.50196078]),
111 atol=TOLERANCE_ABSOLUTE_TESTS,
112 )
114 np.testing.assert_allclose(
115 convert(
116 RGB_a,
117 "RGB",
118 "Scene-Referred RGB",
119 RGB_to_RGB={"output_colourspace": RGB_COLOURSPACE_ACES2065_1},
120 ),
121 np.array([0.37308227, 0.31241444, 0.24746366]),
122 atol=TOLERANCE_ABSOLUTE_TESTS,
123 )
125 # Consistency check to verify that all the colour models are properly
126 # named in the graph:
127 for model in COLOURSPACE_MODELS:
128 convert(
129 np.array([0.20654008, 0.12197225, 0.05136952]),
130 "CIE XYZ",
131 model,
132 )
134 def test_convert_direct_keyword_argument_passing(self) -> None:
135 """
136 Test :func:`colour.graph.conversion.convert` definition behaviour when
137 direct keyword arguments are passed.
138 """
140 a = np.array([0.20654008, 0.12197225, 0.05136952])
141 illuminant = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D50"]
142 np.testing.assert_allclose(
143 convert(a, "CIE XYZ", "CIE UVW", XYZ_to_UVW={"illuminant": illuminant}),
144 convert(a, "CIE XYZ", "CIE UVW", illuminant=illuminant),
145 atol=TOLERANCE_ABSOLUTE_TESTS,
146 )
148 # Illuminant "ndarray" is converted to tuple here so that it can
149 # be hashed by the "sd_to_XYZ" definition, this should never occur
150 # in practical application.
151 pytest.raises(
152 AttributeError,
153 lambda: convert(
154 SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"],
155 "Spectral Distribution",
156 "sRGB",
157 illuminant=tuple(illuminant),
158 ),
159 )
161 def test_convert_reference_scale(self) -> None:
162 """
163 Test :func:`colour.graph.conversion.convert` definition behaviour with
164 `from_reference_scale` and `to_reference_scale` parameters.
165 """
167 XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
169 Lab_auto = convert(XYZ, "CIE XYZ", "CIE Lab", to_reference_scale=True)
171 Lab_manual = convert(XYZ, "CIE XYZ", "CIE Lab")
172 metadata = get_domain_range_scale_metadata(XYZ_to_Lab)
173 range_scale = metadata["range"]
174 Lab_manual_scaled = Lab_manual * range_scale
176 np.testing.assert_allclose(
177 Lab_auto,
178 Lab_manual_scaled,
179 atol=TOLERANCE_ABSOLUTE_TESTS,
180 )
182 Lab_native = Lab_auto
184 XYZ_auto = convert(
185 Lab_native,
186 "CIE Lab",
187 "CIE XYZ",
188 from_reference_scale=True,
189 )
191 Lab_manual_normalized = Lab_native / range_scale
192 XYZ_manual = convert(Lab_manual_normalized, "CIE Lab", "CIE XYZ")
194 np.testing.assert_allclose(
195 XYZ_auto,
196 XYZ_manual,
197 atol=TOLERANCE_ABSOLUTE_TESTS,
198 )
200 XYZ_roundtrip = convert(
201 convert(
202 XYZ,
203 "CIE XYZ",
204 "CIE Lab",
205 to_reference_scale=True,
206 ),
207 "CIE Lab",
208 "CIE XYZ",
209 from_reference_scale=True,
210 )
212 np.testing.assert_allclose(
213 XYZ_roundtrip,
214 XYZ,
215 atol=TOLERANCE_ABSOLUTE_TESTS,
216 )
218 XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
220 # Test CIE Lab and CIE LCHab consistency
221 Lab = convert(XYZ, "CIE XYZ", "CIE Lab", to_reference_scale=True)
222 LCHab = convert(XYZ, "CIE XYZ", "CIE LCHab", to_reference_scale=True)
224 # L component should be identical
225 np.testing.assert_allclose(
226 Lab[0],
227 LCHab[0],
228 atol=TOLERANCE_ABSOLUTE_TESTS,
229 )
231 # C should equal sqrt(a^2 + b^2)
232 expected_C = np.sqrt(Lab[1] ** 2 + Lab[2] ** 2)
233 np.testing.assert_allclose(
234 LCHab[1],
235 expected_C,
236 atol=TOLERANCE_ABSOLUTE_TESTS,
237 )
239 # Test CIE Luv and CIE LCHuv consistency
240 Luv = convert(XYZ, "CIE XYZ", "CIE Luv", to_reference_scale=True)
241 LCHuv = convert(XYZ, "CIE XYZ", "CIE LCHuv", to_reference_scale=True)
243 # L component should be identical
244 np.testing.assert_allclose(
245 Luv[0],
246 LCHuv[0],
247 atol=TOLERANCE_ABSOLUTE_TESTS,
248 )
250 # C should equal sqrt(u^2 + v^2)
251 expected_C = np.sqrt(Luv[1] ** 2 + Luv[2] ** 2)
252 np.testing.assert_allclose(
253 LCHuv[1],
254 expected_C,
255 atol=TOLERANCE_ABSOLUTE_TESTS,
256 )