Coverage for plotting/models.py: 79%
295 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2Colour Models Plotting
3======================
5Define the colour models plotting objects:
7- :func:`colour.plotting.lines_pointer_gamut`
8- :func:`colour.plotting.\
9plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931`
10- :func:`colour.plotting.\
11plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS`
12- :func:`colour.plotting.\
13plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS`
14- :func:`colour.plotting.\
15plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931`
16- :func:`colour.plotting.\
17plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS`
18- :func:`colour.plotting.\
19plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS`
20- :func:`colour.plotting.\
21plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931`
22- :func:`colour.plotting.\
23plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS`
24- :func:`colour.plotting.\
25plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS`
26- :func:`colour.plotting.plot_single_cctf`
27- :func:`colour.plotting.plot_multi_cctfs`
28- :func:`colour.plotting.plot_constant_hue_loci`
30References
31----------
32- :cite:`Ebner1998` : Ebner, F., & Fairchild, M. D. (1998). Finding constant
33 hue surfaces in color space. In G. B. Beretta & R. Eschbach (Eds.), Proc.
34 SPIE 3300, Color Imaging: Device-Independent Color, Color Hardcopy, and
35 Graphic Arts III, (2 January 1998) (pp. 107-117). doi:10.1117/12.298269
36- :cite:`Hung1995` : Hung, P.-C., & Berns, R. S. (1995). Determination of
37 constant Hue Loci for a CRT gamut and their predictions using color
38 appearance spaces. Color Research & Application, 20(5), 285-295.
39 doi:10.1002/col.5080200506
40- :cite:`Mansencal2019` : Mansencal, T. (2019). Colour - Datasets.
41 doi:10.5281/zenodo.3362520
42"""
44from __future__ import annotations
46import typing
48import numpy as np
49from matplotlib.collections import LineCollection
50from matplotlib.patches import Ellipse
51from matplotlib.path import Path
53from colour.adaptation import chromatic_adaptation_VonKries
54from colour.algebra import normalise_maximum
55from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON
56from colour.geometry import (
57 ellipse_coefficients_canonical_form,
58 ellipse_fitting,
59 point_at_angle_on_ellipse,
60)
61from colour.graph import colourspace_model_to_reference, convert
63if typing.TYPE_CHECKING:
64 from matplotlib.axes import Axes
65 from matplotlib.figure import Figure
66 from colour.colorimetry import MultiSpectralDistributions
67 from colour.hints import (
68 Any,
69 ArrayLike,
70 Callable,
71 Dict,
72 Literal,
73 LiteralColourspaceModel,
74 LiteralRGBColourspace,
75 NDArray,
76 NDArrayFloat,
77 Sequence,
78 Tuple,
79 )
81from colour.hints import List, cast
82from colour.models import LCHab_to_Lab # pyright: ignore
83from colour.models import (
84 CCS_ILLUMINANT_POINTER_GAMUT,
85 CCS_POINTER_GAMUT_BOUNDARY,
86 CCTF_DECODINGS,
87 CCTF_ENCODINGS,
88 COLOURSPACE_MODELS_AXIS_LABELS,
89 DATA_MACADAM_1942_ELLIPSES,
90 DATA_POINTER_GAMUT_VOLUME,
91 Lab_to_XYZ,
92 RGB_Colourspace,
93 RGB_to_RGB,
94 RGB_to_XYZ,
95 XYZ_to_RGB,
96 XYZ_to_xy,
97 xy_to_XYZ,
98)
99from colour.plotting import (
100 CONSTANTS_COLOUR_STYLE,
101 METHODS_CHROMATICITY_DIAGRAM,
102 XYZ_to_plotting_colourspace,
103 artist,
104 colour_cycle,
105 colour_style,
106 filter_cmfs,
107 filter_passthrough,
108 filter_RGB_colourspaces,
109 override_style,
110 plot_chromaticity_diagram_CIE1931,
111 plot_chromaticity_diagram_CIE1960UCS,
112 plot_chromaticity_diagram_CIE1976UCS,
113 plot_multi_functions,
114 render,
115 update_settings_collection,
116)
117from colour.plotting.diagrams import plot_chromaticity_diagram
118from colour.utilities import (
119 CanonicalMapping,
120 as_array,
121 as_float_array,
122 as_int_array,
123 domain_range_scale,
124 first_item,
125 optional,
126 required,
127 tsplit,
128 validate_method,
129 zeros,
130)
132__author__ = "Colour Developers"
133__copyright__ = "Copyright 2013 Colour Developers"
134__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
135__maintainer__ = "Colour Developers"
136__email__ = "colour-developers@colour-science.org"
137__status__ = "Production"
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]
162COLOURSPACE_MODELS_AXIS_ORDER: CanonicalMapping = CanonicalMapping(
163 {
164 "CAM02LCD": (1, 2, 0),
165 "CAM02SCD": (1, 2, 0),
166 "CAM02UCS": (1, 2, 0),
167 "CAM16LCD": (1, 2, 0),
168 "CAM16SCD": (1, 2, 0),
169 "CAM16UCS": (1, 2, 0),
170 "CIE 1931": (0, 1, 2),
171 "CIE 1960 UCS": (0, 1, 2),
172 "CIE 1976 UCS": (0, 1, 2),
173 "CIE LCHab": (1, 2, 0),
174 "CIE LCHuv": (1, 2, 0),
175 "CIE Lab": (1, 2, 0),
176 "CIE Luv": (1, 2, 0),
177 "CIE UCS": (0, 1, 2),
178 "CIE UVW": (1, 2, 0),
179 "CIE XYZ": (0, 1, 2),
180 "CIE xyY": (0, 1, 2),
181 "DIN99": (1, 2, 0),
182 "HCL": (0, 1, 2),
183 "HSL": (0, 1, 2),
184 "HSV": (0, 1, 2),
185 "Hunter Lab": (1, 2, 0),
186 "Hunter Rdab": (1, 2, 0),
187 "ICaCb": (1, 2, 0),
188 "ICtCp": (1, 2, 0),
189 "IHLS": (0, 2, 1),
190 "IPT Ragoo 2021": (1, 2, 0),
191 "IPT": (1, 2, 0),
192 "IgPgTg": (1, 2, 0),
193 "Jzazbz": (1, 2, 0),
194 "OSA UCS": (1, 2, 0),
195 "Oklab": (1, 2, 0),
196 "RGB": (0, 1, 2),
197 "YCbCr": (1, 2, 0),
198 "YCoCg": (1, 2, 0),
199 "Yrg": (1, 2, 0),
200 "hdr-CIELAB": (1, 2, 0),
201 "hdr-IPT": (1, 2, 0),
202 }
203)
204"""Colourspace models axis order."""
207def colourspace_model_axis_reorder(
208 a: ArrayLike,
209 model: LiteralColourspaceModel | str,
210 direction: Literal["Forward", "Inverse"] | str = "Forward",
211) -> NDArrayFloat:
212 """
213 Reorder the axes of the specified colourspace model array :math:`a` to
214 match the standard axes order used for volume plotting.
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.
226 Returns
227 -------
228 :class:`numpy.ndarray`
229 Reordered colourspace model array :math:`a`.
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 """
245 a = as_float_array(a)
247 model = validate_method(
248 model,
249 tuple(COLOURSPACE_MODELS_AXIS_ORDER),
250 '"{0}" model is invalid, it must be one of {1}!',
251 )
253 direction = validate_method(
254 direction,
255 ("Forward", "Inverse"),
256 '"{0}" direction is invalid, it must be one of {1}!',
257 )
259 order = COLOURSPACE_MODELS_AXIS_ORDER.get(model, (0, 1, 2))
261 if direction == "forward":
262 indexes = (order[0], order[1], order[2])
263 else:
264 indexes = (order.index(0), order.index(1), order.index(2))
266 return a[..., indexes]
269def lines_pointer_gamut(
270 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
271) -> tuple[NDArray, NDArray]:
272 """
273 Return the *Pointer's Gamut* line vertices, i.e., positions, normals,
274 and colours, using the specified chromaticity diagram method.
276 Parameters
277 ----------
278 method
279 *Chromaticity Diagram* method.
281 Returns
282 -------
283 :class:`tuple`
284 Tuple of *Pointer's Gamut* boundary and volume vertices.
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 """
299 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
301 illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint
303 XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
304 ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"]
306 XYZ = xy_to_XYZ(CCS_POINTER_GAMUT_BOUNDARY)
307 XYZ = chromatic_adaptation_VonKries(
308 XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
309 )
310 ij_b = XYZ_to_ij(XYZ)
311 ij_b = np.vstack([ij_b, ij_b[0]])
312 colours_b = normalise_maximum(
313 XYZ_to_plotting_colourspace(ij_to_XYZ(ij_b, illuminant), illuminant),
314 axis=-1,
315 )
317 lines_b = zeros(
318 ij_b.shape[0],
319 [
320 ("position", DTYPE_FLOAT_DEFAULT, 2),
321 ("normal", DTYPE_FLOAT_DEFAULT, 2),
322 ("colour", DTYPE_FLOAT_DEFAULT, 3),
323 ], # pyright: ignore
324 )
326 lines_b["position"] = ij_b
327 lines_b["colour"] = colours_b
329 XYZ = Lab_to_XYZ(
330 LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT
331 )
332 XYZ = chromatic_adaptation_VonKries(
333 XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
334 )
335 ij_v = XYZ_to_ij(XYZ)
337 colours_v = normalise_maximum(
338 XYZ_to_plotting_colourspace(ij_to_XYZ(ij_v, illuminant), illuminant),
339 axis=-1,
340 )
342 lines_v = zeros(
343 ij_v.shape[0],
344 [
345 ("position", DTYPE_FLOAT_DEFAULT, 2),
346 ("normal", DTYPE_FLOAT_DEFAULT, 2),
347 ("colour", DTYPE_FLOAT_DEFAULT, 3),
348 ], # pyright: ignore
349 )
351 lines_v["position"] = ij_v
352 lines_v["colour"] = colours_v
354 return lines_b, lines_v
357@override_style()
358def plot_pointer_gamut(
359 pointer_gamut_colours: ArrayLike | str | None = None,
360 pointer_gamut_opacity: float = 1,
361 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
362 **kwargs: Any,
363) -> Tuple[Figure, Axes]:
364 """
365 Plot *Pointer's Gamut* using the specified plotting method.
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.
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.
383 Returns
384 -------
385 :class:`tuple`
386 Current figure and axes.
388 Examples
389 --------
390 >>> plot_pointer_gamut(pointer_gamut_colours="RGB") # doctest: +ELLIPSIS
391 (<Figure size ... with 1 Axes>, <...Axes...>)
393 .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png
394 :align: center
395 :alt: plot_pointer_gamut
396 """
398 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
400 pointer_gamut_colours = optional(
401 pointer_gamut_colours, CONSTANTS_COLOUR_STYLE.colour.dark
402 )
404 use_RGB_colours = str(pointer_gamut_colours).upper() == "RGB"
406 pointer_gamut_opacity = optional(
407 pointer_gamut_opacity, CONSTANTS_COLOUR_STYLE.opacity.high
408 )
410 settings: Dict[str, Any] = {"uniform": True}
411 settings.update(kwargs)
413 _figure, axes = artist(**settings)
415 lines_b, lines_v = lines_pointer_gamut(method)
417 axes.add_collection(
418 LineCollection(
419 np.reshape(
420 np.concatenate(
421 [lines_b["position"][:-1], lines_b["position"][1:]],
422 axis=1, # pyright: ignore
423 ),
424 (-1, 2, 2),
425 ),
426 colors=(lines_b["colour"] if use_RGB_colours else pointer_gamut_colours),
427 alpha=pointer_gamut_opacity,
428 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
429 )
430 )
432 scatter_settings = {
433 "alpha": pointer_gamut_opacity / 2,
434 "c": lines_v["colour"] if use_RGB_colours else pointer_gamut_colours,
435 "marker": "+",
436 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
437 }
438 axes.scatter(
439 lines_v["position"][..., 0],
440 lines_v["position"][..., 1],
441 **scatter_settings,
442 )
444 settings.update({"axes": axes})
445 settings.update(kwargs)
447 return render(**settings)
450@override_style()
451def plot_RGB_colourspaces_in_chromaticity_diagram(
452 colourspaces: (
453 RGB_Colourspace
454 | LiteralRGBColourspace
455 | str
456 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
457 ),
458 cmfs: (
459 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
460 ) = "CIE 1931 2 Degree Standard Observer",
461 chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
462 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
463 show_whitepoints: bool = True,
464 show_pointer_gamut: bool = False,
465 chromatically_adapt: bool = False,
466 plot_kwargs: dict | List[dict] | None = None,
467 **kwargs: Any,
468) -> Tuple[Figure, Axes]:
469 """
470 Plot specified *RGB* colourspaces in the *Chromaticity Diagram* using
471 the specified method.
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.
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.
513 Returns
514 -------
515 :class:`tuple`
516 Current figure and axes.
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...>)
531 .. image:: ../_static/Plotting_\
532Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png
533 :align: center
534 :alt: plot_RGB_colourspaces_in_chromaticity_diagram
535 """
537 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
539 colourspaces = cast(
540 "List[RGB_Colourspace]",
541 list(filter_RGB_colourspaces(colourspaces).values()),
542 ) # pyright: ignore
544 settings: Dict[str, Any] = {"uniform": True}
545 settings.update(kwargs)
547 _figure, axes = artist(**settings)
549 cmfs = cast("MultiSpectralDistributions", first_item(filter_cmfs(cmfs).values()))
551 title = (
552 f"{', '.join([colourspace.name for colourspace in colourspaces])}\n"
553 f"{cmfs.name} - {method.upper()} Chromaticity Diagram"
554 )
556 settings = {"axes": axes, "title": title, "method": method}
557 settings.update(kwargs)
558 settings["show"] = False
560 chromaticity_diagram_callable(**settings)
562 if show_pointer_gamut:
563 settings = {"axes": axes, "method": method}
564 settings.update(kwargs)
565 settings["show"] = False
567 plot_pointer_gamut(**settings)
569 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
571 if method == "cie 1931":
572 x_limit_min, x_limit_max = [-0.1], [0.9]
573 y_limit_min, y_limit_max = [-0.1], [0.9]
575 elif method == "cie 1960 ucs":
576 x_limit_min, x_limit_max = [-0.1], [0.7]
577 y_limit_min, y_limit_max = [-0.2], [0.6]
579 elif method == "cie 1976 ucs":
580 x_limit_min, x_limit_max = [-0.1], [0.7]
581 y_limit_min, y_limit_max = [-0.1], [0.7]
583 settings = {"colour_cycle_count": len(colourspaces)}
584 settings.update(kwargs)
586 cycle = colour_cycle(**settings)
588 plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
590 plot_settings_collection = [
591 {
592 "label": f"{colourspace.name}",
593 "marker": "o",
594 "color": next(cycle)[:3],
595 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
596 }
597 for colourspace in colourspaces
598 ]
600 if plot_kwargs is not None:
601 update_settings_collection(
602 plot_settings_collection, plot_kwargs, len(colourspaces)
603 )
605 for i, colourspace in enumerate(colourspaces):
606 plot_settings = plot_settings_collection[i]
608 if chromatically_adapt and not np.array_equal(
609 colourspace.whitepoint, plotting_colourspace.whitepoint
610 ):
611 colourspace = colourspace.chromatically_adapt( # noqa: PLW2901
612 plotting_colourspace.whitepoint,
613 plotting_colourspace.whitepoint_name,
614 )
616 # RGB colourspaces such as *ACES2065-1* have primaries with
617 # chromaticity coordinates set to 0 thus we prevent nan from being
618 # yield by zero division in later colour transformations.
619 P = np.where(
620 colourspace.primaries == 0,
621 EPSILON,
622 colourspace.primaries,
623 )
624 P = xy_to_ij(P)
625 W = xy_to_ij(colourspace.whitepoint)
627 P_p = np.vstack([P, P[0]])
628 axes.plot(P_p[..., 0], P_p[..., 1], **plot_settings)
630 if show_whitepoints:
631 plot_settings["marker"] = "o"
632 plot_settings.pop("label")
634 W_p = np.vstack([W, W])
635 axes.plot(W_p[..., 0], W_p[..., 1], **plot_settings)
637 x_limit_min.append(cast("float", np.amin(P[..., 0]) - 0.1))
638 y_limit_min.append(cast("float", np.amin(P[..., 1]) - 0.1))
639 x_limit_max.append(cast("float", np.amax(P[..., 0]) + 0.1))
640 y_limit_max.append(cast("float", np.amax(P[..., 1]) + 0.1))
642 bounding_box = (
643 min(x_limit_min),
644 max(x_limit_max),
645 min(y_limit_min),
646 max(y_limit_max),
647 )
649 settings.update(
650 {
651 "show": True,
652 "legend": True,
653 "bounding_box": bounding_box,
654 }
655 )
656 settings.update(kwargs)
658 return render(**settings)
661@override_style()
662def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
663 colourspaces: (
664 RGB_Colourspace
665 | LiteralRGBColourspace
666 | str
667 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
668 ),
669 cmfs: (
670 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
671 ) = "CIE 1931 2 Degree Standard Observer",
672 chromaticity_diagram_callable_CIE1931: Callable = (
673 plot_chromaticity_diagram_CIE1931
674 ),
675 show_whitepoints: bool = True,
676 show_pointer_gamut: bool = False,
677 chromatically_adapt: bool = False,
678 plot_kwargs: dict | List[dict] | None = None,
679 **kwargs: Any,
680) -> Tuple[Figure, Axes]:
681 """
682 Plot specified *RGB* colourspaces in the *CIE 1931 Chromaticity Diagram*.
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.
714 Other Parameters
715 ----------------
716 kwargs
717 {:func:`colour.plotting.artist`,
718 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
719 :func:`colour.plotting.models.plot_pointer_gamut`,
720 :func:`colour.plotting.models.\
721plot_RGB_colourspaces_in_chromaticity_diagram`,
722 :func:`colour.plotting.render`},
723 See the documentation of the previously listed definitions.
725 Returns
726 -------
727 :class:`tuple`
728 Current figure and axes.
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...>)
738 .. image:: ../_static/Plotting_\
739Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png
740 :align: center
741 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
742 """
744 settings = dict(kwargs)
745 settings.update({"method": "CIE 1931"})
747 return plot_RGB_colourspaces_in_chromaticity_diagram(
748 colourspaces,
749 cmfs,
750 chromaticity_diagram_callable_CIE1931,
751 show_whitepoints=show_whitepoints,
752 show_pointer_gamut=show_pointer_gamut,
753 chromatically_adapt=chromatically_adapt,
754 plot_kwargs=plot_kwargs,
755 **settings,
756 )
759@override_style()
760def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
761 colourspaces: (
762 RGB_Colourspace
763 | LiteralRGBColourspace
764 | str
765 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
766 ),
767 cmfs: (
768 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
769 ) = "CIE 1931 2 Degree Standard Observer",
770 chromaticity_diagram_callable_CIE1960UCS: Callable = (
771 plot_chromaticity_diagram_CIE1960UCS
772 ),
773 show_whitepoints: bool = True,
774 show_pointer_gamut: bool = False,
775 chromatically_adapt: bool = False,
776 plot_kwargs: dict | List[dict] | None = None,
777 **kwargs: Any,
778) -> Tuple[Figure, Axes]:
779 """
780 Plot specified *RGB* colourspaces in the
781 *CIE 1960 UCS Chromaticity Diagram*.
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.
813 Other Parameters
814 ----------------
815 kwargs
816 {:func:`colour.plotting.artist`,
817 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
818 :func:`colour.plotting.models.plot_pointer_gamut`,
819 :func:`colour.plotting.models.\
820plot_RGB_colourspaces_in_chromaticity_diagram`,
821 :func:`colour.plotting.render`},
822 See the documentation of the previously listed definitions.
824 Returns
825 -------
826 :class:`tuple`
827 Current figure and axes.
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...>)
837 .. image:: ../_static/Plotting_\
838Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1960UCS.png
839 :align: center
840 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
841 """
843 settings = dict(kwargs)
844 settings.update({"method": "CIE 1960 UCS"})
846 return plot_RGB_colourspaces_in_chromaticity_diagram(
847 colourspaces,
848 cmfs,
849 chromaticity_diagram_callable_CIE1960UCS,
850 show_whitepoints=show_whitepoints,
851 show_pointer_gamut=show_pointer_gamut,
852 chromatically_adapt=chromatically_adapt,
853 plot_kwargs=plot_kwargs,
854 **settings,
855 )
858@override_style()
859def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
860 colourspaces: (
861 RGB_Colourspace
862 | LiteralRGBColourspace
863 | str
864 | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
865 ),
866 cmfs: (
867 MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
868 ) = "CIE 1931 2 Degree Standard Observer",
869 chromaticity_diagram_callable_CIE1976UCS: Callable = (
870 plot_chromaticity_diagram_CIE1976UCS
871 ),
872 show_whitepoints: bool = True,
873 show_pointer_gamut: bool = False,
874 chromatically_adapt: bool = False,
875 plot_kwargs: dict | List[dict] | None = None,
876 **kwargs: Any,
877) -> Tuple[Figure, Axes]:
878 """
879 Plot the specified *RGB* colourspaces in the
880 *CIE 1976 UCS Chromaticity Diagram*.
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.
912 Other Parameters
913 ----------------
914 kwargs
915 {:func:`colour.plotting.artist`,
916 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
917 :func:`colour.plotting.models.plot_pointer_gamut`,
918 :func:`colour.plotting.models.\
919plot_RGB_colourspaces_in_chromaticity_diagram`,
920 :func:`colour.plotting.render`},
921 See the documentation of the previously listed definitions.
923 Returns
924 -------
925 :class:`tuple`
926 Current figure and axes.
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...>)
936 .. image:: ../_static/Plotting_\
937Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1976UCS.png
938 :align: center
939 :alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
940 """
942 settings = dict(kwargs)
943 settings.update({"method": "CIE 1976 UCS"})
945 return plot_RGB_colourspaces_in_chromaticity_diagram(
946 colourspaces,
947 cmfs,
948 chromaticity_diagram_callable_CIE1976UCS,
949 show_whitepoints=show_whitepoints,
950 show_pointer_gamut=show_pointer_gamut,
951 chromatically_adapt=chromatically_adapt,
952 plot_kwargs=plot_kwargs,
953 **settings,
954 )
957@override_style()
958def plot_RGB_chromaticities_in_chromaticity_diagram(
959 RGB: ArrayLike,
960 colourspace: (
961 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
962 ) = "sRGB",
963 chromaticity_diagram_callable: Callable = (
964 plot_RGB_colourspaces_in_chromaticity_diagram
965 ),
966 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
967 scatter_kwargs: dict | None = None,
968 **kwargs: Any,
969) -> Tuple[Figure, Axes]:
970 """
971 Plot the specified *RGB* colourspace array in the *Chromaticity Diagram*
972 using the specified method.
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:
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.
997 Other Parameters
998 ----------------
999 kwargs
1000 {:func:`colour.plotting.artist`,
1001 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1002 :func:`colour.plotting.models.\
1003plot_RGB_colourspaces_in_chromaticity_diagram`,
1004 :func:`colour.plotting.render`},
1005 See the documentation of the previously listed definitions.
1007 Returns
1008 -------
1009 :class:`tuple`
1010 Current figure and axes.
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...>)
1019 .. image:: ../_static/Plotting_\
1020Plot_RGB_Chromaticities_In_Chromaticity_Diagram.png
1021 :align: center
1022 :alt: plot_RGB_chromaticities_in_chromaticity_diagram
1023 """
1025 RGB = np.reshape(as_float_array(RGB)[..., :3], (-1, 3))
1026 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1028 settings: Dict[str, Any] = {"uniform": True}
1029 settings.update(kwargs)
1031 _figure, axes = artist(**settings)
1033 scatter_settings = {
1034 "s": 40,
1035 "c": "RGB",
1036 "marker": "o",
1037 "alpha": 0.85,
1038 "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
1039 "apply_cctf_encoding": True,
1040 }
1041 if scatter_kwargs is not None:
1042 scatter_settings.update(scatter_kwargs)
1044 settings = dict(kwargs)
1045 settings.update({"axes": axes, "show": False})
1047 colourspace = cast(
1048 "RGB_Colourspace",
1049 first_item(filter_RGB_colourspaces(colourspace).values()),
1050 )
1052 settings["colourspaces"] = [colourspace, *settings.get("colourspaces", [])]
1054 chromaticity_diagram_callable(**settings)
1056 use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1057 apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding")
1058 if use_RGB_colours:
1059 RGB = RGB[RGB[:, 1].argsort()]
1060 scatter_settings["c"] = np.clip(
1061 np.reshape(
1062 RGB_to_RGB(
1063 RGB,
1064 colourspace,
1065 CONSTANTS_COLOUR_STYLE.colour.colourspace,
1066 apply_cctf_encoding=apply_cctf_encoding,
1067 ),
1068 (-1, 3),
1069 ),
1070 0,
1071 1,
1072 )
1074 XYZ = RGB_to_XYZ(RGB, colourspace)
1076 XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
1078 ij = XYZ_to_ij(XYZ, colourspace.whitepoint)
1080 axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings)
1082 settings.update({"show": True})
1083 settings.update(kwargs)
1085 return render(**settings)
1088@override_style()
1089def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
1090 RGB: ArrayLike,
1091 colourspace: (
1092 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1093 ) = "sRGB",
1094 chromaticity_diagram_callable_CIE1931: Callable = (
1095 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
1096 ),
1097 scatter_kwargs: dict | None = None,
1098 **kwargs: Any,
1099) -> Tuple[Figure, Axes]:
1100 """
1101 Plot specified *RGB* colourspace array in the *CIE 1931 Chromaticity
1102 Diagram*.
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:
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.
1127 Other Parameters
1128 ----------------
1129 kwargs
1130 {:func:`colour.plotting.artist`,
1131 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1132 :func:`colour.plotting.models.\
1133plot_RGB_colourspaces_in_chromaticity_diagram`,
1134 :func:`colour.plotting.render`},
1135 See the documentation of the previously listed definitions.
1137 Returns
1138 -------
1139 :class:`tuple`
1140 Current figure and axes.
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...>)
1151 .. image:: ../_static/Plotting_\
1152Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1931.png
1153 :align: center
1154 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931
1155 """
1157 settings = dict(kwargs)
1158 settings.update({"method": "CIE 1931"})
1160 return plot_RGB_chromaticities_in_chromaticity_diagram(
1161 RGB,
1162 colourspace,
1163 chromaticity_diagram_callable_CIE1931,
1164 scatter_kwargs=scatter_kwargs,
1165 **settings,
1166 )
1169@override_style()
1170def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
1171 RGB: ArrayLike,
1172 colourspace: (
1173 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1174 ) = "sRGB",
1175 chromaticity_diagram_callable_CIE1960UCS: Callable = (
1176 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
1177 ),
1178 scatter_kwargs: dict | None = None,
1179 **kwargs: Any,
1180) -> Tuple[Figure, Axes]:
1181 """
1182 Plot the specified *RGB* colourspace array in the
1183 *CIE 1960 UCS Chromaticity Diagram*.
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:
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.
1208 Other Parameters
1209 ----------------
1210 kwargs
1211 {:func:`colour.plotting.artist`,
1212 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1213 :func:`colour.plotting.models.\
1214plot_RGB_colourspaces_in_chromaticity_diagram`,
1215 :func:`colour.plotting.render`},
1216 See the documentation of the previously listed definitions.
1218 Returns
1219 -------
1220 :class:`tuple`
1221 Current figure and axes.
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...>)
1232 .. image:: ../_static/Plotting_\
1233Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1960UCS.png
1234 :align: center
1235 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS
1236 """
1238 settings = dict(kwargs)
1239 settings.update({"method": "CIE 1960 UCS"})
1241 return plot_RGB_chromaticities_in_chromaticity_diagram(
1242 RGB,
1243 colourspace,
1244 chromaticity_diagram_callable_CIE1960UCS,
1245 scatter_kwargs=scatter_kwargs,
1246 **settings,
1247 )
1250@override_style()
1251def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
1252 RGB: ArrayLike,
1253 colourspace: (
1254 RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
1255 ) = "sRGB",
1256 chromaticity_diagram_callable_CIE1976UCS: Callable = (
1257 plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
1258 ),
1259 scatter_kwargs: dict | None = None,
1260 **kwargs: Any,
1261) -> Tuple[Figure, Axes]:
1262 """
1263 Plot the specified *RGB* colourspace array in the
1264 *CIE 1976 UCS Chromaticity Diagram*.
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:
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.
1289 Other Parameters
1290 ----------------
1291 kwargs
1292 {:func:`colour.plotting.artist`,
1293 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1294 :func:`colour.plotting.models.\
1295plot_RGB_colourspaces_in_chromaticity_diagram`,
1296 :func:`colour.plotting.render`},
1297 See the documentation of the previously listed definitions.
1299 Returns
1300 -------
1301 :class:`tuple`
1302 Current figure and axes.
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...>)
1313 .. image:: ../_static/Plotting_\
1314Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1976UCS.png
1315 :align: center
1316 :alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS
1317 """
1319 settings = dict(kwargs)
1320 settings.update({"method": "CIE 1976 UCS"})
1322 return plot_RGB_chromaticities_in_chromaticity_diagram(
1323 RGB,
1324 colourspace,
1325 chromaticity_diagram_callable_CIE1976UCS,
1326 scatter_kwargs=scatter_kwargs,
1327 **settings,
1328 )
1331def ellipses_MacAdam1942(
1332 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
1333) -> List[NDArrayFloat]:
1334 """
1335 Return *MacAdam (1942) Ellipses (Observer PGN)* coefficients using the
1336 specified method.
1338 Parameters
1339 ----------
1340 method
1341 Computation method.
1343 Returns
1344 -------
1345 :class:`list`
1346 *MacAdam (1942) Ellipses (Observer PGN)* coefficients.
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 """
1355 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
1357 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
1359 x, y, _a, _b, _theta, a, b, theta = tsplit(DATA_MACADAM_1942_ELLIPSES)
1361 ellipses_coefficients = []
1362 for i in range(len(theta)):
1363 xy = point_at_angle_on_ellipse(
1364 np.linspace(0, 360, 36),
1365 [x[i], y[i], a[i] / 60, b[i] / 60, theta[i]],
1366 )
1367 ij = xy_to_ij(xy)
1368 ellipses_coefficients.append(
1369 ellipse_coefficients_canonical_form(ellipse_fitting(ij))
1370 )
1372 return ellipses_coefficients
1375@override_style()
1376def plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1377 chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
1378 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
1379 chromaticity_diagram_clipping: bool = False,
1380 ellipse_kwargs: dict | List[dict] | None = None,
1381 **kwargs: Any,
1382) -> Tuple[Figure, Axes]:
1383 """
1384 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1385 *Chromaticity Diagram* using the specified method.
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.
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.
1410 Returns
1411 -------
1412 :class:`tuple`
1413 Current figure and axes.
1415 Examples
1416 --------
1417 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram()
1418 ... # doctest: +ELLIPSIS
1419 (<Figure size ... with 1 Axes>, <...Axes...>)
1421 .. image:: ../_static/\
1422Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png
1423 :align: center
1424 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram
1425 """
1427 settings: Dict[str, Any] = {"uniform": True}
1428 settings.update(kwargs)
1430 _figure, axes = artist(**settings)
1432 settings = dict(kwargs)
1433 settings.update({"axes": axes, "show": False})
1435 ellipses_coefficients = ellipses_MacAdam1942(method=method)
1437 if chromaticity_diagram_clipping:
1438 diagram_clipping_path_x = []
1439 diagram_clipping_path_y = []
1440 for coefficients in ellipses_coefficients:
1441 coefficients = np.copy(coefficients) # noqa: PLW2901
1443 coefficients[2:4] /= 2
1445 x, y = tsplit(
1446 point_at_angle_on_ellipse(
1447 np.linspace(0, 360, 36),
1448 coefficients,
1449 )
1450 )
1451 diagram_clipping_path_x.append(x)
1452 diagram_clipping_path_y.append(y)
1454 diagram_clipping_path = np.rollaxis(
1455 np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3
1456 )
1457 diagram_clipping_path = Path.make_compound_path_from_polys(
1458 diagram_clipping_path
1459 ).vertices
1460 settings.update({"diagram_clipping_path": diagram_clipping_path})
1462 chromaticity_diagram_callable(**settings)
1464 ellipse_settings_collection = [
1465 {
1466 "color": CONSTANTS_COLOUR_STYLE.colour.cycle[4],
1467 "alpha": 0.4,
1468 "linewidth": colour_style()["lines.linewidth"],
1469 "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
1470 }
1471 for _ellipses_coefficient in ellipses_coefficients
1472 ]
1474 if ellipse_kwargs is not None:
1475 update_settings_collection(
1476 ellipse_settings_collection,
1477 ellipse_kwargs,
1478 len(ellipses_coefficients),
1479 )
1481 for i, coefficients in enumerate(ellipses_coefficients):
1482 x_c, y_c, a_a, a_b, theta_e = coefficients
1483 ellipse = Ellipse(
1484 (x_c, y_c),
1485 a_a,
1486 a_b,
1487 angle=theta_e,
1488 **ellipse_settings_collection[i],
1489 )
1490 axes.add_artist(ellipse)
1492 settings.update({"show": True})
1493 settings.update(kwargs)
1495 return render(**settings)
1498@override_style()
1499def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931(
1500 chromaticity_diagram_callable_CIE1931: Callable = (
1501 plot_chromaticity_diagram_CIE1931
1502 ),
1503 chromaticity_diagram_clipping: bool = False,
1504 ellipse_kwargs: dict | List[dict] | None = None,
1505 **kwargs: Any,
1506) -> Tuple[Figure, Axes]:
1507 """
1508 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1509 *CIE 1931 Chromaticity Diagram*.
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.
1524 Other Parameters
1525 ----------------
1526 kwargs
1527 {:func:`colour.plotting.artist`,
1528 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1529 :func:`colour.plotting.models.\
1530plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1531 :func:`colour.plotting.render`},
1532 See the documentation of the previously listed definitions.
1534 Returns
1535 -------
1536 :class:`tuple`
1537 Current figure and axes.
1539 Examples
1540 --------
1541 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931()
1542 ... # doctest: +ELLIPSIS
1543 (<Figure size ... with 1 Axes>, <...Axes...>)
1545 .. image:: ../_static/\
1546Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1931.png
1547 :align: center
1548 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931
1549 """
1551 settings = dict(kwargs)
1552 settings.update({"method": "CIE 1931"})
1554 return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1555 chromaticity_diagram_callable_CIE1931,
1556 chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1557 ellipse_kwargs=ellipse_kwargs,
1558 **settings,
1559 )
1562@override_style()
1563def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS(
1564 chromaticity_diagram_callable_CIE1960UCS: Callable = (
1565 plot_chromaticity_diagram_CIE1960UCS
1566 ),
1567 chromaticity_diagram_clipping: bool = False,
1568 ellipse_kwargs: dict | List[dict] | None = None,
1569 **kwargs: Any,
1570) -> Tuple[Figure, Axes]:
1571 """
1572 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1573 *CIE 1960 UCS Chromaticity Diagram*.
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.
1589 Other Parameters
1590 ----------------
1591 kwargs
1592 {:func:`colour.plotting.artist`,
1593 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1594 :func:`colour.plotting.models.\
1595plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1596 :func:`colour.plotting.render`},
1597 See the documentation of the previously listed definitions.
1599 Returns
1600 -------
1601 :class:`tuple`
1602 Current figure and axes.
1604 Examples
1605 --------
1606 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS()
1607 ... # doctest: +ELLIPSIS
1608 (<Figure size ... with 1 Axes>, <...Axes...>)
1610 .. image:: ../_static/\
1611Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1960UCS.png
1612 :align: center
1613 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS
1614 """
1616 settings = dict(kwargs)
1617 settings.update({"method": "CIE 1960 UCS"})
1619 return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1620 chromaticity_diagram_callable_CIE1960UCS,
1621 chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1622 ellipse_kwargs=ellipse_kwargs,
1623 **settings,
1624 )
1627@override_style()
1628def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS(
1629 chromaticity_diagram_callable_CIE1976UCS: Callable = (
1630 plot_chromaticity_diagram_CIE1976UCS
1631 ),
1632 chromaticity_diagram_clipping: bool = False,
1633 ellipse_kwargs: dict | List[dict] | None = None,
1634 **kwargs: Any,
1635) -> Tuple[Figure, Axes]:
1636 """
1637 Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
1638 *CIE 1976 UCS Chromaticity Diagram*.
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.
1654 Other Parameters
1655 ----------------
1656 kwargs
1657 {:func:`colour.plotting.artist`,
1658 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
1659 :func:`colour.plotting.models.\
1660plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
1661 :func:`colour.plotting.render`},
1662 See the documentation of the previously listed definitions.
1664 Returns
1665 -------
1666 :class:`tuple`
1667 Current figure and axes.
1669 Examples
1670 --------
1671 >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS()
1672 ... # doctest: +ELLIPSIS
1673 (<Figure size ... with 1 Axes>, <...Axes...>)
1675 .. image:: ../_static/\
1676Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1976UCS.png
1677 :align: center
1678 :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS
1679 """
1681 settings = dict(kwargs)
1682 settings.update({"method": "CIE 1976 UCS"})
1684 return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
1685 chromaticity_diagram_callable_CIE1976UCS,
1686 chromaticity_diagram_clipping=chromaticity_diagram_clipping,
1687 ellipse_kwargs=ellipse_kwargs,
1688 **settings,
1689 )
1692@override_style()
1693def plot_single_cctf(
1694 cctf: Callable | str, cctf_decoding: bool = False, **kwargs: Any
1695) -> Tuple[Figure, Axes]:
1696 """
1697 Plot specified colourspace colour component transfer function.
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.
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.
1716 Returns
1717 -------
1718 :class:`tuple`
1719 Current figure and axes.
1721 Examples
1722 --------
1723 >>> plot_single_cctf("ITU-R BT.709") # doctest: +ELLIPSIS
1724 (<Figure size ... with 1 Axes>, <...Axes...>)
1726 .. image:: ../_static/Plotting_Plot_Single_CCTF.png
1727 :align: center
1728 :alt: plot_single_cctf
1729 """
1731 settings: Dict[str, Any] = {
1732 "title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF"
1733 }
1734 settings.update(kwargs)
1736 return plot_multi_cctfs([cctf], cctf_decoding, **settings)
1739@override_style()
1740def plot_multi_cctfs(
1741 cctfs: Callable | str | Sequence[Callable | str],
1742 cctf_decoding: bool = False,
1743 **kwargs: Any,
1744) -> Tuple[Figure, Axes]:
1745 """
1746 Plot the specified colour component transfer functions.
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.
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.
1765 Returns
1766 -------
1767 :class:`tuple`
1768 Current figure and axes.
1770 Examples
1771 --------
1772 >>> plot_multi_cctfs(["ITU-R BT.709", "sRGB"]) # doctest: +ELLIPSIS
1773 (<Figure size ... with 1 Axes>, <...Axes...>)
1775 .. image:: ../_static/Plotting_Plot_Multi_CCTFs.png
1776 :align: center
1777 :alt: plot_multi_cctfs
1778 """
1780 cctfs_filtered = filter_passthrough(
1781 CCTF_DECODINGS if cctf_decoding else CCTF_ENCODINGS, cctfs
1782 )
1784 mode = "Decoding" if cctf_decoding else "Encoding"
1785 title = f"{', '.join(list(cctfs_filtered))} - {mode} CCTFs"
1787 settings: Dict[str, Any] = {
1788 "bounding_box": (0, 1, 0, 1),
1789 "legend": True,
1790 "title": title,
1791 "x_label": "Signal Value" if cctf_decoding else "Tristimulus Value",
1792 "y_label": "Tristimulus Value" if cctf_decoding else "Signal Value",
1793 }
1794 settings.update(kwargs)
1796 with domain_range_scale("1"):
1797 return plot_multi_functions(cctfs_filtered, **settings)
1800@override_style()
1801@required("SciPy")
1802def plot_constant_hue_loci(
1803 data: ArrayLike,
1804 model: LiteralColourspaceModel | str = "CIE Lab",
1805 scatter_kwargs: dict | None = None,
1806 convert_kwargs: dict | None = None,
1807 **kwargs: Any,
1808) -> Tuple[Figure, Axes]:
1809 """
1810 Plot specified constant hue loci colour matches data such as that from
1811 :cite:`Hung1995` or :cite:`Ebner1998` that are easily loaded with
1812 `Colour - Datasets <https://github.com/colour-science/colour-datasets>`__.
1814 Parameters
1815 ----------
1816 data
1817 Constant hue loci colour matches data expected to be an `ArrayLike` as
1818 follows::
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 ]
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:
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.
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.
1856 Returns
1857 -------
1858 :class:`tuple`
1859 Current figure and axes.
1861 References
1862 ----------
1863 :cite:`Ebner1998`, :cite:`Hung1995`, :cite:`Mansencal2019`
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...>)
1943 .. image:: ../_static/Plotting_Plot_Constant_Hue_Loci.png
1944 :align: center
1945 :alt: plot_constant_hue_loci
1946 """
1948 import scipy.optimize # noqa: PLC0415
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
1955 settings: Dict[str, Any] = {"uniform": True}
1956 settings.update(kwargs)
1958 _figure, axes = artist(**settings)
1960 scatter_settings = {
1961 "s": 40,
1962 "c": "RGB",
1963 "marker": "o",
1964 "alpha": 0.85,
1965 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
1966 }
1967 if scatter_kwargs is not None:
1968 scatter_settings.update(scatter_kwargs)
1970 convert_kwargs = optional(convert_kwargs, {})
1972 use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
1974 colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
1975 for hue_data in data:
1976 _name, XYZ_r, XYZ_cr, XYZ_ct, _metadata = hue_data
1978 xy_r = XYZ_to_xy(XYZ_r)
1980 convert_settings = {"illuminant": xy_r}
1981 convert_settings.update(convert_kwargs)
1983 ijk_ct = colourspace_model_axis_reorder(
1984 convert(XYZ_ct, "CIE XYZ", model, **convert_settings), # pyright: ignore
1985 model,
1986 )
1987 ijk_cr = colourspace_model_axis_reorder(
1988 convert(XYZ_cr, "CIE XYZ", model, **convert_settings), # pyright: ignore
1989 model,
1990 )
1992 ijk_ct = colourspace_model_to_reference(ijk_ct, model)
1993 ijk_cr = colourspace_model_to_reference(ijk_cr, model)
1995 def _linear_equation(
1996 x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat
1997 ) -> NDArrayFloat:
1998 """Define the canonical linear equation for a line."""
2000 return a * x + b
2002 popt, _pcov = scipy.optimize.curve_fit(
2003 _linear_equation, ijk_ct[..., 0], ijk_ct[..., 1]
2004 )
2006 axes.plot(
2007 ijk_ct[..., 0],
2008 _linear_equation(ijk_ct[..., 0], *popt), # type: ignore
2009 c=CONSTANTS_COLOUR_STYLE.colour.average,
2010 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2011 )
2013 if use_RGB_colours:
2014 RGB_ct = XYZ_to_RGB(XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True)
2015 scatter_settings["c"] = np.clip(RGB_ct, 0, 1)
2016 RGB_cr = XYZ_to_RGB(XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True)
2017 RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1)
2018 else:
2019 RGB_cr = scatter_settings["c"]
2021 axes.scatter(ijk_ct[..., 0], ijk_ct[..., 1], **scatter_settings)
2023 axes.plot(
2024 ijk_cr[..., 0],
2025 ijk_cr[..., 1],
2026 "s",
2027 c=RGB_cr,
2028 markersize=CONSTANTS_COLOUR_STYLE.geometry.short * 8,
2029 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
2030 )
2032 labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[
2033 as_int_array(colourspace_model_axis_reorder([0, 1, 2], model))
2034 ]
2036 settings = {
2037 "axes": axes,
2038 "title": f"Constant Hue Loci - {model}",
2039 "x_label": labels[0],
2040 "y_label": labels[1],
2041 }
2042 settings.update(kwargs)
2044 return render(**settings)