def visible_spectrum_plot(cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots the visible colours spectrum using given standard observer *CIE XYZ* colour matching functions. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. out_of_gamut_clipping : bool, optional Whether to clip out of gamut colours otherwise, the colours will be offset by the absolute minimal colour leading to a rendering on gray background, less saturated and smoother. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. References ---------- - :cite:`Spiker2015a` Examples -------- >>> visible_spectrum_plot() # doctest: +SKIP """ settings = {'y_label': None, 'y_ticker': False, 'standalone': False} single_spd_plot(ones_spd(DEFAULT_SPECTRAL_SHAPE), cmfs=cmfs, out_of_gamut_clipping=out_of_gamut_clipping, **settings) cmfs = get_cmfs(cmfs) settings = { 'title': 'The Visible Spectrum - {0}'.format(cmfs.strict_name), 'x_label': 'Wavelength $\\lambda$ (nm)', 'standalone': True } settings.update(kwargs) return render(**settings)
def test_spectral_to_aces_relative_exposure_values(self): """ Tests :func:`colour.models.rgb.aces_it. spectral_to_aces_relative_exposure_values` definition. """ shape = ACES_RICD.shape grey_reflector = constant_spd(0.18, shape) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(grey_reflector), np.array([0.18, 0.18, 0.18])) perfect_reflector = ones_spd(shape) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(perfect_reflector), np.array([0.97783784, 0.97783784, 0.97783784])) dark_skin = (COLOURCHECKERS_SPDS['ColorChecker N Ohta']['dark skin']) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(dark_skin), np.array([0.11876978, 0.08708666, 0.0589442]))
def test_spectral_to_aces_relative_exposure_values(self): """ Tests :func:`colour.models.rgb.aces_it. spectral_to_aces_relative_exposure_values` definition. """ shape = ACES_RICD.shape grey_reflector = constant_spd(0.18, shape) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(grey_reflector), np.array([0.18, 0.18, 0.18])) perfect_reflector = ones_spd(shape) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(perfect_reflector), np.array([0.97783784, 0.97783784, 0.97783784])) dark_skin = ( COLOURCHECKERS_SPDS.get('ColorChecker N Ohta').get('dark skin')) np.testing.assert_almost_equal( spectral_to_aces_relative_exposure_values(dark_skin), np.array([0.11876978, 0.08708666, 0.0589442]))
def spectral_to_XYZ( spd, cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'], illuminant=ones_spd(ASTME30815_PRACTISE_SHAPE), method='ASTM E308-15', **kwargs): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions, illuminant and method. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional Illuminant spectral power distribution. method : unicode, optional **{'ASTM E308-15', 'Integration'}**, Computation method. Other Parameters ---------------- use_practice_range : bool, optional {:func:`spectral_to_XYZ_ASTME30815`}, Practise *ASTM E308-15* working wavelengths range is [360, 780], if `True` this argument will trim the colour matching functions appropriately. mi_5nm_omission_method : bool, optional {:func:`spectral_to_XYZ_ASTME30815`}, 5 nm measurement intervals spectral power distribution conversion to tristimulus values will use a 5 nm version of the colour matching functions instead of a table of tristimulus weighting factors. mi_20nm_interpolation_method : bool, optional {:func:`spectral_to_XYZ_ASTME30815`}, 20 nm measurement intervals spectral power distribution conversion to tristimulus values will use a dedicated interpolation method instead of a table of tristimulus weighting factors. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- The output range of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 100]. Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> data = { ... 400: 0.0641, ... 420: 0.0645, ... 440: 0.0562, ... 460: 0.0537, ... 480: 0.0559, ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360, ... 620: 0.1511, ... 640: 0.1688, ... 660: 0.1996, ... 680: 0.2397, ... 700: 0.2852} >>> spd = SpectralPowerDistribution('Sample', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS['D50'] >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant) array([ 11.5290265..., 9.9502091..., 4.7098882...]) >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant, use_practice_range=False) array([ 11.5291275..., 9.9502369..., 4.7098811...]) >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant, method='Integration') array([ 11.5296285..., 9.9499467..., 4.7066079...]) """ function = SPECTRAL_TO_XYZ_METHODS[method] filter_kwargs(function, **kwargs) return function(spd, cmfs, illuminant, **kwargs)
def spectral_to_XYZ_ASTME30815( spd, cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'], illuminant=ones_spd(ASTME30815_PRACTISE_SHAPE), use_practice_range=True, mi_5nm_omission_method=True, mi_20nm_interpolation_method=True): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions and illuminant accordingly to practise *ASTM E308-15* method [2]_. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional Illuminant spectral power distribution. use_practice_range : bool, optional Practise *ASTM E308-15* working wavelengths range is [360, 780], if `True` this argument will trim the colour matching functions appropriately. mi_5nm_omission_method : bool, optional 5 nm measurement intervals spectral power distribution conversion to tristimulus values will use a 5 nm version of the colour matching functions instead of a table of tristimulus weighting factors. mi_20nm_interpolation_method : bool, optional 20 nm measurement intervals spectral power distribution conversion to tristimulus values will use a dedicated interpolation method instead of a table of tristimulus weighting factors. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- - The tables of tristimulus weighting factors are cached in :attr:`_TRISTIMULUS_WEIGHTING_FACTORS_CACHE` attribute. Their identifier key is defined by the colour matching functions and illuminant names along the current shape such as: `CIE 1964 10 Degree Standard Observer, A, (360.0, 830.0, 10.0)` Considering the above, one should be mindful that using similar colour matching functions and illuminant names but with different spectral data will lead to unexpected behaviour. - The output range of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 100]. Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> data = { ... 400: 0.0641, ... 420: 0.0645, ... 440: 0.0562, ... 460: 0.0537, ... 480: 0.0559, ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360, ... 620: 0.1511, ... 640: 0.1688, ... 660: 0.1996, ... 680: 0.2397, ... 700: 0.2852} >>> spd = SpectralPowerDistribution('Sample', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS['D50'] >>> spectral_to_XYZ_ASTME30815( ... spd, cmfs, illuminant) # doctest: +ELLIPSIS array([ 11.5290265..., 9.9502091..., 4.7098882...]) """ if spd.shape.interval not in (1, 5, 10, 20): raise ValueError( 'Tristimulus values conversion from spectral data accordingly to ' 'practise "ASTM E308-15" should be performed on spectral data ' 'with measurement interval of 1, 5, 10 or 20nm!') if use_practice_range: cmfs = cmfs.clone().trim_wavelengths(ASTME30815_PRACTISE_SHAPE) method = spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815 if spd.shape.interval == 1: method = spectral_to_XYZ_integration elif spd.shape.interval == 5 and mi_5nm_omission_method: if cmfs.shape.interval != 5: cmfs = cmfs.clone().interpolate(SpectralShape(interval=5)) method = spectral_to_XYZ_integration elif spd.shape.interval == 20 and mi_20nm_interpolation_method: spd = spd.clone() if spd.shape.boundaries != cmfs.shape.boundaries: warning( 'Trimming "{0}" spectral power distribution shape to "{1}" ' 'colour matching functions shape.'.format(illuminant, cmfs)) spd.trim_wavelengths(cmfs.shape) # Extrapolation of additional 20nm padding intervals. spd.align(SpectralShape(spd.shape.start - 20, spd.shape.end + 20, 10)) for i in range(2): spd[spd.wavelengths[i]] = (3 * spd.values[i + 2] - 3 * spd.values[i + 4] + spd.values[i + 6]) i_e = len(spd) - 1 - i spd[spd.wavelengths[i_e]] = (spd.values[i_e - 6] - 3 * spd.values[i_e - 4] + 3 * spd.values[i_e - 2]) # Interpolating every odd numbered values. # TODO: Investigate code vectorisation. for i in range(3, len(spd) - 3, 2): spd[spd.wavelengths[i]] = (-0.0625 * spd.values[i - 3] + 0.5625 * spd.values[i - 1] + 0.5625 * spd.values[i + 1] - 0.0625 * spd.values[i + 3]) # Discarding the additional 20nm padding intervals. spd.trim_wavelengths(SpectralShape(spd.shape.start + 20, spd.shape.end - 20, 10)) XYZ = method(spd, cmfs, illuminant) return XYZ
def spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815( spd, cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'], illuminant=ones_spd(ASTME30815_PRACTISE_SHAPE)): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions and illuminant using a table of tristimulus weighting factors accordingly to practise *ASTM E308-15* method [2]_. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional Illuminant spectral power distribution. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- The output range of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 100]. Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> data = { ... 400: 0.0641, ... 420: 0.0645, ... 440: 0.0562, ... 460: 0.0537, ... 480: 0.0559, ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360, ... 620: 0.1511, ... 640: 0.1688, ... 660: 0.1996, ... 680: 0.2397, ... 700: 0.2852} >>> spd = SpectralPowerDistribution('Sample', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS['D50'] >>> spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815( ... spd, cmfs, illuminant) # doctest: +ELLIPSIS array([ 11.5296311..., 9.9505845..., 4.7098037...]) """ if illuminant.shape != cmfs.shape: warning('Aligning "{0}" illuminant shape to "{1}" colour matching ' 'functions shape.'.format(illuminant, cmfs)) illuminant = illuminant.clone().align(cmfs.shape) if spd.shape.boundaries != cmfs.shape.boundaries: warning('Trimming "{0}" spectral power distribution shape to "{1}" ' 'colour matching functions shape.'.format(illuminant, cmfs)) spd = spd.clone().trim_wavelengths(cmfs.shape) W = tristimulus_weighting_factors_ASTME202211( cmfs, illuminant, SpectralShape( cmfs.shape.start, cmfs.shape.end, spd.shape.interval)) start_w = cmfs.shape.start end_w = cmfs.shape.start + spd.shape.interval * (W.shape[0] - 1) W = adjust_tristimulus_weighting_factors_ASTME30815( W, SpectralShape(start_w, end_w, spd.shape.interval), spd.shape) R = spd.values XYZ = np.sum(W * R[..., np.newaxis], axis=0) return XYZ
def spectral_to_XYZ_integration( spd, cmfs=STANDARD_OBSERVERS_CMFS[ 'CIE 1931 2 Degree Standard Observer'], illuminant=ones_spd( STANDARD_OBSERVERS_CMFS[ 'CIE 1931 2 Degree Standard Observer'].shape)): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions and illuminant accordingly to classical integration method. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional Illuminant spectral power distribution. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- The output range of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 100]. References ---------- .. [3] Wyszecki, G., & Stiles, W. S. (2000). Integration Replace by Summation. In Color Science: Concepts and Methods, Quantitative Data and Formulae (pp. 158–163). Wiley. ISBN:978-0471399186 Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> data = { ... 400: 0.0641, ... 420: 0.0645, ... 440: 0.0562, ... 460: 0.0537, ... 480: 0.0559, ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360, ... 620: 0.1511, ... 640: 0.1688, ... 660: 0.1996, ... 680: 0.2397, ... 700: 0.2852} >>> spd = SpectralPowerDistribution('Sample', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS['D50'] >>> spectral_to_XYZ_integration( # doctest: +ELLIPSIS ... spd, cmfs, illuminant) array([ 11.5296285..., 9.9499467..., 4.7066079...]) """ if illuminant.shape != cmfs.shape: warning('Aligning "{0}" illuminant shape to "{1}" colour matching ' 'functions shape.'.format(illuminant, cmfs)) illuminant = illuminant.clone().align(cmfs.shape) if spd.shape != cmfs.shape: warning('Aligning "{0}" spectral power distribution shape to "{1}" ' 'colour matching functions shape.'.format(spd, cmfs)) spd = spd.clone().align(cmfs.shape) S = illuminant.values x_bar, y_bar, z_bar = tsplit(cmfs.values) R = spd.values dw = cmfs.shape.interval k = 100 / (np.sum(y_bar * S) * dw) X_p = R * x_bar * S * dw Y_p = R * y_bar * S * dw Z_p = R * z_bar * S * dw XYZ = k * np.sum(np.array([X_p, Y_p, Z_p]), axis=-1) return XYZ
def spectral_to_XYZ(spd, cmfs=STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer'), illuminant=ones_spd( STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer').shape), method='ASTM E308–15', **kwargs): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions, illuminant and method. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional Illuminant spectral power distribution. method : unicode, optional **{'ASTM E308–15', 'Integration'}**, Computation method. \**kwargs : dict, optional Keywords arguments. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- The output range of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 100]. Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') >>> data = { ... 400: 0.0641, ... 420: 0.0645, ... 440: 0.0562, ... 460: 0.0537, ... 480: 0.0559, ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360, ... 620: 0.1511, ... 640: 0.1688, ... 660: 0.1996, ... 680: 0.2397, ... 700: 0.2852} >>> spd = SpectralPowerDistribution('Sample', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50') >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant) array([ 11.5290265..., 9.9502091..., 4.7098882...]) >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant, use_practice_range=False) array([ 11.5291275..., 9.9502369..., 4.7098811...]) >>> spectral_to_XYZ( # doctest: +ELLIPSIS ... spd, cmfs, illuminant, method='Integration') array([ 11.5296285..., 9.9499467..., 4.7066079...]) """ function = SPECTRAL_TO_XYZ_METHODS[method] filter_kwargs(function, **kwargs) return function(spd, cmfs, illuminant, **kwargs)
def XYZ_to_spectral_Meng2015( XYZ, cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'], interval=5, tolerance=1e-10, maximum_iterations=2000): """ Recovers the spectral power distribution of given *CIE XYZ* tristimulus values using *Meng et alii (2015)* method. Parameters ---------- XYZ : array_like, (3,) *CIE XYZ* tristimulus values to recover the spectral power distribution from. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. interval : numeric, optional Wavelength :math:`\lambda_{i}` range interval in nm. The smaller ``interval`` is, the longer the computations will be. tolerance : numeric, optional Tolerance for termination. The lower ``tolerance`` is, the smoother the recovered spectral power distribution will be. maximum_iterations : int, optional Maximum number of iterations to perform. Returns ------- SpectralPowerDistribution Recovered spectral power distribution. Notes ----- - The definition used to convert spectrum to *CIE XYZ* tristimulus values is :func:`colour.colorimetry.spectral_to_XYZ_integration` definition because it processes any measurement interval opposed to :func:`colour.colorimetry.spectral_to_XYZ_ASTME30815` definition that handles only measurement interval of 1, 5, 10 or 20nm. References ---------- - :cite:`Meng2015c` Examples -------- >>> from colour.utilities import numpy_print_options >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) >>> spd = XYZ_to_spectral_Meng2015(XYZ, interval=10) >>> with numpy_print_options(suppress=True): ... spd # doctest: +ELLIPSIS SpectralPowerDistribution([[ 360. , 0.0788075...], [ 370. , 0.0788543...], [ 380. , 0.0788825...], [ 390. , 0.0788714...], [ 400. , 0.0788911...], [ 410. , 0.07893 ...], [ 420. , 0.0797471...], [ 430. , 0.0813339...], [ 440. , 0.0840145...], [ 450. , 0.0892826...], [ 460. , 0.0965359...], [ 470. , 0.1053176...], [ 480. , 0.1150921...], [ 490. , 0.1244252...], [ 500. , 0.1326083...], [ 510. , 0.1390282...], [ 520. , 0.1423548...], [ 530. , 0.1414636...], [ 540. , 0.1365195...], [ 550. , 0.1277319...], [ 560. , 0.1152622...], [ 570. , 0.1004513...], [ 580. , 0.0844187...], [ 590. , 0.0686863...], [ 600. , 0.0543013...], [ 610. , 0.0423486...], [ 620. , 0.0333861...], [ 630. , 0.0273558...], [ 640. , 0.0233407...], [ 650. , 0.0211208...], [ 660. , 0.0197248...], [ 670. , 0.0187157...], [ 680. , 0.0181510...], [ 690. , 0.0179691...], [ 700. , 0.0179247...], [ 710. , 0.0178665...], [ 720. , 0.0178005...], [ 730. , 0.0177570...], [ 740. , 0.0177090...], [ 750. , 0.0175743...], [ 760. , 0.0175058...], [ 770. , 0.0174492...], [ 780. , 0.0174984...], [ 790. , 0.0175667...], [ 800. , 0.0175657...], [ 810. , 0.0175319...], [ 820. , 0.0175184...], [ 830. , 0.0175390...]], interpolator=SpragueInterpolator, interpolator_args={}, extrapolator=Extrapolator, extrapolator_args={...}) >>> spectral_to_XYZ_integration(spd) / 100 # doctest: +ELLIPSIS array([ 0.0705100..., 0.1007987..., 0.0956738...]) """ XYZ = np.asarray(XYZ) shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, interval) cmfs = cmfs.copy().align(shape) illuminant = ones_spd(shape) spd = ones_spd(shape) def function_objective(a): """ Objective function. """ return np.sum(np.diff(a) ** 2) def function_constraint(a): """ Function defining the constraint. """ spd[:] = a return spectral_to_XYZ_integration( spd, cmfs=cmfs, illuminant=illuminant) - XYZ wavelengths = spd.wavelengths bins = wavelengths.size constraints = {'type': 'eq', 'fun': function_constraint} bounds = np.tile(np.array([0, 1000]), (bins, 1)) result = minimize( function_objective, spd.values, method='SLSQP', constraints=constraints, bounds=bounds, options={'ftol': tolerance, 'maxiter': maximum_iterations}) if not result.success: raise RuntimeError( 'Optimization failed for {0} after {1} iterations: "{2}".'.format( XYZ, result.nit, result.message)) return SpectralPowerDistribution( dict(zip(wavelengths, result.x * 100)), name='Meng (2015) - {0}'.format(XYZ))
def spectral_to_XYZ(spd, cmfs=STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer'), illuminant=None): """ Converts given spectral power distribution to *CIE XYZ* tristimulus values using given colour matching functions and illuminant. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional *Illuminant* spectral power distribution. Returns ------- ndarray, (3,) *CIE XYZ* tristimulus values. Warning ------- The output domain of that definition is non standard! Notes ----- - Output *CIE XYZ* tristimulus values are in domain [0, 100]. References ---------- .. [1] Wyszecki, G., & Stiles, W. S. (2000). Integration Replace by Summation. In Color Science: Concepts and Methods, Quantitative Data and Formulae (pp. 158–163). Wiley. ISBN:978-0471399186 Examples -------- >>> from colour import ( ... CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution) >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') >>> data = {380: 0.0600, 390: 0.0600} >>> spd = SpectralPowerDistribution('Custom', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50') >>> spectral_to_XYZ(spd, cmfs, illuminant) # doctest: +ELLIPSIS array([ 4.5764852...e-04, 1.2964866...e-05, 2.1615807...e-03]) """ shape = cmfs.shape if spd.shape != cmfs.shape: spd = spd.clone().zeros(shape) if illuminant is None: illuminant = ones_spd(shape) else: if illuminant.shape != cmfs.shape: illuminant = illuminant.clone().zeros(shape) spd = spd.values x_bar, y_bar, z_bar = (cmfs.x_bar.values, cmfs.y_bar.values, cmfs.z_bar.values) illuminant = illuminant.values x_products = spd * x_bar * illuminant y_products = spd * y_bar * illuminant z_products = spd * z_bar * illuminant normalising_factor = 100 / np.sum(y_bar * illuminant) XYZ = np.array([normalising_factor * np.sum(x_products), normalising_factor * np.sum(y_products), normalising_factor * np.sum(z_products)]) return XYZ
def spectral_to_XYZ(spd, cmfs=STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer'), illuminant=None): """ Converts given spectral power distribution to *CIE XYZ* colourspace using given colour matching functions and illuminant. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional *Illuminant* spectral power distribution. Returns ------- ndarray, (3,) *CIE XYZ* colourspace matrix. Warning ------- The output domain of that definition is non standard! Notes ----- - Output *CIE XYZ* colourspace matrix is in domain [0, 100]. References ---------- .. [1] **Wyszecki & Stiles**, *Color Science - Concepts and Methods Data and Formulae - Second Edition*, Wiley Classics Library Edition, published 2000, ISBN-10: 0-471-39918-3, page 158. Examples -------- >>> from colour import CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution # noqa >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') >>> data = {380: 0.0600, 390: 0.0600} >>> spd = SpectralPowerDistribution('Custom', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50') >>> spectral_to_XYZ(spd, cmfs, illuminant) # doctest: +ELLIPSIS array([ 4.5764852...e-04, 1.2964866...e-05, 2.1615807...e-03]) """ shape = cmfs.shape if spd.shape != cmfs.shape: spd = spd.clone().zeros(shape) if illuminant is None: illuminant = ones_spd(shape) else: if illuminant.shape != cmfs.shape: illuminant = illuminant.clone().zeros(shape) illuminant = illuminant.values spd = spd.values x_bar, y_bar, z_bar = (cmfs.x_bar.values, cmfs.y_bar.values, cmfs.z_bar.values) x_products = spd * x_bar * illuminant y_products = spd * y_bar * illuminant z_products = spd * z_bar * illuminant normalising_factor = 100 / np.sum(y_bar * illuminant) XYZ = np.array([normalising_factor * np.sum(x_products), normalising_factor * np.sum(y_products), normalising_factor * np.sum(z_products)]) return XYZ
def spectral_to_XYZ(spd, cmfs=STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer'), illuminant=None): """ Converts given spectral power distribution to *CIE XYZ* colourspace using given colour matching functions and illuminant. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralPowerDistribution, optional *Illuminant* spectral power distribution. Returns ------- ndarray, (3,) *CIE XYZ* colourspace matrix. Warning ------- The output domain of that definition is non standard! Notes ----- - Output *CIE XYZ* colourspace matrix is in domain [0, 100]. References ---------- .. [1] **Wyszecki & Stiles**, *Color Science - Concepts and Methods Data and Formulae - Second Edition*, Wiley Classics Library Edition, published 2000, ISBN-10: 0-471-39918-3, page 158. Examples -------- >>> from colour import CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution # noqa >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') >>> data = {380: 0.0600, 390: 0.0600} >>> spd = SpectralPowerDistribution('Custom', data) >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50') >>> spectral_to_XYZ(spd, cmfs, illuminant) # doctest: +ELLIPSIS array([ 4.5764852...e-04, 1.2964866...e-05, 2.1615807...e-03]) """ shape = cmfs.shape if spd.shape != cmfs.shape: spd = spd.clone().zeros(shape) if illuminant is None: illuminant = ones_spd(shape) else: if illuminant.shape != cmfs.shape: illuminant = illuminant.clone().zeros(shape) illuminant = illuminant.values spd = spd.values x_bar, y_bar, z_bar = (cmfs.x_bar.values, cmfs.y_bar.values, cmfs.z_bar.values) x_products = spd * x_bar * illuminant y_products = spd * y_bar * illuminant z_products = spd * z_bar * illuminant normalising_factor = 100 / np.sum(y_bar * illuminant) XYZ = np.array([ normalising_factor * np.sum(x_products), normalising_factor * np.sum(y_products), normalising_factor * np.sum(z_products) ]) return XYZ