示例#1
0
def colour_quality_scale(spd_test, T, additional_data=False):

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    CCT, _D_uv = T

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(spd_test,
                                                   spd_reference,
                                                   VS_SPDS,
                                                   cmfs,
                                                   chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        spd_reference, spd_reference, VS_SPDS, cmfs)

    XYZ_r = spectral_to_XYZ(spd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)

    Q_as = colour_quality_scales(test_vs_colorimetry_data,
                                 reference_vs_colorimetry_data, CCT_f)

    D_E_RMS = delta_E_RMS(Q_as, 'D_E_ab')
    D_Ep_RMS = delta_E_RMS(Q_as, 'D_Ep_ab')

    Q_a = scale_conversion(D_Ep_RMS, CCT_f)
    Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928)

    p_delta_C = np.average([
        sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0
        for sample_data in Q_as.values()
    ])
    Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C)

    G_t = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in test_vs_colorimetry_data])
    G_r = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in reference_vs_colorimetry_data])

    Q_g = G_t / D65_GAMUT_AREA * 100
    Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            spd_test.name, Q_a, Q_f, Q_p, Q_g, Q_d, Q_as,
            (test_vs_colorimetry_data, reference_vs_colorimetry_data))
    else:
        return Q_a
示例#2
0
    def test_planckian_table(self):
        """
        Tests :func:`colour.temperature.cct.planckian_table` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')

        np.testing.assert_almost_equal(
            [(x.Ti, x.ui, x.vi, x.di) for x in planckian_table(
                np.array([0.1978, 0.3122]), cmfs, 1000, 1010, 10)],
            PLANCKIAN_TABLE)
示例#3
0
    def test_planckian_table_minimal_distance_index(self):
        """
        Tests
        :func:`colour.temperature.cct.planckian_table_minimal_distance_index`
        definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        self.assertEqual(
            planckian_table_minimal_distance_index(
                planckian_table((0.1978, 0.3122), cmfs, 1000, 1010, 10)), 9)
示例#4
0
    def test_planckian_table(self):
        """
        Tests :func:`colour.temperature.cct.planckian_table` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        to_tuple = lambda x: (x.Ti, x.ui, x.vi, x.di)
        np.testing.assert_almost_equal([
            to_tuple(x)
            for x in planckian_table((0.1978, 0.3122), cmfs, 1000, 1010, 10)
        ], [to_tuple(x) for x in PLANCKIAN_TABLE])
示例#5
0
    def test_planckian_table_minimal_distance_index(self):
        """
        Tests :func:`colour.temperature.cct.\
planckian_table_minimal_distance_index` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        self.assertEqual(
            planckian_table_minimal_distance_index(
                planckian_table(
                    np.array([0.1978, 0.3122]), cmfs, 1000, 1010, 10)),
            9)
示例#6
0
    def test_CCT_to_uv_ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.CCT_to_uv_ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(CCT_to_uv_ohno2013(
            6507.4342201047066, 0.003223690901512735,
            cmfs), (0.19780034881616862, 0.31220050291046603),
                                       decimal=7)
        np.testing.assert_almost_equal(CCT_to_uv_ohno2013(
            1041.849524611546, -0.067377582728534946,
            cmfs), (0.43280250331413772, 0.28829975758516474),
                                       decimal=7)
        np.testing.assert_almost_equal(CCT_to_uv_ohno2013(
            2448.9489053326438, -0.084324704634692743,
            cmfs), (0.29256616302348853, 0.27221773141874955),
                                       decimal=7)
示例#7
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.5470349001507, 0.0032236908012382953]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.8672179878763, -0.067377582642145384]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.1932942782669, -0.084369982045528508]),
            decimal=7)
示例#8
0
    def test_uv_to_CCT_ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(uv_to_CCT_ohno2013(
            (0.1978, 0.3122),
            cmfs), (6507.5470349001507, 0.0032236908012382953),
                                       decimal=7)

        np.testing.assert_almost_equal(uv_to_CCT_ohno2013(
            (0.4328, 0.2883),
            cmfs), (1041.8672179878763, -0.067377582642145384),
                                       decimal=7)

        np.testing.assert_almost_equal(uv_to_CCT_ohno2013(
            (0.2927, 0.2722), cmfs,
            iterations=4), (2452.1932942782669, -0.084369982045528508),
                                       decimal=7)
示例#9
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.51282029, 0.00322336]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.68315360, -0.06737802]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.15316417, -0.08437064]),
            decimal=7)
示例#10
0
def is_within_visible_spectrum(XYZ,
                               cmfs=STANDARD_OBSERVERS_CMFS.get(
                                   'CIE 1931 2 Degree Standard Observer'),
                               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.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    tolerance : numeric, optional
        Tolerance allowed in the inside-triangle check.

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

    Notes
    -----
    -   Input *CIE XYZ* tristimulus values are in domain [0, 1].
    -   This definition requires *scipy* to be installed.

    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)
    """

    return is_within_mesh_volume(XYZ, cmfs.values, tolerance)
示例#11
0
文件: spectrum.py 项目: brehm/colour
def is_within_visible_spectrum(XYZ,
                               cmfs=STANDARD_OBSERVERS_CMFS.get(
                                   'CIE 1931 2 Degree Standard Observer'),
                               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.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    tolerance : numeric, optional
        Tolerance allowed in the inside-triangle check.

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

    Notes
    -----
    -   Input *CIE XYZ* tristimulus values are in domain [0, 1].
    -   This definition requires *scipy* to be installed.

    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)
    """

    return is_within_mesh_volume(XYZ, cmfs.values, tolerance)
示例#12
0
    def test_CCT_to_uv_ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.CCT_to_uv_ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            CCT_to_uv_ohno2013(
                6507.4342201047066, 0.003223690901512735, cmfs),
            (0.19780034881616862, 0.31220050291046603),
            decimal=7)
        np.testing.assert_almost_equal(
            CCT_to_uv_ohno2013(
                1041.849524611546, -0.067377582728534946, cmfs),
            (0.43280250331413772, 0.28829975758516474),
            decimal=7)
        np.testing.assert_almost_equal(
            CCT_to_uv_ohno2013(
                2448.9489053326438, -0.084324704634692743, cmfs),
            (0.29256616302348853, 0.27221773141874955),
            decimal=7)
示例#13
0
    def test_CCT_to_uv_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.CCT_to_uv_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            CCT_to_uv_Ohno2013(
                6507.43422010, 0.003223690901513, cmfs),
            np.array([0.19779990, 0.31220046]),
            decimal=7)

        np.testing.assert_almost_equal(
            CCT_to_uv_Ohno2013(
                1041.84952461, -0.067377582728535, cmfs),
            np.array([0.43276248, 0.28830361]),
            decimal=7)

        np.testing.assert_almost_equal(
            CCT_to_uv_Ohno2013(
                2448.94890533, -0.084324704634693, cmfs),
            np.array([0.29256477, 0.2722181]),
            decimal=7)
示例#14
0
def wavelength_to_XYZ(wavelength,
                      cmfs=STANDARD_OBSERVERS_CMFS.get(
                          'CIE 1931 2 Degree Standard Observer')):
    """
    Converts given wavelength :math:`\lambda` to *CIE XYZ* colourspace using
    given colour matching functions.

    If the wavelength :math:`\lambda` is not available in the colour matching
    function, its value will be calculated using *CIE* recommendations:
    The method developed by *Sprague (1880)* should be used for interpolating
    functions having a uniformly spaced independent variable and a
    *Cubic Spline* method for non-uniformly spaced independent variable.

    Parameters
    ----------
    wavelength : numeric
        Wavelength :math:`\lambda` in nm.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* colourspace matrix.

    Raises
    ------
    ValueError
        If wavelength :math:`\lambda` is not in the colour matching
        functions domain.

    Notes
    -----
    -   Output *CIE XYZ* colourspace matrix is in domain [0, 1].
    -   If *scipy* is not unavailable the *Cubic Spline* method will
        fallback to legacy *Linear* interpolation.

    Examples
    --------
    >>> from colour import CMFS
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> wavelength_to_XYZ(480)  # doctest: +ELLIPSIS
    array([ 0.09564  ,  0.13902  ,  0.812950...])
    """

    shape = cmfs.shape
    if wavelength < shape.start or wavelength > shape.end:
        raise ValueError(
            '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format(
                wavelength, shape.start, shape.end))

    if wavelength not in cmfs:
        wavelengths, values, = cmfs.wavelengths, cmfs.values
        interpolator = (SpragueInterpolator
                        if cmfs.is_uniform() else
                        SplineInterpolator)

        interpolators = [interpolator(wavelengths, values[:, i])
                         for i in range(values.shape[-1])]

        return np.array([interpolator(wavelength)
                         for interpolator in interpolators])
    else:
        return np.array(cmfs.get(wavelength))
示例#15
0
文件: cct.py 项目: scooperly/colour
def uv_to_CCT_Ohno2013(uv,
                       cmfs=STANDARD_OBSERVERS_CMFS.get(
                           'CIE 1931 2 Degree Standard Observer'),
                       start=CCT_MINIMAL,
                       end=CCT_MAXIMAL,
                       count=CCT_SAMPLES,
                       iterations=CCT_CALCULATION_ITERATIONS):
    """
    Returns the correlated colour temperature :math:`T_{cp}` and
    :math:`\Delta_{uv}` from given *CIE UCS* colourspace *uv* chromaticity
    coordinates, colour matching functions and temperature range using
    Ohno (2013) method.

    The iterations parameter defines the calculations precision: The higher its
    value, the more planckian tables will be generated through cascade
    expansion in order to converge to the exact solution.

    Parameters
    ----------
    uv : array_like
        *CIE UCS* colourspace *uv* chromaticity coordinates.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    start : numeric, optional
        Temperature range start in kelvins.
    end : numeric, optional
        Temperature range end in kelvins.
    count : int, optional
        Temperatures count in the planckian tables.
    iterations : int, optional
        Number of planckian tables to generate.

    Returns
    -------
    ndarray
        Correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}`.

    References
    ----------
    .. [3]  Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv.
            LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> cmfs = 'CIE 1931 2 Degree Standard Observer'
    >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs)
    >>> uv = np.array([0.1978, 0.3122])
    >>> uv_to_CCT_Ohno2013(uv, cmfs)  # doctest: +ELLIPSIS
    array([  6.5075470...e+03,   3.2236908...e-03])
    """

    # Ensuring we do at least one iteration to initialise variables.
    if iterations <= 0:
        iterations = 1

    # Planckian table creation through cascade expansion.
    for _i in range(iterations):
        table = planckian_table(uv, cmfs, start, end, count)
        index = planckian_table_minimal_distance_index(table)
        if index == 0:
            warning(
                ('Minimal distance index is on lowest planckian table bound, '
                 'unpredictable results may occur!'))
            index += 1
        elif index == len(table) - 1:
            warning(
                ('Minimal distance index is on highest planckian table bound, '
                 'unpredictable results may occur!'))
            index -= 1

        start = table[index - 1].Ti
        end = table[index + 1].Ti

    _ux, vx = uv

    Tuvdip, Tuvdi, Tuvdin = (table[index - 1], table[index], table[index + 1])
    Tip, uip, vip, dip = Tuvdip.Ti, Tuvdip.ui, Tuvdip.vi, Tuvdip.di
    Ti, di = Tuvdi.Ti, Tuvdi.di
    Tin, uin, vin, din = Tuvdin.Ti, Tuvdin.ui, Tuvdin.vi, Tuvdin.di

    # Triangular solution.
    l = np.sqrt((uin - uip)**2 + (vin - vip)**2)
    x = (dip**2 - din**2 + l**2) / (2 * l)
    T = Tip + (Tin - Tip) * (x / l)

    vtx = vip + (vin - vip) * (x / l)
    sign = 1 if vx - vtx >= 0 else -1
    D_uv = (dip**2 - x**2)**(1 / 2) * sign

    # Parabolic solution.
    if D_uv < 0.002:
        X = (Tin - Ti) * (Tip - Tin) * (Ti - Tip)
        a = (Tip * (din - di) + Ti * (dip - din) + Tin * (di - dip)) * X**-1
        b = (-(Tip**2 * (din - di) + Ti**2 * (dip - din) + Tin**2 *
               (di - dip)) * X**-1)
        c = (-(dip * (Tin - Ti) * Ti * Tin + di *
               (Tip - Tin) * Tip * Tin + din * (Ti - Tip) * Tip * Ti) * X**-1)

        T = -b / (2 * a)

        D_uv = sign * (a * T**2 + b * T + c)

    return np.array([T, D_uv])
示例#16
0
def wavelength_to_XYZ(wavelength,
                      cmfs=STANDARD_OBSERVERS_CMFS.get(
                          'CIE 1931 2 Degree Standard Observer'),
                      method=None):
    """
    Converts given wavelength :math:`\lambda` to *CIE XYZ* tristimulus values
    using given colour matching functions.

    If the wavelength :math:`\lambda` is not available in the colour matching
    function, its value will be calculated using *CIE* recommendations:
    The method developed by Sprague (1880) should be used for interpolating
    functions having a uniformly spaced independent variable and a
    *Cubic Spline* method for non-uniformly spaced independent variable.

    Parameters
    ----------
    wavelength : numeric or array_like
        Wavelength :math:`\lambda` in nm.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    method : unicode, optional
        {None, 'Cubic Spline', 'Linear', 'Pchip', 'Sprague'},
        Enforce given interpolation method.

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

    Raises
    ------
    RuntimeError
        If Sprague (1880) interpolation method is forced with a
        non-uniformly spaced independent variable.
    ValueError
        If the interpolation method is not defined or if wavelength
        :math:`\lambda` is not contained in the colour matching functions
        domain.

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in domain [0, 1].
    -   If *scipy* is not unavailable the *Cubic Spline* method will fallback
        to legacy *Linear* interpolation.
    -   Sprague (1880) interpolator cannot be used for interpolating
        functions having a non-uniformly spaced independent variable.

    Warning
    -------
    -   If *scipy* is not unavailable the *Cubic Spline* method will fallback
        to legacy *Linear* interpolation.
    -   *Cubic Spline* interpolator requires at least 3 wavelengths
        :math:`\lambda_n` for interpolation.
    -   *Linear* interpolator requires at least 2 wavelengths :math:`\lambda_n`
        for interpolation.
    -   *Pchip* interpolator requires at least 2 wavelengths :math:`\lambda_n`
        for interpolation.
    -   Sprague (1880) interpolator requires at least 6 wavelengths
        :math:`\lambda_n` for interpolation.

    Examples
    --------
    Uniform data is using Sprague (1880) interpolation by default:

    >>> from colour import CMFS
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> wavelength_to_XYZ(480, cmfs)  # doctest: +ELLIPSIS
    array([ 0.09564  ,  0.13902  ,  0.812950...])
    >>> wavelength_to_XYZ(480.5, cmfs)  # doctest: +ELLIPSIS
    array([ 0.0914287...,  0.1418350...,  0.7915726...])

    Enforcing *Cubic Spline* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Cubic Spline')  # doctest: +ELLIPSIS
    array([ 0.0914288...,  0.1418351...,  0.7915729...])

    Enforcing *Linear* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Linear')  # doctest: +ELLIPSIS
    array([ 0.0914697...,  0.1418482...,  0.7917337...])

    Enforcing *Pchip* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Pchip')  # doctest: +ELLIPSIS
    array([ 0.0914280...,  0.1418341...,  0.7915711...])
    """

    cmfs_shape = cmfs.shape
    if (np.min(wavelength) < cmfs_shape.start or
            np.max(wavelength) > cmfs_shape.end):
        raise ValueError(
            '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format(
                wavelength, cmfs_shape.start, cmfs_shape.end))

    if wavelength not in cmfs:
        wavelengths, values, = cmfs.wavelengths, cmfs.values

        if is_string(method):
            method = method.lower()

        is_uniform = cmfs.is_uniform()

        if method is None:
            if is_uniform:
                interpolator = SpragueInterpolator
            else:
                interpolator = CubicSplineInterpolator
        elif method == 'cubic spline':
            interpolator = CubicSplineInterpolator
        elif method == 'linear':
            interpolator = LinearInterpolator
        elif method == 'pchip':
            interpolator = PchipInterpolator
        elif method == 'sprague':
            if is_uniform:
                interpolator = SpragueInterpolator
            else:
                raise RuntimeError(
                    ('"Sprague" interpolator can only be used for '
                     'interpolating functions having a uniformly spaced '
                     'independent variable!'))
        else:
            raise ValueError(
                'Undefined "{0}" interpolator!'.format(method))

        interpolators = [interpolator(wavelengths, values[..., i])
                         for i in range(values.shape[-1])]

        XYZ = np.dstack([i(np.ravel(wavelength)) for i in interpolators])
    else:
        XYZ = cmfs.get(wavelength)

    XYZ = np.reshape(XYZ, np.asarray(wavelength).shape + (3,))

    return XYZ
示例#17
0
def spectral_to_XYZ(spd,
                    cmfs=STANDARD_OBSERVERS_CMFS.get(
                        'CIE 1931 2 Degree Standard Observer'),
                    illuminant=None):
    """
    Converts given spectral power distribution to *CIE XYZ* colourspace using
    given colour matching functions and illuminant.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        *Illuminant* spectral power distribution.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* colourspace matrix.

    Warning
    -------
    The output domain of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* colourspace matrix is in domain [0, 100].

    References
    ----------
    .. [1]  **Wyszecki & Stiles**,
            *Color Science - Concepts and Methods Data and Formulae -
            Second Edition*,
            Wiley Classics Library Edition, published 2000,
            ISBN-10: 0-471-39918-3,
            page  158.

    Examples
    --------
    >>> from colour import CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution  # noqa
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {380: 0.0600, 390: 0.0600}
    >>> spd = SpectralPowerDistribution('Custom', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ(spd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([  4.5764852...e-04,   1.2964866...e-05,   2.1615807...e-03])
    """

    shape = cmfs.shape
    if spd.shape != cmfs.shape:
        spd = spd.clone().zeros(shape)

    if illuminant is None:
        illuminant = ones_spd(shape)
    else:
        if illuminant.shape != cmfs.shape:
            illuminant = illuminant.clone().zeros(shape)

    illuminant = illuminant.values
    spd = spd.values

    x_bar, y_bar, z_bar = (cmfs.x_bar.values,
                           cmfs.y_bar.values,
                           cmfs.z_bar.values)

    x_products = spd * x_bar * illuminant
    y_products = spd * y_bar * illuminant
    z_products = spd * z_bar * illuminant

    normalising_factor = 100 / np.sum(y_bar * illuminant)

    XYZ = np.array([normalising_factor * np.sum(x_products),
                    normalising_factor * np.sum(y_products),
                    normalising_factor * np.sum(z_products)])

    return XYZ
示例#18
0
def spectral_to_XYZ(spd,
                    cmfs=STANDARD_OBSERVERS_CMFS.get(
                        'CIE 1931 2 Degree Standard Observer'),
                    illuminant=ones_spd(
                        STANDARD_OBSERVERS_CMFS.get(
                            'CIE 1931 2 Degree Standard Observer').shape),
                    method='ASTM E308–15',
                    **kwargs):
    """
    Converts given spectral power distribution to *CIE XYZ* tristimulus values
    using given colour matching functions, illuminant and method.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        Illuminant spectral power distribution.
    method : unicode, optional
        **{'ASTM E308–15', 'Integration'}**,
        Computation method.
    \**kwargs : dict, optional
        Keywords arguments.

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

    Warning
    -------
    The output range of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in range [0, 100].

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution)
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {
    ...     400: 0.0641,
    ...     420: 0.0645,
    ...     440: 0.0562,
    ...     460: 0.0537,
    ...     480: 0.0559,
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360,
    ...     620: 0.1511,
    ...     640: 0.1688,
    ...     660: 0.1996,
    ...     680: 0.2397,
    ...     700: 0.2852}
    >>> spd = SpectralPowerDistribution('Sample', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ(  # doctest: +ELLIPSIS
    ...     spd, cmfs, illuminant)
    array([ 11.5290265...,   9.9502091...,   4.7098882...])
    >>> spectral_to_XYZ(  # doctest: +ELLIPSIS
    ...     spd, cmfs, illuminant, use_practice_range=False)
    array([ 11.5291275...,   9.9502369...,   4.7098811...])
    >>> spectral_to_XYZ(  # doctest: +ELLIPSIS
    ...     spd, cmfs, illuminant, method='Integration')
    array([ 11.5296285...,   9.9499467...,   4.7066079...])
    """

    function = SPECTRAL_TO_XYZ_METHODS[method]

    filter_kwargs(function, **kwargs)

    return function(spd, cmfs, illuminant, **kwargs)
示例#19
0
def wavelength_to_XYZ(wavelength,
                      cmfs=STANDARD_OBSERVERS_CMFS.get(
                          'CIE 1931 2 Degree Standard Observer'),
                      method=None):
    """
    Converts given wavelength :math:`\lambda` to *CIE XYZ* tristimulus values
    using given colour matching functions.

    If the wavelength :math:`\lambda` is not available in the colour matching
    function, its value will be calculated using *CIE* recommendations:
    The method developed by Sprague (1880) should be used for interpolating
    functions having a uniformly spaced independent variable and a
    *Cubic Spline* method for non-uniformly spaced independent variable.

    Parameters
    ----------
    wavelength : numeric or array_like
        Wavelength :math:`\lambda` in nm.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    method : unicode, optional
        {None, 'Cubic Spline', 'Linear', 'Pchip', 'Sprague'},
        Enforce given interpolation method.

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

    Raises
    ------
    RuntimeError
        If Sprague (1880) interpolation method is forced with a
        non-uniformly spaced independent variable.
    ValueError
        If the interpolation method is not defined or if wavelength
        :math:`\lambda` is not contained in the colour matching functions
        domain.

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in range [0, 1].
    -   If *scipy* is not unavailable the *Cubic Spline* method will fallback
        to legacy *Linear* interpolation.
    -   Sprague (1880) interpolator cannot be used for interpolating
        functions having a non-uniformly spaced independent variable.

    Warning
    -------
    -   If *scipy* is not unavailable the *Cubic Spline* method will fallback
        to legacy *Linear* interpolation.
    -   *Cubic Spline* interpolator requires at least 3 wavelengths
        :math:`\lambda_n` for interpolation.
    -   *Linear* interpolator requires at least 2 wavelengths :math:`\lambda_n`
        for interpolation.
    -   *Pchip* interpolator requires at least 2 wavelengths :math:`\lambda_n`
        for interpolation.
    -   Sprague (1880) interpolator requires at least 6 wavelengths
        :math:`\lambda_n` for interpolation.

    Examples
    --------
    Uniform data is using Sprague (1880) interpolation by default:

    >>> from colour import CMFS
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> wavelength_to_XYZ(480, cmfs)  # doctest: +ELLIPSIS
    array([ 0.09564  ,  0.13902  ,  0.812950...])
    >>> wavelength_to_XYZ(480.5, cmfs)  # doctest: +ELLIPSIS
    array([ 0.0914287...,  0.1418350...,  0.7915726...])

    Enforcing *Cubic Spline* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Cubic Spline')  # doctest: +ELLIPSIS
    array([ 0.0914288...,  0.1418351...,  0.7915729...])

    Enforcing *Linear* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Linear')  # doctest: +ELLIPSIS
    array([ 0.0914697...,  0.1418482...,  0.7917337...])

    Enforcing *Pchip* interpolation:

    >>> wavelength_to_XYZ(480.5, cmfs, 'Pchip')  # doctest: +ELLIPSIS
    array([ 0.0914280...,  0.1418341...,  0.7915711...])
    """

    cmfs_shape = cmfs.shape
    if (np.min(wavelength) < cmfs_shape.start
            or np.max(wavelength) > cmfs_shape.end):
        raise ValueError(
            '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format(
                wavelength, cmfs_shape.start, cmfs_shape.end))

    if wavelength not in cmfs:
        wavelengths, values, = cmfs.wavelengths, cmfs.values

        if is_string(method):
            method = method.lower()

        is_uniform = cmfs.is_uniform()

        if method is None:
            if is_uniform:
                interpolator = SpragueInterpolator
            else:
                interpolator = CubicSplineInterpolator
        elif method == 'cubic spline':
            interpolator = CubicSplineInterpolator
        elif method == 'linear':
            interpolator = LinearInterpolator
        elif method == 'pchip':
            interpolator = PchipInterpolator
        elif method == 'sprague':
            if is_uniform:
                interpolator = SpragueInterpolator
            else:
                raise RuntimeError(
                    ('"Sprague" interpolator can only be used for '
                     'interpolating functions having a uniformly spaced '
                     'independent variable!'))
        else:
            raise ValueError('Undefined "{0}" interpolator!'.format(method))

        interpolators = [
            interpolator(wavelengths, values[..., i])
            for i in range(values.shape[-1])
        ]

        XYZ = np.dstack([i(np.ravel(wavelength)) for i in interpolators])
    else:
        XYZ = cmfs.get(wavelength)

    XYZ = np.reshape(XYZ, np.asarray(wavelength).shape + (3, ))

    return XYZ
示例#20
0
def spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815(
    spd,
    cmfs=STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer'),
    illuminant=ones_spd(
        STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer').shape)):
    """
    Converts given spectral power distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant using a table
    of tristimulus weighting factors accordingly to practise
    *ASTM E308–15* method [2]_.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        Illuminant spectral power distribution.

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

    Warning
    -------
    The output range of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in range [0, 100].

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution)
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {
    ...     400: 0.0641,
    ...     420: 0.0645,
    ...     440: 0.0562,
    ...     460: 0.0537,
    ...     480: 0.0559,
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360,
    ...     620: 0.1511,
    ...     640: 0.1688,
    ...     660: 0.1996,
    ...     680: 0.2397,
    ...     700: 0.2852}
    >>> spd = SpectralPowerDistribution('Sample', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815(
    ...     spd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([ 11.5296311...,   9.9505845...,   4.7098037...])
    """

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

    if spd.shape.boundaries != cmfs.shape.boundaries:
        warning('Trimming "{0}" spectral power distribution shape to "{1}" '
                'colour matching functions shape.'.format(illuminant, cmfs))
        spd = spd.clone().trim_wavelengths(cmfs.shape)

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

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

    return XYZ
示例#21
0
def spectral_to_XYZ_ASTME30815(
        spd,
        cmfs=STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer'),
        illuminant=ones_spd(
            STANDARD_OBSERVERS_CMFS.get(
                'CIE 1931 2 Degree Standard Observer').shape),
        use_practice_range=True,
        mi_5nm_omission_method=True,
        mi_20nm_interpolation_method=True):
    """
    Converts given spectral power distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant accordingly to
    practise *ASTM E308–15* method [2]_.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        Illuminant spectral power distribution.
    use_practice_range : bool, optional
        Practise *ASTM E308–15*  working wavelengths range is [360, 780],
        if `True` this argument will trim the colour matching functions
        appropriately.
    mi_5nm_omission_method : bool, optional
        5 nm measurement intervals spectral power distribution conversion to
        tristimulus values will use a 5 nm version of the colour matching
        functions instead of a table of tristimulus weighting factors.
    mi_20nm_interpolation_method : bool, optional
        20 nm measurement intervals spectral power distribution conversion to
        tristimulus values will use a dedicated interpolation method instead
        of a table of tristimulus weighting factors.

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

    Warning
    -------
    -   The tables of tristimulus weighting factors are cached in
        :attr:`_TRISTIMULUS_WEIGHTING_FACTORS_CACHE` attribute. Their
        identifier key is defined by the colour matching functions and
        illuminant names along the current shape such as:
        `CIE 1964 10 Degree Standard Observer, A, (360.0, 830.0, 10.0)`
        Considering the above, one should be mindful that using similar colour
        matching functions and illuminant names but with different spectral
        data will lead to unexpected behaviour.
    -   The output range of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in range [0, 100].

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution)
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {
    ...     400: 0.0641,
    ...     420: 0.0645,
    ...     440: 0.0562,
    ...     460: 0.0537,
    ...     480: 0.0559,
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360,
    ...     620: 0.1511,
    ...     640: 0.1688,
    ...     660: 0.1996,
    ...     680: 0.2397,
    ...     700: 0.2852}
    >>> spd = SpectralPowerDistribution('Sample', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ_ASTME30815(
    ...     spd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([ 11.5290265...,   9.9502091...,   4.7098882...])
    """

    if spd.shape.interval not in (1, 5, 10, 20):
        raise ValueError(
            'Tristimulus values conversion from spectral data accordingly to '
            'practise *ASTM E308-15* should be performed on spectral data '
            'with measurement interval of 1, 5, 10 or 20nm!')

    if use_practice_range:
        cmfs = cmfs.clone().trim_wavelengths(SpectralShape(360, 780, 1))

    method = spectral_to_XYZ_tristimulus_weighting_factors_ASTME30815
    if spd.shape.interval == 1:
        method = spectral_to_XYZ_integration
    elif spd.shape.interval == 5 and mi_5nm_omission_method:
        if cmfs.shape.interval != 5:
            cmfs = cmfs.clone().interpolate(SpectralShape(interval=5))
        method = spectral_to_XYZ_integration
    elif spd.shape.interval == 20 and mi_20nm_interpolation_method:
        spd = spd.clone()
        if spd.shape.boundaries != cmfs.shape.boundaries:
            warning(
                'Trimming "{0}" spectral power distribution shape to "{1}" '
                'colour matching functions shape.'.format(illuminant, cmfs))
            spd.trim_wavelengths(cmfs.shape)

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

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

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

    XYZ = method(spd, cmfs, illuminant)

    return XYZ
示例#22
0
def spectral_to_XYZ(spd,
                    cmfs=STANDARD_OBSERVERS_CMFS.get(
                        'CIE 1931 2 Degree Standard Observer'),
                    illuminant=None):
    """
    Converts given spectral power distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        *Illuminant* spectral power distribution.

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

    Warning
    -------
    The output domain of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in domain [0, 100].

    References
    ----------
    .. [1]  Wyszecki, G., & Stiles, W. S. (2000). Integration Replace by
            Summation. In Color Science: Concepts and Methods, Quantitative
            Data and Formulae (pp. 158–163). Wiley. ISBN:978-0471399186

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution)
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {380: 0.0600, 390: 0.0600}
    >>> spd = SpectralPowerDistribution('Custom', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ(spd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([  4.5764852...e-04,   1.2964866...e-05,   2.1615807...e-03])
    """

    shape = cmfs.shape
    if spd.shape != cmfs.shape:
        spd = spd.clone().zeros(shape)

    if illuminant is None:
        illuminant = ones_spd(shape)
    else:
        if illuminant.shape != cmfs.shape:
            illuminant = illuminant.clone().zeros(shape)

    spd = spd.values
    x_bar, y_bar, z_bar = (cmfs.x_bar.values,
                           cmfs.y_bar.values,
                           cmfs.z_bar.values)
    illuminant = illuminant.values

    x_products = spd * x_bar * illuminant
    y_products = spd * y_bar * illuminant
    z_products = spd * z_bar * illuminant

    normalising_factor = 100 / np.sum(y_bar * illuminant)

    XYZ = np.array([normalising_factor * np.sum(x_products),
                    normalising_factor * np.sum(y_products),
                    normalising_factor * np.sum(z_products)])

    return XYZ
示例#23
0
def spectral_to_XYZ_integration(
    spd,
    cmfs=STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer'),
    illuminant=ones_spd(
        STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer').shape)):
    """
    Converts given spectral power distribution to *CIE XYZ* tristimulus values
    using given colour matching functions and illuminant accordingly to
    classical integration method.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        Illuminant spectral power distribution.

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

    Warning
    -------
    The output range of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* tristimulus values are in range [0, 100].

    References
    ----------
    .. [3]  Wyszecki, G., & Stiles, W. S. (2000). Integration Replace by
            Summation. In Color Science: Concepts and Methods, Quantitative
            Data and Formulae (pp. 158–163). Wiley. ISBN:978-0471399186

    Examples
    --------
    >>> from colour import (
    ...     CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution)
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {
    ...     400: 0.0641,
    ...     420: 0.0645,
    ...     440: 0.0562,
    ...     460: 0.0537,
    ...     480: 0.0559,
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360,
    ...     620: 0.1511,
    ...     640: 0.1688,
    ...     660: 0.1996,
    ...     680: 0.2397,
    ...     700: 0.2852}
    >>> spd = SpectralPowerDistribution('Sample', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ_integration(  # doctest: +ELLIPSIS
    ...     spd, cmfs, illuminant)
    array([ 11.5296285...,   9.9499467...,   4.7066079...])
    """

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

    if spd.shape != cmfs.shape:
        warning('Aligning "{0}" spectral power distribution shape to "{1}" '
                'colour matching functions shape.'.format(spd, cmfs))
        spd = spd.clone().align(cmfs.shape)

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

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

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

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

    return XYZ
示例#24
0
def spectral_to_XYZ(spd,
                    cmfs=STANDARD_OBSERVERS_CMFS.get(
                        'CIE 1931 2 Degree Standard Observer'),
                    illuminant=None):
    """
    Converts given spectral power distribution to *CIE XYZ* colourspace using
    given colour matching functions and illuminant.

    Parameters
    ----------
    spd : SpectralPowerDistribution
        Spectral power distribution.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    illuminant : SpectralPowerDistribution, optional
        *Illuminant* spectral power distribution.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* colourspace matrix.

    Warning
    -------
    The output domain of that definition is non standard!

    Notes
    -----
    -   Output *CIE XYZ* colourspace matrix is in domain [0, 100].

    References
    ----------
    .. [1]  **Wyszecki & Stiles**,
            *Color Science - Concepts and Methods Data and Formulae -
            Second Edition*,
            Wiley Classics Library Edition, published 2000,
            ISBN-10: 0-471-39918-3,
            page  158.

    Examples
    --------
    >>> from colour import CMFS, ILLUMINANTS_RELATIVE_SPDS, SpectralPowerDistribution  # noqa
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> data = {380: 0.0600, 390: 0.0600}
    >>> spd = SpectralPowerDistribution('Custom', data)
    >>> illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D50')
    >>> spectral_to_XYZ(spd, cmfs, illuminant)  # doctest: +ELLIPSIS
    array([  4.5764852...e-04,   1.2964866...e-05,   2.1615807...e-03])
    """

    shape = cmfs.shape
    if spd.shape != cmfs.shape:
        spd = spd.clone().zeros(shape)

    if illuminant is None:
        illuminant = ones_spd(shape)
    else:
        if illuminant.shape != cmfs.shape:
            illuminant = illuminant.clone().zeros(shape)

    illuminant = illuminant.values
    spd = spd.values

    x_bar, y_bar, z_bar = (cmfs.x_bar.values, cmfs.y_bar.values,
                           cmfs.z_bar.values)

    x_products = spd * x_bar * illuminant
    y_products = spd * y_bar * illuminant
    z_products = spd * z_bar * illuminant

    normalising_factor = 100 / np.sum(y_bar * illuminant)

    XYZ = np.array([
        normalising_factor * np.sum(x_products),
        normalising_factor * np.sum(y_products),
        normalising_factor * np.sum(z_products)
    ])

    return XYZ
示例#25
0
文件: cct.py 项目: fangjy88/colour
def uv_to_CCT_Ohno2013(uv,
                       cmfs=STANDARD_OBSERVERS_CMFS.get(
                           'CIE 1931 2 Degree Standard Observer'),
                       start=CCT_MINIMAL,
                       end=CCT_MAXIMAL,
                       count=CCT_SAMPLES,
                       iterations=CCT_CALCULATION_ITERATIONS):
    """
    Returns the correlated colour temperature :math:`T_{cp}` and
    :math:`\Delta_{uv}` from given *CIE UCS* colourspace *uv* chromaticity
    coordinates, colour matching functions and temperature range using
    Ohno (2013) method.

    The iterations parameter defines the calculations precision: The higher its
    value, the more planckian tables will be generated through cascade
    expansion in order to converge to the exact solution.

    Parameters
    ----------
    uv : array_like
        *CIE UCS* colourspace *uv* chromaticity coordinates.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.
    start : numeric, optional
        Temperature range start in kelvins.
    end : numeric, optional
        Temperature range end in kelvins.
    count : int, optional
        Temperatures count in the planckian tables.
    iterations : int, optional
        Number of planckian tables to generate.

    Returns
    -------
    ndarray
        Correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}`.

    References
    ----------
    .. [3]  Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv.
            LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> cmfs = 'CIE 1931 2 Degree Standard Observer'
    >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs)
    >>> uv = np.array([0.1978, 0.3122])
    >>> uv_to_CCT_Ohno2013(uv, cmfs)  # doctest: +ELLIPSIS
    array([  6.5075470...e+03,   3.2236908...e-03])
    """

    # Ensuring we do at least one iteration to initialise variables.
    if iterations <= 0:
        iterations = 1

    # Planckian table creation through cascade expansion.
    for _i in range(iterations):
        table = planckian_table(uv, cmfs, start, end, count)
        index = planckian_table_minimal_distance_index(table)
        if index == 0:
            warning(
                ('Minimal distance index is on lowest planckian table bound, '
                 'unpredictable results may occur!'))
            index += 1
        elif index == len(table) - 1:
            warning(
                ('Minimal distance index is on highest planckian table bound, '
                 'unpredictable results may occur!'))
            index -= 1

        start = table[index - 1].Ti
        end = table[index + 1].Ti

    _ux, vx = uv

    Tuvdip, Tuvdi, Tuvdin = (table[index - 1], table[index], table[index + 1])
    Tip, uip, vip, dip = Tuvdip.Ti, Tuvdip.ui, Tuvdip.vi, Tuvdip.di
    Ti, di = Tuvdi.Ti, Tuvdi.di
    Tin, uin, vin, din = Tuvdin.Ti, Tuvdin.ui, Tuvdin.vi, Tuvdin.di

    # Triangular solution.
    l = np.sqrt((uin - uip) ** 2 + (vin - vip) ** 2)
    x = (dip ** 2 - din ** 2 + l ** 2) / (2 * l)
    T = Tip + (Tin - Tip) * (x / l)

    vtx = vip + (vin - vip) * (x / l)
    sign = 1 if vx - vtx >= 0 else -1
    D_uv = (dip ** 2 - x ** 2) ** (1 / 2) * sign

    # Parabolic solution.
    if D_uv < 0.002:
        X = (Tin - Ti) * (Tip - Tin) * (Ti - Tip)
        a = (Tip * (din - di) + Ti * (dip - din) + Tin * (di - dip)) * X ** -1
        b = (-(Tip ** 2 * (din - di) + Ti ** 2 * (dip - din) + Tin ** 2 *
               (di - dip)) * X ** -1)
        c = (-(dip * (Tin - Ti) * Ti * Tin + di * (Tip - Tin) * Tip * Tin
               + din * (Ti - Tip) * Tip * Ti) * X ** -1)

        T = -b / (2 * a)

        D_uv = sign * (a * T ** 2 + b * T + c)

    return np.array([T, D_uv])
示例#26
0
文件: cri.py 项目: KevinJW/colour
def colour_rendering_index(test_spd, additional_data=False):
    """
    Returns the *colour rendering index* of given spectral power distribution.

    Parameters
    ----------
    test_spd : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or (numeric, dict)
        Colour rendering index, Tsc data.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_rendering_index(spd)  # doctest: +ELLIPSIS
    64.1507331...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    test_spd = test_spd.clone().align(shape)

    tcs_spds = {}
    for index, tcs_spd in sorted(TCS_SPDS.items()):
        tcs_spds[index] = tcs_spd.clone().align(shape)

    XYZ = spectral_to_XYZ(test_spd, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, Duv = uv_to_CCT_robertson1968(uv)

    if CCT < 5000:
        reference_spd = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_illuminant_D(CCT)
        reference_spd = D_illuminant_relative_spd(xy)
        reference_spd.align(shape)

    test_tcs_colorimetry_data = _tcs_colorimetry_data(
        test_spd, reference_spd, tcs_spds, cmfs, chromatic_adaptation=True)
    reference_tcs_colorimetry_data = _tcs_colorimetry_data(
        reference_spd, reference_spd, tcs_spds, cmfs)

    colour_rendering_indexes = _colour_rendering_indexes(
        test_tcs_colorimetry_data, reference_tcs_colorimetry_data)

    colour_rendering_index = np.average([
        v for k, v in colour_rendering_indexes.items()
        if k in (1, 2, 3, 4, 5, 6, 7, 8)
    ])

    if additional_data:
        return (colour_rendering_index, colour_rendering_indexes,
                [test_tcs_colorimetry_data, reference_tcs_colorimetry_data])
    else:
        return colour_rendering_index
示例#27
0
文件: cri.py 项目: scooperly/colour
def colour_rendering_index(spd_test, additional_data=False):
    """
    Returns the *colour rendering index* :math:`Q_a` of given spectral power
    distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CRI_Specification
        Colour rendering index.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_rendering_index(spd)  # doctest: +ELLIPSIS
    64.1507331...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    spd_test = spd_test.clone().align(shape)

    tcs_spds = {}
    for index, tcs_spd in TCS_SPDS.items():
        tcs_spds[index] = tcs_spd.clone().align(shape)

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Robertson1968(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(spd_test,
                                                     spd_reference,
                                                     tcs_spds,
                                                     cmfs,
                                                     chromatic_adaptation=True)

    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        spd_reference, spd_reference, tcs_spds, cmfs)

    Q_as = colour_rendering_indexes(test_tcs_colorimetry_data,
                                    reference_tcs_colorimetry_data)

    Q_a = np.average(
        [v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)])

    if additional_data:
        return CRI_Specification(
            spd_test.name, Q_a, Q_as,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data))
    else:
        return Q_a
示例#28
0
文件: cqs.py 项目: scooperly/colour
def colour_quality_scale(spd_test, additional_data=False):
    """
    Returns the *colour quality scale* of given spectral power distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_quality_scale(spd)  # doctest: +ELLIPSIS
    64.6860580...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    spd_test = spd_test.clone().align(shape)

    vs_spds = {}
    for index, vs_spd in VS_SPDS.items():
        vs_spds[index] = vs_spd.clone().align(shape)

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(spd_test,
                                                   spd_reference,
                                                   vs_spds,
                                                   cmfs,
                                                   chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        spd_reference, spd_reference, vs_spds, cmfs)

    XYZ_r = spectral_to_XYZ(spd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)

    Q_as = colour_quality_scales(test_vs_colorimetry_data,
                                 reference_vs_colorimetry_data, CCT_f)

    D_E_RMS = delta_E_RMS(Q_as, 'D_E_ab')
    D_Ep_RMS = delta_E_RMS(Q_as, 'D_Ep_ab')

    Q_a = scale_conversion(D_Ep_RMS, CCT_f)
    Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928)

    p_delta_C = np.average([
        sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0
        for sample_data in Q_as.values()
    ])
    Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C)

    G_t = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in test_vs_colorimetry_data])
    G_r = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in reference_vs_colorimetry_data])

    Q_g = G_t / D65_GAMUT_AREA * 100
    Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            spd_test.name, Q_a, Q_f, Q_p, Q_g, Q_d, Q_as,
            (test_vs_colorimetry_data, reference_vs_colorimetry_data))
    else:
        return Q_a
示例#29
0
文件: cqs.py 项目: Nick-Shaw/colour
def colour_quality_scale(spd_test, additional_data=False):
    """
    Returns the *colour quality scale* of given spectral power distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_quality_scale(spd)  # doctest: +ELLIPSIS
    64.6781117...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get(
        'CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(
        spd_test,
        spd_reference,
        VS_SPDS,
        cmfs,
        chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        spd_reference,
        spd_reference,
        VS_SPDS,
        cmfs)

    XYZ_r = spectral_to_XYZ(spd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)

    Q_as = colour_quality_scales(
        test_vs_colorimetry_data, reference_vs_colorimetry_data, CCT_f)

    D_E_RMS = delta_E_RMS(Q_as, 'D_E_ab')
    D_Ep_RMS = delta_E_RMS(Q_as, 'D_Ep_ab')

    Q_a = scale_conversion(D_Ep_RMS, CCT_f)
    Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928)

    p_delta_C = np.average(
        [sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0
         for sample_data in
         Q_as.values()])
    Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C)

    G_t = gamut_area([vs_CQS_data.Lab
                      for vs_CQS_data in test_vs_colorimetry_data])
    G_r = gamut_area([vs_CQS_data.Lab
                      for vs_CQS_data in reference_vs_colorimetry_data])

    Q_g = G_t / D65_GAMUT_AREA * 100
    Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(spd_test.name,
                                 Q_a,
                                 Q_f,
                                 Q_p,
                                 Q_g,
                                 Q_d,
                                 Q_as,
                                 (test_vs_colorimetry_data,
                                  reference_vs_colorimetry_data))
    else:
        return Q_a
示例#30
0
文件: cct.py 项目: scooperly/colour
def CCT_to_uv_Ohno2013(
    CCT,
    D_uv=0,
    cmfs=STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')):
    """
    Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given
    correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}` and
    colour matching functions using Ohno (2013) method.

    Parameters
    ----------
    CCT : numeric
        Correlated colour temperature :math:`T_{cp}`.
    D_uv : numeric, optional
        :math:`\Delta_{uv}`.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

    Returns
    -------
    ndarray
        *CIE UCS* colourspace *uv* chromaticity coordinates.

    References
    ----------
    .. [4]  Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv.
            LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> cmfs = 'CIE 1931 2 Degree Standard Observer'
    >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs)
    >>> CCT = 6507.4342201047066
    >>> D_uv = 0.003223690901512735
    >>> CCT_to_uv_Ohno2013(CCT, D_uv, cmfs)  # doctest: +ELLIPSIS
    array([ 0.1978003...,  0.3122005...])
    """

    shape = cmfs.shape
    delta = 0.01

    spd = blackbody_spd(CCT, shape)
    XYZ = spectral_to_XYZ(spd, cmfs)
    XYZ *= 1 / np.max(XYZ)
    UVW = XYZ_to_UCS(XYZ)
    u0, v0 = UCS_to_uv(UVW)

    if D_uv == 0:
        return np.array([u0, v0])
    else:
        spd = blackbody_spd(CCT + delta, shape)
        XYZ = spectral_to_XYZ(spd, cmfs)
        XYZ *= 1 / np.max(XYZ)
        UVW = XYZ_to_UCS(XYZ)
        u1, v1 = UCS_to_uv(UVW)

        du = u0 - u1
        dv = v0 - v1

        u = u0 - D_uv * (dv / np.sqrt(du**2 + dv**2))
        v = v0 + D_uv * (du / np.sqrt(du**2 + dv**2))

        return np.array([u, v])
示例#31
0
文件: cri.py 项目: brehm/colour
def colour_rendering_index(spd_test, additional_data=False):
    """
    Returns the *colour rendering index* :math:`Q_a` of given spectral power
    distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CRI_Specification
        Colour rendering index.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_rendering_index(spd)  # doctest: +ELLIPSIS
    64.1507331...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    spd_test = spd_test.clone().align(shape)

    tcs_spds = {}
    for index, tcs_spd in TCS_SPDS.items():
        tcs_spds[index] = tcs_spd.clone().align(shape)

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Robertson1968(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(
        spd_test,
        spd_reference,
        tcs_spds,
        cmfs,
        chromatic_adaptation=True)

    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        spd_reference,
        spd_reference,
        tcs_spds,
        cmfs)

    Q_as = colour_rendering_indexes(
        test_tcs_colorimetry_data, reference_tcs_colorimetry_data)

    Q_a = np.average([v.Q_a for k, v in Q_as.items()
                      if k in (1, 2, 3, 4, 5, 6, 7, 8)])

    if additional_data:
        return CRI_Specification(spd_test.name,
                                 Q_a,
                                 Q_as,
                                 (test_tcs_colorimetry_data,
                                  reference_tcs_colorimetry_data))
    else:
        return Q_a
示例#32
0
文件: cri.py 项目: KevinJW/colour
def colour_rendering_index(test_spd, additional_data=False):
    """
    Returns the *colour rendering index* of given spectral power distribution.

    Parameters
    ----------
    test_spd : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or (numeric, dict)
        Colour rendering index, Tsc data.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_rendering_index(spd)  # doctest: +ELLIPSIS
    64.1507331...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape
    test_spd = test_spd.clone().align(shape)

    tcs_spds = {}
    for index, tcs_spd in sorted(TCS_SPDS.items()):
        tcs_spds[index] = tcs_spd.clone().align(shape)

    XYZ = spectral_to_XYZ(test_spd, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, Duv = uv_to_CCT_robertson1968(uv)

    if CCT < 5000:
        reference_spd = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_illuminant_D(CCT)
        reference_spd = D_illuminant_relative_spd(xy)
        reference_spd.align(shape)

    test_tcs_colorimetry_data = _tcs_colorimetry_data(
        test_spd,
        reference_spd,
        tcs_spds,
        cmfs,
        chromatic_adaptation=True)
    reference_tcs_colorimetry_data = _tcs_colorimetry_data(
        reference_spd,
        reference_spd,
        tcs_spds, cmfs)

    colour_rendering_indexes = _colour_rendering_indexes(
        test_tcs_colorimetry_data, reference_tcs_colorimetry_data)

    colour_rendering_index = np.average(
        [v for k, v in colour_rendering_indexes.items()
         if k in (1, 2, 3, 4, 5, 6, 7, 8)])

    if additional_data:
        return (colour_rendering_index,
                colour_rendering_indexes,
                [test_tcs_colorimetry_data, reference_tcs_colorimetry_data])
    else:
        return colour_rendering_index
示例#33
0
文件: cct.py 项目: fangjy88/colour
def CCT_to_uv_Ohno2013(CCT,
                       D_uv=0,
                       cmfs=STANDARD_OBSERVERS_CMFS.get(
                           'CIE 1931 2 Degree Standard Observer')):
    """
    Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given
    correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}` and
    colour matching functions using Ohno (2013) method.

    Parameters
    ----------
    CCT : numeric
        Correlated colour temperature :math:`T_{cp}`.
    D_uv : numeric, optional
        :math:`\Delta_{uv}`.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

    Returns
    -------
    ndarray
        *CIE UCS* colourspace *uv* chromaticity coordinates.

    References
    ----------
    .. [4]  Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv.
            LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> cmfs = 'CIE 1931 2 Degree Standard Observer'
    >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs)
    >>> CCT = 6507.4342201047066
    >>> D_uv = 0.003223690901512735
    >>> CCT_to_uv_Ohno2013(CCT, D_uv, cmfs)  # doctest: +ELLIPSIS
    array([ 0.1978003...,  0.3122005...])
    """

    shape = cmfs.shape
    delta = 0.01

    spd = blackbody_spd(CCT, shape)
    XYZ = spectral_to_XYZ(spd, cmfs)
    XYZ *= 1 / np.max(XYZ)
    UVW = XYZ_to_UCS(XYZ)
    u0, v0 = UCS_to_uv(UVW)

    if D_uv == 0:
        return np.array([u0, v0])
    else:
        spd = blackbody_spd(CCT + delta, shape)
        XYZ = spectral_to_XYZ(spd, cmfs)
        XYZ *= 1 / np.max(XYZ)
        UVW = XYZ_to_UCS(XYZ)
        u1, v1 = UCS_to_uv(UVW)

        du = u0 - u1
        dv = v0 - v1

        u = u0 - D_uv * (dv / np.sqrt(du ** 2 + dv ** 2))
        v = v0 + D_uv * (du / np.sqrt(du ** 2 + dv ** 2))

        return np.array([u, v])
示例#34
0
def wavelength_to_XYZ(
    wavelength,
    cmfs=STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')):
    """
    Converts given wavelength :math:`\lambda` to *CIE XYZ* colourspace using
    given colour matching functions.

    If the wavelength :math:`\lambda` is not available in the colour matching
    function, its value will be calculated using *CIE* recommendations:
    The method developed by *Sprague (1880)* should be used for interpolating
    functions having a uniformly spaced independent variable and a
    *Cubic Spline* method for non-uniformly spaced independent variable.

    Parameters
    ----------
    wavelength : numeric
        Wavelength :math:`\lambda` in nm.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

    Returns
    -------
    ndarray, (3,)
        *CIE XYZ* colourspace matrix.

    Raises
    ------
    ValueError
        If wavelength :math:`\lambda` is not in the colour matching
        functions domain.

    Notes
    -----
    -   Output *CIE XYZ* colourspace matrix is in domain [0, 1].
    -   If *scipy* is not unavailable the *Cubic Spline* method will
        fallback to legacy *Linear* interpolation.

    Examples
    --------
    >>> from colour import CMFS
    >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer')
    >>> wavelength_to_XYZ(480)  # doctest: +ELLIPSIS
    array([ 0.09564  ,  0.13902  ,  0.812950...])
    """

    shape = cmfs.shape
    if wavelength < shape.start or wavelength > shape.end:
        raise ValueError(
            '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format(
                wavelength, shape.start, shape.end))

    if wavelength not in cmfs:
        wavelengths, values, = cmfs.wavelengths, cmfs.values
        interpolator = (SpragueInterpolator
                        if cmfs.is_uniform() else SplineInterpolator)

        interpolators = [
            interpolator(wavelengths, values[:, i])
            for i in range(values.shape[-1])
        ]

        return np.array(
            [interpolator(wavelength) for interpolator in interpolators])
    else:
        return np.array(cmfs.get(wavelength))