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

colour-science / colour / 25544858007

08 May 2026 08:12AM UTC coverage: 77.584% (-21.2%) from 98.818%
25544858007

push

github

KelSolaar
Implement support for *Python Array API Standard*.

14022 of 14574 new or added lines in 367 files covered. (96.21%)

10005 existing lines in 360 files now uncovered.

37920 of 48876 relevant lines covered (77.58%)

0.78 hits per line

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

79.18
/colour/plotting/models.py
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

UNCOV
44
from __future__ import annotations
×
45

UNCOV
46
import typing
×
47

UNCOV
48
import numpy as np
×
UNCOV
49
from matplotlib.collections import LineCollection
×
UNCOV
50
from matplotlib.patches import Ellipse
×
UNCOV
51
from matplotlib.path import Path
×
52

UNCOV
53
from colour.adaptation import chromatic_adaptation_VonKries
×
UNCOV
54
from colour.algebra import normalise_maximum
×
UNCOV
55
from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON
×
UNCOV
56
from colour.geometry import (
×
57
    ellipse_coefficients_canonical_form,
58
    ellipse_fitting,
59
    point_at_angle_on_ellipse,
60
)
UNCOV
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

UNCOV
81
from colour.hints import List, cast
×
UNCOV
82
from colour.models import LCHab_to_Lab  # pyright: ignore
×
UNCOV
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
)
UNCOV
117
from colour.plotting.diagrams import plot_chromaticity_diagram
×
UNCOV
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

UNCOV
132
__author__ = "Colour Developers"
×
UNCOV
133
__copyright__ = "Copyright 2013 Colour Developers"
×
UNCOV
134
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
×
UNCOV
135
__maintainer__ = "Colour Developers"
×
UNCOV
136
__email__ = "colour-developers@colour-science.org"
×
UNCOV
137
__status__ = "Production"
×
138

UNCOV
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

UNCOV
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
)
UNCOV
204
"""Colourspace models axis order."""
×
205

206

UNCOV
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)
1✔
246

247
    model = validate_method(
1✔
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(
1✔
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))
1✔
260

261
    if direction == "forward":
1✔
262
        indexes = (order[0], order[1], order[2])
1✔
263
    else:
264
        indexes = (order.index(0), order.index(1), order.index(2))
1✔
265

266
    return a[..., indexes]
1✔
267

268

UNCOV
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"))
1✔
300

301
    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint
1✔
302

303
    XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
1✔
304
    ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"]
1✔
305

306
    XYZ = xy_to_XYZ(CCS_POINTER_GAMUT_BOUNDARY)
1✔
307
    XYZ = chromatic_adaptation_VonKries(
1✔
308
        XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
309
    )
310
    ij_b = XYZ_to_ij(XYZ)
1✔
311
    ij_b = np.vstack([ij_b, ij_b[0]])
1✔
312
    colours_b = normalise_maximum(
1✔
313
        XYZ_to_plotting_colourspace(ij_to_XYZ(ij_b, illuminant), illuminant),
314
        axis=-1,
315
    )
316

317
    lines_b = zeros(
1✔
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
1✔
327
    lines_b["colour"] = colours_b
1✔
328

329
    XYZ = Lab_to_XYZ(
1✔
330
        LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT
331
    )
332
    XYZ = chromatic_adaptation_VonKries(
1✔
333
        XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
334
    )
335
    ij_v = XYZ_to_ij(XYZ)
1✔
336

337
    colours_v = normalise_maximum(
1✔
338
        XYZ_to_plotting_colourspace(ij_to_XYZ(ij_v, illuminant), illuminant),
339
        axis=-1,
340
    )
341

342
    lines_v = zeros(
1✔
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
1✔
352
    lines_v["colour"] = colours_v
1✔
353

354
    return lines_b, lines_v
1✔
355

356

UNCOV
357
@override_style()
×
UNCOV
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"))
1✔
399

400
    pointer_gamut_colours = optional(
1✔
401
        pointer_gamut_colours, CONSTANTS_COLOUR_STYLE.colour.dark
402
    )
403

404
    use_RGB_colours = str(pointer_gamut_colours).upper() == "RGB"
1✔
405

406
    pointer_gamut_opacity = optional(
1✔
407
        pointer_gamut_opacity, CONSTANTS_COLOUR_STYLE.opacity.high
408
    )
409

410
    settings: Dict[str, Any] = {"uniform": True}
1✔
411
    settings.update(kwargs)
1✔
412

413
    _figure, axes = artist(**settings)
1✔
414

415
    lines_b, lines_v = lines_pointer_gamut(method)
1✔
416

417
    axes.add_collection(
1✔
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 = {
1✔
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(
1✔
439
        lines_v["position"][..., 0],
440
        lines_v["position"][..., 1],
441
        **scatter_settings,
442
    )
443

444
    settings.update({"axes": axes})
1✔
445
    settings.update(kwargs)
1✔
446

447
    return render(**settings)
1✔
448

449

UNCOV
450
@override_style()
×
UNCOV
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"))
1✔
538

539
    colourspaces = cast(
1✔
540
        "List[RGB_Colourspace]",
541
        list(filter_RGB_colourspaces(colourspaces).values()),
542
    )  # pyright: ignore
543

544
    settings: Dict[str, Any] = {"uniform": True}
1✔
545
    settings.update(kwargs)
1✔
546

547
    _figure, axes = artist(**settings)
1✔
548

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

551
    title = (
1✔
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}
1✔
557
    settings.update(kwargs)
1✔
558
    settings["show"] = False
1✔
559

560
    chromaticity_diagram_callable(**settings)
1✔
561

562
    if show_pointer_gamut:
1✔
563
        settings = {"axes": axes, "method": method}
1✔
564
        settings.update(kwargs)
1✔
565
        settings["show"] = False
1✔
566

567
        plot_pointer_gamut(**settings)
1✔
568

569
    xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
1✔
570

571
    if method == "cie 1931":
1✔
572
        x_limit_min, x_limit_max = [-0.1], [0.9]
1✔
573
        y_limit_min, y_limit_max = [-0.1], [0.9]
1✔
574

575
    elif method == "cie 1960 ucs":
1✔
576
        x_limit_min, x_limit_max = [-0.1], [0.7]
1✔
577
        y_limit_min, y_limit_max = [-0.2], [0.6]
1✔
578

579
    elif method == "cie 1976 ucs":
1✔
580
        x_limit_min, x_limit_max = [-0.1], [0.7]
1✔
581
        y_limit_min, y_limit_max = [-0.1], [0.7]
1✔
582

583
    settings = {"colour_cycle_count": len(colourspaces)}
1✔
584
    settings.update(kwargs)
1✔
585

586
    cycle = colour_cycle(**settings)
1✔
587

588
    plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1✔
589

590
    plot_settings_collection = [
1✔
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:
1✔
601
        update_settings_collection(
1✔
602
            plot_settings_collection, plot_kwargs, len(colourspaces)
603
        )
604

605
    for i, colourspace in enumerate(colourspaces):
1✔
606
        plot_settings = plot_settings_collection[i]
1✔
607

608
        if chromatically_adapt and not np.array_equal(
1✔
609
            colourspace.whitepoint, plotting_colourspace.whitepoint
610
        ):
611
            colourspace = colourspace.chromatically_adapt(  # noqa: PLW2901
1✔
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(
1✔
620
            colourspace.primaries == 0,
621
            EPSILON,
622
            colourspace.primaries,
623
        )
624
        P = xy_to_ij(P)
1✔
625
        W = xy_to_ij(colourspace.whitepoint)
1✔
626

627
        P_p = np.vstack([P, P[0]])
1✔
628
        axes.plot(P_p[..., 0], P_p[..., 1], **plot_settings)
1✔
629

630
        if show_whitepoints:
1✔
631
            plot_settings["marker"] = "o"
1✔
632
            plot_settings.pop("label")
1✔
633

634
            W_p = np.vstack([W, W])
1✔
635
            axes.plot(W_p[..., 0], W_p[..., 1], **plot_settings)
1✔
636

637
        x_limit_min.append(cast("float", np.amin(P[..., 0]) - 0.1))
1✔
638
        y_limit_min.append(cast("float", np.amin(P[..., 1]) - 0.1))
1✔
639
        x_limit_max.append(cast("float", np.amax(P[..., 0]) + 0.1))
1✔
640
        y_limit_max.append(cast("float", np.amax(P[..., 1]) + 0.1))
1✔
641

642
    bounding_box = (
1✔
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(
1✔
650
        {
651
            "show": True,
652
            "legend": True,
653
            "bounding_box": bounding_box,
654
        }
655
    )
656
    settings.update(kwargs)
1✔
657

658
    return render(**settings)
1✔
659

660

UNCOV
661
@override_style()
×
UNCOV
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)
1✔
745
    settings.update({"method": "CIE 1931"})
1✔
746

747
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
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

UNCOV
759
@override_style()
×
UNCOV
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)
1✔
844
    settings.update({"method": "CIE 1960 UCS"})
1✔
845

846
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
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

UNCOV
858
@override_style()
×
UNCOV
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)
1✔
943
    settings.update({"method": "CIE 1976 UCS"})
1✔
944

945
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
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

UNCOV
957
@override_style()
×
UNCOV
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))
1✔
1026
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
1027

1028
    settings: Dict[str, Any] = {"uniform": True}
1✔
1029
    settings.update(kwargs)
1✔
1030

1031
    _figure, axes = artist(**settings)
1✔
1032

1033
    scatter_settings = {
1✔
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:
1✔
1042
        scatter_settings.update(scatter_kwargs)
1✔
1043

1044
    settings = dict(kwargs)
1✔
1045
    settings.update({"axes": axes, "show": False})
1✔
1046

1047
    colourspace = cast(
1✔
1048
        "RGB_Colourspace",
1049
        first_item(filter_RGB_colourspaces(colourspace).values()),
1050
    )
1051

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

1054
    chromaticity_diagram_callable(**settings)
1✔
1055

1056
    use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1✔
1057
    apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding")
1✔
1058
    if use_RGB_colours:
1✔
1059
        RGB = RGB[RGB[:, 1].argsort()]
1✔
1060
        scatter_settings["c"] = np.clip(
1✔
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)
1✔
1075

1076
    XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
1✔
1077

1078
    ij = XYZ_to_ij(XYZ, colourspace.whitepoint)
1✔
1079

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

1082
    settings.update({"show": True})
1✔
1083
    settings.update(kwargs)
1✔
1084

1085
    return render(**settings)
1✔
1086

1087

UNCOV
1088
@override_style()
×
UNCOV
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)
1✔
1158
    settings.update({"method": "CIE 1931"})
1✔
1159

1160
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1161
        RGB,
1162
        colourspace,
1163
        chromaticity_diagram_callable_CIE1931,
1164
        scatter_kwargs=scatter_kwargs,
1165
        **settings,
1166
    )
1167

1168

UNCOV
1169
@override_style()
×
UNCOV
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)
1✔
1239
    settings.update({"method": "CIE 1960 UCS"})
1✔
1240

1241
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1242
        RGB,
1243
        colourspace,
1244
        chromaticity_diagram_callable_CIE1960UCS,
1245
        scatter_kwargs=scatter_kwargs,
1246
        **settings,
1247
    )
1248

1249

UNCOV
1250
@override_style()
×
UNCOV
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)
1✔
1320
    settings.update({"method": "CIE 1976 UCS"})
1✔
1321

1322
    return plot_RGB_chromaticities_in_chromaticity_diagram(
1✔
1323
        RGB,
1324
        colourspace,
1325
        chromaticity_diagram_callable_CIE1976UCS,
1326
        scatter_kwargs=scatter_kwargs,
1327
        **settings,
1328
    )
1329

1330

UNCOV
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"))
1✔
1356

1357
    xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
1✔
1358

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

1361
    ellipses_coefficients = []
1✔
1362
    for i in range(len(theta)):
1✔
1363
        xy = point_at_angle_on_ellipse(
1✔
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)
1✔
1368
        ellipses_coefficients.append(
1✔
1369
            ellipse_coefficients_canonical_form(ellipse_fitting(ij))
1370
        )
1371

1372
    return ellipses_coefficients
1✔
1373

1374

UNCOV
1375
@override_style()
×
UNCOV
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}
1✔
1428
    settings.update(kwargs)
1✔
1429

1430
    _figure, axes = artist(**settings)
1✔
1431

1432
    settings = dict(kwargs)
1✔
1433
    settings.update({"axes": axes, "show": False})
1✔
1434

1435
    ellipses_coefficients = ellipses_MacAdam1942(method=method)
1✔
1436

1437
    if chromaticity_diagram_clipping:
1✔
1438
        diagram_clipping_path_x = []
1✔
1439
        diagram_clipping_path_y = []
1✔
1440
        for coefficients in ellipses_coefficients:
1✔
1441
            coefficients = np.copy(coefficients)  # noqa: PLW2901
1✔
1442

1443
            coefficients[2:4] /= 2
1✔
1444

1445
            x, y = tsplit(
1✔
1446
                point_at_angle_on_ellipse(
1447
                    np.linspace(0, 360, 36),
1448
                    coefficients,
1449
                )
1450
            )
1451
            diagram_clipping_path_x.append(x)
1✔
1452
            diagram_clipping_path_y.append(y)
1✔
1453

1454
        diagram_clipping_path = np.rollaxis(
1✔
1455
            np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3
1456
        )
1457
        diagram_clipping_path = Path.make_compound_path_from_polys(
1✔
1458
            diagram_clipping_path
1459
        ).vertices
1460
        settings.update({"diagram_clipping_path": diagram_clipping_path})
1✔
1461

1462
    chromaticity_diagram_callable(**settings)
1✔
1463

1464
    ellipse_settings_collection = [
1✔
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:
1✔
1475
        update_settings_collection(
1✔
1476
            ellipse_settings_collection,
1477
            ellipse_kwargs,
1478
            len(ellipses_coefficients),
1479
        )
1480

1481
    for i, coefficients in enumerate(ellipses_coefficients):
1✔
1482
        x_c, y_c, a_a, a_b, theta_e = coefficients
1✔
1483
        ellipse = Ellipse(
1✔
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)
1✔
1491

1492
    settings.update({"show": True})
1✔
1493
    settings.update(kwargs)
1✔
1494

1495
    return render(**settings)
1✔
1496

1497

UNCOV
1498
@override_style()
×
UNCOV
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)
1✔
1552
    settings.update({"method": "CIE 1931"})
1✔
1553

1554
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1555
        chromaticity_diagram_callable_CIE1931,
1556
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1557
        ellipse_kwargs=ellipse_kwargs,
1558
        **settings,
1559
    )
1560

1561

UNCOV
1562
@override_style()
×
UNCOV
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)
1✔
1617
    settings.update({"method": "CIE 1960 UCS"})
1✔
1618

1619
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1620
        chromaticity_diagram_callable_CIE1960UCS,
1621
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1622
        ellipse_kwargs=ellipse_kwargs,
1623
        **settings,
1624
    )
1625

1626

UNCOV
1627
@override_style()
×
UNCOV
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)
1✔
1682
    settings.update({"method": "CIE 1976 UCS"})
1✔
1683

1684
    return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1✔
1685
        chromaticity_diagram_callable_CIE1976UCS,
1686
        chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1687
        ellipse_kwargs=ellipse_kwargs,
1688
        **settings,
1689
    )
1690

1691

UNCOV
1692
@override_style()
×
UNCOV
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] = {
1✔
1732
        "title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF"
1733
    }
1734
    settings.update(kwargs)
1✔
1735

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

1738

UNCOV
1739
@override_style()
×
UNCOV
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"
1✔
1785
    title = f"{', '.join(list(cctfs_filtered))} - {mode} CCTFs"
1✔
1786

1787
    settings: Dict[str, Any] = {
1✔
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)
1✔
1795

1796
    with domain_range_scale("1"):
1✔
1797
        return plot_multi_functions(cctfs_filtered, **settings)
1✔
1798

1799

UNCOV
1800
@override_style()
×
UNCOV
1801
@required("SciPy")
×
UNCOV
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
1✔
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
1✔
1954

1955
    settings: Dict[str, Any] = {"uniform": True}
1✔
1956
    settings.update(kwargs)
1✔
1957

1958
    _figure, axes = artist(**settings)
1✔
1959

1960
    scatter_settings = {
1✔
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:
1✔
1968
        scatter_settings.update(scatter_kwargs)
1✔
1969

1970
    convert_kwargs = optional(convert_kwargs, {})
1✔
1971

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

1974
    colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1✔
1975
    for hue_data in data:
1✔
1976
        _name, XYZ_r, XYZ_cr, XYZ_ct, _metadata = hue_data
1✔
1977

1978
        xy_r = XYZ_to_xy(XYZ_r)
1✔
1979

1980
        convert_settings = {"illuminant": xy_r}
1✔
1981
        convert_settings.update(convert_kwargs)
1✔
1982

1983
        ijk_ct = colourspace_model_axis_reorder(
1✔
1984
            convert(XYZ_ct, "CIE XYZ", model, **convert_settings),  # pyright: ignore
1985
            model,
1986
        )
1987
        ijk_cr = colourspace_model_axis_reorder(
1✔
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)
1✔
1993
        ijk_cr = colourspace_model_to_reference(ijk_cr, model)
1✔
1994

1995
        def _linear_equation(
1✔
1996
            x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat
1997
        ) -> NDArrayFloat:
1998
            """Define the canonical linear equation for a line."""
1999

2000
            return a * x + b
1✔
2001

2002
        popt, _pcov = scipy.optimize.curve_fit(
1✔
2003
            _linear_equation, ijk_ct[..., 0], ijk_ct[..., 1]
2004
        )
2005

2006
        axes.plot(
1✔
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:
1✔
2014
            RGB_ct = XYZ_to_RGB(XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True)
1✔
2015
            scatter_settings["c"] = np.clip(RGB_ct, 0, 1)
1✔
2016
            RGB_cr = XYZ_to_RGB(XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True)
1✔
2017
            RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1)
1✔
2018
        else:
2019
            RGB_cr = scatter_settings["c"]
1✔
2020

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

2023
        axes.plot(
1✔
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])[
1✔
2033
        as_int_array(colourspace_model_axis_reorder([0, 1, 2], model))
2034
    ]
2035

2036
    settings = {
1✔
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)
1✔
2043

2044
    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