"""
Colour Temperature & Correlated Colour Temperature Plotting
===========================================================
Defines the colour temperature and correlated colour temperature plotting
objects:
- :func:`colour.plotting.\
plot_planckian_locus_in_chromaticity_diagram_CIE1931`
- :func:`colour.plotting.\
plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS`
"""
from __future__ import annotations
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from colour.algebra import normalise_maximum
from colour.colorimetry import MSDS_CMFS, CCS_ILLUMINANTS
from colour.hints import (
Any,
ArrayLike,
Callable,
Dict,
Floating,
List,
Literal,
NDArray,
Optional,
Sequence,
Tuple,
Union,
cast,
)
from colour.models import (
UCS_to_uv,
UCS_uv_to_xy,
XYZ_to_UCS,
xy_to_Luv_uv,
xy_to_XYZ,
)
from colour.temperature import CCT_to_uv
from colour.plotting import (
CONSTANTS_COLOUR_STYLE,
CONSTANTS_ARROW_STYLE,
XYZ_to_plotting_colourspace,
artist,
plot_chromaticity_diagram_CIE1931,
plot_chromaticity_diagram_CIE1960UCS,
filter_passthrough,
override_style,
render,
update_settings_collection,
)
from colour.plotting.diagrams import plot_chromaticity_diagram
from colour.utilities import (
as_int_scalar,
full,
optional,
tstack,
validate_method,
zeros,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "New BSD License - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"plot_planckian_locus",
"plot_planckian_locus_in_chromaticity_diagram",
"plot_planckian_locus_in_chromaticity_diagram_CIE1931",
"plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS",
]
[docs]@override_style()
def plot_planckian_locus(
planckian_locus_colours: Optional[Union[ArrayLike, str]] = None,
planckian_locus_opacity: Floating = 1,
planckian_locus_labels: Optional[Sequence] = None,
method: Union[
Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"], str
] = "CIE 1931",
**kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
"""
Plot the *Planckian Locus* according to given method.
Parameters
----------
planckian_locus_colours
Colours of the *Planckian Locus*, if ``planckian_locus_colours`` is set
to *RGB*, the colours will be computed according to the corresponding
chromaticity coordinates.
planckian_locus_opacity
Opacity of the *Planckian Locus*.
planckian_locus_labels
Array of labels used to customise which iso-temperature lines will be
drawn along the *Planckian Locus*. Passing an empty array will result
in no iso-temperature lines 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_planckian_locus(planckian_locus_colours='RGB')
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...AxesSubplot...>)
.. image:: ../_static/Plotting_Plot_Planckian_Locus.png
:align: center
:alt: plot_planckian_locus
"""
method = validate_method(
method, ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]
)
planckian_locus_colours = optional(
planckian_locus_colours, CONSTANTS_COLOUR_STYLE.colour.dark
)
labels = cast(
Tuple,
optional(
planckian_locus_labels,
(10**6 / 600, 2000, 2500, 3000, 4000, 6000, 10**6 / 100),
),
)
D_uv = 0.05
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
if method == "cie 1931":
def uv_to_ij(uv: NDArray) -> NDArray:
"""
Convert given *uv* chromaticity coordinates to *ij* chromaticity
coordinates.
"""
return UCS_uv_to_xy(uv)
elif method == "cie 1960 ucs":
def uv_to_ij(uv: NDArray) -> NDArray:
"""
Convert given *uv* chromaticity coordinates to *ij* chromaticity
coordinates.
"""
return uv
elif method == "cie 1976 ucs":
def uv_to_ij(uv: NDArray) -> NDArray:
"""
Convert given *uv* chromaticity coordinates to *ij* chromaticity
coordinates.
"""
return xy_to_Luv_uv(UCS_uv_to_xy(uv))
def CCT_D_uv_to_plotting_colourspace(CCT_D_uv):
"""
Convert given *uv* chromaticity coordinates to the default plotting
colourspace.
"""
return normalise_maximum(
XYZ_to_plotting_colourspace(
xy_to_XYZ(UCS_uv_to_xy(CCT_to_uv(CCT_D_uv, "Robertson 1968")))
),
axis=-1,
)
start, end = 10**6 / 600, 10**6 / 10
CCT = np.arange(start, end + 100, 100)
CCT_D_uv = np.reshape(tstack([CCT, zeros(CCT.shape)]), (-1, 1, 2))
ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))
use_RGB_planckian_locus_colours = (
str(planckian_locus_colours).upper() == "RGB"
)
if use_RGB_planckian_locus_colours:
pl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
else:
pl_colours = planckian_locus_colours
line_collection = LineCollection(
np.concatenate([ij[:-1], ij[1:]], axis=1),
colors=pl_colours,
alpha=planckian_locus_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
)
axes.add_collection(line_collection)
for label in labels:
CCT_D_uv = np.reshape(
tstack([full(10, label), np.linspace(-D_uv, D_uv, 10)]), (-1, 1, 2)
)
if use_RGB_planckian_locus_colours:
itl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
else:
itl_colours = planckian_locus_colours
ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))
line_collection = LineCollection(
np.concatenate([ij[:-1], ij[1:]], axis=1),
colors=itl_colours,
alpha=planckian_locus_opacity,
zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
)
axes.add_collection(line_collection)
axes.annotate(
f"{as_int_scalar(label)}K",
xy=(ij[-1, :, 0], ij[-1, :, 1]),
xytext=(0, CONSTANTS_COLOUR_STYLE.geometry.long / 2),
textcoords="offset points",
size="x-small",
zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,
)
settings = {"axes": axes}
settings.update(kwargs)
return render(**settings)
[docs]@override_style()
def plot_planckian_locus_in_chromaticity_diagram(
illuminants: Union[str, Sequence[str]],
chromaticity_diagram_callable: Callable = (
plot_chromaticity_diagram # type: ignore[has-type]
),
method: Union[Literal["CIE 1931", "CIE 1960 UCS"], str] = "CIE 1931",
annotate_kwargs: Optional[Union[Dict, List[Dict]]] = None,
plot_kwargs: Optional[Union[Dict, List[Dict]]] = None,
**kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
"""
Plot the *Planckian Locus* and given illuminants in the
*Chromaticity Diagram* according to given method.
Parameters
----------
illuminants
Illuminants to plot. ``illuminants`` elements can be of any
type or form supported by the
:func:`colour.plotting.filter_passthrough` 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 illuminants. ``plot_kwargs``
can be either a single dictionary applied to all the plotted
illuminants with the same settings or a sequence of dictionaries with
different settings for eachplotted illuminant.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.temperature.plot_planckian_locus`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> annotate_kwargs = [
... {'xytext': (-25, 15), 'arrowprops':{'arrowstyle':'-'}},
... {'arrowprops':{'arrowstyle':'-['}},
... {},
... ]
>>> plot_kwargs = [
... {
... 'markersize' : 15,
... },
... { 'color': 'r'},
... {},
... ]
>>> plot_planckian_locus_in_chromaticity_diagram(
... ['A', 'B', 'C'],
... annotate_kwargs=annotate_kwargs,
... plot_kwargs=plot_kwargs
... ) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...AxesSubplot...>)
.. image:: ../_static/Plotting_\
Plot_Planckian_Locus_In_Chromaticity_Diagram.png
:align: center
:alt: plot_planckian_locus_in_chromaticity_diagram
"""
cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
illuminants_filtered = filter_passthrough(
CCS_ILLUMINANTS.get(cmfs.name), illuminants # type: ignore[arg-type]
)
settings: Dict[str, Any] = {"uniform": True}
settings.update(kwargs)
_figure, axes = artist(**settings)
method = method.upper()
settings = {"axes": axes, "method": method}
settings.update(kwargs)
settings["standalone"] = False
chromaticity_diagram_callable(**settings)
plot_planckian_locus(**settings)
if method == "CIE 1931":
def xy_to_ij(xy: NDArray) -> NDArray:
"""
Convert given *CIE xy* chromaticity coordinates to *ij*
chromaticity coordinates.
"""
return xy
bounding_box = (-0.1, 0.9, -0.1, 0.9)
elif method == "CIE 1960 UCS":
def xy_to_ij(xy: NDArray) -> NDArray:
"""
Convert given *CIE xy* chromaticity coordinates to *ij*
chromaticity coordinates.
"""
return UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy)))
bounding_box = (-0.1, 0.7, -0.2, 0.6)
else:
raise ValueError(
f'Invalid method: "{method}", must be one of '
f'["CIE 1931", "CIE 1960 UCS"]'
)
annotate_settings_collection = [
{
"annotate": True,
"xytext": (-50, 30),
"textcoords": "offset points",
"arrowprops": CONSTANTS_ARROW_STYLE,
"zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_annotation,
}
for _ in range(len(illuminants_filtered))
]
if annotate_kwargs is not None:
update_settings_collection(
annotate_settings_collection,
annotate_kwargs,
len(illuminants_filtered),
)
plot_settings_collection = [
{
"color": CONSTANTS_COLOUR_STYLE.colour.brightest,
"label": f"{illuminant}",
"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.foreground_line,
}
for illuminant in illuminants_filtered
]
if plot_kwargs is not None:
update_settings_collection(
plot_settings_collection, plot_kwargs, len(illuminants_filtered)
)
for i, (illuminant, xy) in enumerate(illuminants_filtered.items()):
plot_settings = plot_settings_collection[i]
ij = xy_to_ij(xy)
axes.plot(ij[0], ij[1], **plot_settings)
if annotate_settings_collection[i]["annotate"]:
annotate_settings = annotate_settings_collection[i]
annotate_settings.pop("annotate")
axes.annotate(illuminant, xy=ij, **annotate_settings)
title = (
(
f"{', '.join(illuminants_filtered)} Illuminants - Planckian Locus\n"
f"{method.upper()} Chromaticity Diagram - "
"CIE 1931 2 Degree Standard Observer"
)
if illuminants_filtered
else (
f"Planckian Locus\n{method.upper()} Chromaticity Diagram - "
f"CIE 1931 2 Degree Standard Observer"
)
)
settings.update(
{
"axes": axes,
"standalone": True,
"bounding_box": bounding_box,
"title": title,
}
)
settings.update(kwargs)
return render(**settings)
[docs]@override_style()
def plot_planckian_locus_in_chromaticity_diagram_CIE1931(
illuminants: Union[str, Sequence[str]],
chromaticity_diagram_callable_CIE1931: Callable = (
plot_chromaticity_diagram_CIE1931 # type: ignore[has-type]
),
annotate_kwargs: Optional[Union[Dict, List[Dict]]] = None,
plot_kwargs: Optional[Union[Dict, List[Dict]]] = None,
**kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
"""
Plot the *Planckian Locus* and given illuminants in
*CIE 1931 Chromaticity Diagram*.
Parameters
----------
illuminants
Illuminants to plot. ``illuminants`` elements can be of any
type or form supported by the
:func:`colour.plotting.filter_passthrough` 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 illuminants. ``plot_kwargs``
can be either a single dictionary applied to all the plotted
illuminants with the same settings or a sequence of dictionaries with
different settings for eachplotted illuminant.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.temperature.plot_planckian_locus`,
:func:`colour.plotting.temperature.\
plot_planckian_locus_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_planckian_locus_in_chromaticity_diagram_CIE1931(['A', 'B', 'C'])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...AxesSubplot...>)
.. image:: ../_static/Plotting_\
Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1931.png
:align: center
:alt: plot_planckian_locus_in_chromaticity_diagram_CIE1931
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1931"})
return plot_planckian_locus_in_chromaticity_diagram(
illuminants,
chromaticity_diagram_callable_CIE1931,
annotate_kwargs=annotate_kwargs,
plot_kwargs=plot_kwargs,
**settings,
)
[docs]@override_style()
def plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS(
illuminants: Union[str, Sequence[str]],
chromaticity_diagram_callable_CIE1960UCS: Callable = (
plot_chromaticity_diagram_CIE1960UCS # type: ignore[has-type]
),
annotate_kwargs: Optional[Union[Dict, List[Dict]]] = None,
plot_kwargs: Optional[Union[Dict, List[Dict]]] = None,
**kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
"""
Plot the *Planckian Locus* and given illuminants in
*CIE 1960 UCS Chromaticity Diagram*.
Parameters
----------
illuminants
Illuminants to plot. ``illuminants`` elements can be of any
type or form supported by the
:func:`colour.plotting.filter_passthrough` 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 illuminants. ``plot_kwargs``
can be either a single dictionary applied to all the plotted
illuminants with the same settings or a sequence of dictionaries with
different settings for eachplotted illuminant.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
:func:`colour.plotting.temperature.plot_planckian_locus`,
:func:`colour.plotting.temperature.\
plot_planckian_locus_in_chromaticity_diagram`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS(
... ['A', 'C', 'E']) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...AxesSubplot...>)
.. image:: ../_static/Plotting_\
Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1960UCS.png
:align: center
:alt: plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS
"""
settings = dict(kwargs)
settings.update({"method": "CIE 1960 UCS"})
return plot_planckian_locus_in_chromaticity_diagram(
illuminants,
chromaticity_diagram_callable_CIE1960UCS,
annotate_kwargs=annotate_kwargs,
plot_kwargs=plot_kwargs,
**settings,
)