# -*- coding: utf-8 -*-
"""
Spectrum
========
Defines the classes handling spectral data computations:
- :class:`colour.SpectralShape`
- :class:`colour.SpectralDistribution`
- :class:`colour.MultiSpectralDistribution`
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_float, first_item, is_iterable, is_numeric,
is_string, is_uniform, interval, runtime_warning)
from colour.utilities.deprecation import Removed, Renamed
__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2019 - 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', 'DEFAULT_SPECTRAL_SHAPE', 'SpectralDistribution',
'MultiSpectralDistribution'
]
[docs]class SpectralShape(object):
"""
Defines the base object for spectral 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(np.int64),
np.around(self.range() / tolerance).astype(np.int64)))
[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 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
runtime_warning(
('"{0}" shape could not be honoured, using '
'"{1}"!').format((self._start, self._end, self._interval),
self))
return self._range
DEFAULT_SPECTRAL_SHAPE = SpectralShape(360, 780, 1)
"""
Default spectral shape according to *ASTM E308-15* practise shape.
DEFAULT_SPECTRAL_SHAPE : SpectralShape
"""
[docs]class SpectralDistribution(Signal):
"""
Defines the spectral distribution: the base object for spectral
computations.
The spectral 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, SpectralDistribution or array_like or \
dict_like, optional
Data to be stored in the spectral distribution.
domain : array_like, optional
Values to initialise the
:attr:`colour.SpectralDistribution.wavelength` attribute with.
If both ``data`` and ``domain`` arguments are defined, the latter will
be used to initialise the
:attr:`colour.SpectralDistribution.wavelength` attribute.
Other Parameters
----------------
name : unicode, optional
Spectral 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 distribution name for figures, default to
:attr:`colour.SpectralDistribution.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 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):
... SpectralDistribution(data) # doctest: +ELLIPSIS
SpectralDistribution([[ 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 distribution with a non-uniformly spaced
independent variable:
>>> data[510] = 0.31416
>>> with numpy_print_options(suppress=True):
... SpectralDistribution(data) # doctest: +ELLIPSIS
SpectralDistribution([[ 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(SpectralDistribution, 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 distribution strict name.
Parameters
----------
value : unicode
Value to set the spectral distribution strict name with.
Returns
-------
unicode
Spectral 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 distribution wavelengths
:math:`\\lambda_n`.
Parameters
----------
value : array_like
Value to set the spectral distribution wavelengths
:math:`\\lambda_n` with.
Returns
-------
ndarray
Spectral 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 distribution values.
Parameters
----------
value : array_like
Value to set the spectral distribution wavelengths values
with.
Returns
-------
ndarray
Spectral 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 distribution shape.
Returns
-------
SpectralShape
Spectral distribution shape.
Notes
-----
- A spectral distribution with a non-uniformly spaced independent
variable have multiple intervals, in that case
:attr:`colour.SpectralDistribution.shape` attribute returns
the *minimum* interval size.
Warning
-------
:attr:`colour.SpectralDistribution.shape` attribute is read only.
Examples
--------
Shape of a spectral 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
... }
>>> SpectralDistribution(data).shape
SpectralShape(500.0, 600.0, 20.0)
Shape of a spectral distribution with a non-uniformly spaced
independent variable:
>>> data[510] = 0.31416
>>> SpectralDistribution(data).shape
SpectralShape(500.0, 600.0, 10.0)
"""
wavelengths_interval = interval(self.wavelengths)
if wavelengths_interval.size != 1:
runtime_warning(('"{0}" spectral distribution is not uniform, '
'using minimum interval!'.format(self.name)))
return SpectralShape(
min(self.wavelengths), max(self.wavelengths),
as_float(min(wavelengths_interval)))
[docs] def interpolate(self, shape, interpolator=None, interpolator_args=None):
"""
Interpolates the spectral 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
-------
SpectralDistribution
Interpolated spectral distribution.
Notes
-----
- Interpolation will be performed over boundaries range, if you need
to extend the range of the spectral distribution use the
:meth:`colour.SpectralDistribution.extrapolate` or
:meth:`colour.SpectralDistribution.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 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
... }
>>> sd = SpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(sd.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 distribution with a no-uniformly spaced independent variable
uses *Cubic Spline* interpolation:
>>> sd = SpectralDistribution(data)
>>> sd[510] = np.pi / 10
>>> with numpy_print_options(suppress=True):
... print(sd.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):
runtime_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 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
-------
SpectralDistribution
Aligned spectral 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
... }
>>> sd = SpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(sd.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 distribution wavelengths to given spectral shape.
Parameters
----------
shape : SpectralShape
Spectral shape used for trimming.
Returns
-------
SpectralDistribution
Trimmed spectral 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
... }
>>> sd = SpectralDistribution(data)
>>> sd = sd.interpolate(SpectralShape(interval=1))
>>> with numpy_print_options(suppress=True):
... print(sd.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 distribution using given normalization factor.
Parameters
----------
factor : numeric, optional
Normalization factor
Returns
-------
SpectralDistribution
Normalised spectral 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
... }
>>> sd = SpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(sd.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.
runtime_warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralDistribution.strict_name')))
return self.strict_name
@title.setter
def title(self, value):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('SpectralPowerDistribution.title',
'SpectralDistribution.strict_name')))
self.strict_name = value
@property
def data(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralDistribution.data')))
@property
def items(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralDistribution.items')))
def __iter__(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralDistribution.__iter__')))
def get(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralDistribution.get')))
def zeros(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('SpectralDistribution.zeros')))
def trim_wavelengths(self, shape):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('SpectralPowerDistribution.trim_wavelengths',
'SpectralDistribution.trim')))
return self.trim(shape)
def clone(self):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('SpectralPowerDistribution.clone',
'SpectralDistribution.copy')))
return self.copy()
[docs]class MultiSpectralDistribution(MultiSignal):
"""
Defines multi-spectral distribution: the base object for multi spectral
computations. It is used to model colour matching functions, display
primaries, camera sensitivities, etc...
The multi-spectral 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 \
MultiSpectralDistribution or array_like or dict_like, optional
Data to be stored in the multi-spectral distribution.
domain : array_like, optional
Values to initialise the multiple :class:`colour.SpectralDistribution`
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.SpectralDistribution` class
instances.
Other Parameters
----------------
name : unicode, optional
Multi-spectral distribution name.
interpolator : object, optional
Interpolator class type to use as interpolating function for the
:class:`colour.SpectralDistribution` class instances.
interpolator_args : dict_like, optional
Arguments to use when instantiating the interpolating function of the
:class:`colour.SpectralDistribution` class instances.
extrapolator : object, optional
Extrapolator class type to use as extrapolating function for the
:class:`colour.SpectralDistribution` class instances.
extrapolator_args : dict_like, optional
Arguments to use when instantiating the extrapolating function of the
:class:`colour.SpectralDistribution` class instances.
strict_labels : array_like, optional
Multi-spectral distribution labels for figures, default to
:attr:`colour.MultiSpectralDistribution.labels` attribute value.
Attributes
----------
strict_name
strict_labels
wavelengths
values
shape
Methods
-------
extrapolate
interpolate
align
trim
normalise
to_sds
References
----------
:cite:`CIETC1-382005e`, :cite:`CIETC1-382005g`, :cite:`CIETC1-482004l`
Examples
--------
Instantiating a multi-spectral 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):
... MultiSpectralDistribution(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 distribution with a non-uniformly spaced
independent variable:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> with numpy_print_options(suppress=True):
... MultiSpectralDistribution(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(MultiSpectralDistribution, self).__init__(
signals, domain, signal_type=SpectralDistribution, **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 distribution strict
name.
Parameters
----------
value : unicode
Value to set the multi-spectral distribution strict name with.
Returns
-------
unicode
Multi-spectral 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 distribution strict
labels.
Parameters
----------
value : array_like
Value to set the multi-spectral distribution strict labels with.
Returns
-------
array_like
Multi-spectral 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 distribution
wavelengths :math:`\\lambda_n`.
Parameters
----------
value : array_like
Value to set the multi-spectral distribution wavelengths
:math:`\\lambda_n` with.
Returns
-------
ndarray
Multi-spectral 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 distribution values.
Parameters
----------
value : array_like
Value to set the multi-spectral distribution wavelengths values
with.
Returns
-------
ndarray
Multi-spectral 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 distribution shape.
Returns
-------
SpectralShape
Multi-spectral distribution shape.
Notes
-----
- A multi-spectral distribution with a non-uniformly spaced
independent variable have multiple intervals, in that case
:attr:`colour.MultiSpectralDistribution.shape` attribute returns
the *minimum* interval size.
Warning
-------
:attr:`colour.MultiSpectralDistribution.shape` attribute is read only.
Examples
--------
Shape of a multi-spectral 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)
... }
>>> MultiSpectralDistribution(data).shape
SpectralShape(500.0, 560.0, 10.0)
Shape of a multi-spectral distribution with a non-uniformly spaced
independent variable:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> MultiSpectralDistribution(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 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
-------
MultiSpectralDistribution
Interpolated multi-spectral distribution.
Notes
-----
- See :meth:`colour.SpectralDistribution.interpolate` method notes
section.
Warning
-------
See :meth:`colour.SpectralDistribution.interpolate` method warning
section.
References
----------
:cite:`CIETC1-382005e`
Examples
--------
Multi-spectral 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_sd = MultiSpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_sd.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 distribution with a non-uniformly spaced independent
variable uses *Cubic Spline* interpolation:
>>> data[511] = (0.00314, 0.31416, 0.03142)
>>> multi_sd = MultiSpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_sd.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 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
-------
MultiSpectralDistribution
Aligned multi-spectral 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_sd = MultiSpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_sd.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 distribution wavelengths to given shape.
Parameters
----------
shape : SpectralShape
Spectral shape used for trimming.
Returns
-------
MultiSpectralDistribution
Trimmed multi-spectral 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_sd = MultiSpectralDistribution(data)
>>> multi_sd = multi_sd.interpolate(SpectralShape(interval=1))
>>> with numpy_print_options(suppress=True):
... print(multi_sd.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 distribution with given normalization
factor.
Parameters
----------
factor : numeric, optional
Normalization factor
Returns
-------
MultiSpectralDistribution
Normalised multi- spectral distribution.
Notes
-----
- The implementation uses the maximum value for each
:class:`colour.SpectralDistribution` 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_sd = MultiSpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... print(multi_sd.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
[docs] def to_sds(self):
"""
Converts the multi-spectral distributions to a list of spectral
distributions and update their name and strict name using the labels
and strict labels.
Returns
-------
list
List of spectral distributions.
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_sd = MultiSpectralDistribution(data)
>>> with numpy_print_options(suppress=True):
... for sd in multi_sd.to_sds():
... print(sd) # doctest: +ELLIPSIS
[[ 500. 0.0049 ...]
[ 510. 0.0093 ...]
[ 520. 0.06327...]
[ 530. 0.1655 ...]
[ 540. 0.2904 ...]
[ 550. 0.43345...]
[ 560. 0.5945 ...]]
[[ 500. 0.323 ...]
[ 510. 0.503 ...]
[ 520. 0.71 ...]
[ 530. 0.862 ...]
[ 540. 0.954 ...]
[ 550. 0.99495...]
[ 560. 0.995 ...]]
[[ 500. 0.272 ...]
[ 510. 0.1582 ...]
[ 520. 0.07825...]
[ 530. 0.04216...]
[ 540. 0.0203 ...]
[ 550. 0.00875...]
[ 560. 0.0039 ...]]
"""
sds = []
for i, signal in enumerate(self.signals.values()):
signal = signal.copy()
signal.name = '{0} - {1}'.format(self.labels[i], signal.name)
signal.strict_name = '{0} - {1}'.format(self.strict_labels[i],
signal.strict_name)
sds.append(signal)
return sds
# ------------------------------------------------------------------------#
# --- API Changes and Deprecation Management ---#
# ------------------------------------------------------------------------#
@property
def title(self):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('TriSpectralPowerDistribution.title',
'SpectralDistribution.strict_name')))
return self.strict_name
@title.setter
def title(self, value):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('TriSpectralPowerDistribution.title',
'SpectralDistribution.strict_name')))
self.strict_name = value
@property
def data(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.data')))
@property
def items(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.items')))
@property
def mapping(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.mapping')))
@property
def x(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.x')))
@property
def y(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.y')))
@property
def z(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.z')))
def __iter__(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(
str(Removed('MultiSpectralDistribution.__iter__')))
def get(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.get')))
def zeros(self):
# Docstrings are omitted for documentation purposes.
raise AttributeError(str(Removed('MultiSpectralDistribution.zeros')))
def trim_wavelengths(self, shape):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('TriSpectralPowerDistribution.trim_wavelengths',
'MultiSpectralDistribution.trim')))
return self.trim(shape)
def clone(self):
# Docstrings are omitted for documentation purposes.
runtime_warning(
str(
Renamed('TriSpectralPowerDistribution.clone',
'MultiSpectralDistribution.copy')))
return self.copy()