"""
Colorimetry Plotting
====================
Define the colorimetry plotting objects:
- :func:`colour.plotting.plot_single_sd`
- :func:`colour.plotting.plot_multi_sds`
- :func:`colour.plotting.plot_single_cmfs`
- :func:`colour.plotting.plot_multi_cmfs`
- :func:`colour.plotting.plot_single_illuminant_sd`
- :func:`colour.plotting.plot_multi_illuminant_sds`
- :func:`colour.plotting.plot_visible_spectrum`
- :func:`colour.plotting.plot_single_lightness_function`
- :func:`colour.plotting.plot_multi_lightness_functions`
- :func:`colour.plotting.plot_single_luminance_function`
- :func:`colour.plotting.plot_multi_luminance_functions`
- :func:`colour.plotting.plot_blackbody_spectral_radiance`
- :func:`colour.plotting.plot_blackbody_colours`
References
----------
- :cite:`Spiker2015a` : Borer, T. (2017). Private Discussion with Mansencal,
T. and Shaw, N.
"""
from __future__ import annotations
from functools import reduce
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.patches import Polygon
from colour.algebra import (
LinearInterpolator,
normalise_maximum,
sdiv,
sdiv_mode,
)
from colour.colorimetry import (
CCS_ILLUMINANTS,
LIGHTNESS_METHODS,
LUMINANCE_METHODS,
SDS_ILLUMINANTS,
MultiSpectralDistributions,
SpectralDistribution,
SpectralShape,
sd_blackbody,
sd_ones,
sd_to_XYZ,
sds_and_msds_to_sds,
wavelength_to_XYZ,
)
from colour.hints import (
Any,
Callable,
Dict,
List,
Sequence,
Tuple,
cast,
)
from colour.plotting import (
CONSTANTS_COLOUR_STYLE,
XYZ_to_plotting_colourspace,
artist,
filter_cmfs,
filter_illuminants,
filter_passthrough,
override_style,
plot_multi_functions,
plot_single_colour_swatch,
render,
update_settings_collection,
)
from colour.utilities import (
as_float_array,
domain_range_scale,
first_item,
ones,
tstack,
)
__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__ = [
"plot_single_sd",
"plot_multi_sds",
"plot_single_cmfs",
"plot_multi_cmfs",
"plot_single_illuminant_sd",
"plot_multi_illuminant_sds",
"plot_visible_spectrum",
"plot_single_lightness_function",
"plot_multi_lightness_functions",
"plot_single_luminance_function",
"plot_multi_luminance_functions",
"plot_blackbody_spectral_radiance",
"plot_blackbody_colours",
]
[docs]
@override_style()
def plot_single_sd(
sd: SpectralDistribution,
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
out_of_gamut_clipping: bool = True,
modulate_colours_with_sd_amplitude: bool = False,
equalize_sd_amplitude: bool = False,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distribution.
Parameters
----------
sd
Spectral distribution to plot.
cmfs
Standard observer colour matching functions used for computing the
spectrum domain and colours. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
out_of_gamut_clipping
Whether to clip out of gamut colours otherwise, the colours will be
offset by the absolute minimal colour leading to a rendering on
gray background, less saturated and smoother.
modulate_colours_with_sd_amplitude
Whether to modulate the colours with the spectral distribution
amplitude.
equalize_sd_amplitude
Whether to equalize the spectral distribution amplitude.
Equalization occurs after the colours modulation thus setting both
arguments to *True* will generate a spectrum strip where each
wavelength colour is modulated by the spectral distribution amplitude.
The usual 5% margin above the spectral distribution is also omitted.
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.
References
----------
:cite:`Spiker2015a`
Examples
--------
>>> from colour import SpectralDistribution
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360,
... }
>>> sd = SpectralDistribution(data, name="Custom")
>>> plot_single_sd(sd) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_SD.png
:align: center
:alt: plot_single_sd
"""
_figure, axes = artist(**kwargs)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
sd = sd.copy()
sd.interpolator = LinearInterpolator
wavelengths = cmfs.wavelengths[
np.logical_and(
cmfs.wavelengths >= max(min(cmfs.wavelengths), min(sd.wavelengths)),
cmfs.wavelengths <= min(max(cmfs.wavelengths), max(sd.wavelengths)),
)
]
values = sd[wavelengths]
RGB = XYZ_to_plotting_colourspace(
wavelength_to_XYZ(wavelengths, cmfs),
CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["E"],
apply_cctf_encoding=False,
)
if not out_of_gamut_clipping:
RGB += np.abs(np.min(RGB))
RGB = normalise_maximum(RGB)
if modulate_colours_with_sd_amplitude:
with sdiv_mode():
RGB *= sdiv(values, np.max(values))[..., None]
RGB = CONSTANTS_COLOUR_STYLE.colour.colourspace.cctf_encoding(RGB)
if equalize_sd_amplitude:
values = ones(values.shape)
margin = 0 if equalize_sd_amplitude else 0.05
x_min, x_max = min(wavelengths), max(wavelengths)
y_min, y_max = 0, max(values) + max(values) * margin
polygon = Polygon(
np.vstack(
[
(x_min, 0),
tstack([wavelengths, values]),
(x_max, 0),
]
),
facecolor="none",
edgecolor="none",
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
)
axes.add_patch(polygon)
padding = 0.1
axes.bar(
x=wavelengths - padding,
height=max(values),
width=1 + padding,
color=RGB,
align="edge",
clip_path=polygon,
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
)
axes.plot(
wavelengths,
values,
color=CONSTANTS_COLOUR_STYLE.colour.dark,
zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
)
settings: Dict[str, Any] = {
"axes": axes,
"bounding_box": (x_min, x_max, y_min, y_max),
"title": f"{sd.display_name} - {cmfs.display_name}",
"x_label": "Wavelength $\\lambda$ (nm)",
"y_label": "Spectral Distribution",
}
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_multi_sds(
sds: (
Sequence[SpectralDistribution | MultiSpectralDistributions]
| SpectralDistribution
| MultiSpectralDistributions
),
plot_kwargs: dict | List[dict] | None = None,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given spectral distributions.
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.
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.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> from colour import SpectralDistribution
>>> data_1 = {
... 500: 0.004900,
... 510: 0.009300,
... 520: 0.063270,
... 530: 0.165500,
... 540: 0.290400,
... 550: 0.433450,
... 560: 0.594500,
... }
>>> data_2 = {
... 500: 0.323000,
... 510: 0.503000,
... 520: 0.710000,
... 530: 0.862000,
... 540: 0.954000,
... 550: 0.994950,
... 560: 0.995000,
... }
>>> sd_1 = SpectralDistribution(data_1, name="Custom 1")
>>> sd_2 = SpectralDistribution(data_2, name="Custom 2")
>>> plot_kwargs = [
... {"use_sd_colours": True},
... {"use_sd_colours": True, "linestyle": "dashed"},
... ]
>>> plot_multi_sds([sd_1, sd_2], plot_kwargs=plot_kwargs)
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_SDS.png
:align: center
:alt: plot_multi_sds
"""
_figure, axes = artist(**kwargs)
sds_converted = sds_and_msds_to_sds(sds)
plot_settings_collection = [
{
"label": f"{sd.display_name}",
"zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_line,
"cmfs": "CIE 1931 2 Degree Standard Observer",
"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)
)
x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
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")
wavelengths, values = sd.wavelengths, sd.values
shape = sd.shape
x_limit_min.append(shape.start)
x_limit_max.append(shape.end)
y_limit_min.append(min(values))
y_limit_max.append(max(values))
if use_sd_colours:
with domain_range_scale("1"):
XYZ = sd_to_XYZ(sd, cmfs, illuminant)
if normalise_sd_colours:
XYZ /= XYZ[..., 1]
plot_settings["color"] = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1)
axes.plot(wavelengths, values, **plot_settings)
bounding_box = (
min(x_limit_min),
max(x_limit_max),
min(y_limit_min),
max(y_limit_max) * 1.05,
)
settings: Dict[str, Any] = {
"axes": axes,
"bounding_box": bounding_box,
"legend": True,
"x_label": "Wavelength $\\lambda$ (nm)",
"y_label": "Spectral Distribution",
}
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_single_cmfs(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given colour matching functions.
Parameters
----------
cmfs
Colour matching functions to plot. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_multi_cmfs`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_single_cmfs("CIE 1931 2 Degree Standard Observer")
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_CMFS.png
:align: center
:alt: plot_single_cmfs
"""
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
settings: Dict[str, Any] = {
"title": f"{cmfs.display_name} - Colour Matching Functions"
}
settings.update(kwargs)
return plot_multi_cmfs((cmfs,), **settings)
[docs]
@override_style()
def plot_multi_cmfs(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
),
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given colour matching functions.
Parameters
----------
cmfs
Colour matching functions to plot. ``cmfs`` elements can be of any
type or form supported by the :func:`colour.plotting.common.filter_cmfs`
definition.
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
--------
>>> cmfs = [
... "CIE 1931 2 Degree Standard Observer",
... "CIE 1964 10 Degree Standard Observer",
... ]
>>> plot_multi_cmfs(cmfs) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_CMFS.png
:align: center
:alt: plot_multi_cmfs
"""
cmfs = cast(List[MultiSpectralDistributions], list(filter_cmfs(cmfs).values())) # pyright: ignore
_figure, axes = artist(**kwargs)
axes.axhline(
color=CONSTANTS_COLOUR_STYLE.colour.dark,
linestyle="--",
zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
)
x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
for i, cmfs_i in enumerate(cmfs):
for j, RGB in enumerate(as_float_array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])):
RGB = [ # noqa: PLW2901
reduce(lambda y, _: y * 0.5, range(i), x) for x in RGB
]
values = cmfs_i.values[:, j]
shape = cmfs_i.shape
x_limit_min.append(shape.start)
x_limit_max.append(shape.end)
y_limit_min.append(np.min(values))
y_limit_max.append(np.max(values))
axes.plot(
cmfs_i.wavelengths,
values,
color=RGB,
label=f"{cmfs_i.display_labels[j]} - {cmfs_i.display_name}",
zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
)
bounding_box = (
min(x_limit_min),
max(x_limit_max),
min(y_limit_min) - np.abs(np.min(y_limit_min)) * 0.05,
max(y_limit_max) + np.abs(np.max(y_limit_max)) * 0.05,
)
cmfs_display_names = ", ".join([cmfs_i.display_name for cmfs_i in cmfs])
title = f"{cmfs_display_names} - Colour Matching Functions"
settings: Dict[str, Any] = {
"axes": axes,
"bounding_box": bounding_box,
"legend": True,
"title": title,
"x_label": "Wavelength $\\lambda$ (nm)",
"y_label": "Tristimulus Values",
}
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_single_illuminant_sd(
illuminant: SpectralDistribution | str,
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given single illuminant spectral distribution.
Parameters
----------
illuminant
Illuminant to plot. ``illuminant`` can be of any type or form supported
by the :func:`colour.plotting.common.filter_illuminants` definition.
cmfs
Standard observer colour matching functions used for computing the
spectrum domain and colours. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_single_sd`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
References
----------
:cite:`Spiker2015a`
Examples
--------
>>> plot_single_illuminant_sd("A") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_Illuminant_SD.png
:align: center
:alt: plot_single_illuminant_sd
"""
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
title = f"Illuminant {illuminant} - {cmfs.display_name}"
illuminant = cast(
SpectralDistribution,
first_item(filter_illuminants(illuminant).values()),
)
settings: Dict[str, Any] = {"title": title, "y_label": "Relative Power"}
settings.update(kwargs)
return plot_single_sd(illuminant, **settings)
[docs]
@override_style()
def plot_multi_illuminant_sds(
illuminants: (SpectralDistribution | str | Sequence[SpectralDistribution | str]),
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given illuminants spectral distributions.
Parameters
----------
illuminants
Illuminants to plot. ``illuminants`` elements can be of any type or
form supported by the :func:`colour.plotting.common.filter_illuminants`
definition.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_multi_sds`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_multi_illuminant_sds(["A", "B", "C"]) # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_Illuminant_SDS.png
:align: center
:alt: plot_multi_illuminant_sds
"""
if "plot_kwargs" not in kwargs:
kwargs["plot_kwargs"] = {}
SD_E = SDS_ILLUMINANTS["E"]
if isinstance(kwargs["plot_kwargs"], dict):
kwargs["plot_kwargs"]["illuminant"] = SD_E
else:
for i in range(len(kwargs["plot_kwargs"])):
kwargs["plot_kwargs"][i]["illuminant"] = SD_E
illuminants = cast(
List[SpectralDistribution],
list(filter_illuminants(illuminants).values()),
) # pyright: ignore
illuminant_display_names = ", ".join(
[illuminant.display_name for illuminant in illuminants]
)
title = f"{illuminant_display_names} - Illuminants Spectral Distributions"
settings: Dict[str, Any] = {"title": title, "y_label": "Relative Power"}
settings.update(kwargs)
return plot_multi_sds(illuminants, **settings)
[docs]
@override_style(
**{
"ytick.left": False,
"ytick.labelleft": False,
}
)
def plot_visible_spectrum(
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
out_of_gamut_clipping: bool = True,
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot the visible colours spectrum using given standard observer *CIE XYZ*
colour matching functions.
Parameters
----------
cmfs
Standard observer colour matching functions used for computing the
spectrum domain and colours. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
out_of_gamut_clipping
Whether to clip out of gamut colours otherwise, the colours will be
offset by the absolute minimal colour leading to a rendering on
gray background, less saturated and smoother.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_single_sd`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
References
----------
:cite:`Spiker2015a`
Examples
--------
>>> plot_visible_spectrum() # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Visible_Spectrum.png
:align: center
:alt: plot_visible_spectrum
"""
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
bounding_box = (min(cmfs.wavelengths), max(cmfs.wavelengths), 0, 1)
settings: Dict[str, Any] = {"bounding_box": bounding_box, "y_label": None}
settings.update(kwargs)
settings["show"] = False
_figure, axes = plot_single_sd(
sd_ones(cmfs.shape),
cmfs=cmfs,
out_of_gamut_clipping=out_of_gamut_clipping,
**settings,
)
# Removing wavelength line as it doubles with the axes spine.
axes.lines[0].remove()
settings = {
"axes": axes,
"show": True,
"title": f"The Visible Spectrum - {cmfs.display_name}",
"x_label": "Wavelength $\\lambda$ (nm)",
}
settings.update(kwargs)
return render(**settings)
[docs]
@override_style()
def plot_single_lightness_function(
function: Callable | str, **kwargs: Any
) -> Tuple[Figure, Axes]:
"""
Plot given *Lightness* function.
Parameters
----------
function
*Lightness* function to plot. ``function`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_passthrough` 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.
Examples
--------
>>> plot_single_lightness_function("CIE 1976") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_Lightness_Function.png
:align: center
:alt: plot_single_lightness_function
"""
settings: Dict[str, Any] = {"title": f"{function} - Lightness Function"}
settings.update(kwargs)
return plot_multi_lightness_functions((function,), **settings)
[docs]
@override_style()
def plot_multi_lightness_functions(
functions: Callable | str | Sequence[Callable | str],
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *Lightness* functions.
Parameters
----------
functions
*Lightness* functions to plot. ``functions`` elements can be of any
type or form supported by the
:func:`colour.plotting.common.filter_passthrough` 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.
Examples
--------
>>> plot_multi_lightness_functions(["CIE 1976", "Wyszecki 1963"])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_Lightness_Functions.png
:align: center
:alt: plot_multi_lightness_functions
"""
functions_filtered = filter_passthrough(LIGHTNESS_METHODS, functions)
settings: Dict[str, Any] = {
"bounding_box": (0, 1, 0, 1),
"legend": True,
"title": f"{', '.join(functions_filtered)} - Lightness Functions",
"x_label": "Normalised Relative Luminance Y",
"y_label": "Normalised Lightness",
}
settings.update(kwargs)
with domain_range_scale("1"):
return plot_multi_functions(functions_filtered, **settings)
[docs]
@override_style()
def plot_single_luminance_function(
function: Callable | str, **kwargs: Any
) -> Tuple[Figure, Axes]:
"""
Plot given *Luminance* function.
Parameters
----------
function
*Luminance* function to plot.
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_luminance_function("CIE 1976") # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Single_Luminance_Function.png
:align: center
:alt: plot_single_luminance_function
"""
settings: Dict[str, Any] = {"title": f"{function} - Luminance Function"}
settings.update(kwargs)
return plot_multi_luminance_functions((function,), **settings)
[docs]
@override_style()
def plot_multi_luminance_functions(
functions: Callable | str | Sequence[Callable | str],
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given *Luminance* functions.
Parameters
----------
functions
*Luminance* functions to plot. ``functions`` elements can be of any
type or form supported by the
:func:`colour.plotting.common.filter_passthrough` 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.
Examples
--------
>>> plot_multi_luminance_functions(["CIE 1976", "Newhall 1943"])
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Multi_Luminance_Functions.png
:align: center
:alt: plot_multi_luminance_functions
"""
functions_filtered = filter_passthrough(LUMINANCE_METHODS, functions)
settings: Dict[str, Any] = {
"bounding_box": (0, 1, 0, 1),
"legend": True,
"title": f"{', '.join(functions_filtered)} - Luminance Functions",
"x_label": "Normalised Munsell Value / Lightness",
"y_label": "Normalised Relative Luminance Y",
}
settings.update(kwargs)
with domain_range_scale("1"):
return plot_multi_functions(functions_filtered, **settings)
[docs]
@override_style()
def plot_blackbody_spectral_radiance(
temperature: float = 3500,
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
blackbody: str = "VY Canis Major",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot given blackbody spectral radiance.
Parameters
----------
temperature
Blackbody temperature.
cmfs
Standard observer colour matching functions used for computing the
spectrum domain and colours. ``cmfs`` can be of any type or form
supported by the :func:`colour.plotting.common.filter_cmfs` definition.
blackbody
Blackbody name.
Other Parameters
----------------
kwargs
{:func:`colour.plotting.artist`,
:func:`colour.plotting.plot_single_sd`,
:func:`colour.plotting.render`},
See the documentation of the previously listed definitions.
Returns
-------
:class:`tuple`
Current figure and axes.
Examples
--------
>>> plot_blackbody_spectral_radiance(3500, blackbody="VY Canis Major")
... # doctest: +ELLIPSIS
(<Figure size ... with 2 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png
:align: center
:alt: plot_blackbody_spectral_radiance
"""
figure = plt.figure()
figure.subplots_adjust(hspace=CONSTANTS_COLOUR_STYLE.geometry.short / 2)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
sd = sd_blackbody(temperature, cmfs.shape)
axes = figure.add_subplot(211)
settings: Dict[str, Any] = {
"axes": axes,
"title": f"{blackbody} - Spectral Radiance",
"y_label": "W / (sr m$^2$) / m",
}
settings.update(kwargs)
settings["show"] = False
plot_single_sd(sd, cmfs.name, **settings)
axes = figure.add_subplot(212)
with domain_range_scale("1"):
XYZ = sd_to_XYZ(sd, cmfs)
RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ))
settings = {
"axes": axes,
"aspect": None,
"title": f"{blackbody} - Colour",
"x_label": f"{temperature}K",
"y_label": "",
"x_ticker": False,
"y_ticker": False,
}
settings.update(kwargs)
settings["show"] = False
figure, axes = plot_single_colour_swatch(RGB, **settings)
settings = {"axes": axes, "show": True}
settings.update(kwargs)
return render(**settings)
[docs]
@override_style(
**{
"ytick.left": False,
"ytick.labelleft": False,
}
)
def plot_blackbody_colours(
shape: SpectralShape = SpectralShape(150, 12500, 50),
cmfs: (
MultiSpectralDistributions | str | Sequence[MultiSpectralDistributions | str]
) = "CIE 1931 2 Degree Standard Observer",
**kwargs: Any,
) -> Tuple[Figure, Axes]:
"""
Plot blackbody colours.
Parameters
----------
shape
Spectral shape to use as plot boundaries.
cmfs
Standard observer colour matching functions used for computing the
blackbody colours. ``cmfs`` can be of any type or form supported by the
:func:`colour.plotting.common.filter_cmfs` definition.
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_blackbody_colours(SpectralShape(150, 12500, 50))
... # doctest: +ELLIPSIS
(<Figure size ... with 1 Axes>, <...Axes...>)
.. image:: ../_static/Plotting_Plot_Blackbody_Colours.png
:align: center
:alt: plot_blackbody_colours
"""
_figure, axes = artist(**kwargs)
cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values()))
RGB = []
temperatures = []
for temperature in shape:
sd = sd_blackbody(temperature, cmfs.shape)
with domain_range_scale("1"):
XYZ = sd_to_XYZ(sd, cmfs)
RGB.append(normalise_maximum(XYZ_to_plotting_colourspace(XYZ)))
temperatures.append(temperature)
x_min, x_max = min(temperatures), max(temperatures)
y_min, y_max = 0, 1
padding = 0.1
axes.bar(
x=as_float_array(temperatures) - padding,
height=1,
width=shape.interval + (padding * shape.interval),
color=RGB,
align="edge",
zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
)
settings: Dict[str, Any] = {
"axes": axes,
"bounding_box": (x_min, x_max, y_min, y_max),
"title": "Blackbody Colours",
"x_label": "Temperature K",
"y_label": None,
}
settings.update(kwargs)
return render(**settings)