Coverage for colour/plotting/tests/test_common.py: 100%

175 statements  

« 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.plotting.common` module.""" 

2 

3from __future__ import annotations 

4 

5import os 

6import shutil 

7import tempfile 

8from functools import partial 

9 

10import matplotlib.font_manager 

11import matplotlib.pyplot as plt 

12import numpy as np 

13from matplotlib.axes import Axes 

14from matplotlib.figure import Figure 

15 

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 

48 

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" 

55 

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] 

80 

81 

82class TestColourStyle: 

83 """ 

84 Define :func:`colour.plotting.common.colour_style` definition unit tests 

85 methods. 

86 """ 

87 

88 def test_colour_style(self) -> None: 

89 """Test :func:`colour.plotting.common.colour_style` definition.""" 

90 

91 assert isinstance(colour_style(use_style=False), dict) 

92 

93 

94class TestOverrideStyle: 

95 """ 

96 Define :func:`colour.plotting.common.override_style` definition unit tests 

97 methods. 

98 """ 

99 

100 def test_override_style(self) -> None: 

101 """Test :func:`colour.plotting.common.override_style` definition.""" 

102 

103 text_color = plt.rcParams["text.color"] 

104 try: 

105 

106 @override_style(**{"text.color": "red"}) 

107 def test_text_color_override() -> None: 

108 """Test :func:`colour.plotting.common.override_style` definition.""" 

109 

110 attest(plt.rcParams["text.color"] == "red") 

111 

112 test_text_color_override() 

113 finally: 

114 plt.rcParams["text.color"] = text_color 

115 

116 

117class TestFontScaling: 

118 """ 

119 Define :func:`colour.plotting.common.font_scaling` definition unit tests 

120 methods. 

121 """ 

122 

123 def test_font_scaling(self) -> None: 

124 """Test :func:`colour.plotting.common.font_scaling` definition.""" 

125 

126 with font_scaling("medium-colour-science", 2): 

127 assert matplotlib.font_manager.font_scalings["medium-colour-science"] == 2 

128 

129 assert matplotlib.font_manager.font_scalings["medium-colour-science"] == 1 

130 

131 

132class TestXYZToPlottingColourspace: 

133 """ 

134 Define :func:`colour.plotting.common.XYZ_to_plotting_colourspace` 

135 definition unit tests methods. 

136 """ 

137 

138 def test_XYZ_to_plotting_colourspace(self) -> None: 

139 """ 

140 Test :func:`colour.plotting.common.XYZ_to_plotting_colourspace` 

141 definition. 

142 """ 

143 

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 ) 

150 

151 

152class TestColourCycle: 

153 """ 

154 Define :func:`colour.plotting.common.colour_cycle` definition unit tests 

155 methods. 

156 """ 

157 

158 def test_colour_cycle(self) -> None: 

159 """Test :func:`colour.plotting.common.colour_cycle` definition.""" 

160 

161 cycler = colour_cycle() 

162 

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 ) 

168 

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 ) 

174 

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 ) 

180 

181 cycler = colour_cycle(colour_cycle_map="viridis") 

182 

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 ) 

188 

189 

190class TestArtist: 

191 """ 

192 Define :func:`colour.plotting.common.artist` definition unit tests 

193 methods. 

194 """ 

195 

196 def test_artist(self) -> None: 

197 """Test :func:`colour.plotting.common.artist` definition.""" 

198 

199 figure_1 = plt.figure() 

200 axes = figure_1.subfigures().subfigures().gca() # pyright: ignore 

201 

202 figure_2, _axes = artist(axes=axes) 

203 

204 assert figure_1 is figure_2 

205 

206 plt.figure() 

207 

208 figure_2, _axes = artist(axes=axes) 

209 assert figure_1 is figure_2 

210 

211 figure_1, axes_1 = artist() 

212 

213 assert isinstance(figure_1, Figure) 

214 assert isinstance(axes_1, Axes) 

215 

216 _figure_2, axes_2 = artist(axes=axes_1, uniform=True) 

217 assert axes_1 is axes_2 

218 

219 figure_3, _axes_3 = artist(uniform=True) 

220 assert figure_3.get_figwidth() == figure_3.get_figheight() 

221 

222 

223class TestCamera: 

224 """ 

225 Define :func:`colour.plotting.common.camera` definition unit tests 

226 methods. 

227 """ 

228 

229 def test_camera(self) -> None: 

230 """Test :func:`colour.plotting.common.camera` definition.""" 

231 

232 figure, _axes = artist() 

233 axes = figure.add_subplot(111, projection="3d") 

234 

235 _figure, axes = camera(axes=axes, elevation=45, azimuth=90) 

236 

237 assert axes.elev == 45 

238 assert axes.azim == 90 

239 

240 

241class TestRender: 

242 """ 

243 Define :func:`colour.plotting.common.render` definition unit tests 

244 methods. 

245 """ 

246 

247 def setup_method(self) -> None: 

248 """Initialise the common tests attributes.""" 

249 

250 self._temporary_directory = tempfile.mkdtemp() 

251 

252 def teardown_method(self) -> None: 

253 """After tests actions.""" 

254 

255 shutil.rmtree(self._temporary_directory) 

256 

257 def test_render(self) -> None: 

258 """Test :func:`colour.plotting.common.render` definition.""" 

259 

260 figure, axes = artist() 

261 

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 ) 

280 

281 render(standalone=True) 

282 

283 render( 

284 filename=os.path.join(self._temporary_directory, "render.png"), 

285 axes_visible=False, 

286 ) 

287 

288 

289class TestLabelRectangles: 

290 """ 

291 Define :func:`colour.plotting.common.label_rectangles` definition unit 

292 tests methods. 

293 """ 

294 

295 def test_label_rectangles(self) -> None: 

296 """Test :func:`colour.plotting.common.label_rectangles` definition.""" 

297 

298 figure, axes = artist() 

299 

300 samples = np.linspace(0, 1, 10) 

301 

302 _figure, axes = label_rectangles( 

303 cast("List[float]", samples.tolist()), 

304 axes.bar(samples, 1), 

305 figure=figure, 

306 axes=axes, 

307 ) 

308 

309 assert len(axes.texts) == len(samples) 

310 

311 

312class TestUniformAxes3d: 

313 """ 

314 Define :func:`colour.plotting.common.uniform_axes3d` definition unit tests 

315 methods. 

316 """ 

317 

318 def test_uniform_axes3d(self) -> None: 

319 """Test :func:`colour.plotting.common.uniform_axes3d` definition.""" 

320 

321 figure, _axes = artist() 

322 axes = figure.add_subplot(111, projection="3d") 

323 

324 uniform_axes3d(axes=axes) 

325 

326 assert axes.get_xlim() == axes.get_ylim() 

327 assert axes.get_xlim() == axes.get_zlim() 

328 

329 

330class TestFilterPassthrough: 

331 """ 

332 Define :func:`colour.plotting.common.filter_passthrough` definition unit 

333 tests methods. 

334 """ 

335 

336 def test_filter_passthrough(self) -> None: 

337 """Test :func:`colour.plotting.common.filter_passthrough` definition.""" 

338 

339 assert sorted( 

340 colourspace.name 

341 for colourspace in filter_passthrough( 

342 RGB_COLOURSPACES, ["ACES2065-1"] 

343 ).values() 

344 ) == ["ACES2065-1"] 

345 

346 assert sorted(filter_passthrough(RGB_COLOURSPACES, ["aces2065-1"]).keys()) == [ 

347 "ACES2065-1" 

348 ] 

349 

350 assert sorted(filter_passthrough(RGB_COLOURSPACES, ["aces20651"]).keys()) == [ 

351 "ACES2065-1" 

352 ] 

353 

354 assert filter_passthrough( 

355 SDS_ILLUMINANTS, 

356 [SDS_ILLUMINANTS["D65"], {"Is": "Excluded"}], 

357 allow_non_siblings=False, 

358 ) == {"D65": SDS_ILLUMINANTS["D65"]} 

359 

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"} 

365 

366 assert sorted( 

367 element 

368 for element in filter_passthrough( 

369 {"John": "Doe", "Luke": "Skywalker"}, ["John"] 

370 ).values() 

371 ) == ["Doe", "John"] 

372 

373 

374class TestFilterRgbColourspaces: 

375 """ 

376 Define :func:`colour.plotting.common.filter_RGB_colourspaces` definition 

377 unit tests methods. 

378 """ 

379 

380 def test_filter_RGB_colourspaces(self) -> None: 

381 """ 

382 Test :func:`colour.plotting.common.filter_RGB_colourspaces` 

383 definition. 

384 """ 

385 

386 assert sorted( 

387 colourspace.name 

388 for colourspace in filter_RGB_colourspaces(["ACES2065-1"]).values() 

389 ) == ["ACES2065-1"] 

390 

391 

392class TestFilterCmfs: 

393 """ 

394 Define :func:`colour.plotting.common.filter_cmfs` definition unit tests 

395 methods. 

396 """ 

397 

398 def test_filter_cmfs(self) -> None: 

399 """Test :func:`colour.plotting.common.filter_cmfs` definition.""" 

400 

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 ] 

407 

408 

409class TestFilterIlluminants: 

410 """ 

411 Define :func:`colour.plotting.common.filter_illuminants` definition unit 

412 tests methods. 

413 """ 

414 

415 def test_filter_illuminants(self) -> None: 

416 """Test :func:`colour.plotting.common.filter_illuminants` definition.""" 

417 

418 assert sorted(filter_illuminants(["D50"]).keys()) == ["D50"] 

419 

420 

421class TestFilterColourCheckers: 

422 """ 

423 Define :func:`colour.plotting.common.filter_colour_checkers` definition 

424 unit tests methods. 

425 """ 

426 

427 def test_filter_colour_checkers(self) -> None: 

428 """Test :func:`colour.plotting.common.filter_colour_checkers` definition.""" 

429 

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 ] 

438 

439 

440class TestUpdateSettingsCollection: 

441 """ 

442 Define :func:`colour.plotting.common.update_settings_collection` 

443 definition unit tests methods. 

444 """ 

445 

446 def test_update_settings_collection(self) -> None: 

447 """ 

448 Test :func:`colour.plotting.common.update_settings_collection` 

449 definition. 

450 """ 

451 

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}] 

456 

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}] 

461 

462 

463class TestPlotSingleColourSwatch: 

464 """ 

465 Define :func:`colour.plotting.common.plot_single_colour_swatch` definition 

466 unit tests methods. 

467 """ 

468 

469 def test_plot_single_colour_swatch(self) -> None: 

470 """ 

471 Test :func:`colour.plotting.common.plot_single_colour_swatch` 

472 definition. 

473 """ 

474 

475 figure, axes = plot_single_colour_swatch( 

476 ColourSwatch((0.45620519, 0.03081071, 0.04091952)) 

477 ) 

478 

479 assert isinstance(figure, Figure) 

480 assert isinstance(axes, Axes) 

481 

482 figure, axes = plot_single_colour_swatch( 

483 np.array([0.45620519, 0.03081071, 0.04091952]) 

484 ) 

485 

486 assert isinstance(figure, Figure) 

487 assert isinstance(axes, Axes) 

488 

489 

490class TestPlotMultiColourSwatches: 

491 """ 

492 Define :func:`colour.plotting.common.plot_multi_colour_swatches` 

493 definition unit tests methods. 

494 """ 

495 

496 def test_plot_multi_colour_swatches(self) -> None: 

497 """ 

498 Test :func:`colour.plotting.common.plot_multi_colour_swatches` 

499 definition. 

500 """ 

501 

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 ) 

508 

509 assert isinstance(figure, Figure) 

510 assert isinstance(axes, Axes) 

511 

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 ) 

521 

522 assert isinstance(figure, Figure) 

523 assert isinstance(axes, Axes) 

524 

525 

526class TestPlotSingleFunction: 

527 """ 

528 Define :func:`colour.plotting.common.plot_single_function` definition unit 

529 tests methods. 

530 """ 

531 

532 def test_plot_single_function(self) -> None: 

533 """Test :func:`colour.plotting.common.plot_single_function` definition.""" 

534 

535 figure, axes = plot_single_function(partial(gamma_function, exponent=1 / 2.2)) 

536 

537 assert isinstance(figure, Figure) 

538 assert isinstance(axes, Axes) 

539 

540 

541class TestPlotMultiFunctions: 

542 """ 

543 Define :func:`colour.plotting.common.plot_multi_functions` definition unit 

544 tests methods. 

545 """ 

546 

547 def test_plot_multi_functions(self) -> None: 

548 """Test :func:`colour.plotting.common.plot_multi_functions` definition.""" 

549 

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) 

557 

558 assert isinstance(figure, Figure) 

559 assert isinstance(axes, Axes) 

560 

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 ) 

565 

566 assert isinstance(figure, Figure) 

567 assert isinstance(axes, Axes) 

568 

569 figure, axes = plot_multi_functions(functions, log_x=10) 

570 

571 assert isinstance(figure, Figure) 

572 assert isinstance(axes, Axes) 

573 

574 figure, axes = plot_multi_functions(functions, log_y=10) 

575 

576 assert isinstance(figure, Figure) 

577 assert isinstance(axes, Axes) 

578 

579 

580class TestPlotImage: 

581 """ 

582 Define :func:`colour.plotting.common.plot_image` definition unit tests 

583 methods. 

584 """ 

585 

586 def test_plot_image(self) -> None: 

587 """Test :func:`colour.plotting.common.plot_image` definition.""" 

588 

589 path = os.path.join( 

590 colour.__path__[0], "..", "docs", "_static", "Logo_Medium_001.png" 

591 ) 

592 

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 

597 

598 figure, axes = plot_image(read_image(path)) 

599 

600 assert isinstance(figure, Figure) 

601 assert isinstance(axes, Axes) 

602 

603 

604class TestPlotRay: 

605 """ 

606 Define :func:`colour.plotting.common.plot_ray` definition unit tests 

607 methods. 

608 """ 

609 

610 def test_plot_ray(self) -> None: 

611 """Test :func:`colour.plotting.common.plot_ray` definition.""" 

612 

613 figure, axes = plt.subplots() 

614 x = np.array([0, 1, 2]) 

615 y = np.array([0, 1, 0]) 

616 

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 ) 

621 

622 plot_ray(axes, x, y, style="dashed", show_arrow=False, show_dots=False) 

623 

624 plt.close(figure) 

625 

626 # If we reach here without exceptions, the test passes 

627 assert True