"""
CIE Chromaticity Diagrams Plotting
==================================
Define the *CIE* chromaticity diagrams plotting objects:
- :func:`colour.plotting.lines_spectral_locus`
- :func:`colour.plotting.plot_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.plot_chromaticity_diagram_CIE1960UCS`
- :func:`colour.plotting.plot_chromaticity_diagram_CIE1976UCS`
- :func:`colour.plotting.plot_sds_in_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.plot_sds_in_chromaticity_diagram_CIE1960UCS`
- :func:`colour.plotting.plot_sds_in_chromaticity_diagram_CIE1976UCS`
"""
from __future__ import annotations
import bisect
import numpy as np
from matplotlib.axes import Axes
from matplotlib.collections import LineCollection
from matplotlib.figure import Figure
from matplotlib.patches import Polygon
from colour.algebra import normalise_maximum, normalise_vector
from colour.colorimetry import (
SDS_ILLUMINANTS,
MultiSpectralDistributions,
SpectralDistribution,
sd_to_XYZ,
sds_and_msds_to_sds,
)
from colour.constants import DTYPE_FLOAT_DEFAULT
from colour.hints import (
Any,
ArrayLike,
Callable,
Dict,
List,
Literal,
NDArray,
Sequence,
Tuple,
cast,
)
from colour.models import (
Luv_to_uv,
Luv_uv_to_xy,
UCS_to_uv,
UCS_uv_to_xy,
XYZ_to_Luv,
XYZ_to_UCS,
XYZ_to_xy,
xy_to_Luv_uv,
xy_to_UCS_uv,
xy_to_XYZ,
)
from colour.notation import HEX_to_RGB
from colour.plotting import (
CONSTANTS_ARROW_STYLE,
CONSTANTS_COLOUR_STYLE,
XYZ_to_plotting_colourspace,
artist,
filter_cmfs,
filter_illuminants,
override_style,
render,
update_settings_collection,
)
from colour.utilities import (
CanonicalMapping,
as_float_array,
domain_range_scale,
first_item,
optional,
tsplit,
tstack,
validate_method,
zeros,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"METHODS_CHROMATICITY_DIAGRAM",
"LABELS_CHROMATICITY_DIAGRAM_DEFAULT",
"lines_spectral_locus",
"plot_spectral_locus",
"plot_chromaticity_diagram_colours",
"plot_chromaticity_diagram",
"plot_chromaticity_diagram_CIE1931",
"plot_chromaticity_diagram_CIE1960UCS",
"plot_chromaticity_diagram_CIE1976UCS",
"plot_sds_in_chromaticity_diagram",
"plot_sds_in_chromaticity_diagram_CIE1931",
"plot_sds_in_chromaticity_diagram_CIE1960UCS",
"plot_sds_in_chromaticity_diagram_CIE1976UCS",
]
METHODS_CHROMATICITY_DIAGRAM: CanonicalMapping = CanonicalMapping(
{
"CIE 1931": {
"XYZ_to_ij": lambda a, *args: XYZ_to_xy(a), # noqa: ARG005
"ij_to_XYZ": lambda a, *args: xy_to_XYZ(a), # noqa: ARG005
"xy_to_ij": lambda a, *args: a, # noqa: ARG005
"uv_to_ij": lambda a, *args: UCS_uv_to_xy(a), # noqa: ARG005
},
"CIE 1960 UCS": {
"XYZ_to_ij": lambda a, *args: UCS_to_uv( # noqa: ARG005
XYZ_to_UCS(a)
),
"ij_to_XYZ": lambda a, *args: xy_to_XYZ( # noqa: ARG005
UCS_uv_to_xy(a)
),
"xy_to_ij": lambda a, *args: xy_to_UCS_uv(a), # noqa: ARG005
"uv_to_ij": lambda a, *args: a, # noqa: ARG005
},
"CIE 1976 UCS": {
"XYZ_to_ij": lambda a, *args: Luv_to_uv(XYZ_to_Luv(a, *args), *args),
"ij_to_XYZ": lambda a, *args: xy_to_XYZ( # noqa: ARG005
Luv_uv_to_xy(a)
),
"xy_to_ij": lambda a, *args: xy_to_Luv_uv(a), # noqa: ARG005
"uv_to_ij": lambda a, *args: xy_to_Luv_uv( # noqa: ARG005
UCS_uv_to_xy(a)
),
},
}
)
"""Chromaticity diagram conversion methods."""
LABELS_CHROMATICITY_DIAGRAM_DEFAULT: CanonicalMapping = CanonicalMapping(
{
"CIE 1931": (
390,
460,
470,
480,
490,
500,
510,
520,
540,
560,
580,
600,
620,
700,
),
"CIE 1960 UCS": (
420,
440,
450,
460,
470,
480,
490,
500,
510,
520,
530,
540,
550,
560,
570,
580,
590,
600,
610,
620,
630,
645,
680,
),
"CIE 1976 UCS": (
420,
440,
450,
460,
470,
480,
490,
500,
510,
520,
530,
540,
550,
560,
570,
580,
590,
600,
610,
620,
630,
645,
680,
),
}
)
"""*Chromaticity Diagram* default labels."""
[docs]
def lines_spectral_locus(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
labels: Sequence | None = None,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
) -> Tuple[NDArray, NDArray]:
"""
Return the *Spectral Locus* line vertices, i.e., positions, normals and
colours, according to given method.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
labels
Array of wavelength labels used to customise which labels will be drawn
around the spectral locus. Passing an empty array will result in no
wavelength labels being drawn.
method
*Chromaticity Diagram* method.
Returns
-------
:class:`tuple`
Tuple of *Spectral Locus* vertices and wavelength labels vertices.
Examples
--------
>>> lines = lines_spectral_locus()
>>> len(lines)
2
>>> lines[0].dtype
dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \
('colour', '<f8', (3,))])
>>> lines[1].dtype
dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \
('colour', '<f8', (3,))])
"""
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
labels = optional(labels, LABELS_CHROMATICITY_DIAGRAM_DEFAULT[method])
method = validate_method(method, tuple(METHODS_CHROMATICITY_DIAGRAM))
illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint
wavelengths = list(cmfs.wavelengths)
equal_energy = np.array([1 / 3] * 2)
XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"]
# CMFS
ij_cmfs = XYZ_to_ij(cmfs.values, illuminant)
# Line of Purples
ij_pl = tstack(
[
np.linspace(ij_cmfs[-1][0], ij_cmfs[0][0], 20),
np.linspace(ij_cmfs[-1][1], ij_cmfs[0][1], 20),
]
)
ij_sl = np.vstack([ij_cmfs, ij_pl])
colour_sl = normalise_maximum(
XYZ_to_plotting_colourspace(np.reshape(ij_to_XYZ(ij_sl, illuminant), (-1, 3))),
axis=-1,
)
lines_sl = zeros(
ij_sl.shape[0],
[
("position", DTYPE_FLOAT_DEFAULT, 2),
("normal", DTYPE_FLOAT_DEFAULT, 2),
("colour", DTYPE_FLOAT_DEFAULT, 3),
], # pyright: ignore
)
lines_sl["position"] = ij_sl
lines_sl["colour"] = colour_sl
# Labels Normals
ij_n, colour_l, normal_l = [], [], []
wl_ij_cmfs = dict(zip(wavelengths, ij_cmfs))
for label in cast(Tuple, labels):
ij_l = wl_ij_cmfs.get(label)
if ij_l is None:
continue
i, j = tsplit(ij_l)
index = bisect.bisect(wavelengths, label)
left = wavelengths[index - 1] if index >= 0 else wavelengths[index]
right = wavelengths[index] if index < len(wavelengths) else wavelengths[-1]
dx = wl_ij_cmfs[right][0] - wl_ij_cmfs[left][0]
dy = wl_ij_cmfs[right][1] - wl_ij_cmfs[left][1]
direction = np.array([-dy, dx])
normal = (
np.array([-dy, dx])
if np.dot(
normalise_vector(ij_l - equal_energy),
normalise_vector(direction),
)
> 0
else np.array([dy, -dx])
)
normal = normalise_vector(normal)
ij_n.append([[i, j], [i + normal[0] / 50, j + normal[1] / 50]])
normal_l.append([normal, normal])
colour_l.append([ij_l, ij_l])
ij_w = np.reshape(as_float_array(ij_n), (-1, 2))
normal_w = np.reshape(as_float_array(normal_l), (-1, 2))
colours_w = np.reshape(as_float_array(colour_l), (-1, 2))
colour_w = normalise_maximum(
XYZ_to_plotting_colourspace(
np.reshape(ij_to_XYZ(colours_w, illuminant), (-1, 3))
),
axis=-1,
)
lines_w = zeros(
ij_w.shape[0],
[
("position", DTYPE_FLOAT_DEFAULT, 2),
("normal", DTYPE_FLOAT_DEFAULT, 2),
("colour", DTYPE_FLOAT_DEFAULT, 3),
], # pyright: ignore
)
lines_w["position"] = ij_w
lines_w["normal"] = normal_w
lines_w["colour"] = colour_w
return lines_sl, lines_w
[docs]
@override_style()
def plot_spectral_locus(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
spectral_locus_colours: ArrayLike | str | None = None,
spectral_locus_opacity: float = 1,
spectral_locus_labels: Sequence | None = None,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *Spectral Locus* according to given method.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
spectral_locus_colours
Colours of the *Spectral Locus*, if ``spectral_locus_colours`` is set
to *RGB*, the colours will be computed according to the corresponding
chromaticity coordinates.
spectral_locus_opacity
Opacity of the *Spectral Locus*.
spectral_locus_labels
Array of wavelength labels used to customise which labels will be drawn
around the spectral locus. Passing an empty array will result in no
wavelength labels being drawn.
method
*Chromaticity Diagram* method.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_spectral_locus(spectral_locus_colours="RGB") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Spectral_Locus.png
:align: center
:alt: plot_spectral_locus
"""
method = validate_method(method, tuple(METHODS_CHROMATICITY_DIAGRAM))
spectral_locus_colours = optional(
spectral_locus_colours, CONSTANTS_COLOUR_STYLE.colour.dark
)
labels = optional(
spectral_locus_labels, LABELS_CHROMATICITY_DIAGRAM_DEFAULT[method]
)
use_RGB_colours = str(spectral_locus_colours).upper() == "RGB"
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
lines_sl, lines_w = lines_spectral_locus(cmfs, spectral_locus_labels, method)
axes.add_collection(
LineCollection(
np.reshape(
np.concatenate(
[lines_sl["position"][:-1], lines_sl["position"][1:]],
axis=1, # pyright: ignore
),
(-1, 2, 2),
),
colors=(lines_sl["colour"] if use_RGB_colours else spectral_locus_colours),
alpha=spectral_locus_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
)
)
axes.add_collection(
LineCollection(
np.reshape(lines_w["position"], (-1, 2, 2)), # pyright: ignore
colors=(
lines_w["colour"][::2] if use_RGB_colours else spectral_locus_colours
),
alpha=spectral_locus_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
)
)
for i, label in enumerate([label for label in labels if label in cmfs.wavelengths]):
positions = lines_w["position"][::2]
normals = lines_w["normal"][::2]
colours = lines_w["colour"][::2]
axes.plot(
positions[i, 0],
positions[i, 1],
"o",
color=colours[i] if use_RGB_colours else spectral_locus_colours,
alpha=spectral_locus_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
)
axes.text(
positions[i, 0] + normals[i, 0] / 50 * 1.25,
positions[i, 1] + normals[i, 1] / 50 * 1.25,
label,
clip_on=True,
ha="left" if lines_w["normal"][::2][i, 0] >= 0 else "right",
va="center",
fontsize="x-small-colour-science",
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_label,
)
settings = {"axes": axes}
settings.update(kwargs)
return render(**kwargs)
[docs]
@override_style()
def plot_chromaticity_diagram_colours(
samples: int = 256,
diagram_colours: ArrayLike | str | None = None,
diagram_opacity: float = 1,
diagram_clipping_path: ArrayLike | None = None,
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *Chromaticity Diagram* colours according to given method.
Parameters
----------
samples
Sample count on one axis when computing the *Chromaticity Diagram*
colours.
diagram_colours
Colours of the *Chromaticity Diagram*, if ``diagram_colours`` is set
to *RGB*, the colours will be computed according to the corresponding
coordinates.
diagram_opacity
Opacity of the *Chromaticity Diagram*.
diagram_clipping_path
Path of points used to clip the *Chromaticity Diagram* colours.
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
method
*Chromaticity Diagram* method.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_chromaticity_diagram_colours(diagram_colours="RGB")
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
:align: center
:alt: plot_chromaticity_diagram_colours
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
diagram_colours = optional(
diagram_colours, HEX_to_RGB(CONSTANTS_COLOUR_STYLE.colour.average)
)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint
XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
spectral_locus = XYZ_to_ij(cmfs.values, illuminant)
use_RGB_diagram_colours = str(diagram_colours).upper() == "RGB"
if use_RGB_diagram_colours:
ii, jj = np.meshgrid(np.linspace(0, 1, samples), np.linspace(1, 0, samples))
ij = tstack([ii, jj])
ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"]
diagram_colours = normalise_maximum(
XYZ_to_plotting_colourspace(ij_to_XYZ(ij), illuminant), axis=-1
)
polygon = Polygon(
(spectral_locus if diagram_clipping_path is None else diagram_clipping_path),
facecolor=(
"none"
if use_RGB_diagram_colours
else np.hstack([diagram_colours, diagram_opacity])
),
edgecolor=(
"none"
if use_RGB_diagram_colours
else np.hstack([diagram_colours, diagram_opacity])
),
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
)
axes.add_patch(polygon)
if use_RGB_diagram_colours:
# Preventing bounding box related issues as per
# https://github.com/matplotlib/matplotlib/issues/10529
image = axes.imshow(
diagram_colours,
interpolation="bilinear",
extent=(0, 1, 0, 1),
clip_path=None,
alpha=diagram_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
)
image.set_clip_path(polygon)
settings = {"axes": axes}
settings.update(kwargs)
return render(**kwargs)
[docs]
@override_style()
def plot_chromaticity_diagram(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
show_diagram_colours: bool = True,
show_spectral_locus: bool = True,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *Chromaticity Diagram* according to given method.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
show_diagram_colours
Whether to display the *Chromaticity Diagram* background colours.
show_spectral_locus
Whether to display the *Spectral Locus*.
method
*Chromaticity Diagram* method.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_spectral_locus`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram_colours`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_chromaticity_diagram() # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram.png
:align: center
:alt: plot_chromaticity_diagram
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
if show_diagram_colours:
settings = {"axes": axes, "method": method, "diagram_colours": "RGB"}
settings.update(kwargs)
settings["show"] = False
settings["cmfs"] = cmfs
plot_chromaticity_diagram_colours(**settings)
if show_spectral_locus:
settings = {"axes": axes, "method": method}
settings.update(kwargs)
settings["show"] = False
settings["cmfs"] = cmfs
plot_spectral_locus(**settings)
if method == "cie 1931":
x_label, y_label = "CIE x", "CIE y"
elif method == "cie 1960 ucs":
x_label, y_label = "CIE u", "CIE v"
elif method == "cie 1976 ucs":
x_label, y_label = (
"CIE u'",
"CIE v'",
)
title = f"{method.upper()} Chromaticity Diagram - {cmfs.display_name}"
settings.update(
{
"axes": axes,
"show": True,
"bounding_box": (0, 1, 0, 1),
"title": title,
"x_label": x_label,
"y_label": y_label,
}
)
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_chromaticity_diagram_CIE1931(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
show_diagram_colours: bool = True,
show_spectral_locus: bool = True,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *CIE 1931 Chromaticity Diagram*.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
show_diagram_colours
Whether to display the *Chromaticity Diagram* background colours.
show_spectral_locus
Whether to display the *Spectral Locus*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_chromaticity_diagram_CIE1931() # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_chromaticity_diagram(
cmfs, show_diagram_colours, show_spectral_locus, **settings
)
[docs]
@override_style()
def plot_chromaticity_diagram_CIE1960UCS(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
show_diagram_colours: bool = True,
show_spectral_locus: bool = True,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
show_diagram_colours
Whether to display the *Chromaticity Diagram* background colours.
show_spectral_locus
Whether to display the *Spectral Locus*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_chromaticity_diagram_CIE1960UCS() # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_chromaticity_diagram(
cmfs, show_diagram_colours, show_spectral_locus, **settings
)
[docs]
@override_style()
def plot_chromaticity_diagram_CIE1976UCS(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
show_diagram_colours: bool = True,
show_spectral_locus: bool = True,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the *CIE 1976 UCS Chromaticity Diagram*.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
show_diagram_colours
Whether to display the *Chromaticity Diagram* background colours.
show_spectral_locus
Whether to display the *Spectral Locus*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_chromaticity_diagram_CIE1976UCS() # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_CIE1976UCS.png
:align: center
:alt: plot_chromaticity_diagram_CIE1976UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1976 UCS"})
return plot_chromaticity_diagram(
cmfs, show_diagram_colours, show_spectral_locus, **settings
)
[docs]
@override_style()
def plot_sds_in_chromaticity_diagram(
sds: (
Sequence[SpectralDistribution | MultiSpectralDistributions]
| SpectralDistribution
| MultiSpectralDistributions
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
annotate_kwargs: dict | List[dict] | None = None,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distribution chromaticity coordinates into the
*Chromaticity Diagram* using given method.
Parameters
----------
sds
Spectral distributions or multi-spectral distributions to
plot. `sds` can be a single
:class:`colour.MultiSpectralDistributions` class instance, a list
of :class:`colour.MultiSpectralDistributions` class instances or a
List of :class:`colour.SpectralDistribution` class instances.
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
chromaticity_diagram_callable
Callable responsible for drawing the *Chromaticity Diagram*.
method
*Chromaticity Diagram* method.
annotate_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.annotate`
definition, used to annotate the resulting chromaticity coordinates
with their respective spectral distribution names. ``annotate_kwargs``
can be either a single dictionary applied to all the arrows with same
settings or a sequence of dictionaries with different settings for each
spectral distribution. The following special keyword arguments can also
be used:
- ``annotate`` : Whether to annotate the spectral distributions.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted spectral distributions.
`plot_kwargs`` can be either a single dictionary applied to all the
plotted spectral distributions with the same settings or a sequence of
dictionaries with different settings for each plotted spectral
distributions. The following special keyword arguments can also be
used:
- ``illuminant`` : The illuminant used to compute the spectral
distributions colours. The default is the illuminant associated
with the whitepoint of the default plotting colourspace.
``illuminant`` can be of any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``cmfs`` : The standard observer colour matching functions used for
computing the spectral distributions colours. ``cmfs`` can be of
any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``normalise_sd_colours`` : Whether to normalise the computed
spectral distributions colours. The default is *True*.
- ``use_sd_colours`` : Whether to use the computed spectral
distributions colours under the plotting colourspace illuminant.
Alternatively, it is possible to use the
:func:`matplotlib.pyplot.plot` definition ``color`` argument with
pre-computed values. The default is *True*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> A = SDS_ILLUMINANTS["A"]
>>> D65 = SDS_ILLUMINANTS["D65"]
>>> annotate_kwargs = [
... {"xytext": (-25, 15), "arrowprops": {"arrowstyle": "-"}},
... {},
... ]
>>> plot_kwargs = [
... {
... "illuminant": SDS_ILLUMINANTS["E"],
... "markersize": 15,
... "normalise_sd_colours": True,
... "use_sd_colours": True,
... },
... {"illuminant": SDS_ILLUMINANTS["E"]},
... ]
>>> plot_sds_in_chromaticity_diagram(
... [A, D65], annotate_kwargs=annotate_kwargs, plot_kwargs=plot_kwargs
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_SDS_In_Chromaticity_Diagram.png
:align: center
:alt: plot_sds_in_chromaticity_diagram
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
sds_converted = sds_and_msds_to_sds(sds)
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
settings.update(
{
"axes": axes,
"show": False,
"method": method,
"cmfs": cmfs,
}
)
chromaticity_diagram_callable(**settings)
XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
if method == "cie 1931":
bounding_box = (-0.1, 0.9, -0.1, 0.9)
elif method == "cie 1960 ucs":
bounding_box = (-0.1, 0.7, -0.2, 0.6)
elif method == "cie 1976 ucs":
bounding_box = (-0.1, 0.7, -0.1, 0.7)
annotate_settings_collection = [
{
"annotate": True,
"xytext": (-50, 30),
"textcoords": "offset points",
"arrowprops": CONSTANTS_ARROW_STYLE,
"zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,
}
for _ in range(len(sds_converted))
]
if annotate_kwargs is not None:
update_settings_collection(
annotate_settings_collection, annotate_kwargs, len(sds_converted)
)
plot_settings_collection = [
{
"color": CONSTANTS_COLOUR_STYLE.colour.brightest,
"label": f"{sd.display_name}",
"marker": "o",
"markeredgecolor": CONSTANTS_COLOUR_STYLE.colour.dark,
"markeredgewidth": CONSTANTS_COLOUR_STYLE.geometry.short * 0.75,
"markersize": (
CONSTANTS_COLOUR_STYLE.geometry.short * 6
+ CONSTANTS_COLOUR_STYLE.geometry.short * 0.75
),
"zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_line,
"cmfs": cmfs,
"illuminant": SDS_ILLUMINANTS["E"],
"use_sd_colours": False,
"normalise_sd_colours": False,
}
for sd in sds_converted
]
if plot_kwargs is not None:
update_settings_collection(
plot_settings_collection, plot_kwargs, len(sds_converted)
)
for i, sd in enumerate(sds_converted):
plot_settings = plot_settings_collection[i]
cmfs = cast(
MultiSpectralDistributions,
first_item(filter_cmfs(plot_settings.pop("cmfs")).values()),
)
illuminant = cast(
SpectralDistribution,
first_item(filter_illuminants(plot_settings.pop("illuminant")).values()),
)
normalise_sd_colours = plot_settings.pop("normalise_sd_colours")
use_sd_colours = plot_settings.pop("use_sd_colours")
with domain_range_scale("1"):
XYZ = sd_to_XYZ(sd, cmfs, illuminant)
if use_sd_colours:
if normalise_sd_colours:
XYZ /= XYZ[..., 1]
plot_settings["color"] = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1)
ij = cast(tuple[float, float], XYZ_to_ij(XYZ))
axes.plot(ij[0], ij[1], **plot_settings)
if sd.name is not None and annotate_settings_collection[i]["annotate"]:
annotate_settings = annotate_settings_collection[i]
annotate_settings.pop("annotate")
axes.annotate(sd.name, xy=ij, **annotate_settings)
settings.update({"show": True, "bounding_box": bounding_box})
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_sds_in_chromaticity_diagram_CIE1931(
sds: (
Sequence[SpectralDistribution | MultiSpectralDistributions]
| MultiSpectralDistributions
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1931: Callable = (
plot_chromaticity_diagram_CIE1931
),
annotate_kwargs: dict | List[dict] | None = None,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distribution chromaticity coordinates into the
*CIE 1931 Chromaticity Diagram*.
Parameters
----------
sds
Spectral distributions or multi-spectral distributions to
plot. `sds` can be a single :class:`colour.MultiSpectralDistributions`
class instance, a list of :class:`colour.MultiSpectralDistributions`
class instances or a list of :class:`colour.SpectralDistribution` class
instances.
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
chromaticity_diagram_callable_CIE1931
Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
annotate_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.annotate`
definition, used to annotate the resulting chromaticity coordinates
with their respective spectral distribution names. ``annotate_kwargs``
can be either a single dictionary applied to all the arrows with same
settings or a sequence of dictionaries with different settings for each
spectral distribution. The following special keyword arguments can also
be used:
- ``annotate`` : Whether to annotate the spectral distributions.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted spectral distributions.
`plot_kwargs`` can be either a single dictionary applied to all the
plotted spectral distributions with the same settings or a sequence of
dictionaries with different settings for each plotted spectral
distributions. The following special keyword arguments can also be
used:
- ``illuminant`` : The illuminant used to compute the spectral
distributions colours. The default is the illuminant associated
with the whitepoint of the default plotting colourspace.
``illuminant`` can be of any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``cmfs`` : The standard observer colour matching functions used for
computing the spectral distributions colours. ``cmfs`` can be of
any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``normalise_sd_colours`` : Whether to normalise the computed
spectral distributions colours. The default is *True*.
- ``use_sd_colours`` : Whether to use the computed spectral
distributions colours under the plotting colourspace illuminant.
Alternatively, it is possible to use the
:func:`matplotlib.pyplot.plot` definition ``color`` argument with
pre-computed values. The default is *True*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> A = SDS_ILLUMINANTS["A"]
>>> D65 = SDS_ILLUMINANTS["D65"]
>>> plot_sds_in_chromaticity_diagram_CIE1931([A, D65])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_SDS_In_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_sds_in_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_sds_in_chromaticity_diagram(
sds,
cmfs,
chromaticity_diagram_callable_CIE1931,
annotate_kwargs=annotate_kwargs,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]
@override_style()
def plot_sds_in_chromaticity_diagram_CIE1960UCS(
sds: (
Sequence[SpectralDistribution | MultiSpectralDistributions]
| MultiSpectralDistributions
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1960UCS: Callable = (
plot_chromaticity_diagram_CIE1960UCS
),
annotate_kwargs: dict | List[dict] | None = None,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distribution chromaticity coordinates into the
*CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
sds
Spectral distributions or multi-spectral distributions to
plot. `sds` can be a single :class:`colour.MultiSpectralDistributions`
class instance, a list of :class:`colour.MultiSpectralDistributions`
class instances or a list of :class:`colour.SpectralDistribution` class
instances.
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
chromaticity_diagram_callable_CIE1960UCS
Callable responsible for drawing the
*CIE 1960 UCS Chromaticity Diagram*.
annotate_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.annotate`
definition, used to annotate the resulting chromaticity coordinates
with their respective spectral distribution names. ``annotate_kwargs``
can be either a single dictionary applied to all the arrows with same
settings or a sequence of dictionaries with different settings for each
spectral distribution. The following special keyword arguments can also
be used:
- ``annotate`` : Whether to annotate the spectral distributions.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted spectral distributions.
`plot_kwargs`` can be either a single dictionary applied to all the
plotted spectral distributions with the same settings or a sequence of
dictionaries with different settings for each plotted spectral
distributions. The following special keyword arguments can also be
used:
- ``illuminant`` : The illuminant used to compute the spectral
distributions colours. The default is the illuminant associated
with the whitepoint of the default plotting colourspace.
``illuminant`` can be of any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``cmfs`` : The standard observer colour matching functions used for
computing the spectral distributions colours. ``cmfs`` can be of
any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``normalise_sd_colours`` : Whether to normalise the computed
spectral distributions colours. The default is *True*.
- ``use_sd_colours`` : Whether to use the computed spectral
distributions colours under the plotting colourspace illuminant.
Alternatively, it is possible to use the
:func:`matplotlib.pyplot.plot` definition ``color`` argument with
pre-computed values. The default is *True*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> A = SDS_ILLUMINANTS["A"]
>>> D65 = SDS_ILLUMINANTS["D65"]
>>> plot_sds_in_chromaticity_diagram_CIE1960UCS([A, D65])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_SDS_In_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_sds_in_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_sds_in_chromaticity_diagram(
sds,
cmfs,
chromaticity_diagram_callable_CIE1960UCS,
annotate_kwargs=annotate_kwargs,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]
@override_style()
def plot_sds_in_chromaticity_diagram_CIE1976UCS(
sds: (
Sequence[SpectralDistribution | MultiSpectralDistributions]
| MultiSpectralDistributions
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1976UCS: Callable = (
plot_chromaticity_diagram_CIE1976UCS
),
annotate_kwargs: dict | List[dict] | None = None,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distribution chromaticity coordinates into the
*CIE 1976 UCS Chromaticity Diagram*.
Parameters
----------
sds
Spectral distributions or multi-spectral distributions to
plot. `sds` can be a single :class:`colour.MultiSpectralDistributions`
class instance, a list of :class:`colour.MultiSpectralDistributions`
class instances or a list of :class:`colour.SpectralDistribution` class
instances.
cmfs
Standard observer colour matching functions used for computing the
spectral locus boundaries. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
chromaticity_diagram_callable_CIE1976UCS
Callable responsible for drawing the
*CIE 1976 UCS Chromaticity Diagram*.
annotate_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.annotate`
definition, used to annotate the resulting chromaticity coordinates
with their respective spectral distribution names. ``annotate_kwargs``
can be either a single dictionary applied to all the arrows with same
settings or a sequence of dictionaries with different settings for each
spectral distribution. The following special keyword arguments can also
be used:
- ``annotate`` : Whether to annotate the spectral distributions.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted spectral distributions.
`plot_kwargs`` can be either a single dictionary applied to all the
plotted spectral distributions with the same settings or a sequence of
dictionaries with different settings for each plotted spectral
distributions. The following special keyword arguments can also be
used:
- ``illuminant`` : The illuminant used to compute the spectral
distributions colours. The default is the illuminant associated
with the whitepoint of the default plotting colourspace.
``illuminant`` can be of any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``cmfs`` : The standard observer colour matching functions used for
computing the spectral distributions colours. ``cmfs`` can be of
any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
- ``normalise_sd_colours`` : Whether to normalise the computed
spectral distributions colours. The default is *True*.
- ``use_sd_colours`` : Whether to use the computed spectral
distributions colours under the plotting colourspace illuminant.
Alternatively, it is possible to use the
:func:`matplotlib.pyplot.plot` definition ``color`` argument with
pre-computed values. The default is *True*.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> A = SDS_ILLUMINANTS["A"]
>>> D65 = SDS_ILLUMINANTS["D65"]
>>> plot_sds_in_chromaticity_diagram_CIE1976UCS([A, D65])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_SDS_In_Chromaticity_Diagram_CIE1976UCS.png
:align: center
:alt: plot_sds_in_chromaticity_diagram_CIE1976UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1976 UCS"})
return plot_sds_in_chromaticity_diagram(
sds,
cmfs,
chromaticity_diagram_callable_CIE1976UCS,
annotate_kwargs=annotate_kwargs,
plot_kwargs=plot_kwargs,
**settings,
)