<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Spectrum
Colour is defined as the characteristic of visual perception that can be described by attributes of hue, brightness (or lightness) and colourfulness (or saturation or chroma).
When necessary, to avoid confusion between other meanings of the word, the term "perceived colour" may be used.
Perceived colour depends on the spectral distribution of the colour stimulus, on the size, shape, structure and surround of the stimulus area, on the state of adaptation of the observer's visual system, and on the observer's experience of the prevailing and similar situations of observation. <a name="back_reference_1"></a><a href="#reference_1">[1]</a>
Light is the electromagnetic radiation that is considered from the point of view of its ability to excite the human visual system. <a name="back_reference_2"></a><a href="#reference_2">[2]</a>
The portion of the electromatic radiation frequencies perceived in the approximate wavelength range 360-780 nanometres (nm) is called the visible spectrum.
Step1:  The spectrum is defined as the display or specification of the monochromatic components of the radiation considered. <a name="back_reference_3"></a><a href="#reference_3">[3]</a>
At the core of Colour is the colour.colorimetry sub-package, it defines the objects needed for spectral related computations and many others
Step2:  Note
Step3:  Note
Step4:  The sample spectral power distribution can be easily plotted against the visible spectrum
Step5:  With the sample spectral power distribution defined, we can retrieve its shape
Step6:  The shape returned is an instance of colour.SpectralShape class
Step7:  colour.SpectralShape is used throughout Colour to define spectral dimensions and is instantiated as follows
Step8:  Colour defines three convenient objects to create constant spectral power distributions
Step9:  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.
Step10:  A custom shape can be passed to construct a constant spectral power distribution with tailored dimensions
Step11:  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
Step12:  Since the sample spectral power distribution is uniform the interpolation will be using the colour.SpragueInterpolator interpolator.
Note
Step13:  Extrapolation although dangerous can be used to help aligning two spectral power distributions together. CIE 015
Step14:  The underlying interpolator can be swapped for any of the Colour interpolators.
Step15:  The extrapolation behaviour can be changed for Linear method instead of the Constant default method or even use arbitrary constant left and right values
Step16:  Aligning a spectral power distribution is a convenient way to first interpolate the current data within its original bounds then if needed extrapolates any missing values to match the requested shape
Step17:  The colour.SpectralPowerDistribution class also supports various arithmetic operations like addition, subtraction, multiplication, division or exponentiation with numeric and array_like variables or other colour.SpectralPowerDistribution class instances
Step18:  The spectral power distribution can be normalised with an arbitrary factor
Step19:  Colour Matching Functions
In the late 1920's, Wright (1928) and Guild (1931) independently conducted a series of colour matching experiments to quantify the colour ability of an average human observer which laid the foundation for the specification of the CIE XYZ colourspace. The results obtained were summarized by the Wright & Guild 1931 2° RGB CMFs $\bar{r}(\lambda)$,$\bar{g}(\lambda)$,$\bar{b}(\lambda)$ colour matching functions
Step20:  With an RGB model of human vision based on Wright & Guild 1931 2° RGB CMFs $\bar{r}(\lambda)$,$\bar{g}(\lambda)$,$\bar{b}(\lambda)$ colour matching functions and for pragmatic reasons the CIE members developed a new colour space that would relate to the CIE RGB colourspace but for which all tristimulus values would be positive for real colours
Step21:  In the 1960's it appeared that cones were present in a larger region of eye than the one initially covered by the experiments that lead to the CIE 1931 2° Standard Observer specification.
As a result, colour computations done with the CIE 1931 2° Standard Observer do not always correlate to the visual observation.
In 1964, the CIE defined an additional standard observer
Step22:  Note
Step23:  Retrieving the CIE XYZ tristimulus values of any wavelength from colour matching functions is done using the colour.wavelength_to_XYZ definition, if the value requested is not available, the colour matching functions will be interpolated following CIE 167
<ASSISTANT_TASK:>
Python Code:
%matplotlib inline
import colour
from colour.plotting import *
colour.filter_warnings(True, False)
colour_plotting_defaults()
# Plotting the visible spectrum.
visible_spectrum_plot()
Explanation: Spectrum
Colour is defined as the characteristic of visual perception that can be described by attributes of hue, brightness (or lightness) and colourfulness (or saturation or chroma).
When necessary, to avoid confusion between other meanings of the word, the term "perceived colour" may be used.
Perceived colour depends on the spectral distribution of the colour stimulus, on the size, shape, structure and surround of the stimulus area, on the state of adaptation of the observer's visual system, and on the observer's experience of the prevailing and similar situations of observation. <a name="back_reference_1"></a><a href="#reference_1">[1]</a>
Light is the electromagnetic radiation that is considered from the point of view of its ability to excite the human visual system. <a name="back_reference_2"></a><a href="#reference_2">[2]</a>
The portion of the electromatic radiation frequencies perceived in the approximate wavelength range 360-780 nanometres (nm) is called the visible spectrum.
End of explanation
from pprint import pprint
import colour.colorimetry as colorimetry
pprint(colorimetry.__all__)
Explanation: The spectrum is defined as the display or specification of the monochromatic components of the radiation considered. <a name="back_reference_3"></a><a href="#reference_3">[3]</a>
At the core of Colour is the colour.colorimetry sub-package, it defines the objects needed for spectral related computations and many others:
End of explanation
import colour.colorimetry.dataset as dataset
pprint(dataset.__all__)
Explanation: Note: colour.colorimetry sub-package public API is directly available from colour namespace.
Colour computations are based on a comprehensive dataset available in pretty much each sub-packages, for example colour.colorimetry.dataset defines the following data:
End of explanation
import colour
# Defining a sample spectral power distribution data.
sample_spd_data = {
    380: 0.048,
    385: 0.051,
    390: 0.055,
    395: 0.06,
    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_spd_data, name='Sample')
print(spd)
Explanation: Note: colour.colorimetry.dataset sub-package public API is directly available from colour namespace.
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:
End of explanation
# Plotting the sample spectral power distribution.
single_spd_plot(spd)
Explanation: The sample spectral power distribution can be easily plotted against the visible spectrum:
End of explanation
# Displaying the sample spectral power distribution shape.
print(spd.shape)
Explanation: With the sample spectral power distribution defined, we can retrieve its shape:
End of explanation
repr(spd.shape)
Explanation: The shape returned is an instance of colour.SpectralShape class:
End of explanation
# Using *colour.SpectralShape* with iteration.
shape = colour.SpectralShape(start=0, end=10, interval=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()
Explanation: colour.SpectralShape is used throughout Colour to define spectral dimensions and is instantiated as follows:
End of explanation
# 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])
Explanation: Colour defines three convenient objects to create constant spectral power distributions:
colour.constant_spd
colour.zeros_spd
colour.ones_spd
End of explanation
print(repr(colour.DEFAULT_SPECTRAL_SHAPE))
Explanation: 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.
End of explanation
colour.ones_spd(colour.SpectralShape(400, 700, 5))[450]
Explanation: A custom shape can be passed to construct a constant spectral power distribution with tailored dimensions:
End of explanation
# Checking the sample spectral power distribution uniformity.
print(spd.is_uniform())
Explanation: 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. <a name="back_reference_4"></a><a href="#reference_4">[4]</a>
We can check the uniformity of the sample spectral power distribution:
End of explanation
# Copying the sample spectral power distribution.
spd_copy = spd.copy()
# Interpolating the copied sample spectral power distribution.
spd_copy.interpolate(colour.SpectralShape(400, 770, 1))
spd_copy[401]
# Comparing the interpolated spectral power distribution with the original one.
multi_spd_plot([spd, spd_copy], bounding_box=[730,780, 0.1, 0.5])
Explanation: 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.
End of explanation
# Extrapolating the copied sample spectral power distribution.
spd_copy.extrapolate(colour.SpectralShape(340, 830))
spd_copy[340], spd_copy[830]
Explanation: Extrapolation although dangerous can be used to help aligning two spectral power distributions together. CIE 015:2004 Colorimetry, 3rd Edition recommends that unmeasured values may be set equal to the nearest measured value of the appropriate quantity in truncation: <a name="back_reference_5"></a><a href="#reference_5">[5]</a>
End of explanation
pprint([
    export for export in colour.algebra.interpolation.__all__
    if 'Interpolator' in export
])
# Changing interpolator while trimming the copied spectral power distribution.
spd_copy.interpolate(
    colour.SpectralShape(400, 700, 10), interpolator=colour.LinearInterpolator)
Explanation: The underlying interpolator can be swapped for any of the Colour interpolators.
End of explanation
# Extrapolating the copied sample spectral power distribution with *Linear* method.
spd_copy.extrapolate(
    colour.SpectralShape(340, 830),
    extrapolator_args={'method': 'Linear',
                       'right': 0})
spd_copy[340], spd_copy[830]
Explanation: The extrapolation behaviour can be changed for Linear method instead of the Constant default method or even use arbitrary constant left and right values:
End of explanation
# Aligning the cloned sample spectral power distribution.
# We first trim the spectral power distribution as above.
spd_copy.interpolate(colour.SpectralShape(400, 700))  
spd_copy.align(colour.SpectralShape(340, 830, 5))
spd_copy[340], spd_copy[830]
Explanation: Aligning a spectral power distribution is a convenient way to first interpolate the current data within its original bounds then if needed extrapolates any missing values to match the requested shape:
End of explanation
spd = colour.SpectralPowerDistribution({
    410: 0.25,
    420: 0.50,
    430: 0.75,
    440: 1.0,
    450: 0.75,
    460: 0.50,
    480: 0.25
})
print((spd.copy() + 1).values)
print((spd.copy() * 2).values)
print((spd * [0.35, 1.55, 0.75, 2.55, 0.95, 0.65, 0.15]).values)
print((spd * colour.constant_spd(2, spd.shape) * colour.constant_spd(3, spd.shape)).values)
Explanation: The colour.SpectralPowerDistribution class also supports various arithmetic operations like addition, subtraction, multiplication, division or exponentiation with numeric and array_like variables or other colour.SpectralPowerDistribution class instances:
End of explanation
print(spd.normalise().values)
print(spd.normalise(100).values)
Explanation: The spectral power distribution can be normalised with an arbitrary factor:
End of explanation
# Plotting *Wright & Guild 1931 2 Degree RGB CMFs* colour matching functions.
single_cmfs_plot('Wright & Guild 1931 2 Degree RGB CMFs')
Explanation: Colour Matching Functions
In the late 1920's, Wright (1928) and Guild (1931) independently conducted a series of colour matching experiments to quantify the colour ability of an average human observer which laid the foundation for the specification of the CIE XYZ colourspace. The results obtained were summarized by the Wright & Guild 1931 2° RGB CMFs $\bar{r}(\lambda)$,$\bar{g}(\lambda)$,$\bar{b}(\lambda)$ colour matching functions: they represent the amounts of three monochromatic primary colours $\textbf{R}$,$\textbf{G}$,$\textbf{B}$ needed to match the test colour at a single wavelength of light.
See Also: The Colour Matching Functions notebook for in-depth information about the colour matching functions.
End of explanation
# Plotting *CIE XYZ 1931 2 Degree Standard Observer* colour matching functions.
single_cmfs_plot('CIE 1931 2 Degree Standard Observer')
Explanation: With an RGB model of human vision based on Wright & Guild 1931 2° RGB CMFs $\bar{r}(\lambda)$,$\bar{g}(\lambda)$,$\bar{b}(\lambda)$ colour matching functions and for pragmatic reasons the CIE members developed a new colour space that would relate to the CIE RGB colourspace but for which all tristimulus values would be positive for real colours: CIE XYZ described with $\bar{x}(\lambda)$,$\bar{y}(\lambda)$,$\bar{z}(\lambda)$ colour matching functions.
End of explanation
spd = colour.SpectralPowerDistribution(sample_spd_data, name='Sample')
cmfs = colour.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
illuminant = colour.ILLUMINANTS_RELATIVE_SPDS['A']
# Calculating the sample spectral power distribution *CIE XYZ* tristimulus values.
colour.spectral_to_XYZ(spd, cmfs, illuminant)
Explanation: In the 1960's it appeared that cones were present in a larger region of eye than the one initially covered by the experiments that lead to the CIE 1931 2° Standard Observer specification.
As a result, colour computations done with the CIE 1931 2° Standard Observer do not always correlate to the visual observation.
In 1964, the CIE defined an additional standard observer: the CIE 1964 10° Standard Observer derived from the work of Stiles and Burch (1959), and Speranskaya (1959). The CIE 1964 10° Standard Observer is believed to be a better representation of the human vision spectral response and recommended when dealing with a field of view of more than 4°.
For example and as per CIE recommendation, the CIE 1964 10° Standard Observer is commonly used with spectrophotometers for colour measurements whereas colorimeters generally use the CIE 1931 2° Standard Observer for quality control and other colour evaluation applications.
CIE XYZ Tristimulus Values
The CIE XYZ tristimulus values specify a colour stimulus in terms of the visual system. Their values for colour of a surface with spectral reflectance $\beta(\lambda)$ under an illuminant of relative spectral power $S(\lambda)$ are calculated using the following equations: <a name="back_reference_6"></a><a href="#reference_6">[6]</a>
$$
\begin{equation}
X=k\int_{\lambda}\beta(\lambda)S(\lambda)\bar{x}(\lambda)d\lambda\
Y=k\int_{\lambda}\beta(\lambda)S(\lambda)\bar{y}(\lambda)d\lambda\
Z=k\int_{\lambda}\beta(\lambda)S(\lambda)\bar{z}(\lambda)d\lambda
\end{equation}
$$
where
$$
\begin{equation}
k=\cfrac{100}{\int_{\lambda}S(\lambda)\bar{y}(\lambda)d\lambda}
\end{equation}
$$
However in virtually all practical computations of CIE XYZ tristimulus values, the integrals are replaced by summations:
$$
\begin{equation}
X=k\sum\limits_{\lambda=\lambda_a}^{\lambda_b}\beta(\lambda)S(\lambda)\bar{x}(\lambda)\Delta\lambda\
Y=k\sum\limits_{\lambda=\lambda_a}^{\lambda_b}\beta(\lambda)S(\lambda)\bar{y}(\lambda)\Delta\lambda\
Z=k\sum\limits_{\lambda=\lambda_a}^{\lambda_b}\beta(\lambda)S(\lambda)\bar{z}(\lambda)\Delta\lambda\
\end{equation}
$$
where
$$
\begin{equation}
k=\cfrac{100}{\sum\limits_{\lambda=\lambda_a}^{\lambda_b}S(\lambda)\bar{y}(\lambda)\Delta\lambda}
\end{equation}
$$
Calculating the CIE XYZ tristimulus values of a colour stimulus is done using the colour.spectral_to_XYZ definition which follows ASTM E2022–11 and ASTM E308–15 practises computation method:
End of explanation
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.
chromaticity_diagram_plot_CIE1931(standalone=False)
# Calculating the *xy* chromaticity coordinates.
# 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.
x, y = colour.XYZ_to_xy(colour.spectral_to_XYZ(spd, cmfs, illuminant) / 100)
# Plotting the *xy* chromaticity coordinates.
pylab.plot(x, y, 'o-', color='white')
# Annotating the plot.
pylab.annotate(spd.name,
               xy=(x, y),
               xytext=(-50, 30),
               textcoords='offset points',
               arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=-0.2'))
# Displaying the plot.
render(standalone=True)
Explanation: Note: Output CIE XYZ colourspace matrix is in domain [0, 100].
CIE XYZ tristimulus values can be plotted into the CIE 1931 Chromaticity Diagram:
End of explanation
colour.wavelength_to_XYZ(546.1, colour.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'])
Explanation: Retrieving the CIE XYZ tristimulus values of any wavelength from colour matching functions is done using the colour.wavelength_to_XYZ definition, if the value requested is not available, the colour matching functions will be interpolated following CIE 167:2005 recommendation:
End of explanation
<END_TASK>