Source code for colour.plotting.colorimetry

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Colorimetry Plotting
====================

Defines the colorimetry plotting objects:

-   :func:`single_spd_plot`
-   :func:`multi_spd_plot`
-   :func:`single_cmfs_plot`
-   :func:`multi_cmfs_plot`
-   :func:`single_illuminant_relative_spd_plot`
-   :func:`multi_illuminants_relative_spd_plot`
-   :func:`visible_spectrum_plot`
-   :func:`single_lightness_function_plot`
-   :func:`multi_lightness_function_plot`
-   :func:`blackbody_spectral_radiance_plot`
-   :func:`blackbody_colours_plot`
"""

from __future__ import division

import matplotlib.pyplot
import numpy as np
import pylab

from colour.algebra import normalise
from colour.colorimetry import (
    CMFS,
    DEFAULT_SPECTRAL_SHAPE,
    ILLUMINANTS_RELATIVE_SPDS,
    LIGHTNESS_METHODS,
    SpectralShape,
    spectral_to_XYZ,
    wavelength_to_XYZ,
    blackbody_spd)
from colour.models import XYZ_to_sRGB
from colour.plotting import (
    DEFAULT_FIGURE_WIDTH,
    canvas,
    decorate,
    boundaries,
    display,
    colour_parameter,
    colour_parameters_plot,
    single_colour_plot)

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'

__all__ = ['get_cmfs',
           'get_illuminant',
           'single_spd_plot',
           'multi_spd_plot',
           'single_cmfs_plot',
           'multi_cmfs_plot',
           'single_illuminant_relative_spd_plot',
           'multi_illuminants_relative_spd_plot',
           'visible_spectrum_plot',
           'single_lightness_function_plot',
           'multi_lightness_function_plot',
           'blackbody_spectral_radiance_plot',
           'blackbody_colours_plot']


[docs]def get_cmfs(cmfs): """ Returns the colour matching functions with given name. Parameters ---------- cmfs : unicode Colour matching functions name. Returns ------- RGB_ColourMatchingFunctions or XYZ_ColourMatchingFunctions Colour matching functions. Raises ------ KeyError If the given colour matching functions is not found in the factory colour matching functions. """ cmfs, name = CMFS.get(cmfs), cmfs if cmfs is None: raise KeyError( ('"{0}" not found in factory colour matching functions: ' '"{1}".').format(name, ', '.join(sorted(CMFS.keys())))) return cmfs
[docs]def get_illuminant(illuminant): """ Returns the illuminant with given name. Parameters ---------- illuminant : unicode Illuminant name. Returns ------- SpectralPowerDistribution Illuminant. Raises ------ KeyError If the given illuminant is not found in the factory illuminants. """ illuminant, name = ILLUMINANTS_RELATIVE_SPDS.get(illuminant), illuminant if illuminant is None: raise KeyError( '"{0}" not found in factory illuminants: "{1}".'.format( name, ', '.join(sorted(ILLUMINANTS_RELATIVE_SPDS.keys())))) return illuminant
[docs]def single_spd_plot(spd, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots given spectral power distribution. Parameters ---------- spd : SpectralPowerDistribution, optional Spectral power distribution to plot. cmfs : unicode Standard observer colour matching functions used for spectrum creation. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import SpectralPowerDistribution >>> data = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> spd = SpectralPowerDistribution('Custom', data) >>> single_spd_plot(spd) # doctest: +SKIP True """ cmfs = get_cmfs(cmfs) shape = cmfs.shape spd = spd.clone().interpolate(shape) wavelengths = shape.range() colours = [] y1 = [] for wavelength, value in spd: XYZ = wavelength_to_XYZ(wavelength, cmfs) colours.append(XYZ_to_sRGB(XYZ)) y1.append(value) colours = normalise(colours) settings = { 'title': '{0} - {1}'.format(spd.title, cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'x_ticker': True, 'y_ticker': True} settings.update(kwargs) return colour_parameters_plot( [colour_parameter(x=x[0], y1=x[1], RGB=x[2]) for x in tuple(zip(wavelengths, y1, colours))], **settings)
[docs]def multi_spd_plot(spds, cmfs='CIE 1931 2 Degree Standard Observer', use_spds_colours=False, normalise_spds_colours=False, **kwargs): """ Plots given spectral power distributions. Parameters ---------- spds : list, optional Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_spds_colours : bool, optional Use spectral power distributions colours. normalise_spds_colours : bool Should spectral power distributions colours normalised. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import SpectralPowerDistribution >>> data1 = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> data2 = {400: 0.134, 420: 0.789, 440: 1.289} >>> spd1 = SpectralPowerDistribution('Custom1', data1) >>> spd2 = SpectralPowerDistribution('Custom2', data2) >>> multi_spd_plot([spd1, spd2]) # doctest: +SKIP True """ canvas(**kwargs) cmfs = get_cmfs(cmfs) if use_spds_colours: illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D65') x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for spd in spds: wavelengths, values = tuple(zip(*spd.items)) shape = spd.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)) matplotlib.pyplot.rc("axes", color_cycle=["r", "g", "b", "y"]) if use_spds_colours: XYZ = spectral_to_XYZ(spd, cmfs, illuminant) / 100 if normalise_spds_colours: XYZ = normalise(XYZ, clip=False) RGB = np.clip(XYZ_to_sRGB(XYZ), 0, 1) pylab.plot(wavelengths, values, color=RGB, label=spd.title, linewidth=2) else: pylab.plot(wavelengths, values, label=spd.title, linewidth=2) settings = { 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'legend': True, 'legend_location': 'upper left', 'x_ticker': True, 'y_ticker': True, 'limits': [min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max)]} settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def single_cmfs_plot(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots given colour matching functions. Parameters ---------- cmfs : unicode, optional Colour matching functions to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> single_cmfs_plot() # doctest: +SKIP True """ cmfs = get_cmfs(cmfs) settings = { 'title': '{0} - Colour Matching Functions'.format(cmfs.title)} settings.update(kwargs) return multi_cmfs_plot([cmfs.name], **settings)
[docs]def multi_cmfs_plot(cmfs=None, **kwargs): """ Plots given colour matching functions. Parameters ---------- cmfs : array_like, optional Colour matching functions to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> cmfs = [ ... 'CIE 1931 2 Degree Standard Observer', ... 'CIE 1964 10 Degree Standard Observer'] >>> multi_cmfs_plot(cmfs) # doctest: +SKIP True """ canvas(**kwargs) if cmfs is None: cmfs = ('CIE 1931 2 Degree Standard Observer', 'CIE 1964 10 Degree Standard Observer') x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for axis, rgb in (('x', [1, 0, 0]), ('y', [0, 1, 0]), ('z', [0, 0, 1])): for i, cmfs_i in enumerate(cmfs): cmfs_i = get_cmfs(cmfs_i) rgb = [reduce(lambda y, _: y * 0.5, range(i), x) for x in rgb] wavelengths, values = tuple( zip(*[(key, value) for key, value in getattr(cmfs_i, axis)])) shape = cmfs_i.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)) pylab.plot(wavelengths, values, color=rgb, label=u'{0} - {1}'.format( cmfs_i.labels.get(axis), cmfs_i.title), linewidth=2) settings = { 'title': '{0} - Colour Matching Functions'.format(', '.join( [get_cmfs(cmfs_i).title for cmfs_i in cmfs])), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Tristimulus Values', 'x_tighten': True, 'legend': True, 'legend_location': 'upper right', 'x_ticker': True, 'y_ticker': True, 'grid': True, 'y_axis_line': True, 'limits': [min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max)]} settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def single_illuminant_relative_spd_plot( illuminant='A', cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots given single illuminant relative spectral power distribution. Parameters ---------- illuminant : unicode, optional Factory illuminant to plot. cmfs : unicode, optional Standard observer colour matching functions to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> single_illuminant_relative_spd_plot() # doctest: +SKIP True """ cmfs = get_cmfs(cmfs) title = 'Illuminant {0} - {1}'.format(illuminant, cmfs.title) illuminant = get_illuminant(illuminant) settings = { 'title': title, 'y_label': 'Relative Spectral Power Distribution'} settings.update(kwargs) return single_spd_plot(illuminant, **settings)
[docs]def multi_illuminants_relative_spd_plot(illuminants=None, **kwargs): """ Plots given illuminants relative spectral power distributions. Parameters ---------- illuminants : array_like, optional Factory illuminants to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> multi_illuminants_relative_spd_plot(['A', 'B', 'C']) # doctest: +SKIP True """ if illuminants is None: illuminants = ('A', 'B', 'C') spds = [] for illuminant in illuminants: spds.append(get_illuminant(illuminant)) settings = { 'title': ( '{0} - Illuminants Relative Spectral Power Distribution').format( ', '.join([spd.title for spd in spds])), 'y_label': 'Relative Spectral Power Distribution'} settings.update(kwargs) return multi_spd_plot(spds, **settings)
[docs]def visible_spectrum_plot(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the visible colours spectrum using given standard observer *CIE XYZ* colour matching functions. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> visible_spectrum_plot() # doctest: +SKIP True """ cmfs = get_cmfs(cmfs) cmfs = cmfs.clone().align(DEFAULT_SPECTRAL_SHAPE) wavelengths = cmfs.shape.range() colours = [] for i in wavelengths: XYZ = wavelength_to_XYZ(i, cmfs) colours.append(XYZ_to_sRGB(XYZ)) colours = np.array([np.ravel(x) for x in colours]) colours *= 1 / np.max(colours) colours = np.clip(colours, 0, 1) settings = { 'title': 'The Visible Spectrum - {0}'.format(cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'x_tighten': True} settings.update(kwargs) return colour_parameters_plot([colour_parameter(x=x[0], RGB=x[1]) for x in tuple(zip(wavelengths, colours))], **settings)
[docs]def single_lightness_function_plot(function='CIE 1976', **kwargs): """ Plots given *Lightness* function. Parameters ---------- function : unicode, optional *Lightness* function to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> single_lightness_function_plot() # doctest: +SKIP True """ settings = { 'title': '{0} - Lightness Function'.format(function)} settings.update(kwargs) return multi_lightness_function_plot([function], **settings)
[docs]def multi_lightness_function_plot(functions=None, **kwargs): """ Plots given *Lightness* functions. Parameters ---------- functions : array_like, optional *Lightness* functions to plot. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Raises ------ KeyError If one of the given *Lightness* function is not found in the factory *Lightness* functions. Examples -------- >>> fs = ('CIE 1976', 'Wyszecki 1963') >>> multi_lightness_function_plot(fs) # doctest: +SKIP True """ settings = { 'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) if functions is None: functions = ('CIE 1976', 'Wyszecki 1963') samples = np.linspace(0, 100, 1000) for i, function in enumerate(functions): function, name = LIGHTNESS_METHODS.get(function), function if function is None: raise KeyError( ('"{0}" "Lightness" function not found in factory ' '"Lightness" functions: "{1}".').format( name, sorted(LIGHTNESS_METHODS.keys()))) pylab.plot(samples, [function(x) for x in samples], label='{0}'.format(name), linewidth=2) settings.update({ 'title': '{0} - Lightness Functions'.format(', '.join(functions)), 'x_label': 'Luminance Y', 'y_label': 'Lightness L*', 'x_tighten': True, 'legend': True, 'legend_location': 'upper left', 'x_ticker': True, 'y_ticker': True, 'grid': True, 'limits': [0, 100, 0, 100], 'aspect': 'equal'}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def blackbody_spectral_radiance_plot( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> blackbody_spectral_radiance_plot() # doctest: +SKIP True """ canvas(**kwargs) cmfs = get_cmfs(cmfs) matplotlib.pyplot.subplots_adjust(hspace=0.4) spd = blackbody_spd(temperature, cmfs.shape) matplotlib.pyplot.figure(1) matplotlib.pyplot.subplot(211) settings = { 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', 'standalone': False} settings.update(kwargs) single_spd_plot(spd, cmfs.name, **settings) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise(XYZ_to_sRGB(XYZ / 100)) matplotlib.pyplot.subplot(212) settings = {'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'aspect': None, 'standalone': False} single_colour_plot(colour_parameter(name='', RGB=RGB), **settings) settings = { 'standalone': True} settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def blackbody_colours_plot(shape=SpectralShape(150, 12500, 50), cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots blackbody colours. Parameters ---------- shape : SpectralShape, optional Spectral shape to use as plot boundaries. cmfs : unicode, optional Standard observer colour matching functions. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> blackbody_colours_plot() # doctest: +SKIP True """ cmfs = get_cmfs(cmfs) colours = [] temperatures = [] for temperature in shape: spd = blackbody_spd(temperature, cmfs.shape) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise(XYZ_to_sRGB(XYZ / 100)) colours.append(RGB) temperatures.append(temperature) settings = { 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': '', 'x_tighten': True, 'x_ticker': True, 'y_ticker': False} settings.update(kwargs) return colour_parameters_plot([colour_parameter(x=x[0], RGB=x[1]) for x in tuple(zip(temperatures, colours))], **settings)