Source code for colour.plotting.diagrams

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

"""
CIE Chromaticity Diagrams Plotting
==================================

Defines the *CIE* chromaticity diagrams plotting objects:

-   :func:`CIE_1931_chromaticity_diagram_plot`
-   :func:`CIE_1960_UCS_chromaticity_diagram_plot`
-   :func:`CIE_1976_UCS_chromaticity_diagram_plot`
-   :func:`spds_CIE_1931_chromaticity_diagram_plot`
-   :func:`spds_CIE_1960_UCS_chromaticity_diagram_plot`
-   :func:`spds_CIE_1976_UCS_chromaticity_diagram_plot`
"""

from __future__ import division

import bisect
import os

import matplotlib
import matplotlib.image
import matplotlib.path
import numpy as np
import pylab

from colour.algebra import normalise
from colour.colorimetry import ILLUMINANTS, spectral_to_XYZ
from colour.models import (
    UCS_uv_to_xy,
    XYZ_to_UCS,
    XYZ_to_xy,
    UCS_to_uv,
    xy_to_XYZ,
    XYZ_to_Luv,
    Luv_to_uv,
    Luv_uv_to_xy,
    XYZ_to_sRGB)
from colour.plotting import (
    DEFAULT_FIGURE_WIDTH,
    PLOTTING_RESOURCES_DIRECTORY,
    canvas,
    decorate,
    boundaries,
    display,
    get_cmfs)

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

__all__ = ['CIE_1931_chromaticity_diagram_colours_plot',
           'CIE_1931_chromaticity_diagram_plot',
           'CIE_1960_UCS_chromaticity_diagram_colours_plot',
           'CIE_1960_UCS_chromaticity_diagram_plot',
           'CIE_1976_UCS_chromaticity_diagram_colours_plot',
           'CIE_1976_UCS_chromaticity_diagram_plot',
           'spds_CIE_1931_chromaticity_diagram_plot',
           'spds_CIE_1960_UCS_chromaticity_diagram_plot',
           'spds_CIE_1976_UCS_chromaticity_diagram_plot']


[docs]def CIE_1931_chromaticity_diagram_colours_plot( surface=1.25, spacing=0.00075, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1931 Chromaticity Diagram* colours. Parameters ---------- surface : numeric, optional Generated markers surface. spacing : numeric, optional Spacing between markers. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1931_chromaticity_diagram_colours_plot() # doctest: +SKIP True """ settings = {'figure_size': (32, 32)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) illuminant = ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('E') XYZs = [value for key, value in cmfs] path = matplotlib.path.Path([XYZ_to_xy(x) for x in XYZs]) x_dot, y_dot, colours = [], [], [] for i in np.arange(0, 1, spacing): for j in np.arange(0, 1, spacing): if path.contains_path(matplotlib.path.Path([[i, j], [i, j]])): x_dot.append(i) y_dot.append(j) XYZ = xy_to_XYZ((i, j)) RGB = normalise(XYZ_to_sRGB(XYZ, illuminant)) colours.append(RGB) pylab.scatter(x_dot, y_dot, color=colours, s=surface) settings.update({ 'no_ticks': True, 'bounding_box': [0, 1, 0, 1], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def CIE_1931_chromaticity_diagram_plot( cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1931 Chromaticity Diagram*. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1931_chromaticity_diagram_plot() # doctest: +SKIP True """ settings = {'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) image = matplotlib.image.imread( os.path.join(PLOTTING_RESOURCES_DIRECTORY, 'CIE_1931_Chromaticity_Diagram_{0}_Large.png'.format( cmfs.name.replace(' ', '_')))) pylab.imshow(image, interpolation='nearest', extent=(0, 1, 0, 1)) labels = ( [390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600, 620, 700]) wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) xy = np.array([XYZ_to_xy(XYZ) for XYZ in cmfs.values]) wavelengths_chromaticity_coordinates = dict(tuple(zip(wavelengths, xy))) pylab.plot(xy[:, 0], xy[:, 1], color='black', linewidth=2) pylab.plot((xy[-1][0], xy[0][0]), (xy[-1][1], xy[0][1]), color='black', linewidth=2) for label in labels: x, y = wavelengths_chromaticity_coordinates.get(label) pylab.plot(x, y, 'o', color='black', linewidth=2) index = bisect.bisect(wavelengths, label) left = wavelengths[index - 1] if index >= 0 else wavelengths[index] right = (wavelengths[index] if index < len(wavelengths) else wavelengths[-1]) dx = (wavelengths_chromaticity_coordinates.get(right)[0] - wavelengths_chromaticity_coordinates.get(left)[0]) dy = (wavelengths_chromaticity_coordinates.get(right)[1] - wavelengths_chromaticity_coordinates.get(left)[1]) norme = lambda x: x / np.linalg.norm(x) xy = np.array([x, y]) direction = np.array((-dy, dx)) normal = (np.array((-dy, dx)) if np.dot(norme(xy - equal_energy), norme(direction)) > 0 else np.array((dy, -dx))) normal = norme(normal) normal /= 25 pylab.plot([x, x + normal[0] * 0.75], [y, y + normal[1] * 0.75], color='black', linewidth=1.5) pylab.text(x + normal[0], y + normal[1], label, clip_on=True, ha='left' if normal[0] >= 0 else 'right', va='center', fontdict={'size': 'small'}) settings.update({ 'title': 'CIE 1931 Chromaticity Diagram - {0}'.format(cmfs.title), 'x_label': 'CIE x', 'y_label': 'CIE y', 'x_ticker': True, 'y_ticker': True, 'grid': True, 'bounding_box': [-0.1, 0.9, -0.1, 0.9], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def CIE_1960_UCS_chromaticity_diagram_colours_plot( surface=1.25, spacing=0.00075, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1960 UCS Chromaticity Diagram* colours. Parameters ---------- surface : numeric, optional Generated markers surface. spacing : numeric, optional Spacing between markers. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1960_UCS_chromaticity_diagram_colours_plot() # doctest: +SKIP True """ settings = {'figure_size': (32, 32)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) illuminant = ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('E') uv = np.array([UCS_to_uv(XYZ_to_UCS(XYZ)) for XYZ in cmfs.values]) path = matplotlib.path.Path(uv) x_dot, y_dot, colours = [], [], [] for i in np.arange(0, 1, spacing): for j in np.arange(0, 1, spacing): if path.contains_path(matplotlib.path.Path([[i, j], [i, j]])): x_dot.append(i) y_dot.append(j) XYZ = xy_to_XYZ(UCS_uv_to_xy((i, j))) RGB = normalise(XYZ_to_sRGB(XYZ, illuminant)) colours.append(RGB) pylab.scatter(x_dot, y_dot, color=colours, s=surface) settings.update({ 'no_ticks': True, 'bounding_box': [0, 1, 0, 1], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def CIE_1960_UCS_chromaticity_diagram_plot( cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1960_UCS_chromaticity_diagram_plot() # doctest: +SKIP True """ settings = {'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) image = matplotlib.image.imread( os.path.join(PLOTTING_RESOURCES_DIRECTORY, 'CIE_1960_UCS_Chromaticity_Diagram_{0}_Large.png'.format( cmfs.name.replace(' ', '_')))) pylab.imshow(image, interpolation='nearest', extent=(0, 1, 0, 1)) labels = [420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 680] wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) uv = np.array([UCS_to_uv(XYZ_to_UCS(XYZ)) for XYZ in cmfs.values]) wavelengths_chromaticity_coordinates = dict(tuple(zip(wavelengths, uv))) pylab.plot(uv[:, 0], uv[:, 1], color='black', linewidth=2) pylab.plot((uv[-1][0], uv[0][0]), (uv[-1][1], uv[0][1]), color='black', linewidth=2) for label in labels: u, v = wavelengths_chromaticity_coordinates.get(label) pylab.plot(u, v, 'o', color='black', linewidth=2) index = bisect.bisect(wavelengths, label) left = wavelengths[index - 1] if index >= 0 else wavelengths[index] right = (wavelengths[index] if index < len(wavelengths) else wavelengths[-1]) dx = (wavelengths_chromaticity_coordinates.get(right)[0] - wavelengths_chromaticity_coordinates.get(left)[0]) dy = (wavelengths_chromaticity_coordinates.get(right)[1] - wavelengths_chromaticity_coordinates.get(left)[1]) norme = lambda x: x / np.linalg.norm(x) uv = np.array([u, v]) direction = np.array((-dy, dx)) normal = (np.array((-dy, dx)) if np.dot(norme(uv - equal_energy), norme(direction)) > 0 else np.array((dy, -dx))) normal = norme(normal) normal /= 25 pylab.plot([u, u + normal[0] * 0.75], [v, v + normal[1] * 0.75], color='black', linewidth=1.5) pylab.text(u + normal[0], v + normal[1], label, clip_on=True, ha='left' if normal[0] >= 0 else 'right', va='center', fontdict={'size': 'small'}) settings.update({ 'title': 'CIE 1960 UCS Chromaticity Diagram - {0}'.format(cmfs.title), 'x_label': 'CIE u', 'y_label': 'CIE v', 'x_ticker': True, 'y_ticker': True, 'grid': True, 'bounding_box': [-0.075, 0.675, -0.15, 0.6], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def CIE_1976_UCS_chromaticity_diagram_colours_plot( surface=1.25, spacing=0.00075, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1976 UCS Chromaticity Diagram* colours. Parameters ---------- surface : numeric, optional Generated markers surface. spacing : numeric, optional Spacing between markers. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1976_UCS_chromaticity_diagram_colours_plot() # doctest: +SKIP True """ settings = {'figure_size': (32, 32)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) illuminant = ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50') uv = np.array([Luv_to_uv(XYZ_to_Luv(XYZ, illuminant)) for XYZ in cmfs.values]) path = matplotlib.path.Path(uv) x_dot, y_dot, colours = [], [], [] for i in np.arange(0, 1, spacing): for j in np.arange(0, 1, spacing): if path.contains_path(matplotlib.path.Path([[i, j], [i, j]])): x_dot.append(i) y_dot.append(j) XYZ = xy_to_XYZ(Luv_uv_to_xy((i, j))) RGB = normalise(XYZ_to_sRGB(XYZ, illuminant)) colours.append(RGB) pylab.scatter(x_dot, y_dot, color=colours, s=surface) settings.update({ 'no_ticks': True, 'bounding_box': [0, 1, 0, 1], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def CIE_1976_UCS_chromaticity_diagram_plot( cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1976 UCS Chromaticity Diagram*. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1976_UCS_chromaticity_diagram_plot() # doctest: +SKIP True """ settings = {'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) image = matplotlib.image.imread( os.path.join(PLOTTING_RESOURCES_DIRECTORY, 'CIE_1976_UCS_Chromaticity_Diagram_{0}_Large.png'.format( cmfs.name.replace(' ', '_')))) pylab.imshow(image, interpolation='nearest', extent=(0, 1, 0, 1)) labels = [420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 680] wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) illuminant = ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50') uv = np.array([Luv_to_uv(XYZ_to_Luv(XYZ, illuminant)) for XYZ in cmfs.values]) wavelengths_chromaticity_coordinates = dict(zip(wavelengths, uv)) pylab.plot(uv[:, 0], uv[:, 1], color='black', linewidth=2) pylab.plot((uv[-1][0], uv[0][0]), (uv[-1][1], uv[0][1]), color='black', linewidth=2) for label in labels: u, v = wavelengths_chromaticity_coordinates.get(label) pylab.plot(u, v, 'o', color='black', linewidth=2) index = bisect.bisect(wavelengths, label) left = wavelengths[index - 1] if index >= 0 else wavelengths[index] right = (wavelengths[index] if index < len(wavelengths) else wavelengths[-1]) dx = (wavelengths_chromaticity_coordinates.get(right)[0] - wavelengths_chromaticity_coordinates.get(left)[0]) dy = (wavelengths_chromaticity_coordinates.get(right)[1] - wavelengths_chromaticity_coordinates.get(left)[1]) norme = lambda x: x / np.linalg.norm(x) uv = np.array([u, v]) direction = np.array((-dy, dx)) normal = (np.array((-dy, dx)) if np.dot(norme(uv - equal_energy), norme(direction)) > 0 else np.array((dy, -dx))) normal = norme(normal) normal /= 25 pylab.plot([u, u + normal[0] * 0.75], [v, v + normal[1] * 0.75], color='black', linewidth=1.5) pylab.text(u + normal[0], v + normal[1], label, clip_on=True, ha='left' if normal[0] >= 0 else 'right', va='center', fontdict={'size': 'small'}) settings.update({ 'title': 'CIE 1976 UCS Chromaticity Diagram - {0}'.format(cmfs.title), 'x_label': 'CIE u\'', 'y_label': 'CIE v\'', 'x_ticker': True, 'y_ticker': True, 'grid': True, 'bounding_box': [-0.1, .7, -.1, .7], 'bbox_inches': 'tight', 'pad_inches': 0}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
[docs]def spds_CIE_1931_chromaticity_diagram_plot( spds, cmfs='CIE 1931 2 Degree Standard Observer', annotate=True, **kwargs): """ Plots given spectral power distribution chromaticity coordinates into the *CIE 1931 Chromaticity Diagram*. Parameters ---------- spds : list, optional Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. annotate : bool Should resulting chromaticity coordinates annotated with their respective spectral power distribution names. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> A = ILLUMINANTS_RELATIVE_SPDS['A'] >>> D65 = ILLUMINANTS_RELATIVE_SPDS['D65'] >>> spds_CIE_1931_chromaticity_diagram_plot([A, D65]) # doctest: +SKIP True """ CIE_1931_chromaticity_diagram_plot(standalone=False, **kwargs) cmfs = get_cmfs(cmfs) cmfs_shape = cmfs.shape for spd in spds: spd = spd.clone().align(cmfs_shape) XYZ = spectral_to_XYZ(spd) / 100 xy = XYZ_to_xy(XYZ) pylab.plot(xy[0], xy[1], 'o', color='white') if spd.name is not None and annotate: pylab.annotate(spd.name, xy=xy, xytext=(50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=0.2')) display(standalone=True)
[docs]def spds_CIE_1960_UCS_chromaticity_diagram_plot( spds, cmfs='CIE 1931 2 Degree Standard Observer', annotate=True, **kwargs): """ Plots given spectral power distribution chromaticity coordinates into the *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- spds : list, optional Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. annotate : bool Should resulting chromaticity coordinates annotated with their respective spectral power distribution names. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> A = ILLUMINANTS_RELATIVE_SPDS['A'] >>> D65 = ILLUMINANTS_RELATIVE_SPDS['D65'] >>> spds_CIE_1960_UCS_chromaticity_diagram_plot([A, D65]) # doctest: +SKIP True """ CIE_1960_UCS_chromaticity_diagram_plot(standalone=False, **kwargs) cmfs = get_cmfs(cmfs) cmfs_shape = cmfs.shape for spd in spds: spd = spd.clone().align(cmfs_shape) XYZ = spectral_to_XYZ(spd) / 100 uv = UCS_to_uv(XYZ_to_UCS(XYZ)) pylab.plot(uv[0], uv[1], 'o', color='white') if spd.name is not None and annotate: pylab.annotate(spd.name, xy=uv, xytext=(50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=0.2')) display(standalone=True)
[docs]def spds_CIE_1976_UCS_chromaticity_diagram_plot( spds, cmfs='CIE 1931 2 Degree Standard Observer', annotate=True, **kwargs): """ Plots given spectral power distribution chromaticity coordinates into the *CIE 1976 UCS Chromaticity Diagram*. Parameters ---------- spds : list, optional Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. annotate : bool Should resulting chromaticity coordinates annotated with their respective spectral power distribution names. \*\*kwargs : \*\* Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> A = ILLUMINANTS_RELATIVE_SPDS['A'] >>> D65 = ILLUMINANTS_RELATIVE_SPDS['D65'] >>> spds_CIE_1976_UCS_chromaticity_diagram_plot([A, D65]) # doctest: +SKIP True """ CIE_1976_UCS_chromaticity_diagram_plot(standalone=False, **kwargs) cmfs = get_cmfs(cmfs) cmfs_shape = cmfs.shape for spd in spds: spd = spd.clone().align(cmfs_shape) XYZ = spectral_to_XYZ(spd) / 100 uv = Luv_to_uv(XYZ_to_Luv(XYZ)) pylab.plot(uv[0], uv[1], 'o', color='white') if spd.name is not None and annotate: pylab.annotate(spd.name, xy=uv, xytext=(50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=0.2')) display(standalone=True)