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

1"""Define the unit tests for the :mod:`colour.plotting.common` module."""

2

3 from __future__ import annotations

4

5 import os

6 import shutil

7 import tempfile

8 from functools import partial

9

10 import matplotlib.font_manager

11 import matplotlib.pyplot as plt

12 import numpy as np

13 from matplotlib.axes import Axes

14 from matplotlib.figure import Figure

15

16 import colour

17 from colour.colorimetry import SDS_ILLUMINANTS

18 from colour.constants import TOLERANCE_ABSOLUTE_TESTS

19 from colour.hints import List, cast

20 from colour.io import read_image

21 from colour.models import RGB_COLOURSPACES, XYZ_to_sRGB, gamma_function

22 from 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)

47 from 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

82 class 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

94 class 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

117 class 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

132 class 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

152 class 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

190 class 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

223 class 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

241 class 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

289 class 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

312 class 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

330 class 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

374 class 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

392 class 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

409 class 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

421 class 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

440 class 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

463 class 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

490 class 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

526 class 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

541 class 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

580 class 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

604 class 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