Coverage for colour/plotting/models.py: 100%

293 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2Colour Models Plotting 

3====================== 

4 

5Define the colour models plotting objects: 

6 

7- :func:`colour.plotting.lines_pointer_gamut` 

8- :func:`colour.plotting.\ 

9plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931` 

10- :func:`colour.plotting.\ 

11plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS` 

12- :func:`colour.plotting.\ 

13plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS` 

14- :func:`colour.plotting.\ 

15plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931` 

16- :func:`colour.plotting.\ 

17plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS` 

18- :func:`colour.plotting.\ 

19plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS` 

20- :func:`colour.plotting.\ 

21plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931` 

22- :func:`colour.plotting.\ 

23plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS` 

24- :func:`colour.plotting.\ 

25plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS` 

26- :func:`colour.plotting.plot_single_cctf` 

27- :func:`colour.plotting.plot_multi_cctfs` 

28- :func:`colour.plotting.plot_constant_hue_loci` 

29 

30References 

31---------- 

32- :cite:`Ebner1998` : Ebner, F., & Fairchild, M. D. (1998). Finding constant 

33 hue surfaces in color space. In G. B. Beretta & R. Eschbach (Eds.), Proc. 

34 SPIE 3300, Color Imaging: Device-Independent Color, Color Hardcopy, and 

35 Graphic Arts III, (2 January 1998) (pp. 107-117). doi:10.1117/12.298269 

36- :cite:`Hung1995` : Hung, P.-C., & Berns, R. S. (1995). Determination of 

37 constant Hue Loci for a CRT gamut and their predictions using color 

38 appearance spaces. Color Research & Application, 20(5), 285-295. 

39 doi:10.1002/col.5080200506 

40- :cite:`Mansencal2019` : Mansencal, T. (2019). Colour - Datasets. 

41 doi:10.5281/zenodo.3362520 

42""" 

43 

44from __future__ import annotations 

45 

46import typing 

47 

48import numpy as np 

49from matplotlib.collections import LineCollection 

50from matplotlib.patches import Ellipse 

51from matplotlib.path import Path 

52 

53from colour.adaptation import chromatic_adaptation_VonKries 

54from colour.algebra import normalise_maximum 

55from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON 

56from colour.geometry import ( 

57 ellipse_coefficients_canonical_form, 

58 ellipse_fitting, 

59 point_at_angle_on_ellipse, 

60) 

61from colour.graph import colourspace_model_to_reference, convert 

62 

63if typing.TYPE_CHECKING: 

64 from matplotlib.axes import Axes 

65 from matplotlib.figure import Figure 

66 from colour.colorimetry import MultiSpectralDistributions 

67 from colour.hints import ( 

68 Any, 

69 ArrayLike, 

70 Callable, 

71 Dict, 

72 Literal, 

73 LiteralColourspaceModel, 

74 LiteralRGBColourspace, 

75 NDArray, 

76 NDArrayFloat, 

77 Sequence, 

78 Tuple, 

79 ) 

80 

81from colour.hints import List, cast 

82from colour.models import LCHab_to_Lab # pyright: ignore 

83from colour.models import ( 

84 CCS_ILLUMINANT_POINTER_GAMUT, 

85 CCS_POINTER_GAMUT_BOUNDARY, 

86 CCTF_DECODINGS, 

87 CCTF_ENCODINGS, 

88 COLOURSPACE_MODELS_AXIS_LABELS, 

89 DATA_MACADAM_1942_ELLIPSES, 

90 DATA_POINTER_GAMUT_VOLUME, 

91 Lab_to_XYZ, 

92 RGB_Colourspace, 

93 RGB_to_RGB, 

94 RGB_to_XYZ, 

95 XYZ_to_RGB, 

96 XYZ_to_xy, 

97 xy_to_XYZ, 

98) 

99from colour.plotting import ( 

100 CONSTANTS_COLOUR_STYLE, 

101 METHODS_CHROMATICITY_DIAGRAM, 

102 XYZ_to_plotting_colourspace, 

103 artist, 

104 colour_cycle, 

105 colour_style, 

106 filter_cmfs, 

107 filter_passthrough, 

108 filter_RGB_colourspaces, 

109 override_style, 

110 plot_chromaticity_diagram_CIE1931, 

111 plot_chromaticity_diagram_CIE1960UCS, 

112 plot_chromaticity_diagram_CIE1976UCS, 

113 plot_multi_functions, 

114 render, 

115 update_settings_collection, 

116) 

117from colour.plotting.diagrams import plot_chromaticity_diagram 

118from colour.utilities import ( 

119 CanonicalMapping, 

120 as_array, 

121 as_float_array, 

122 as_int_array, 

123 domain_range_scale, 

124 first_item, 

125 optional, 

126 required, 

127 tsplit, 

128 validate_method, 

129 zeros, 

130) 

131 

132__author__ = "Colour Developers" 

133__copyright__ = "Copyright 2013 Colour Developers" 

134__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

135__maintainer__ = "Colour Developers" 

136__email__ = "colour-developers@colour-science.org" 

137__status__ = "Production" 

138 

139__all__ = [ 

140 "COLOURSPACE_MODELS_AXIS_ORDER", 

141 "colourspace_model_axis_reorder", 

142 "lines_pointer_gamut", 

143 "plot_pointer_gamut", 

144 "plot_RGB_colourspaces_in_chromaticity_diagram", 

145 "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931", 

146 "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS", 

147 "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS", 

148 "plot_RGB_chromaticities_in_chromaticity_diagram", 

149 "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931", 

150 "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS", 

151 "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS", 

152 "ellipses_MacAdam1942", 

153 "plot_ellipses_MacAdam1942_in_chromaticity_diagram", 

154 "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931", 

155 "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS", 

156 "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS", 

157 "plot_single_cctf", 

158 "plot_multi_cctfs", 

159 "plot_constant_hue_loci", 

160] 

161 

162COLOURSPACE_MODELS_AXIS_ORDER: CanonicalMapping = CanonicalMapping( 

163 { 

164 "CAM02LCD": (1, 2, 0), 

165 "CAM02SCD": (1, 2, 0), 

166 "CAM02UCS": (1, 2, 0), 

167 "CAM16LCD": (1, 2, 0), 

168 "CAM16SCD": (1, 2, 0), 

169 "CAM16UCS": (1, 2, 0), 

170 "CIE 1931": (0, 1, 2), 

171 "CIE 1960 UCS": (0, 1, 2), 

172 "CIE 1976 UCS": (0, 1, 2), 

173 "CIE LCHab": (1, 2, 0), 

174 "CIE LCHuv": (1, 2, 0), 

175 "CIE Lab": (1, 2, 0), 

176 "CIE Luv": (1, 2, 0), 

177 "CIE UCS": (0, 1, 2), 

178 "CIE UVW": (1, 2, 0), 

179 "CIE XYZ": (0, 1, 2), 

180 "CIE xyY": (0, 1, 2), 

181 "DIN99": (1, 2, 0), 

182 "HCL": (0, 1, 2), 

183 "HSL": (0, 1, 2), 

184 "HSV": (0, 1, 2), 

185 "Hunter Lab": (1, 2, 0), 

186 "Hunter Rdab": (1, 2, 0), 

187 "ICaCb": (1, 2, 0), 

188 "ICtCp": (1, 2, 0), 

189 "IHLS": (0, 2, 1), 

190 "IPT Ragoo 2021": (1, 2, 0), 

191 "IPT": (1, 2, 0), 

192 "IgPgTg": (1, 2, 0), 

193 "Jzazbz": (1, 2, 0), 

194 "OSA UCS": (1, 2, 0), 

195 "Oklab": (1, 2, 0), 

196 "RGB": (0, 1, 2), 

197 "YCbCr": (1, 2, 0), 

198 "YCoCg": (1, 2, 0), 

199 "Yrg": (1, 2, 0), 

200 "hdr-CIELAB": (1, 2, 0), 

201 "hdr-IPT": (1, 2, 0), 

202 } 

203) 

204"""Colourspace models axis order.""" 

205 

206 

207def colourspace_model_axis_reorder( 

208 a: ArrayLike, 

209 model: LiteralColourspaceModel | str, 

210 direction: Literal["Forward", "Inverse"] | str = "Forward", 

211) -> NDArrayFloat: 

212 """ 

213 Reorder the axes of the specified colourspace model array :math:`a` to 

214 match the standard axes order used for volume plotting. 

215 

216 Parameters 

217 ---------- 

218 a 

219 Colourspace model array :math:`a` to be reordered. 

220 model 

221 Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute 

222 for the list of supported colourspace models. 

223 direction 

224 Reordering direction. 

225 

226 Returns 

227 ------- 

228 :class:`numpy.ndarray` 

229 Reordered colourspace model array :math:`a`. 

230 

231 Examples 

232 -------- 

233 >>> a = np.array([0, 1, 2]) 

234 >>> colourspace_model_axis_reorder(a, "CIE Lab") 

235 array([ 1., 2., 0.]) 

236 >>> colourspace_model_axis_reorder(a, "IPT") 

237 array([ 1., 2., 0.]) 

238 >>> colourspace_model_axis_reorder(a, "OSA UCS") 

239 array([ 1., 2., 0.]) 

240 >>> b = np.array([1, 2, 0]) 

241 >>> colourspace_model_axis_reorder(b, "OSA UCS", "Inverse") 

242 array([ 0., 1., 2.]) 

243 """ 

244 

245 a = as_float_array(a) 

246 

247 model = validate_method( 

248 model, 

249 tuple(COLOURSPACE_MODELS_AXIS_ORDER), 

250 '"{0}" model is invalid, it must be one of {1}!', 

251 ) 

252 

253 direction = validate_method( 

254 direction, 

255 ("Forward", "Inverse"), 

256 '"{0}" direction is invalid, it must be one of {1}!', 

257 ) 

258 

259 order = COLOURSPACE_MODELS_AXIS_ORDER.get(model, (0, 1, 2)) 

260 

261 if direction == "forward": 

262 indexes = (order[0], order[1], order[2]) 

263 else: 

264 indexes = (order.index(0), order.index(1), order.index(2)) 

265 

266 return a[..., indexes] 

267 

268 

269def lines_pointer_gamut( 

270 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

271) -> tuple[NDArray, NDArray]: 

272 """ 

273 Return the *Pointer's Gamut* line vertices, i.e., positions, normals, 

274 and colours, using the specified chromaticity diagram method. 

275 

276 Parameters 

277 ---------- 

278 method 

279 *Chromaticity Diagram* method. 

280 

281 Returns 

282 ------- 

283 :class:`tuple` 

284 Tuple of *Pointer's Gamut* boundary and volume vertices. 

285 

286 Examples 

287 -------- 

288 >>> lines = lines_pointer_gamut() 

289 >>> len(lines) 

290 2 

291 >>> lines[0].dtype 

292 dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \ 

293('colour', '<f8', (3,))]) 

294 >>> lines[1].dtype 

295 dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \ 

296('colour', '<f8', (3,))]) 

297 """ 

298 

299 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

300 

301 illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint 

302 

303 XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"] 

304 ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"] 

305 

306 XYZ = xy_to_XYZ(CCS_POINTER_GAMUT_BOUNDARY) 

307 XYZ = chromatic_adaptation_VonKries( 

308 XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant) 

309 ) 

310 ij_b = XYZ_to_ij(XYZ) 

311 ij_b = np.vstack([ij_b, ij_b[0]]) 

312 colours_b = normalise_maximum( 

313 XYZ_to_plotting_colourspace(ij_to_XYZ(ij_b, illuminant), illuminant), 

314 axis=-1, 

315 ) 

316 

317 lines_b = zeros( 

318 ij_b.shape[0], 

319 [ 

320 ("position", DTYPE_FLOAT_DEFAULT, 2), 

321 ("normal", DTYPE_FLOAT_DEFAULT, 2), 

322 ("colour", DTYPE_FLOAT_DEFAULT, 3), 

323 ], # pyright: ignore 

324 ) 

325 

326 lines_b["position"] = ij_b 

327 lines_b["colour"] = colours_b 

328 

329 XYZ = Lab_to_XYZ( 

330 LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT 

331 ) 

332 XYZ = chromatic_adaptation_VonKries( 

333 XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant) 

334 ) 

335 ij_v = XYZ_to_ij(XYZ) 

336 

337 colours_v = normalise_maximum( 

338 XYZ_to_plotting_colourspace(ij_to_XYZ(ij_v, illuminant), illuminant), 

339 axis=-1, 

340 ) 

341 

342 lines_v = zeros( 

343 ij_v.shape[0], 

344 [ 

345 ("position", DTYPE_FLOAT_DEFAULT, 2), 

346 ("normal", DTYPE_FLOAT_DEFAULT, 2), 

347 ("colour", DTYPE_FLOAT_DEFAULT, 3), 

348 ], # pyright: ignore 

349 ) 

350 

351 lines_v["position"] = ij_v 

352 lines_v["colour"] = colours_v 

353 

354 return lines_b, lines_v 

355 

356 

357@override_style() 

358def plot_pointer_gamut( 

359 pointer_gamut_colours: ArrayLike | str | None = None, 

360 pointer_gamut_opacity: float = 1, 

361 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

362 **kwargs: Any, 

363) -> Tuple[Figure, Axes]: 

364 """ 

365 Plot *Pointer's Gamut* using the specified plotting method. 

366 

367 Parameters 

368 ---------- 

369 pointer_gamut_colours 

370 Colours of *Pointer's Gamut*. 

371 pointer_gamut_opacity 

372 Opacity of *Pointer's Gamut*. 

373 method 

374 Plotting method. 

375 

376 Other Parameters 

377 ---------------- 

378 kwargs 

379 {:func:`colour.plotting.artist`, 

380 :func:`colour.plotting.render`}, 

381 See the documentation of the previously listed definitions. 

382 

383 Returns 

384 ------- 

385 :class:`tuple` 

386 Current figure and axes. 

387 

388 Examples 

389 -------- 

390 >>> plot_pointer_gamut(pointer_gamut_colours="RGB") # doctest: +ELLIPSIS 

391 (<Figure size ... with 1 Axes>, <...Axes...>) 

392 

393 .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png 

394 :align: center 

395 :alt: plot_pointer_gamut 

396 """ 

397 

398 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

399 

400 pointer_gamut_colours = optional( 

401 pointer_gamut_colours, CONSTANTS_COLOUR_STYLE.colour.dark 

402 ) 

403 

404 use_RGB_colours = str(pointer_gamut_colours).upper() == "RGB" 

405 

406 pointer_gamut_opacity = optional( 

407 pointer_gamut_opacity, CONSTANTS_COLOUR_STYLE.opacity.high 

408 ) 

409 

410 settings: Dict[str, Any] = {"uniform": True} 

411 settings.update(kwargs) 

412 

413 _figure, axes = artist(**settings) 

414 

415 lines_b, lines_v = lines_pointer_gamut(method) 

416 

417 axes.add_collection( 

418 LineCollection( 

419 np.reshape( 

420 np.concatenate( 

421 [lines_b["position"][:-1], lines_b["position"][1:]], 

422 axis=1, # pyright: ignore 

423 ), 

424 (-1, 2, 2), 

425 ), 

426 colors=(lines_b["colour"] if use_RGB_colours else pointer_gamut_colours), 

427 alpha=pointer_gamut_opacity, 

428 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

429 ) 

430 ) 

431 

432 scatter_settings = { 

433 "alpha": pointer_gamut_opacity / 2, 

434 "c": lines_v["colour"] if use_RGB_colours else pointer_gamut_colours, 

435 "marker": "+", 

436 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter, 

437 } 

438 axes.scatter( 

439 lines_v["position"][..., 0], 

440 lines_v["position"][..., 1], 

441 **scatter_settings, 

442 ) 

443 

444 settings.update({"axes": axes}) 

445 settings.update(kwargs) 

446 

447 return render(**settings) 

448 

449 

450@override_style() 

451def plot_RGB_colourspaces_in_chromaticity_diagram( 

452 colourspaces: ( 

453 RGB_Colourspace 

454 | LiteralRGBColourspace 

455 | str 

456 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

457 ), 

458 cmfs: ( 

459 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str] 

460 ) = "CIE 1931 2 Degree Standard Observer", 

461 chromaticity_diagram_callable: Callable = plot_chromaticity_diagram, 

462 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

463 show_whitepoints: bool = True, 

464 show_pointer_gamut: bool = False, 

465 chromatically_adapt: bool = False, 

466 plot_kwargs: dict | List[dict] | None = None, 

467 **kwargs: Any, 

468) -> Tuple[Figure, Axes]: 

469 """ 

470 Plot specified *RGB* colourspaces in the *Chromaticity Diagram* using 

471 the specified method. 

472 

473 Parameters 

474 ---------- 

475 colourspaces 

476 *RGB* colourspaces to plot. ``colourspaces`` elements can be of any 

477 type or form supported by the 

478 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

479 cmfs 

480 Standard observer colour matching functions used for computing the 

481 spectral locus boundaries. ``cmfs`` can be of any type or form 

482 supported by the :func:`colour.plotting.common.filter_cmfs` 

483 definition. 

484 chromaticity_diagram_callable 

485 Callable responsible for drawing the *Chromaticity Diagram*. 

486 method 

487 *Chromaticity Diagram* method. 

488 show_whitepoints 

489 Whether to display the *RGB* colourspaces whitepoints. 

490 show_pointer_gamut 

491 Whether to display the *Pointer's Gamut*. 

492 chromatically_adapt 

493 Whether to chromatically adapt the *RGB* colourspaces specified in 

494 ``colourspaces`` to the whitepoint of the default plotting 

495 colourspace. 

496 plot_kwargs 

497 Keyword arguments for the :func:`matplotlib.pyplot.plot` 

498 definition, used to control the style of the plotted *RGB* 

499 colourspaces. ``plot_kwargs`` can be either a single dictionary 

500 applied to all the plotted *RGB* colourspaces with the same 

501 settings or a sequence of dictionaries with different settings for 

502 each plotted *RGB* colourspace. 

503 

504 Other Parameters 

505 ---------------- 

506 kwargs 

507 {:func:`colour.plotting.artist`, 

508 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

509 :func:`colour.plotting.models.plot_pointer_gamut`, 

510 :func:`colour.plotting.render`}, 

511 See the documentation of the previously listed definitions. 

512 

513 Returns 

514 ------- 

515 :class:`tuple` 

516 Current figure and axes. 

517 

518 Examples 

519 -------- 

520 >>> plot_kwargs = [ 

521 ... {"color": "r"}, 

522 ... {"linestyle": "dashed"}, 

523 ... {"marker": None}, 

524 ... ] 

525 >>> plot_RGB_colourspaces_in_chromaticity_diagram( 

526 ... ["ITU-R BT.709", "ACEScg", "S-Gamut"], plot_kwargs=plot_kwargs 

527 ... ) 

528 ... # doctest: +ELLIPSIS 

529 (<Figure size ... with 1 Axes>, <...Axes...>) 

530 

531 .. image:: ../_static/Plotting_\ 

532Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png 

533 :align: center 

534 :alt: plot_RGB_colourspaces_in_chromaticity_diagram 

535 """ 

536 

537 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

538 

539 colourspaces = cast( 

540 "List[RGB_Colourspace]", 

541 list(filter_RGB_colourspaces(colourspaces).values()), 

542 ) # pyright: ignore 

543 

544 settings: Dict[str, Any] = {"uniform": True} 

545 settings.update(kwargs) 

546 

547 _figure, axes = artist(**settings) 

548 

549 cmfs = cast("MultiSpectralDistributions", first_item(filter_cmfs(cmfs).values())) 

550 

551 title = ( 

552 f"{', '.join([colourspace.name for colourspace in colourspaces])}\n" 

553 f"{cmfs.name} - {method.upper()} Chromaticity Diagram" 

554 ) 

555 

556 settings = {"axes": axes, "title": title, "method": method} 

557 settings.update(kwargs) 

558 settings["show"] = False 

559 

560 chromaticity_diagram_callable(**settings) 

561 

562 if show_pointer_gamut: 

563 settings = {"axes": axes, "method": method} 

564 settings.update(kwargs) 

565 settings["show"] = False 

566 

567 plot_pointer_gamut(**settings) 

568 

569 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"] 

570 

571 if method == "cie 1931": 

572 x_limit_min, x_limit_max = [-0.1], [0.9] 

573 y_limit_min, y_limit_max = [-0.1], [0.9] 

574 

575 elif method == "cie 1960 ucs": 

576 x_limit_min, x_limit_max = [-0.1], [0.7] 

577 y_limit_min, y_limit_max = [-0.2], [0.6] 

578 

579 elif method == "cie 1976 ucs": 

580 x_limit_min, x_limit_max = [-0.1], [0.7] 

581 y_limit_min, y_limit_max = [-0.1], [0.7] 

582 

583 settings = {"colour_cycle_count": len(colourspaces)} 

584 settings.update(kwargs) 

585 

586 cycle = colour_cycle(**settings) 

587 

588 plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace 

589 

590 plot_settings_collection = [ 

591 { 

592 "label": f"{colourspace.name}", 

593 "marker": "o", 

594 "color": next(cycle)[:3], 

595 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

596 } 

597 for colourspace in colourspaces 

598 ] 

599 

600 if plot_kwargs is not None: 

601 update_settings_collection( 

602 plot_settings_collection, plot_kwargs, len(colourspaces) 

603 ) 

604 

605 for i, colourspace in enumerate(colourspaces): 

606 plot_settings = plot_settings_collection[i] 

607 

608 if chromatically_adapt and not np.array_equal( 

609 colourspace.whitepoint, plotting_colourspace.whitepoint 

610 ): 

611 colourspace = colourspace.chromatically_adapt( # noqa: PLW2901 

612 plotting_colourspace.whitepoint, 

613 plotting_colourspace.whitepoint_name, 

614 ) 

615 

616 # RGB colourspaces such as *ACES2065-1* have primaries with 

617 # chromaticity coordinates set to 0 thus we prevent nan from being 

618 # yield by zero division in later colour transformations. 

619 P = np.where( 

620 colourspace.primaries == 0, 

621 EPSILON, 

622 colourspace.primaries, 

623 ) 

624 P = xy_to_ij(P) 

625 W = xy_to_ij(colourspace.whitepoint) 

626 

627 P_p = np.vstack([P, P[0]]) 

628 axes.plot(P_p[..., 0], P_p[..., 1], **plot_settings) 

629 

630 if show_whitepoints: 

631 plot_settings["marker"] = "o" 

632 plot_settings.pop("label") 

633 

634 W_p = np.vstack([W, W]) 

635 axes.plot(W_p[..., 0], W_p[..., 1], **plot_settings) 

636 

637 x_limit_min.append(cast("float", np.amin(P[..., 0]) - 0.1)) 

638 y_limit_min.append(cast("float", np.amin(P[..., 1]) - 0.1)) 

639 x_limit_max.append(cast("float", np.amax(P[..., 0]) + 0.1)) 

640 y_limit_max.append(cast("float", np.amax(P[..., 1]) + 0.1)) 

641 

642 bounding_box = ( 

643 min(x_limit_min), 

644 max(x_limit_max), 

645 min(y_limit_min), 

646 max(y_limit_max), 

647 ) 

648 

649 settings.update( 

650 { 

651 "show": True, 

652 "legend": True, 

653 "bounding_box": bounding_box, 

654 } 

655 ) 

656 settings.update(kwargs) 

657 

658 return render(**settings) 

659 

660 

661@override_style() 

662def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931( 

663 colourspaces: ( 

664 RGB_Colourspace 

665 | LiteralRGBColourspace 

666 | str 

667 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

668 ), 

669 cmfs: ( 

670 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str] 

671 ) = "CIE 1931 2 Degree Standard Observer", 

672 chromaticity_diagram_callable_CIE1931: Callable = ( 

673 plot_chromaticity_diagram_CIE1931 

674 ), 

675 show_whitepoints: bool = True, 

676 show_pointer_gamut: bool = False, 

677 chromatically_adapt: bool = False, 

678 plot_kwargs: dict | List[dict] | None = None, 

679 **kwargs: Any, 

680) -> Tuple[Figure, Axes]: 

681 """ 

682 Plot specified *RGB* colourspaces in the *CIE 1931 Chromaticity Diagram*. 

683 

684 Parameters 

685 ---------- 

686 colourspaces 

687 *RGB* colourspaces to plot. ``colourspaces`` elements can be of any 

688 type or form supported by the 

689 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

690 cmfs 

691 Standard observer colour matching functions used for computing the 

692 spectral locus boundaries. ``cmfs`` can be of any type or form 

693 supported by the :func:`colour.plotting.common.filter_cmfs` 

694 definition. 

695 chromaticity_diagram_callable_CIE1931 

696 Callable responsible for drawing the *CIE 1931 Chromaticity 

697 Diagram*. 

698 show_whitepoints 

699 Whether to display the *RGB* colourspaces whitepoints. 

700 show_pointer_gamut 

701 Whether to display the *Pointer's Gamut*. 

702 chromatically_adapt 

703 Whether to chromatically adapt the *RGB* colourspaces specified in 

704 ``colourspaces`` to the whitepoint of the default plotting 

705 colourspace. 

706 plot_kwargs 

707 Keyword arguments for the :func:`matplotlib.pyplot.plot` definition, 

708 used to control the style of the plotted *RGB* colourspaces. 

709 ``plot_kwargs`` can be either a single dictionary applied to all the 

710 plotted *RGB* colourspaces with the same settings or a sequence of 

711 dictionaries with different settings for each plotted *RGB* 

712 colourspace. 

713 

714 Other Parameters 

715 ---------------- 

716 kwargs 

717 {:func:`colour.plotting.artist`, 

718 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

719 :func:`colour.plotting.models.plot_pointer_gamut`, 

720 :func:`colour.plotting.models.\ 

721plot_RGB_colourspaces_in_chromaticity_diagram`, 

722 :func:`colour.plotting.render`}, 

723 See the documentation of the previously listed definitions. 

724 

725 Returns 

726 ------- 

727 :class:`tuple` 

728 Current figure and axes. 

729 

730 Examples 

731 -------- 

732 >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931( 

733 ... ["ITU-R BT.709", "ACEScg", "S-Gamut"] 

734 ... ) 

735 ... # doctest: +ELLIPSIS 

736 (<Figure size ... with 1 Axes>, <...Axes...>) 

737 

738 .. image:: ../_static/Plotting_\ 

739Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png 

740 :align: center 

741 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931 

742 """ 

743 

744 settings = dict(kwargs) 

745 settings.update({"method": "CIE 1931"}) 

746 

747 return plot_RGB_colourspaces_in_chromaticity_diagram( 

748 colourspaces, 

749 cmfs, 

750 chromaticity_diagram_callable_CIE1931, 

751 show_whitepoints=show_whitepoints, 

752 show_pointer_gamut=show_pointer_gamut, 

753 chromatically_adapt=chromatically_adapt, 

754 plot_kwargs=plot_kwargs, 

755 **settings, 

756 ) 

757 

758 

759@override_style() 

760def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS( 

761 colourspaces: ( 

762 RGB_Colourspace 

763 | LiteralRGBColourspace 

764 | str 

765 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

766 ), 

767 cmfs: ( 

768 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str] 

769 ) = "CIE 1931 2 Degree Standard Observer", 

770 chromaticity_diagram_callable_CIE1960UCS: Callable = ( 

771 plot_chromaticity_diagram_CIE1960UCS 

772 ), 

773 show_whitepoints: bool = True, 

774 show_pointer_gamut: bool = False, 

775 chromatically_adapt: bool = False, 

776 plot_kwargs: dict | List[dict] | None = None, 

777 **kwargs: Any, 

778) -> Tuple[Figure, Axes]: 

779 """ 

780 Plot specified *RGB* colourspaces in the 

781 *CIE 1960 UCS Chromaticity Diagram*. 

782 

783 Parameters 

784 ---------- 

785 colourspaces 

786 *RGB* colourspaces to plot. ``colourspaces`` elements can be of any 

787 type or form supported by the 

788 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

789 cmfs 

790 Standard observer colour matching functions used for computing the 

791 spectral locus boundaries. ``cmfs`` can be of any type or form 

792 supported by the :func:`colour.plotting.common.filter_cmfs` 

793 definition. 

794 chromaticity_diagram_callable_CIE1960UCS 

795 Callable responsible for drawing the 

796 *CIE 1960 UCS Chromaticity Diagram*. 

797 show_whitepoints 

798 Whether to display the *RGB* colourspaces whitepoints. 

799 show_pointer_gamut 

800 Whether to display the *Pointer's Gamut*. 

801 chromatically_adapt 

802 Whether to chromatically adapt the *RGB* colourspaces specified in 

803 ``colourspaces`` to the whitepoint of the default plotting 

804 colourspace. 

805 plot_kwargs 

806 Keyword arguments for the :func:`matplotlib.pyplot.plot` 

807 definition, used to control the style of the plotted *RGB* 

808 colourspaces. ``plot_kwargs`` can be either a single dictionary 

809 applied to all the plotted *RGB* colourspaces with the same 

810 settings or a sequence of dictionaries with different settings for 

811 each plotted *RGB* colourspace. 

812 

813 Other Parameters 

814 ---------------- 

815 kwargs 

816 {:func:`colour.plotting.artist`, 

817 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

818 :func:`colour.plotting.models.plot_pointer_gamut`, 

819 :func:`colour.plotting.models.\ 

820plot_RGB_colourspaces_in_chromaticity_diagram`, 

821 :func:`colour.plotting.render`}, 

822 See the documentation of the previously listed definitions. 

823 

824 Returns 

825 ------- 

826 :class:`tuple` 

827 Current figure and axes. 

828 

829 Examples 

830 -------- 

831 >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS( 

832 ... ["ITU-R BT.709", "ACEScg", "S-Gamut"] 

833 ... ) 

834 ... # doctest: +ELLIPSIS 

835 (<Figure size ... with 1 Axes>, <...Axes...>) 

836 

837 .. image:: ../_static/Plotting_\ 

838Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1960UCS.png 

839 :align: center 

840 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS 

841 """ 

842 

843 settings = dict(kwargs) 

844 settings.update({"method": "CIE 1960 UCS"}) 

845 

846 return plot_RGB_colourspaces_in_chromaticity_diagram( 

847 colourspaces, 

848 cmfs, 

849 chromaticity_diagram_callable_CIE1960UCS, 

850 show_whitepoints=show_whitepoints, 

851 show_pointer_gamut=show_pointer_gamut, 

852 chromatically_adapt=chromatically_adapt, 

853 plot_kwargs=plot_kwargs, 

854 **settings, 

855 ) 

856 

857 

858@override_style() 

859def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS( 

860 colourspaces: ( 

861 RGB_Colourspace 

862 | LiteralRGBColourspace 

863 | str 

864 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

865 ), 

866 cmfs: ( 

867 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str] 

868 ) = "CIE 1931 2 Degree Standard Observer", 

869 chromaticity_diagram_callable_CIE1976UCS: Callable = ( 

870 plot_chromaticity_diagram_CIE1976UCS 

871 ), 

872 show_whitepoints: bool = True, 

873 show_pointer_gamut: bool = False, 

874 chromatically_adapt: bool = False, 

875 plot_kwargs: dict | List[dict] | None = None, 

876 **kwargs: Any, 

877) -> Tuple[Figure, Axes]: 

878 """ 

879 Plot the specified *RGB* colourspaces in the 

880 *CIE 1976 UCS Chromaticity Diagram*. 

881 

882 Parameters 

883 ---------- 

884 colourspaces 

885 *RGB* colourspaces to plot. ``colourspaces`` elements can be of any 

886 type or form supported by the 

887 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

888 cmfs 

889 Standard observer colour matching functions used for computing the 

890 spectral locus boundaries. ``cmfs`` can be of any type or form 

891 supported by the :func:`colour.plotting.common.filter_cmfs` 

892 definition. 

893 chromaticity_diagram_callable_CIE1976UCS 

894 Callable responsible for drawing the 

895 *CIE 1976 UCS Chromaticity Diagram*. 

896 show_whitepoints 

897 Whether to display the *RGB* colourspaces whitepoints. 

898 show_pointer_gamut 

899 Whether to display the *Pointer's Gamut*. 

900 chromatically_adapt 

901 Whether to chromatically adapt the *RGB* colourspaces specified in 

902 ``colourspaces`` to the whitepoint of the default plotting 

903 colourspace. 

904 plot_kwargs 

905 Keyword arguments for the :func:`matplotlib.pyplot.plot` definition, 

906 used to control the style of the plotted *RGB* colourspaces. 

907 ``plot_kwargs`` can be either a single dictionary applied to all the 

908 plotted *RGB* colourspaces with the same settings or a sequence of 

909 dictionaries with different settings for each plotted *RGB* 

910 colourspace. 

911 

912 Other Parameters 

913 ---------------- 

914 kwargs 

915 {:func:`colour.plotting.artist`, 

916 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

917 :func:`colour.plotting.models.plot_pointer_gamut`, 

918 :func:`colour.plotting.models.\ 

919plot_RGB_colourspaces_in_chromaticity_diagram`, 

920 :func:`colour.plotting.render`}, 

921 See the documentation of the previously listed definitions. 

922 

923 Returns 

924 ------- 

925 :class:`tuple` 

926 Current figure and axes. 

927 

928 Examples 

929 -------- 

930 >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS( 

931 ... ["ITU-R BT.709", "ACEScg", "S-Gamut"] 

932 ... ) 

933 ... # doctest: +ELLIPSIS 

934 (<Figure size ... with 1 Axes>, <...Axes...>) 

935 

936 .. image:: ../_static/Plotting_\ 

937Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1976UCS.png 

938 :align: center 

939 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS 

940 """ 

941 

942 settings = dict(kwargs) 

943 settings.update({"method": "CIE 1976 UCS"}) 

944 

945 return plot_RGB_colourspaces_in_chromaticity_diagram( 

946 colourspaces, 

947 cmfs, 

948 chromaticity_diagram_callable_CIE1976UCS, 

949 show_whitepoints=show_whitepoints, 

950 show_pointer_gamut=show_pointer_gamut, 

951 chromatically_adapt=chromatically_adapt, 

952 plot_kwargs=plot_kwargs, 

953 **settings, 

954 ) 

955 

956 

957@override_style() 

958def plot_RGB_chromaticities_in_chromaticity_diagram( 

959 RGB: ArrayLike, 

960 colourspace: ( 

961 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

962 ) = "sRGB", 

963 chromaticity_diagram_callable: Callable = ( 

964 plot_RGB_colourspaces_in_chromaticity_diagram 

965 ), 

966 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

967 scatter_kwargs: dict | None = None, 

968 **kwargs: Any, 

969) -> Tuple[Figure, Axes]: 

970 """ 

971 Plot the specified *RGB* colourspace array in the *Chromaticity Diagram* 

972 using the specified method. 

973 

974 Parameters 

975 ---------- 

976 RGB 

977 *RGB* colourspace array. 

978 colourspace 

979 *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any 

980 type or form supported by the 

981 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

982 chromaticity_diagram_callable 

983 Callable responsible for drawing the *Chromaticity Diagram*. 

984 method 

985 *Chromaticity Diagram* method. 

986 scatter_kwargs 

987 Keyword arguments for the :func:`matplotlib.pyplot.scatter` 

988 definition. The following special keyword arguments can also be used: 

989 

990 - ``c`` : If ``c`` is set to *RGB*, the scatter will use the 

991 colours as specified by the ``RGB`` argument. 

992 - ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to 

993 *False*, the encoding colour component transfer function / 

994 opto-electronic transfer function is not applied when encoding 

995 the samples to the plotting space. 

996 

997 Other Parameters 

998 ---------------- 

999 kwargs 

1000 {:func:`colour.plotting.artist`, 

1001 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1002 :func:`colour.plotting.models.\ 

1003plot_RGB_colourspaces_in_chromaticity_diagram`, 

1004 :func:`colour.plotting.render`}, 

1005 See the documentation of the previously listed definitions. 

1006 

1007 Returns 

1008 ------- 

1009 :class:`tuple` 

1010 Current figure and axes. 

1011 

1012 Examples 

1013 -------- 

1014 >>> RGB = np.random.random((128, 128, 3)) 

1015 >>> plot_RGB_chromaticities_in_chromaticity_diagram(RGB, "ITU-R BT.709") 

1016 ... # doctest: +ELLIPSIS 

1017 (<Figure size ... with 1 Axes>, <...Axes...>) 

1018 

1019 .. image:: ../_static/Plotting_\ 

1020Plot_RGB_Chromaticities_In_Chromaticity_Diagram.png 

1021 :align: center 

1022 :alt: plot_RGB_chromaticities_in_chromaticity_diagram 

1023 """ 

1024 

1025 RGB = np.reshape(as_float_array(RGB)[..., :3], (-1, 3)) 

1026 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

1027 

1028 settings: Dict[str, Any] = {"uniform": True} 

1029 settings.update(kwargs) 

1030 

1031 _figure, axes = artist(**settings) 

1032 

1033 scatter_settings = { 

1034 "s": 40, 

1035 "c": "RGB", 

1036 "marker": "o", 

1037 "alpha": 0.85, 

1038 "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_scatter, 

1039 "apply_cctf_encoding": True, 

1040 } 

1041 if scatter_kwargs is not None: 

1042 scatter_settings.update(scatter_kwargs) 

1043 

1044 settings = dict(kwargs) 

1045 settings.update({"axes": axes, "show": False}) 

1046 

1047 colourspace = cast( 

1048 "RGB_Colourspace", 

1049 first_item(filter_RGB_colourspaces(colourspace).values()), 

1050 ) 

1051 

1052 settings["colourspaces"] = [colourspace, *settings.get("colourspaces", [])] 

1053 

1054 chromaticity_diagram_callable(**settings) 

1055 

1056 use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB" 

1057 apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding") 

1058 if use_RGB_colours: 

1059 RGB = RGB[RGB[:, 1].argsort()] 

1060 scatter_settings["c"] = np.clip( 

1061 np.reshape( 

1062 RGB_to_RGB( 

1063 RGB, 

1064 colourspace, 

1065 CONSTANTS_COLOUR_STYLE.colour.colourspace, 

1066 apply_cctf_encoding=apply_cctf_encoding, 

1067 ), 

1068 (-1, 3), 

1069 ), 

1070 0, 

1071 1, 

1072 ) 

1073 

1074 XYZ = RGB_to_XYZ(RGB, colourspace) 

1075 

1076 XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"] 

1077 

1078 ij = XYZ_to_ij(XYZ, colourspace.whitepoint) 

1079 

1080 axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings) 

1081 

1082 settings.update({"show": True}) 

1083 settings.update(kwargs) 

1084 

1085 return render(**settings) 

1086 

1087 

1088@override_style() 

1089def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931( 

1090 RGB: ArrayLike, 

1091 colourspace: ( 

1092 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

1093 ) = "sRGB", 

1094 chromaticity_diagram_callable_CIE1931: Callable = ( 

1095 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931 

1096 ), 

1097 scatter_kwargs: dict | None = None, 

1098 **kwargs: Any, 

1099) -> Tuple[Figure, Axes]: 

1100 """ 

1101 Plot specified *RGB* colourspace array in the *CIE 1931 Chromaticity 

1102 Diagram*. 

1103 

1104 Parameters 

1105 ---------- 

1106 RGB 

1107 *RGB* colourspace array. 

1108 colourspace 

1109 *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any 

1110 type or form supported by the 

1111 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

1112 chromaticity_diagram_callable_CIE1931 

1113 Callable responsible for drawing the *CIE 1931 Chromaticity 

1114 Diagram*. 

1115 scatter_kwargs 

1116 Keyword arguments for the :func:`matplotlib.pyplot.scatter` 

1117 definition. The following special keyword arguments can also be 

1118 used: 

1119 

1120 - ``c`` : If ``c`` is set to *RGB*, the scatter will use the 

1121 colours as specified by the ``RGB`` argument. 

1122 - ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to 

1123 *False*, the encoding colour component transfer function / 

1124 opto-electronic transfer function is not applied when encoding 

1125 the samples to the plotting space. 

1126 

1127 Other Parameters 

1128 ---------------- 

1129 kwargs 

1130 {:func:`colour.plotting.artist`, 

1131 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1132 :func:`colour.plotting.models.\ 

1133plot_RGB_colourspaces_in_chromaticity_diagram`, 

1134 :func:`colour.plotting.render`}, 

1135 See the documentation of the previously listed definitions. 

1136 

1137 Returns 

1138 ------- 

1139 :class:`tuple` 

1140 Current figure and axes. 

1141 

1142 Examples 

1143 -------- 

1144 >>> RGB = np.random.random((128, 128, 3)) 

1145 >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931( 

1146 ... RGB, "ITU-R BT.709" 

1147 ... ) 

1148 ... # doctest: +ELLIPSIS 

1149 (<Figure size ... with 1 Axes>, <...Axes...>) 

1150 

1151 .. image:: ../_static/Plotting_\ 

1152Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1931.png 

1153 :align: center 

1154 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931 

1155 """ 

1156 

1157 settings = dict(kwargs) 

1158 settings.update({"method": "CIE 1931"}) 

1159 

1160 return plot_RGB_chromaticities_in_chromaticity_diagram( 

1161 RGB, 

1162 colourspace, 

1163 chromaticity_diagram_callable_CIE1931, 

1164 scatter_kwargs=scatter_kwargs, 

1165 **settings, 

1166 ) 

1167 

1168 

1169@override_style() 

1170def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS( 

1171 RGB: ArrayLike, 

1172 colourspace: ( 

1173 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

1174 ) = "sRGB", 

1175 chromaticity_diagram_callable_CIE1960UCS: Callable = ( 

1176 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS 

1177 ), 

1178 scatter_kwargs: dict | None = None, 

1179 **kwargs: Any, 

1180) -> Tuple[Figure, Axes]: 

1181 """ 

1182 Plot the specified *RGB* colourspace array in the 

1183 *CIE 1960 UCS Chromaticity Diagram*. 

1184 

1185 Parameters 

1186 ---------- 

1187 RGB 

1188 *RGB* colourspace array. 

1189 colourspace 

1190 *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any 

1191 type or form supported by the 

1192 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

1193 chromaticity_diagram_callable_CIE1960UCS 

1194 Callable responsible for drawing the 

1195 *CIE 1960 UCS Chromaticity Diagram*. 

1196 scatter_kwargs 

1197 Keyword arguments for the :func:`matplotlib.pyplot.scatter` 

1198 definition. The following special keyword arguments can also be 

1199 used: 

1200 

1201 - ``c`` : If ``c`` is set to *RGB*, the scatter will use the 

1202 colours as specified by the ``RGB`` argument. 

1203 - ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to 

1204 *False*, the encoding colour component transfer function / 

1205 opto-electronic transfer function is not applied when encoding 

1206 the samples to the plotting space. 

1207 

1208 Other Parameters 

1209 ---------------- 

1210 kwargs 

1211 {:func:`colour.plotting.artist`, 

1212 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1213 :func:`colour.plotting.models.\ 

1214plot_RGB_colourspaces_in_chromaticity_diagram`, 

1215 :func:`colour.plotting.render`}, 

1216 See the documentation of the previously listed definitions. 

1217 

1218 Returns 

1219 ------- 

1220 :class:`tuple` 

1221 Current figure and axes. 

1222 

1223 Examples 

1224 -------- 

1225 >>> RGB = np.random.random((128, 128, 3)) 

1226 >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS( 

1227 ... RGB, "ITU-R BT.709" 

1228 ... ) 

1229 ... # doctest: +ELLIPSIS 

1230 (<Figure size ... with 1 Axes>, <...Axes...>) 

1231 

1232 .. image:: ../_static/Plotting_\ 

1233Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1960UCS.png 

1234 :align: center 

1235 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS 

1236 """ 

1237 

1238 settings = dict(kwargs) 

1239 settings.update({"method": "CIE 1960 UCS"}) 

1240 

1241 return plot_RGB_chromaticities_in_chromaticity_diagram( 

1242 RGB, 

1243 colourspace, 

1244 chromaticity_diagram_callable_CIE1960UCS, 

1245 scatter_kwargs=scatter_kwargs, 

1246 **settings, 

1247 ) 

1248 

1249 

1250@override_style() 

1251def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS( 

1252 RGB: ArrayLike, 

1253 colourspace: ( 

1254 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str] 

1255 ) = "sRGB", 

1256 chromaticity_diagram_callable_CIE1976UCS: Callable = ( 

1257 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS 

1258 ), 

1259 scatter_kwargs: dict | None = None, 

1260 **kwargs: Any, 

1261) -> Tuple[Figure, Axes]: 

1262 """ 

1263 Plot the specified *RGB* colourspace array in the 

1264 *CIE 1976 UCS Chromaticity Diagram*. 

1265 

1266 Parameters 

1267 ---------- 

1268 RGB 

1269 *RGB* colourspace array. 

1270 colourspace 

1271 *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any 

1272 type or form supported by the 

1273 :func:`colour.plotting.common.filter_RGB_colourspaces` definition. 

1274 chromaticity_diagram_callable_CIE1976UCS 

1275 Callable responsible for drawing the 

1276 *CIE 1976 UCS Chromaticity Diagram*. 

1277 scatter_kwargs 

1278 Keyword arguments for the :func:`matplotlib.pyplot.scatter` 

1279 definition. The following special keyword arguments can also be 

1280 used: 

1281 

1282 - ``c`` : If ``c`` is set to *RGB*, the scatter will use the 

1283 colours as specified by the ``RGB`` argument. 

1284 - ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to 

1285 *False*, the encoding colour component transfer function / 

1286 opto-electronic transfer function is not applied when encoding 

1287 the samples to the plotting space. 

1288 

1289 Other Parameters 

1290 ---------------- 

1291 kwargs 

1292 {:func:`colour.plotting.artist`, 

1293 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1294 :func:`colour.plotting.models.\ 

1295plot_RGB_colourspaces_in_chromaticity_diagram`, 

1296 :func:`colour.plotting.render`}, 

1297 See the documentation of the previously listed definitions. 

1298 

1299 Returns 

1300 ------- 

1301 :class:`tuple` 

1302 Current figure and axes. 

1303 

1304 Examples 

1305 -------- 

1306 >>> RGB = np.random.random((128, 128, 3)) 

1307 >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS( 

1308 ... RGB, "ITU-R BT.709" 

1309 ... ) 

1310 ... # doctest: +ELLIPSIS 

1311 (<Figure size ... with 1 Axes>, <...Axes...>) 

1312 

1313 .. image:: ../_static/Plotting_\ 

1314Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1976UCS.png 

1315 :align: center 

1316 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS 

1317 """ 

1318 

1319 settings = dict(kwargs) 

1320 settings.update({"method": "CIE 1976 UCS"}) 

1321 

1322 return plot_RGB_chromaticities_in_chromaticity_diagram( 

1323 RGB, 

1324 colourspace, 

1325 chromaticity_diagram_callable_CIE1976UCS, 

1326 scatter_kwargs=scatter_kwargs, 

1327 **settings, 

1328 ) 

1329 

1330 

1331def ellipses_MacAdam1942( 

1332 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

1333) -> List[NDArrayFloat]: 

1334 """ 

1335 Return *MacAdam (1942) Ellipses (Observer PGN)* coefficients using the 

1336 specified method. 

1337 

1338 Parameters 

1339 ---------- 

1340 method 

1341 Computation method. 

1342 

1343 Returns 

1344 ------- 

1345 :class:`list` 

1346 *MacAdam (1942) Ellipses (Observer PGN)* coefficients. 

1347 

1348 Examples 

1349 -------- 

1350 >>> ellipses_MacAdam1942()[0] # doctest: +SKIP 

1351 array([ 1.60000000e-01, 5.70000000e-02, 5.00000023e-03, 

1352 1.56666660e-02, -2.77000015e+01]) 

1353 """ 

1354 

1355 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

1356 

1357 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"] 

1358 

1359 x, y, _a, _b, _theta, a, b, theta = tsplit(DATA_MACADAM_1942_ELLIPSES) 

1360 

1361 ellipses_coefficients = [] 

1362 for i in range(len(theta)): 

1363 xy = point_at_angle_on_ellipse( 

1364 np.linspace(0, 360, 36), 

1365 [x[i], y[i], a[i] / 60, b[i] / 60, theta[i]], 

1366 ) 

1367 ij = xy_to_ij(xy) 

1368 ellipses_coefficients.append( 

1369 ellipse_coefficients_canonical_form(ellipse_fitting(ij)) 

1370 ) 

1371 

1372 return ellipses_coefficients 

1373 

1374 

1375@override_style() 

1376def plot_ellipses_MacAdam1942_in_chromaticity_diagram( 

1377 chromaticity_diagram_callable: Callable = plot_chromaticity_diagram, 

1378 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

1379 chromaticity_diagram_clipping: bool = False, 

1380 ellipse_kwargs: dict | List[dict] | None = None, 

1381 **kwargs: Any, 

1382) -> Tuple[Figure, Axes]: 

1383 """ 

1384 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the 

1385 *Chromaticity Diagram* using the specified method. 

1386 

1387 Parameters 

1388 ---------- 

1389 chromaticity_diagram_callable 

1390 Callable responsible for drawing the *Chromaticity Diagram*. 

1391 method 

1392 *Chromaticity Diagram* method. 

1393 chromaticity_diagram_clipping 

1394 Whether to clip the *Chromaticity Diagram* colours with the 

1395 ellipses. 

1396 ellipse_kwargs 

1397 Parameters for the :class:`Ellipse` class. ``ellipse_kwargs`` 

1398 can be either a single dictionary applied to all the ellipses 

1399 with the same settings or a sequence of dictionaries with 

1400 different settings for each ellipse. 

1401 

1402 Other Parameters 

1403 ---------------- 

1404 kwargs 

1405 {:func:`colour.plotting.artist`, 

1406 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1407 :func:`colour.plotting.render`}, 

1408 See the documentation of the previously listed definitions. 

1409 

1410 Returns 

1411 ------- 

1412 :class:`tuple` 

1413 Current figure and axes. 

1414 

1415 Examples 

1416 -------- 

1417 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram() 

1418 ... # doctest: +ELLIPSIS 

1419 (<Figure size ... with 1 Axes>, <...Axes...>) 

1420 

1421 .. image:: ../_static/\ 

1422Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png 

1423 :align: center 

1424 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram 

1425 """ 

1426 

1427 settings: Dict[str, Any] = {"uniform": True} 

1428 settings.update(kwargs) 

1429 

1430 _figure, axes = artist(**settings) 

1431 

1432 settings = dict(kwargs) 

1433 settings.update({"axes": axes, "show": False}) 

1434 

1435 ellipses_coefficients = ellipses_MacAdam1942(method=method) 

1436 

1437 if chromaticity_diagram_clipping: 

1438 diagram_clipping_path_x = [] 

1439 diagram_clipping_path_y = [] 

1440 for coefficients in ellipses_coefficients: 

1441 coefficients = np.copy(coefficients) # noqa: PLW2901 

1442 

1443 coefficients[2:4] /= 2 

1444 

1445 x, y = tsplit( 

1446 point_at_angle_on_ellipse( 

1447 np.linspace(0, 360, 36), 

1448 coefficients, 

1449 ) 

1450 ) 

1451 diagram_clipping_path_x.append(x) 

1452 diagram_clipping_path_y.append(y) 

1453 

1454 diagram_clipping_path = np.rollaxis( 

1455 np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3 

1456 ) 

1457 diagram_clipping_path = Path.make_compound_path_from_polys( 

1458 diagram_clipping_path 

1459 ).vertices 

1460 settings.update({"diagram_clipping_path": diagram_clipping_path}) 

1461 

1462 chromaticity_diagram_callable(**settings) 

1463 

1464 ellipse_settings_collection = [ 

1465 { 

1466 "color": CONSTANTS_COLOUR_STYLE.colour.cycle[4], 

1467 "alpha": 0.4, 

1468 "linewidth": colour_style()["lines.linewidth"], 

1469 "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_polygon, 

1470 } 

1471 for _ellipses_coefficient in ellipses_coefficients 

1472 ] 

1473 

1474 if ellipse_kwargs is not None: 

1475 update_settings_collection( 

1476 ellipse_settings_collection, 

1477 ellipse_kwargs, 

1478 len(ellipses_coefficients), 

1479 ) 

1480 

1481 for i, coefficients in enumerate(ellipses_coefficients): 

1482 x_c, y_c, a_a, a_b, theta_e = coefficients 

1483 ellipse = Ellipse( 

1484 (x_c, y_c), 

1485 a_a, 

1486 a_b, 

1487 angle=theta_e, 

1488 **ellipse_settings_collection[i], 

1489 ) 

1490 axes.add_artist(ellipse) 

1491 

1492 settings.update({"show": True}) 

1493 settings.update(kwargs) 

1494 

1495 return render(**settings) 

1496 

1497 

1498@override_style() 

1499def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931( 

1500 chromaticity_diagram_callable_CIE1931: Callable = ( 

1501 plot_chromaticity_diagram_CIE1931 

1502 ), 

1503 chromaticity_diagram_clipping: bool = False, 

1504 ellipse_kwargs: dict | List[dict] | None = None, 

1505 **kwargs: Any, 

1506) -> Tuple[Figure, Axes]: 

1507 """ 

1508 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the 

1509 *CIE 1931 Chromaticity Diagram*. 

1510 

1511 Parameters 

1512 ---------- 

1513 chromaticity_diagram_callable_CIE1931 

1514 Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*. 

1515 chromaticity_diagram_clipping 

1516 Whether to clip the *CIE 1931 Chromaticity Diagram* colours with the 

1517 ellipses. 

1518 ellipse_kwargs 

1519 Parameters for the :class:`Ellipse` class. ``ellipse_kwargs`` 

1520 can be either a single dictionary applied to all the ellipses 

1521 with the same settings or a sequence of dictionaries with 

1522 different settings for each ellipse. 

1523 

1524 Other Parameters 

1525 ---------------- 

1526 kwargs 

1527 {:func:`colour.plotting.artist`, 

1528 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1529 :func:`colour.plotting.models.\ 

1530plot_ellipses_MacAdam1942_in_chromaticity_diagram`}, 

1531 :func:`colour.plotting.render`}, 

1532 See the documentation of the previously listed definitions. 

1533 

1534 Returns 

1535 ------- 

1536 :class:`tuple` 

1537 Current figure and axes. 

1538 

1539 Examples 

1540 -------- 

1541 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931() 

1542 ... # doctest: +ELLIPSIS 

1543 (<Figure size ... with 1 Axes>, <...Axes...>) 

1544 

1545 .. image:: ../_static/\ 

1546Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1931.png 

1547 :align: center 

1548 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931 

1549 """ 

1550 

1551 settings = dict(kwargs) 

1552 settings.update({"method": "CIE 1931"}) 

1553 

1554 return plot_ellipses_MacAdam1942_in_chromaticity_diagram( 

1555 chromaticity_diagram_callable_CIE1931, 

1556 chromaticity_diagram_clipping=chromaticity_diagram_clipping, 

1557 ellipse_kwargs=ellipse_kwargs, 

1558 **settings, 

1559 ) 

1560 

1561 

1562@override_style() 

1563def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS( 

1564 chromaticity_diagram_callable_CIE1960UCS: Callable = ( 

1565 plot_chromaticity_diagram_CIE1960UCS 

1566 ), 

1567 chromaticity_diagram_clipping: bool = False, 

1568 ellipse_kwargs: dict | List[dict] | None = None, 

1569 **kwargs: Any, 

1570) -> Tuple[Figure, Axes]: 

1571 """ 

1572 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the 

1573 *CIE 1960 UCS Chromaticity Diagram*. 

1574 

1575 Parameters 

1576 ---------- 

1577 chromaticity_diagram_callable_CIE1960UCS 

1578 Callable responsible for drawing the 

1579 *CIE 1960 UCS Chromaticity Diagram*. 

1580 chromaticity_diagram_clipping 

1581 Whether to clip the *CIE 1960 UCS Chromaticity Diagram* colours 

1582 with the ellipses. 

1583 ellipse_kwargs 

1584 Parameters for the :class:`Ellipse` class. ``ellipse_kwargs`` 

1585 can be either a single dictionary applied to all the ellipses 

1586 with the same settings or a sequence of dictionaries with 

1587 different settings for each ellipse. 

1588 

1589 Other Parameters 

1590 ---------------- 

1591 kwargs 

1592 {:func:`colour.plotting.artist`, 

1593 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1594 :func:`colour.plotting.models.\ 

1595plot_ellipses_MacAdam1942_in_chromaticity_diagram`}, 

1596 :func:`colour.plotting.render`}, 

1597 See the documentation of the previously listed definitions. 

1598 

1599 Returns 

1600 ------- 

1601 :class:`tuple` 

1602 Current figure and axes. 

1603 

1604 Examples 

1605 -------- 

1606 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS() 

1607 ... # doctest: +ELLIPSIS 

1608 (<Figure size ... with 1 Axes>, <...Axes...>) 

1609 

1610 .. image:: ../_static/\ 

1611Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1960UCS.png 

1612 :align: center 

1613 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS 

1614 """ 

1615 

1616 settings = dict(kwargs) 

1617 settings.update({"method": "CIE 1960 UCS"}) 

1618 

1619 return plot_ellipses_MacAdam1942_in_chromaticity_diagram( 

1620 chromaticity_diagram_callable_CIE1960UCS, 

1621 chromaticity_diagram_clipping=chromaticity_diagram_clipping, 

1622 ellipse_kwargs=ellipse_kwargs, 

1623 **settings, 

1624 ) 

1625 

1626 

1627@override_style() 

1628def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS( 

1629 chromaticity_diagram_callable_CIE1976UCS: Callable = ( 

1630 plot_chromaticity_diagram_CIE1976UCS 

1631 ), 

1632 chromaticity_diagram_clipping: bool = False, 

1633 ellipse_kwargs: dict | List[dict] | None = None, 

1634 **kwargs: Any, 

1635) -> Tuple[Figure, Axes]: 

1636 """ 

1637 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the 

1638 *CIE 1976 UCS Chromaticity Diagram*. 

1639 

1640 Parameters 

1641 ---------- 

1642 chromaticity_diagram_callable_CIE1976UCS 

1643 Callable responsible for drawing the 

1644 *CIE 1976 UCS Chromaticity Diagram*. 

1645 chromaticity_diagram_clipping 

1646 Whether to clip the *CIE 1976 UCS Chromaticity Diagram* colours 

1647 with the ellipses. 

1648 ellipse_kwargs 

1649 Parameters for the :class:`Ellipse` class. ``ellipse_kwargs`` 

1650 can be either a single dictionary applied to all the ellipses 

1651 with the same settings or a sequence of dictionaries with 

1652 different settings for each ellipse. 

1653 

1654 Other Parameters 

1655 ---------------- 

1656 kwargs 

1657 {:func:`colour.plotting.artist`, 

1658 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

1659 :func:`colour.plotting.models.\ 

1660plot_ellipses_MacAdam1942_in_chromaticity_diagram`}, 

1661 :func:`colour.plotting.render`}, 

1662 See the documentation of the previously listed definitions. 

1663 

1664 Returns 

1665 ------- 

1666 :class:`tuple` 

1667 Current figure and axes. 

1668 

1669 Examples 

1670 -------- 

1671 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS() 

1672 ... # doctest: +ELLIPSIS 

1673 (<Figure size ... with 1 Axes>, <...Axes...>) 

1674 

1675 .. image:: ../_static/\ 

1676Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1976UCS.png 

1677 :align: center 

1678 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS 

1679 """ 

1680 

1681 settings = dict(kwargs) 

1682 settings.update({"method": "CIE 1976 UCS"}) 

1683 

1684 return plot_ellipses_MacAdam1942_in_chromaticity_diagram( 

1685 chromaticity_diagram_callable_CIE1976UCS, 

1686 chromaticity_diagram_clipping=chromaticity_diagram_clipping, 

1687 ellipse_kwargs=ellipse_kwargs, 

1688 **settings, 

1689 ) 

1690 

1691 

1692@override_style() 

1693def plot_single_cctf( 

1694 cctf: Callable | str, cctf_decoding: bool = False, **kwargs: Any 

1695) -> Tuple[Figure, Axes]: 

1696 """ 

1697 Plot specified colourspace colour component transfer function. 

1698 

1699 Parameters 

1700 ---------- 

1701 cctf 

1702 Colour component transfer function to plot. ``function`` can be of 

1703 any type or form supported by the 

1704 :func:`colour.plotting.common.filter_passthrough` definition. 

1705 cctf_decoding 

1706 Plot the decoding colour component transfer function instead. 

1707 

1708 Other Parameters 

1709 ---------------- 

1710 kwargs 

1711 {:func:`colour.plotting.artist`, 

1712 :func:`colour.plotting.plot_multi_functions`, 

1713 :func:`colour.plotting.render`}, 

1714 See the documentation of the previously listed definitions. 

1715 

1716 Returns 

1717 ------- 

1718 :class:`tuple` 

1719 Current figure and axes. 

1720 

1721 Examples 

1722 -------- 

1723 >>> plot_single_cctf("ITU-R BT.709") # doctest: +ELLIPSIS 

1724 (<Figure size ... with 1 Axes>, <...Axes...>) 

1725 

1726 .. image:: ../_static/Plotting_Plot_Single_CCTF.png 

1727 :align: center 

1728 :alt: plot_single_cctf 

1729 """ 

1730 

1731 settings: Dict[str, Any] = { 

1732 "title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF" 

1733 } 

1734 settings.update(kwargs) 

1735 

1736 return plot_multi_cctfs([cctf], cctf_decoding, **settings) 

1737 

1738 

1739@override_style() 

1740def plot_multi_cctfs( 

1741 cctfs: Callable | str | Sequence[Callable | str], 

1742 cctf_decoding: bool = False, 

1743 **kwargs: Any, 

1744) -> Tuple[Figure, Axes]: 

1745 """ 

1746 Plot the specified colour component transfer functions. 

1747 

1748 Parameters 

1749 ---------- 

1750 cctfs 

1751 Colour component transfer function to plot. ``cctfs`` elements can be 

1752 of any type or form supported by the 

1753 :func:`colour.plotting.common.filter_passthrough` definition. 

1754 cctf_decoding 

1755 Plot the decoding colour component transfer function instead. 

1756 

1757 Other Parameters 

1758 ---------------- 

1759 kwargs 

1760 {:func:`colour.plotting.artist`, 

1761 :func:`colour.plotting.plot_multi_functions`, 

1762 :func:`colour.plotting.render`}, 

1763 See the documentation of the previously listed definitions. 

1764 

1765 Returns 

1766 ------- 

1767 :class:`tuple` 

1768 Current figure and axes. 

1769 

1770 Examples 

1771 -------- 

1772 >>> plot_multi_cctfs(["ITU-R BT.709", "sRGB"]) # doctest: +ELLIPSIS 

1773 (<Figure size ... with 1 Axes>, <...Axes...>) 

1774 

1775 .. image:: ../_static/Plotting_Plot_Multi_CCTFs.png 

1776 :align: center 

1777 :alt: plot_multi_cctfs 

1778 """ 

1779 

1780 cctfs_filtered = filter_passthrough( 

1781 CCTF_DECODINGS if cctf_decoding else CCTF_ENCODINGS, cctfs 

1782 ) 

1783 

1784 mode = "Decoding" if cctf_decoding else "Encoding" 

1785 title = f"{', '.join(list(cctfs_filtered))} - {mode} CCTFs" 

1786 

1787 settings: Dict[str, Any] = { 

1788 "bounding_box": (0, 1, 0, 1), 

1789 "legend": True, 

1790 "title": title, 

1791 "x_label": "Signal Value" if cctf_decoding else "Tristimulus Value", 

1792 "y_label": "Tristimulus Value" if cctf_decoding else "Signal Value", 

1793 } 

1794 settings.update(kwargs) 

1795 

1796 with domain_range_scale("1"): 

1797 return plot_multi_functions(cctfs_filtered, **settings) 

1798 

1799 

1800@override_style() 

1801@required("SciPy") 

1802def plot_constant_hue_loci( 

1803 data: ArrayLike, 

1804 model: LiteralColourspaceModel | str = "CIE Lab", 

1805 scatter_kwargs: dict | None = None, 

1806 convert_kwargs: dict | None = None, 

1807 **kwargs: Any, 

1808) -> Tuple[Figure, Axes]: 

1809 """ 

1810 Plot specified constant hue loci colour matches data such as that from 

1811 :cite:`Hung1995` or :cite:`Ebner1998` that are easily loaded with 

1812 `Colour - Datasets <https://github.com/colour-science/colour-datasets>`__. 

1813 

1814 Parameters 

1815 ---------- 

1816 data 

1817 Constant hue loci colour matches data expected to be an `ArrayLike` as 

1818 follows:: 

1819 

1820 [ 

1821 ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \ 

1822 {metadata}), 

1823 ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \ 

1824 {metadata}), 

1825 ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \ 

1826 {metadata}), 

1827 ... 

1828 ] 

1829 

1830 where ``name`` is the hue angle or name, ``XYZ_r`` the *CIE XYZ* 

1831 tristimulus values of the reference illuminant, ``XYZ_cr`` the 

1832 *CIE XYZ* tristimulus values of the reference colour under the 

1833 reference illuminant, ``XYZ_ct`` the *CIE XYZ* tristimulus values of 

1834 the colour matches under the reference illuminant and ``metadata`` the 

1835 dataset metadata. 

1836 model 

1837 Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for 

1838 the list of supported colourspace models. 

1839 scatter_kwargs 

1840 Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition. 

1841 The following special keyword arguments can also be used: 

1842 

1843 - ``c`` : If ``c`` is set to *RGB*, the scatter will use the 

1844 colours as specified by the ``RGB`` argument. 

1845 convert_kwargs 

1846 Keyword arguments for the :func:`colour.convert` definition. 

1847 

1848 Other Parameters 

1849 ---------------- 

1850 kwargs 

1851 {:func:`colour.plotting.artist`, 

1852 :func:`colour.plotting.plot_multi_functions`, 

1853 :func:`colour.plotting.render`}, 

1854 See the documentation of the previously listed definitions. 

1855 

1856 Returns 

1857 ------- 

1858 :class:`tuple` 

1859 Current figure and axes. 

1860 

1861 References 

1862 ---------- 

1863 :cite:`Ebner1998`, :cite:`Hung1995`, :cite:`Mansencal2019` 

1864 

1865 Examples 

1866 -------- 

1867 >>> data = [ 

1868 ... [ 

1869 ... None, 

1870 ... np.array([0.95010000, 1.00000000, 1.08810000]), 

1871 ... np.array([0.40920000, 0.28120000, 0.30600000]), 

1872 ... np.array( 

1873 ... [ 

1874 ... [0.02495100, 0.01908600, 0.02032900], 

1875 ... [0.10944300, 0.06235900, 0.06788100], 

1876 ... [0.27186500, 0.18418700, 0.19565300], 

1877 ... [0.48898900, 0.40749400, 0.44854600], 

1878 ... ] 

1879 ... ), 

1880 ... None, 

1881 ... ], 

1882 ... [ 

1883 ... None, 

1884 ... np.array([0.95010000, 1.00000000, 1.08810000]), 

1885 ... np.array([0.30760000, 0.48280000, 0.42770000]), 

1886 ... np.array( 

1887 ... [ 

1888 ... [0.02108000, 0.02989100, 0.02790400], 

1889 ... [0.06194700, 0.11251000, 0.09334400], 

1890 ... [0.15255800, 0.28123300, 0.23234900], 

1891 ... [0.34157700, 0.56681300, 0.47035300], 

1892 ... ] 

1893 ... ), 

1894 ... None, 

1895 ... ], 

1896 ... [ 

1897 ... None, 

1898 ... np.array([0.95010000, 1.00000000, 1.08810000]), 

1899 ... np.array([0.39530000, 0.28120000, 0.18450000]), 

1900 ... np.array( 

1901 ... [ 

1902 ... [0.02436400, 0.01908600, 0.01468800], 

1903 ... [0.10331200, 0.06235900, 0.02854600], 

1904 ... [0.26311900, 0.18418700, 0.12109700], 

1905 ... [0.43158700, 0.40749400, 0.39008600], 

1906 ... ] 

1907 ... ), 

1908 ... None, 

1909 ... ], 

1910 ... [ 

1911 ... None, 

1912 ... np.array([0.95010000, 1.00000000, 1.08810000]), 

1913 ... np.array([0.20510000, 0.18420000, 0.57130000]), 

1914 ... np.array( 

1915 ... [ 

1916 ... [0.03039800, 0.02989100, 0.06123300], 

1917 ... [0.08870000, 0.08498400, 0.21843500], 

1918 ... [0.18405800, 0.18418700, 0.40111400], 

1919 ... [0.32550100, 0.34047200, 0.50296900], 

1920 ... [0.53826100, 0.56681300, 0.80010400], 

1921 ... ] 

1922 ... ), 

1923 ... None, 

1924 ... ], 

1925 ... [ 

1926 ... None, 

1927 ... np.array([0.95010000, 1.00000000, 1.08810000]), 

1928 ... np.array([0.35770000, 0.28120000, 0.11250000]), 

1929 ... np.array( 

1930 ... [ 

1931 ... [0.03678100, 0.02989100, 0.01481100], 

1932 ... [0.17127700, 0.11251000, 0.01229900], 

1933 ... [0.30080900, 0.28123300, 0.21229800], 

1934 ... [0.52976000, 0.40749400, 0.11720000], 

1935 ... ] 

1936 ... ), 

1937 ... None, 

1938 ... ], 

1939 ... ] 

1940 >>> plot_constant_hue_loci(data, "CIE Lab") # doctest: +ELLIPSIS 

1941 (<Figure size ... with 1 Axes>, <...Axes...>) 

1942 

1943 .. image:: ../_static/Plotting_Plot_Constant_Hue_Loci.png 

1944 :align: center 

1945 :alt: plot_constant_hue_loci 

1946 """ 

1947 

1948 import scipy.optimize # noqa: PLC0415 

1949 

1950 # TODO: Filter appropriate colour models. 

1951 # NOTE: "dtype=object" is required for ragged array support 

1952 # in "Numpy" 1.24.0. 

1953 data = as_array(data, dtype=object) # pyright: ignore 

1954 

1955 settings: Dict[str, Any] = {"uniform": True} 

1956 settings.update(kwargs) 

1957 

1958 _figure, axes = artist(**settings) 

1959 

1960 scatter_settings = { 

1961 "s": 40, 

1962 "c": "RGB", 

1963 "marker": "o", 

1964 "alpha": 0.85, 

1965 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter, 

1966 } 

1967 if scatter_kwargs is not None: 

1968 scatter_settings.update(scatter_kwargs) 

1969 

1970 convert_kwargs = optional(convert_kwargs, {}) 

1971 

1972 use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB" 

1973 

1974 colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace 

1975 for hue_data in data: 

1976 _name, XYZ_r, XYZ_cr, XYZ_ct, _metadata = hue_data 

1977 

1978 xy_r = XYZ_to_xy(XYZ_r) 

1979 

1980 convert_settings = {"illuminant": xy_r} 

1981 convert_settings.update(convert_kwargs) 

1982 

1983 ijk_ct = colourspace_model_axis_reorder( 

1984 convert(XYZ_ct, "CIE XYZ", model, **convert_settings), # pyright: ignore 

1985 model, 

1986 ) 

1987 ijk_cr = colourspace_model_axis_reorder( 

1988 convert(XYZ_cr, "CIE XYZ", model, **convert_settings), # pyright: ignore 

1989 model, 

1990 ) 

1991 

1992 ijk_ct = colourspace_model_to_reference(ijk_ct, model) 

1993 ijk_cr = colourspace_model_to_reference(ijk_cr, model) 

1994 

1995 def _linear_equation( 

1996 x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat 

1997 ) -> NDArrayFloat: 

1998 """Define the canonical linear equation for a line.""" 

1999 

2000 return a * x + b 

2001 

2002 popt, _pcov = scipy.optimize.curve_fit( 

2003 _linear_equation, ijk_ct[..., 0], ijk_ct[..., 1] 

2004 ) 

2005 

2006 axes.plot( 

2007 ijk_ct[..., 0], 

2008 _linear_equation(ijk_ct[..., 0], *popt), # type: ignore 

2009 c=CONSTANTS_COLOUR_STYLE.colour.average, 

2010 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line, 

2011 ) 

2012 

2013 if use_RGB_colours: 

2014 RGB_ct = XYZ_to_RGB(XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True) 

2015 scatter_settings["c"] = np.clip(RGB_ct, 0, 1) 

2016 RGB_cr = XYZ_to_RGB(XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True) 

2017 RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1) 

2018 else: 

2019 RGB_cr = scatter_settings["c"] 

2020 

2021 axes.scatter(ijk_ct[..., 0], ijk_ct[..., 1], **scatter_settings) 

2022 

2023 axes.plot( 

2024 ijk_cr[..., 0], 

2025 ijk_cr[..., 1], 

2026 "s", 

2027 c=RGB_cr, 

2028 markersize=CONSTANTS_COLOUR_STYLE.geometry.short * 8, 

2029 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line, 

2030 ) 

2031 

2032 labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[ 

2033 as_int_array(colourspace_model_axis_reorder([0, 1, 2], model)) 

2034 ] 

2035 

2036 settings = { 

2037 "axes": axes, 

2038 "title": f"Constant Hue Loci - {model}", 

2039 "x_label": labels[0], 

2040 "y_label": labels[1], 

2041 } 

2042 settings.update(kwargs) 

2043 

2044 return render(**settings)