• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

colour-science / colour / 5981586480

26 Aug 2023 12:17AM UTC coverage: 99.793% (+0.008%) from 99.785%
5981586480

push

github-actions

KelSolaar
Merge branch 'feature/v0.4.3' into develop

10 of 10 new or added lines in 5 files covered. (100.0%)

39523 of 39605 relevant lines covered (99.79%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.34
/colour/plotting/models.py
1
"""
2
Colour Models Plotting
3
======================
4

5
Defines the colour models plotting objects:
6

7
-   :func:`colour.plotting.\
8
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931`
9
-   :func:`colour.plotting.\
10
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS`
11
-   :func:`colour.plotting.\
12
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS`
13
-   :func:`colour.plotting.\
14
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931`
15
-   :func:`colour.plotting.\
16
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS`
17
-   :func:`colour.plotting.\
18
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS`
19
-   :func:`colour.plotting.\
20
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931`
21
-   :func:`colour.plotting.\
22
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS`
23
-   :func:`colour.plotting.\
24
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS`
25
-   :func:`colour.plotting.plot_single_cctf`
26
-   :func:`colour.plotting.plot_multi_cctfs`
27
-   :func:`colour.plotting.plot_constant_hue_loci`
28

29
References
30
----------
31
-   :cite:`Ebner1998` : Ebner, F., & Fairchild, M. D. (1998). Finding constant
32
    hue surfaces in color space. In G. B. Beretta & R. Eschbach (Eds.), Proc.
33
    SPIE 3300, Color Imaging: Device-Independent Color, Color Hardcopy, and
34
    Graphic Arts III, (2 January 1998) (pp. 107-117). doi:10.1117/12.298269
35
-   :cite:`Hung1995` : Hung, P.-C., & Berns, R. S. (1995). Determination of
36
    constant Hue Loci for a CRT gamut and their predictions using color
37
    appearance spaces. Color Research & Application, 20(5), 285-295.
38
    doi:10.1002/col.5080200506
39
-   :cite:`Mansencal2019` : Mansencal, T. (2019). Colour - Datasets.
40
    doi:10.5281/zenodo.3362520
41
"""
42

43
from __future__ import annotations
1✔
44

45
import matplotlib.pyplot as plt
1✔
46
import numpy as np
1✔
47
import scipy.optimize
1✔
48
from matplotlib.patches import Ellipse
1✔
49
from matplotlib.path import Path
1✔
50

51
from colour.colorimetry import MultiSpectralDistributions
1✔
52
from colour.constants import EPSILON
1✔
53
from colour.geometry import (
1✔
54
    point_at_angle_on_ellipse,
55
    ellipse_coefficients_canonical_form,
56
    ellipse_fitting,
57
)
58
from colour.graph import convert
1✔
59
from colour.hints import (
1✔
60
    Any,
61
    ArrayLike,
62
    Callable,
63
    Dict,
64
    List,
65
    Literal,
66
    NDArrayFloat,
67
    Sequence,
68
    Tuple,
69
    cast,
70
)
71
from colour.models import (
1✔
72
    COLOURSPACE_MODELS_AXIS_LABELS,
73
    COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE,
74
    CCTF_ENCODINGS,
75
    CCTF_DECODINGS,
76
    LCHab_to_Lab,
77
    Lab_to_XYZ,
78
    Luv_to_uv,
79
    DATA_MACADAM_1942_ELLIPSES,
80
    CCS_POINTER_GAMUT_BOUNDARY,
81
    DATA_POINTER_GAMUT_VOLUME,
82
    CCS_ILLUMINANT_POINTER_GAMUT,
83
    RGB_Colourspace,
84
    RGB_to_RGB,
85
    RGB_to_XYZ,
86
    UCS_to_uv,
87
    XYZ_to_Luv,
88
    XYZ_to_RGB,
89
    XYZ_to_UCS,
90
    XYZ_to_xy,
91
    xy_to_Luv_uv,
92
    xy_to_UCS_uv,
93
)
94
from colour.plotting import (
95
    CONSTANTS_COLOUR_STYLE,
96
    plot_chromaticity_diagram_CIE1931,
97
    artist,
98
    plot_chromaticity_diagram_CIE1960UCS,
99
    plot_chromaticity_diagram_CIE1976UCS,
100
    colour_cycle,
101
    colour_style,
102
    filter_passthrough,
103
    filter_RGB_colourspaces,
104
    filter_cmfs,
105
    plot_multi_functions,
106
    override_style,
107
    render,
108
    update_settings_collection,
109
)
110
from colour.plotting.diagrams import plot_chromaticity_diagram
1✔
111
from colour.utilities import (
1✔
112
    CanonicalMapping,
113
    as_array,
114
    as_float_array,
115
    as_int_array,
116
    domain_range_scale,
117
    first_item,
118
    optional,
119
    tsplit,
120
    validate_method,
121
)
122

123
__author__ = "Colour Developers"
1✔
124
__copyright__ = "Copyright 2013 Colour Developers"
1✔
125
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
1✔
126
__maintainer__ = "Colour Developers"
1✔
127
__email__ = "colour-developers@colour-science.org"
1✔
128
__status__ = "Production"
1✔
129

130
__all__ = [
1✔
131
    "COLOURSPACE_MODELS_AXIS_ORDER",
132
    "colourspace_model_axis_reorder",
133
    "plot_pointer_gamut",
134
    "plot_RGB_colourspaces_in_chromaticity_diagram",
135
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931",
136
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS",
137
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS",
138
    "plot_RGB_chromaticities_in_chromaticity_diagram",
139
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931",
140
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS",
141
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS",
142
    "ellipses_MacAdam1942",
143
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram",
144
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931",
145
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS",
146
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS",
147
    "plot_single_cctf",
148
    "plot_multi_cctfs",
149
    "plot_constant_hue_loci",
150
]
151

152
COLOURSPACE_MODELS_AXIS_ORDER: CanonicalMapping = CanonicalMapping(
1✔
153
    {
154
        "CAM02LCD": (1, 2, 0),
155
        "CAM02SCD": (1, 2, 0),
156
        "CAM02UCS": (1, 2, 0),
157
        "CAM16LCD": (1, 2, 0),
158
        "CAM16SCD": (1, 2, 0),
159
        "CAM16UCS": (1, 2, 0),
160
        "CIE XYZ": (0, 1, 2),
161
        "CIE xyY": (0, 1, 2),
162
        "CIE Lab": (1, 2, 0),
163
        "CIE LCHab": (1, 2, 0),
164
        "CIE Luv": (1, 2, 0),
165
        "CIE LCHuv": (1, 2, 0),
166
        "CIE UCS": (0, 1, 2),
167
        "CIE UVW": (1, 2, 0),
168
        "DIN99": (1, 2, 0),
169
        "Hunter Lab": (1, 2, 0),
170
        "Hunter Rdab": (1, 2, 0),
171
        "ICaCb": (1, 2, 0),
172
        "ICtCp": (1, 2, 0),
173
        "IPT": (1, 2, 0),
174
        "IPT Ragoo 2021": (1, 2, 0),
175
        "IgPgTg": (1, 2, 0),
176
        "Jzazbz": (1, 2, 0),
177
        "OSA UCS": (1, 2, 0),
178
        "Oklab": (1, 2, 0),
179
        "hdr-CIELAB": (1, 2, 0),
180
        "hdr-IPT": (1, 2, 0),
181
        "Yrg": (1, 2, 0),
182
    }
183
)
184
"""Colourspace models axis order."""
1✔
185

186

187
def colourspace_model_axis_reorder(
1✔
188
    a: ArrayLike,
189
    model: Literal[
190
        "CAM02LCD",
191
        "CAM02SCD",
192
        "CAM02UCS",
193
        "CAM16LCD",
194
        "CAM16SCD",
195
        "CAM16UCS",
196
        "CIE XYZ",
197
        "CIE xyY",
198
        "CIE Lab",
199
        "CIE LCHab",
200
        "CIE Luv",
201
        "CIE LCHuv",
202
        "CIE UCS",
203
        "CIE UVW",
204
        "DIN99",
205
        "Hunter Lab",
206
        "Hunter Rdab",
207
        "ICaCb",
208
        "ICtCp",
209
        "IPT",
210
        "IPT Ragoo 2021",
211
        "IgPgTg",
212
        "Jzazbz",
213
        "OSA UCS",
214
        "Oklab",
215
        "hdr-CIELAB",
216
        "hdr-IPT",
217
        "Yrg",
218
    ]
219
    | str,
220
    direction: Literal["Forward", "Inverse"] | str = "Forward",
221
) -> NDArrayFloat:
222
    """
223
    Reorder the axes of given colourspace model :math:`a` array according to
224
    the most common volume plotting axes order.
225

226
    Parameters
227
    ----------
228
    a
229
        Colourspace model :math:`a` array.
230
    model
231
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
232
        the list of supported colourspace models.
233
    direction
234
        Reordering direction.
235

236
    Returns
237
    -------
238
    :class:`numpy.ndarray`
239
        Reordered colourspace model :math:`a` array.
240

241
    Examples
242
    --------
243
    >>> a = np.array([0, 1, 2])
244
    >>> colourspace_model_axis_reorder(a, "CIE Lab")
245
    array([ 1.,  2.,  0.])
246
    >>> colourspace_model_axis_reorder(a, "IPT")
247
    array([ 1.,  2.,  0.])
248
    >>> colourspace_model_axis_reorder(a, "OSA UCS")
249
    array([ 1.,  2.,  0.])
250
    >>> b = np.array([1, 2, 0])
251
    >>> colourspace_model_axis_reorder(b, "OSA UCS", "Inverse")
252
    array([ 0.,  1.,  2.])
253
    """
254

255
    a = as_float_array(a)
1✔
256

257
    model = validate_method(
1✔
258
        model,
259
        tuple(COLOURSPACE_MODELS_AXIS_ORDER),
260
        '"{0}" model is invalid, it must be one of {1}!',
261
    )
262

263
    direction = validate_method(
1✔
264
        direction,
265
        ("Forward", "Inverse"),
266
        '"{0}" direction is invalid, it must be one of {1}!',
267
    )
268

269
    order = COLOURSPACE_MODELS_AXIS_ORDER.get(model, (0, 1, 2))
1✔
270

271
    if direction == "forward":
1✔
272
        indexes = (order[0], order[1], order[2])
1✔
273
    else:
274
        indexes = (order.index(0), order.index(1), order.index(2))
1✔
275

276
    return a[..., indexes]
1✔
277

278

279
@override_style()
1✔
280
def plot_pointer_gamut(
1✔
281
    pointer_gamut_colours: ArrayLike | str | None = None,
282
    pointer_gamut_opacity: float = 1,
283
    method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
284
    | str = "CIE 1931",
285
    **kwargs: Any,
286
) -> Tuple[plt.Figure, plt.Axes]:
287
    """
288
    Plot *Pointer's Gamut* according to given method.
289

290
    Parameters
291
    ----------
292
    pointer_gamut_colours
293
       Colours of the *Pointer's Gamut*.
294
    pointer_gamut_opacity
295
       Opacity of the *Pointer's Gamut*.
296
    method
297
        Plotting method.
298

299
    Other Parameters
300
    ----------------
301
    kwargs
302
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
303
        See the documentation of the previously listed definitions.
304

305
    Returns
306
    -------
307
    :class:`tuple`
308
        Current figure and axes.
309

310
    Examples
311
    --------
312
    >>> plot_pointer_gamut()  # doctest: +ELLIPSIS
313
    (<Figure size ... with 1 Axes>, <...Axes...>)
314

315
    .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png
316
        :align: center
317
        :alt: plot_pointer_gamut
318
    """
319

320
    method = validate_method(
1✔
321
        method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")
322
    )
323

324
    pointer_gamut_colours = optional(
1✔
325
        pointer_gamut_colours, CONSTANTS_COLOUR_STYLE.colour.dark
326
    )
327
    pointer_gamut_opacity = optional(
1✔
328
        pointer_gamut_opacity, CONSTANTS_COLOUR_STYLE.opacity.high
329
    )
330

331
    settings: Dict[str, Any] = {"uniform": True}
1✔
332
    settings.update(kwargs)
1✔
333

334
    _figure, axes = artist(**settings)
1✔
335

336
    if method == "cie 1931":
1✔
337

338
        def XYZ_to_ij(
1✔
339
            XYZ: NDArrayFloat, *args: Any  # noqa: ARG001
340
        ) -> NDArrayFloat:
341
            """
342
            Convert given *CIE XYZ* tristimulus values to *ij* chromaticity
343
            coordinates.
344
            """
345

346
            return XYZ_to_xy(XYZ)
1✔
347

348
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
349
            """
350
            Convert given *CIE xy* chromaticity coordinates to *ij*
351
            chromaticity coordinates.
352
            """
353

354
            return xy
1✔
355

356
    elif method == "cie 1960 ucs":
1✔
357

358
        def XYZ_to_ij(
1✔
359
            XYZ: NDArrayFloat, *args: Any  # noqa: ARG001
360
        ) -> NDArrayFloat:
361
            """
362
            Convert given *CIE XYZ* tristimulus values to *ij* chromaticity
363
            coordinates.
364
            """
365

366
            return UCS_to_uv(XYZ_to_UCS(XYZ))
1✔
367

368
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
369
            """
370
            Convert given *CIE xy* chromaticity coordinates to *ij*
371
            chromaticity coordinates.
372
            """
373

374
            return xy_to_UCS_uv(xy)
1✔
375

376
    elif method == "cie 1976 ucs":
1✔
377

378
        def XYZ_to_ij(XYZ: NDArrayFloat, *args: Any) -> NDArrayFloat:
1✔
379
            """
380
            Convert given *CIE XYZ* tristimulus values to *ij* chromaticity
381
            coordinates.
382
            """
383

384
            return Luv_to_uv(XYZ_to_Luv(XYZ, *args), *args)
1✔
385

386
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
387
            """
388
            Convert given *CIE xy* chromaticity coordinates to *ij*
389
            chromaticity coordinates.
390
            """
391

392
            return xy_to_Luv_uv(xy)
1✔
393

394
    ij = xy_to_ij(CCS_POINTER_GAMUT_BOUNDARY)
1✔
395
    axes.plot(
1✔
396
        ij[..., 0],
397
        ij[..., 1],
398
        label="Pointer's Gamut",
399
        color=pointer_gamut_colours,
400
        alpha=pointer_gamut_opacity,
401
        zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
402
    )
403
    axes.plot(
1✔
404
        (ij[-1][0], ij[0][0]),
405
        (ij[-1][1], ij[0][1]),
406
        color=pointer_gamut_colours,
407
        alpha=pointer_gamut_opacity,
408
        zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
409
    )
410

411
    XYZ = Lab_to_XYZ(
1✔
412
        LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT
413
    )
414
    ij = XYZ_to_ij(XYZ, CCS_ILLUMINANT_POINTER_GAMUT)
1✔
415

416
    scatter_settings = {
1✔
417
        "alpha": pointer_gamut_opacity / 2,
418
        "color": pointer_gamut_colours,
419
        "marker": "+",
420
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
421
    }
422
    axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings)
1✔
423

424
    settings.update({"axes": axes})
1✔
425
    settings.update(kwargs)
1✔
426

427
    return render(**settings)
1✔
428

429

430
@override_style()
1✔
431
def plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
432
    colourspaces: RGB_Colourspace | str | Sequence[RGB_Colourspace | str],
433
    cmfs: MultiSpectralDistributions
434
    | str
435
    | Sequence[
436
        MultiSpectralDistributions | str
437
    ] = "CIE 1931 2 Degree Standard Observer",
438
    chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
439
    method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
440
    | str = "CIE 1931",
441
    show_whitepoints: bool = True,
442
    show_pointer_gamut: bool = False,
443
    chromatically_adapt: bool = False,
444
    plot_kwargs: dict | List[dict] | None = None,
445
    **kwargs: Any,
446
) -> Tuple[plt.Figure, plt.Axes]:
447
    """
448
    Plot given *RGB* colourspaces in the *Chromaticity Diagram* according
449
    to given method.
450

451
    Parameters
452
    ----------
453
    colourspaces
454
        *RGB* colourspaces to plot. ``colourspaces`` elements
455
        can be of any type or form supported by the
456
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
457
    cmfs
458
        Standard observer colour matching functions used for computing the
459
        spectral locus boundaries. ``cmfs`` can be of any type or form
460
        supported by the :func:`colour.plotting.common.filter_cmfs` definition.
461
    chromaticity_diagram_callable
462
        Callable responsible for drawing the *Chromaticity Diagram*.
463
    method
464
        *Chromaticity Diagram* method.
465
    show_whitepoints
466
        Whether to display the *RGB* colourspaces whitepoints.
467
    show_pointer_gamut
468
        Whether to display the *Pointer's Gamut*.
469
    chromatically_adapt
470
        Whether to chromatically adapt the *RGB* colourspaces given in
471
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
472
    plot_kwargs
473
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
474
        used to control the style of the plotted *RGB* colourspaces.
475
        ``plot_kwargs`` can be either a single dictionary applied to all the
476
        plotted *RGB* colourspaces with the same settings or a sequence of
477
        dictionaries with different settings for each plotted *RGB*
478
        colourspace.
479

480
    Other Parameters
481
    ----------------
482
    kwargs
483
        {:func:`colour.plotting.artist`,
484
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
485
        :func:`colour.plotting.models.plot_pointer_gamut`,
486
        :func:`colour.plotting.render`},
487
        See the documentation of the previously listed definitions.
488

489
    Returns
490
    -------
491
    :class:`tuple`
492
        Current figure and axes.
493

494
    Examples
495
    --------
496
    >>> plot_kwargs = [
497
    ...     {"color": "r"},
498
    ...     {"linestyle": "dashed"},
499
    ...     {"marker": None},
500
    ... ]
501
    >>> plot_RGB_colourspaces_in_chromaticity_diagram(
502
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"], plot_kwargs=plot_kwargs
503
    ... )
504
    ... # doctest: +ELLIPSIS
505
    (<Figure size ... with 1 Axes>, <...Axes...>)
506

507
    .. image:: ../_static/Plotting_\
508
Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png
509
        :align: center
510
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram
511
    """
512

513
    method = validate_method(
1✔
514
        method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")
515
    )
516

517
    colourspaces = cast(
1✔
518
        List[RGB_Colourspace],
519
        list(filter_RGB_colourspaces(colourspaces).values()),
520
    )  # pyright: ignore
521

522
    settings: Dict[str, Any] = {"uniform": True}
1✔
523
    settings.update(kwargs)
1✔
524

525
    _figure, axes = artist(**settings)
1✔
526

527
    cmfs = cast(
1✔
528
        MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values())
529
    )
530

531
    title = (
1✔
532
        f"{', '.join([colourspace.name for colourspace in colourspaces])}\n"
533
        f"{cmfs.name} - {method.upper()} Chromaticity Diagram"
534
    )
535

536
    settings = {"axes": axes, "title": title, "method": method}
1✔
537
    settings.update(kwargs)
1✔
538
    settings["show"] = False
1✔
539

540
    chromaticity_diagram_callable(**settings)
1✔
541

542
    if show_pointer_gamut:
1✔
543
        settings = {"axes": axes, "method": method}
1✔
544
        settings.update(kwargs)
1✔
545
        settings["show"] = False
1✔
546

547
        plot_pointer_gamut(**settings)
1✔
548

549
    if method == "cie 1931":
1✔
550

551
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
552
            """
553
            Convert given *CIE xy* chromaticity coordinates to *ij*
554
            chromaticity coordinates.
555
            """
556

557
            return xy
1✔
558

559
        x_limit_min, x_limit_max = [-0.1], [0.9]
1✔
560
        y_limit_min, y_limit_max = [-0.1], [0.9]
1✔
561

562
    elif method == "cie 1960 ucs":
1✔
563

564
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
565
            """
566
            Convert given *CIE xy* chromaticity coordinates to *ij*
567
            chromaticity coordinates.
568
            """
569

570
            return xy_to_UCS_uv(xy)
1✔
571

572
        x_limit_min, x_limit_max = [-0.1], [0.7]
1✔
573
        y_limit_min, y_limit_max = [-0.2], [0.6]
1✔
574

575
    elif method == "cie 1976 ucs":
1✔
576

577
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
578
            """
579
            Convert given *CIE xy* chromaticity coordinates to *ij*
580
            chromaticity coordinates.
581
            """
582

583
            return xy_to_Luv_uv(xy)
1✔
584

585
        x_limit_min, x_limit_max = [-0.1], [0.7]
1✔
586
        y_limit_min, y_limit_max = [-0.1], [0.7]
1✔
587

588
    settings = {"colour_cycle_count": len(colourspaces)}
1✔
589
    settings.update(kwargs)
1✔
590

591
    cycle = colour_cycle(**settings)
1✔
592

593
    plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1✔
594

595
    plot_settings_collection = [
1✔
596
        {
597
            "label": f"{colourspace.name}",
598
            "marker": "o",
599
            "color": next(cycle)[:3],
600
            "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
601
        }
602
        for colourspace in colourspaces
603
    ]
604

605
    if plot_kwargs is not None:
1✔
606
        update_settings_collection(
1✔
607
            plot_settings_collection, plot_kwargs, len(colourspaces)
608
        )
609

610
    for i, colourspace in enumerate(colourspaces):
1✔
611
        plot_settings = plot_settings_collection[i]
1✔
612

613
        if chromatically_adapt and not np.array_equal(
1✔
614
            colourspace.whitepoint, plotting_colourspace.whitepoint
615
        ):
616
            colourspace = colourspace.chromatically_adapt(  # noqa: PLW2901
1✔
617
                plotting_colourspace.whitepoint,
618
                plotting_colourspace.whitepoint_name,
619
            )
620

621
        # RGB colourspaces such as *ACES2065-1* have primaries with
622
        # chromaticity coordinates set to 0 thus we prevent nan from being
623
        # yield by zero division in later colour transformations.
624
        P = np.where(
1✔
625
            colourspace.primaries == 0,
626
            EPSILON,
627
            colourspace.primaries,
628
        )
629
        P = xy_to_ij(P)
1✔
630
        W = xy_to_ij(colourspace.whitepoint)
1✔
631

632
        P_p = np.vstack([P, P[0]])
1✔
633
        axes.plot(P_p[..., 0], P_p[..., 1], **plot_settings)
1✔
634

635
        if show_whitepoints:
1✔
636
            plot_settings["marker"] = "o"
1✔
637
            plot_settings.pop("label")
1✔
638

639
            W_p = np.vstack([W, W])
1✔
640
            axes.plot(W_p[..., 0], W_p[..., 1], **plot_settings)
1✔
641

642
        x_limit_min.append(cast(float, np.amin(P[..., 0]) - 0.1))
1✔
643
        y_limit_min.append(cast(float, np.amin(P[..., 1]) - 0.1))
1✔
644
        x_limit_max.append(cast(float, np.amax(P[..., 0]) + 0.1))
1✔
645
        y_limit_max.append(cast(float, np.amax(P[..., 1]) + 0.1))
1✔
646

647
    bounding_box = (
1✔
648
        min(x_limit_min),
649
        max(x_limit_max),
650
        min(y_limit_min),
651
        max(y_limit_max),
652
    )
653

654
    settings.update(
1✔
655
        {
656
            "show": True,
657
            "legend": True,
658
            "bounding_box": bounding_box,
659
        }
660
    )
661
    settings.update(kwargs)
1✔
662

663
    return render(**settings)
1✔
664

665

666
@override_style()
1✔
667
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
1✔
668
    colourspaces: RGB_Colourspace | str | Sequence[RGB_Colourspace | str],
669
    cmfs: MultiSpectralDistributions
670
    | str
671
    | Sequence[
672
        MultiSpectralDistributions | str
673
    ] = "CIE 1931 2 Degree Standard Observer",
674
    chromaticity_diagram_callable_CIE1931: Callable = (
675
        plot_chromaticity_diagram_CIE1931
676
    ),
677
    show_whitepoints: bool = True,
678
    show_pointer_gamut: bool = False,
679
    chromatically_adapt: bool = False,
680
    plot_kwargs: dict | List[dict] | None = None,
681
    **kwargs: Any,
682
) -> Tuple[plt.Figure, plt.Axes]:
683
    """
684
    Plot given *RGB* colourspaces in the *CIE 1931 Chromaticity Diagram*.
685

686
    Parameters
687
    ----------
688
    colourspaces
689
        *RGB* colourspaces to plot. ``colourspaces`` elements
690
        can be of any type or form supported by the
691
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
692
    cmfs
693
        Standard observer colour matching functions used for computing the
694
        spectral locus boundaries. ``cmfs`` can be of any type or form
695
        supported by the :func:`colour.plotting.common.filter_cmfs` definition.
696
    chromaticity_diagram_callable_CIE1931
697
        Callable responsible for drawing the *CIE 1931 Chromaticity 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 given in
704
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
705
    plot_kwargs
706
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
707
        used to control the style of the plotted *RGB* colourspaces.
708
        ``plot_kwargs`` can be either a single dictionary applied to all the
709
        plotted *RGB* colourspaces with the same settings or a sequence of
710
        dictionaries with different settings for each plotted *RGB*
711
        colourspace.
712

713
    Other Parameters
714
    ----------------
715
    kwargs
716
        {:func:`colour.plotting.artist`,
717
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
718
        :func:`colour.plotting.models.plot_pointer_gamut`,
719
        :func:`colour.plotting.models.\
720
plot_RGB_colourspaces_in_chromaticity_diagram`,
721
        :func:`colour.plotting.render`},
722
        See the documentation of the previously listed definitions.
723

724
    Returns
725
    -------
726
    :class:`tuple`
727
        Current figure and axes.
728

729
    Examples
730
    --------
731
    >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
732
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"]
733
    ... )
734
    ... # doctest: +ELLIPSIS
735
    (<Figure size ... with 1 Axes>, <...Axes...>)
736

737
    .. image:: ../_static/Plotting_\
738
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png
739
        :align: center
740
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
741
    """
742

743
    settings = dict(kwargs)
1✔
744
    settings.update({"method": "CIE 1931"})
1✔
745

746
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
747
        colourspaces,
748
        cmfs,
749
        chromaticity_diagram_callable_CIE1931,
750
        show_whitepoints=show_whitepoints,
751
        show_pointer_gamut=show_pointer_gamut,
752
        chromatically_adapt=chromatically_adapt,
753
        plot_kwargs=plot_kwargs,
754
        **settings,
755
    )
756

757

758
@override_style()
1✔
759
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
1✔
760
    colourspaces: RGB_Colourspace | str | Sequence[RGB_Colourspace | str],
761
    cmfs: MultiSpectralDistributions
762
    | str
763
    | Sequence[
764
        MultiSpectralDistributions | str
765
    ] = "CIE 1931 2 Degree Standard Observer",
766
    chromaticity_diagram_callable_CIE1960UCS: Callable = (
767
        plot_chromaticity_diagram_CIE1960UCS
768
    ),
769
    show_whitepoints: bool = True,
770
    show_pointer_gamut: bool = False,
771
    chromatically_adapt: bool = False,
772
    plot_kwargs: dict | List[dict] | None = None,
773
    **kwargs: Any,
774
) -> Tuple[plt.Figure, plt.Axes]:
775
    """
776
    Plot given *RGB* colourspaces in the *CIE 1960 UCS Chromaticity Diagram*.
777

778
    Parameters
779
    ----------
780
    colourspaces
781
        *RGB* colourspaces to plot. ``colourspaces`` elements
782
        can be of any type or form supported by the
783
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
784
    cmfs
785
        Standard observer colour matching functions used for computing the
786
        spectral locus boundaries. ``cmfs`` can be of any type or form
787
        supported by the :func:`colour.plotting.common.filter_cmfs` definition.
788
    chromaticity_diagram_callable_CIE1960UCS
789
        Callable responsible for drawing the
790
        *CIE 1960 UCS Chromaticity Diagram*.
791
    show_whitepoints
792
        Whether to display the *RGB* colourspaces whitepoints.
793
    show_pointer_gamut
794
        Whether to display the *Pointer's Gamut*.
795
    chromatically_adapt
796
        Whether to chromatically adapt the *RGB* colourspaces given in
797
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
798
    plot_kwargs
799
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
800
        used to control the style of the plotted *RGB* colourspaces.
801
        ``plot_kwargs`` can be either a single dictionary applied to all the
802
        plotted *RGB* colourspaces with the same settings or a sequence of
803
        dictionaries with different settings for each plotted *RGB*
804
        colourspace.
805

806
    Other Parameters
807
    ----------------
808
    kwargs
809
        {:func:`colour.plotting.artist`,
810
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
811
        :func:`colour.plotting.models.plot_pointer_gamut`,
812
        :func:`colour.plotting.models.\
813
plot_RGB_colourspaces_in_chromaticity_diagram`,
814
        :func:`colour.plotting.render`},
815
        See the documentation of the previously listed definitions.
816

817
    Returns
818
    -------
819
    :class:`tuple`
820
        Current figure and axes.
821

822
    Examples
823
    --------
824
    >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
825
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"]
826
    ... )
827
    ... # doctest: +ELLIPSIS
828
    (<Figure size ... with 1 Axes>, <...Axes...>)
829

830
    .. image:: ../_static/Plotting_\
831
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1960UCS.png
832
        :align: center
833
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
834
    """
835

836
    settings = dict(kwargs)
1✔
837
    settings.update({"method": "CIE 1960 UCS"})
1✔
838

839
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
840
        colourspaces,
841
        cmfs,
842
        chromaticity_diagram_callable_CIE1960UCS,
843
        show_whitepoints=show_whitepoints,
844
        show_pointer_gamut=show_pointer_gamut,
845
        chromatically_adapt=chromatically_adapt,
846
        plot_kwargs=plot_kwargs,
847
        **settings,
848
    )
849

850

851
@override_style()
1✔
852
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
1✔
853
    colourspaces: RGB_Colourspace | str | Sequence[RGB_Colourspace | str],
854
    cmfs: MultiSpectralDistributions
855
    | str
856
    | Sequence[
857
        MultiSpectralDistributions | str
858
    ] = "CIE 1931 2 Degree Standard Observer",
859
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
860
        plot_chromaticity_diagram_CIE1976UCS
861
    ),
862
    show_whitepoints: bool = True,
863
    show_pointer_gamut: bool = False,
864
    chromatically_adapt: bool = False,
865
    plot_kwargs: dict | List[dict] | None = None,
866
    **kwargs: Any,
867
) -> Tuple[plt.Figure, plt.Axes]:
868
    """
869
    Plot given *RGB* colourspaces in the *CIE 1976 UCS Chromaticity Diagram*.
870

871
    Parameters
872
    ----------
873
    colourspaces
874
        *RGB* colourspaces to plot. ``colourspaces`` elements
875
        can be of any type or form supported by the
876
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
877
    cmfs
878
        Standard observer colour matching functions used for computing the
879
        spectral locus boundaries. ``cmfs`` can be of any type or form
880
        supported by the :func:`colour.plotting.common.filter_cmfs` definition.
881
    chromaticity_diagram_callable_CIE1976UCS
882
        Callable responsible for drawing the
883
        *CIE 1976 UCS Chromaticity Diagram*.
884
    show_whitepoints
885
        Whether to display the *RGB* colourspaces whitepoints.
886
    show_pointer_gamut
887
        Whether to display the *Pointer's Gamut*.
888
    chromatically_adapt
889
        Whether to chromatically adapt the *RGB* colourspaces given in
890
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
891
    plot_kwargs
892
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
893
        used to control the style of the plotted *RGB* colourspaces.
894
        ``plot_kwargs`` can be either a single dictionary applied to all the
895
        plotted *RGB* colourspaces with the same settings or a sequence of
896
        dictionaries with different settings for each plotted *RGB*
897
        colourspace.
898

899
    Other Parameters
900
    ----------------
901
    kwargs
902
        {:func:`colour.plotting.artist`,
903
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
904
        :func:`colour.plotting.models.plot_pointer_gamut`,
905
        :func:`colour.plotting.models.\
906
plot_RGB_colourspaces_in_chromaticity_diagram`,
907
        :func:`colour.plotting.render`},
908
        See the documentation of the previously listed definitions.
909

910
    Returns
911
    -------
912
    :class:`tuple`
913
        Current figure and axes.
914

915
    Examples
916
    --------
917
    >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
918
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"]
919
    ... )
920
    ... # doctest: +ELLIPSIS
921
    (<Figure size ... with 1 Axes>, <...Axes...>)
922

923
    .. image:: ../_static/Plotting_\
924
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1976UCS.png
925
        :align: center
926
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
927
    """
928

929
    settings = dict(kwargs)
1✔
930
    settings.update({"method": "CIE 1976 UCS"})
1✔
931

932
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
933
        colourspaces,
934
        cmfs,
935
        chromaticity_diagram_callable_CIE1976UCS,
936
        show_whitepoints=show_whitepoints,
937
        show_pointer_gamut=show_pointer_gamut,
938
        chromatically_adapt=chromatically_adapt,
939
        plot_kwargs=plot_kwargs,
940
        **settings,
941
    )
942

943

944
@override_style()
1✔
945
def plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
946
    RGB: ArrayLike,
947
    colourspace: RGB_Colourspace
948
    | str
949
    | Sequence[RGB_Colourspace | str] = "sRGB",
950
    chromaticity_diagram_callable: Callable = (
951
        plot_RGB_colourspaces_in_chromaticity_diagram
952
    ),
953
    method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
954
    | str = "CIE 1931",
955
    scatter_kwargs: dict | None = None,
956
    **kwargs: Any,
957
) -> Tuple[plt.Figure, plt.Axes]:
958
    """
959
    Plot given *RGB* colourspace array in the *Chromaticity Diagram* according
960
    to given method.
961

962
    Parameters
963
    ----------
964
    RGB
965
        *RGB* colourspace array.
966
    colourspace
967
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
968
        type or form supported by the
969
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
970
    chromaticity_diagram_callable
971
        Callable responsible for drawing the *Chromaticity Diagram*.
972
    method
973
        *Chromaticity Diagram* method.
974
    scatter_kwargs
975
        Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
976
        The following special keyword arguments can also be used:
977

978
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
979
            as given by the ``RGB`` argument.
980
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
981
            *False*, the encoding colour component transfer function /
982
            opto-electronic transfer function is not applied when encoding the
983
            samples to the plotting space.
984

985
    Other Parameters
986
    ----------------
987
    kwargs
988
        {:func:`colour.plotting.artist`,
989
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
990
        :func:`colour.plotting.models.\
991
plot_RGB_colourspaces_in_chromaticity_diagram`,
992
        :func:`colour.plotting.render`},
993
        See the documentation of the previously listed definitions.
994

995
    Returns
996
    -------
997
    :class:`tuple`
998
        Current figure and axes.
999

1000
    Examples
1001
    --------
1002
    >>> RGB = np.random.random((128, 128, 3))
1003
    >>> plot_RGB_chromaticities_in_chromaticity_diagram(RGB, "ITU-R BT.709")
1004
    ... # doctest: +ELLIPSIS
1005
    (<Figure size ... with 1 Axes>, <...Axes...>)
1006

1007
    .. image:: ../_static/Plotting_\
1008
Plot_RGB_Chromaticities_In_Chromaticity_Diagram.png
1009
        :align: center
1010
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram
1011
    """
1012

1013
    RGB = np.reshape(as_float_array(RGB), (-1, 3))
1✔
1014
    method = validate_method(
1✔
1015
        method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")
1016
    )
1017

1018
    settings: Dict[str, Any] = {"uniform": True}
1✔
1019
    settings.update(kwargs)
1✔
1020

1021
    _figure, axes = artist(**settings)
1✔
1022

1023
    scatter_settings = {
1✔
1024
        "s": 40,
1025
        "c": "RGB",
1026
        "marker": "o",
1027
        "alpha": 0.85,
1028
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
1029
        "apply_cctf_encoding": True,
1030
    }
1031
    if scatter_kwargs is not None:
1✔
1032
        scatter_settings.update(scatter_kwargs)
1✔
1033

1034
    settings = dict(kwargs)
1✔
1035
    settings.update({"axes": axes, "show": False})
1✔
1036

1037
    colourspace = cast(
1✔
1038
        RGB_Colourspace,
1039
        first_item(filter_RGB_colourspaces(colourspace).values()),
1040
    )
1041

1042
    settings["colourspaces"] = [colourspace, *settings.get("colourspaces", [])]
1✔
1043

1044
    chromaticity_diagram_callable(**settings)
1✔
1045

1046
    use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1✔
1047
    apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding")
1✔
1048
    if use_RGB_colours:
1✔
1049
        RGB = RGB[RGB[:, 1].argsort()]
1✔
1050
        scatter_settings["c"] = np.clip(
1✔
1051
            np.reshape(
1052
                RGB_to_RGB(
1053
                    RGB,
1054
                    colourspace,
1055
                    CONSTANTS_COLOUR_STYLE.colour.colourspace,
1056
                    apply_cctf_encoding=apply_cctf_encoding,
1057
                ),
1058
                (-1, 3),
1059
            ),
1060
            0,
1061
            1,
1062
        )
1063

1064
    XYZ = RGB_to_XYZ(RGB, colourspace)
1✔
1065

1066
    if method == "cie 1931":
1✔
1067
        ij = XYZ_to_xy(XYZ)
1✔
1068

1069
    elif method == "cie 1960 ucs":
1✔
1070
        ij = UCS_to_uv(XYZ_to_UCS(XYZ))
1✔
1071

1072
    elif method == "cie 1976 ucs":
1✔
1073
        ij = Luv_to_uv(
1✔
1074
            XYZ_to_Luv(XYZ, colourspace.whitepoint), colourspace.whitepoint
1075
        )
1076

1077
    axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings)
1✔
1078

1079
    settings.update({"show": True})
1✔
1080
    settings.update(kwargs)
1✔
1081

1082
    return render(**settings)
1✔
1083

1084

1085
@override_style()
1✔
1086
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
1✔
1087
    RGB: ArrayLike,
1088
    colourspace: RGB_Colourspace
1089
    | str
1090
    | Sequence[RGB_Colourspace | str] = "sRGB",
1091
    chromaticity_diagram_callable_CIE1931: Callable = (
1092
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
1093
    ),
1094
    scatter_kwargs: dict | None = None,
1095
    **kwargs: Any,
1096
) -> Tuple[plt.Figure, plt.Axes]:
1097
    """
1098
    Plot given *RGB* colourspace array in the *CIE 1931 Chromaticity Diagram*.
1099

1100
    Parameters
1101
    ----------
1102
    RGB
1103
        *RGB* colourspace array.
1104
    colourspace
1105
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
1106
        type or form supported by the
1107
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
1108
    chromaticity_diagram_callable_CIE1931
1109
        Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
1110
    scatter_kwargs
1111
        Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
1112
        The following special keyword arguments can also be used:
1113

1114
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
1115
            as given by the ``RGB`` argument.
1116
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1117
            *False*, the encoding colour component transfer function /
1118
            opto-electronic transfer function is not applied when encoding the
1119
            samples to the plotting space.
1120

1121
    Other Parameters
1122
    ----------------
1123
    kwargs
1124
        {:func:`colour.plotting.artist`,
1125
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1126
        :func:`colour.plotting.models.\
1127
plot_RGB_colourspaces_in_chromaticity_diagram`,
1128
        :func:`colour.plotting.render`},
1129
        See the documentation of the previously listed definitions.
1130

1131
    Returns
1132
    -------
1133
    :class:`tuple`
1134
        Current figure and axes.
1135

1136
    Examples
1137
    --------
1138
    >>> RGB = np.random.random((128, 128, 3))
1139
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
1140
    ...     RGB, "ITU-R BT.709"
1141
    ... )
1142
    ... # doctest: +ELLIPSIS
1143
    (<Figure size ... with 1 Axes>, <...Axes...>)
1144

1145
    .. image:: ../_static/Plotting_\
1146
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1931.png
1147
        :align: center
1148
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931
1149
    """
1150

1151
    settings = dict(kwargs)
1✔
1152
    settings.update({"method": "CIE 1931"})
1✔
1153

1154
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1155
        RGB,
1156
        colourspace,
1157
        chromaticity_diagram_callable_CIE1931,
1158
        scatter_kwargs=scatter_kwargs,
1159
        **settings,
1160
    )
1161

1162

1163
@override_style()
1✔
1164
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
1✔
1165
    RGB: ArrayLike,
1166
    colourspace: RGB_Colourspace
1167
    | str
1168
    | Sequence[RGB_Colourspace | str] = "sRGB",
1169
    chromaticity_diagram_callable_CIE1960UCS: Callable = (
1170
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
1171
    ),
1172
    scatter_kwargs: dict | None = None,
1173
    **kwargs: Any,
1174
) -> Tuple[plt.Figure, plt.Axes]:
1175
    """
1176
    Plot given *RGB* colourspace array in the
1177
    *CIE 1960 UCS Chromaticity Diagram*.
1178

1179
    Parameters
1180
    ----------
1181
    RGB
1182
        *RGB* colourspace array.
1183
    colourspace
1184
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
1185
        type or form supported by the
1186
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
1187
    chromaticity_diagram_callable_CIE1960UCS
1188
        Callable responsible for drawing the
1189
        *CIE 1960 UCS Chromaticity Diagram*.
1190
    scatter_kwargs
1191
        Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
1192
        The following special keyword arguments can also be used:
1193

1194
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
1195
            as given by the ``RGB`` argument.
1196
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1197
            *False*, the encoding colour component transfer function /
1198
            opto-electronic transfer function is not applied when encoding the
1199
            samples to the plotting space.
1200

1201
    Other Parameters
1202
    ----------------
1203
    kwargs
1204
        {:func:`colour.plotting.artist`,
1205
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1206
        :func:`colour.plotting.models.\
1207
plot_RGB_colourspaces_in_chromaticity_diagram`,
1208
        :func:`colour.plotting.render`},
1209
        See the documentation of the previously listed definitions.
1210

1211
    Returns
1212
    -------
1213
    :class:`tuple`
1214
        Current figure and axes.
1215

1216
    Examples
1217
    --------
1218
    >>> RGB = np.random.random((128, 128, 3))
1219
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
1220
    ...     RGB, "ITU-R BT.709"
1221
    ... )
1222
    ... # doctest: +ELLIPSIS
1223
    (<Figure size ... with 1 Axes>, <...Axes...>)
1224

1225
    .. image:: ../_static/Plotting_\
1226
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1960UCS.png
1227
        :align: center
1228
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS
1229
    """
1230

1231
    settings = dict(kwargs)
1✔
1232
    settings.update({"method": "CIE 1960 UCS"})
1✔
1233

1234
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1235
        RGB,
1236
        colourspace,
1237
        chromaticity_diagram_callable_CIE1960UCS,
1238
        scatter_kwargs=scatter_kwargs,
1239
        **settings,
1240
    )
1241

1242

1243
@override_style()
1✔
1244
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
1✔
1245
    RGB: ArrayLike,
1246
    colourspace: RGB_Colourspace
1247
    | str
1248
    | Sequence[RGB_Colourspace | str] = "sRGB",
1249
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
1250
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
1251
    ),
1252
    scatter_kwargs: dict | None = None,
1253
    **kwargs: Any,
1254
) -> Tuple[plt.Figure, plt.Axes]:
1255
    """
1256
    Plot given *RGB* colourspace array in the
1257
    *CIE 1976 UCS Chromaticity Diagram*.
1258

1259
    Parameters
1260
    ----------
1261
    RGB
1262
        *RGB* colourspace array.
1263
    colourspace
1264
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
1265
        type or form supported by the
1266
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
1267
    chromaticity_diagram_callable_CIE1976UCS
1268
        Callable responsible for drawing the
1269
        *CIE 1976 UCS Chromaticity Diagram*.
1270
    scatter_kwargs
1271
        Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
1272
        The following special keyword arguments can also be used:
1273

1274
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
1275
            as given by the ``RGB`` argument.
1276
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1277
            *False*, the encoding colour component transfer function /
1278
            opto-electronic transfer function is not applied when encoding the
1279
            samples to the plotting space.
1280

1281
    Other Parameters
1282
    ----------------
1283
    kwargs
1284
        {:func:`colour.plotting.artist`,
1285
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1286
        :func:`colour.plotting.models.\
1287
plot_RGB_colourspaces_in_chromaticity_diagram`,
1288
        :func:`colour.plotting.render`},
1289
        See the documentation of the previously listed definitions.
1290

1291
    Returns
1292
    -------
1293
    :class:`tuple`
1294
        Current figure and axes.
1295

1296
    Examples
1297
    --------
1298
    >>> RGB = np.random.random((128, 128, 3))
1299
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
1300
    ...     RGB, "ITU-R BT.709"
1301
    ... )
1302
    ... # doctest: +ELLIPSIS
1303
    (<Figure size ... with 1 Axes>, <...Axes...>)
1304

1305
    .. image:: ../_static/Plotting_\
1306
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1976UCS.png
1307
        :align: center
1308
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS
1309
    """
1310

1311
    settings = dict(kwargs)
1✔
1312
    settings.update({"method": "CIE 1976 UCS"})
1✔
1313

1314
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1315
        RGB,
1316
        colourspace,
1317
        chromaticity_diagram_callable_CIE1976UCS,
1318
        scatter_kwargs=scatter_kwargs,
1319
        **settings,
1320
    )
1321

1322

1323
def ellipses_MacAdam1942(
1✔
1324
    method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
1325
    | str = "CIE 1931"
1326
) -> List[NDArrayFloat]:
1327
    """
1328
    Return *MacAdam (1942) Ellipses (Observer PGN)* coefficients according to
1329
    given method.
1330

1331
    Parameters
1332
    ----------
1333
    method
1334
        Computation method.
1335

1336
    Returns
1337
    -------
1338
    :class:`list`
1339
        *MacAdam (1942) Ellipses (Observer PGN)* coefficients.
1340

1341
    Examples
1342
    --------
1343
    >>> ellipses_MacAdam1942()[0]  # doctest: +SKIP
1344
    array([  1.60000000e-01,   5.70000000e-02,   5.00000023e-03,
1345
             1.56666660e-02,  -2.77000015e+01])
1346
    """
1347

1348
    method = validate_method(
1✔
1349
        method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")
1350
    )
1351

1352
    if method == "cie 1931":
1✔
1353

1354
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
1355
            """
1356
            Convert given *CIE xy* chromaticity coordinates to *ij*
1357
            chromaticity coordinates.
1358
            """
1359

1360
            return xy
1✔
1361

1362
    elif method == "cie 1960 ucs":
1✔
1363

1364
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
1365
            """
1366
            Convert given *CIE xy* chromaticity coordinates to *ij*
1367
            chromaticity coordinates.
1368
            """
1369

1370
            return xy_to_UCS_uv(xy)
1✔
1371

1372
    elif method == "cie 1976 ucs":
1✔
1373

1374
        def xy_to_ij(xy: NDArrayFloat) -> NDArrayFloat:
1✔
1375
            """
1376
            Convert given *CIE xy* chromaticity coordinates to *ij*
1377
            chromaticity coordinates.
1378
            """
1379

1380
            return xy_to_Luv_uv(xy)
1✔
1381

1382
    x, y, _a, _b, _theta, a, b, theta = tsplit(DATA_MACADAM_1942_ELLIPSES)
1✔
1383

1384
    ellipses_coefficients = []
1✔
1385
    for i in range(len(theta)):
1✔
1386
        xy = point_at_angle_on_ellipse(
1✔
1387
            np.linspace(0, 360, 36),
1388
            [x[i], y[i], a[i] / 60, b[i] / 60, theta[i]],
1389
        )
1390
        ij = xy_to_ij(xy)
1✔
1391
        ellipses_coefficients.append(
1✔
1392
            ellipse_coefficients_canonical_form(ellipse_fitting(ij))
1393
        )
1394

1395
    return ellipses_coefficients
1✔
1396

1397

1398
@override_style()
1✔
1399
def plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1400
    chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
1401
    method: Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
1402
    | str = "CIE 1931",
1403
    chromaticity_diagram_clipping: bool = False,
1404
    ellipse_kwargs: dict | List[dict] | None = None,
1405
    **kwargs: Any,
1406
) -> Tuple[plt.Figure, plt.Axes]:
1407
    """
1408
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1409
    *Chromaticity Diagram* according to given method.
1410

1411
    Parameters
1412
    ----------
1413
    chromaticity_diagram_callable
1414
        Callable responsible for drawing the *Chromaticity Diagram*.
1415
    method
1416
        *Chromaticity Diagram* method.
1417
    chromaticity_diagram_clipping
1418
        Whether to clip the *Chromaticity Diagram* colours with the ellipses.
1419
    ellipse_kwargs
1420
        Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
1421
        be either a single dictionary applied to all the ellipses with same
1422
        settings or a sequence of dictionaries with different settings for each
1423
        ellipse.
1424

1425
    Other Parameters
1426
    ----------------
1427
    kwargs
1428
        {:func:`colour.plotting.artist`,
1429
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1430
        :func:`colour.plotting.render`},
1431
        See the documentation of the previously listed definitions.
1432

1433
    Returns
1434
    -------
1435
    :class:`tuple`
1436
        Current figure and axes.
1437

1438
    Examples
1439
    --------
1440
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram()
1441
    ... # doctest: +ELLIPSIS
1442
    (<Figure size ... with 1 Axes>, <...Axes...>)
1443

1444
    .. image:: ../_static/\
1445
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png
1446
        :align: center
1447
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram
1448
    """
1449

1450
    settings: Dict[str, Any] = {"uniform": True}
1✔
1451
    settings.update(kwargs)
1✔
1452

1453
    _figure, axes = artist(**settings)
1✔
1454

1455
    settings = dict(kwargs)
1✔
1456
    settings.update({"axes": axes, "show": False})
1✔
1457

1458
    ellipses_coefficients = ellipses_MacAdam1942(method=method)
1✔
1459

1460
    if chromaticity_diagram_clipping:
1✔
1461
        diagram_clipping_path_x = []
1✔
1462
        diagram_clipping_path_y = []
1✔
1463
        for coefficients in ellipses_coefficients:
1✔
1464
            coefficients = np.copy(coefficients)  # noqa: PLW2901
1✔
1465

1466
            coefficients[2:4] /= 2
1✔
1467

1468
            x, y = tsplit(
1✔
1469
                point_at_angle_on_ellipse(
1470
                    np.linspace(0, 360, 36),
1471
                    coefficients,
1472
                )
1473
            )
1474
            diagram_clipping_path_x.append(x)
1✔
1475
            diagram_clipping_path_y.append(y)
1✔
1476

1477
        diagram_clipping_path = np.rollaxis(
1✔
1478
            np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3
1479
        )
1480
        diagram_clipping_path = Path.make_compound_path_from_polys(
1✔
1481
            diagram_clipping_path
1482
        ).vertices
1483
        settings.update({"diagram_clipping_path": diagram_clipping_path})
1✔
1484

1485
    chromaticity_diagram_callable(**settings)
1✔
1486

1487
    ellipse_settings_collection = [
1✔
1488
        {
1489
            "color": CONSTANTS_COLOUR_STYLE.colour.cycle[4],
1490
            "alpha": 0.4,
1491
            "linewidth": colour_style()["lines.linewidth"],
1492
            "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
1493
        }
1494
        for _ellipses_coefficient in ellipses_coefficients
1495
    ]
1496

1497
    if ellipse_kwargs is not None:
1✔
1498
        update_settings_collection(
1✔
1499
            ellipse_settings_collection,
1500
            ellipse_kwargs,
1501
            len(ellipses_coefficients),
1502
        )
1503

1504
    for i, coefficients in enumerate(ellipses_coefficients):
1✔
1505
        x_c, y_c, a_a, a_b, theta_e = coefficients
1✔
1506
        ellipse = Ellipse(
1✔
1507
            (x_c, y_c),
1508
            a_a,
1509
            a_b,
1510
            angle=theta_e,
1511
            **ellipse_settings_collection[i],
1512
        )
1513
        axes.add_artist(ellipse)
1✔
1514

1515
    settings.update({"show": True})
1✔
1516
    settings.update(kwargs)
1✔
1517

1518
    return render(**settings)
1✔
1519

1520

1521
@override_style()
1✔
1522
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931(
1✔
1523
    chromaticity_diagram_callable_CIE1931: Callable = (
1524
        plot_chromaticity_diagram_CIE1931
1525
    ),
1526
    chromaticity_diagram_clipping: bool = False,
1527
    ellipse_kwargs: dict | List[dict] | None = None,
1528
    **kwargs: Any,
1529
) -> Tuple[plt.Figure, plt.Axes]:
1530
    """
1531
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1532
    *CIE 1931 Chromaticity Diagram*.
1533

1534
    Parameters
1535
    ----------
1536
    chromaticity_diagram_callable_CIE1931
1537
        Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
1538
    chromaticity_diagram_clipping
1539
        Whether to clip the *CIE 1931 Chromaticity Diagram* colours with the
1540
        ellipses.
1541
    ellipse_kwargs
1542
        Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
1543
        be either a single dictionary applied to all the ellipses with same
1544
        settings or a sequence of dictionaries with different settings for each
1545
        ellipse.
1546

1547
    Other Parameters
1548
    ----------------
1549
    kwargs
1550
        {:func:`colour.plotting.artist`,
1551
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1552
        :func:`colour.plotting.models.\
1553
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1554
        :func:`colour.plotting.render`},
1555
        See the documentation of the previously listed definitions.
1556

1557
    Returns
1558
    -------
1559
    :class:`tuple`
1560
        Current figure and axes.
1561

1562
    Examples
1563
    --------
1564
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931()
1565
    ... # doctest: +ELLIPSIS
1566
    (<Figure size ... with 1 Axes>, <...Axes...>)
1567

1568
    .. image:: ../_static/\
1569
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1931.png
1570
        :align: center
1571
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931
1572
    """
1573

1574
    settings = dict(kwargs)
1✔
1575
    settings.update({"method": "CIE 1931"})
1✔
1576

1577
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1578
        chromaticity_diagram_callable_CIE1931,
1579
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1580
        ellipse_kwargs=ellipse_kwargs,
1581
        **settings,
1582
    )
1583

1584

1585
@override_style()
1✔
1586
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS(
1✔
1587
    chromaticity_diagram_callable_CIE1960UCS: Callable = (
1588
        plot_chromaticity_diagram_CIE1960UCS
1589
    ),
1590
    chromaticity_diagram_clipping: bool = False,
1591
    ellipse_kwargs: dict | List[dict] | None = None,
1592
    **kwargs: Any,
1593
) -> Tuple[plt.Figure, plt.Axes]:
1594
    """
1595
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1596
    *CIE 1960 UCS Chromaticity Diagram*.
1597

1598
    Parameters
1599
    ----------
1600
    chromaticity_diagram_callable_CIE1960UCS
1601
        Callable responsible for drawing the
1602
        *CIE 1960 UCS Chromaticity Diagram*.
1603
    chromaticity_diagram_clipping
1604
        Whether to clip the *CIE 1960 UCS Chromaticity Diagram* colours with
1605
        the ellipses.
1606
    ellipse_kwargs
1607
        Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
1608
        be either a single dictionary applied to all the ellipses with same
1609
        settings or a sequence of dictionaries with different settings for each
1610
        ellipse.
1611

1612
    Other Parameters
1613
    ----------------
1614
    kwargs
1615
        {:func:`colour.plotting.artist`,
1616
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1617
        :func:`colour.plotting.models.\
1618
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1619
        :func:`colour.plotting.render`},
1620
        See the documentation of the previously listed definitions.
1621

1622
    Returns
1623
    -------
1624
    :class:`tuple`
1625
        Current figure and axes.
1626

1627
    Examples
1628
    --------
1629
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS()
1630
    ... # doctest: +ELLIPSIS
1631
    (<Figure size ... with 1 Axes>, <...Axes...>)
1632

1633
    .. image:: ../_static/\
1634
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1960UCS.png
1635
        :align: center
1636
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS
1637
    """
1638

1639
    settings = dict(kwargs)
1✔
1640
    settings.update({"method": "CIE 1960 UCS"})
1✔
1641

1642
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1643
        chromaticity_diagram_callable_CIE1960UCS,
1644
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1645
        ellipse_kwargs=ellipse_kwargs,
1646
        **settings,
1647
    )
1648

1649

1650
@override_style()
1✔
1651
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS(
1✔
1652
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
1653
        plot_chromaticity_diagram_CIE1976UCS
1654
    ),
1655
    chromaticity_diagram_clipping: bool = False,
1656
    ellipse_kwargs: dict | List[dict] | None = None,
1657
    **kwargs: Any,
1658
) -> Tuple[plt.Figure, plt.Axes]:
1659
    """
1660
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1661
    *CIE 1976 UCS Chromaticity Diagram*.
1662

1663
    Parameters
1664
    ----------
1665
    chromaticity_diagram_callable_CIE1976UCS
1666
        Callable responsible for drawing the
1667
        *CIE 1976 UCS Chromaticity Diagram*.
1668
    chromaticity_diagram_clipping
1669
        Whether to clip the *CIE 1976 UCS Chromaticity Diagram* colours with
1670
        the ellipses.
1671
    ellipse_kwargs
1672
        Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
1673
        be either a single dictionary applied to all the ellipses with same
1674
        settings or a sequence of dictionaries with different settings for each
1675
        ellipse.
1676

1677
    Other Parameters
1678
    ----------------
1679
    kwargs
1680
        {:func:`colour.plotting.artist`,
1681
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1682
        :func:`colour.plotting.models.\
1683
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1684
        :func:`colour.plotting.render`},
1685
        See the documentation of the previously listed definitions.
1686

1687
    Returns
1688
    -------
1689
    :class:`tuple`
1690
        Current figure and axes.
1691

1692
    Examples
1693
    --------
1694
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS()
1695
    ... # doctest: +ELLIPSIS
1696
    (<Figure size ... with 1 Axes>, <...Axes...>)
1697

1698
    .. image:: ../_static/\
1699
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1976UCS.png
1700
        :align: center
1701
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS
1702
    """
1703

1704
    settings = dict(kwargs)
1✔
1705
    settings.update({"method": "CIE 1976 UCS"})
1✔
1706

1707
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1708
        chromaticity_diagram_callable_CIE1976UCS,
1709
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1710
        ellipse_kwargs=ellipse_kwargs,
1711
        **settings,
1712
    )
1713

1714

1715
@override_style()
1✔
1716
def plot_single_cctf(
1✔
1717
    cctf: Callable | str, cctf_decoding: bool = False, **kwargs: Any
1718
) -> Tuple[plt.Figure, plt.Axes]:
1719
    """
1720
    Plot given colourspace colour component transfer function.
1721

1722
    Parameters
1723
    ----------
1724
    cctf
1725
        Colour component transfer function to plot. ``function`` can be of any
1726
        type or form supported by the
1727
        :func:`colour.plotting.common.filter_passthrough` definition.
1728
    cctf_decoding
1729
        Plot the decoding colour component transfer function instead.
1730

1731
    Other Parameters
1732
    ----------------
1733
    kwargs
1734
        {:func:`colour.plotting.artist`,
1735
        :func:`colour.plotting.plot_multi_functions`,
1736
        :func:`colour.plotting.render`},
1737
        See the documentation of the previously listed definitions.
1738

1739
    Returns
1740
    -------
1741
    :class:`tuple`
1742
        Current figure and axes.
1743

1744
    Examples
1745
    --------
1746
    >>> plot_single_cctf("ITU-R BT.709")  # doctest: +ELLIPSIS
1747
    (<Figure size ... with 1 Axes>, <...Axes...>)
1748

1749
    .. image:: ../_static/Plotting_Plot_Single_CCTF.png
1750
        :align: center
1751
        :alt: plot_single_cctf
1752
    """
1753

1754
    settings: Dict[str, Any] = {
1✔
1755
        "title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF"
1756
    }
1757
    settings.update(kwargs)
1✔
1758

1759
    return plot_multi_cctfs([cctf], cctf_decoding, **settings)
1✔
1760

1761

1762
@override_style()
1✔
1763
def plot_multi_cctfs(
1✔
1764
    cctfs: Callable | str | Sequence[Callable | str],
1765
    cctf_decoding: bool = False,
1766
    **kwargs: Any,
1767
) -> Tuple[plt.Figure, plt.Axes]:
1768
    """
1769
    Plot given colour component transfer functions.
1770

1771
    Parameters
1772
    ----------
1773
    cctfs
1774
        Colour component transfer function to plot. ``cctfs`` elements can be
1775
        of any type or form supported by the
1776
        :func:`colour.plotting.common.filter_passthrough` definition.
1777
    cctf_decoding
1778
        Plot the decoding colour component transfer function instead.
1779

1780
    Other Parameters
1781
    ----------------
1782
    kwargs
1783
        {:func:`colour.plotting.artist`,
1784
        :func:`colour.plotting.plot_multi_functions`,
1785
        :func:`colour.plotting.render`},
1786
        See the documentation of the previously listed definitions.
1787

1788
    Returns
1789
    -------
1790
    :class:`tuple`
1791
        Current figure and axes.
1792

1793
    Examples
1794
    --------
1795
    >>> plot_multi_cctfs(["ITU-R BT.709", "sRGB"])  # doctest: +ELLIPSIS
1796
    (<Figure size ... with 1 Axes>, <...Axes...>)
1797

1798
    .. image:: ../_static/Plotting_Plot_Multi_CCTFs.png
1799
        :align: center
1800
        :alt: plot_multi_cctfs
1801
    """
1802

1803
    cctfs_filtered = filter_passthrough(
1804
        CCTF_DECODINGS if cctf_decoding else CCTF_ENCODINGS, cctfs
1805
    )
1806

1807
    mode = "Decoding" if cctf_decoding else "Encoding"
1✔
1808
    title = f"{', '.join(list(cctfs_filtered))} - {mode} CCTFs"
1✔
1809

1810
    settings: Dict[str, Any] = {
1✔
1811
        "bounding_box": (0, 1, 0, 1),
1812
        "legend": True,
1813
        "title": title,
1814
        "x_label": "Signal Value" if cctf_decoding else "Tristimulus Value",
1815
        "y_label": "Tristimulus Value" if cctf_decoding else "Signal Value",
1816
    }
1817
    settings.update(kwargs)
1✔
1818

1819
    with domain_range_scale("1"):
1✔
1820
        return plot_multi_functions(cctfs_filtered, **settings)
1✔
1821

1822

1823
@override_style()
1✔
1824
def plot_constant_hue_loci(
1✔
1825
    data: ArrayLike,
1826
    model: Literal[
1827
        "CAM02LCD",
1828
        "CAM02SCD",
1829
        "CAM02UCS",
1830
        "CAM16LCD",
1831
        "CAM16SCD",
1832
        "CAM16UCS",
1833
        "CIE XYZ",
1834
        "CIE xyY",
1835
        "CIE Lab",
1836
        "CIE Luv",
1837
        "CIE UCS",
1838
        "CIE UVW",
1839
        "DIN99",
1840
        "Hunter Lab",
1841
        "Hunter Rdab",
1842
        "ICaCb",
1843
        "ICtCp",
1844
        "IPT",
1845
        "IPT Ragoo 2021",
1846
        "IgPgTg",
1847
        "Jzazbz",
1848
        "OSA UCS",
1849
        "Oklab",
1850
        "hdr-CIELAB",
1851
        "hdr-IPT",
1852
    ]
1853
    | str = "CIE Lab",
1854
    scatter_kwargs: dict | None = None,
1855
    convert_kwargs: dict | None = None,
1856
    **kwargs: Any,
1857
) -> Tuple[plt.Figure, plt.Axes]:
1858
    """
1859
    Plot given constant hue loci colour matches data such as that from
1860
    :cite:`Hung1995` or :cite:`Ebner1998` that are easily loaded with
1861
    `Colour - Datasets <https://github.com/colour-science/colour-datasets>`__.
1862

1863
    Parameters
1864
    ----------
1865
    data
1866
        Constant hue loci colour matches data expected to be an `ArrayLike` as
1867
        follows::
1868

1869
            [
1870
                ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
1871
    {metadata}),
1872
                ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
1873
    {metadata}),
1874
                ('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
1875
    {metadata}),
1876
                ...
1877
            ]
1878

1879
        where ``name`` is the hue angle or name, ``XYZ_r`` the *CIE XYZ*
1880
        tristimulus values of the reference illuminant, ``XYZ_cr`` the
1881
        *CIE XYZ* tristimulus values of the reference colour under the
1882
        reference illuminant, ``XYZ_ct`` the *CIE XYZ* tristimulus values of
1883
        the colour matches under the reference illuminant and ``metadata`` the
1884
        dataset metadata.
1885
    model
1886
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
1887
        the list of supported colourspace models.
1888
    scatter_kwargs
1889
        Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
1890
        The following special keyword arguments can also be used:
1891

1892
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
1893
            as given by the ``RGB`` argument.
1894
    convert_kwargs
1895
        Keyword arguments for the :func:`colour.convert` definition.
1896

1897
    Other Parameters
1898
    ----------------
1899
    kwargs
1900
        {:func:`colour.plotting.artist`,
1901
        :func:`colour.plotting.plot_multi_functions`,
1902
        :func:`colour.plotting.render`},
1903
        See the documentation of the previously listed definitions.
1904

1905
    Returns
1906
    -------
1907
    :class:`tuple`
1908
        Current figure and axes.
1909

1910
    References
1911
    ----------
1912
    :cite:`Ebner1998`, :cite:`Hung1995`, :cite:`Mansencal2019`
1913

1914
    Examples
1915
    --------
1916
    >>> data = [
1917
    ...     [
1918
    ...         None,
1919
    ...         np.array([0.95010000, 1.00000000, 1.08810000]),
1920
    ...         np.array([0.40920000, 0.28120000, 0.30600000]),
1921
    ...         np.array(
1922
    ...             [
1923
    ...                 [0.02495100, 0.01908600, 0.02032900],
1924
    ...                 [0.10944300, 0.06235900, 0.06788100],
1925
    ...                 [0.27186500, 0.18418700, 0.19565300],
1926
    ...                 [0.48898900, 0.40749400, 0.44854600],
1927
    ...             ]
1928
    ...         ),
1929
    ...         None,
1930
    ...     ],
1931
    ...     [
1932
    ...         None,
1933
    ...         np.array([0.95010000, 1.00000000, 1.08810000]),
1934
    ...         np.array([0.30760000, 0.48280000, 0.42770000]),
1935
    ...         np.array(
1936
    ...             [
1937
    ...                 [0.02108000, 0.02989100, 0.02790400],
1938
    ...                 [0.06194700, 0.11251000, 0.09334400],
1939
    ...                 [0.15255800, 0.28123300, 0.23234900],
1940
    ...                 [0.34157700, 0.56681300, 0.47035300],
1941
    ...             ]
1942
    ...         ),
1943
    ...         None,
1944
    ...     ],
1945
    ...     [
1946
    ...         None,
1947
    ...         np.array([0.95010000, 1.00000000, 1.08810000]),
1948
    ...         np.array([0.39530000, 0.28120000, 0.18450000]),
1949
    ...         np.array(
1950
    ...             [
1951
    ...                 [0.02436400, 0.01908600, 0.01468800],
1952
    ...                 [0.10331200, 0.06235900, 0.02854600],
1953
    ...                 [0.26311900, 0.18418700, 0.12109700],
1954
    ...                 [0.43158700, 0.40749400, 0.39008600],
1955
    ...             ]
1956
    ...         ),
1957
    ...         None,
1958
    ...     ],
1959
    ...     [
1960
    ...         None,
1961
    ...         np.array([0.95010000, 1.00000000, 1.08810000]),
1962
    ...         np.array([0.20510000, 0.18420000, 0.57130000]),
1963
    ...         np.array(
1964
    ...             [
1965
    ...                 [0.03039800, 0.02989100, 0.06123300],
1966
    ...                 [0.08870000, 0.08498400, 0.21843500],
1967
    ...                 [0.18405800, 0.18418700, 0.40111400],
1968
    ...                 [0.32550100, 0.34047200, 0.50296900],
1969
    ...                 [0.53826100, 0.56681300, 0.80010400],
1970
    ...             ]
1971
    ...         ),
1972
    ...         None,
1973
    ...     ],
1974
    ...     [
1975
    ...         None,
1976
    ...         np.array([0.95010000, 1.00000000, 1.08810000]),
1977
    ...         np.array([0.35770000, 0.28120000, 0.11250000]),
1978
    ...         np.array(
1979
    ...             [
1980
    ...                 [0.03678100, 0.02989100, 0.01481100],
1981
    ...                 [0.17127700, 0.11251000, 0.01229900],
1982
    ...                 [0.30080900, 0.28123300, 0.21229800],
1983
    ...                 [0.52976000, 0.40749400, 0.11720000],
1984
    ...             ]
1985
    ...         ),
1986
    ...         None,
1987
    ...     ],
1988
    ... ]
1989
    >>> plot_constant_hue_loci(data, "CIE Lab")  # doctest: +ELLIPSIS
1990
    (<Figure size ... with 1 Axes>, <...Axes...>)
1991

1992
    .. image:: ../_static/Plotting_Plot_Constant_Hue_Loci.png
1993
        :align: center
1994
        :alt: plot_constant_hue_loci
1995
    """
1996

1997
    # TODO: Filter appropriate colour models.
1998
    # NOTE: "dtype=object" is required for ragged array support
1999
    # in "Numpy" 1.24.0.
2000
    data = as_array(data, dtype=object)  # pyright: ignore
1✔
2001

2002
    settings: Dict[str, Any] = {"uniform": True}
1✔
2003
    settings.update(kwargs)
1✔
2004

2005
    _figure, axes = artist(**settings)
1✔
2006

2007
    scatter_settings = {
1✔
2008
        "s": 40,
2009
        "c": "RGB",
2010
        "marker": "o",
2011
        "alpha": 0.85,
2012
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
2013
    }
2014
    if scatter_kwargs is not None:
1✔
2015
        scatter_settings.update(scatter_kwargs)
1✔
2016

2017
    convert_kwargs = optional(convert_kwargs, {})
1✔
2018

2019
    use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1✔
2020

2021
    colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1✔
2022
    for hue_data in data:
1✔
2023
        _name, XYZ_r, XYZ_cr, XYZ_ct, _metadata = hue_data
1✔
2024

2025
        xy_r = XYZ_to_xy(XYZ_r)
1✔
2026

2027
        convert_settings = {"illuminant": xy_r}
1✔
2028
        convert_settings.update(convert_kwargs)
1✔
2029

2030
        ijk_ct = colourspace_model_axis_reorder(
1✔
2031
            convert(XYZ_ct, "CIE XYZ", model, **convert_settings), model
2032
        )
2033
        ijk_cr = colourspace_model_axis_reorder(
1✔
2034
            convert(XYZ_cr, "CIE XYZ", model, **convert_settings), model
2035
        )
2036

2037
        ijk_ct *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
1✔
2038
        ijk_cr *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
1✔
2039

2040
        def _linear_equation(
1✔
2041
            x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat
2042
        ) -> NDArrayFloat:
2043
            """Define the canonical linear equation for a line."""
2044

2045
            return a * x + b
1✔
2046

2047
        popt, _pcov = scipy.optimize.curve_fit(
1✔
2048
            _linear_equation, ijk_ct[..., 0], ijk_ct[..., 1]
2049
        )
2050

2051
        axes.plot(
1✔
2052
            ijk_ct[..., 0],
2053
            _linear_equation(ijk_ct[..., 0], *popt),
2054
            c=CONSTANTS_COLOUR_STYLE.colour.average,
2055
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2056
        )
2057

2058
        if use_RGB_colours:
1✔
2059
            RGB_ct = XYZ_to_RGB(
1✔
2060
                XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True
2061
            )
2062
            scatter_settings["c"] = np.clip(RGB_ct, 0, 1)
1✔
2063
            RGB_cr = XYZ_to_RGB(
1✔
2064
                XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True
2065
            )
2066
            RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1)
1✔
2067
        else:
2068
            scatter_settings["c"] = CONSTANTS_COLOUR_STYLE.colour.dark
×
2069
            RGB_cr = CONSTANTS_COLOUR_STYLE.colour.dark
×
2070

2071
        axes.scatter(ijk_ct[..., 0], ijk_ct[..., 1], **scatter_settings)
1✔
2072

2073
        axes.plot(
1✔
2074
            ijk_cr[..., 0],
2075
            ijk_cr[..., 1],
2076
            "s",
2077
            c=RGB_cr,
2078
            markersize=CONSTANTS_COLOUR_STYLE.geometry.short * 8,
2079
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2080
        )
2081

2082
    labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[
1✔
2083
        as_int_array(colourspace_model_axis_reorder([0, 1, 2], model))
2084
    ]
2085

2086
    settings = {
1✔
2087
        "axes": axes,
2088
        "title": f"Constant Hue Loci - {model}",
2089
        "x_label": labels[0],
2090
        "y_label": labels[1],
2091
    }
2092
    settings.update(kwargs)
1✔
2093

2094
    return render(**settings)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc