Coverage for plotting/models.py: 79%

1"""

2 Colour Models Plotting

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

4

5 Define the colour models plotting objects:

6

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

8- :func:`colour.plotting.\

9 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931`

10- :func:`colour.plotting.\

11 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS`

12- :func:`colour.plotting.\

13 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS`

14- :func:`colour.plotting.\

15 plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931`

16- :func:`colour.plotting.\

17 plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS`

18- :func:`colour.plotting.\

19 plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS`

20- :func:`colour.plotting.\

21 plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931`

22- :func:`colour.plotting.\

23 plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS`

24- :func:`colour.plotting.\

25 plot_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

30 References

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

44 from __future__ import annotations

45

46 import typing

47

48 import numpy as np

49 from matplotlib.collections import LineCollection

50 from matplotlib.patches import Ellipse

51 from matplotlib.path import Path

52

53 from colour.adaptation import chromatic_adaptation_VonKries

54 from colour.algebra import normalise_maximum

55 from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON

56 from colour.geometry import (

57 ellipse_coefficients_canonical_form,

58 ellipse_fitting,

59 point_at_angle_on_ellipse,

60)

61 from colour.graph import colourspace_model_to_reference, convert

62

63 if 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

81 from colour.hints import List, cast

82 from colour.models import LCHab_to_Lab # pyright: ignore

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

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

117 from colour.plotting.diagrams import plot_chromaticity_diagram

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

162 COLOURSPACE_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

207 def 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

269 def 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()

358 def 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()

451 def 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_\

532 Plot_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()

662 def 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.\

721 plot_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_\

739 Plot_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()

760 def 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.\

820 plot_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_\

838 Plot_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()

859 def 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.\

919 plot_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_\

937 Plot_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()

958 def 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.\

1003 plot_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_\

1020 Plot_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()

1089 def 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.\

1133 plot_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_\

1152 Plot_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()

1170 def 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.\

1214 plot_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_\

1233 Plot_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()

1251 def 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.\

1295 plot_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_\

1314 Plot_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

1331 def 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()

1376 def 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/\

1422 Plotting_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()

1499 def 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.\

1530 plot_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/\

1546 Plotting_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()

1563 def 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.\

1595 plot_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/\

1611 Plotting_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()

1628 def 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.\

1660 plot_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/\

1676 Plotting_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()

1693 def 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()

1740 def 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")

1802 def 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)