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

colour-science / colour / 18370061804

09 Oct 2025 08:19AM UTC coverage: 76.753% (-22.6%) from 99.349%
18370061804

push

github

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

32663 of 42556 relevant lines covered (76.75%)

0.77 hits per line

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

79.11
/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

44
from __future__ import annotations
×
45

46
import typing
×
47

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

54
from colour.adaptation import chromatic_adaptation_VonKries
×
55
from colour.algebra import normalise_maximum
×
56
from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON
×
57
from colour.geometry import (
×
58
    ellipse_coefficients_canonical_form,
59
    ellipse_fitting,
60
    point_at_angle_on_ellipse,
61
)
62
from colour.graph import convert
×
63

64
if typing.TYPE_CHECKING:
65
    from matplotlib.axes import Axes
66
    from matplotlib.figure import Figure
67
    from colour.colorimetry import MultiSpectralDistributions
68
    from colour.hints import (
69
        Any,
70
        ArrayLike,
71
        Callable,
72
        Dict,
73
        Literal,
74
        LiteralColourspaceModel,
75
        LiteralRGBColourspace,
76
        NDArray,
77
        NDArrayFloat,
78
        Sequence,
79
        Tuple,
80
    )
81

82
from colour.hints import List, cast
×
83
from colour.models import LCHab_to_Lab  # pyright: ignore
×
84
from colour.models import (
×
85
    CCS_ILLUMINANT_POINTER_GAMUT,
86
    CCS_POINTER_GAMUT_BOUNDARY,
87
    CCTF_DECODINGS,
88
    CCTF_ENCODINGS,
89
    COLOURSPACE_MODELS_AXIS_LABELS,
90
    COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE,
91
    DATA_MACADAM_1942_ELLIPSES,
92
    DATA_POINTER_GAMUT_VOLUME,
93
    Lab_to_XYZ,
94
    RGB_Colourspace,
95
    RGB_to_RGB,
96
    RGB_to_XYZ,
97
    XYZ_to_RGB,
98
    XYZ_to_xy,
99
    xy_to_XYZ,
100
)
101
from colour.plotting import (
102
    CONSTANTS_COLOUR_STYLE,
103
    METHODS_CHROMATICITY_DIAGRAM,
104
    XYZ_to_plotting_colourspace,
105
    artist,
106
    colour_cycle,
107
    colour_style,
108
    filter_cmfs,
109
    filter_passthrough,
110
    filter_RGB_colourspaces,
111
    override_style,
112
    plot_chromaticity_diagram_CIE1931,
113
    plot_chromaticity_diagram_CIE1960UCS,
114
    plot_chromaticity_diagram_CIE1976UCS,
115
    plot_multi_functions,
116
    render,
117
    update_settings_collection,
118
)
119
from colour.plotting.diagrams import plot_chromaticity_diagram
×
120
from colour.utilities import (
×
121
    CanonicalMapping,
122
    as_array,
123
    as_float_array,
124
    as_int_array,
125
    domain_range_scale,
126
    first_item,
127
    optional,
128
    tsplit,
129
    validate_method,
130
    zeros,
131
)
132

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

140
__all__ = [
×
141
    "COLOURSPACE_MODELS_AXIS_ORDER",
142
    "colourspace_model_axis_reorder",
143
    "lines_pointer_gamut",
144
    "plot_pointer_gamut",
145
    "plot_RGB_colourspaces_in_chromaticity_diagram",
146
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931",
147
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS",
148
    "plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS",
149
    "plot_RGB_chromaticities_in_chromaticity_diagram",
150
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931",
151
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS",
152
    "plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS",
153
    "ellipses_MacAdam1942",
154
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram",
155
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931",
156
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS",
157
    "plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS",
158
    "plot_single_cctf",
159
    "plot_multi_cctfs",
160
    "plot_constant_hue_loci",
161
]
162

163
COLOURSPACE_MODELS_AXIS_ORDER: CanonicalMapping = CanonicalMapping(
×
164
    {
165
        "CAM02LCD": (1, 2, 0),
166
        "CAM02SCD": (1, 2, 0),
167
        "CAM02UCS": (1, 2, 0),
168
        "CAM16LCD": (1, 2, 0),
169
        "CAM16SCD": (1, 2, 0),
170
        "CAM16UCS": (1, 2, 0),
171
        "CIE 1931": (0, 1, 2),
172
        "CIE 1960 UCS": (0, 1, 2),
173
        "CIE 1976 UCS": (0, 1, 2),
174
        "CIE LCHab": (1, 2, 0),
175
        "CIE LCHuv": (1, 2, 0),
176
        "CIE Lab": (1, 2, 0),
177
        "CIE Luv": (1, 2, 0),
178
        "CIE UCS": (0, 1, 2),
179
        "CIE UVW": (1, 2, 0),
180
        "CIE XYZ": (0, 1, 2),
181
        "CIE xyY": (0, 1, 2),
182
        "DIN99": (1, 2, 0),
183
        "HCL": (0, 1, 2),
184
        "HSL": (0, 1, 2),
185
        "HSV": (0, 1, 2),
186
        "Hunter Lab": (1, 2, 0),
187
        "Hunter Rdab": (1, 2, 0),
188
        "ICaCb": (1, 2, 0),
189
        "ICtCp": (1, 2, 0),
190
        "IHLS": (0, 2, 1),
191
        "IPT Ragoo 2021": (1, 2, 0),
192
        "IPT": (1, 2, 0),
193
        "IgPgTg": (1, 2, 0),
194
        "Jzazbz": (1, 2, 0),
195
        "OSA UCS": (1, 2, 0),
196
        "Oklab": (1, 2, 0),
197
        "RGB": (0, 1, 2),
198
        "YCbCr": (1, 2, 0),
199
        "YCoCg": (1, 2, 0),
200
        "Yrg": (1, 2, 0),
201
        "hdr-CIELAB": (1, 2, 0),
202
        "hdr-IPT": (1, 2, 0),
203
    }
204
)
205
"""Colourspace models axis order."""
×
206

207

208
def colourspace_model_axis_reorder(
×
209
    a: ArrayLike,
210
    model: LiteralColourspaceModel | str,
211
    direction: Literal["Forward", "Inverse"] | str = "Forward",
212
) -> NDArrayFloat:
213
    """
214
    Reorder the axes of the specified colourspace model array :math:`a` to
215
    match the standard axes order used for volume plotting.
216

217
    Parameters
218
    ----------
219
    a
220
        Colourspace model array :math:`a` to be reordered.
221
    model
222
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute
223
        for the list of supported colourspace models.
224
    direction
225
        Reordering direction.
226

227
    Returns
228
    -------
229
    :class:`numpy.ndarray`
230
        Reordered colourspace model array :math:`a`.
231

232
    Examples
233
    --------
234
    >>> a = np.array([0, 1, 2])
235
    >>> colourspace_model_axis_reorder(a, "CIE Lab")
236
    array([ 1.,  2.,  0.])
237
    >>> colourspace_model_axis_reorder(a, "IPT")
238
    array([ 1.,  2.,  0.])
239
    >>> colourspace_model_axis_reorder(a, "OSA UCS")
240
    array([ 1.,  2.,  0.])
241
    >>> b = np.array([1, 2, 0])
242
    >>> colourspace_model_axis_reorder(b, "OSA UCS", "Inverse")
243
    array([ 0.,  1.,  2.])
244
    """
245

246
    a = as_float_array(a)
1✔
247

248
    model = validate_method(
1✔
249
        model,
250
        tuple(COLOURSPACE_MODELS_AXIS_ORDER),
251
        '"{0}" model is invalid, it must be one of {1}!',
252
    )
253

254
    direction = validate_method(
1✔
255
        direction,
256
        ("Forward", "Inverse"),
257
        '"{0}" direction is invalid, it must be one of {1}!',
258
    )
259

260
    order = COLOURSPACE_MODELS_AXIS_ORDER.get(model, (0, 1, 2))
1✔
261

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

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

269

270
def lines_pointer_gamut(
×
271
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
272
) -> tuple[NDArray, NDArray]:
273
    """
274
    Return the *Pointer's Gamut* line vertices, i.e., positions, normals,
275
    and colours, using the specified chromaticity diagram method.
276

277
    Parameters
278
    ----------
279
    method
280
        *Chromaticity Diagram* method.
281

282
    Returns
283
    -------
284
    :class:`tuple`
285
        Tuple of *Pointer's Gamut* boundary and volume vertices.
286

287
    Examples
288
    --------
289
    >>> lines = lines_pointer_gamut()
290
    >>> len(lines)
291
    2
292
    >>> lines[0].dtype
293
    dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \
294
('colour', '<f8', (3,))])
295
    >>> lines[1].dtype
296
    dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \
297
('colour', '<f8', (3,))])
298
    """
299

300
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
301

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

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

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

318
    lines_b = zeros(
1✔
319
        ij_b.shape[0],
320
        [
321
            ("position", DTYPE_FLOAT_DEFAULT, 2),
322
            ("normal", DTYPE_FLOAT_DEFAULT, 2),
323
            ("colour", DTYPE_FLOAT_DEFAULT, 3),
324
        ],  # pyright: ignore
325
    )
326

327
    lines_b["position"] = ij_b
1✔
328
    lines_b["colour"] = colours_b
1✔
329

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

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

343
    lines_v = zeros(
1✔
344
        ij_v.shape[0],
345
        [
346
            ("position", DTYPE_FLOAT_DEFAULT, 2),
347
            ("normal", DTYPE_FLOAT_DEFAULT, 2),
348
            ("colour", DTYPE_FLOAT_DEFAULT, 3),
349
        ],  # pyright: ignore
350
    )
351

352
    lines_v["position"] = ij_v
1✔
353
    lines_v["colour"] = colours_v
1✔
354

355
    return lines_b, lines_v
1✔
356

357

358
@override_style()
×
359
def plot_pointer_gamut(
×
360
    pointer_gamut_colours: ArrayLike | str | None = None,
361
    pointer_gamut_opacity: float = 1,
362
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
363
    **kwargs: Any,
364
) -> Tuple[Figure, Axes]:
365
    """
366
    Plot *Pointer's Gamut* using the specified plotting method.
367

368
    Parameters
369
    ----------
370
    pointer_gamut_colours
371
        Colours of *Pointer's Gamut*.
372
    pointer_gamut_opacity
373
        Opacity of *Pointer's Gamut*.
374
    method
375
        Plotting method.
376

377
    Other Parameters
378
    ----------------
379
    kwargs
380
        {:func:`colour.plotting.artist`,
381
        :func:`colour.plotting.render`},
382
        See the documentation of the previously listed definitions.
383

384
    Returns
385
    -------
386
    :class:`tuple`
387
        Current figure and axes.
388

389
    Examples
390
    --------
391
    >>> plot_pointer_gamut(pointer_gamut_colours="RGB")  # doctest: +ELLIPSIS
392
    (<Figure size ... with 1 Axes>, <...Axes...>)
393

394
    .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png
395
        :align: center
396
        :alt: plot_pointer_gamut
397
    """
398

399
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
400

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

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

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

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

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

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

418
    axes.add_collection(
1✔
419
        LineCollection(
420
            np.reshape(
421
                np.concatenate(
422
                    [lines_b["position"][:-1], lines_b["position"][1:]],
423
                    axis=1,  # pyright: ignore
424
                ),
425
                (-1, 2, 2),
426
            ),
427
            colors=(lines_b["colour"] if use_RGB_colours else pointer_gamut_colours),
428
            alpha=pointer_gamut_opacity,
429
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
430
        )
431
    )
432

433
    scatter_settings = {
1✔
434
        "alpha": pointer_gamut_opacity / 2,
435
        "c": lines_v["colour"] if use_RGB_colours else pointer_gamut_colours,
436
        "marker": "+",
437
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
438
    }
439
    axes.scatter(
1✔
440
        lines_v["position"][..., 0],
441
        lines_v["position"][..., 1],
442
        **scatter_settings,
443
    )
444

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

448
    return render(**settings)
1✔
449

450

451
@override_style()
×
452
def plot_RGB_colourspaces_in_chromaticity_diagram(
×
453
    colourspaces: (
454
        RGB_Colourspace
455
        | LiteralRGBColourspace
456
        | str
457
        | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
458
    ),
459
    cmfs: (
460
        MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
461
    ) = "CIE 1931 2 Degree Standard Observer",
462
    chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
463
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
464
    show_whitepoints: bool = True,
465
    show_pointer_gamut: bool = False,
466
    chromatically_adapt: bool = False,
467
    plot_kwargs: dict | List[dict] | None = None,
468
    **kwargs: Any,
469
) -> Tuple[Figure, Axes]:
470
    """
471
    Plot specified *RGB* colourspaces in the *Chromaticity Diagram* using
472
    the specified method.
473

474
    Parameters
475
    ----------
476
    colourspaces
477
        *RGB* colourspaces to plot. ``colourspaces`` elements can be of any
478
        type or form supported by the
479
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
480
    cmfs
481
        Standard observer colour matching functions used for computing the
482
        spectral locus boundaries. ``cmfs`` can be of any type or form
483
        supported by the :func:`colour.plotting.common.filter_cmfs`
484
        definition.
485
    chromaticity_diagram_callable
486
        Callable responsible for drawing the *Chromaticity Diagram*.
487
    method
488
        *Chromaticity Diagram* method.
489
    show_whitepoints
490
        Whether to display the *RGB* colourspaces whitepoints.
491
    show_pointer_gamut
492
        Whether to display the *Pointer's Gamut*.
493
    chromatically_adapt
494
        Whether to chromatically adapt the *RGB* colourspaces specified in
495
        ``colourspaces`` to the whitepoint of the default plotting
496
        colourspace.
497
    plot_kwargs
498
        Keyword arguments for the :func:`matplotlib.pyplot.plot`
499
        definition, used to control the style of the plotted *RGB*
500
        colourspaces. ``plot_kwargs`` can be either a single dictionary
501
        applied to all the plotted *RGB* colourspaces with the same
502
        settings or a sequence of dictionaries with different settings for
503
        each plotted *RGB* colourspace.
504

505
    Other Parameters
506
    ----------------
507
    kwargs
508
        {:func:`colour.plotting.artist`,
509
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
510
        :func:`colour.plotting.models.plot_pointer_gamut`,
511
        :func:`colour.plotting.render`},
512
        See the documentation of the previously listed definitions.
513

514
    Returns
515
    -------
516
    :class:`tuple`
517
        Current figure and axes.
518

519
    Examples
520
    --------
521
    >>> plot_kwargs = [
522
    ...     {"color": "r"},
523
    ...     {"linestyle": "dashed"},
524
    ...     {"marker": None},
525
    ... ]
526
    >>> plot_RGB_colourspaces_in_chromaticity_diagram(
527
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"], plot_kwargs=plot_kwargs
528
    ... )
529
    ... # doctest: +ELLIPSIS
530
    (<Figure size ... with 1 Axes>, <...Axes...>)
531

532
    .. image:: ../_static/Plotting_\
533
Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png
534
        :align: center
535
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram
536
    """
537

538
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
539

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

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

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

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

552
    title = (
1✔
553
        f"{', '.join([colourspace.name for colourspace in colourspaces])}\n"
554
        f"{cmfs.name} - {method.upper()} Chromaticity Diagram"
555
    )
556

557
    settings = {"axes": axes, "title": title, "method": method}
1✔
558
    settings.update(kwargs)
1✔
559
    settings["show"] = False
1✔
560

561
    chromaticity_diagram_callable(**settings)
1✔
562

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

568
        plot_pointer_gamut(**settings)
1✔
569

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

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

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

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

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

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

589
    plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1✔
590

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

601
    if plot_kwargs is not None:
1✔
602
        update_settings_collection(
1✔
603
            plot_settings_collection, plot_kwargs, len(colourspaces)
604
        )
605

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

609
        if chromatically_adapt and not np.array_equal(
1✔
610
            colourspace.whitepoint, plotting_colourspace.whitepoint
611
        ):
612
            colourspace = colourspace.chromatically_adapt(  # noqa: PLW2901
1✔
613
                plotting_colourspace.whitepoint,
614
                plotting_colourspace.whitepoint_name,
615
            )
616

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

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

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

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

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

643
    bounding_box = (
1✔
644
        min(x_limit_min),
645
        max(x_limit_max),
646
        min(y_limit_min),
647
        max(y_limit_max),
648
    )
649

650
    settings.update(
1✔
651
        {
652
            "show": True,
653
            "legend": True,
654
            "bounding_box": bounding_box,
655
        }
656
    )
657
    settings.update(kwargs)
1✔
658

659
    return render(**settings)
1✔
660

661

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

685
    Parameters
686
    ----------
687
    colourspaces
688
        *RGB* colourspaces to plot. ``colourspaces`` elements can be of any
689
        type or form supported by the
690
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
691
    cmfs
692
        Standard observer colour matching functions used for computing the
693
        spectral locus boundaries. ``cmfs`` can be of any type or form
694
        supported by the :func:`colour.plotting.common.filter_cmfs`
695
        definition.
696
    chromaticity_diagram_callable_CIE1931
697
        Callable responsible for drawing the *CIE 1931 Chromaticity
698
        Diagram*.
699
    show_whitepoints
700
        Whether to display the *RGB* colourspaces whitepoints.
701
    show_pointer_gamut
702
        Whether to display the *Pointer's Gamut*.
703
    chromatically_adapt
704
        Whether to chromatically adapt the *RGB* colourspaces specified in
705
        ``colourspaces`` to the whitepoint of the default plotting
706
        colourspace.
707
    plot_kwargs
708
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
709
        used to control the style of the plotted *RGB* colourspaces.
710
        ``plot_kwargs`` can be either a single dictionary applied to all the
711
        plotted *RGB* colourspaces with the same settings or a sequence of
712
        dictionaries with different settings for each plotted *RGB*
713
        colourspace.
714

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

726
    Returns
727
    -------
728
    :class:`tuple`
729
        Current figure and axes.
730

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

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

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

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

759

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

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

814
    Other Parameters
815
    ----------------
816
    kwargs
817
        {:func:`colour.plotting.artist`,
818
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
819
        :func:`colour.plotting.models.plot_pointer_gamut`,
820
        :func:`colour.plotting.models.\
821
plot_RGB_colourspaces_in_chromaticity_diagram`,
822
        :func:`colour.plotting.render`},
823
        See the documentation of the previously listed definitions.
824

825
    Returns
826
    -------
827
    :class:`tuple`
828
        Current figure and axes.
829

830
    Examples
831
    --------
832
    >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
833
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"]
834
    ... )
835
    ... # doctest: +ELLIPSIS
836
    (<Figure size ... with 1 Axes>, <...Axes...>)
837

838
    .. image:: ../_static/Plotting_\
839
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1960UCS.png
840
        :align: center
841
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
842
    """
843

844
    settings = dict(kwargs)
1✔
845
    settings.update({"method": "CIE 1960 UCS"})
1✔
846

847
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
848
        colourspaces,
849
        cmfs,
850
        chromaticity_diagram_callable_CIE1960UCS,
851
        show_whitepoints=show_whitepoints,
852
        show_pointer_gamut=show_pointer_gamut,
853
        chromatically_adapt=chromatically_adapt,
854
        plot_kwargs=plot_kwargs,
855
        **settings,
856
    )
857

858

859
@override_style()
×
860
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
×
861
    colourspaces: (
862
        RGB_Colourspace
863
        | LiteralRGBColourspace
864
        | str
865
        | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
866
    ),
867
    cmfs: (
868
        MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
869
    ) = "CIE 1931 2 Degree Standard Observer",
870
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
871
        plot_chromaticity_diagram_CIE1976UCS
872
    ),
873
    show_whitepoints: bool = True,
874
    show_pointer_gamut: bool = False,
875
    chromatically_adapt: bool = False,
876
    plot_kwargs: dict | List[dict] | None = None,
877
    **kwargs: Any,
878
) -> Tuple[Figure, Axes]:
879
    """
880
    Plot the specified *RGB* colourspaces in the
881
    *CIE 1976 UCS Chromaticity Diagram*.
882

883
    Parameters
884
    ----------
885
    colourspaces
886
        *RGB* colourspaces to plot. ``colourspaces`` elements can be of any
887
        type or form supported by the
888
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
889
    cmfs
890
        Standard observer colour matching functions used for computing the
891
        spectral locus boundaries. ``cmfs`` can be of any type or form
892
        supported by the :func:`colour.plotting.common.filter_cmfs`
893
        definition.
894
    chromaticity_diagram_callable_CIE1976UCS
895
        Callable responsible for drawing the
896
        *CIE 1976 UCS Chromaticity Diagram*.
897
    show_whitepoints
898
        Whether to display the *RGB* colourspaces whitepoints.
899
    show_pointer_gamut
900
        Whether to display the *Pointer's Gamut*.
901
    chromatically_adapt
902
        Whether to chromatically adapt the *RGB* colourspaces specified in
903
        ``colourspaces`` to the whitepoint of the default plotting
904
        colourspace.
905
    plot_kwargs
906
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
907
        used to control the style of the plotted *RGB* colourspaces.
908
        ``plot_kwargs`` can be either a single dictionary applied to all the
909
        plotted *RGB* colourspaces with the same settings or a sequence of
910
        dictionaries with different settings for each plotted *RGB*
911
        colourspace.
912

913
    Other Parameters
914
    ----------------
915
    kwargs
916
        {:func:`colour.plotting.artist`,
917
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
918
        :func:`colour.plotting.models.plot_pointer_gamut`,
919
        :func:`colour.plotting.models.\
920
plot_RGB_colourspaces_in_chromaticity_diagram`,
921
        :func:`colour.plotting.render`},
922
        See the documentation of the previously listed definitions.
923

924
    Returns
925
    -------
926
    :class:`tuple`
927
        Current figure and axes.
928

929
    Examples
930
    --------
931
    >>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
932
    ...     ["ITU-R BT.709", "ACEScg", "S-Gamut"]
933
    ... )
934
    ... # doctest: +ELLIPSIS
935
    (<Figure size ... with 1 Axes>, <...Axes...>)
936

937
    .. image:: ../_static/Plotting_\
938
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1976UCS.png
939
        :align: center
940
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
941
    """
942

943
    settings = dict(kwargs)
1✔
944
    settings.update({"method": "CIE 1976 UCS"})
1✔
945

946
    return plot_RGB_colourspaces_in_chromaticity_diagram(
1✔
947
        colourspaces,
948
        cmfs,
949
        chromaticity_diagram_callable_CIE1976UCS,
950
        show_whitepoints=show_whitepoints,
951
        show_pointer_gamut=show_pointer_gamut,
952
        chromatically_adapt=chromatically_adapt,
953
        plot_kwargs=plot_kwargs,
954
        **settings,
955
    )
956

957

958
@override_style()
×
959
def plot_RGB_chromaticities_in_chromaticity_diagram(
×
960
    RGB: ArrayLike,
961
    colourspace: (
962
        RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
963
    ) = "sRGB",
964
    chromaticity_diagram_callable: Callable = (
965
        plot_RGB_colourspaces_in_chromaticity_diagram
966
    ),
967
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
968
    scatter_kwargs: dict | None = None,
969
    **kwargs: Any,
970
) -> Tuple[Figure, Axes]:
971
    """
972
    Plot the specified *RGB* colourspace array in the *Chromaticity Diagram*
973
    using the specified method.
974

975
    Parameters
976
    ----------
977
    RGB
978
        *RGB* colourspace array.
979
    colourspace
980
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
981
        type or form supported by the
982
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
983
    chromaticity_diagram_callable
984
        Callable responsible for drawing the *Chromaticity Diagram*.
985
    method
986
        *Chromaticity Diagram* method.
987
    scatter_kwargs
988
        Keyword arguments for the :func:`matplotlib.pyplot.scatter`
989
        definition. The following special keyword arguments can also be used:
990

991
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the
992
            colours as specified by the ``RGB`` argument.
993
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
994
            *False*, the encoding colour component transfer function /
995
            opto-electronic transfer function is not applied when encoding
996
            the samples to the plotting space.
997

998
    Other Parameters
999
    ----------------
1000
    kwargs
1001
        {:func:`colour.plotting.artist`,
1002
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1003
        :func:`colour.plotting.models.\
1004
plot_RGB_colourspaces_in_chromaticity_diagram`,
1005
        :func:`colour.plotting.render`},
1006
        See the documentation of the previously listed definitions.
1007

1008
    Returns
1009
    -------
1010
    :class:`tuple`
1011
        Current figure and axes.
1012

1013
    Examples
1014
    --------
1015
    >>> RGB = np.random.random((128, 128, 3))
1016
    >>> plot_RGB_chromaticities_in_chromaticity_diagram(RGB, "ITU-R BT.709")
1017
    ... # doctest: +ELLIPSIS
1018
    (<Figure size ... with 1 Axes>, <...Axes...>)
1019

1020
    .. image:: ../_static/Plotting_\
1021
Plot_RGB_Chromaticities_In_Chromaticity_Diagram.png
1022
        :align: center
1023
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram
1024
    """
1025

1026
    RGB = np.reshape(as_float_array(RGB)[..., :3], (-1, 3))
1✔
1027
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
1028

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

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

1034
    scatter_settings = {
1✔
1035
        "s": 40,
1036
        "c": "RGB",
1037
        "marker": "o",
1038
        "alpha": 0.85,
1039
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
1040
        "apply_cctf_encoding": True,
1041
    }
1042
    if scatter_kwargs is not None:
1✔
1043
        scatter_settings.update(scatter_kwargs)
1✔
1044

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

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

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

1055
    chromaticity_diagram_callable(**settings)
1✔
1056

1057
    use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1✔
1058
    apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding")
1✔
1059
    if use_RGB_colours:
1✔
1060
        RGB = RGB[RGB[:, 1].argsort()]
1✔
1061
        scatter_settings["c"] = np.clip(
1✔
1062
            np.reshape(
1063
                RGB_to_RGB(
1064
                    RGB,
1065
                    colourspace,
1066
                    CONSTANTS_COLOUR_STYLE.colour.colourspace,
1067
                    apply_cctf_encoding=apply_cctf_encoding,
1068
                ),
1069
                (-1, 3),
1070
            ),
1071
            0,
1072
            1,
1073
        )
1074

1075
    XYZ = RGB_to_XYZ(RGB, colourspace)
1✔
1076

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

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

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

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

1086
    return render(**settings)
1✔
1087

1088

1089
@override_style()
×
1090
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
×
1091
    RGB: ArrayLike,
1092
    colourspace: (
1093
        RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1094
    ) = "sRGB",
1095
    chromaticity_diagram_callable_CIE1931: Callable = (
1096
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
1097
    ),
1098
    scatter_kwargs: dict | None = None,
1099
    **kwargs: Any,
1100
) -> Tuple[Figure, Axes]:
1101
    """
1102
    Plot specified *RGB* colourspace array in the *CIE 1931 Chromaticity
1103
    Diagram*.
1104

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

1121
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the
1122
            colours as specified by the ``RGB`` argument.
1123
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1124
            *False*, the encoding colour component transfer function /
1125
            opto-electronic transfer function is not applied when encoding
1126
            the samples to the plotting space.
1127

1128
    Other Parameters
1129
    ----------------
1130
    kwargs
1131
        {:func:`colour.plotting.artist`,
1132
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1133
        :func:`colour.plotting.models.\
1134
plot_RGB_colourspaces_in_chromaticity_diagram`,
1135
        :func:`colour.plotting.render`},
1136
        See the documentation of the previously listed definitions.
1137

1138
    Returns
1139
    -------
1140
    :class:`tuple`
1141
        Current figure and axes.
1142

1143
    Examples
1144
    --------
1145
    >>> RGB = np.random.random((128, 128, 3))
1146
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
1147
    ...     RGB, "ITU-R BT.709"
1148
    ... )
1149
    ... # doctest: +ELLIPSIS
1150
    (<Figure size ... with 1 Axes>, <...Axes...>)
1151

1152
    .. image:: ../_static/Plotting_\
1153
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1931.png
1154
        :align: center
1155
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931
1156
    """
1157

1158
    settings = dict(kwargs)
1✔
1159
    settings.update({"method": "CIE 1931"})
1✔
1160

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

1169

1170
@override_style()
×
1171
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
×
1172
    RGB: ArrayLike,
1173
    colourspace: (
1174
        RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1175
    ) = "sRGB",
1176
    chromaticity_diagram_callable_CIE1960UCS: Callable = (
1177
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
1178
    ),
1179
    scatter_kwargs: dict | None = None,
1180
    **kwargs: Any,
1181
) -> Tuple[Figure, Axes]:
1182
    """
1183
    Plot the specified *RGB* colourspace array in the
1184
    *CIE 1960 UCS Chromaticity Diagram*.
1185

1186
    Parameters
1187
    ----------
1188
    RGB
1189
        *RGB* colourspace array.
1190
    colourspace
1191
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
1192
        type or form supported by the
1193
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
1194
    chromaticity_diagram_callable_CIE1960UCS
1195
        Callable responsible for drawing the
1196
        *CIE 1960 UCS Chromaticity Diagram*.
1197
    scatter_kwargs
1198
        Keyword arguments for the :func:`matplotlib.pyplot.scatter`
1199
        definition. The following special keyword arguments can also be
1200
        used:
1201

1202
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the
1203
            colours as specified by the ``RGB`` argument.
1204
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1205
            *False*, the encoding colour component transfer function /
1206
            opto-electronic transfer function is not applied when encoding
1207
            the samples to the plotting space.
1208

1209
    Other Parameters
1210
    ----------------
1211
    kwargs
1212
        {:func:`colour.plotting.artist`,
1213
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1214
        :func:`colour.plotting.models.\
1215
plot_RGB_colourspaces_in_chromaticity_diagram`,
1216
        :func:`colour.plotting.render`},
1217
        See the documentation of the previously listed definitions.
1218

1219
    Returns
1220
    -------
1221
    :class:`tuple`
1222
        Current figure and axes.
1223

1224
    Examples
1225
    --------
1226
    >>> RGB = np.random.random((128, 128, 3))
1227
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
1228
    ...     RGB, "ITU-R BT.709"
1229
    ... )
1230
    ... # doctest: +ELLIPSIS
1231
    (<Figure size ... with 1 Axes>, <...Axes...>)
1232

1233
    .. image:: ../_static/Plotting_\
1234
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1960UCS.png
1235
        :align: center
1236
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS
1237
    """
1238

1239
    settings = dict(kwargs)
1✔
1240
    settings.update({"method": "CIE 1960 UCS"})
1✔
1241

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

1250

1251
@override_style()
×
1252
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
×
1253
    RGB: ArrayLike,
1254
    colourspace: (
1255
        RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1256
    ) = "sRGB",
1257
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
1258
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
1259
    ),
1260
    scatter_kwargs: dict | None = None,
1261
    **kwargs: Any,
1262
) -> Tuple[Figure, Axes]:
1263
    """
1264
    Plot the specified *RGB* colourspace array in the
1265
    *CIE 1976 UCS Chromaticity Diagram*.
1266

1267
    Parameters
1268
    ----------
1269
    RGB
1270
        *RGB* colourspace array.
1271
    colourspace
1272
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
1273
        type or form supported by the
1274
        :func:`colour.plotting.common.filter_RGB_colourspaces` definition.
1275
    chromaticity_diagram_callable_CIE1976UCS
1276
        Callable responsible for drawing the
1277
        *CIE 1976 UCS Chromaticity Diagram*.
1278
    scatter_kwargs
1279
        Keyword arguments for the :func:`matplotlib.pyplot.scatter`
1280
        definition. The following special keyword arguments can also be
1281
        used:
1282

1283
        -   ``c`` : If ``c`` is set to *RGB*, the scatter will use the
1284
            colours as specified by the ``RGB`` argument.
1285
        -   ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
1286
            *False*, the encoding colour component transfer function /
1287
            opto-electronic transfer function is not applied when encoding
1288
            the samples to the plotting space.
1289

1290
    Other Parameters
1291
    ----------------
1292
    kwargs
1293
        {:func:`colour.plotting.artist`,
1294
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1295
        :func:`colour.plotting.models.\
1296
plot_RGB_colourspaces_in_chromaticity_diagram`,
1297
        :func:`colour.plotting.render`},
1298
        See the documentation of the previously listed definitions.
1299

1300
    Returns
1301
    -------
1302
    :class:`tuple`
1303
        Current figure and axes.
1304

1305
    Examples
1306
    --------
1307
    >>> RGB = np.random.random((128, 128, 3))
1308
    >>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
1309
    ...     RGB, "ITU-R BT.709"
1310
    ... )
1311
    ... # doctest: +ELLIPSIS
1312
    (<Figure size ... with 1 Axes>, <...Axes...>)
1313

1314
    .. image:: ../_static/Plotting_\
1315
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1976UCS.png
1316
        :align: center
1317
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS
1318
    """
1319

1320
    settings = dict(kwargs)
1✔
1321
    settings.update({"method": "CIE 1976 UCS"})
1✔
1322

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

1331

1332
def ellipses_MacAdam1942(
×
1333
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
1334
) -> List[NDArrayFloat]:
1335
    """
1336
    Return *MacAdam (1942) Ellipses (Observer PGN)* coefficients using the
1337
    specified method.
1338

1339
    Parameters
1340
    ----------
1341
    method
1342
        Computation method.
1343

1344
    Returns
1345
    -------
1346
    :class:`list`
1347
        *MacAdam (1942) Ellipses (Observer PGN)* coefficients.
1348

1349
    Examples
1350
    --------
1351
    >>> ellipses_MacAdam1942()[0]  # doctest: +SKIP
1352
    array([  1.60000000e-01,   5.70000000e-02,   5.00000023e-03,
1353
             1.56666660e-02,  -2.77000015e+01])
1354
    """
1355

1356
    method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1✔
1357

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

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

1362
    ellipses_coefficients = []
1✔
1363
    for i in range(len(theta)):
1✔
1364
        xy = point_at_angle_on_ellipse(
1✔
1365
            np.linspace(0, 360, 36),
1366
            [x[i], y[i], a[i] / 60, b[i] / 60, theta[i]],
1367
        )
1368
        ij = xy_to_ij(xy)
1✔
1369
        ellipses_coefficients.append(
1✔
1370
            ellipse_coefficients_canonical_form(ellipse_fitting(ij))
1371
        )
1372

1373
    return ellipses_coefficients
1✔
1374

1375

1376
@override_style()
×
1377
def plot_ellipses_MacAdam1942_in_chromaticity_diagram(
×
1378
    chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
1379
    method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
1380
    chromaticity_diagram_clipping: bool = False,
1381
    ellipse_kwargs: dict | List[dict] | None = None,
1382
    **kwargs: Any,
1383
) -> Tuple[Figure, Axes]:
1384
    """
1385
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1386
    *Chromaticity Diagram* using the specified method.
1387

1388
    Parameters
1389
    ----------
1390
    chromaticity_diagram_callable
1391
        Callable responsible for drawing the *Chromaticity Diagram*.
1392
    method
1393
        *Chromaticity Diagram* method.
1394
    chromaticity_diagram_clipping
1395
        Whether to clip the *Chromaticity Diagram* colours with the
1396
        ellipses.
1397
    ellipse_kwargs
1398
        Parameters for the :class:`Ellipse` class. ``ellipse_kwargs``
1399
        can be either a single dictionary applied to all the ellipses
1400
        with the same settings or a sequence of dictionaries with
1401
        different settings for each ellipse.
1402

1403
    Other Parameters
1404
    ----------------
1405
    kwargs
1406
        {:func:`colour.plotting.artist`,
1407
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1408
        :func:`colour.plotting.render`},
1409
        See the documentation of the previously listed definitions.
1410

1411
    Returns
1412
    -------
1413
    :class:`tuple`
1414
        Current figure and axes.
1415

1416
    Examples
1417
    --------
1418
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram()
1419
    ... # doctest: +ELLIPSIS
1420
    (<Figure size ... with 1 Axes>, <...Axes...>)
1421

1422
    .. image:: ../_static/\
1423
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png
1424
        :align: center
1425
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram
1426
    """
1427

1428
    settings: Dict[str, Any] = {"uniform": True}
1✔
1429
    settings.update(kwargs)
1✔
1430

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

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

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

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

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

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

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

1463
    chromaticity_diagram_callable(**settings)
1✔
1464

1465
    ellipse_settings_collection = [
1✔
1466
        {
1467
            "color": CONSTANTS_COLOUR_STYLE.colour.cycle[4],
1468
            "alpha": 0.4,
1469
            "linewidth": colour_style()["lines.linewidth"],
1470
            "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
1471
        }
1472
        for _ellipses_coefficient in ellipses_coefficients
1473
    ]
1474

1475
    if ellipse_kwargs is not None:
1✔
1476
        update_settings_collection(
1✔
1477
            ellipse_settings_collection,
1478
            ellipse_kwargs,
1479
            len(ellipses_coefficients),
1480
        )
1481

1482
    for i, coefficients in enumerate(ellipses_coefficients):
1✔
1483
        x_c, y_c, a_a, a_b, theta_e = coefficients
1✔
1484
        ellipse = Ellipse(
1✔
1485
            (x_c, y_c),
1486
            a_a,
1487
            a_b,
1488
            angle=theta_e,
1489
            **ellipse_settings_collection[i],
1490
        )
1491
        axes.add_artist(ellipse)
1✔
1492

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

1496
    return render(**settings)
1✔
1497

1498

1499
@override_style()
×
1500
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931(
×
1501
    chromaticity_diagram_callable_CIE1931: Callable = (
1502
        plot_chromaticity_diagram_CIE1931
1503
    ),
1504
    chromaticity_diagram_clipping: bool = False,
1505
    ellipse_kwargs: dict | List[dict] | None = None,
1506
    **kwargs: Any,
1507
) -> Tuple[Figure, Axes]:
1508
    """
1509
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1510
    *CIE 1931 Chromaticity Diagram*.
1511

1512
    Parameters
1513
    ----------
1514
    chromaticity_diagram_callable_CIE1931
1515
        Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
1516
    chromaticity_diagram_clipping
1517
        Whether to clip the *CIE 1931 Chromaticity Diagram* colours with the
1518
        ellipses.
1519
    ellipse_kwargs
1520
        Parameters for the :class:`Ellipse` class. ``ellipse_kwargs``
1521
        can be either a single dictionary applied to all the ellipses
1522
        with the same settings or a sequence of dictionaries with
1523
        different settings for each ellipse.
1524

1525
    Other Parameters
1526
    ----------------
1527
    kwargs
1528
        {:func:`colour.plotting.artist`,
1529
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1530
        :func:`colour.plotting.models.\
1531
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1532
        :func:`colour.plotting.render`},
1533
        See the documentation of the previously listed definitions.
1534

1535
    Returns
1536
    -------
1537
    :class:`tuple`
1538
        Current figure and axes.
1539

1540
    Examples
1541
    --------
1542
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931()
1543
    ... # doctest: +ELLIPSIS
1544
    (<Figure size ... with 1 Axes>, <...Axes...>)
1545

1546
    .. image:: ../_static/\
1547
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1931.png
1548
        :align: center
1549
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931
1550
    """
1551

1552
    settings = dict(kwargs)
1✔
1553
    settings.update({"method": "CIE 1931"})
1✔
1554

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

1562

1563
@override_style()
×
1564
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS(
×
1565
    chromaticity_diagram_callable_CIE1960UCS: Callable = (
1566
        plot_chromaticity_diagram_CIE1960UCS
1567
    ),
1568
    chromaticity_diagram_clipping: bool = False,
1569
    ellipse_kwargs: dict | List[dict] | None = None,
1570
    **kwargs: Any,
1571
) -> Tuple[Figure, Axes]:
1572
    """
1573
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1574
    *CIE 1960 UCS Chromaticity Diagram*.
1575

1576
    Parameters
1577
    ----------
1578
    chromaticity_diagram_callable_CIE1960UCS
1579
        Callable responsible for drawing the
1580
        *CIE 1960 UCS Chromaticity Diagram*.
1581
    chromaticity_diagram_clipping
1582
        Whether to clip the *CIE 1960 UCS Chromaticity Diagram* colours
1583
        with the ellipses.
1584
    ellipse_kwargs
1585
        Parameters for the :class:`Ellipse` class. ``ellipse_kwargs``
1586
        can be either a single dictionary applied to all the ellipses
1587
        with the same settings or a sequence of dictionaries with
1588
        different settings for each ellipse.
1589

1590
    Other Parameters
1591
    ----------------
1592
    kwargs
1593
        {:func:`colour.plotting.artist`,
1594
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1595
        :func:`colour.plotting.models.\
1596
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1597
        :func:`colour.plotting.render`},
1598
        See the documentation of the previously listed definitions.
1599

1600
    Returns
1601
    -------
1602
    :class:`tuple`
1603
        Current figure and axes.
1604

1605
    Examples
1606
    --------
1607
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS()
1608
    ... # doctest: +ELLIPSIS
1609
    (<Figure size ... with 1 Axes>, <...Axes...>)
1610

1611
    .. image:: ../_static/\
1612
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1960UCS.png
1613
        :align: center
1614
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS
1615
    """
1616

1617
    settings = dict(kwargs)
1✔
1618
    settings.update({"method": "CIE 1960 UCS"})
1✔
1619

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

1627

1628
@override_style()
×
1629
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS(
×
1630
    chromaticity_diagram_callable_CIE1976UCS: Callable = (
1631
        plot_chromaticity_diagram_CIE1976UCS
1632
    ),
1633
    chromaticity_diagram_clipping: bool = False,
1634
    ellipse_kwargs: dict | List[dict] | None = None,
1635
    **kwargs: Any,
1636
) -> Tuple[Figure, Axes]:
1637
    """
1638
    Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1639
    *CIE 1976 UCS Chromaticity Diagram*.
1640

1641
    Parameters
1642
    ----------
1643
    chromaticity_diagram_callable_CIE1976UCS
1644
        Callable responsible for drawing the
1645
        *CIE 1976 UCS Chromaticity Diagram*.
1646
    chromaticity_diagram_clipping
1647
        Whether to clip the *CIE 1976 UCS Chromaticity Diagram* colours
1648
        with the ellipses.
1649
    ellipse_kwargs
1650
        Parameters for the :class:`Ellipse` class. ``ellipse_kwargs``
1651
        can be either a single dictionary applied to all the ellipses
1652
        with the same settings or a sequence of dictionaries with
1653
        different settings for each ellipse.
1654

1655
    Other Parameters
1656
    ----------------
1657
    kwargs
1658
        {:func:`colour.plotting.artist`,
1659
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1660
        :func:`colour.plotting.models.\
1661
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1662
        :func:`colour.plotting.render`},
1663
        See the documentation of the previously listed definitions.
1664

1665
    Returns
1666
    -------
1667
    :class:`tuple`
1668
        Current figure and axes.
1669

1670
    Examples
1671
    --------
1672
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS()
1673
    ... # doctest: +ELLIPSIS
1674
    (<Figure size ... with 1 Axes>, <...Axes...>)
1675

1676
    .. image:: ../_static/\
1677
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1976UCS.png
1678
        :align: center
1679
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS
1680
    """
1681

1682
    settings = dict(kwargs)
1✔
1683
    settings.update({"method": "CIE 1976 UCS"})
1✔
1684

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

1692

1693
@override_style()
×
1694
def plot_single_cctf(
×
1695
    cctf: Callable | str, cctf_decoding: bool = False, **kwargs: Any
1696
) -> Tuple[Figure, Axes]:
1697
    """
1698
    Plot specified colourspace colour component transfer function.
1699

1700
    Parameters
1701
    ----------
1702
    cctf
1703
        Colour component transfer function to plot. ``function`` can be of
1704
        any type or form supported by the
1705
        :func:`colour.plotting.common.filter_passthrough` definition.
1706
    cctf_decoding
1707
        Plot the decoding colour component transfer function instead.
1708

1709
    Other Parameters
1710
    ----------------
1711
    kwargs
1712
        {:func:`colour.plotting.artist`,
1713
        :func:`colour.plotting.plot_multi_functions`,
1714
        :func:`colour.plotting.render`},
1715
        See the documentation of the previously listed definitions.
1716

1717
    Returns
1718
    -------
1719
    :class:`tuple`
1720
        Current figure and axes.
1721

1722
    Examples
1723
    --------
1724
    >>> plot_single_cctf("ITU-R BT.709")  # doctest: +ELLIPSIS
1725
    (<Figure size ... with 1 Axes>, <...Axes...>)
1726

1727
    .. image:: ../_static/Plotting_Plot_Single_CCTF.png
1728
        :align: center
1729
        :alt: plot_single_cctf
1730
    """
1731

1732
    settings: Dict[str, Any] = {
1✔
1733
        "title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF"
1734
    }
1735
    settings.update(kwargs)
1✔
1736

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

1739

1740
@override_style()
×
1741
def plot_multi_cctfs(
×
1742
    cctfs: Callable | str | Sequence[Callable | str],
1743
    cctf_decoding: bool = False,
1744
    **kwargs: Any,
1745
) -> Tuple[Figure, Axes]:
1746
    """
1747
    Plot the specified colour component transfer functions.
1748

1749
    Parameters
1750
    ----------
1751
    cctfs
1752
        Colour component transfer function to plot. ``cctfs`` elements can be
1753
        of any type or form supported by the
1754
        :func:`colour.plotting.common.filter_passthrough` definition.
1755
    cctf_decoding
1756
        Plot the decoding colour component transfer function instead.
1757

1758
    Other Parameters
1759
    ----------------
1760
    kwargs
1761
        {:func:`colour.plotting.artist`,
1762
        :func:`colour.plotting.plot_multi_functions`,
1763
        :func:`colour.plotting.render`},
1764
        See the documentation of the previously listed definitions.
1765

1766
    Returns
1767
    -------
1768
    :class:`tuple`
1769
        Current figure and axes.
1770

1771
    Examples
1772
    --------
1773
    >>> plot_multi_cctfs(["ITU-R BT.709", "sRGB"])  # doctest: +ELLIPSIS
1774
    (<Figure size ... with 1 Axes>, <...Axes...>)
1775

1776
    .. image:: ../_static/Plotting_Plot_Multi_CCTFs.png
1777
        :align: center
1778
        :alt: plot_multi_cctfs
1779
    """
1780

1781
    cctfs_filtered = filter_passthrough(
1782
        CCTF_DECODINGS if cctf_decoding else CCTF_ENCODINGS, cctfs
1783
    )
1784

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

1788
    settings: Dict[str, Any] = {
1✔
1789
        "bounding_box": (0, 1, 0, 1),
1790
        "legend": True,
1791
        "title": title,
1792
        "x_label": "Signal Value" if cctf_decoding else "Tristimulus Value",
1793
        "y_label": "Tristimulus Value" if cctf_decoding else "Signal Value",
1794
    }
1795
    settings.update(kwargs)
1✔
1796

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

1800

1801
@override_style()
×
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
    # TODO: Filter appropriate colour models.
1949
    # NOTE: "dtype=object" is required for ragged array support
1950
    # in "Numpy" 1.24.0.
1951
    data = as_array(data, dtype=object)  # pyright: ignore
1✔
1952

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

1956
    _figure, axes = artist(**settings)
1✔
1957

1958
    scatter_settings = {
1✔
1959
        "s": 40,
1960
        "c": "RGB",
1961
        "marker": "o",
1962
        "alpha": 0.85,
1963
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
1964
    }
1965
    if scatter_kwargs is not None:
1✔
1966
        scatter_settings.update(scatter_kwargs)
1✔
1967

1968
    convert_kwargs = optional(convert_kwargs, {})
1✔
1969

1970
    use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1✔
1971

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

1976
        xy_r = XYZ_to_xy(XYZ_r)
1✔
1977

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

1981
        ijk_ct = colourspace_model_axis_reorder(
1✔
1982
            convert(XYZ_ct, "CIE XYZ", model, **convert_settings), model
1983
        )
1984
        ijk_cr = colourspace_model_axis_reorder(
1✔
1985
            convert(XYZ_cr, "CIE XYZ", model, **convert_settings), model
1986
        )
1987

1988
        ijk_ct *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
1✔
1989
        ijk_cr *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
1✔
1990

1991
        def _linear_equation(
1✔
1992
            x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat
1993
        ) -> NDArrayFloat:
1994
            """Define the canonical linear equation for a line."""
1995

1996
            return a * x + b
1✔
1997

1998
        popt, _pcov = scipy.optimize.curve_fit(
1✔
1999
            _linear_equation, ijk_ct[..., 0], ijk_ct[..., 1]
2000
        )
2001

2002
        axes.plot(
1✔
2003
            ijk_ct[..., 0],
2004
            _linear_equation(ijk_ct[..., 0], *popt),
2005
            c=CONSTANTS_COLOUR_STYLE.colour.average,
2006
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2007
        )
2008

2009
        if use_RGB_colours:
1✔
2010
            RGB_ct = XYZ_to_RGB(XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True)
1✔
2011
            scatter_settings["c"] = np.clip(RGB_ct, 0, 1)
1✔
2012
            RGB_cr = XYZ_to_RGB(XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True)
1✔
2013
            RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1)
1✔
2014
        else:
2015
            RGB_cr = scatter_settings["c"]
1✔
2016

2017
        axes.scatter(ijk_ct[..., 0], ijk_ct[..., 1], **scatter_settings)
1✔
2018

2019
        axes.plot(
1✔
2020
            ijk_cr[..., 0],
2021
            ijk_cr[..., 1],
2022
            "s",
2023
            c=RGB_cr,
2024
            markersize=CONSTANTS_COLOUR_STYLE.geometry.short * 8,
2025
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2026
        )
2027

2028
    labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[
1✔
2029
        as_int_array(colourspace_model_axis_reorder([0, 1, 2], model))
2030
    ]
2031

2032
    settings = {
1✔
2033
        "axes": axes,
2034
        "title": f"Constant Hue Loci - {model}",
2035
        "x_label": labels[0],
2036
        "y_label": labels[1],
2037
    }
2038
    settings.update(kwargs)
1✔
2039

2040
    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