Coverage for plotting/tests/test_common.py: 100%
183 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.plotting.common` module."""
3from __future__ import annotations
5import os
6import shutil
7import tempfile
8from functools import partial
10import matplotlib.font_manager
11import matplotlib.pyplot as plt
12import numpy as np
13from matplotlib.axes import Axes
14from matplotlib.figure import Figure
16import colour
17from colour.colorimetry import SDS_ILLUMINANTS
18from colour.constants import TOLERANCE_ABSOLUTE_TESTS
19from colour.hints import List, cast
20from colour.io import read_image
21from colour.models import RGB_COLOURSPACES, XYZ_to_sRGB, gamma_function
22from colour.plotting import (
23 ColourSwatch,
24 XYZ_to_plotting_colourspace,
25 artist,
26 camera,
27 colour_cycle,
28 colour_style,
29 filter_cmfs,
30 filter_colour_checkers,
31 filter_illuminants,
32 filter_passthrough,
33 filter_RGB_colourspaces,
34 font_scaling,
35 label_rectangles,
36 override_style,
37 plot_image,
38 plot_multi_colour_swatches,
39 plot_multi_functions,
40 plot_ray,
41 plot_single_colour_swatch,
42 plot_single_function,
43 render,
44 uniform_axes3d,
45 update_settings_collection,
46)
47from colour.utilities import attest
49__author__ = "Colour Developers"
50__copyright__ = "Copyright 2013 Colour Developers"
51__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
52__maintainer__ = "Colour Developers"
53__email__ = "colour-developers@colour-science.org"
54__status__ = "Production"
56__all__ = [
57 "TestColourStyle",
58 "TestOverrideStyle",
59 "TestFontScaling",
60 "TestXYZToPlottingColourspace",
61 "TestColourCycle",
62 "TestArtist",
63 "TestCamera",
64 "TestRender",
65 "TestLabelRectangles",
66 "TestUniformAxes3d",
67 "TestFilterPassthrough",
68 "TestFilterRgbColourspaces",
69 "TestFilterCmfs",
70 "TestFilterIlluminants",
71 "TestFilterColourCheckers",
72 "TestUpdateSettingsCollection",
73 "TestPlotSingleColourSwatch",
74 "TestPlotMultiColourSwatches",
75 "TestPlotSingleFunction",
76 "TestPlotMultiFunctions",
77 "TestPlotImage",
78 "TestPlotRay",
79]
82class TestColourStyle:
83 """
84 Define :func:`colour.plotting.common.colour_style` definition unit tests
85 methods.
86 """
88 def test_colour_style(self) -> None:
89 """Test :func:`colour.plotting.common.colour_style` definition."""
91 assert isinstance(colour_style(use_style=False), dict)
94class TestOverrideStyle:
95 """
96 Define :func:`colour.plotting.common.override_style` definition unit tests
97 methods.
98 """
100 def test_override_style(self) -> None:
101 """Test :func:`colour.plotting.common.override_style` definition."""
103 text_color = plt.rcParams["text.color"]
104 try:
106 @override_style(**{"text.color": "red"})
107 def test_text_color_override() -> None:
108 """Test :func:`colour.plotting.common.override_style` definition."""
110 attest(plt.rcParams["text.color"] == "red")
112 test_text_color_override()
113 finally:
114 plt.rcParams["text.color"] = text_color
117class TestFontScaling:
118 """
119 Define :func:`colour.plotting.common.font_scaling` definition unit tests
120 methods.
121 """
123 def test_font_scaling(self) -> None:
124 """Test :func:`colour.plotting.common.font_scaling` definition."""
126 with font_scaling("medium-colour-science", 2):
127 assert matplotlib.font_manager.font_scalings["medium-colour-science"] == 2
129 assert matplotlib.font_manager.font_scalings["medium-colour-science"] == 1
132class TestXYZToPlottingColourspace:
133 """
134 Define :func:`colour.plotting.common.XYZ_to_plotting_colourspace`
135 definition unit tests methods.
136 """
138 def test_XYZ_to_plotting_colourspace(self) -> None:
139 """
140 Test :func:`colour.plotting.common.XYZ_to_plotting_colourspace`
141 definition.
142 """
144 XYZ = np.random.random(3)
145 np.testing.assert_allclose(
146 XYZ_to_sRGB(XYZ),
147 XYZ_to_plotting_colourspace(XYZ),
148 atol=TOLERANCE_ABSOLUTE_TESTS,
149 )
152class TestColourCycle:
153 """
154 Define :func:`colour.plotting.common.colour_cycle` definition unit tests
155 methods.
156 """
158 def test_colour_cycle(self) -> None:
159 """Test :func:`colour.plotting.common.colour_cycle` definition."""
161 cycler = colour_cycle()
163 np.testing.assert_allclose(
164 next(cycler),
165 np.array([0.95686275, 0.26274510, 0.21176471, 1.00000000]),
166 atol=TOLERANCE_ABSOLUTE_TESTS,
167 )
169 np.testing.assert_allclose(
170 next(cycler),
171 np.array([0.61582468, 0.15423299, 0.68456747, 1.00000000]),
172 atol=TOLERANCE_ABSOLUTE_TESTS,
173 )
175 np.testing.assert_allclose(
176 next(cycler),
177 np.array([0.25564014, 0.31377163, 0.70934256, 1.00000000]),
178 atol=TOLERANCE_ABSOLUTE_TESTS,
179 )
181 cycler = colour_cycle(colour_cycle_map="viridis")
183 np.testing.assert_allclose(
184 next(cycler),
185 np.array([0.26700400, 0.00487400, 0.32941500, 1.00000000]),
186 atol=TOLERANCE_ABSOLUTE_TESTS,
187 )
190class TestArtist:
191 """
192 Define :func:`colour.plotting.common.artist` definition unit tests
193 methods.
194 """
196 def test_artist(self) -> None:
197 """Test :func:`colour.plotting.common.artist` definition."""
199 figure_1 = plt.figure()
200 axes = figure_1.subfigures().subfigures().gca() # pyright: ignore
202 figure_2, _axes = artist(axes=axes)
204 assert figure_1 is figure_2
206 plt.figure()
208 figure_2, _axes = artist(axes=axes)
209 assert figure_1 is figure_2
211 figure_1, axes_1 = artist()
213 assert isinstance(figure_1, Figure)
214 assert isinstance(axes_1, Axes)
216 _figure_2, axes_2 = artist(axes=axes_1, uniform=True)
217 assert axes_1 is axes_2
219 figure_3, _axes_3 = artist(uniform=True)
220 assert figure_3.get_figwidth() == figure_3.get_figheight()
223class TestCamera:
224 """
225 Define :func:`colour.plotting.common.camera` definition unit tests
226 methods.
227 """
229 def test_camera(self) -> None:
230 """Test :func:`colour.plotting.common.camera` definition."""
232 figure, _axes = artist()
233 axes = figure.add_subplot(111, projection="3d")
235 _figure, axes = camera(axes=axes, elevation=45, azimuth=90)
237 assert axes.elev == 45
238 assert axes.azim == 90
241class TestRender:
242 """
243 Define :func:`colour.plotting.common.render` definition unit tests
244 methods.
245 """
247 def setup_method(self) -> None:
248 """Initialise the common tests attributes."""
250 self._temporary_directory = tempfile.mkdtemp()
252 def teardown_method(self) -> None:
253 """After tests actions."""
255 shutil.rmtree(self._temporary_directory)
257 def test_render(self) -> None:
258 """Test :func:`colour.plotting.common.render` definition."""
260 figure, axes = artist()
262 render(
263 figure=figure,
264 axes=axes,
265 standalone=False,
266 aspect="equal",
267 axes_visible=True,
268 bounding_box=[0, 1, 0, 1],
269 tight_layout=False,
270 legend=True,
271 legend_columns=2,
272 transparent_background=False,
273 title="Render Unit Test",
274 wrap_title=True,
275 x_label="x Label",
276 y_label="y Label",
277 x_ticker=False,
278 y_ticker=False,
279 )
281 render(standalone=True)
283 render(
284 filename=os.path.join(self._temporary_directory, "render.png"),
285 axes_visible=False,
286 )
289class TestLabelRectangles:
290 """
291 Define :func:`colour.plotting.common.label_rectangles` definition unit
292 tests methods.
293 """
295 def test_label_rectangles(self) -> None:
296 """Test :func:`colour.plotting.common.label_rectangles` definition."""
298 figure, axes = artist()
300 samples = np.linspace(0, 1, 10)
302 _figure, axes = label_rectangles(
303 cast("List[float]", samples.tolist()),
304 axes.bar(samples, 1),
305 figure=figure,
306 axes=axes,
307 )
309 assert len(axes.texts) == len(samples)
312class TestUniformAxes3d:
313 """
314 Define :func:`colour.plotting.common.uniform_axes3d` definition unit tests
315 methods.
316 """
318 def test_uniform_axes3d(self) -> None:
319 """Test :func:`colour.plotting.common.uniform_axes3d` definition."""
321 figure, _axes = artist()
322 axes = figure.add_subplot(111, projection="3d")
324 uniform_axes3d(axes=axes)
326 assert axes.get_xlim() == axes.get_ylim()
327 assert axes.get_xlim() == axes.get_zlim()
330class TestFilterPassthrough:
331 """
332 Define :func:`colour.plotting.common.filter_passthrough` definition unit
333 tests methods.
334 """
336 def test_filter_passthrough(self) -> None:
337 """Test :func:`colour.plotting.common.filter_passthrough` definition."""
339 assert sorted(
340 colourspace.name
341 for colourspace in filter_passthrough(
342 RGB_COLOURSPACES, ["ACES2065-1"]
343 ).values()
344 ) == ["ACES2065-1"]
346 assert sorted(filter_passthrough(RGB_COLOURSPACES, ["aces2065-1"]).keys()) == [
347 "ACES2065-1"
348 ]
350 assert sorted(filter_passthrough(RGB_COLOURSPACES, ["aces20651"]).keys()) == [
351 "ACES2065-1"
352 ]
354 assert filter_passthrough(
355 SDS_ILLUMINANTS,
356 [SDS_ILLUMINANTS["D65"], {"Is": "Excluded"}],
357 allow_non_siblings=False,
358 ) == {"D65": SDS_ILLUMINANTS["D65"]}
360 assert filter_passthrough(
361 SDS_ILLUMINANTS,
362 [SDS_ILLUMINANTS["D65"], {"Is": "Included"}],
363 allow_non_siblings=True,
364 ) == {"D65": SDS_ILLUMINANTS["D65"], "Is": "Included"}
366 assert sorted(
367 element
368 for element in filter_passthrough(
369 {"John": "Doe", "Luke": "Skywalker"}, ["John"]
370 ).values()
371 ) == ["Doe", "John"]
374class TestFilterRgbColourspaces:
375 """
376 Define :func:`colour.plotting.common.filter_RGB_colourspaces` definition
377 unit tests methods.
378 """
380 def test_filter_RGB_colourspaces(self) -> None:
381 """
382 Test :func:`colour.plotting.common.filter_RGB_colourspaces`
383 definition.
384 """
386 assert sorted(
387 colourspace.name
388 for colourspace in filter_RGB_colourspaces(["ACES2065-1"]).values()
389 ) == ["ACES2065-1"]
392class TestFilterCmfs:
393 """
394 Define :func:`colour.plotting.common.filter_cmfs` definition unit tests
395 methods.
396 """
398 def test_filter_cmfs(self) -> None:
399 """Test :func:`colour.plotting.common.filter_cmfs` definition."""
401 assert sorted(
402 cmfs.name
403 for cmfs in filter_cmfs(["CIE 1931 2 Degree Standard Observer"]).values()
404 ) == [
405 "CIE 1931 2 Degree Standard Observer",
406 ]
409class TestFilterIlluminants:
410 """
411 Define :func:`colour.plotting.common.filter_illuminants` definition unit
412 tests methods.
413 """
415 def test_filter_illuminants(self) -> None:
416 """Test :func:`colour.plotting.common.filter_illuminants` definition."""
418 assert sorted(filter_illuminants(["D50"]).keys()) == ["D50"]
421class TestFilterColourCheckers:
422 """
423 Define :func:`colour.plotting.common.filter_colour_checkers` definition
424 unit tests methods.
425 """
427 def test_filter_colour_checkers(self) -> None:
428 """Test :func:`colour.plotting.common.filter_colour_checkers` definition."""
430 assert sorted(
431 colour_checker.name
432 for colour_checker in filter_colour_checkers(
433 ["ColorChecker24 - After November 2014"]
434 ).values()
435 ) == [
436 "ColorChecker24 - After November 2014",
437 ]
440class TestUpdateSettingsCollection:
441 """
442 Define :func:`colour.plotting.common.update_settings_collection`
443 definition unit tests methods.
444 """
446 def test_update_settings_collection(self) -> None:
447 """
448 Test :func:`colour.plotting.common.update_settings_collection`
449 definition.
450 """
452 settings_collection = [{1: 2}, {3: 4}]
453 keyword_arguments = {5: 6}
454 update_settings_collection(settings_collection, keyword_arguments, 2)
455 assert settings_collection == [{1: 2, 5: 6}, {3: 4, 5: 6}]
457 settings_collection = [{1: 2}, {3: 4}]
458 keyword_arguments = [{5: 6}, {7: 8}]
459 update_settings_collection(settings_collection, keyword_arguments, 2)
460 assert settings_collection == [{1: 2, 5: 6}, {3: 4, 7: 8}]
463class TestPlotSingleColourSwatch:
464 """
465 Define :func:`colour.plotting.common.plot_single_colour_swatch` definition
466 unit tests methods.
467 """
469 def test_plot_single_colour_swatch(self) -> None:
470 """
471 Test :func:`colour.plotting.common.plot_single_colour_swatch`
472 definition.
473 """
475 figure, axes = plot_single_colour_swatch(
476 ColourSwatch((0.45620519, 0.03081071, 0.04091952))
477 )
479 assert isinstance(figure, Figure)
480 assert isinstance(axes, Axes)
482 figure, axes = plot_single_colour_swatch(
483 np.array([0.45620519, 0.03081071, 0.04091952])
484 )
486 assert isinstance(figure, Figure)
487 assert isinstance(axes, Axes)
490class TestPlotMultiColourSwatches:
491 """
492 Define :func:`colour.plotting.common.plot_multi_colour_swatches`
493 definition unit tests methods.
494 """
496 def test_plot_multi_colour_swatches(self) -> None:
497 """
498 Test :func:`colour.plotting.common.plot_multi_colour_swatches`
499 definition.
500 """
502 figure, axes = plot_multi_colour_swatches(
503 [
504 ColourSwatch((0.45293517, 0.31732158, 0.26414773)),
505 ColourSwatch((0.77875824, 0.57726450, 0.50453169)),
506 ]
507 )
509 assert isinstance(figure, Figure)
510 assert isinstance(axes, Axes)
512 figure, axes = plot_multi_colour_swatches(
513 np.array(
514 [
515 [0.45293517, 0.31732158, 0.26414773],
516 [0.77875824, 0.57726450, 0.50453169],
517 ]
518 ),
519 direction="-y",
520 )
522 assert isinstance(figure, Figure)
523 assert isinstance(axes, Axes)
526class TestPlotSingleFunction:
527 """
528 Define :func:`colour.plotting.common.plot_single_function` definition unit
529 tests methods.
530 """
532 def test_plot_single_function(self) -> None:
533 """Test :func:`colour.plotting.common.plot_single_function` definition."""
535 figure, axes = plot_single_function(partial(gamma_function, exponent=1 / 2.2))
537 assert isinstance(figure, Figure)
538 assert isinstance(axes, Axes)
541class TestPlotMultiFunctions:
542 """
543 Define :func:`colour.plotting.common.plot_multi_functions` definition unit
544 tests methods.
545 """
547 def test_plot_multi_functions(self) -> None:
548 """Test :func:`colour.plotting.common.plot_multi_functions` definition."""
550 functions = {
551 "Gamma 2.2": lambda x: x ** (1 / 2.2),
552 "Gamma 2.4": lambda x: x ** (1 / 2.4),
553 "Gamma 2.6": lambda x: x ** (1 / 2.6),
554 }
555 plot_kwargs = {"c": "r"}
556 figure, axes = plot_multi_functions(functions, plot_kwargs=plot_kwargs)
558 assert isinstance(figure, Figure)
559 assert isinstance(axes, Axes)
561 plot_kwargs = [{"c": "r"}, {"c": "g"}, {"c": "b"}]
562 figure, axes = plot_multi_functions(
563 functions, log_x=10, log_y=10, plot_kwargs=plot_kwargs
564 )
566 assert isinstance(figure, Figure)
567 assert isinstance(axes, Axes)
569 figure, axes = plot_multi_functions(functions, log_x=10)
571 assert isinstance(figure, Figure)
572 assert isinstance(axes, Axes)
574 figure, axes = plot_multi_functions(functions, log_y=10)
576 assert isinstance(figure, Figure)
577 assert isinstance(axes, Axes)
580class TestPlotImage:
581 """
582 Define :func:`colour.plotting.common.plot_image` definition unit tests
583 methods.
584 """
586 def test_plot_image(self) -> None:
587 """Test :func:`colour.plotting.common.plot_image` definition."""
589 path = os.path.join(
590 colour.__path__[0], "..", "docs", "_static", "Logo_Medium_001.png"
591 )
593 # Distribution does not ship the documentation thus we are skipping
594 # this unit test if the image does not exist.
595 if not os.path.exists(path): # pragma: no cover
596 return
598 figure, axes = plot_image(read_image(path))
600 assert isinstance(figure, Figure)
601 assert isinstance(axes, Axes)
604class TestPlotRay:
605 """
606 Define :func:`colour.plotting.common.plot_ray` definition unit tests
607 methods.
608 """
610 def test_plot_ray(self) -> None:
611 """Test :func:`colour.plotting.common.plot_ray` definition."""
613 figure, axes = plt.subplots()
614 x = np.array([0, 1, 2])
615 y = np.array([0, 1, 0])
617 # plot_ray returns None, so we test all variations and ensure no exceptions
618 plot_ray(
619 axes, x, y, style="solid", label="Ray", show_arrow=True, show_dots=True
620 )
622 plot_ray(axes, x, y, style="dashed", show_arrow=False, show_dots=False)
624 plt.close(figure)
626 # If we reach here without exceptions, the test passes
627 assert True