예제 #1
0
def is_within_visible_spectrum(
        XYZ,
        interval=10,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(
            STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
            shape),
        tolerance=None):
    """
    Returns if given *CIE XYZ* tristimulus values are within visible spectrum
    volume / given colour matching functions volume.

    Parameters
    ----------
    XYZ : array_like
        *CIE XYZ* tristimulus values.
    interval : int, optional
        Wavelength :math:`\\lambda_{i}` range interval used to compute the
        pulse waves for the *CIE XYZ* colourspace outer surface.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    tolerance : numeric, optional
        Tolerance allowed in the inside-triangle check.

    Returns
    -------
    bool
        Is within visible spectrum.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> import numpy as np
    >>> is_within_visible_spectrum(np.array([0.3205, 0.4131, 0.51]))
    array(True, dtype=bool)
    >>> a = np.array([[0.3205, 0.4131, 0.51],
    ...               [-0.0005, 0.0031, 0.001]])
    >>> is_within_visible_spectrum(a)
    array([ True, False], dtype=bool)
    """

    key = (interval, hash(cmfs), hash(illuminant))
    vertices = _XYZ_OUTER_SURFACE_POINTS_CACHE.get(key)
    if vertices is None:
        _XYZ_OUTER_SURFACE_POINTS_CACHE[key] = vertices = (XYZ_outer_surface(
            interval,
            STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
            illuminant))

    return is_within_mesh_volume(XYZ, vertices, tolerance)
예제 #2
0
    def test_handle_spectral_arguments(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.\
handle_spectral_arguments` definition.
        """

        cmfs, illuminant = handle_spectral_arguments()
        # pylint: disable=E1102
        self.assertEqual(
            cmfs,
            reshape_msds(MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]),
        )
        self.assertEqual(illuminant, reshape_sd(SDS_ILLUMINANTS["D65"]))

        shape = SpectralShape(400, 700, 20)
        cmfs, illuminant = handle_spectral_arguments(shape_default=shape)
        self.assertEqual(cmfs.shape, shape)
        self.assertEqual(illuminant.shape, shape)

        cmfs, illuminant = handle_spectral_arguments(
            cmfs_default="CIE 2012 2 Degree Standard Observer",
            illuminant_default="E",
            shape_default=shape,
        )
        self.assertEqual(
            cmfs,
            reshape_msds(MSDS_CMFS["CIE 2012 2 Degree Standard Observer"],
                         shape=shape),
        )
        self.assertEqual(illuminant,
                         sd_ones(shape, interpolator=LinearInterpolator) * 100)
예제 #3
0
def is_within_visible_spectrum(
        XYZ,
        interval=10,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(STANDARD_OBSERVERS_CMFS[
            'CIE 1931 2 Degree Standard Observer'].shape),
        tolerance=None):
    """
    Returns if given *CIE XYZ* tristimulus values are within visible spectrum
    volume / given colour matching functions volume.

    Parameters
    ----------
    XYZ : array_like
        *CIE XYZ* tristimulus values.
    interval : int, optional
        Wavelength :math:`\\lambda_{i}` range interval used to compute the
        pulse waves for the *CIE XYZ* colourspace outer surface.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    tolerance : numeric, optional
        Tolerance allowed in the inside-triangle check.

    Returns
    -------
    bool
        Is within visible spectrum.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> import numpy as np
    >>> is_within_visible_spectrum(np.array([0.3205, 0.4131, 0.51]))
    array(True, dtype=bool)
    >>> a = np.array([[0.3205, 0.4131, 0.51],
    ...               [-0.0005, 0.0031, 0.001]])
    >>> is_within_visible_spectrum(a)
    array([ True, False], dtype=bool)
    """

    key = (interval, hash(cmfs), hash(illuminant))
    vertices = _XYZ_OUTER_SURFACE_POINTS_CACHE.get(key)
    if vertices is None:
        _XYZ_OUTER_SURFACE_POINTS_CACHE[key] = vertices = (XYZ_outer_surface(
            interval,
            STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
            illuminant))

    return is_within_mesh_volume(XYZ, vertices, tolerance)
예제 #4
0
    def test_spectral_to_aces_relative_exposure_values(self):
        """
                Test :func:`colour.characterisation.aces_it.
        sd_to_aces_relative_exposure_values` definition.
        """

        shape = MSDS_ACES_RICD.shape
        grey_reflector = sd_constant(0.18, shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(grey_reflector),
            np.array([0.18, 0.18, 0.18]),
            decimal=7,
        )

        perfect_reflector = sd_ones(shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(perfect_reflector),
            np.array([0.97783784, 0.97783784, 0.97783784]),
            decimal=7,
        )

        dark_skin = SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"]
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin),
            np.array([0.11718149, 0.08663609, 0.05897268]),
            decimal=7,
        )

        dark_skin = SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"]
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin,
                                                SDS_ILLUMINANTS["A"]),
            np.array([0.13583991, 0.09431845, 0.05928214]),
            decimal=7,
        )

        dark_skin = SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"]
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin, apply_chromatic_adaptation=True),
            np.array([0.11807796, 0.08690312, 0.05891252]),
            decimal=7,
        )

        dark_skin = SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"]
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin,
                apply_chromatic_adaptation=True,
                chromatic_adaptation_transform="Bradford",
            ),
            np.array([0.11805993, 0.08689013, 0.05900396]),
            decimal=7,
        )
예제 #5
0
def tcs_colorimetry_data(
    sd_irradiance: SpectralDistribution,
    sds_tcs: MultiSpectralDistributions,
    cmfs: MultiSpectralDistributions,
) -> Tuple[TCS_ColorimetryData_CIE2017, ...]:
    """
    Return the *test colour samples* colorimetry data under given test light
    source or reference illuminant spectral distribution for the
    *CIE 2017 Colour Fidelity Index* (CFI) computations.

    Parameters
    ----------
    sd_irradiance
        Test light source or reference illuminant spectral distribution, i.e.
        the irradiance emitter.
    sds_tcs
        *Test colour samples* spectral distributions.
    cmfs
        Standard observer colour matching functions.

    Returns
    -------
    :class:`tuple`
        *Test colour samples* colorimetry data under the given test light
        source or reference illuminant spectral distribution.

    Examples
    --------
    >>> delta_E_to_R_f(4.4410383190)  # doctest: +ELLIPSIS
    70.1208254...
    """

    XYZ_w = sd_to_XYZ(sd_ones(), cmfs, sd_irradiance)
    Y_b = 20
    L_A = 100
    surround = VIEWING_CONDITIONS_CIECAM02["Average"]

    tcs_data = []
    for sd_tcs in sds_tcs.to_sds():
        XYZ = sd_to_XYZ(sd_tcs, cmfs, sd_irradiance)
        CAM = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround, True)
        JMh = tstack([CAM.J, CAM.M, CAM.h])
        Jpapbp = JMh_CIECAM02_to_CAM02UCS(JMh)

        tcs_data.append(
            TCS_ColorimetryData_CIE2017(sd_tcs.name, XYZ, CAM, JMh, Jpapbp))

    return tuple(tcs_data)
예제 #6
0
    def test_spectral_to_aces_relative_exposure_values(self):
        """
        Tests :func:`colour.models.rgb.aces_it.
sd_to_aces_relative_exposure_values` definition.
        """

        shape = ACES_RICD.shape
        grey_reflector = sd_constant(0.18, shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(grey_reflector),
            np.array([0.18, 0.18, 0.18]),
            decimal=7)

        perfect_reflector = sd_ones(shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(perfect_reflector),
            np.array([0.97783784, 0.97783784, 0.97783784]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin),
            np.array([0.11717855, 0.08663479, 0.05897071]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin,
                                                ILLUMINANTS_SDS['A']),
            np.array([0.13584109, 0.09431910, 0.05928216]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin, apply_chromatic_adaptation=True),
            np.array([0.11807662, 0.0869023, 0.05891045]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin,
                apply_chromatic_adaptation=True,
                chromatic_adaptation_transform='Bradford'),
            np.array([0.11805856, 0.08688928, 0.05900204]),
            decimal=7)
예제 #7
0
    def test_spectral_to_aces_relative_exposure_values(self):
        """
        Tests :func:`colour.models.rgb.aces_it.
sd_to_aces_relative_exposure_values` definition.
        """

        shape = ACES_RICD.shape
        grey_reflector = sd_constant(0.18, shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(grey_reflector),
            np.array([0.18, 0.18, 0.18]),
            decimal=7)

        perfect_reflector = sd_ones(shape)
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(perfect_reflector),
            np.array([0.97783784, 0.97783784, 0.97783784]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin),
            np.array([0.11717855, 0.08663479, 0.05897071]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(dark_skin,
                                                ILLUMINANTS_SDS['A']),
            np.array([0.13584109, 0.09431910, 0.05928216]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin, apply_chromatic_adaptation=True),
            np.array([0.11807662, 0.0869023, 0.05891045]),
            decimal=7)

        dark_skin = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
        np.testing.assert_almost_equal(
            sd_to_aces_relative_exposure_values(
                dark_skin,
                apply_chromatic_adaptation=True,
                chromatic_adaptation_transform='Bradford'),
            np.array([0.11805856, 0.08688928, 0.05900204]),
            decimal=7)
예제 #8
0
파일: color.py 프로젝트: DuraMAT/pvarc
def spectrum_to_XYZ(
    wavelength,
    spectrum,
    # cmfs=None,
    illuminant=None,
):
    """
    Calculate the rgb color given a wavelength and spectrum

    Parameters
    ----------
    wavelength
    spectrum
    cmfs
    illuminant

    Returns
    -------

    """
    # if cmfs is None:
    # cmfs = colour.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']

    if illuminant is None:
        # illuminant = ILLUMINANTS_SDS['CIE 1931 2 Degree Standard Observer']['D65']
        illuminant = sd_ones()
    elif type(illuminant) == type(''):
        illuminant = SDS_ILLUMINANTS[illuminant]
    # # Get illuminant
    # if type(illuminant) == type(''):
    #     illuminant = colour.ILLUMINANTS_SDS[illuminant]

    # Build spectral distribution object
    sd = SpectralDistribution(pd.Series(spectrum, index=wavelength),
                              interpolator=CubicSplineInterpolator,
                              extrapolator=Extrapolator)

    # Calculate xyz color coordinates.
    xyz = sd_to_XYZ(sd=sd, illuminant=illuminant)

    return xyz
예제 #9
0
def multi_sd_to_XYZ_integration(
    msd,
    shape,
    cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
    illuminant=sd_ones(
        STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].shape)):
    """
    Converts given multi-spectral distribution array :math:`msd` with given
    spectral shape to *CIE XYZ* tristimulus values using given colour matching
    functions and illuminant.

    Parameters
    ----------
    msa : array_like
        Multi-spectral distribution array :math:`msd`, the wavelengths are
        expected to be in the last axis, e.g. for a 512x384 multi-spectral
        image with 77 bins, ``msd`` shape should be (384, 512, 77).
    shape : SpectralShape, optional
        Spectral shape of the multi-spectral distribution array :math:`msd`,
        ``cmfs`` and ``illuminant`` will be aligned with it.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Returns
    -------
    array_like
        *CIE XYZ* tristimulus values, for a 512x384 multi-spectral image with
        77 bins, the output shape will be (384, 512, 3).

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> msd = np.array([
    ...     [
    ...         [0.0137, 0.0913, 0.0152, 0.0281, 0.1918, 0.0430],
    ...         [0.0159, 0.3145, 0.0842, 0.0907, 0.7103, 0.0437],
    ...         [0.0096, 0.2582, 0.4139, 0.2228, 0.0041, 0.3744],
    ...         [0.0111, 0.0709, 0.0220, 0.1249, 0.1817, 0.0020],
    ...         [0.0179, 0.2971, 0.5630, 0.2375, 0.0024, 0.5819],
    ...         [0.1057, 0.4620, 0.1918, 0.5625, 0.4209, 0.0027],
    ...     ],
    ...     [
    ...         [0.0433, 0.2683, 0.2373, 0.0518, 0.0118, 0.0823],
    ...         [0.0258, 0.0831, 0.0430, 0.3230, 0.2302, 0.0081],
    ...         [0.0248, 0.1203, 0.0054, 0.0065, 0.1860, 0.3625],
    ...         [0.0186, 0.1292, 0.0079, 0.4006, 0.9404, 0.3213],
    ...         [0.0310, 0.1682, 0.3719, 0.0861, 0.0041, 0.7849],
    ...         [0.0473, 0.3221, 0.2268, 0.3161, 0.1124, 0.0024],
    ...     ],
    ... ])
    >>> D65 = ILLUMINANTS_SDS['D65']
    >>> multi_sd_to_XYZ(
    ... msd, SpectralShape(400, 700, 60), illuminant=D65)
    ... # doctest: +ELLIPSIS
    array([[[  7.1958378...,   3.8605390...,  10.1016398...],
            [ 25.5738615...,  14.7200581...,  34.8440007...],
            [ 17.5854414...,  28.5668344...,  30.1806687...],
            [ 11.3271912...,   8.4598177...,   7.9015758...],
            [ 19.6581831...,  35.5918480...,  35.1430220...],
            [ 45.8212491...,  39.2600939...,  51.7907710...]],
    <BLANKLINE>
           [[  8.8287837...,  13.3870357...,  30.5702050...],
            [ 22.3324362...,  18.9560919...,   9.3952305...],
            [  6.6887212...,   2.5728891...,  13.2618778...],
            [ 41.8166227...,  27.1191979...,  14.2627944...],
            [  9.2414098...,  20.2056200...,  20.1992502...],
            [ 24.7830551...,  26.2221584...,  36.4430633...]]])
    """

    msd = as_float_array(msd)

    if cmfs.shape != shape:
        runtime_warning('Aligning "{0}" cmfs shape to "{1}".'.format(
            cmfs.name, shape))
        cmfs = cmfs.copy().align(shape)

    if illuminant.shape != shape:
        runtime_warning('Aligning "{0}" illuminant shape to "{1}".'.format(
            illuminant.name, shape))
        illuminant = illuminant.copy().align(shape)

    S = illuminant.values
    x_bar, y_bar, z_bar = tsplit(cmfs.values)
    dw = cmfs.shape.interval

    k = 100 / (np.sum(y_bar * S) * dw)

    X_p = msd * x_bar * S * dw
    Y_p = msd * y_bar * S * dw
    Z_p = msd * z_bar * S * dw

    XYZ = k * np.sum(np.array([X_p, Y_p, Z_p]), axis=-1)

    return from_range_100(np.rollaxis(XYZ, 0, msd.ndim))
예제 #10
0
def sd_to_XYZ(
        sd,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        method='ASTM E308-15',
        **kwargs):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values using
    given colour matching functions, illuminant and method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    method : unicode, optional
        **{'ASTM E308-15', 'Integration'}**,
        Computation method.

    Other Parameters
    ----------------
    mi_5nm_omission_method : bool, optional
        {:func:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        5 nm measurement intervals spectral 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:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        20 nm measurement intervals spectral distribution conversion to
        tristimulus values will use a dedicated interpolation method instead
        of a table of tristimulus weighting factors.
    use_practice_range : bool, optional
        {:func:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        Practise *ASTM E308-15* working wavelengths range is [360, 780],
        if *True* this argument will trim the colour matching functions
        appropriately.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`ASTMInternational2011a`, :cite:`ASTMInternational2015b`,
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ(sd, cmfs, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 10.8399031...,   9.6840375...,   6.2164159...])
    >>> sd_to_XYZ(sd, cmfs, illuminant, use_practice_range=False)
    ... # doctest: +ELLIPSIS
    array([ 10.8399852...,   9.6840602...,   6.2164085...])
    >>> sd_to_XYZ(sd, cmfs, illuminant, method='Integration')
    ... # doctest: +ELLIPSIS
    array([ 10.8401846...,   9.6837311...,   6.2120912...])
    """

    function = SD_TO_XYZ_METHODS[method]

    return function(sd, cmfs, illuminant, **filter_kwargs(function, **kwargs))
예제 #11
0
def sd_to_XYZ_ASTME30815(
        sd,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        use_practice_range=True,
        mi_5nm_omission_method=True,
        mi_20nm_interpolation_method=True):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values using
    given colour matching functions and illuminant according to practise
    *ASTM E308-15* method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral 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 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 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:`colour.colorimetry.tristimulus.\
_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.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`ASTMInternational2015b`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ_ASTME30815(sd, cmfs, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 10.8399031...,   9.6840375...,   6.2164159...])
    """

    if sd.shape.interval not in (1, 5, 10, 20):
        raise ValueError(
            'Tristimulus values conversion from spectral data according 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.copy().trim(ASTME30815_PRACTISE_SHAPE)

    method = sd_to_XYZ_tristimulus_weighting_factors_ASTME30815
    if sd.shape.interval == 1:
        method = sd_to_XYZ_integration
    elif sd.shape.interval == 5 and mi_5nm_omission_method:
        if cmfs.shape.interval != 5:
            cmfs = cmfs.copy().interpolate(SpectralShape(interval=5))
        method = sd_to_XYZ_integration
    elif sd.shape.interval == 20 and mi_20nm_interpolation_method:
        sd = sd.copy()
        if sd.shape.boundaries != cmfs.shape.boundaries:
            runtime_warning(
                'Trimming "{0}" spectral distribution shape to "{1}" '
                'colour matching functions shape.'.format(
                    illuminant.name, cmfs.name))
            sd.trim(cmfs.shape)

        # Extrapolation of additional 20nm padding intervals.
        sd.align(SpectralShape(sd.shape.start - 20, sd.shape.end + 20, 10))
        for i in range(2):
            sd[sd.wavelengths[i]] = (
                3 * sd.values[i + 2] -
                3 * sd.values[i + 4] + sd.values[i + 6])  # yapf: disable
            i_e = len(sd.domain) - 1 - i
            sd[sd.wavelengths[i_e]] = (sd.values[i_e - 6] -
                                       3 * sd.values[i_e - 4] +
                                       3 * sd.values[i_e - 2])

        # Interpolating every odd numbered values.
        # TODO: Investigate code vectorisation.
        for i in range(3, len(sd.domain) - 3, 2):
            sd[sd.wavelengths[i]] = (-0.0625 * sd.values[i - 3] +
                                     0.5625 * sd.values[i - 1] +
                                     0.5625 * sd.values[i + 1] -
                                     0.0625 * sd.values[i + 3])

        # Discarding the additional 20nm padding intervals.
        sd.trim(SpectralShape(sd.shape.start + 20, sd.shape.end - 20, 10))

    XYZ = method(sd, cmfs, illuminant)

    return XYZ
예제 #12
0
def sd_to_XYZ_tristimulus_weighting_factors_ASTME30815(
    sd,
    cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
    illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE)):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant using a table of
    tristimulus weighting factors according to practise *ASTM E308-15* method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`ASTMInternational2015b`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ_tristimulus_weighting_factors_ASTME30815(
    ...     sd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([ 10.8402899...,   9.6843539...,   6.2160858...])
    """

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    if sd.shape.boundaries != cmfs.shape.boundaries:
        runtime_warning('Trimming "{0}" spectral distribution shape to "{1}" '
                        'colour matching functions shape.'.format(
                            illuminant.name, cmfs.name))
        sd = sd.copy().trim(cmfs.shape)

    W = tristimulus_weighting_factors_ASTME202211(
        cmfs, illuminant,
        SpectralShape(cmfs.shape.start, cmfs.shape.end, sd.shape.interval))
    start_w = cmfs.shape.start
    end_w = cmfs.shape.start + sd.shape.interval * (W.shape[0] - 1)
    W = adjust_tristimulus_weighting_factors_ASTME30815(
        W, SpectralShape(start_w, end_w, sd.shape.interval), sd.shape)
    R = sd.values

    XYZ = np.sum(W * R[..., np.newaxis], axis=0)

    return from_range_100(XYZ)
예제 #13
0
def sd_to_XYZ_integration(
    sd,
    cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
    illuminant=sd_ones(
        STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].shape)):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant according to classical
    integration method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ_integration(sd, cmfs, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 10.8401846...,   9.6837311...,   6.2120912...])
    """

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    if sd.shape != cmfs.shape:
        runtime_warning('Aligning "{0}" spectral distribution shape to "{1}" '
                        'colour matching functions shape.'.format(
                            sd.name, cmfs.name))
        sd = sd.copy().align(cmfs.shape)

    S = illuminant.values
    x_bar, y_bar, z_bar = tsplit(cmfs.values)
    R = sd.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 from_range_100(XYZ)
예제 #14
0
def XYZ_to_sd_Meng2015(
    XYZ: ArrayLike,
    cmfs: Optional[MultiSpectralDistributions] = None,
    illuminant: Optional[SpectralDistribution] = None,
    optimisation_kwargs: Optional[Dict] = None,
) -> SpectralDistribution:
    """
    Recover the spectral distribution of given *CIE XYZ* tristimulus values
    using *Meng et al. (2015)* method.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values to recover the spectral distribution from.
    cmfs
        Standard observer colour matching functions. The wavelength
        :math:`\\lambda_{i}` range interval of the colour matching functions
        affects directly the time the computations take. The current default
        interval of 5 is a good compromise between precision and time spent,
        default to the *CIE 1931 2 Degree Standard Observer*.
    illuminant
        Illuminant spectral distribution, default to
        *CIE Standard Illuminant D65*.
    optimisation_kwargs
        Parameters for :func:`scipy.optimize.minimize` definition.

    Returns
    -------
    :class:`colour.SpectralDistribution`
        Recovered spectral distribution.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    -   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.sd_to_XYZ_ASTME308` definition that
        handles only measurement interval of 1, 5, 10 or 20nm.

    References
    ----------
    :cite:`Meng2015c`

    Examples
    --------
    >>> from colour import MSDS_CMFS, SDS_ILLUMINANTS
    >>> from colour.utilities import numpy_print_options
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> cmfs = (
    ...     MSDS_CMFS['CIE 1931 2 Degree Standard Observer'].
    ...     copy().align(SpectralShape(360, 780, 10))
    ... )
    >>> illuminant = SDS_ILLUMINANTS['D65'].copy().align(cmfs.shape)
    >>> sd = XYZ_to_sd_Meng2015(XYZ, cmfs, illuminant)
    >>> with numpy_print_options(suppress=True):
    ...     sd  # doctest: +SKIP
    SpectralDistribution([[ 360.        ,    0.0762005...],
                          [ 370.        ,    0.0761792...],
                          [ 380.        ,    0.0761363...],
                          [ 390.        ,    0.0761194...],
                          [ 400.        ,    0.0762539...],
                          [ 410.        ,    0.0761671...],
                          [ 420.        ,    0.0754649...],
                          [ 430.        ,    0.0731519...],
                          [ 440.        ,    0.0676701...],
                          [ 450.        ,    0.0577800...],
                          [ 460.        ,    0.0441993...],
                          [ 470.        ,    0.0285064...],
                          [ 480.        ,    0.0138728...],
                          [ 490.        ,    0.0033585...],
                          [ 500.        ,    0.       ...],
                          [ 510.        ,    0.       ...],
                          [ 520.        ,    0.       ...],
                          [ 530.        ,    0.       ...],
                          [ 540.        ,    0.0055767...],
                          [ 550.        ,    0.0317581...],
                          [ 560.        ,    0.0754491...],
                          [ 570.        ,    0.1314115...],
                          [ 580.        ,    0.1937649...],
                          [ 590.        ,    0.2559311...],
                          [ 600.        ,    0.3123173...],
                          [ 610.        ,    0.3584966...],
                          [ 620.        ,    0.3927335...],
                          [ 630.        ,    0.4159458...],
                          [ 640.        ,    0.4306660...],
                          [ 650.        ,    0.4391040...],
                          [ 660.        ,    0.4439497...],
                          [ 670.        ,    0.4463618...],
                          [ 680.        ,    0.4474625...],
                          [ 690.        ,    0.4479868...],
                          [ 700.        ,    0.4482116...],
                          [ 710.        ,    0.4482800...],
                          [ 720.        ,    0.4483472...],
                          [ 730.        ,    0.4484251...],
                          [ 740.        ,    0.4484633...],
                          [ 750.        ,    0.4485071...],
                          [ 760.        ,    0.4484969...],
                          [ 770.        ,    0.4484853...],
                          [ 780.        ,    0.4485134...]],
                         interpolator=SpragueInterpolator,
                         interpolator_kwargs={},
                         extrapolator=Extrapolator,
                         extrapolator_kwargs={...})
    >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100  # doctest: +ELLIPSIS
    array([ 0.2065400...,  0.1219722...,  0.0513695...])
    """

    XYZ = to_domain_1(XYZ)

    cmfs, illuminant = handle_spectral_arguments(
        cmfs, illuminant, shape_default=SPECTRAL_SHAPE_MENG2015
    )

    sd = sd_ones(cmfs.shape)

    def objective_function(a: ArrayLike) -> FloatingOrNDArray:
        """Define the objective function."""

        return np.sum(np.diff(a) ** 2)

    def constraint_function(a: ArrayLike) -> NDArray:
        """Define the constraint function."""

        sd[:] = a
        return (
            sd_to_XYZ_integration(sd, cmfs=cmfs, illuminant=illuminant) - XYZ
        )

    wavelengths = sd.wavelengths
    bins = wavelengths.size

    optimisation_settings = {
        "method": "SLSQP",
        "constraints": {"type": "eq", "fun": constraint_function},
        "bounds": np.tile(np.array([0, 1000]), (bins, 1)),
        "options": {
            "ftol": 1e-10,
        },
    }
    if optimisation_kwargs is not None:
        optimisation_settings.update(optimisation_kwargs)

    result = minimize(objective_function, sd.values, **optimisation_settings)

    if not result.success:
        raise RuntimeError(
            f"Optimization failed for {XYZ} after {result.nit} iterations: "
            f'"{result.message}".'
        )

    return SpectralDistribution(
        from_range_100(result.x * 100),
        wavelengths,
        name=f"{XYZ} (XYZ) - Meng (2015)",
    )
예제 #15
0
def sd_to_XYZ_integration(
        sd,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(
            STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
            shape),
        k=None):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant according to classical
    integration method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    k : numeric, optional
        Normalisation constant :math:`k`. For reflecting or transmitting object
        colours, :math:`k` is chosen so that :math:`Y = 100` for objects for
        which the spectral reflectance factor :math:`R(\\lambda)` of the object
        colour or the spectral transmittance factor :math:`\\tau(\\lambda)` of
        the object is equal to unity for all wavelengths. For self-luminous
        objects and illuminants, the constants :math:`k` is usually chosen on
        the grounds of convenience. If, however, in the CIE 1931 standard
        colorimetric system, the :math:`Y` value is required to be numerically
        equal to the absolute value of a photometric quantity, the constant,
        :math:`k`, must be put equal to the numerical value of :math:`K_m`, the
        maximum spectral luminous efficacy (which is equal to
        683 :math:`lm\\cdot W^{-1}`) and :math:`\\Phi_\\lambda(\\lambda)` must
        be the spectral concentration of the radiometric quantity corresponding
        to the photometric quantity required.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ_integration(sd, cmfs, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 10.8401846...,   9.6837311...,   6.2120912...])
    """

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    if sd.shape != cmfs.shape:
        runtime_warning('Aligning "{0}" spectral distribution shape to "{1}" '
                        'colour matching functions shape.'.format(
                            sd.name, cmfs.name))
        sd = sd.copy().align(cmfs.shape)

    S = illuminant.values
    x_bar, y_bar, z_bar = tsplit(cmfs.values)
    R = sd.values
    dw = cmfs.shape.interval

    k = 100 / (np.sum(y_bar * S) * dw) if k is None else k

    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 from_range_100(XYZ)
예제 #16
0
def XYZ_outer_surface(
    interval=10,
    cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
    illuminant=sd_ones(
        STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].shape)):
    """
    Generates the *CIE XYZ* colourspace outer surface for given colour matching
    functions using multi-spectral conversion of pulse waves to *CIE XYZ*
    tristimulus values.

    Parameters
    ----------
    interval : int, optional
        Wavelength :math:`\\lambda_{i}` range interval used to compute the
        pulse waves.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Returns
    -------
    ndarray
        Outer surface *CIE XYZ* tristimulus values.

    References
    ----------
    :cite:`Lindbloom2015`, :cite:`Mansencal2018`

    Examples
    --------
    >>> XYZ_outer_surface(84)  # doctest: +ELLIPSIS
    array([[  0.0000000...e+00,   0.0000000...e+00,   0.0000000...e+00],
           [  1.4766924...e-03,   4.1530347...e-05,   6.9884362...e-03],
           [  1.6281275...e-01,   3.7114387...e-02,   9.0151471...e-01],
           [  1.8650894...e-01,   5.6617464...e-01,   9.1355179...e-02],
           [  6.1555347...e-01,   3.8427775...e-01,   4.7422070...e-04],
           [  3.3622045...e-02,   1.2354556...e-02,   0.0000000...e+00],
           [  1.0279500...e-04,   3.7121158...e-05,   0.0000000...e+00],
           [  1.6428945...e-01,   3.7155917...e-02,   9.0850314...e-01],
           [  3.4932169...e-01,   6.0328903...e-01,   9.9286989...e-01],
           [  8.0206241...e-01,   9.5045240...e-01,   9.1829399...e-02],
           [  6.4917552...e-01,   3.9663231...e-01,   4.7422070...e-04],
           [  3.3724840...e-02,   1.2391678...e-02,   0.0000000...e+00],
           [  1.5794874...e-03,   7.8651505...e-05,   6.9884362...e-03],
           [  3.5079839...e-01,   6.0333056...e-01,   9.9985832...e-01],
           [  9.6487517...e-01,   9.8756679...e-01,   9.9334411...e-01],
           [  8.3568446...e-01,   9.6280696...e-01,   9.1829399...e-02],
           [  6.4927831...e-01,   3.9666943...e-01,   4.7422070...e-04],
           [  3.5201532...e-02,   1.2433208...e-02,   6.9884362...e-03],
           [  1.6439224...e-01,   3.7193038...e-02,   9.0850314...e-01],
           [  9.6635186...e-01,   9.8760832...e-01,   1.0003325...e+00],
           [  9.9849722...e-01,   9.9992134...e-01,   9.9334411...e-01],
           [  8.3578726...e-01,   9.6284408...e-01,   9.1829399...e-02],
           [  6.5075501...e-01,   3.9671096...e-01,   7.4626569...e-03],
           [  1.9801429...e-01,   4.9547595...e-02,   9.0850314...e-01],
           [  3.5090118...e-01,   6.0336768...e-01,   9.9985832...e-01],
           [  9.9997391...e-01,   9.9996287...e-01,   1.0003325...e+00],
           [  9.9860001...e-01,   9.9995847...e-01,   9.9334411...e-01],
           [  8.3726395...e-01,   9.6288561...e-01,   9.8817836...e-02],
           [  8.1356776...e-01,   4.3382535...e-01,   9.0897737...e-01],
           [  3.8452323...e-01,   6.1572224...e-01,   9.9985832...e-01],
           [  9.6645466...e-01,   9.8764544...e-01,   1.0003325...e+00],
           [  1.0000767...e+00,   1.0000000...e+00,   1.0003325...e+00]])

    """

    key = (interval, hash(cmfs), hash(illuminant))
    XYZ = _XYZ_OUTER_SURFACE_CACHE.get(key)
    if XYZ is None:
        wavelengths = SpectralShape(DEFAULT_SPECTRAL_SHAPE.start,
                                    DEFAULT_SPECTRAL_SHAPE.end,
                                    interval).range()
        values = []
        domain = DEFAULT_SPECTRAL_SHAPE.range()
        for wave in generate_pulse_waves(len(wavelengths)):
            values.append(
                NearestNeighbourInterpolator(wavelengths, wave)(domain))

        XYZ = multi_sd_to_XYZ_integration(values, DEFAULT_SPECTRAL_SHAPE, cmfs,
                                          illuminant)

        XYZ = XYZ / np.max(XYZ[-1, 1])

        _XYZ_OUTER_SURFACE_CACHE[key] = XYZ

    return XYZ
예제 #17
0
def plot_visible_spectrum(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.artist`,
        :func:`colour.plotting.plot_single_sd`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

    Returns
    -------
    tuple
        Current figure and axes.

    References
    ----------
    :cite:`Spiker2015a`

    Examples
    --------
    >>> plot_visible_spectrum()  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Visible_Spectrum.png
        :align: center
        :alt: plot_visible_spectrum
    """

    cmfs = first_item(filter_cmfs(cmfs).values())

    bounding_box = (min(cmfs.wavelengths), max(cmfs.wavelengths), 0, 1)

    settings = {'bounding_box': bounding_box, 'y_label': None}
    settings.update(kwargs)
    settings['standalone'] = False

    _figure, axes = plot_single_sd(
        sd_ones(cmfs.shape),
        cmfs=cmfs,
        out_of_gamut_clipping=out_of_gamut_clipping,
        **settings)

    # Removing wavelength line as it doubles with the axes spine.
    axes.lines.pop(0)

    settings = {
        'axes': axes,
        'standalone': True,
        'title': 'The Visible Spectrum - {0}'.format(cmfs.strict_name),
        'x_label': 'Wavelength $\\lambda$ (nm)',
    }
    settings.update(kwargs)

    return render(**settings)
예제 #18
0
파일: meng2015.py 프로젝트: wenh06/colour
def XYZ_to_sd_Meng2015(
        XYZ,
        cmfs=MSDS_CMFS_STANDARD_OBSERVER['CIE 1931 2 Degree Standard Observer']
    .copy().align(SPECTRAL_SHAPE_MENG2015),
        illuminant=SDS_ILLUMINANTS['D65'].copy().align(
            SPECTRAL_SHAPE_MENG2015),
        optimisation_kwargs=None,
        **kwargs):
    """
    Recovers the spectral distribution of given *CIE XYZ* tristimulus values
    using *Meng et al. (2015)* method.

    Parameters
    ----------
    XYZ : array_like, (3,)
        *CIE XYZ* tristimulus values to recover the spectral distribution from.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions. The wavelength
        :math:`\\lambda_{i}` range interval of the colour matching functions
        affects directly the time the computations take. The current default
        interval of 5 is a good compromise between precision and time spent.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    optimisation_kwargs : dict_like, optional
        Parameters for :func:`scipy.optimize.minimize` definition.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for deprecation management.

    Returns
    -------
    SpectralDistribution
        Recovered spectral distribution.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    -   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.sd_to_XYZ_ASTME308` 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.20654008, 0.12197225, 0.05136952])
    >>> cmfs = (
    ...     MSDS_CMFS_STANDARD_OBSERVER['CIE 1931 2 Degree Standard Observer'].
    ...     copy().align(SpectralShape(360, 780, 10))
    ... )
    >>> illuminant = SDS_ILLUMINANTS['D65'].copy().align(cmfs.shape)
    >>> sd = XYZ_to_sd_Meng2015(XYZ, cmfs, illuminant)
    >>> with numpy_print_options(suppress=True):
    ...     # Doctests skip for Python 2.x compatibility.
    ...     sd  # doctest: +SKIP
    SpectralDistribution([[ 360.        ,    0.0765153...],
                          [ 370.        ,    0.0764771...],
                          [ 380.        ,    0.0764286...],
                          [ 390.        ,    0.0764329...],
                          [ 400.        ,    0.0765863...],
                          [ 410.        ,    0.0764339...],
                          [ 420.        ,    0.0757213...],
                          [ 430.        ,    0.0733091...],
                          [ 440.        ,    0.0676493...],
                          [ 450.        ,    0.0577616...],
                          [ 460.        ,    0.0440805...],
                          [ 470.        ,    0.0284802...],
                          [ 480.        ,    0.0138019...],
                          [ 490.        ,    0.0033557...],
                          [ 500.        ,    0.       ...],
                          [ 510.        ,    0.       ...],
                          [ 520.        ,    0.       ...],
                          [ 530.        ,    0.       ...],
                          [ 540.        ,    0.0055360...],
                          [ 550.        ,    0.0317335...],
                          [ 560.        ,    0.075457 ...],
                          [ 570.        ,    0.1314930...],
                          [ 580.        ,    0.1938219...],
                          [ 590.        ,    0.2559747...],
                          [ 600.        ,    0.3122869...],
                          [ 610.        ,    0.3584363...],
                          [ 620.        ,    0.3927112...],
                          [ 630.        ,    0.4158866...],
                          [ 640.        ,    0.4305832...],
                          [ 650.        ,    0.4391142...],
                          [ 660.        ,    0.4439484...],
                          [ 670.        ,    0.4464121...],
                          [ 680.        ,    0.4475718...],
                          [ 690.        ,    0.4481182...],
                          [ 700.        ,    0.4483734...],
                          [ 710.        ,    0.4484743...],
                          [ 720.        ,    0.4485753...],
                          [ 730.        ,    0.4486474...],
                          [ 740.        ,    0.4486629...],
                          [ 750.        ,    0.4486995...],
                          [ 760.        ,    0.4486925...],
                          [ 770.        ,    0.4486794...],
                          [ 780.        ,    0.4486982...]],
                         interpolator=SpragueInterpolator,
                         interpolator_kwargs={},
                         extrapolator=Extrapolator,
                         extrapolator_kwargs={...})
    >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100  # doctest: +ELLIPSIS
    array([ 0.2065400...,  0.1219722...,  0.0513695...])
    """

    optimisation_kwargs = handle_arguments_deprecation(
        {
            'ArgumentRenamed':
            [['optimisation_parameters', 'optimisation_kwargs']],
        }, **kwargs).get('optimisation_kwargs', optimisation_kwargs)

    XYZ = to_domain_1(XYZ)

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    sd = sd_ones(cmfs.shape)

    def objective_function(a):
        """
        Objective function.
        """

        return np.sum(np.diff(a)**2)

    def constraint_function(a):
        """
        Function defining the constraint.
        """

        sd[:] = a
        return sd_to_XYZ_integration(sd, cmfs=cmfs,
                                     illuminant=illuminant) - XYZ

    wavelengths = sd.wavelengths
    bins = wavelengths.size

    optimisation_settings = {
        'method': 'SLSQP',
        'constraints': {
            'type': 'eq',
            'fun': constraint_function
        },
        'bounds': np.tile(np.array([0, 1000]), (bins, 1)),
        'options': {
            'ftol': 1e-10,
        },
    }
    if optimisation_kwargs is not None:
        optimisation_settings.update(optimisation_kwargs)

    result = minimize(objective_function, sd.values, **optimisation_settings)

    if not result.success:
        raise RuntimeError(
            'Optimization failed for {0} after {1} iterations: "{2}".'.format(
                XYZ, result.nit, result.message))

    return SpectralDistribution(from_range_100(result.x * 100),
                                wavelengths,
                                name='{0} (XYZ) - Meng (2015)'.format(XYZ))
예제 #19
0
파일: spectrum.py 프로젝트: wenh06/colour
def XYZ_outer_surface(
        cmfs=MSDS_CMFS['CIE 1931 2 Degree Standard Observer'].copy().align(
            SPECTRAL_SHAPE_OUTER_SURFACE_XYZ),
        illuminant=sd_ones(SPECTRAL_SHAPE_OUTER_SURFACE_XYZ),
        **kwargs):
    """
    Generates the *CIE XYZ* colourspace outer surface for given colour matching
    functions using multi-spectral conversion of pulse waves to *CIE XYZ*
    tristimulus values.

    Parameters
    ----------
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.msds_to_XYZ`},
        Please refer to the documentation of the previously listed definition.

    Returns
    -------
    ndarray
        Outer surface *CIE XYZ* tristimulus values.

    References
    ----------
    :cite:`Lindbloom2015`, :cite:`Mansencal2018`

    Examples
    --------
    >>> from colour.colorimetry import SPECTRAL_SHAPE_DEFAULT
    >>> shape = SpectralShape(
    ...     SPECTRAL_SHAPE_DEFAULT.start, SPECTRAL_SHAPE_DEFAULT.end, 84)
    >>> cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer']
    >>> XYZ_outer_surface(cmfs.copy().align(shape))  # doctest: +ELLIPSIS
    array([[  0.0000000...e+00,   0.0000000...e+00,   0.0000000...e+00],
           [  9.6361381...e-05,   2.9056776...e-06,   4.4961226...e-04],
           [  2.5910529...e-01,   2.1031298...e-02,   1.3207468...e+00],
           [  1.0561021...e-01,   6.2038243...e-01,   3.5423571...e-02],
           [  7.2647980...e-01,   3.5460869...e-01,   2.1005149...e-04],
           [  1.0971874...e-02,   3.9635453...e-03,   0.0000000...e+00],
           [  3.0792572...e-05,   1.1119762...e-05,   0.0000000...e+00],
           [  2.5920165...e-01,   2.1034203...e-02,   1.3211965...e+00],
           [  3.6471551...e-01,   6.4141373...e-01,   1.3561704...e+00],
           [  8.3209002...e-01,   9.7499113...e-01,   3.5633622...e-02],
           [  7.3745167...e-01,   3.5857224...e-01,   2.1005149...e-04],
           [  1.1002667...e-02,   3.9746651...e-03,   0.0000000...e+00],
           [  1.2715395...e-04,   1.4025439...e-05,   4.4961226...e-04],
           [  3.6481187...e-01,   6.4141663...e-01,   1.3566200...e+00],
           [  1.0911953...e+00,   9.9602242...e-01,   1.3563805...e+00],
           [  8.4306189...e-01,   9.7895467...e-01,   3.5633622...e-02],
           [  7.3748247...e-01,   3.5858336...e-01,   2.1005149...e-04],
           [  1.1099028...e-02,   3.9775708...e-03,   4.4961226...e-04],
           [  2.5923244...e-01,   2.1045323...e-02,   1.3211965...e+00],
           [  1.0912916...e+00,   9.9602533...e-01,   1.3568301...e+00],
           [  1.1021671...e+00,   9.9998597...e-01,   1.3563805...e+00],
           [  8.4309268...e-01,   9.7896579...e-01,   3.5633622...e-02],
           [  7.3757883...e-01,   3.5858626...e-01,   6.5966375...e-04],
           [  2.7020432...e-01,   2.5008868...e-02,   1.3211965...e+00],
           [  3.6484266...e-01,   6.4142775...e-01,   1.3566200...e+00],
           [  1.1022635...e+00,   9.9998888...e-01,   1.3568301...e+00],
           [  1.1021979...e+00,   9.9999709...e-01,   1.3563805...e+00],
           [  8.4318905...e-01,   9.7896870...e-01,   3.6083235...e-02],
           [  9.9668412...e-01,   3.7961756...e-01,   1.3214065...e+00],
           [  3.7581454...e-01,   6.4539130...e-01,   1.3566200...e+00],
           [  1.0913224...e+00,   9.9603645...e-01,   1.3568301...e+00],
           [  1.1022943...e+00,   1.0000000...e+00,   1.3568301...e+00]])
    """

    settings = {'method': 'Integration', 'shape': cmfs.shape}
    settings.update(kwargs)

    key = (hash(cmfs), hash(illuminant), six.text_type(settings))
    XYZ = _CACHE_OUTER_SURFACE_XYZ.get(key)

    if XYZ is None:
        pulse_waves = generate_pulse_waves(len(cmfs.wavelengths))
        XYZ = msds_to_XYZ(pulse_waves, cmfs, illuminant, **settings) / 100

        _CACHE_OUTER_SURFACE_XYZ[key] = XYZ

    return XYZ
예제 #20
0
def multi_sds_to_XYZ_integration(
        msd,
        shape,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(
            STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
            shape),
        k=None):
    """
    Converts given multi-spectral distribution array :math:`msd` with given
    spectral shape to *CIE XYZ* tristimulus values using given colour matching
    functions and illuminant.

    Parameters
    ----------
    msa : array_like
        Multi-spectral distribution array :math:`msd`, the wavelengths are
        expected to be in the last axis, e.g. for a 512x384 multi-spectral
        image with 77 bins, ``msd`` shape should be (384, 512, 77).
    shape : SpectralShape, optional
        Spectral shape of the multi-spectral distribution array :math:`msd`,
        ``cmfs`` and ``illuminant`` will be aligned with it.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    k : numeric, optional
        Normalisation constant :math:`k`. For reflecting or transmitting object
        colours, :math:`k` is chosen so that :math:`Y = 100` for objects for
        which the spectral reflectance factor :math:`R(\\lambda)` of the object
        colour or the spectral transmittance factor :math:`\\tau(\\lambda)` of
        the object is equal to unity for all wavelengths. For self-luminous
        objects and illuminants, the constants :math:`k` is usually chosen on
        the grounds of convenience. If, however, in the CIE 1931 standard
        colorimetric system, the :math:`Y` value is required to be numerically
        equal to the absolute value of a photometric quantity, the constant,
        :math:`k`, must be put equal to the numerical value of :math:`K_m`, the
        maximum spectral luminous efficacy (which is equal to
        683 :math:`lm\\cdot W^{-1}`) and :math:`\\Phi_\\lambda(\\lambda)` must
        be the spectral concentration of the radiometric quantity corresponding
        to the photometric quantity required.

    Returns
    -------
    array_like
        *CIE XYZ* tristimulus values, for a 512x384 multi-spectral image with
        77 bins, the output shape will be (384, 512, 3).

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> msd = np.array([
    ...     [
    ...         [0.0137, 0.0913, 0.0152, 0.0281, 0.1918, 0.0430],
    ...         [0.0159, 0.3145, 0.0842, 0.0907, 0.7103, 0.0437],
    ...         [0.0096, 0.2582, 0.4139, 0.2228, 0.0041, 0.3744],
    ...         [0.0111, 0.0709, 0.0220, 0.1249, 0.1817, 0.0020],
    ...         [0.0179, 0.2971, 0.5630, 0.2375, 0.0024, 0.5819],
    ...         [0.1057, 0.4620, 0.1918, 0.5625, 0.4209, 0.0027],
    ...     ],
    ...     [
    ...         [0.0433, 0.2683, 0.2373, 0.0518, 0.0118, 0.0823],
    ...         [0.0258, 0.0831, 0.0430, 0.3230, 0.2302, 0.0081],
    ...         [0.0248, 0.1203, 0.0054, 0.0065, 0.1860, 0.3625],
    ...         [0.0186, 0.1292, 0.0079, 0.4006, 0.9404, 0.3213],
    ...         [0.0310, 0.1682, 0.3719, 0.0861, 0.0041, 0.7849],
    ...         [0.0473, 0.3221, 0.2268, 0.3161, 0.1124, 0.0024],
    ...     ],
    ... ])
    >>> D65 = ILLUMINANTS_SDS['D65']
    >>> multi_sds_to_XYZ(
    ... msd, SpectralShape(400, 700, 60), illuminant=D65)
    ... # doctest: +ELLIPSIS
    array([[[  7.1958378...,   3.8605390...,  10.1016398...],
            [ 25.5738615...,  14.7200581...,  34.8440007...],
            [ 17.5854414...,  28.5668344...,  30.1806687...],
            [ 11.3271912...,   8.4598177...,   7.9015758...],
            [ 19.6581831...,  35.5918480...,  35.1430220...],
            [ 45.8212491...,  39.2600939...,  51.7907710...]],
    <BLANKLINE>
           [[  8.8287837...,  13.3870357...,  30.5702050...],
            [ 22.3324362...,  18.9560919...,   9.3952305...],
            [  6.6887212...,   2.5728891...,  13.2618778...],
            [ 41.8166227...,  27.1191979...,  14.2627944...],
            [  9.2414098...,  20.2056200...,  20.1992502...],
            [ 24.7830551...,  26.2221584...,  36.4430633...]]])
    """

    msd = as_float_array(msd)

    if cmfs.shape != shape:
        runtime_warning('Aligning "{0}" cmfs shape to "{1}".'.format(
            cmfs.name, shape))
        cmfs = cmfs.copy().align(shape)

    if illuminant.shape != shape:
        runtime_warning('Aligning "{0}" illuminant shape to "{1}".'.format(
            illuminant.name, shape))
        illuminant = illuminant.copy().align(shape)

    S = illuminant.values
    x_bar, y_bar, z_bar = tsplit(cmfs.values)
    dw = cmfs.shape.interval

    k = 100 / (np.sum(y_bar * S) * dw) if k is None else k

    X_p = msd * x_bar * S * dw
    Y_p = msd * y_bar * S * dw
    Z_p = msd * z_bar * S * dw

    XYZ = k * np.sum(np.array([X_p, Y_p, Z_p]), axis=-1)

    return from_range_100(np.rollaxis(XYZ, 0, msd.ndim))
예제 #21
0
def sd_to_XYZ(
        sd,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        k=None,
        method='ASTM E308-15',
        **kwargs):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values using
    given colour matching functions, illuminant and method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    k : numeric, optional
        Normalisation constant :math:`k`. For reflecting or transmitting object
        colours, :math:`k` is chosen so that :math:`Y = 100` for objects for
        which the spectral reflectance factor :math:`R(\\lambda)` of the object
        colour or the spectral transmittance factor :math:`\\tau(\\lambda)` of
        the object is equal to unity for all wavelengths. For self-luminous
        objects and illuminants, the constants :math:`k` is usually chosen on
        the grounds of convenience. If, however, in the CIE 1931 standard
        colorimetric system, the :math:`Y` value is required to be numerically
        equal to the absolute value of a photometric quantity, the constant,
        :math:`k`, must be put equal to the numerical value of :math:`K_m`, the
        maximum spectral luminous efficacy (which is equal to
        683 :math:`lm\\cdot W^{-1}`) and :math:`\\Phi_\\lambda(\\lambda)` must
        be the spectral concentration of the radiometric quantity corresponding
        to the photometric quantity required.
    method : unicode, optional
        **{'ASTM E308-15', 'Integration'}**,
        Computation method.

    Other Parameters
    ----------------
    mi_5nm_omission_method : bool, optional
        {:func:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        5 nm measurement intervals spectral 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:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        20 nm measurement intervals spectral distribution conversion to
        tristimulus values will use a dedicated interpolation method instead
        of a table of tristimulus weighting factors.
    use_practice_range : bool, optional
        {:func:`colour.colorimetry.sd_to_XYZ_ASTME30815`},
        Practise *ASTM E308-15* working wavelengths range is [360, 780],
        if *True* this argument will trim the colour matching functions
        appropriately.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`ASTMInternational2011a`, :cite:`ASTMInternational2015b`,
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ(sd, cmfs, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 10.8399031...,   9.6840375...,   6.2164159...])
    >>> sd_to_XYZ(sd, cmfs, illuminant, use_practice_range=False)
    ... # doctest: +ELLIPSIS
    array([ 10.8399852...,   9.6840602...,   6.2164085...])
    >>> sd_to_XYZ(sd, cmfs, illuminant, method='Integration')
    ... # doctest: +ELLIPSIS
    array([ 10.8401846...,   9.6837311...,   6.2120912...])
    """

    function = SD_TO_XYZ_METHODS[method]

    return function(sd,
                    cmfs,
                    illuminant,
                    k=k,
                    **filter_kwargs(function, **kwargs))
예제 #22
0
def sd_to_XYZ_tristimulus_weighting_factors_ASTME30815(
        sd,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        k=None):
    """
    Converts given spectral distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant using a table of
    tristimulus weighting factors according to practise *ASTM E308-15* method.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    k : numeric, optional
        Normalisation constant :math:`k`. For reflecting or transmitting object
        colours, :math:`k` is chosen so that :math:`Y = 100` for objects for
        which the spectral reflectance factor :math:`R(\\lambda)` of the object
        colour or the spectral transmittance factor :math:`\\tau(\\lambda)` of
        the object is equal to unity for all wavelengths. For self-luminous
        objects and illuminants, the constants :math:`k` is usually chosen on
        the grounds of convenience. If, however, in the CIE 1931 standard
        colorimetric system, the :math:`Y` value is required to be numerically
        equal to the absolute value of a photometric quantity, the constant,
        :math:`k`, must be put equal to the numerical value of :math:`K_m`, the
        maximum spectral luminous efficacy (which is equal to
        683 :math:`lm\\cdot W^{-1}`) and :math:`\\Phi_\\lambda(\\lambda)` must
        be the spectral concentration of the radiometric quantity corresponding
        to the photometric quantity required.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* tristimulus values.

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`ASTMInternational2015b`

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_SDS, SpectralDistribution)
    >>> 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
    ... }
    >>> sd = SpectralDistribution(data)
    >>> illuminant = ILLUMINANTS_SDS['D65']
    >>> sd_to_XYZ_tristimulus_weighting_factors_ASTME30815(
    ...     sd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([ 10.8402899...,   9.6843539...,   6.2160858...])
    """

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    if sd.shape.boundaries != cmfs.shape.boundaries:
        runtime_warning('Trimming "{0}" spectral distribution shape to "{1}" '
                        'colour matching functions shape.'.format(
                            illuminant.name, cmfs.name))
        sd = sd.copy().trim(cmfs.shape)

    W = tristimulus_weighting_factors_ASTME202211(
        cmfs, illuminant,
        SpectralShape(cmfs.shape.start, cmfs.shape.end, sd.shape.interval), k)
    start_w = cmfs.shape.start
    end_w = cmfs.shape.start + sd.shape.interval * (W.shape[0] - 1)
    W = adjust_tristimulus_weighting_factors_ASTME30815(
        W, SpectralShape(start_w, end_w, sd.shape.interval), sd.shape)
    R = sd.values

    XYZ = np.sum(W * R[..., np.newaxis], axis=0)

    return from_range_100(XYZ)
예제 #23
0
def multi_sd_to_XYZ(
        msd,
        shape=DEFAULT_SPECTRAL_SHAPE,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        method='Integration'):
    """
    Converts given multi-spectral distribution array :math:`msd` with given
    spectral shape to *CIE XYZ* tristimulus values using given colour matching
    functions and illuminant.

    Parameters
    ----------
    msa : array_like
        Multi-spectral distribution array :math:`msd`, the wavelengths are
        expected to be in the last axis, e.g. for a 512x384 multi-spectral
        image with 77 bins, ``msd`` shape should be (384, 512, 77).
    shape : SpectralShape, optional
        Spectral shape of the multi-spectral distribution array :math:`msd`,
        ``cmfs`` and ``illuminant`` will be aligned with it.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    method : unicode, optional
        **{'Integration'}**,
        Computation method.

    Returns
    -------
    array_like
        *CIE XYZ* tristimulus values, for a 512x384 multi-spectral image with
        77 bins, the output shape will be (384, 512, 3).

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> msd = np.array([
    ...     [
    ...         [0.0137, 0.0913, 0.0152, 0.0281, 0.1918, 0.0430],
    ...         [0.0159, 0.3145, 0.0842, 0.0907, 0.7103, 0.0437],
    ...         [0.0096, 0.2582, 0.4139, 0.2228, 0.0041, 0.3744],
    ...         [0.0111, 0.0709, 0.0220, 0.1249, 0.1817, 0.0020],
    ...         [0.0179, 0.2971, 0.5630, 0.2375, 0.0024, 0.5819],
    ...         [0.1057, 0.4620, 0.1918, 0.5625, 0.4209, 0.0027],
    ...     ],
    ...     [
    ...         [0.0433, 0.2683, 0.2373, 0.0518, 0.0118, 0.0823],
    ...         [0.0258, 0.0831, 0.0430, 0.3230, 0.2302, 0.0081],
    ...         [0.0248, 0.1203, 0.0054, 0.0065, 0.1860, 0.3625],
    ...         [0.0186, 0.1292, 0.0079, 0.4006, 0.9404, 0.3213],
    ...         [0.0310, 0.1682, 0.3719, 0.0861, 0.0041, 0.7849],
    ...         [0.0473, 0.3221, 0.2268, 0.3161, 0.1124, 0.0024],
    ...     ],
    ... ])
    >>> multi_sd_to_XYZ(msd, SpectralShape(400, 700, 60))
    ... # doctest: +ELLIPSIS
    array([[[  7.6862675...,   4.0925470...,   8.4950412...],
            [ 27.4119366...,  15.5014764...,  29.2825122...],
            [ 17.1283666...,  27.7798651...,  25.5232032...],
            [ 11.9824544...,   8.8127109...,   6.6518695...],
            [ 19.1030682...,  34.4597818...,  29.7653804...],
            [ 46.8243374...,  39.9551652...,  43.6541858...]],
    <BLANKLINE>
           [[  8.0978189...,  12.7544378...,  25.8004512...],
            [ 23.4360673...,  19.6127966...,   7.9342408...],
            [  7.0933208...,   2.7894394...,  11.1527704...],
            [ 45.6313772...,  29.0068105...,  11.9934522...],
            [  8.9327884...,  19.4008147...,  17.1534186...],
            [ 24.6610235...,  26.1093760...,  30.7298791...]]])
    """

    function = MULTI_SD_TO_XYZ_METHODS[method]

    return function(msd, shape, cmfs, illuminant)
예제 #24
0
def XYZ_to_sd_Meng2015(
        XYZ,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
    copy().align(DEFAULT_SPECTRAL_SHAPE_MENG_2015),
        illuminant=sd_ones(DEFAULT_SPECTRAL_SHAPE_MENG_2015),
        optimisation_parameters=None):
    """
    Recovers the spectral distribution of given *CIE XYZ* tristimulus values
    using *Meng et al. (2015)* method.

    Parameters
    ----------
    XYZ : array_like, (3,)
        *CIE XYZ* tristimulus values to recover the spectral distribution from.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions. The wavelength
        :math:`\\lambda_{i}` range interval of the colour matching functions
        affects directly the time the computations take. The current default
        interval of 5 is a good compromise between precision and time spent.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    optimisation_parameters : dict_like, optional
        Parameters for :func:`scipy.optimize.minimize` definition.

    Returns
    -------
    SpectralDistribution
        Recovered spectral distribution.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    -   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.sd_to_XYZ_ASTME308` 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.20654008, 0.12197225, 0.05136952])
    >>> cmfs = (
    ...     STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
    ...     copy().align(SpectralShape(360, 780, 10))
    ... )
    >>> sd = XYZ_to_sd_Meng2015(XYZ, cmfs)
    >>> with numpy_print_options(suppress=True):
    ...     # Doctests skip for Python 2.x compatibility.
    ...     sd  # doctest: +SKIP
    SpectralDistribution([[ 360.        ,    0.0780114...],
                          [ 370.        ,    0.0780316...],
                          [ 380.        ,    0.0780471...],
                          [ 390.        ,    0.0780351...],
                          [ 400.        ,    0.0779702...],
                          [ 410.        ,    0.0778033...],
                          [ 420.        ,    0.0770958...],
                          [ 430.        ,    0.0748008...],
                          [ 440.        ,    0.0693230...],
                          [ 450.        ,    0.0601136...],
                          [ 460.        ,    0.0477407...],
                          [ 470.        ,    0.0334964...],
                          [ 480.        ,    0.0193352...],
                          [ 490.        ,    0.0074858...],
                          [ 500.        ,    0.0001225...],
                          [ 510.        ,    0.       ...],
                          [ 520.        ,    0.       ...],
                          [ 530.        ,    0.       ...],
                          [ 540.        ,    0.0124896...],
                          [ 550.        ,    0.0389831...],
                          [ 560.        ,    0.0775105...],
                          [ 570.        ,    0.1247947...],
                          [ 580.        ,    0.1765339...],
                          [ 590.        ,    0.2281918...],
                          [ 600.        ,    0.2751347...],
                          [ 610.        ,    0.3140115...],
                          [ 620.        ,    0.3433561...],
                          [ 630.        ,    0.3635777...],
                          [ 640.        ,    0.3765428...],
                          [ 650.        ,    0.3841726...],
                          [ 660.        ,    0.3883633...],
                          [ 670.        ,    0.3905415...],
                          [ 680.        ,    0.3916742...],
                          [ 690.        ,    0.3922554...],
                          [ 700.        ,    0.3925427...],
                          [ 710.        ,    0.3926783...],
                          [ 720.        ,    0.3927330...],
                          [ 730.        ,    0.3927586...],
                          [ 740.        ,    0.3927548...],
                          [ 750.        ,    0.3927681...],
                          [ 760.        ,    0.3927813...],
                          [ 770.        ,    0.3927840...],
                          [ 780.        ,    0.3927536...]],
                         interpolator=SpragueInterpolator,
                         interpolator_args={},
                         extrapolator=Extrapolator,
                         extrapolator_args={...})
    >>> sd_to_XYZ_integration(sd) / 100  # doctest: +ELLIPSIS
    array([ 0.2065812...,  0.1219752...,  0.0514132...])
    """

    XYZ = to_domain_1(XYZ)

    if illuminant.shape != cmfs.shape:
        runtime_warning(
            'Aligning "{0}" illuminant shape to "{1}" colour matching '
            'functions shape.'.format(illuminant.name, cmfs.name))
        illuminant = illuminant.copy().align(cmfs.shape)

    sd = sd_ones(cmfs.shape)

    def objective_function(a):
        """
        Objective function.
        """

        return np.sum(np.diff(a)**2)

    def constraint_function(a):
        """
        Function defining the constraint.
        """

        sd[:] = a
        return sd_to_XYZ_integration(sd, cmfs=cmfs,
                                     illuminant=illuminant) - XYZ

    wavelengths = sd.wavelengths
    bins = wavelengths.size

    optimisation_settings = {
        'method': 'SLSQP',
        'constraints': {
            'type': 'eq',
            'fun': constraint_function
        },
        'bounds': np.tile(np.array([0, 1000]), (bins, 1)),
        'options': {
            'ftol': 1e-10,
        },
    }
    if optimisation_parameters is not None:
        optimisation_settings.update(optimisation_parameters)

    result = minimize(objective_function, sd.values, **optimisation_settings)

    if not result.success:
        raise RuntimeError(
            'Optimization failed for {0} after {1} iterations: "{2}".'.format(
                XYZ, result.nit, result.message))

    return SpectralDistribution(from_range_100(result.x * 100),
                                wavelengths,
                                name='Meng (2015) - {0}'.format(XYZ))
예제 #25
0
def multi_sds_to_XYZ(
        msd,
        shape=DEFAULT_SPECTRAL_SHAPE,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(ASTME30815_PRACTISE_SHAPE),
        method='Integration',
        **kwargs):
    """
    Converts given multi-spectral distribution array :math:`msd` with given
    spectral shape to *CIE XYZ* tristimulus values using given colour matching
    functions and illuminant.

    Parameters
    ----------
    msa : array_like
        Multi-spectral distribution array :math:`msd`, the wavelengths are
        expected to be in the last axis, e.g. for a 512x384 multi-spectral
        image with 77 bins, ``msd`` shape should be (384, 512, 77).
    shape : SpectralShape, optional
        Spectral shape of the multi-spectral distribution array :math:`msd`,
        ``cmfs`` and ``illuminant`` will be aligned with it.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    method : unicode, optional
        **{'Integration'}**,
        Computation method.

    Other Parameters
    ----------------
    k : numeric, optional
        {:func:`colour.colorimetry.multi_sds_to_XYZ_integration`},
        Normalisation constant :math:`k`. For reflecting or transmitting object
        colours, :math:`k` is chosen so that :math:`Y = 100` for objects for
        which the spectral reflectance factor :math:`R(\\lambda)` of the object
        colour or the spectral transmittance factor :math:`\\tau(\\lambda)` of
        the object is equal to unity for all wavelengths. For self-luminous
        objects and illuminants, the constants :math:`k` is usually chosen on
        the grounds of convenience. If, however, in the CIE 1931 standard
        colorimetric system, the :math:`Y` value is required to be numerically
        equal to the absolute value of a photometric quantity, the constant,
        :math:`k`, must be put equal to the numerical value of :math:`K_m`, the
        maximum spectral luminous efficacy (which is equal to
        683 :math:`lm\\cdot W^{-1}`) and :math:`\\Phi_\\lambda(\\lambda)` must
        be the spectral concentration of the radiometric quantity corresponding
        to the photometric quantity required.

    Returns
    -------
    array_like
        *CIE XYZ* tristimulus values, for a 512x384 multi-spectral image with
        77 bins, the output shape will be (384, 512, 3).

    Notes
    -----

    +-----------+-----------------------+---------------+
    | **Range** | **Scale - Reference** | **Scale - 1** |
    +===========+=======================+===============+
    | ``XYZ``   | [0, 100]              | [0, 1]        |
    +-----------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki2000bf`

    Examples
    --------
    >>> msd = np.array([
    ...     [
    ...         [0.0137, 0.0913, 0.0152, 0.0281, 0.1918, 0.0430],
    ...         [0.0159, 0.3145, 0.0842, 0.0907, 0.7103, 0.0437],
    ...         [0.0096, 0.2582, 0.4139, 0.2228, 0.0041, 0.3744],
    ...         [0.0111, 0.0709, 0.0220, 0.1249, 0.1817, 0.0020],
    ...         [0.0179, 0.2971, 0.5630, 0.2375, 0.0024, 0.5819],
    ...         [0.1057, 0.4620, 0.1918, 0.5625, 0.4209, 0.0027],
    ...     ],
    ...     [
    ...         [0.0433, 0.2683, 0.2373, 0.0518, 0.0118, 0.0823],
    ...         [0.0258, 0.0831, 0.0430, 0.3230, 0.2302, 0.0081],
    ...         [0.0248, 0.1203, 0.0054, 0.0065, 0.1860, 0.3625],
    ...         [0.0186, 0.1292, 0.0079, 0.4006, 0.9404, 0.3213],
    ...         [0.0310, 0.1682, 0.3719, 0.0861, 0.0041, 0.7849],
    ...         [0.0473, 0.3221, 0.2268, 0.3161, 0.1124, 0.0024],
    ...     ],
    ... ])
    >>> multi_sds_to_XYZ(msd, SpectralShape(400, 700, 60))
    ... # doctest: +ELLIPSIS
    array([[[  7.6862675...,   4.0925470...,   8.4950412...],
            [ 27.4119366...,  15.5014764...,  29.2825122...],
            [ 17.1283666...,  27.7798651...,  25.5232032...],
            [ 11.9824544...,   8.8127109...,   6.6518695...],
            [ 19.1030682...,  34.4597818...,  29.7653804...],
            [ 46.8243374...,  39.9551652...,  43.6541858...]],
    <BLANKLINE>
           [[  8.0978189...,  12.7544378...,  25.8004512...],
            [ 23.4360673...,  19.6127966...,   7.9342408...],
            [  7.0933208...,   2.7894394...,  11.1527704...],
            [ 45.6313772...,  29.0068105...,  11.9934522...],
            [  8.9327884...,  19.4008147...,  17.1534186...],
            [ 24.6610235...,  26.1093760...,  30.7298791...]]])
    """

    function = MULTI_SD_TO_XYZ_METHODS[method]

    return function(msd, shape, cmfs, illuminant,
                    **filter_kwargs(function, **kwargs))
예제 #26
0
파일: spectrum.py 프로젝트: wenh06/colour
def is_within_visible_spectrum(
        XYZ,
        cmfs=MSDS_CMFS['CIE 1931 2 Degree Standard Observer'].copy().align(
            SPECTRAL_SHAPE_OUTER_SURFACE_XYZ),
        illuminant=sd_ones(SPECTRAL_SHAPE_OUTER_SURFACE_XYZ),
        tolerance=None,
        **kwargs):
    """
    Returns if given *CIE XYZ* tristimulus values are within visible spectrum
    volume / given colour matching functions volume.

    Parameters
    ----------
    XYZ : array_like
        *CIE XYZ* tristimulus values.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.
    tolerance : numeric, optional
        Tolerance allowed in the inside-triangle check.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.msds_to_XYZ`},
        Please refer to the documentation of the previously listed definition.

    Returns
    -------
    bool
        Is within visible spectrum.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> import numpy as np
    >>> is_within_visible_spectrum(np.array([0.3205, 0.4131, 0.51]))
    array(True, dtype=bool)
    >>> a = np.array([[0.3205, 0.4131, 0.51],
    ...               [-0.0005, 0.0031, 0.001]])
    >>> is_within_visible_spectrum(a)
    array([ True, False], dtype=bool)
    """

    key = (hash(cmfs), hash(illuminant), six.text_type(kwargs))
    vertices = _CACHE_OUTER_SURFACE_XYZ_POINTS.get(key)

    if vertices is None:
        _CACHE_OUTER_SURFACE_XYZ_POINTS[key] = vertices = (XYZ_outer_surface(
            cmfs, illuminant, **kwargs))

    return is_within_mesh_volume(XYZ, vertices, tolerance)
예제 #27
0
def XYZ_to_spectral(
        XYZ,
        cmfs=colour.STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        interval=5,
        tolerance=1e-10,
        maximum_iterations=5000,
        illuminant=sd_ones(),
        max_refl=1.0):

    XYZ = to_domain_1(XYZ)
    shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, interval)
    cmfs = cmfs.copy().align(shape)
    illuminant = illuminant.copy().align(shape)
    spd = sd_zeros(shape)

    def function_objective(a):
        """
        Objective function.
        """
        
        return np.sum(np.diff(a)**2)

    def function_constraint(a):
        """
        Function defining the constraint for XYZ=XYZ.
        """
        
        spd[:] = np.exp(a)
        
        return (XYZ -
                (colour.colorimetry.spectral_to_XYZ_integration(
                    spd, cmfs=cmfs, illuminant=illuminant)))

    def function_constraint2(a):
        """
        Function defining constraint on emission/reflectance
        """
        if max_refl <= 0.0:
            return 0.0
        return max_refl - np.exp(np.max(a)) * 100.

    wavelengths = spd.wavelengths
    bins = wavelengths.size
    constraints = ({'type': 'eq', 'fun': function_constraint},
                   {'type': 'ineq', 'fun': function_constraint2})

    result = minimize(
        function_objective,
        spd.values,
        method='SLSQP',
        constraints=constraints,
        options={
            'ftol': tolerance,
            'maxiter': maximum_iterations,
            'disp': True
        })

    if not result.success:
        raise RuntimeError(
            'Optimization failed for {0} after {1} iterations: "{2}".'.format(
                XYZ, result.nit, result.message))

    return SpectralDistribution(
        from_range_100(np.exp(result.x) * 100),
        wavelengths,
        name='Meng (2015) - {0}'.format(XYZ))
예제 #28
0
def XYZ_to_sd_Meng2015(
        XYZ,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        interval=5,
        optimisation_parameters=None):
    """
    Recovers the spectral distribution of given *CIE XYZ* tristimulus values
    using *Meng et al. (2015)* method.

    Parameters
    ----------
    XYZ : array_like, (3,)
        *CIE XYZ* tristimulus values to recover the spectral 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.
    optimisation_parameters : dict_like, optional
        Parameters for :func:`scipy.optimize.minimize` definition.

    Returns
    -------
    SpectralDistribution
        Recovered spectral distribution.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    -   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.sd_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.20654008, 0.12197225, 0.05136952])
    >>> sd = XYZ_to_sd_Meng2015(XYZ, interval=10)
    >>> with numpy_print_options(suppress=True):
    ...     # Doctests skip for Python 2.x compatibility.
    ...     sd  # doctest: +SKIP
    SpectralDistribution([[ 360.        ,    0.0780368...],
                          [ 370.        ,    0.0780387...],
                          [ 380.        ,    0.0780469...],
                          [ 390.        ,    0.0780894...],
                          [ 400.        ,    0.0780285...],
                          [ 410.        ,    0.0777034...],
                          [ 420.        ,    0.0769175...],
                          [ 430.        ,    0.0746243...],
                          [ 440.        ,    0.0691410...],
                          [ 450.        ,    0.0599949...],
                          [ 460.        ,    0.04779  ...],
                          [ 470.        ,    0.0337270...],
                          [ 480.        ,    0.0196952...],
                          [ 490.        ,    0.0078056...],
                          [ 500.        ,    0.0004368...],
                          [ 510.        ,    0.0000065...],
                          [ 520.        ,    0.       ...],
                          [ 530.        ,    0.       ...],
                          [ 540.        ,    0.0124283...],
                          [ 550.        ,    0.0389186...],
                          [ 560.        ,    0.0774087...],
                          [ 570.        ,    0.1246716...],
                          [ 580.        ,    0.1765055...],
                          [ 590.        ,    0.2281652...],
                          [ 600.        ,    0.2751726...],
                          [ 610.        ,    0.3141208...],
                          [ 620.        ,    0.3434564...],
                          [ 630.        ,    0.3636521...],
                          [ 640.        ,    0.3765182...],
                          [ 650.        ,    0.3841561...],
                          [ 660.        ,    0.3884648...],
                          [ 670.        ,    0.3906975...],
                          [ 680.        ,    0.3918679...],
                          [ 690.        ,    0.3924590...],
                          [ 700.        ,    0.3927439...],
                          [ 710.        ,    0.3928570...],
                          [ 720.        ,    0.3928867...],
                          [ 730.        ,    0.3929099...],
                          [ 740.        ,    0.3928997...],
                          [ 750.        ,    0.3928827...],
                          [ 760.        ,    0.3928579...],
                          [ 770.        ,    0.3927857...],
                          [ 780.        ,    0.3927272...],
                          [ 790.        ,    0.3926867...],
                          [ 800.        ,    0.3926441...],
                          [ 810.        ,    0.3926385...],
                          [ 820.        ,    0.3926247...],
                          [ 830.        ,    0.3926105...]],
                         interpolator=SpragueInterpolator,
                         interpolator_args={},
                         extrapolator=Extrapolator,
                         extrapolator_args={...})
    >>> sd_to_XYZ_integration(sd) / 100  # doctest: +ELLIPSIS
    array([ 0.2065817...,  0.1219754...,  0.0514131...])
    """

    XYZ = to_domain_1(XYZ)
    shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, interval)
    cmfs = cmfs.copy().align(shape)
    illuminant = sd_ones(shape)
    sd = sd_ones(shape)

    def objective_function(a):
        """
        Objective function.
        """

        return np.sum(np.diff(a) ** 2)

    def constraint_function(a):
        """
        Function defining the constraint.
        """

        sd[:] = a
        return sd_to_XYZ_integration(
            sd, cmfs=cmfs, illuminant=illuminant) - XYZ

    wavelengths = sd.wavelengths
    bins = wavelengths.size

    optimisation_settings = {
        'method': 'SLSQP',
        'constraints': {
            'type': 'eq',
            'fun': constraint_function
        },
        'bounds': np.tile(np.array([0, 1000]), (bins, 1)),
        'options': {
            'ftol': 1e-10,
            'maxiter': 2000
        },
    }
    if optimisation_parameters is not None:
        optimisation_settings.update(optimisation_parameters)

    result = minimize(objective_function, sd.values, **optimisation_settings)

    if not result.success:
        raise RuntimeError(
            'Optimization failed for {0} after {1} iterations: "{2}".'.format(
                XYZ, result.nit, result.message))

    return SpectralDistribution(
        from_range_100(result.x * 100),
        wavelengths,
        name='Meng (2015) - {0}'.format(XYZ))
예제 #29
0
def plot_visible_spectrum(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    out_of_gamut_clipping: Boolean = True,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the visible colours spectrum using given standard observer *CIE XYZ*
    colour matching functions.

    Parameters
    ----------
    cmfs
        Standard observer colour matching functions used for computing the
        spectrum domain and colours. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    out_of_gamut_clipping
        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
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.plot_single_sd`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    References
    ----------
    :cite:`Spiker2015a`

    Examples
    --------
    >>> plot_visible_spectrum()  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Visible_Spectrum.png
        :align: center
        :alt: plot_visible_spectrum
    """

    cmfs = cast(MultiSpectralDistributions,
                first_item(filter_cmfs(cmfs).values()))

    bounding_box = (min(cmfs.wavelengths), max(cmfs.wavelengths), 0, 1)

    settings: Dict[str, Any] = {"bounding_box": bounding_box, "y_label": None}
    settings.update(kwargs)
    settings["standalone"] = False

    _figure, axes = plot_single_sd(
        sd_ones(cmfs.shape),
        cmfs=cmfs,
        out_of_gamut_clipping=out_of_gamut_clipping,
        **settings,
    )

    # Removing wavelength line as it doubles with the axes spine.
    axes.lines.pop(0)

    settings = {
        "axes": axes,
        "standalone": True,
        "title": f"The Visible Spectrum - {cmfs.strict_name}",
        "x_label": "Wavelength $\\lambda$ (nm)",
    }
    settings.update(kwargs)

    return render(**settings)
예제 #30
0
파일: colorimetry.py 프로젝트: yixw/colour
def plot_visible_spectrum(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.artist`,
        :func:`colour.plotting.plot_single_sd`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

    Returns
    -------
    tuple
        Current figure and axes.

    References
    ----------
    :cite:`Spiker2015a`

    Examples
    --------
    >>> plot_visible_spectrum()  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Visible_Spectrum.png
        :align: center
        :alt: plot_visible_spectrum
    """

    cmfs = first_item(filter_cmfs(cmfs).values())

    bounding_box = (min(cmfs.wavelengths), max(cmfs.wavelengths), 0, 1)

    settings = {'bounding_box': bounding_box, 'y_label': None}
    settings.update(kwargs)
    settings['standalone'] = False

    _figure, axes = plot_single_sd(sd_ones(cmfs.shape),
                                   cmfs=cmfs,
                                   out_of_gamut_clipping=out_of_gamut_clipping,
                                   **settings)

    # Removing wavelength line as it doubles with the axes spine.
    axes.lines.pop(0)

    settings = {
        'axes': axes,
        'standalone': True,
        'title': 'The Visible Spectrum - {0}'.format(cmfs.strict_name),
        'x_label': 'Wavelength $\\lambda$ (nm)',
    }
    settings.update(kwargs)

    return render(**settings)
예제 #31
0
def XYZ_outer_surface(
        interval=10,
        cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'],
        illuminant=sd_ones(STANDARD_OBSERVERS_CMFS[
            'CIE 1931 2 Degree Standard Observer'].shape)):
    """
    Generates the *CIE XYZ* colourspace outer surface for given colour matching
    functions using multi-spectral conversion of pulse waves to *CIE XYZ*
    tristimulus values.

    Parameters
    ----------
    interval : int, optional
        Wavelength :math:`\\lambda_{i}` range interval used to compute the
        pulse waves.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    illuminant : SpectralDistribution, optional
        Illuminant spectral distribution.

    Returns
    -------
    ndarray
        Outer surface *CIE XYZ* tristimulus values.

    References
    ----------
    :cite:`Lindbloom2015`, :cite:`Mansencal2018`

    Examples
    --------
    >>> XYZ_outer_surface(84)  # doctest: +ELLIPSIS
    array([[  0.0000000...e+00,   0.0000000...e+00,   0.0000000...e+00],
           [  1.4766924...e-03,   4.1530347...e-05,   6.9884362...e-03],
           [  1.6281275...e-01,   3.7114387...e-02,   9.0151471...e-01],
           [  1.8650894...e-01,   5.6617464...e-01,   9.1355179...e-02],
           [  6.1555347...e-01,   3.8427775...e-01,   4.7422070...e-04],
           [  3.3622045...e-02,   1.2354556...e-02,   0.0000000...e+00],
           [  1.0279500...e-04,   3.7121158...e-05,   0.0000000...e+00],
           [  1.6428945...e-01,   3.7155917...e-02,   9.0850314...e-01],
           [  3.4932169...e-01,   6.0328903...e-01,   9.9286989...e-01],
           [  8.0206241...e-01,   9.5045240...e-01,   9.1829399...e-02],
           [  6.4917552...e-01,   3.9663231...e-01,   4.7422070...e-04],
           [  3.3724840...e-02,   1.2391678...e-02,   0.0000000...e+00],
           [  1.5794874...e-03,   7.8651505...e-05,   6.9884362...e-03],
           [  3.5079839...e-01,   6.0333056...e-01,   9.9985832...e-01],
           [  9.6487517...e-01,   9.8756679...e-01,   9.9334411...e-01],
           [  8.3568446...e-01,   9.6280696...e-01,   9.1829399...e-02],
           [  6.4927831...e-01,   3.9666943...e-01,   4.7422070...e-04],
           [  3.5201532...e-02,   1.2433208...e-02,   6.9884362...e-03],
           [  1.6439224...e-01,   3.7193038...e-02,   9.0850314...e-01],
           [  9.6635186...e-01,   9.8760832...e-01,   1.0003325...e+00],
           [  9.9849722...e-01,   9.9992134...e-01,   9.9334411...e-01],
           [  8.3578726...e-01,   9.6284408...e-01,   9.1829399...e-02],
           [  6.5075501...e-01,   3.9671096...e-01,   7.4626569...e-03],
           [  1.9801429...e-01,   4.9547595...e-02,   9.0850314...e-01],
           [  3.5090118...e-01,   6.0336768...e-01,   9.9985832...e-01],
           [  9.9997391...e-01,   9.9996287...e-01,   1.0003325...e+00],
           [  9.9860001...e-01,   9.9995847...e-01,   9.9334411...e-01],
           [  8.3726395...e-01,   9.6288561...e-01,   9.8817836...e-02],
           [  8.1356776...e-01,   4.3382535...e-01,   9.0897737...e-01],
           [  3.8452323...e-01,   6.1572224...e-01,   9.9985832...e-01],
           [  9.6645466...e-01,   9.8764544...e-01,   1.0003325...e+00],
           [  1.0000767...e+00,   1.0000000...e+00,   1.0003325...e+00]])

    """

    key = (interval, hash(cmfs), hash(illuminant))
    XYZ = _XYZ_OUTER_SURFACE_CACHE.get(key)
    if XYZ is None:
        wavelengths = SpectralShape(DEFAULT_SPECTRAL_SHAPE.start,
                                    DEFAULT_SPECTRAL_SHAPE.end,
                                    interval).range()
        values = []
        domain = DEFAULT_SPECTRAL_SHAPE.range()
        for wave in generate_pulse_waves(len(wavelengths)):
            values.append(
                NearestNeighbourInterpolator(wavelengths, wave)(domain))

        XYZ = multi_sds_to_XYZ_integration(values, DEFAULT_SPECTRAL_SHAPE,
                                           cmfs, illuminant)

        XYZ = XYZ / np.max(XYZ[-1, 1])

        _XYZ_OUTER_SURFACE_CACHE[key] = XYZ

    return XYZ