# -*- coding: utf-8 -*-
"""
Spectrum
========
Defines the classes handling spectral data computations:
- :class:`colour.SpectralShape`
- :class:`colour.SpectralPowerDistribution`
- :class:`colour.MultiSpectralPowerDistribution`
See Also
--------
`Spectrum Jupyter Notebook
<http://nbviewer.jupyter.org/github/colour-science/colour-notebooks/\
blob/master/notebooks/colorimetry/spectrum.ipynb>`_
References
----------
- :cite:`CIETC1-382005e` : 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
- :cite:`CIETC1-382005g` : CIE TC 1-38. (2005). EXTRAPOLATION. In
CIE 167:2005 Recommended Practice for Tabulating Spectral Data for Use in
Colour Computations (pp. 19-20). ISBN:978-3-901-90641-1
- :cite:`CIETC1-482004l` : CIE TC 1-48. (2004). Extrapolation. In
CIE 015:2004 Colorimetry, 3rd Edition (p. 24). ISBN:978-3-901-90633-6
"""
from __future__ import division, unicode_literals
import numpy as np
from six.moves import zip
from colour.algebra import (Extrapolator, CubicSplineInterpolator,
SpragueInterpolator)
from colour.constants import DEFAULT_FLOAT_DTYPE
from colour.continuous import Signal, MultiSignal
from colour.utilities import (as_numeric, first_item, is_iterable, is_numeric,
is_string, is_uniform, interval, warning)
from colour.utilities.deprecation import Removed, Renamed
__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2018 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'
__all__ = [
'SpectralShape', 'SpectralPowerDistribution',
'MultiSpectralPowerDistribution', 'DEFAULT_SPECTRAL_SHAPE', 'constant_spd',
'zeros_spd', 'ones_spd'
]
[docs]class SpectralShape(object):
"""
Defines the base object for spectral power distribution shape.
Parameters
----------
start : numeric, optional
Wavelength :math:`\lambda_{i}` range start in nm.
end : numeric, optional
Wavelength :math:`\lambda_{i}` range end in nm.
interval : numeric, optional
Wavelength :math:`\lambda_{i}` range interval.
Attributes
----------
start
end
interval
boundaries
Methods
-------
__str__
__repr__
__iter__
__contains__
__len__
__eq__
__ne__
range
Examples
--------
>>> SpectralShape(360, 830, 1)
SpectralShape(360, 830, 1)
"""
[docs] def __init__(self, start=None, end=None, interval=None):
self._range = None
self._start = None
self._end = None
self._interval = None
self.start = start
self.end = end
self.interval = interval
@property
def start(self):
"""
Getter and setter property for the spectral shape start.
Parameters
----------
value : numeric
Value to set the spectral shape start with.
Returns
-------
numeric
Spectral shape start.
"""
return self._start
@start.setter
def start(self, value):
"""
Setter for the **self.start** property.
"""
if value is not None:
assert is_numeric(value), (
'"{0}" attribute: "{1}" is not a "numeric"!'.format(
'start', value))
if self._end is not None:
assert value < self._end, (
'"{0}" attribute value must be strictly less than '
'"{1}"!'.format('start', self._end))
# Invalidating the *range* cache.
if value != self._start:
self._range = None
self._start = value
@property
def end(self):
"""
Getter and setter property for the spectral shape end.
Parameters
----------
value : numeric
Value to set the spectral shape end with.
Returns
-------
numeric
Spectral shape end.
"""
return self._end
@end.setter
def end(self, value):
"""
Setter for the **self.end** property.
"""
if value is not None:
assert is_numeric(value), (
'"{0}" attribute: "{1}" is not a "numeric"!'.format(
'end', value))
if self._start is not None:
assert value > self._start, (
'"{0}" attribute value must be strictly greater than '
'"{1}"!'.format('end', self._start))
# Invalidating the *range* cache.
if value != self._end:
self._range = None
self._end = value
@property
def interval(self):
"""
Getter and setter property for the spectral shape interval.
Parameters
----------
value : numeric
Value to set the spectral shape interval with.
Returns
-------
numeric
Spectral shape interval.
"""
return self._interval
@interval.setter
def interval(self, value):
"""
Setter for the **self.interval** property.
"""
if value is not None:
assert is_numeric(value), (
'"{0}" attribute: "{1}" is not a "numeric"!'.format(
'interval', value))
# Invalidating the *range* cache.
if value != self._interval:
self._range = None
self._interval = value
@property
def boundaries(self):
"""
Getter and setter property for the spectral shape boundaries.
Parameters
----------
value : array_like
Value to set the spectral shape boundaries with.
Returns
-------
tuple
Spectral shape boundaries.
"""
return self._start, self._end
@boundaries.setter
def boundaries(self, value):
"""
Setter for the **self.boundaries** property.
"""
if value is not None:
assert is_iterable(value), (
'"{0}" attribute: "{1}" is not an "iterable"!'.format(
'boundaries', value))
assert len(value) == 2, (
'"{0}" attribute: "{1}" must have exactly '
'two elements!'.format('boundaries', value))
start, end = value
self.start = start
self.end = end
[docs] def __str__(self):
"""
Returns a formatted string representation of the spectral shape.
Returns
-------
unicode
Formatted string representation.
"""
return '({0}, {1}, {2})'.format(self._start, self._end, self._interval)
[docs] def __repr__(self):
"""
Returns an evaluable string representation of the spectral shape.
Returns
-------
unicode
Evaluable string representation.
"""
return 'SpectralShape({0}, {1}, {2})'.format(self._start, self._end,
self._interval)
[docs] def __iter__(self):
"""
Returns a generator for the spectral shape data.
Returns
-------
generator
Spectral shape data generator.
Examples
--------
>>> shape = SpectralShape(0, 10, 1)
>>> for wavelength in shape:
... print(wavelength)
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
"""
return iter(self.range())
[docs] def __contains__(self, wavelength):
"""
Returns if the spectral shape contains given wavelength
:math:`\lambda`.
Parameters
----------
wavelength : numeric or array_like
Wavelength :math:`\lambda`.
Returns
-------
bool
Is wavelength :math:`\lambda` contained in the spectral shape.
Examples
--------
>>> 0.5 in SpectralShape(0, 10, 0.1)
True
>>> 0.6 in SpectralShape(0, 10, 0.1)
True
>>> 0.51 in SpectralShape(0, 10, 0.1)
False
>>> np.array([0.5, 0.6]) in SpectralShape(0, 10, 0.1)
True
>>> np.array([0.51, 0.6]) in SpectralShape(0, 10, 0.1)
False
"""
tolerance = 10 ** -np.finfo(DEFAULT_FLOAT_DTYPE).precision
return np.all(
np.in1d(
np.around(wavelength / tolerance).astype(int),
np.around(self.range() / tolerance).astype(int)))
[docs] def __len__(self):
"""
Returns the spectral shape wavelength :math:`\lambda_n` count.
Returns
-------
int
Spectral shape wavelength :math:`\lambda_n` count.
Examples
--------
>>> len(SpectralShape(0, 10, 0.1))
101
"""
return len(self.range())
[docs] def __eq__(self, shape):
"""
Returns the spectral shape equality with given other spectral shape.
Parameters
----------
shape : SpectralShape
Spectral shape to compare for equality.
Returns
-------
bool
Spectral shape equality.
Examples
--------
>>> SpectralShape(0, 10, 0.1) == SpectralShape(0, 10, 0.1)
True
>>> SpectralShape(0, 10, 0.1) == SpectralShape(0, 10, 1)
False
"""
return (isinstance(shape, self.__class__) and
np.array_equal(self.range(), shape.range()))
[docs] def __ne__(self, shape):
"""
Returns the spectral shape inequality with given other spectral shape.
Parameters
----------
shape : SpectralShape
Spectral shape to compare for inequality.
Returns
-------
bool
Spectral shape inequality.
Examples
--------
>>> SpectralShape(0, 10, 0.1) != SpectralShape(0, 10, 0.1)
False
>>> SpectralShape(0, 10, 0.1) != SpectralShape(0, 10, 1)
True
"""
return not (self == shape)
[docs] def range(self, dtype=DEFAULT_FLOAT_DTYPE):
"""
Returns an iterable range for the spectral shape.
Parameters
----------
dtype : type
Data type used to generate the range.
Returns
-------
ndarray
Iterable range for the spectral power distribution shape
Raises
------
RuntimeError
If one of spectral shape *start*, *end* or *interval* attributes is
not defined.
Examples
--------
>>> SpectralShape(0, 10, 0.1).range()
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8,
0.9, 1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7,
1.8, 1.9, 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6,
2.7, 2.8, 2.9, 3. , 3.1, 3.2, 3.3, 3.4, 3.5,
3.6, 3.7, 3.8, 3.9, 4. , 4.1, 4.2, 4.3, 4.4,
4.5, 4.6, 4.7, 4.8, 4.9, 5. , 5.1, 5.2, 5.3,
5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6. , 6.1, 6.2,
6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7. , 7.1,
7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8. ,
8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9,
9. , 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8,
9.9, 10. ])
"""
if None in (self._start, self._end, self._interval):
raise RuntimeError(('One of the spectral shape "start", "end" or '
'"interval" attributes is not defined!'))
if self._range is None:
samples = round(
(self._interval + self._end - self._start) / self._interval)
range_, current_interval = np.linspace(
self._start, self._end, samples, retstep=True, dtype=dtype)
self._range = range_
if current_interval != self._interval:
self._interval = current_interval
warning(('"{0}" shape could not be honoured, using '
'"{1}"!').format((self._start, self._end,
self._interval), self))
return self._range
[docs]class SpectralPowerDistribution(Signal):
"""
Defines the spectral power distribution: the base object for spectral
computations.
The spectral power distribution will be initialised according to
*CIE 15:2004* recommendation: the method developed by
*Sprague (1880)* will be used for interpolating functions having a
uniformly spaced independent variable and the *Cubic Spline* method for
non-uniformly spaced independent variable. Extrapolation is performed
according to *CIE 167:2005* recommendation.
Parameters
----------
data : Series or Signal, SpectralPowerDistribution or array_like or \
dict_like, optional
Data to be stored in the spectral power distribution.
domain : array_like, optional
Values to initialise the
:attr:`colour.SpectralPowerDistribution.wavelength` attribute with.
If both ``data`` and ``domain`` arguments are defined, the latter will
be used to initialise the
:attr:`colour.SpectralPowerDistribution.wavelength` attribute.
Other Parameters
----------------
name : unicode, optional
Spectral power distribution name.
interpolator : object, optional
Interpolator class type to use as interpolating function.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function.
extrapolator : object, optional
Extrapolator class type to use as extrapolating function.
extrapolator_args : dict_like, optional
Arguments to use when instantiating the extrapolating function.
strict_name : unicode, optional
Spectral power distribution name for figures, default to
:attr:`colour.SpectralPowerDistribution.name` attribute value.
Attributes
----------
strict_name
wavelengths
values
shape
Methods
-------
__init__
extrapolate
interpolate
align
trim
normalise
References
----------
- :cite:`CIETC1-382005e`
- :cite:`CIETC1-382005g`
- :cite:`CIETC1-482004l`
Examples
--------
Instantiating a spectral power distribution with a uniformly spaced
independent variable:
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> with numpy_print_options(suppress=True):
... SpectralPowerDistribution(data) # doctest: +ELLIPSIS
SpectralPowerDistribution([[ 500. , 0.0651],
[ 520. , 0.0705],
[ 540. , 0.0772],
[ 560. , 0.087 ],
[ 580. , 0.1128],
[ 600. , 0.136 ]],
interpolator=SpragueInterpolator,
interpolator_args={},
extrapolator=Extrapolator,
extrapolator_args={...})
Instantiating a spectral power distribution with a non-uniformly spaced
independent variable:
>>> data[510] = 0.31416
>>> with numpy_print_options(suppress=True):
... SpectralPowerDistribution(data) # doctest: +ELLIPSIS
SpectralPowerDistribution([[ 500. , 0.0651 ],
[ 510. , 0.31416],
[ 520. , 0.0705 ],
[ 540. , 0.0772 ],
[ 560. , 0.087 ],
[ 580. , 0.1128 ],
[ 600. , 0.136 ]],
interpolator=CubicSplineInterpolator,
interpolator_args={},
extrapolator=Extrapolator,
extrapolator_args={...})
"""
[docs] def __init__(self, data=None, domain=None, **kwargs):
domain, range_ = self.signal_unpack_data(data, domain)
uniform = is_uniform(domain) if domain is not None else True
# Initialising with *CIE 15:2004* and *CIE 167:2005* recommendations
# defaults.
kwargs['interpolator'] = kwargs.get('interpolator', SpragueInterpolator
if uniform else
CubicSplineInterpolator)
kwargs['interpolator_args'] = kwargs.get('interpolator_args', {})
kwargs['extrapolator'] = kwargs.get('extrapolator', Extrapolator)
kwargs['extrapolator_args'] = kwargs.get('extrapolator_args', {
'method': 'Constant',
'left': None,
'right': None
})
super(SpectralPowerDistribution, self).__init__(
range_, domain, **kwargs)
self._strict_name = None
self.strict_name = kwargs.get('strict_name')
@property
def strict_name(self):
"""
Getter and setter property for the spectral power distribution strict
name.
Parameters
----------
value : unicode
Value to set the spectral power distribution strict name with.
Returns
-------
unicode
Spectral power distribution strict name.
"""
if self._strict_name is not None:
return self._strict_name
else:
return self._name
@strict_name.setter
def strict_name(self, value):
"""
Setter for **self.strict_name** property.
"""
if value is not None:
assert is_string(value), (
('"{0}" attribute: "{1}" is not a "string" like object!'
).format('strict_name', value))
self._strict_name = value
@property
def wavelengths(self):
"""
Getter and setter property for the spectral power distribution
wavelengths :math:`\lambda_n`.
Parameters
----------
value : array_like
Value to set the spectral power distribution wavelengths
:math:`\lambda_n` with.
Returns
-------
ndarray
Spectral power distribution wavelengths :math:`\lambda_n`.
"""
return self.domain
@wavelengths.setter
def wavelengths(self, value):
"""
Setter for the **self.wavelengths** property.
"""
self.domain = value
@property
def values(self):
"""
Getter and setter property for the spectral power distribution values.
Parameters
----------
value : array_like
Value to set the spectral power distribution wavelengths values
with.
Returns
-------
ndarray
Spectral power distribution values.
"""
return self.range
@values.setter
def values(self, value):
"""
Setter for the **self.values** property.
"""
self.range = value
@property
def shape(self):
"""
Getter and setter property for the spectral power distribution shape.
Returns
-------
SpectralShape
Spectral power distribution shape.
Notes
-----
- A spectral power distribution with a non-uniformly spaced
independent variable have multiple intervals, in that case
:attr:`colour.SpectralPowerDistribution.shape` attribute returns
the *minimum* interval size.
Warning
-------
:attr:`colour.SpectralPowerDistribution.shape` attribute is read only.
Examples
--------
Shape of a spectral power distribution with a uniformly spaced
independent variable:
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> SpectralPowerDistribution(data).shape
SpectralShape(500.0, 600.0, 20.0)
Shape of a spectral power distribution with a non-uniformly spaced
independent variable:
>>> data[510] = 0.31416
>>> SpectralPowerDistribution(data).shape
SpectralShape(500.0, 600.0, 10.0)
"""
wavelengths_interval = interval(self.wavelengths)
if wavelengths_interval.size != 1:
warning(('"{0}" spectral power distribution is not uniform, '
'using minimum interval!'.format(self.name)))
return SpectralShape(
min(self.wavelengths),
max(self.wavelengths), as_numeric(min(wavelengths_interval)))
[docs] def interpolate(self, shape, interpolator=None, interpolator_args=None):
"""
Interpolates the spectral power distribution in-place according to
*CIE 167:2005* recommendation or given interpolation arguments.
Parameters
----------
shape : SpectralShape, optional
Spectral shape used for interpolation.
interpolator : object, optional
Interpolator class type to use as interpolating function.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function.
Returns
-------
SpectralPowerDistribution
Interpolated spectral power distribution.
Notes
-----
- Interpolation will be performed over boundaries range, if you need
to extend the range of the spectral power distribution use the
:meth:`colour.SpectralPowerDistribution.extrapolate` or
:meth:`colour.SpectralPowerDistribution.align` methods.
Warning
-------
- *Cubic Spline* interpolator requires at least 3 wavelengths
:math:`\lambda_n` for interpolation.
- *Sprague (1880)* interpolator requires at least 6 wavelengths
:math:`\lambda_n` for interpolation.
References
----------
- :cite:`CIETC1-382005e`
Examples
--------
Spectral power distribution with a uniformly spaced independent
variable uses *Sprague (1880)* interpolation:
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> spd = SpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(spd.interpolate(SpectralShape(interval=1)))
... # doctest: +ELLIPSIS
[[ 500. 0.0651 ...]
[ 501. 0.0653522...]
[ 502. 0.0656105...]
[ 503. 0.0658715...]
[ 504. 0.0661328...]
[ 505. 0.0663929...]
[ 506. 0.0666509...]
[ 507. 0.0669069...]
[ 508. 0.0671613...]
[ 509. 0.0674150...]
[ 510. 0.0676692...]
[ 511. 0.0679253...]
[ 512. 0.0681848...]
[ 513. 0.0684491...]
[ 514. 0.0687197...]
[ 515. 0.0689975...]
[ 516. 0.0692832...]
[ 517. 0.0695771...]
[ 518. 0.0698787...]
[ 519. 0.0701870...]
[ 520. 0.0705 ...]
[ 521. 0.0708155...]
[ 522. 0.0711336...]
[ 523. 0.0714547...]
[ 524. 0.0717789...]
[ 525. 0.0721063...]
[ 526. 0.0724367...]
[ 527. 0.0727698...]
[ 528. 0.0731051...]
[ 529. 0.0734423...]
[ 530. 0.0737808...]
[ 531. 0.0741203...]
[ 532. 0.0744603...]
[ 533. 0.0748006...]
[ 534. 0.0751409...]
[ 535. 0.0754813...]
[ 536. 0.0758220...]
[ 537. 0.0761633...]
[ 538. 0.0765060...]
[ 539. 0.0768511...]
[ 540. 0.0772 ...]
[ 541. 0.0775527...]
[ 542. 0.0779042...]
[ 543. 0.0782507...]
[ 544. 0.0785908...]
[ 545. 0.0789255...]
[ 546. 0.0792576...]
[ 547. 0.0795917...]
[ 548. 0.0799334...]
[ 549. 0.0802895...]
[ 550. 0.0806671...]
[ 551. 0.0810740...]
[ 552. 0.0815176...]
[ 553. 0.0820049...]
[ 554. 0.0825423...]
[ 555. 0.0831351...]
[ 556. 0.0837873...]
[ 557. 0.0845010...]
[ 558. 0.0852763...]
[ 559. 0.0861110...]
[ 560. 0.087 ...]
[ 561. 0.0879383...]
[ 562. 0.0889300...]
[ 563. 0.0899793...]
[ 564. 0.0910876...]
[ 565. 0.0922541...]
[ 566. 0.0934760...]
[ 567. 0.0947487...]
[ 568. 0.0960663...]
[ 569. 0.0974220...]
[ 570. 0.0988081...]
[ 571. 0.1002166...]
[ 572. 0.1016394...]
[ 573. 0.1030687...]
[ 574. 0.1044972...]
[ 575. 0.1059186...]
[ 576. 0.1073277...]
[ 577. 0.1087210...]
[ 578. 0.1100968...]
[ 579. 0.1114554...]
[ 580. 0.1128 ...]
[ 581. 0.1141333...]
[ 582. 0.1154495...]
[ 583. 0.1167424...]
[ 584. 0.1180082...]
[ 585. 0.1192452...]
[ 586. 0.1204536...]
[ 587. 0.1216348...]
[ 588. 0.1227915...]
[ 589. 0.1239274...]
[ 590. 0.1250465...]
[ 591. 0.1261531...]
[ 592. 0.1272517...]
[ 593. 0.1283460...]
[ 594. 0.1294393...]
[ 595. 0.1305340...]
[ 596. 0.1316310...]
[ 597. 0.1327297...]
[ 598. 0.1338277...]
[ 599. 0.1349201...]
[ 600. 0.136 ...]]
Spectral power distribution with a no-uniformly spaced independent
variable uses *Cubic Spline* interpolation:
>>> spd = SpectralPowerDistribution(data)
>>> spd[510] = np.pi / 10
>>> with numpy_print_options(suppress=True):
... print(spd.interpolate(SpectralShape(interval=1)))
... # doctest: +ELLIPSIS
[[ 500. 0.0651 ...]
[ 501. 0.1365202...]
[ 502. 0.1953263...]
[ 503. 0.2423724...]
[ 504. 0.2785126...]
[ 505. 0.3046010...]
[ 506. 0.3214916...]
[ 507. 0.3300387...]
[ 508. 0.3310962...]
[ 509. 0.3255184...]
[ 510. 0.3141592...]
[ 511. 0.2978729...]
[ 512. 0.2775135...]
[ 513. 0.2539351...]
[ 514. 0.2279918...]
[ 515. 0.2005378...]
[ 516. 0.1724271...]
[ 517. 0.1445139...]
[ 518. 0.1176522...]
[ 519. 0.0926962...]
[ 520. 0.0705 ...]
[ 521. 0.0517370...]
[ 522. 0.0363589...]
[ 523. 0.0241365...]
[ 524. 0.0148407...]
[ 525. 0.0082424...]
[ 526. 0.0041126...]
[ 527. 0.0022222...]
[ 528. 0.0023421...]
[ 529. 0.0042433...]
[ 530. 0.0076966...]
[ 531. 0.0124729...]
[ 532. 0.0183432...]
[ 533. 0.0250785...]
[ 534. 0.0324496...]
[ 535. 0.0402274...]
[ 536. 0.0481829...]
[ 537. 0.0560870...]
[ 538. 0.0637106...]
[ 539. 0.0708246...]
[ 540. 0.0772 ...]
[ 541. 0.0826564...]
[ 542. 0.0872086...]
[ 543. 0.0909203...]
[ 544. 0.0938549...]
[ 545. 0.0960760...]
[ 546. 0.0976472...]
[ 547. 0.0986321...]
[ 548. 0.0990942...]
[ 549. 0.0990971...]
[ 550. 0.0987043...]
[ 551. 0.0979794...]
[ 552. 0.0969861...]
[ 553. 0.0957877...]
[ 554. 0.0944480...]
[ 555. 0.0930304...]
[ 556. 0.0915986...]
[ 557. 0.0902161...]
[ 558. 0.0889464...]
[ 559. 0.0878532...]
[ 560. 0.087 ...]
[ 561. 0.0864371...]
[ 562. 0.0861623...]
[ 563. 0.0861600...]
[ 564. 0.0864148...]
[ 565. 0.0869112...]
[ 566. 0.0876336...]
[ 567. 0.0885665...]
[ 568. 0.0896945...]
[ 569. 0.0910020...]
[ 570. 0.0924735...]
[ 571. 0.0940936...]
[ 572. 0.0958467...]
[ 573. 0.0977173...]
[ 574. 0.0996899...]
[ 575. 0.1017491...]
[ 576. 0.1038792...]
[ 577. 0.1060649...]
[ 578. 0.1082906...]
[ 579. 0.1105408...]
[ 580. 0.1128 ...]
[ 581. 0.1150526...]
[ 582. 0.1172833...]
[ 583. 0.1194765...]
[ 584. 0.1216167...]
[ 585. 0.1236884...]
[ 586. 0.1256760...]
[ 587. 0.1275641...]
[ 588. 0.1293373...]
[ 589. 0.1309798...]
[ 590. 0.1324764...]
[ 591. 0.1338114...]
[ 592. 0.1349694...]
[ 593. 0.1359349...]
[ 594. 0.1366923...]
[ 595. 0.1372262...]
[ 596. 0.1375211...]
[ 597. 0.1375614...]
[ 598. 0.1373316...]
[ 599. 0.1368163...]
[ 600. 0.136 ...]]
"""
self_shape = self.shape
s_e_i = zip((shape.start, shape.end, shape.interval),
(self_shape.start, self_shape.end, self_shape.interval))
shape = SpectralShape(
* [x[0] if x[0] is not None else x[1] for x in s_e_i])
# Defining proper interpolation bounds.
# TODO: Provide support for fractional interval like 0.1, etc...
if (round(self_shape.start) != self_shape.start or
round(self_shape.end) != self_shape.end):
warning('Fractional bound encountered, rounding will occur!')
shape.start = max(shape.start, np.ceil(self_shape.start))
shape.end = min(shape.end, np.floor(self_shape.end))
if interpolator is None:
if self.is_uniform():
interpolator = SpragueInterpolator
else:
interpolator = CubicSplineInterpolator
if interpolator_args is None:
interpolator_args = {}
interpolator = interpolator(self.wavelengths, self.values,
**interpolator_args)
self.domain = shape.range()
self.range = interpolator(self.domain)
return self
[docs] def align(self,
shape,
interpolator=None,
interpolator_args=None,
extrapolator=None,
extrapolator_args=None):
"""
Aligns the spectral power distribution in-place to given spectral
shape: Interpolates first then extrapolates to fit the given range.
Parameters
----------
shape : SpectralShape
Spectral shape used for alignment.
interpolator : object, optional
Interpolator class type to use as interpolating function.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function.
extrapolator : object, optional
Extrapolator class type to use as extrapolating function.
extrapolator_args : dict_like, optional
Arguments to use when instantiating the extrapolating function.
Returns
-------
SpectralPowerDistribution
Aligned spectral power distribution.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> spd = SpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(spd.align(SpectralShape(505, 565, 1)))
... # doctest: +ELLIPSIS
[[ 505. 0.0663929...]
[ 506. 0.0666509...]
[ 507. 0.0669069...]
[ 508. 0.0671613...]
[ 509. 0.0674150...]
[ 510. 0.0676692...]
[ 511. 0.0679253...]
[ 512. 0.0681848...]
[ 513. 0.0684491...]
[ 514. 0.0687197...]
[ 515. 0.0689975...]
[ 516. 0.0692832...]
[ 517. 0.0695771...]
[ 518. 0.0698787...]
[ 519. 0.0701870...]
[ 520. 0.0705 ...]
[ 521. 0.0708155...]
[ 522. 0.0711336...]
[ 523. 0.0714547...]
[ 524. 0.0717789...]
[ 525. 0.0721063...]
[ 526. 0.0724367...]
[ 527. 0.0727698...]
[ 528. 0.0731051...]
[ 529. 0.0734423...]
[ 530. 0.0737808...]
[ 531. 0.0741203...]
[ 532. 0.0744603...]
[ 533. 0.0748006...]
[ 534. 0.0751409...]
[ 535. 0.0754813...]
[ 536. 0.0758220...]
[ 537. 0.0761633...]
[ 538. 0.0765060...]
[ 539. 0.0768511...]
[ 540. 0.0772 ...]
[ 541. 0.0775527...]
[ 542. 0.0779042...]
[ 543. 0.0782507...]
[ 544. 0.0785908...]
[ 545. 0.0789255...]
[ 546. 0.0792576...]
[ 547. 0.0795917...]
[ 548. 0.0799334...]
[ 549. 0.0802895...]
[ 550. 0.0806671...]
[ 551. 0.0810740...]
[ 552. 0.0815176...]
[ 553. 0.0820049...]
[ 554. 0.0825423...]
[ 555. 0.0831351...]
[ 556. 0.0837873...]
[ 557. 0.0845010...]
[ 558. 0.0852763...]
[ 559. 0.0861110...]
[ 560. 0.087 ...]
[ 561. 0.0879383...]
[ 562. 0.0889300...]
[ 563. 0.0899793...]
[ 564. 0.0910876...]
[ 565. 0.0922541...]]
"""
self.interpolate(shape, interpolator, interpolator_args)
self.extrapolate(shape, extrapolator, extrapolator_args)
return self
[docs] def trim(self, shape):
"""
Trims the spectral power distribution wavelengths to given spectral
shape.
Parameters
----------
shape : SpectralShape
Spectral shape used for trimming.
Returns
-------
SpectralPowerDistribution
Trimmed spectral power distribution.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> spd = SpectralPowerDistribution(data)
>>> spd = spd.interpolate(SpectralShape(interval=1))
>>> with numpy_print_options(suppress=True):
... print(spd.trim(SpectralShape(520, 580, 5)))
... # doctest: +ELLIPSIS
[[ 520. 0.0705 ...]
[ 521. 0.0708155...]
[ 522. 0.0711336...]
[ 523. 0.0714547...]
[ 524. 0.0717789...]
[ 525. 0.0721063...]
[ 526. 0.0724367...]
[ 527. 0.0727698...]
[ 528. 0.0731051...]
[ 529. 0.0734423...]
[ 530. 0.0737808...]
[ 531. 0.0741203...]
[ 532. 0.0744603...]
[ 533. 0.0748006...]
[ 534. 0.0751409...]
[ 535. 0.0754813...]
[ 536. 0.0758220...]
[ 537. 0.0761633...]
[ 538. 0.0765060...]
[ 539. 0.0768511...]
[ 540. 0.0772 ...]
[ 541. 0.0775527...]
[ 542. 0.0779042...]
[ 543. 0.0782507...]
[ 544. 0.0785908...]
[ 545. 0.0789255...]
[ 546. 0.0792576...]
[ 547. 0.0795917...]
[ 548. 0.0799334...]
[ 549. 0.0802895...]
[ 550. 0.0806671...]
[ 551. 0.0810740...]
[ 552. 0.0815176...]
[ 553. 0.0820049...]
[ 554. 0.0825423...]
[ 555. 0.0831351...]
[ 556. 0.0837873...]
[ 557. 0.0845010...]
[ 558. 0.0852763...]
[ 559. 0.0861110...]
[ 560. 0.087 ...]
[ 561. 0.0879383...]
[ 562. 0.0889300...]
[ 563. 0.0899793...]
[ 564. 0.0910876...]
[ 565. 0.0922541...]
[ 566. 0.0934760...]
[ 567. 0.0947487...]
[ 568. 0.0960663...]
[ 569. 0.0974220...]
[ 570. 0.0988081...]
[ 571. 0.1002166...]
[ 572. 0.1016394...]
[ 573. 0.1030687...]
[ 574. 0.1044972...]
[ 575. 0.1059186...]
[ 576. 0.1073277...]
[ 577. 0.1087210...]
[ 578. 0.1100968...]
[ 579. 0.1114554...]
[ 580. 0.1128 ...]]
"""
start = max(shape.start, self.shape.start)
end = min(shape.end, self.shape.end)
indexes = np.where(
np.logical_and(self.domain >= start, self.domain <= end))
wavelengths = self.wavelengths[indexes]
values = self.values[indexes]
self.wavelengths = wavelengths
self.values = values
return self
[docs] def normalise(self, factor=1):
"""
Normalises the spectral power distribution using given normalization
factor.
Parameters
----------
factor : numeric, optional
Normalization factor
Returns
-------
SpectralPowerDistribution
Normalised spectral power distribution.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: 0.0651,
... 520: 0.0705,
... 540: 0.0772,
... 560: 0.0870,
... 580: 0.1128,
... 600: 0.1360
... }
>>> spd = SpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(spd.normalise()) # doctest: +ELLIPSIS
[[ 500. 0.4786764...]
[ 520. 0.5183823...]
[ 540. 0.5676470...]
[ 560. 0.6397058...]
[ 580. 0.8294117...]
[ 600. 1. ...]]
"""
self *= 1 / max(self.values) * factor
return self
# ------------------------------------------------------------------------#
# --- API Changes and Deprecation Management ---#
# ------------------------------------------------------------------------#
@property
def title(self):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralPowerDistribution.strict_name')))
return self.strict_name
@title.setter
def title(self, value):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralPowerDistribution.strict_name')))
self.strict_name = value
@property
def data(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralPowerDistribution.data')))
@property
def items(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralPowerDistribution.items')))
def __iter__(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('SpectralPowerDistribution.__iter__')))
def get(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralPowerDistribution.get')))
def zeros(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralPowerDistribution.zeros')))
def trim_wavelengths(self, shape):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.trim_wavelengths',
'SpectralPowerDistribution.trim')))
return self.trim(shape)
def clone(self):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.clone',
'SpectralPowerDistribution.copy')))
return self.copy()
[docs]class MultiSpectralPowerDistribution(MultiSignal):
"""
Defines multi-spectral power distribution: the base object for multi
spectral computations. It is used to model colour matching functions,
display primaries, camera sensitivities, etc...
The multi-spectral power distribution will be initialised according to
*CIE 15:2004* recommendation: the method developed by
*Sprague (1880)* will be used for interpolating functions having a
uniformly spaced independent variable and the *Cubic Spline* method for
non-uniformly spaced independent variable. Extrapolation is performed
according to *CIE 167:2005* recommendation.
Parameters
----------
data : Series or Dataframe or Signal or MultiSignal or \
MultiSpectralPowerDistribution or array_like or dict_like, optional
Data to be stored in the multi-spectral power distribution.
domain : array_like, optional
Values to initialise the multiple
:class:`colour.SpectralPowerDistribution` class instances
:attr:`colour.continuous.Signal.wavelengths` attribute with. If both
``data`` and ``domain`` arguments are defined, the latter will be used
to initialise the :attr:`colour.continuous.Signal.wavelengths`
attribute.
labels : array_like, optional
Names to use for the :class:`colour.SpectralPowerDistribution` class
instances.
Other Parameters
----------------
name : unicode, optional
Multi-spectral power distribution name.
interpolator : object, optional
Interpolator class type to use as interpolating function for the
:class:`colour.SpectralPowerDistribution` class instances.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function
of the :class:`colour.SpectralPowerDistribution` class instances.
extrapolator : object, optional
Extrapolator class type to use as extrapolating function for the
:class:`colour.SpectralPowerDistribution` class instances.
extrapolator_args : dict_like, optional
Arguments to use when instantiating the extrapolating function
of the :class:`colour.SpectralPowerDistribution` class instances.
strict_labels : array_like, optional
Multi-spectral power distribution labels for figures, default to
:attr:`colour.MultiSpectralPowerDistribution.labels` attribute value.
Attributes
----------
strict_name
strict_labels
wavelengths
values
shape
Methods
-------
extrapolate
interpolate
align
trim
normalise
References
----------
- :cite:`CIETC1-382005e`
- :cite:`CIETC1-382005g`
- :cite:`CIETC1-482004l`
Examples
--------
Instantiating a multi-spectral power distribution with a uniformly spaced
independent variable:
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> labels = ('x_bar', 'y_bar', 'z_bar')
>>> with numpy_print_options(suppress=True):
... MultiSpectralPowerDistribution(data, labels=labels)
... # doctest: +ELLIPSIS
MultiSpectral...([[ 500. , 0.0049 , 0.323 , 0.272 ],
... [ 510. , 0.0093 , 0.503 , 0.1582 ],
... [ 520. , 0.06327, 0.71 , 0.07825],
... [ 530. , 0.1655 , 0.862 , 0.04216],
... [ 540. , 0.2904 , 0.954 , 0.0203 ],
... [ 550. , 0.43345, 0.99495, 0.00875],
... [ 560. , 0.5945 , 0.995 , 0.0039 ]],
... labels=[...'x_bar', ...'y_bar', ...'z_bar'],
... interpolator=SpragueInterpolator,
... interpolator_args={},
... extrapolator=Extrapolator,
... extrapolator_args={...})
Instantiating a spectral power distribution with a non-uniformly spaced
independent variable:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> with numpy_print_options(suppress=True):
... MultiSpectralPowerDistribution(data, labels=labels)
... # doctest: +ELLIPSIS
MultiSpectral...([[ 500. , 0.0049 , 0.323 , 0.272 ],
... [ 510. , 0.0093 , 0.503 , 0.1582 ],
... [ 511. , 0.00314, 0.31416, 0.03142],
... [ 520. , 0.06327, 0.71 , 0.07825],
... [ 530. , 0.1655 , 0.862 , 0.04216],
... [ 540. , 0.2904 , 0.954 , 0.0203 ],
... [ 550. , 0.43345, 0.99495, 0.00875],
... [ 560. , 0.5945 , 0.995 , 0.0039 ]],
... labels=[...'x_bar', ...'y_bar', ...'z_bar'],
... interpolator=CubicSplineInterpolator,
... interpolator_args={},
... extrapolator=Extrapolator,
... extrapolator_args={...})
"""
[docs] def __init__(self, data=None, domain=None, labels=None, **kwargs):
signals = self.multi_signal_unpack_data(data, domain, labels)
domain = signals[list(signals.keys())[0]].domain if signals else None
uniform = is_uniform(domain) if domain is not None else True
# Initialising with *CIE 15:2004* and *CIE 167:2005* recommendations
# defaults.
kwargs['interpolator'] = kwargs.get('interpolator', SpragueInterpolator
if uniform else
CubicSplineInterpolator)
kwargs['interpolator_args'] = kwargs.get('interpolator_args', {})
kwargs['extrapolator'] = kwargs.get('extrapolator', Extrapolator)
kwargs['extrapolator_args'] = kwargs.get('extrapolator_args', {
'method': 'Constant',
'left': None,
'right': None
})
super(MultiSpectralPowerDistribution, self).__init__(
signals, domain, signal_type=SpectralPowerDistribution, **kwargs)
self._strict_name = None
self.strict_name = kwargs.get('strict_name')
self._strict_labels = None
self.strict_labels = kwargs.get('strict_labels')
@property
def strict_name(self):
"""
Getter and setter property for the multi-spectral power distribution
strict name.
Parameters
----------
value : unicode
Value to set the multi-spectral power distribution strict name
with.
Returns
-------
unicode
Multi-spectral power distribution strict name.
"""
if self._strict_name is not None:
return self._strict_name
else:
return self._name
@strict_name.setter
def strict_name(self, value):
"""
Setter for **self.strict_name** property.
"""
if value is not None:
assert is_string(value), (
('"{0}" attribute: "{1}" is not a "string" like object!'
).format('strict_name', value))
self._strict_name = value
@property
def strict_labels(self):
"""
Getter and setter property for the multi-spectral power distribution
strict labels.
Parameters
----------
value : array_like
Value to set the multi-spectral power distribution strict labels
with.
Returns
-------
array_like
Multi-spectral power distribution strict labels.
"""
if self._strict_labels is not None:
return self._strict_labels
else:
return self.labels
@strict_labels.setter
def strict_labels(self, value):
"""
Setter for **self.strict_labels** property.
"""
if value is not None:
assert is_iterable(value), (
'"{0}" attribute: "{1}" is not an "iterable" like object!'.
format('strict_labels', value))
assert len(value) == len(self.labels), (
'"{0}" attribute: length must be "{1}"!'.format(
'strict_labels', len(self.labels)))
self._strict_labels = value
@property
def wavelengths(self):
"""
Getter and setter property for the multi-spectral power distribution
wavelengths :math:`\lambda_n`.
Parameters
----------
value : array_like
Value to set the multi-spectral power distribution wavelengths
:math:`\lambda_n` with.
Returns
-------
ndarray
Multi-spectral power distribution wavelengths :math:`\lambda_n`.
"""
return self.domain
@wavelengths.setter
def wavelengths(self, value):
"""
Setter for the **self.wavelengths** property.
"""
self.domain = value
@property
def values(self):
"""
Getter and setter property for the multi-spectral power distribution
values.
Parameters
----------
value : array_like
Value to set the multi-spectral power distribution wavelengths
values with.
Returns
-------
ndarray
Multi-spectral power distribution values.
"""
return self.range
@values.setter
def values(self, value):
"""
Setter for the **self.values** property.
"""
self.range = value
@property
def shape(self):
"""
Getter and setter property for the multi-spectral power distribution
shape.
Returns
-------
SpectralShape
Multi-spectral power distribution shape.
Notes
-----
- A multi-spectral power distribution with a non-uniformly spaced
independent variable have multiple intervals, in that case
:attr:`colour.MultiSpectralPowerDistribution.shape` attribute
returns the *minimum* interval size.
Warning
-------
:attr:`colour.MultiSpectralPowerDistribution.shape` attribute is read
only.
Examples
--------
Shape of a multi-spectral power distribution with a uniformly spaced
independent variable:
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> MultiSpectralPowerDistribution(data).shape
SpectralShape(500.0, 560.0, 10.0)
Shape of a multi-spectral power distribution with a non-uniformly
spaced independent variable:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> MultiSpectralPowerDistribution(data).shape
SpectralShape(500.0, 560.0, 1.0)
"""
if self.signals:
return first_item(self._signals.values()).shape
[docs] def interpolate(self, shape, interpolator=None, interpolator_args=None):
"""
Interpolates the multi-spectral power distribution in-place accordingly
to *CIE 167:2005* recommendation or given interpolation arguments.
Parameters
----------
shape : SpectralShape, optional
Spectral shape used for interpolation.
interpolator : object, optional
Interpolator class type to use as interpolating function.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function.
Returns
-------
MultiSpectralPowerDistribution
Interpolated multi-spectral power distribution.
Notes
-----
- See :meth:`colour.SpectralPowerDistribution.interpolate` method
notes section.
Warning
-------
See :meth:`colour.SpectralPowerDistribution.interpolate` method warning
section.
References
----------
- :cite:`CIETC1-382005e`
Examples
--------
Multi-spectral power distribution with a uniformly spaced independent
variable uses *Sprague (1880)* interpolation:
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> multi_spd = MultiSpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_spd.interpolate(SpectralShape(interval=1)))
... # doctest: +ELLIPSIS
[[ 500. 0.0049 ... 0.323 ... 0.272 ...]
[ 501. 0.0043252... 0.3400642... 0.2599848...]
[ 502. 0.0037950... 0.3572165... 0.2479849...]
[ 503. 0.0033761... 0.3744030... 0.2360688...]
[ 504. 0.0031397... 0.3916650... 0.2242878...]
[ 505. 0.0031582... 0.4091067... 0.2126801...]
[ 506. 0.0035019... 0.4268629... 0.2012748...]
[ 507. 0.0042365... 0.4450668... 0.1900968...]
[ 508. 0.0054192... 0.4638181... 0.1791709...]
[ 509. 0.0070965... 0.4831505... 0.1685260...]
[ 510. 0.0093 ... 0.503 ... 0.1582 ...]
[ 511. 0.0120562... 0.5232543... 0.1482365...]
[ 512. 0.0154137... 0.5439717... 0.1386625...]
[ 513. 0.0193991... 0.565139 ... 0.1294993...]
[ 514. 0.0240112... 0.5866255... 0.1207676...]
[ 515. 0.0292289... 0.6082226... 0.1124864...]
[ 516. 0.0350192... 0.6296821... 0.1046717...]
[ 517. 0.0413448... 0.6507558... 0.0973361...]
[ 518. 0.0481727... 0.6712346... 0.0904871...]
[ 519. 0.0554816... 0.6909873... 0.0841267...]
[ 520. 0.06327 ... 0.71 ... 0.07825 ...]
[ 521. 0.0715642... 0.7283456... 0.0728614...]
[ 522. 0.0803970... 0.7459679... 0.0680051...]
[ 523. 0.0897629... 0.7628184... 0.0636823...]
[ 524. 0.0996227... 0.7789004... 0.0598449...]
[ 525. 0.1099142... 0.7942533... 0.0564111...]
[ 526. 0.1205637... 0.8089368... 0.0532822...]
[ 527. 0.1314973... 0.8230153... 0.0503588...]
[ 528. 0.1426523... 0.8365417... 0.0475571...]
[ 529. 0.1539887... 0.8495422... 0.0448253...]
[ 530. 0.1655 ... 0.862 ... 0.04216 ...]
[ 531. 0.1772055... 0.8738585... 0.0395936...]
[ 532. 0.1890877... 0.8850940... 0.0371046...]
[ 533. 0.2011304... 0.8957073... 0.0346733...]
[ 534. 0.2133310... 0.9057092... 0.0323006...]
[ 535. 0.2256968... 0.9151181... 0.0300011...]
[ 536. 0.2382403... 0.9239560... 0.0277974...]
[ 537. 0.2509754... 0.9322459... 0.0257131...]
[ 538. 0.2639130... 0.9400080... 0.0237668...]
[ 539. 0.2770569... 0.9472574... 0.0219659...]
[ 540. 0.2904 ... 0.954 ... 0.0203 ...]
[ 541. 0.3039194... 0.9602409... 0.0187414...]
[ 542. 0.3175893... 0.9660106... 0.0172748...]
[ 543. 0.3314022... 0.9713260... 0.0158947...]
[ 544. 0.3453666... 0.9761850... 0.0146001...]
[ 545. 0.3595019... 0.9805731... 0.0133933...]
[ 546. 0.3738324... 0.9844703... 0.0122777...]
[ 547. 0.3883818... 0.9878583... 0.0112562...]
[ 548. 0.4031674... 0.9907270... 0.0103302...]
[ 549. 0.4181943... 0.9930817... 0.0094972...]
[ 550. 0.43345 ... 0.99495 ... 0.00875 ...]
[ 551. 0.4489082... 0.9963738... 0.0080748...]
[ 552. 0.4645599... 0.9973682... 0.0074580...]
[ 553. 0.4803950... 0.9979568... 0.0068902...]
[ 554. 0.4963962... 0.9981802... 0.0063660...]
[ 555. 0.5125410... 0.9980910... 0.0058818...]
[ 556. 0.5288034... 0.9977488... 0.0054349...]
[ 557. 0.5451560... 0.9972150... 0.0050216...]
[ 558. 0.5615719... 0.9965479... 0.0046357...]
[ 559. 0.5780267... 0.9957974... 0.0042671...]
[ 560. 0.5945 ... 0.995 ... 0.0039 ...]]
Multi-spectral power distribution with a non-uniformly spaced
independent variable uses *Cubic Spline* interpolation:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> multi_spd = MultiSpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_spd.interpolate(SpectralShape(interval=1)))
... # doctest: +ELLIPSIS
[[ 500. 0.0049 ... 0.323 ... 0.272 ...]
[ 501. 0.0300110... 0.9455153... 0.5985102...]
[ 502. 0.0462136... 1.3563103... 0.8066498...]
[ 503. 0.0547925... 1.5844039... 0.9126502...]
[ 504. 0.0570325... 1.6588148... 0.9327429...]
[ 505. 0.0542183... 1.6085619... 0.8831594...]
[ 506. 0.0476346... 1.4626640... 0.7801312...]
[ 507. 0.0385662... 1.2501401... 0.6398896...]
[ 508. 0.0282978... 1.0000089... 0.4786663...]
[ 509. 0.0181142... 0.7412892... 0.3126925...]
[ 510. 0.0093 ... 0.503 ... 0.1582 ...]
[ 511. 0.00314 ... 0.31416 ... 0.03142 ...]
[ 512. 0.0006228... 0.1970419... -0.0551709...]
[ 513. 0.0015528... 0.1469341... -0.1041165...]
[ 514. 0.0054381... 0.1523785... -0.1217152...]
[ 515. 0.0117869... 0.2019173... -0.1142659...]
[ 516. 0.0201073... 0.2840925... -0.0880670...]
[ 517. 0.0299077... 0.3874463... -0.0494174...]
[ 518. 0.0406961... 0.5005208... -0.0046156...]
[ 519. 0.0519808... 0.6118579... 0.0400397...]
[ 520. 0.06327 ... 0.71 ... 0.07825 ...]
[ 521. 0.0741690... 0.7859059... 0.1050384...]
[ 522. 0.0846726... 0.8402033... 0.1207164...]
[ 523. 0.0948728... 0.8759363... 0.1269173...]
[ 524. 0.1048614... 0.8961496... 0.1252743...]
[ 525. 0.1147305... 0.9038874... 0.1174207...]
[ 526. 0.1245719... 0.9021942... 0.1049899...]
[ 527. 0.1344776... 0.8941145... 0.0896151...]
[ 528. 0.1445395... 0.8826926... 0.0729296...]
[ 529. 0.1548497... 0.8709729... 0.0565668...]
[ 530. 0.1655 ... 0.862 ... 0.04216 ...]
[ 531. 0.1765618... 0.858179 ... 0.0309976...]
[ 532. 0.1880244... 0.8593588... 0.0229897...]
[ 533. 0.1998566... 0.8647493... 0.0177013...]
[ 534. 0.2120269... 0.8735601... 0.0146975...]
[ 535. 0.2245042... 0.8850011... 0.0135435...]
[ 536. 0.2372572... 0.8982820... 0.0138044...]
[ 537. 0.2502546... 0.9126126... 0.0150454...]
[ 538. 0.2634650... 0.9272026... 0.0168315...]
[ 539. 0.2768572... 0.9412618... 0.0187280...]
[ 540. 0.2904 ... 0.954 ... 0.0203 ...]
[ 541. 0.3040682... 0.9647869... 0.0211987...]
[ 542. 0.3178617... 0.9736329... 0.0214207...]
[ 543. 0.3317865... 0.9807080... 0.0210486...]
[ 544. 0.3458489... 0.9861825... 0.0201650...]
[ 545. 0.3600548... 0.9902267... 0.0188525...]
[ 546. 0.3744103... 0.9930107... 0.0171939...]
[ 547. 0.3889215... 0.9947048... 0.0152716...]
[ 548. 0.4035944... 0.9954792... 0.0131685...]
[ 549. 0.4184352... 0.9955042... 0.0109670...]
[ 550. 0.43345 ... 0.99495 ... 0.00875 ...]
[ 551. 0.4486447... 0.9939867... 0.0065999...]
[ 552. 0.4640255... 0.9927847... 0.0045994...]
[ 553. 0.4795984... 0.9915141... 0.0028313...]
[ 554. 0.4953696... 0.9903452... 0.0013781...]
[ 555. 0.5113451... 0.9894483... 0.0003224...]
[ 556. 0.5275310... 0.9889934... -0.0002530...]
[ 557. 0.5439334... 0.9891509... -0.0002656...]
[ 558. 0.5605583... 0.9900910... 0.0003672...]
[ 559. 0.5774118... 0.9919840... 0.0017282...]
[ 560. 0.5945 ... 0.995 ... 0.0039 ...]]
"""
for signal in self.signals.values():
signal.interpolate(shape, interpolator, interpolator_args)
return self
[docs] def align(self,
shape,
interpolator=None,
interpolator_args=None,
extrapolator=None,
extrapolator_args=None):
"""
Aligns the multi-spectral power distribution in-place to given spectral
shape: Interpolates first then extrapolates to fit the given range.
Parameters
----------
shape : SpectralShape
Spectral shape used for alignment.
interpolator : object, optional
Interpolator class type to use as interpolating function.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function.
extrapolator : object, optional
Extrapolator class type to use as extrapolating function.
extrapolator_args : dict_like, optional
Arguments to use when instantiating the extrapolating function.
Returns
-------
MultiSpectralPowerDistribution
Aligned multi-spectral power distribution.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> multi_spd = MultiSpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_spd.align(SpectralShape(505, 565, 1)))
... # doctest: +ELLIPSIS
[[ 505. 0.0031582... 0.4091067... 0.2126801...]
[ 506. 0.0035019... 0.4268629... 0.2012748...]
[ 507. 0.0042365... 0.4450668... 0.1900968...]
[ 508. 0.0054192... 0.4638181... 0.1791709...]
[ 509. 0.0070965... 0.4831505... 0.1685260...]
[ 510. 0.0093 ... 0.503 ... 0.1582 ...]
[ 511. 0.0120562... 0.5232543... 0.1482365...]
[ 512. 0.0154137... 0.5439717... 0.1386625...]
[ 513. 0.0193991... 0.565139 ... 0.1294993...]
[ 514. 0.0240112... 0.5866255... 0.1207676...]
[ 515. 0.0292289... 0.6082226... 0.1124864...]
[ 516. 0.0350192... 0.6296821... 0.1046717...]
[ 517. 0.0413448... 0.6507558... 0.0973361...]
[ 518. 0.0481727... 0.6712346... 0.0904871...]
[ 519. 0.0554816... 0.6909873... 0.0841267...]
[ 520. 0.06327 ... 0.71 ... 0.07825 ...]
[ 521. 0.0715642... 0.7283456... 0.0728614...]
[ 522. 0.0803970... 0.7459679... 0.0680051...]
[ 523. 0.0897629... 0.7628184... 0.0636823...]
[ 524. 0.0996227... 0.7789004... 0.0598449...]
[ 525. 0.1099142... 0.7942533... 0.0564111...]
[ 526. 0.1205637... 0.8089368... 0.0532822...]
[ 527. 0.1314973... 0.8230153... 0.0503588...]
[ 528. 0.1426523... 0.8365417... 0.0475571...]
[ 529. 0.1539887... 0.8495422... 0.0448253...]
[ 530. 0.1655 ... 0.862 ... 0.04216 ...]
[ 531. 0.1772055... 0.8738585... 0.0395936...]
[ 532. 0.1890877... 0.8850940... 0.0371046...]
[ 533. 0.2011304... 0.8957073... 0.0346733...]
[ 534. 0.2133310... 0.9057092... 0.0323006...]
[ 535. 0.2256968... 0.9151181... 0.0300011...]
[ 536. 0.2382403... 0.9239560... 0.0277974...]
[ 537. 0.2509754... 0.9322459... 0.0257131...]
[ 538. 0.2639130... 0.9400080... 0.0237668...]
[ 539. 0.2770569... 0.9472574... 0.0219659...]
[ 540. 0.2904 ... 0.954 ... 0.0203 ...]
[ 541. 0.3039194... 0.9602409... 0.0187414...]
[ 542. 0.3175893... 0.9660106... 0.0172748...]
[ 543. 0.3314022... 0.9713260... 0.0158947...]
[ 544. 0.3453666... 0.9761850... 0.0146001...]
[ 545. 0.3595019... 0.9805731... 0.0133933...]
[ 546. 0.3738324... 0.9844703... 0.0122777...]
[ 547. 0.3883818... 0.9878583... 0.0112562...]
[ 548. 0.4031674... 0.9907270... 0.0103302...]
[ 549. 0.4181943... 0.9930817... 0.0094972...]
[ 550. 0.43345 ... 0.99495 ... 0.00875 ...]
[ 551. 0.4489082... 0.9963738... 0.0080748...]
[ 552. 0.4645599... 0.9973682... 0.0074580...]
[ 553. 0.4803950... 0.9979568... 0.0068902...]
[ 554. 0.4963962... 0.9981802... 0.0063660...]
[ 555. 0.5125410... 0.9980910... 0.0058818...]
[ 556. 0.5288034... 0.9977488... 0.0054349...]
[ 557. 0.5451560... 0.9972150... 0.0050216...]
[ 558. 0.5615719... 0.9965479... 0.0046357...]
[ 559. 0.5780267... 0.9957974... 0.0042671...]
[ 560. 0.5945 ... 0.995 ... 0.0039 ...]
[ 561. 0.5945 ... 0.995 ... 0.0039 ...]
[ 562. 0.5945 ... 0.995 ... 0.0039 ...]
[ 563. 0.5945 ... 0.995 ... 0.0039 ...]
[ 564. 0.5945 ... 0.995 ... 0.0039 ...]
[ 565. 0.5945 ... 0.995 ... 0.0039 ...]]
"""
for signal in self.signals.values():
signal.align(shape, interpolator, interpolator_args, extrapolator,
extrapolator_args)
return self
[docs] def trim(self, shape):
"""
Trims the multi-spectral power distribution wavelengths to given shape.
Parameters
----------
shape : SpectralShape
Spectral shape used for trimming.
Returns
-------
MultiSpectralPowerDistribution
Trimmed multi-spectral power distribution.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> multi_spd = MultiSpectralPowerDistribution(data)
>>> multi_spd = multi_spd.interpolate(SpectralShape(interval=1))
>>> with numpy_print_options(suppress=True):
... print(multi_spd.trim(SpectralShape(520, 580, 5)))
... # doctest: +ELLIPSIS
[[ 520. 0.06327 ... 0.71 ... 0.07825 ...]
[ 521. 0.0715642... 0.7283456... 0.0728614...]
[ 522. 0.0803970... 0.7459679... 0.0680051...]
[ 523. 0.0897629... 0.7628184... 0.0636823...]
[ 524. 0.0996227... 0.7789004... 0.0598449...]
[ 525. 0.1099142... 0.7942533... 0.0564111...]
[ 526. 0.1205637... 0.8089368... 0.0532822...]
[ 527. 0.1314973... 0.8230153... 0.0503588...]
[ 528. 0.1426523... 0.8365417... 0.0475571...]
[ 529. 0.1539887... 0.8495422... 0.0448253...]
[ 530. 0.1655 ... 0.862 ... 0.04216 ...]
[ 531. 0.1772055... 0.8738585... 0.0395936...]
[ 532. 0.1890877... 0.8850940... 0.0371046...]
[ 533. 0.2011304... 0.8957073... 0.0346733...]
[ 534. 0.2133310... 0.9057092... 0.0323006...]
[ 535. 0.2256968... 0.9151181... 0.0300011...]
[ 536. 0.2382403... 0.9239560... 0.0277974...]
[ 537. 0.2509754... 0.9322459... 0.0257131...]
[ 538. 0.2639130... 0.9400080... 0.0237668...]
[ 539. 0.2770569... 0.9472574... 0.0219659...]
[ 540. 0.2904 ... 0.954 ... 0.0203 ...]
[ 541. 0.3039194... 0.9602409... 0.0187414...]
[ 542. 0.3175893... 0.9660106... 0.0172748...]
[ 543. 0.3314022... 0.9713260... 0.0158947...]
[ 544. 0.3453666... 0.9761850... 0.0146001...]
[ 545. 0.3595019... 0.9805731... 0.0133933...]
[ 546. 0.3738324... 0.9844703... 0.0122777...]
[ 547. 0.3883818... 0.9878583... 0.0112562...]
[ 548. 0.4031674... 0.9907270... 0.0103302...]
[ 549. 0.4181943... 0.9930817... 0.0094972...]
[ 550. 0.43345 ... 0.99495 ... 0.00875 ...]
[ 551. 0.4489082... 0.9963738... 0.0080748...]
[ 552. 0.4645599... 0.9973682... 0.0074580...]
[ 553. 0.4803950... 0.9979568... 0.0068902...]
[ 554. 0.4963962... 0.9981802... 0.0063660...]
[ 555. 0.5125410... 0.9980910... 0.0058818...]
[ 556. 0.5288034... 0.9977488... 0.0054349...]
[ 557. 0.5451560... 0.9972150... 0.0050216...]
[ 558. 0.5615719... 0.9965479... 0.0046357...]
[ 559. 0.5780267... 0.9957974... 0.0042671...]
[ 560. 0.5945 ... 0.995 ... 0.0039 ...]]
"""
for signal in self.signals.values():
signal.trim(shape)
return self
[docs] def normalise(self, factor=1):
"""
Normalises the multi-spectral power distribution with given
normalization factor.
Parameters
----------
factor : numeric, optional
Normalization factor
Returns
-------
MultiSpectralPowerDistribution
Normalised multi- spectral power distribution.
Notes
-----
- The implementation uses the maximum value for each
:class:`colour.SpectralPowerDistribution` class instances.
Examples
--------
>>> from colour.utilities import numpy_print_options
>>> data = {
... 500: (0.004900, 0.323000, 0.272000),
... 510: (0.009300, 0.503000, 0.158200),
... 520: (0.063270, 0.710000, 0.078250),
... 530: (0.165500, 0.862000, 0.042160),
... 540: (0.290400, 0.954000, 0.020300),
... 550: (0.433450, 0.994950, 0.008750),
... 560: (0.594500, 0.995000, 0.003900)
... }
>>> multi_spd = MultiSpectralPowerDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_spd.normalise()) # doctest: +ELLIPSIS
[[ 500. 0.0082422... 0.3246231... 1. ...]
[ 510. 0.0156434... 0.5055276... 0.5816176...]
[ 520. 0.1064255... 0.7135678... 0.2876838...]
[ 530. 0.2783852... 0.8663316... 0.155 ...]
[ 540. 0.4884777... 0.9587939... 0.0746323...]
[ 550. 0.7291000... 0.9999497... 0.0321691...]
[ 560. 1. ... 1. ... 0.0143382...]]
"""
for signal in self.signals.values():
signal.normalise(factor)
return self
# ------------------------------------------------------------------------#
# --- API Changes and Deprecation Management ---#
# ------------------------------------------------------------------------#
@property
def title(self):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralPowerDistribution.strict_name')))
return self.strict_name
@title.setter
def title(self, value):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralPowerDistribution.strict_name')))
self.strict_name = value
@property
def data(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.data')))
@property
def items(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.items')))
@property
def mapping(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.mapping')))
@property
def x(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralPowerDistribution.x')))
@property
def y(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralPowerDistribution.y')))
@property
def z(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralPowerDistribution.z')))
def __iter__(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.__iter__')))
def get(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.get')))
def zeros(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralPowerDistribution.zeros')))
def trim_wavelengths(self, shape):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('MultiSpectralPowerDistribution.trim_wavelengths',
'MultiSpectralPowerDistribution.trim')))
return self.trim(shape)
def clone(self):
# Docstrings are omitted for documentation purposes.
warning(
str(
Renamed('MultiSpectralPowerDistribution.clone',
'MultiSpectralPowerDistribution.copy')))
return self.copy()
DEFAULT_SPECTRAL_SHAPE = SpectralShape(360, 780, 1)
"""
Default spectral shape according to *ASTM E308-15* practise shape.
DEFAULT_SPECTRAL_SHAPE : SpectralShape
"""
[docs]def constant_spd(k, shape=DEFAULT_SPECTRAL_SHAPE, dtype=DEFAULT_FLOAT_DTYPE):
"""
Returns a spectral power distribution of given spectral shape filled with
constant :math:`k` values.
Parameters
----------
k : numeric
Constant :math:`k` to fill the spectral power distribution with.
shape : SpectralShape, optional
Spectral shape used to create the spectral power distribution.
dtype : type
Data type used for the spectral power distribution.
Returns
-------
SpectralPowerDistribution
Constant :math:`k` to filled spectral power distribution.
Notes
-----
- By default, the spectral power distribution will use the shape given
by :attr:`colour.DEFAULT_SPECTRAL_SHAPE` attribute.
Examples
--------
>>> spd = constant_spd(100)
>>> spd.shape
SpectralShape(360.0, 780.0, 1.0)
>>> spd[400]
100.0
"""
wavelengths = shape.range(dtype)
values = np.full(len(wavelengths), k, dtype)
name = '{0} Constant'.format(k)
return SpectralPowerDistribution(
values, wavelengths, name=name, dtype=dtype)
[docs]def zeros_spd(shape=DEFAULT_SPECTRAL_SHAPE):
"""
Returns a spectral power distribution of given spectral shape filled with
zeros.
Parameters
----------
shape : SpectralShape, optional
Spectral shape used to create the spectral power distribution.
Returns
-------
SpectralPowerDistribution
Zeros filled spectral power distribution.
Notes
-----
- By default, the spectral power distribution will use the shape given
by :attr:`colour.DEFAULT_SPECTRAL_SHAPE` attribute.
Examples
--------
>>> spd = zeros_spd()
>>> spd.shape
SpectralShape(360.0, 780.0, 1.0)
>>> spd[400]
0.0
"""
return constant_spd(0, shape)
[docs]def ones_spd(shape=DEFAULT_SPECTRAL_SHAPE):
"""
Returns a spectral power distribution of given spectral shape filled with
ones.
Parameters
----------
shape : SpectralShape, optional
Spectral shape used to create the spectral power distribution.
Returns
-------
SpectralPowerDistribution
Ones filled spectral power distribution.
Notes
-----
- By default, the spectral power distribution will use the shape given
by :attr:`colour.DEFAULT_SPECTRAL_SHAPE` attribute.
Examples
--------
>>> spd = ones_spd()
>>> spd.shape
SpectralShape(360.0, 780.0, 1.0)
>>> spd[400]
1.0
"""
return constant_spd(1, shape)