Colour - Tutorial

Colour spreads over various domains of the Colour Science from colour models to optical phenomenons, this tutorial will not give you a complete overview of the API but will still be good introduction.

Note: A directory full of examples is available at this path in your Colour installation: colour/examples. You can also explore it directly on Github: https://github.com/colour-science/colour/tree/master/colour/examples


In [1]:
%matplotlib inline

In [2]:
from colour.plotting import *

visible_spectrum_plot()


Out[2]:
True

Overview

Colour is organised around various sub-packages:

  • adaptation: Chromatic adaptation models and transformations.
  • algebra: Algebra utilities.
  • appearance: Colour appearance models.
  • characterisation: Colour fitting and camera characterisation.
  • colorimetry: Core objects for colour computations.
  • constants: CIE and CODATA constants.
  • corresponding: Corresponding colour chromaticities computations.
  • difference: Colour difference computations.
  • examples: Examples for the sub-packages.
  • io: Input / output objects.
  • models: Colour models.
  • notation: Colour notation systems.
  • phenomenons: Computation of various optical phenomenons.
  • plotting: Diagrams, figures, etc...
  • quality: Colour quality computation.
  • recovery: Reflectance recovery.
  • temperature: Colour temperature and correlated colour temperature computation.
  • utilities: Various utilities and data structures.
  • volume: Colourspace volumes computation and optimal colour stimuli.

Most of the public API is available from the root colour namespace:


In [3]:
import colour

print(colour.__all__[:5] + ['...'])


['CHROMATIC_ADAPTATION_TRANSFORMS', 'XYZ_SCALING_CAT', 'VON_KRIES_CAT', 'BRADFORD_CAT', 'SHARP_CAT', '...']

The various sub-packages also expose their public API:


In [4]:
from pprint import pprint

import colour.plotting

for sub_package in ('adaptation', 'algebra', 'appearance', 'characterisation',
                    'colorimetry', 'constants', 'corresponding', 'difference', 
                    'io', 'models', 'notation', 'phenomenons', 'plotting', 
                    'quality', 'recovery', 'temperature', 'utilities',
                    'volume'):
    print(sub_package.title())
    pprint(getattr(colour, sub_package).__all__)
    print('\n')


Adaptation
['CHROMATIC_ADAPTATION_TRANSFORMS',
 'XYZ_SCALING_CAT',
 'VON_KRIES_CAT',
 'BRADFORD_CAT',
 'SHARP_CAT',
 'FAIRCHILD_CAT',
 'CMCCAT97_CAT',
 'CMCCAT2000_CAT',
 'CAT02_CAT',
 'CAT02_BRILL_CAT',
 'BS_CAT',
 'BS_PC_CAT',
 'chromatic_adaptation_matrix_VonKries',
 'chromatic_adaptation_VonKries',
 'chromatic_adaptation_Fairchild1990',
 'CMCCAT2000_InductionFactors',
 'CMCCAT2000_VIEWING_CONDITIONS',
 'CMCCAT2000_forward',
 'CMCCAT2000_reverse',
 'chromatic_adaptation_CMCCAT2000',
 'chromatic_adaptation_CIE1994']


Algebra
['cartesian_to_spherical',
 'spherical_to_cartesian',
 'cartesian_to_cylindrical',
 'cylindrical_to_cartesian',
 'Extrapolator1d',
 'LinearInterpolator',
 'SpragueInterpolator',
 'CubicSplineInterpolator',
 'PchipInterpolator',
 'is_identity',
 'random_triplet_generator']


Appearance
['Hunt_InductionFactors',
 'HUNT_VIEWING_CONDITIONS',
 'Hunt_Specification',
 'XYZ_to_Hunt',
 'ATD95_Specification',
 'XYZ_to_ATD95',
 'CIECAM02_InductionFactors',
 'CIECAM02_VIEWING_CONDITIONS',
 'CIECAM02_Specification',
 'XYZ_to_CIECAM02',
 'CIECAM02_to_XYZ',
 'LLAB_VIEWING_CONDITIONS',
 'LLAB_Specification',
 'XYZ_to_LLAB',
 'Nayatani95_Specification',
 'XYZ_to_Nayatani95',
 'RLAB_VIEWING_CONDITIONS',
 'RLAB_D_FACTOR',
 'RLAB_Specification',
 'XYZ_to_RLAB']


Characterisation
['COLOURCHECKERS',
 'COLOURCHECKER_INDEXES_TO_NAMES_MAPPING',
 'COLOURCHECKERS_SPDS',
 'first_order_colour_fit']


Colorimetry
['DEFAULT_WAVELENGTH_DECIMALS',
 'SpectralMapping',
 'SpectralShape',
 'SpectralPowerDistribution',
 'TriSpectralPowerDistribution',
 'DEFAULT_SPECTRAL_SHAPE',
 'constant_spd',
 'zeros_spd',
 'ones_spd',
 'blackbody_spd',
 'blackbody_spectral_radiance',
 'planck_law',
 'LMS_ConeFundamentals',
 'RGB_ColourMatchingFunctions',
 'XYZ_ColourMatchingFunctions',
 'CMFS',
 'LMS_CMFS',
 'RGB_CMFS',
 'STANDARD_OBSERVERS_CMFS',
 'ILLUMINANTS',
 'D_ILLUMINANTS_S_SPDS',
 'ILLUMINANTS_RELATIVE_SPDS',
 'LIGHT_SOURCES',
 'LIGHT_SOURCES_RELATIVE_SPDS',
 'LEFS',
 'PHOTOPIC_LEFS',
 'SCOTOPIC_LEFS',
 'BANDPASS_CORRECTION_METHODS',
 'bandpass_correction',
 'bandpass_correction_Stearns1988',
 'D_illuminant_relative_spd',
 'mesopic_luminous_efficiency_function',
 'mesopic_weighting_function',
 'LIGHTNESS_METHODS',
 'lightness',
 'lightness_Glasser1958',
 'lightness_Wyszecki1963',
 'lightness_1976',
 'LUMINANCE_METHODS',
 'luminance',
 'luminance_Newhall1943',
 'luminance_ASTMD153508',
 'luminance_1976',
 'luminous_flux',
 'luminous_efficacy',
 'RGB_10_degree_cmfs_to_LMS_10_degree_cmfs',
 'RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs',
 'RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs',
 'LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs',
 'LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs',
 'spectral_to_XYZ',
 'wavelength_to_XYZ',
 'WHITENESS_METHODS',
 'whiteness',
 'whiteness_Berger1959',
 'whiteness_Taube1960',
 'whiteness_Stensby1968',
 'whiteness_ASTM313',
 'whiteness_Ganz1979',
 'whiteness_CIE2004']


Constants
['CIE_E',
 'CIE_K',
 'K_M',
 'KP_M',
 'AVOGADRO_CONSTANT',
 'BOLTZMANN_CONSTANT',
 'LIGHT_SPEED',
 'PLANCK_CONSTANT',
 'FLOATING_POINT_NUMBER_PATTERN',
 'INTEGER_THRESHOLD',
 'EPSILON']


Corresponding
['BRENEMAN_EXPERIMENTS',
 'BRENEMAN_EXPERIMENTS_PRIMARIES_CHROMATICITIES',
 'corresponding_chromaticities_prediction_CIE1994',
 'corresponding_chromaticities_prediction_CMCCAT2000',
 'corresponding_chromaticities_prediction_Fairchild1990',
 'corresponding_chromaticities_prediction_VonKries',
 'CORRESPONDING_CHROMATICITIES_PREDICTION_MODELS']


Difference
['DELTA_E_METHODS',
 'delta_E',
 'delta_E_CIE1976',
 'delta_E_CIE1994',
 'delta_E_CIE2000',
 'delta_E_CMC']


Io
['IES_TM2714_Spd',
 'read_image',
 'write_image',
 'read_spectral_data_from_csv_file',
 'read_spds_from_csv_file',
 'write_spds_to_csv_file',
 'read_spds_from_xrite_file']


Models
['XYZ_to_xyY',
 'xyY_to_XYZ',
 'xy_to_xyY',
 'xyY_to_xy',
 'xy_to_XYZ',
 'XYZ_to_xy',
 'RGB_Colourspace',
 'normalised_primary_matrix',
 'primaries_whitepoint',
 'RGB_luminance_equation',
 'RGB_luminance',
 'ACES_RICD',
 'RGB_COLOURSPACES',
 'ACES_2065_1_COLOURSPACE',
 'ACES_CC_COLOURSPACE',
 'ACES_PROXY_COLOURSPACE',
 'ACES_CG_COLOURSPACE',
 'ADOBE_RGB_1998_COLOURSPACE',
 'ADOBE_WIDE_GAMUT_RGB_COLOURSPACE',
 'ALEXA_WIDE_GAMUT_RGB_COLOURSPACE',
 'APPLE_RGB_COLOURSPACE',
 'BEST_RGB_COLOURSPACE',
 'BETA_RGB_COLOURSPACE',
 'CIE_RGB_COLOURSPACE',
 'CINEMA_GAMUT_COLOURSPACE',
 'COLOR_MATCH_RGB_COLOURSPACE',
 'DCI_P3_COLOURSPACE',
 'DCI_P3_P_COLOURSPACE',
 'DON_RGB_4_COLOURSPACE',
 'ECI_RGB_V2_COLOURSPACE',
 'EKTA_SPACE_PS_5_COLOURSPACE',
 'MAX_RGB_COLOURSPACE',
 'NTSC_RGB_COLOURSPACE',
 'PAL_SECAM_RGB_COLOURSPACE',
 'PROPHOTO_RGB_COLOURSPACE',
 'REC_709_COLOURSPACE',
 'REC_2020_COLOURSPACE',
 'RED_COLOR_COLOURSPACE',
 'RED_COLOR_2_COLOURSPACE',
 'RED_COLOR_3_COLOURSPACE',
 'RED_COLOR_4_COLOURSPACE',
 'DRAGON_COLOR_COLOURSPACE',
 'DRAGON_COLOR_2_COLOURSPACE',
 'RUSSELL_RGB_COLOURSPACE',
 'SMPTE_C_RGB_COLOURSPACE',
 'S_GAMUT_COLOURSPACE',
 'S_GAMUT3_COLOURSPACE',
 'S_GAMUT3_CINE_COLOURSPACE',
 'sRGB_COLOURSPACE',
 'V_GAMUT_COLOURSPACE',
 'XTREME_RGB_COLOURSPACE',
 'POINTER_GAMUT_ILLUMINANT',
 'POINTER_GAMUT_DATA',
 'POINTER_GAMUT_BOUNDARIES',
 'XYZ_to_Lab',
 'Lab_to_XYZ',
 'Lab_to_LCHab',
 'LCHab_to_Lab',
 'XYZ_to_Luv',
 'Luv_to_XYZ',
 'Luv_to_uv',
 'Luv_uv_to_xy',
 'Luv_to_LCHuv',
 'LCHuv_to_Luv',
 'XYZ_to_UCS',
 'UCS_to_XYZ',
 'UCS_to_uv',
 'UCS_uv_to_xy',
 'XYZ_to_UVW',
 'XYZ_to_IPT',
 'IPT_to_XYZ',
 'IPT_hue_angle',
 'LINEAR_TO_LOG_METHODS',
 'LOG_TO_LINEAR_METHODS',
 'linear_to_log',
 'log_to_linear',
 'linear_to_cineon',
 'cineon_to_linear',
 'linear_to_panalog',
 'panalog_to_linear',
 'linear_to_red_log_film',
 'red_log_film_to_linear',
 'linear_to_viper_log',
 'viper_log_to_linear',
 'linear_to_pivoted_log',
 'pivoted_log_to_linear',
 'linear_to_c_log',
 'c_log_to_linear',
 'linear_to_aces_cc',
 'aces_cc_to_linear',
 'linear_to_alexa_log_c',
 'alexa_log_c_to_linear',
 'linear_to_s_log',
 'linear_to_dci_p3_log',
 'dci_p3_log_to_linear',
 's_log_to_linear',
 'linear_to_s_log2',
 's_log2_to_linear',
 'linear_to_s_log3',
 's_log3_to_linear',
 'linear_to_v_log',
 'v_log_to_linear',
 'XYZ_to_RGB',
 'RGB_to_XYZ',
 'RGB_to_RGB',
 'XYZ_to_sRGB',
 'sRGB_to_XYZ',
 'spectral_to_aces_relative_exposure_values']


Notation
['MUNSELL_COLOURS_ALL',
 'MUNSELL_COLOURS_1929',
 'MUNSELL_COLOURS_REAL',
 'MUNSELL_COLOURS',
 'munsell_value',
 'MUNSELL_VALUE_METHODS',
 'munsell_value_Priest1920',
 'munsell_value_Munsell1933',
 'munsell_value_Moon1943',
 'munsell_value_Saunderson1944',
 'munsell_value_Ladd1955',
 'munsell_value_McCamy1987',
 'munsell_value_ASTMD153508',
 'munsell_colour_to_xyY',
 'xyY_to_munsell_colour']


Phenomenons
['scattering_cross_section',
 'rayleigh_optical_depth',
 'rayleigh_scattering',
 'rayleigh_scattering_spd']


Plotting
['ASTM_G_173_ETR',
 'PLOTTING_RESOURCES_DIRECTORY',
 'DEFAULT_FIGURE_ASPECT_RATIO',
 'DEFAULT_FIGURE_WIDTH',
 'DEFAULT_FIGURE_HEIGHT',
 'DEFAULT_FIGURE_SIZE',
 'DEFAULT_FONT_SIZE',
 'DEFAULT_COLOUR_CYCLE',
 'DEFAULT_HATCH_PATTERNS',
 'DEFAULT_PARAMETERS',
 'DEFAULT_PLOTTING_ILLUMINANT',
 'DEFAULT_PLOTTING_OECF',
 'ColourParameter',
 'colour_cycle',
 'canvas',
 'camera',
 'decorate',
 'boundaries',
 'display',
 'label_rectangles',
 'equal_axes3d',
 'get_RGB_colourspace',
 'get_cmfs',
 'get_illuminant',
 'colour_parameter',
 'colour_parameters_plot',
 'single_colour_plot',
 'multi_colour_plot',
 'image_plot',
 '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',
 'colour_checker_plot',
 'CIE_1931_chromaticity_diagram_plot',
 'CIE_1960_UCS_chromaticity_diagram_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',
 'corresponding_chromaticities_prediction_plot',
 'quad',
 'grid',
 'cube',
 'RGB_colourspaces_CIE_1931_chromaticity_diagram_plot',
 'RGB_colourspaces_CIE_1960_UCS_chromaticity_diagram_plot',
 'RGB_colourspaces_CIE_1976_UCS_chromaticity_diagram_plot',
 'RGB_chromaticity_coordinates_CIE_1931_chromaticity_diagram_plot',
 'RGB_chromaticity_coordinates_CIE_1960_UCS_chromaticity_diagram_plot',
 'RGB_chromaticity_coordinates_CIE_1976_UCS_chromaticity_diagram_plot',
 'single_transfer_function_plot',
 'multi_transfer_function_plot',
 'single_munsell_value_function_plot',
 'multi_munsell_value_function_plot',
 'single_rayleigh_scattering_spd_plot',
 'the_blue_sky_plot',
 'single_spd_colour_rendering_index_bars_plot',
 'multi_spd_colour_rendering_index_bars_plot',
 'single_spd_colour_quality_scale_bars_plot',
 'multi_spd_colour_quality_scale_bars_plot',
 'planckian_locus_CIE_1931_chromaticity_diagram_plot',
 'planckian_locus_CIE_1960_UCS_chromaticity_diagram_plot',
 'RGB_colourspaces_gamuts_plot',
 'RGB_scatter_plot']


Quality
['TCS_SPDS',
 'VS_SPDS',
 'CRI_Specification',
 'colour_rendering_index',
 'CQS_Specification',
 'colour_quality_scale']


Recovery
['SMITS_1999_SPDS', 'RGB_to_spectral_Smits1999']


Temperature
['CCT_TO_UV_METHODS',
 'UV_TO_CCT_METHODS',
 'CCT_to_uv',
 'CCT_to_uv_Ohno2013',
 'CCT_to_uv_Robertson1968',
 'uv_to_CCT',
 'uv_to_CCT_Ohno2013',
 'uv_to_CCT_Robertson1968',
 'CCT_TO_XY_METHODS',
 'XY_TO_CCT_METHODS',
 'CCT_to_xy',
 'CCT_to_xy_Kang2002',
 'CCT_to_xy_CIE_D',
 'xy_to_CCT',
 'xy_to_CCT_McCamy1992',
 'xy_to_CCT_Hernandez1999']


Utilities
['handle_numpy_errors',
 'ignore_numpy_errors',
 'raise_numpy_errors',
 'print_numpy_errors',
 'warn_numpy_errors',
 'ignore_python_warnings',
 'batch',
 'is_openimageio_installed',
 'is_scipy_installed',
 'is_iterable',
 'is_string',
 'is_numeric',
 'is_integer',
 'as_numeric',
 'closest',
 'normalise',
 'steps',
 'is_uniform',
 'in_array',
 'tstack',
 'tsplit',
 'row_as_diagonal',
 'dot_vector',
 'dot_matrix',
 'ArbitraryPrecisionMapping',
 'Lookup',
 'Structure',
 'CaseInsensitiveMapping',
 'message_box',
 'warning']


Volume
['ILLUMINANTS_OPTIMAL_COLOUR_STIMULI',
 'is_within_macadam_limits',
 'is_within_mesh_volume',
 'is_within_pointer_gamut',
 'is_within_visible_spectrum',
 'RGB_colourspace_limits',
 'RGB_colourspace_volume_MonteCarlo',
 'RGB_colourspace_volume_coverage_MonteCarlo',
 'RGB_colourspace_pointer_gamut_coverage_MonteCarlo',
 'RGB_colourspace_visible_spectrum_coverage_MonteCarlo']


The code is well documented and almost every docstrings have usage examples:


In [5]:
print(colour.CCT_to_uv_Ohno2013.__doc__)


    Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given
    correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}` and
    colour matching functions using Ohno (2013) method.

    Parameters
    ----------
    CCT : numeric
        Correlated colour temperature :math:`T_{cp}`.
    D_uv : numeric, optional
        :math:`\Delta_{uv}`.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

    Returns
    -------
    ndarray
        *CIE UCS* colourspace *uv* chromaticity coordinates.

    References
    ----------
    .. [4]  Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv.
            LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> cmfs = 'CIE 1931 2 Degree Standard Observer'
    >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs)
    >>> CCT = 6507.4342201047066
    >>> D_uv = 0.003223690901512735
    >>> CCT_to_uv_Ohno2013(CCT, D_uv, cmfs)  # doctest: +ELLIPSIS
    array([ 0.1978003...,  0.3122005...])
    

At the core of Colour is the colour.colorimetry sub-package, it defines the objects needed for spectral related computations and many others:


In [6]:
from pprint import pprint
import colour.colorimetry as colorimetry

pprint(colorimetry.__all__)


['DEFAULT_WAVELENGTH_DECIMALS',
 'SpectralMapping',
 'SpectralShape',
 'SpectralPowerDistribution',
 'TriSpectralPowerDistribution',
 'DEFAULT_SPECTRAL_SHAPE',
 'constant_spd',
 'zeros_spd',
 'ones_spd',
 'blackbody_spd',
 'blackbody_spectral_radiance',
 'planck_law',
 'LMS_ConeFundamentals',
 'RGB_ColourMatchingFunctions',
 'XYZ_ColourMatchingFunctions',
 'CMFS',
 'LMS_CMFS',
 'RGB_CMFS',
 'STANDARD_OBSERVERS_CMFS',
 'ILLUMINANTS',
 'D_ILLUMINANTS_S_SPDS',
 'ILLUMINANTS_RELATIVE_SPDS',
 'LIGHT_SOURCES',
 'LIGHT_SOURCES_RELATIVE_SPDS',
 'LEFS',
 'PHOTOPIC_LEFS',
 'SCOTOPIC_LEFS',
 'BANDPASS_CORRECTION_METHODS',
 'bandpass_correction',
 'bandpass_correction_Stearns1988',
 'D_illuminant_relative_spd',
 'mesopic_luminous_efficiency_function',
 'mesopic_weighting_function',
 'LIGHTNESS_METHODS',
 'lightness',
 'lightness_Glasser1958',
 'lightness_Wyszecki1963',
 'lightness_1976',
 'LUMINANCE_METHODS',
 'luminance',
 'luminance_Newhall1943',
 'luminance_ASTMD153508',
 'luminance_1976',
 'luminous_flux',
 'luminous_efficacy',
 'RGB_10_degree_cmfs_to_LMS_10_degree_cmfs',
 'RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs',
 'RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs',
 'LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs',
 'LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs',
 'spectral_to_XYZ',
 'wavelength_to_XYZ',
 'WHITENESS_METHODS',
 'whiteness',
 'whiteness_Berger1959',
 'whiteness_Taube1960',
 'whiteness_Stensby1968',
 'whiteness_ASTM313',
 'whiteness_Ganz1979',
 'whiteness_CIE2004']

Colour computations are based on a comprehensive dataset available in pretty much each sub-packages, for example colour.colorimetry.dataset defines the following data:


In [7]:
import colour.colorimetry.dataset as dataset

pprint(dataset.__all__)


['CMFS',
 'LMS_CMFS',
 'RGB_CMFS',
 'STANDARD_OBSERVERS_CMFS',
 'ILLUMINANTS',
 'D_ILLUMINANTS_S_SPDS',
 'ILLUMINANTS_RELATIVE_SPDS',
 'LIGHT_SOURCES',
 'LIGHT_SOURCES_RELATIVE_SPDS',
 'LEFS',
 'PHOTOPIC_LEFS',
 'SCOTOPIC_LEFS']

Spectral Computations

From Spectral Power Distribution

Whether it be a sample spectral power distribution, colour matching functions or illuminants, spectral data is manipulated using an object built with the colour.SpectralPowerDistribution class or based on it:


In [8]:
# Defining a sample spectral power distribution data.
sample_spd_data = {
    380: 0.048,
    385: 0.051,
    390: 0.055,
    395: 0.060,
    400: 0.065,
    405: 0.068,
    410: 0.068,
    415: 0.067,
    420: 0.064,
    425: 0.062,
    430: 0.059,
    435: 0.057,
    440: 0.055,
    445: 0.054,
    450: 0.053,
    455: 0.053,
    460: 0.052,
    465: 0.052,
    470: 0.052,
    475: 0.053,
    480: 0.054,
    485: 0.055,
    490: 0.057,
    495: 0.059,
    500: 0.061,
    505: 0.062,
    510: 0.065,
    515: 0.067,
    520: 0.070,
    525: 0.072,
    530: 0.074,
    535: 0.075,
    540: 0.076,
    545: 0.078,
    550: 0.079,
    555: 0.082,
    560: 0.087,
    565: 0.092,
    570: 0.100,
    575: 0.107,
    580: 0.115,
    585: 0.122,
    590: 0.129,
    595: 0.134,
    600: 0.138,
    605: 0.142,
    610: 0.146,
    615: 0.150,
    620: 0.154,
    625: 0.158,
    630: 0.163,
    635: 0.167,
    640: 0.173,
    645: 0.180,
    650: 0.188,
    655: 0.196,
    660: 0.204,
    665: 0.213,
    670: 0.222,
    675: 0.231,
    680: 0.242,
    685: 0.251,
    690: 0.261,
    695: 0.271,
    700: 0.282,
    705: 0.294,
    710: 0.305,
    715: 0.318,
    720: 0.334,
    725: 0.354,
    730: 0.372,
    735: 0.392,
    740: 0.409,
    745: 0.420,
    750: 0.436,
    755: 0.450,
    760: 0.462,
    765: 0.465,
    770: 0.448,
    775: 0.432,
    780: 0.421}

spd = colour.SpectralPowerDistribution('Sample', sample_spd_data)
print(spd)


<colour.colorimetry.spectrum.SpectralPowerDistribution object at 0x7f62051ff5d0>

The sample spectral power distribution can be easily plotted against the visible spectrum:


In [9]:
# Plotting the sample spectral power distribution.
single_spd_plot(spd)


Out[9]:
True

With the sample spectral power distribution defined, we can retrieve its shape:


In [10]:
# Displaying the sample spectral power distribution shape.
print(spd.shape)


(380.0, 780.0, 5.0)

The shape returned is an instance of colour.SpectralShape class:


In [11]:
repr(spd.shape)


Out[11]:
'SpectralShape(380.0, 780.0, 5.0)'

colour.SpectralShape is used throughout Colour to define spectral dimensions and is instantiated as follows:


In [12]:
# Using *colour.SpectralShape* with iteration.
shape = colour.SpectralShape(start=0, end=10, steps=1)
for wavelength in shape:
    print(wavelength)

# *colour.SpectralShape.range* method is providing the complete range of values. 
shape = colour.SpectralShape(0, 10, 0.5)
shape.range()


0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
Out[12]:
array([  0. ,   0.5,   1. ,   1.5,   2. ,   2.5,   3. ,   3.5,   4. ,
         4.5,   5. ,   5.5,   6. ,   6.5,   7. ,   7.5,   8. ,   8.5,
         9. ,   9.5,  10. ])

Colour defines three convenient objects to create constant spectral power distributions:

  • colour.constant_spd
  • colour.zeros_spd
  • colour.ones_spd

In [13]:
# Defining a constant spectral power distribution.
constant_spd = colour.constant_spd(100)
print('"Constant Spectral Power Distribution"')
print(constant_spd.shape)
print(constant_spd[400])

# Defining a zeros filled spectral power distribution.
print('\n"Zeros Filled Spectral Power Distribution"')
zeros_spd = colour.zeros_spd()
print(zeros_spd.shape)
print(zeros_spd[400])

# Defining a ones filled spectral power distribution.
print('\n"Ones Filled Spectral Power Distribution"')
ones_spd = colour.ones_spd()
print(ones_spd.shape)
print(ones_spd[400])


"Constant Spectral Power Distribution"
(360.0, 830.0, 1.0)
100.0

"Zeros Filled Spectral Power Distribution"
(360.0, 830.0, 1.0)
0.0

"Ones Filled Spectral Power Distribution"
(360.0, 830.0, 1.0)
1.0

By default the shape used by colour.constant_spd, colour.zeros_spd and colour.ones_spd is the one defined by colour.DEFAULT_SPECTRAL_SHAPE attribute using the CIE 1931 2° Standard Observer shape.


In [14]:
print(repr(colour.DEFAULT_SPECTRAL_SHAPE))


SpectralShape(360.0, 830.0, 1.0)

A custom shape can be passed to construct a constant spectral power distribution with tailored dimensions:


In [15]:
colour.ones_spd(colour.SpectralShape(400, 700, 5))[450]


Out[15]:
array(1.0)

The colour.SpectralPowerDistribution class supports the following arithmetical operations:

  • addition
  • subtraction
  • multiplication
  • division

In [16]:
spd1 = colour.ones_spd()
print('"Ones Filled Spectral Power Distribution"')
print(spd1[400])

print('\n"x2 Constant Multiplied"')
print((spd1 * 2)[400])

print('\n"+ Spectral Power Distribution"')
print((spd1 + colour.ones_spd())[400])


"Ones Filled Spectral Power Distribution"
1.0

"x2 Constant Multiplied"
2.0

"+ Spectral Power Distribution"
3.0

Often interpolation of the spectral power distribution is needed, this is achieved with the colour.SpectralPowerDistribution.interpolate method. Depending on the wavelengths uniformity, the default interpolation method will differ. Following CIE 167:2005 recommendation: The method developed by Sprague (1880) should be used for interpolating functions having a uniformly spaced independent variable and a Cubic Spline method for non-uniformly spaced independent variable. [1]</a>

We can check the uniformity of the sample spectral power distribution:


In [17]:
# Checking the sample spectral power distribution uniformity.
print(spd.is_uniform())


True

Since the sample spectral power distribution is uniform the interpolation will be using the colour.SpragueInterpolator interpolator.

Note: Interpolation happens in place and may alter your original data, use the colour.SpectralPowerDistribution.clone method to produce a copy of your spectral power distribution before interpolation.


In [18]:
# Cloning the sample spectral power distribution.
clone_spd = spd.clone()

# Interpolating the cloned sample spectral power distribution.
clone_spd.interpolate(colour.SpectralShape(400, 770, 1))
clone_spd[401]


Out[18]:
array(0.06580960000000001)

In [19]:
# Comparing the interpolated spectral power distribution with the original one.
multi_spd_plot([spd, clone_spd], bounding_box=[730,780, 0.1, 0.5])


Out[19]:
True

Extrapolation although dangerous can be used to help aligning two spectral power distributions together. CIE publication CIE 15:2004 "Colorimetry" recommends that unmeasured values may be set equal to the nearest measured value of the appropriate quantity in truncation: [2]</a>


In [20]:
# Extrapolating the cloned sample spectral power distribution.
clone_spd.extrapolate(colour.SpectralShape(340, 830))
clone_spd[340], clone_spd[830]


Out[20]:
(array(0.065), array(0.448))

The extrapolation can be forced to use Linear method instead of the Constant default method or even use arbitrary constant left and right values:


In [21]:
# Extrapolating the cloned sample spectral power distribution with *Linear* method.
# We first trim the spectral power distribution using the interpolation method,
# the steps being the same, no interpolation will actually occur.
clone_spd.interpolate(colour.SpectralShape(400, 700))  
clone_spd.extrapolate(colour.SpectralShape(340, 830), method='Linear', right=0)
clone_spd[340], clone_spd[830]


Out[21]:
(array(0.01642399999999955), array(0.0))

Aligning a spectral power distribution is a convenient way to first interpolatesthe current data within its original bounds then if needed extrapolates any missing values to match the requested shape:


In [22]:
# Aligning the cloned sample spectral power distribution.
# We first trim the spectral power distribution as above.
clone_spd.interpolate(colour.SpectralShape(400, 700))  
clone_spd.align(colour.SpectralShape(340, 830, 5))
clone_spd[340], clone_spd[830]


Out[22]:
(array(0.065), array(0.282))

The colour.SpectralPowerDistribution class also supports various arithmetic operations like addition, subtraction, multiplication or division with numeric and array_like variables or other colour.SpectralPowerDistribution class instances:


In [23]:
spd = colour.SpectralPowerDistribution('Sample', {410: 0.50, 420: 0.75, 430: 1., 440: 0.75, 450: 0.50})
print((spd.clone() + 1).values)
print((spd.clone() * 2).values)
print((spd * [0.35, 1.55, 0.75, 2.55, 0.95]).values)
print((spd * colour.constant_spd(2, spd.shape) * colour.constant_spd(3, spd.shape)).values)


[ 1.5   1.75  2.    1.75  1.5 ]
[ 1.   1.5  2.   1.5  1. ]
[ 0.175   1.1625  0.75    1.9125  0.475 ]
[  1.05    6.975   4.5    11.475   2.85 ]

The spectral power distribution can be normalised with an arbitrary factor:


In [24]:
print(spd.normalise().values)
print(spd.normalise(100).values)


[ 0.09150327  0.60784314  0.39215686  1.          0.24836601]
[   9.1503268    60.78431373   39.21568627  100.           24.83660131]

To Tristimulus Values

From a given spectral power distribution, CIE XYZ tristimulus values can be calculated:


In [25]:
spd = colour.SpectralPowerDistribution('Sample', sample_spd_data)
cmfs = colour.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
illuminant = colour.ILLUMINANTS_RELATIVE_SPDS['D65']

# Calculating the sample spectral power distribution *CIE XYZ* tristimulus values.
XYZ = colour.spectral_to_XYZ(spd, cmfs, illuminant)
print(XYZ)


[ 10.9706809    9.7027807    6.05480757]

Note: Output CIE XYZ colourspace matrix is in domain [0, 100].

From CIE XYZ Colourspace

CIE XYZ is the central colourspace for Colour Science from which many computations are available, cascading to even more computations:


In [26]:
# Displaying objects interacting directly with the *CIE XYZ* colourspace.
pprint([name for name in colour.__all__ if name.startswith('XYZ_to')])


['XYZ_to_Hunt',
 'XYZ_to_ATD95',
 'XYZ_to_CIECAM02',
 'XYZ_to_LLAB',
 'XYZ_to_Nayatani95',
 'XYZ_to_RLAB',
 'XYZ_to_xyY',
 'XYZ_to_xy',
 'XYZ_to_Lab',
 'XYZ_to_Luv',
 'XYZ_to_UCS',
 'XYZ_to_UVW',
 'XYZ_to_IPT',
 'XYZ_to_RGB',
 'XYZ_to_sRGB']

To Screen Colours

We can for instance converts the CIE XYZ tristimulus values into sRGB colourspace RGB values in order to display them on screen:


In [27]:
# The output domain of *colour.spectral_to_XYZ* is [0, 100] and the input domain of *colour.XYZ_to_sRGB* is [0, 1].
# We need to take it in account and rescale the input *CIE XYZ* colourspace matrix.
RGB = colour.XYZ_to_sRGB(XYZ / 100)
print(RGB)


[ 0.45678515  0.30983401  0.24860046]

In [28]:
# Plotting the *sRGB* colourspace colour of the *Sample* spectral power distribution.
single_colour_plot(colour_parameter('Sample', RGB), text_size=32)


Out[28]:
True

To Colour Rendition Charts

In the same way, we can compute values from a colour rendition chart sample.

Note: This is useful for render time checks in the VFX industry, where you can use a synthetic colour chart into your render and ensure the colour management is acting as expected.

The colour.characterisation sub-package contains the dataset for various colour rendition charts:


In [29]:
# Colour rendition charts chromaticity coordinates.
print(sorted(colour.characterisation.COLOURCHECKERS.keys()))

# Colour rendition charts spectral power distributions.
print(sorted(colour.characterisation.COLOURCHECKERS_SPDS.keys()))


[u'BabelColor Average', u'ColorChecker 1976', u'ColorChecker 2005', u'babel_average', u'cc2005']
[u'BabelColor Average', u'ColorChecker N Ohta', u'babel_average', u'cc_ohta']

Note: The above cc2005, babel_average and cc_ohta keys are convenient aliases for respectively ColorChecker 2005, BabelColor Average and ColorChecker N Ohta keys.


In [30]:
# Plotting the *sRGB* colourspace colour of *neutral 5 (.70 D)* patch.
patch_name = 'neutral 5 (.70 D)'
patch_spd = colour.COLOURCHECKERS_SPDS['ColorChecker N Ohta'][patch_name]
XYZ = colour.spectral_to_XYZ(patch_spd, cmfs, illuminant)
RGB = colour.XYZ_to_sRGB(XYZ / 100)

single_colour_plot(colour_parameter(patch_name.title(), RGB), text_size=32)


Out[30]:
True

Colour defines a convenient plotting object to draw synthetic colour rendition charts figures:


In [31]:
colour_checker_plot(colour_checker='ColorChecker 2005', text_display=False)


/colour-science/colour/colour/models/dataset/srgb.py:111: RuntimeWarning: invalid value encountered in power
  1.055 * (value ** (1 / 2.4)) - 0.055)
Out[31]:
True

To Chromaticity Coordinates

Given a spectral power distribution, chromaticity coordinates xy can be computed using the colour.XYZ_to_xy definition:


In [32]:
# Computing *xy* chromaticity coordinates for the *neutral 5 (.70 D)* patch.
xy =  colour.XYZ_to_xy(XYZ)
print(xy)


[ 0.31260313  0.32871262]

Chromaticity coordinates xy can be plotted into the CIE 1931 Chromaticity Diagram:


In [33]:
import pylab

# Plotting the *CIE 1931 Chromaticity Diagram*.
# The argument *standalone=False* is passed so that the plot doesn't get displayed
# and can be used as a basis for other plots.
CIE_1931_chromaticity_diagram_plot(standalone=False)

# Plotting the *xy* chromaticity coordinates.
x, y = xy
pylab.plot(x, y, 'o-', color='white')

# Annotating the plot.
pylab.annotate(patch_spd.name.title(),
               xy=xy,
               xytext=(-50, 30),
               textcoords='offset points',
               arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=-0.2'))

# Displaying the plot.
display(standalone=True)


Out[33]:
True

To More

We hope that this small introduction has been useful and gave you the envy to see more, if you want to explore the API a good place to start is the IPython Notebooks page.

References

  1. ^ CIE TC 1-38. (2005). 9. INTERPOLATION. In CIE 167:2005 Recommended Practice for Tabulating Spectral Data for Use in Colour Computations (pp. 14–19). ISBN:978-3-901-90641-1
  2. ^ CIE TC 1-48. (2004). CIE 015:2004 Colorimetry, 3rd Edition. CIE 015:2004 Colorimetry, 3rd Edition (pp. 1–82). ISBN:978-3-901-90633-6