"""
Colour Models Plotting
======================
Define the colour models plotting objects:
- :func:`colour.plotting.lines_pointer_gamut`
- :func:`colour.plotting.\
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.\
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS`
- :func:`colour.plotting.\
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS`
- :func:`colour.plotting.\
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.\
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS`
- :func:`colour.plotting.\
plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS`
- :func:`colour.plotting.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS`
- :func:`colour.plotting.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS`
- :func:`colour.plotting.plot_single_cctf`
- :func:`colour.plotting.plot_multi_cctfs`
- :func:`colour.plotting.plot_constant_hue_loci`
References
----------
- :cite:`Ebner1998` : Ebner, F., & Fairchild, M. D. (1998). Finding constant
hue surfaces in color space. In G. B. Beretta & R. Eschbach (Eds.), Proc.
SPIE 3300, Color Imaging: Device-Independent Color, Color Hardcopy, and
Graphic Arts III, (2 January 1998) (pp. 107-117). doi:10.1117/12.298269
- :cite:`Hung1995` : Hung, P.-C., & Berns, R. S. (1995). Determination of
constant Hue Loci for a CRT gamut and their predictions using color
appearance spaces. Color Research & Application, 20(5), 285-295.
doi:10.1002/col.5080200506
- :cite:`Mansencal2019` : Mansencal, T. (2019). Colour - Datasets.
doi:10.5281/zenodo.3362520
"""
from __future__ import annotations
import typing
import numpy as np
import scipy.optimize
from matplotlib.collections import LineCollection
from matplotlib.patches import Ellipse
from matplotlib.path import Path
from colour.adaptation import chromatic_adaptation_VonKries
from colour.algebra import normalise_maximum
from colour.colorimetry import MultiSpectralDistributions
from colour.constants import DTYPE_FLOAT_DEFAULT, EPSILON
from colour.geometry import (
ellipse_coefficients_canonical_form,
ellipse_fitting,
point_at_angle_on_ellipse,
)
from colour.graph import convert
if typing.TYPE_CHECKING:
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from colour.hints import (
Any,
ArrayLike,
Callable,
Dict,
Literal,
LiteralColourspaceModel,
LiteralRGBColourspace,
NDArray,
NDArrayFloat,
Sequence,
Tuple,
)
from colour.hints import List, cast
from colour.models import LCHab_to_Lab # pyright: ignore
from colour.models import (
CCS_ILLUMINANT_POINTER_GAMUT,
CCS_POINTER_GAMUT_BOUNDARY,
CCTF_DECODINGS,
CCTF_ENCODINGS,
COLOURSPACE_MODELS_AXIS_LABELS,
COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE,
DATA_MACADAM_1942_ELLIPSES,
DATA_POINTER_GAMUT_VOLUME,
Lab_to_XYZ,
RGB_Colourspace,
RGB_to_RGB,
RGB_to_XYZ,
XYZ_to_RGB,
XYZ_to_xy,
xy_to_XYZ,
)
from colour.plotting import (
CONSTANTS_COLOUR_STYLE,
METHODS_CHROMATICITY_DIAGRAM,
XYZ_to_plotting_colourspace,
artist,
colour_cycle,
colour_style,
filter_cmfs,
filter_passthrough,
filter_RGB_colourspaces,
override_style,
plot_chromaticity_diagram_CIE1931,
plot_chromaticity_diagram_CIE1960UCS,
plot_chromaticity_diagram_CIE1976UCS,
plot_multi_functions,
render,
update_settings_collection,
)
from colour.plotting.diagrams import plot_chromaticity_diagram
from colour.utilities import (
CanonicalMapping,
as_array,
as_float_array,
as_int_array,
domain_range_scale,
first_item,
optional,
tsplit,
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__ = [
"COLOURSPACE_MODELS_AXIS_ORDER",
"colourspace_model_axis_reorder",
"lines_pointer_gamut",
"plot_pointer_gamut",
"plot_RGB_colourspaces_in_chromaticity_diagram",
"plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931",
"plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS",
"plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS",
"plot_RGB_chromaticities_in_chromaticity_diagram",
"plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931",
"plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS",
"plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS",
"ellipses_MacAdam1942",
"plot_ellipses_MacAdam1942_in_chromaticity_diagram",
"plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931",
"plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS",
"plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS",
"plot_single_cctf",
"plot_multi_cctfs",
"plot_constant_hue_loci",
]
COLOURSPACE_MODELS_AXIS_ORDER: CanonicalMapping = CanonicalMapping(
{
"CAM02LCD": (1, 2, 0),
"CAM02SCD": (1, 2, 0),
"CAM02UCS": (1, 2, 0),
"CAM16LCD": (1, 2, 0),
"CAM16SCD": (1, 2, 0),
"CAM16UCS": (1, 2, 0),
"CIE 1931": (0, 1, 2),
"CIE 1960 UCS": (0, 1, 2),
"CIE 1976 UCS": (0, 1, 2),
"CIE LCHab": (1, 2, 0),
"CIE LCHuv": (1, 2, 0),
"CIE Lab": (1, 2, 0),
"CIE Luv": (1, 2, 0),
"CIE UCS": (0, 1, 2),
"CIE UVW": (1, 2, 0),
"CIE XYZ": (0, 1, 2),
"CIE xyY": (0, 1, 2),
"DIN99": (1, 2, 0),
"HCL": (0, 1, 2),
"HSL": (0, 1, 2),
"HSV": (0, 1, 2),
"Hunter Lab": (1, 2, 0),
"Hunter Rdab": (1, 2, 0),
"ICaCb": (1, 2, 0),
"ICtCp": (1, 2, 0),
"IHLS": (0, 2, 1),
"IPT Ragoo 2021": (1, 2, 0),
"IPT": (1, 2, 0),
"IgPgTg": (1, 2, 0),
"Jzazbz": (1, 2, 0),
"OSA UCS": (1, 2, 0),
"Oklab": (1, 2, 0),
"RGB": (0, 1, 2),
"YCbCr": (1, 2, 0),
"YCoCg": (1, 2, 0),
"Yrg": (1, 2, 0),
"hdr-CIELAB": (1, 2, 0),
"hdr-IPT": (1, 2, 0),
}
)
"""Colourspace models axis order."""
[docs]
def colourspace_model_axis_reorder(
a: ArrayLike,
model: LiteralColourspaceModel | str,
direction: Literal["Forward", "Inverse"] | str = "Forward",
) -> NDArrayFloat:
"""
Reorder the axes of given colourspace model :math:`a` array according to
the most common volume plotting axes order.
Parameters
----------
a
Colourspace model :math:`a` array.
model
Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
the list of supported colourspace models.
direction
Reordering direction.
Returns
-------
:class:`numpy.ndarray`
Reordered colourspace model :math:`a` array.
Examples
--------
>>> a = np.array([0, 1, 2])
>>> colourspace_model_axis_reorder(a, "CIE Lab")
array([ 1., 2., 0.])
>>> colourspace_model_axis_reorder(a, "IPT")
array([ 1., 2., 0.])
>>> colourspace_model_axis_reorder(a, "OSA UCS")
array([ 1., 2., 0.])
>>> b = np.array([1, 2, 0])
>>> colourspace_model_axis_reorder(b, "OSA UCS", "Inverse")
array([ 0., 1., 2.])
"""
a = as_float_array(a)
model = validate_method(
model,
tuple(COLOURSPACE_MODELS_AXIS_ORDER),
'"{0}" model is invalid, it must be one of {1}!',
)
direction = validate_method(
direction,
("Forward", "Inverse"),
'"{0}" direction is invalid, it must be one of {1}!',
)
order = COLOURSPACE_MODELS_AXIS_ORDER.get(model, (0, 1, 2))
if direction == "forward":
indexes = (order[0], order[1], order[2])
else:
indexes = (order.index(0), order.index(1), order.index(2))
return a[..., indexes]
[docs]
def lines_pointer_gamut(
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
) -> tuple[NDArray, NDArray]:
"""
Return the *Pointer's Gamut* line vertices, i.e., positions, normals and
colours, according to given method.
Parameters
----------
method
*Chromaticity Diagram* method.
Returns
-------
:class:`tuple`
Tuple of *Pointer's Gamut* boundary and volume vertices.
Examples
--------
>>> lines = lines_pointer_gamut()
>>> 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,))])
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint
XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
ij_to_XYZ = METHODS_CHROMATICITY_DIAGRAM[method]["ij_to_XYZ"]
XYZ = xy_to_XYZ(CCS_POINTER_GAMUT_BOUNDARY)
XYZ = chromatic_adaptation_VonKries(
XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
)
ij_b = XYZ_to_ij(XYZ)
ij_b = np.vstack([ij_b, ij_b[0]])
colours_b = normalise_maximum(
XYZ_to_plotting_colourspace(ij_to_XYZ(ij_b, illuminant), illuminant),
axis=-1,
)
lines_b = zeros(
ij_b.shape[0],
[
("position", DTYPE_FLOAT_DEFAULT, 2),
("normal", DTYPE_FLOAT_DEFAULT, 2),
("colour", DTYPE_FLOAT_DEFAULT, 3),
], # pyright: ignore
)
lines_b["position"] = ij_b
lines_b["colour"] = colours_b
XYZ = Lab_to_XYZ(
LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT
)
XYZ = chromatic_adaptation_VonKries(
XYZ, xy_to_XYZ(CCS_ILLUMINANT_POINTER_GAMUT), xy_to_XYZ(illuminant)
)
ij_v = XYZ_to_ij(XYZ)
colours_v = normalise_maximum(
XYZ_to_plotting_colourspace(ij_to_XYZ(ij_v, illuminant), illuminant),
axis=-1,
)
lines_v = zeros(
ij_v.shape[0],
[
("position", DTYPE_FLOAT_DEFAULT, 2),
("normal", DTYPE_FLOAT_DEFAULT, 2),
("colour", DTYPE_FLOAT_DEFAULT, 3),
], # pyright: ignore
)
lines_v["position"] = ij_v
lines_v["colour"] = colours_v
return lines_b, lines_v
[docs]
@override_style()
def plot_pointer_gamut(
pointer_gamut_colours: ArrayLike | str | None = None,
pointer_gamut_opacity: float = 1,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot *Pointer's Gamut* according to given method.
Parameters
----------
pointer_gamut_colours
Colours of the *Pointer's Gamut*.
pointer_gamut_opacity
Opacity of the *Pointer's Gamut*.
method
Plotting 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_pointer_gamut(pointer_gamut_colours="RGB") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Pointer_Gamut.png
:align: center
:alt: plot_pointer_gamut
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
pointer_gamut_colours = optional(
pointer_gamut_colours, CONSTANTS_COLOUR_STYLE.colour.dark
)
use_RGB_colours = str(pointer_gamut_colours).upper() == "RGB"
pointer_gamut_opacity = optional(
pointer_gamut_opacity, CONSTANTS_COLOUR_STYLE.opacity.high
)
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
lines_b, lines_v = lines_pointer_gamut(method)
axes.add_collection(
LineCollection(
np.reshape(
np.concatenate(
[lines_b["position"][:-1], lines_b["position"][1:]],
axis=1, # pyright: ignore
),
(-1, 2, 2),
),
colors=(lines_b["colour"] if use_RGB_colours else pointer_gamut_colours),
alpha=pointer_gamut_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
)
)
scatter_settings = {
"alpha": pointer_gamut_opacity / 2,
"c": lines_v["colour"] if use_RGB_colours else pointer_gamut_colours,
"marker": "+",
"zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
}
axes.scatter(
lines_v["position"][..., 0],
lines_v["position"][..., 1],
**scatter_settings,
)
settings.update({"axes": axes})
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_RGB_colourspaces_in_chromaticity_diagram(
colourspaces: (
RGB_Colourspace
| LiteralRGBColourspace
| str
| Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
),
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",
show_whitepoints: bool = True,
show_pointer_gamut: bool = False,
chromatically_adapt: bool = False,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspaces in the *Chromaticity Diagram* according
to given method.
Parameters
----------
colourspaces
*RGB* colourspaces to plot. ``colourspaces`` elements
can be of any type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
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.
show_whitepoints
Whether to display the *RGB* colourspaces whitepoints.
show_pointer_gamut
Whether to display the *Pointer's Gamut*.
chromatically_adapt
Whether to chromatically adapt the *RGB* colourspaces given in
``colourspaces`` to the whitepoint of the default plotting colourspace.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted *RGB* colourspaces.
``plot_kwargs`` can be either a single dictionary applied to all the
plotted *RGB* colourspaces with the same settings or a sequence of
dictionaries with different settings for each plotted *RGB*
colourspace.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.plot_pointer_gamut`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_kwargs = [
... {"color": "r"},
... {"linestyle": "dashed"},
... {"marker": None},
... ]
>>> plot_RGB_colourspaces_in_chromaticity_diagram(
... ["ITU-R BT.709", "ACEScg", "S-Gamut"], plot_kwargs=plot_kwargs
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png
:align: center
:alt: plot_RGB_colourspaces_in_chromaticity_diagram
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
colourspaces = cast(
List[RGB_Colourspace],
list(filter_RGB_colourspaces(colourspaces).values()),
) # pyright: ignore
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
title = (
f"{', '.join([colourspace.name for colourspace in colourspaces])}\n"
f"{cmfs.name} - {method.upper()} Chromaticity Diagram"
)
settings = {"axes": axes, "title": title, "method": method}
settings.update(kwargs)
settings["show"] = False
chromaticity_diagram_callable(**settings)
if show_pointer_gamut:
settings = {"axes": axes, "method": method}
settings.update(kwargs)
settings["show"] = False
plot_pointer_gamut(**settings)
xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
if method == "cie 1931":
x_limit_min, x_limit_max = [-0.1], [0.9]
y_limit_min, y_limit_max = [-0.1], [0.9]
elif method == "cie 1960 ucs":
x_limit_min, x_limit_max = [-0.1], [0.7]
y_limit_min, y_limit_max = [-0.2], [0.6]
elif method == "cie 1976 ucs":
x_limit_min, x_limit_max = [-0.1], [0.7]
y_limit_min, y_limit_max = [-0.1], [0.7]
settings = {"colour_cycle_count": len(colourspaces)}
settings.update(kwargs)
cycle = colour_cycle(**settings)
plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
plot_settings_collection = [
{
"label": f"{colourspace.name}",
"marker": "o",
"color": next(cycle)[:3],
"zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
}
for colourspace in colourspaces
]
if plot_kwargs is not None:
update_settings_collection(
plot_settings_collection, plot_kwargs, len(colourspaces)
)
for i, colourspace in enumerate(colourspaces):
plot_settings = plot_settings_collection[i]
if chromatically_adapt and not np.array_equal(
colourspace.whitepoint, plotting_colourspace.whitepoint
):
colourspace = colourspace.chromatically_adapt( # noqa: PLW2901
plotting_colourspace.whitepoint,
plotting_colourspace.whitepoint_name,
)
# RGB colourspaces such as *ACES2065-1* have primaries with
# chromaticity coordinates set to 0 thus we prevent nan from being
# yield by zero division in later colour transformations.
P = np.where(
colourspace.primaries == 0,
EPSILON,
colourspace.primaries,
)
P = xy_to_ij(P)
W = xy_to_ij(colourspace.whitepoint)
P_p = np.vstack([P, P[0]])
axes.plot(P_p[..., 0], P_p[..., 1], **plot_settings)
if show_whitepoints:
plot_settings["marker"] = "o"
plot_settings.pop("label")
W_p = np.vstack([W, W])
axes.plot(W_p[..., 0], W_p[..., 1], **plot_settings)
x_limit_min.append(cast(float, np.amin(P[..., 0]) - 0.1))
y_limit_min.append(cast(float, np.amin(P[..., 1]) - 0.1))
x_limit_max.append(cast(float, np.amax(P[..., 0]) + 0.1))
y_limit_max.append(cast(float, np.amax(P[..., 1]) + 0.1))
bounding_box = (
min(x_limit_min),
max(x_limit_max),
min(y_limit_min),
max(y_limit_max),
)
settings.update(
{
"show": True,
"legend": True,
"bounding_box": bounding_box,
}
)
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
colourspaces: (
RGB_Colourspace
| LiteralRGBColourspace
| str
| Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1931: Callable = (
plot_chromaticity_diagram_CIE1931
),
show_whitepoints: bool = True,
show_pointer_gamut: bool = False,
chromatically_adapt: bool = False,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspaces in the *CIE 1931 Chromaticity Diagram*.
Parameters
----------
colourspaces
*RGB* colourspaces to plot. ``colourspaces`` elements
can be of any type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
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*.
show_whitepoints
Whether to display the *RGB* colourspaces whitepoints.
show_pointer_gamut
Whether to display the *Pointer's Gamut*.
chromatically_adapt
Whether to chromatically adapt the *RGB* colourspaces given in
``colourspaces`` to the whitepoint of the default plotting colourspace.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted *RGB* colourspaces.
``plot_kwargs`` can be either a single dictionary applied to all the
plotted *RGB* colourspaces with the same settings or a sequence of
dictionaries with different settings for each plotted *RGB*
colourspace.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.plot_pointer_gamut`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
... ["ITU-R BT.709", "ACEScg", "S-Gamut"]
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_RGB_colourspaces_in_chromaticity_diagram(
colourspaces,
cmfs,
chromaticity_diagram_callable_CIE1931,
show_whitepoints=show_whitepoints,
show_pointer_gamut=show_pointer_gamut,
chromatically_adapt=chromatically_adapt,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]
@override_style()
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
colourspaces: (
RGB_Colourspace
| LiteralRGBColourspace
| str
| Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1960UCS: Callable = (
plot_chromaticity_diagram_CIE1960UCS
),
show_whitepoints: bool = True,
show_pointer_gamut: bool = False,
chromatically_adapt: bool = False,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspaces in the *CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
colourspaces
*RGB* colourspaces to plot. ``colourspaces`` elements
can be of any type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
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*.
show_whitepoints
Whether to display the *RGB* colourspaces whitepoints.
show_pointer_gamut
Whether to display the *Pointer's Gamut*.
chromatically_adapt
Whether to chromatically adapt the *RGB* colourspaces given in
``colourspaces`` to the whitepoint of the default plotting colourspace.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted *RGB* colourspaces.
``plot_kwargs`` can be either a single dictionary applied to all the
plotted *RGB* colourspaces with the same settings or a sequence of
dictionaries with different settings for each plotted *RGB*
colourspace.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.plot_pointer_gamut`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
... ["ITU-R BT.709", "ACEScg", "S-Gamut"]
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_RGB_colourspaces_in_chromaticity_diagram(
colourspaces,
cmfs,
chromaticity_diagram_callable_CIE1960UCS,
show_whitepoints=show_whitepoints,
show_pointer_gamut=show_pointer_gamut,
chromatically_adapt=chromatically_adapt,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]
@override_style()
def plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
colourspaces: (
RGB_Colourspace
| LiteralRGBColourspace
| str
| Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
chromaticity_diagram_callable_CIE1976UCS: Callable = (
plot_chromaticity_diagram_CIE1976UCS
),
show_whitepoints: bool = True,
show_pointer_gamut: bool = False,
chromatically_adapt: bool = False,
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspaces in the *CIE 1976 UCS Chromaticity Diagram*.
Parameters
----------
colourspaces
*RGB* colourspaces to plot. ``colourspaces`` elements
can be of any type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
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*.
show_whitepoints
Whether to display the *RGB* colourspaces whitepoints.
show_pointer_gamut
Whether to display the *Pointer's Gamut*.
chromatically_adapt
Whether to chromatically adapt the *RGB* colourspaces given in
``colourspaces`` to the whitepoint of the default plotting colourspace.
plot_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
used to control the style of the plotted *RGB* colourspaces.
``plot_kwargs`` can be either a single dictionary applied to all the
plotted *RGB* colourspaces with the same settings or a sequence of
dictionaries with different settings for each plotted *RGB*
colourspace.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.plot_pointer_gamut`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
... ["ITU-R BT.709", "ACEScg", "S-Gamut"]
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1976UCS.png
:align: center
:alt: plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1976 UCS"})
return plot_RGB_colourspaces_in_chromaticity_diagram(
colourspaces,
cmfs,
chromaticity_diagram_callable_CIE1976UCS,
show_whitepoints=show_whitepoints,
show_pointer_gamut=show_pointer_gamut,
chromatically_adapt=chromatically_adapt,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]
@override_style()
def plot_RGB_chromaticities_in_chromaticity_diagram(
RGB: ArrayLike,
colourspace: (
RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
) = "sRGB",
chromaticity_diagram_callable: Callable = (
plot_RGB_colourspaces_in_chromaticity_diagram
),
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
scatter_kwargs: dict | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspace array in the *Chromaticity Diagram* according
to given method.
Parameters
----------
RGB
*RGB* colourspace array.
colourspace
*RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
chromaticity_diagram_callable
Callable responsible for drawing the *Chromaticity Diagram*.
method
*Chromaticity Diagram* method.
scatter_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
The following special keyword arguments can also be used:
- ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
as given by the ``RGB`` argument.
- ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
*False*, the encoding colour component transfer function /
opto-electronic transfer function is not applied when encoding the
samples to the plotting space.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> RGB = np.random.random((128, 128, 3))
>>> plot_RGB_chromaticities_in_chromaticity_diagram(RGB, "ITU-R BT.709")
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Chromaticities_In_Chromaticity_Diagram.png
:align: center
:alt: plot_RGB_chromaticities_in_chromaticity_diagram
"""
RGB = np.reshape(as_float_array(RGB)[..., :3], (-1, 3))
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)
scatter_settings = {
"s": 40,
"c": "RGB",
"marker": "o",
"alpha": 0.85,
"zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
"apply_cctf_encoding": True,
}
if scatter_kwargs is not None:
scatter_settings.update(scatter_kwargs)
settings = dict(kwargs)
settings.update({"axes": axes, "show": False})
colourspace = cast(
RGB_Colourspace,
first_item(filter_RGB_colourspaces(colourspace).values()),
)
settings["colourspaces"] = [colourspace, *settings.get("colourspaces", [])]
chromaticity_diagram_callable(**settings)
use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
apply_cctf_encoding = scatter_settings.pop("apply_cctf_encoding")
if use_RGB_colours:
RGB = RGB[RGB[:, 1].argsort()]
scatter_settings["c"] = np.clip(
np.reshape(
RGB_to_RGB(
RGB,
colourspace,
CONSTANTS_COLOUR_STYLE.colour.colourspace,
apply_cctf_encoding=apply_cctf_encoding,
),
(-1, 3),
),
0,
1,
)
XYZ = RGB_to_XYZ(RGB, colourspace)
XYZ_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["XYZ_to_ij"]
ij = XYZ_to_ij(XYZ, colourspace.whitepoint)
axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings)
settings.update({"show": True})
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
RGB: ArrayLike,
colourspace: (
RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
) = "sRGB",
chromaticity_diagram_callable_CIE1931: Callable = (
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931
),
scatter_kwargs: dict | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspace array in the *CIE 1931 Chromaticity Diagram*.
Parameters
----------
RGB
*RGB* colourspace array.
colourspace
*RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
chromaticity_diagram_callable_CIE1931
Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
scatter_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
The following special keyword arguments can also be used:
- ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
as given by the ``RGB`` argument.
- ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
*False*, the encoding colour component transfer function /
opto-electronic transfer function is not applied when encoding the
samples to the plotting space.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> RGB = np.random.random((128, 128, 3))
>>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
... RGB, "ITU-R BT.709"
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_RGB_chromaticities_in_chromaticity_diagram(
RGB,
colourspace,
chromaticity_diagram_callable_CIE1931,
scatter_kwargs=scatter_kwargs,
**settings,
)
[docs]
@override_style()
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
RGB: ArrayLike,
colourspace: (
RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
) = "sRGB",
chromaticity_diagram_callable_CIE1960UCS: Callable = (
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS
),
scatter_kwargs: dict | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspace array in the
*CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
RGB
*RGB* colourspace array.
colourspace
*RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
chromaticity_diagram_callable_CIE1960UCS
Callable responsible for drawing the
*CIE 1960 UCS Chromaticity Diagram*.
scatter_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
The following special keyword arguments can also be used:
- ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
as given by the ``RGB`` argument.
- ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
*False*, the encoding colour component transfer function /
opto-electronic transfer function is not applied when encoding the
samples to the plotting space.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> RGB = np.random.random((128, 128, 3))
>>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
... RGB, "ITU-R BT.709"
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_RGB_chromaticities_in_chromaticity_diagram(
RGB,
colourspace,
chromaticity_diagram_callable_CIE1960UCS,
scatter_kwargs=scatter_kwargs,
**settings,
)
[docs]
@override_style()
def plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
RGB: ArrayLike,
colourspace: (
RGB_Colourspace | str | Sequence[RGB_Colourspace | LiteralRGBColourspace | str]
) = "sRGB",
chromaticity_diagram_callable_CIE1976UCS: Callable = (
plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS
),
scatter_kwargs: dict | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *RGB* colourspace array in the
*CIE 1976 UCS Chromaticity Diagram*.
Parameters
----------
RGB
*RGB* colourspace array.
colourspace
*RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
type or form supported by the
:func:`colour.plotting.common.filter_RGB_colourspaces` definition.
chromaticity_diagram_callable_CIE1976UCS
Callable responsible for drawing the
*CIE 1976 UCS Chromaticity Diagram*.
scatter_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
The following special keyword arguments can also be used:
- ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
as given by the ``RGB`` argument.
- ``apply_cctf_encoding`` : If ``apply_cctf_encoding`` is set to
*False*, the encoding colour component transfer function /
opto-electronic transfer function is not applied when encoding the
samples to the plotting space.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_RGB_colourspaces_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> RGB = np.random.random((128, 128, 3))
>>> plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
... RGB, "ITU-R BT.709"
... )
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_\
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_CIE1976UCS.png
:align: center
:alt: plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1976 UCS"})
return plot_RGB_chromaticities_in_chromaticity_diagram(
RGB,
colourspace,
chromaticity_diagram_callable_CIE1976UCS,
scatter_kwargs=scatter_kwargs,
**settings,
)
def ellipses_MacAdam1942(
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
) -> List[NDArrayFloat]:
"""
Return *MacAdam (1942) Ellipses (Observer PGN)* coefficients according to
given method.
Parameters
----------
method
Computation method.
Returns
-------
:class:`list`
*MacAdam (1942) Ellipses (Observer PGN)* coefficients.
Examples
--------
>>> ellipses_MacAdam1942()[0] # doctest: +SKIP
array([ 1.60000000e-01, 5.70000000e-02, 5.00000023e-03,
1.56666660e-02, -2.77000015e+01])
"""
method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"))
xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"]
x, y, _a, _b, _theta, a, b, theta = tsplit(DATA_MACADAM_1942_ELLIPSES)
ellipses_coefficients = []
for i in range(len(theta)):
xy = point_at_angle_on_ellipse(
np.linspace(0, 360, 36),
[x[i], y[i], a[i] / 60, b[i] / 60, theta[i]],
)
ij = xy_to_ij(xy)
ellipses_coefficients.append(
ellipse_coefficients_canonical_form(ellipse_fitting(ij))
)
return ellipses_coefficients
[docs]
@override_style()
def plot_ellipses_MacAdam1942_in_chromaticity_diagram(
chromaticity_diagram_callable: Callable = plot_chromaticity_diagram,
method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931",
chromaticity_diagram_clipping: bool = False,
ellipse_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
*Chromaticity Diagram* according to given method.
Parameters
----------
chromaticity_diagram_callable
Callable responsible for drawing the *Chromaticity Diagram*.
method
*Chromaticity Diagram* method.
chromaticity_diagram_clipping
Whether to clip the *Chromaticity Diagram* colours with the ellipses.
ellipse_kwargs
Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
be either a single dictionary applied to all the ellipses with same
settings or a sequence of dictionaries with different settings for each
ellipse.
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_ellipses_MacAdam1942_in_chromaticity_diagram()
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/\
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png
:align: center
:alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram
"""
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
settings = dict(kwargs)
settings.update({"axes": axes, "show": False})
ellipses_coefficients = ellipses_MacAdam1942(method=method)
if chromaticity_diagram_clipping:
diagram_clipping_path_x = []
diagram_clipping_path_y = []
for coefficients in ellipses_coefficients:
coefficients = np.copy(coefficients) # noqa: PLW2901
coefficients[2:4] /= 2
x, y = tsplit(
point_at_angle_on_ellipse(
np.linspace(0, 360, 36),
coefficients,
)
)
diagram_clipping_path_x.append(x)
diagram_clipping_path_y.append(y)
diagram_clipping_path = np.rollaxis(
np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3
)
diagram_clipping_path = Path.make_compound_path_from_polys(
diagram_clipping_path
).vertices
settings.update({"diagram_clipping_path": diagram_clipping_path})
chromaticity_diagram_callable(**settings)
ellipse_settings_collection = [
{
"color": CONSTANTS_COLOUR_STYLE.colour.cycle[4],
"alpha": 0.4,
"linewidth": colour_style()["lines.linewidth"],
"zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
}
for _ellipses_coefficient in ellipses_coefficients
]
if ellipse_kwargs is not None:
update_settings_collection(
ellipse_settings_collection,
ellipse_kwargs,
len(ellipses_coefficients),
)
for i, coefficients in enumerate(ellipses_coefficients):
x_c, y_c, a_a, a_b, theta_e = coefficients
ellipse = Ellipse(
(x_c, y_c),
a_a,
a_b,
angle=theta_e,
**ellipse_settings_collection[i],
)
axes.add_artist(ellipse)
settings.update({"show": True})
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931(
chromaticity_diagram_callable_CIE1931: Callable = (
plot_chromaticity_diagram_CIE1931
),
chromaticity_diagram_clipping: bool = False,
ellipse_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
*CIE 1931 Chromaticity Diagram*.
Parameters
----------
chromaticity_diagram_callable_CIE1931
Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.
chromaticity_diagram_clipping
Whether to clip the *CIE 1931 Chromaticity Diagram* colours with the
ellipses.
ellipse_kwargs
Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
be either a single dictionary applied to all the ellipses with same
settings or a sequence of dictionaries with different settings for each
ellipse.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931()
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/\
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
chromaticity_diagram_callable_CIE1931,
chromaticity_diagram_clipping=chromaticity_diagram_clipping,
ellipse_kwargs=ellipse_kwargs,
**settings,
)
[docs]
@override_style()
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS(
chromaticity_diagram_callable_CIE1960UCS: Callable = (
plot_chromaticity_diagram_CIE1960UCS
),
chromaticity_diagram_clipping: bool = False,
ellipse_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
*CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
chromaticity_diagram_callable_CIE1960UCS
Callable responsible for drawing the
*CIE 1960 UCS Chromaticity Diagram*.
chromaticity_diagram_clipping
Whether to clip the *CIE 1960 UCS Chromaticity Diagram* colours with
the ellipses.
ellipse_kwargs
Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
be either a single dictionary applied to all the ellipses with same
settings or a sequence of dictionaries with different settings for each
ellipse.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS()
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/\
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
chromaticity_diagram_callable_CIE1960UCS,
chromaticity_diagram_clipping=chromaticity_diagram_clipping,
ellipse_kwargs=ellipse_kwargs,
**settings,
)
[docs]
@override_style()
def plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS(
chromaticity_diagram_callable_CIE1976UCS: Callable = (
plot_chromaticity_diagram_CIE1976UCS
),
chromaticity_diagram_clipping: bool = False,
ellipse_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot *MacAdam (1942) Ellipses (Observer PGN)* in the
*CIE 1976 UCS Chromaticity Diagram*.
Parameters
----------
chromaticity_diagram_callable_CIE1976UCS
Callable responsible for drawing the
*CIE 1976 UCS Chromaticity Diagram*.
chromaticity_diagram_clipping
Whether to clip the *CIE 1976 UCS Chromaticity Diagram* colours with
the ellipses.
ellipse_kwargs
Parameters for the :class:`Ellipse` class, ``ellipse_kwargs`` can
be either a single dictionary applied to all the ellipses with same
settings or a sequence of dictionaries with different settings for each
ellipse.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.models.\
plot_ellipses_MacAdam1942_in_chromaticity_diagram`},
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS()
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/\
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram_CIE1976UCS.png
:align: center
:alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1976 UCS"})
return plot_ellipses_MacAdam1942_in_chromaticity_diagram(
chromaticity_diagram_callable_CIE1976UCS,
chromaticity_diagram_clipping=chromaticity_diagram_clipping,
ellipse_kwargs=ellipse_kwargs,
**settings,
)
[docs]
@override_style()
def plot_single_cctf(
cctf: Callable | str, cctf_decoding: bool = False, **kwargs: Any
) -> Tuple[Figure, Axes]:
"""
Plot given colourspace colour component transfer function.
Parameters
----------
cctf
Colour component transfer function to plot. ``function`` can be of any
type or form supported by the
:func:`colour.plotting.common.filter_passthrough` definition.
cctf_decoding
Plot the decoding colour component transfer function instead.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_multi_functions`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_single_cctf("ITU-R BT.709") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_CCTF.png
:align: center
:alt: plot_single_cctf
"""
settings: Dict[str, Any] = {
"title": f"{cctf} - {'Decoding' if cctf_decoding else 'Encoding'} CCTF"
}
settings.update(kwargs)
return plot_multi_cctfs([cctf], cctf_decoding, **settings)
[docs]
@override_style()
def plot_multi_cctfs(
cctfs: Callable | str | Sequence[Callable | str],
cctf_decoding: bool = False,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given colour component transfer functions.
Parameters
----------
cctfs
Colour component transfer function to plot. ``cctfs`` elements can be
of any type or form supported by the
:func:`colour.plotting.common.filter_passthrough` definition.
cctf_decoding
Plot the decoding colour component transfer function instead.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_multi_functions`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_multi_cctfs(["ITU-R BT.709", "sRGB"]) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_CCTFs.png
:align: center
:alt: plot_multi_cctfs
"""
cctfs_filtered = filter_passthrough(
CCTF_DECODINGS if cctf_decoding else CCTF_ENCODINGS, cctfs
)
mode = "Decoding" if cctf_decoding else "Encoding"
title = f"{', '.join(list(cctfs_filtered))} - {mode} CCTFs"
settings: Dict[str, Any] = {
"bounding_box": (0, 1, 0, 1),
"legend": True,
"title": title,
"x_label": "Signal Value" if cctf_decoding else "Tristimulus Value",
"y_label": "Tristimulus Value" if cctf_decoding else "Signal Value",
}
settings.update(kwargs)
with domain_range_scale("1"):
return plot_multi_functions(cctfs_filtered, **settings)
[docs]
@override_style()
def plot_constant_hue_loci(
data: ArrayLike,
model: LiteralColourspaceModel | str = "CIE Lab",
scatter_kwargs: dict | None = None,
convert_kwargs: dict | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given constant hue loci colour matches data such as that from
:cite:`Hung1995` or :cite:`Ebner1998` that are easily loaded with
`Colour - Datasets <https://github.com/colour-science/colour-datasets>`__.
Parameters
----------
data
Constant hue loci colour matches data expected to be an `ArrayLike` as
follows::
[
('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
{metadata}),
('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
{metadata}),
('name', XYZ_r, XYZ_cr, (XYZ_ct, XYZ_ct, XYZ_ct, ...), \
{metadata}),
...
]
where ``name`` is the hue angle or name, ``XYZ_r`` the *CIE XYZ*
tristimulus values of the reference illuminant, ``XYZ_cr`` the
*CIE XYZ* tristimulus values of the reference colour under the
reference illuminant, ``XYZ_ct`` the *CIE XYZ* tristimulus values of
the colour matches under the reference illuminant and ``metadata`` the
dataset metadata.
model
Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
the list of supported colourspace models.
scatter_kwargs
Keyword arguments for the :func:`matplotlib.pyplot.scatter` definition.
The following special keyword arguments can also be used:
- ``c`` : If ``c`` is set to *RGB*, the scatter will use the colours
as given by the ``RGB`` argument.
convert_kwargs
Keyword arguments for the :func:`colour.convert` definition.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_multi_functions`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
References
----------
:cite:`Ebner1998`, :cite:`Hung1995`, :cite:`Mansencal2019`
Examples
--------
>>> data = [
... [
... None,
... np.array([0.95010000, 1.00000000, 1.08810000]),
... np.array([0.40920000, 0.28120000, 0.30600000]),
... np.array(
... [
... [0.02495100, 0.01908600, 0.02032900],
... [0.10944300, 0.06235900, 0.06788100],
... [0.27186500, 0.18418700, 0.19565300],
... [0.48898900, 0.40749400, 0.44854600],
... ]
... ),
... None,
... ],
... [
... None,
... np.array([0.95010000, 1.00000000, 1.08810000]),
... np.array([0.30760000, 0.48280000, 0.42770000]),
... np.array(
... [
... [0.02108000, 0.02989100, 0.02790400],
... [0.06194700, 0.11251000, 0.09334400],
... [0.15255800, 0.28123300, 0.23234900],
... [0.34157700, 0.56681300, 0.47035300],
... ]
... ),
... None,
... ],
... [
... None,
... np.array([0.95010000, 1.00000000, 1.08810000]),
... np.array([0.39530000, 0.28120000, 0.18450000]),
... np.array(
... [
... [0.02436400, 0.01908600, 0.01468800],
... [0.10331200, 0.06235900, 0.02854600],
... [0.26311900, 0.18418700, 0.12109700],
... [0.43158700, 0.40749400, 0.39008600],
... ]
... ),
... None,
... ],
... [
... None,
... np.array([0.95010000, 1.00000000, 1.08810000]),
... np.array([0.20510000, 0.18420000, 0.57130000]),
... np.array(
... [
... [0.03039800, 0.02989100, 0.06123300],
... [0.08870000, 0.08498400, 0.21843500],
... [0.18405800, 0.18418700, 0.40111400],
... [0.32550100, 0.34047200, 0.50296900],
... [0.53826100, 0.56681300, 0.80010400],
... ]
... ),
... None,
... ],
... [
... None,
... np.array([0.95010000, 1.00000000, 1.08810000]),
... np.array([0.35770000, 0.28120000, 0.11250000]),
... np.array(
... [
... [0.03678100, 0.02989100, 0.01481100],
... [0.17127700, 0.11251000, 0.01229900],
... [0.30080900, 0.28123300, 0.21229800],
... [0.52976000, 0.40749400, 0.11720000],
... ]
... ),
... None,
... ],
... ]
>>> plot_constant_hue_loci(data, "CIE Lab") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Constant_Hue_Loci.png
:align: center
:alt: plot_constant_hue_loci
"""
# TODO: Filter appropriate colour models.
# NOTE: "dtype=object" is required for ragged array support
# in "Numpy" 1.24.0.
data = as_array(data, dtype=object) # pyright: ignore
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
scatter_settings = {
"s": 40,
"c": "RGB",
"marker": "o",
"alpha": 0.85,
"zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_scatter,
}
if scatter_kwargs is not None:
scatter_settings.update(scatter_kwargs)
convert_kwargs = optional(convert_kwargs, {})
use_RGB_colours = str(scatter_settings["c"]).upper() == "RGB"
colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace
for hue_data in data:
_name, XYZ_r, XYZ_cr, XYZ_ct, _metadata = hue_data
xy_r = XYZ_to_xy(XYZ_r)
convert_settings = {"illuminant": xy_r}
convert_settings.update(convert_kwargs)
ijk_ct = colourspace_model_axis_reorder(
convert(XYZ_ct, "CIE XYZ", model, **convert_settings), model
)
ijk_cr = colourspace_model_axis_reorder(
convert(XYZ_cr, "CIE XYZ", model, **convert_settings), model
)
ijk_ct *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
ijk_cr *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model]
def _linear_equation(
x: NDArrayFloat, a: NDArrayFloat, b: NDArrayFloat
) -> NDArrayFloat:
"""Define the canonical linear equation for a line."""
return a * x + b
popt, _pcov = scipy.optimize.curve_fit(
_linear_equation, ijk_ct[..., 0], ijk_ct[..., 1]
)
axes.plot(
ijk_ct[..., 0],
_linear_equation(ijk_ct[..., 0], *popt),
c=CONSTANTS_COLOUR_STYLE.colour.average,
zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
)
if use_RGB_colours:
RGB_ct = XYZ_to_RGB(XYZ_ct, colourspace, xy_r, apply_cctf_encoding=True)
scatter_settings["c"] = np.clip(RGB_ct, 0, 1)
RGB_cr = XYZ_to_RGB(XYZ_cr, colourspace, xy_r, apply_cctf_encoding=True)
RGB_cr = np.clip(np.ravel(RGB_cr), 0, 1)
else:
RGB_cr = scatter_settings["c"]
axes.scatter(ijk_ct[..., 0], ijk_ct[..., 1], **scatter_settings)
axes.plot(
ijk_cr[..., 0],
ijk_cr[..., 1],
"s",
c=RGB_cr,
markersize=CONSTANTS_COLOUR_STYLE.geometry.short * 8,
zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
)
labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[
as_int_array(colourspace_model_axis_reorder([0, 1, 2], model))
]
settings = {
"axes": axes,
"title": f"Constant Hue Loci - {model}",
"x_label": labels[0],
"y_label": labels[1],
}
settings.update(kwargs)
return render(**settings)