Пример #1
0
def plot_spectra_TM_30_18(ax, spec):
    """
    Plots a comparison of spectral distributions of a test and a reference
    illuminant, for use in *TM-30-18* color rendition reports.

    Parameters
    ==========
    spec : TM_30_18_Specification
        *TM-30-18* color fidelity specification.
    """

    Y_reference = sd_to_XYZ(spec.sd_reference)[1]
    Y_test = sd_to_XYZ(spec.sd_test)[1]

    ax.plot(spec.sd_reference.wavelengths,
            spec.sd_reference.values / Y_reference,
            'k',
            label='Reference')
    ax.plot(spec.sd_test.wavelengths,
            spec.sd_test.values / Y_test,
            'r',
            label='Test')

    ax.set_yticks([])
    ax.grid()

    ax.set_xlabel('Wavelength (nm)')
    ax.set_ylabel('Radiant power')
    ax.legend()
Пример #2
0
    def test_domain_range_scale_XYZ_to_sd_Otsu2018(self):
        """
        Test :func:`colour.recovery.otsu2018.XYZ_to_sd_Otsu2018` definition
        domain and range scale support.
        """

        XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952])
        XYZ_o = sd_to_XYZ(
            XYZ_to_sd_Otsu2018(XYZ_i, self._cmfs, self._sd_D65),
            self._cmfs,
            self._sd_D65,
        )

        d_r = (("reference", 1, 1), ("1", 1, 0.01), ("100", 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(
                    sd_to_XYZ(
                        XYZ_to_sd_Otsu2018(
                            XYZ_i * factor_a, self._cmfs, self._sd_D65
                        ),
                        self._cmfs,
                        self._sd_D65,
                    ),
                    XYZ_o * factor_b,
                    decimal=7,
                )
Пример #3
0
    def test_optimise(self):
        """Test :class:`colour.recovery.otsu2018.Tree_Otsu2018.optimise` method."""

        node_tree = Tree_Otsu2018(self._reflectances, self._cmfs, self._sd_D65)
        node_tree.optimise(iterations=5)

        dataset = node_tree.to_dataset()
        dataset.write(self._path)

        dataset = Dataset_Otsu2018()
        dataset.read(self._path)

        for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = XYZ_to_sd_Otsu2018(
                XYZ, self._cmfs, self._sd_D65, dataset, False
            )
            recovered_XYZ = (
                sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
            )
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = metric_mse(
                reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values,
                recovered_sd.values,
            )
            self.assertLess(error, 0.075)

            delta_E = delta_E_CIE1976(Lab, recovered_Lab)
            self.assertLess(delta_E, 1e-12)
Пример #4
0
def plot_spectra_ANSIIESTM3018(specification, **kwargs):
    """
    Plots a comparison of the spectral distributions of a test emission source
    and a reference illuminant for *ANSI/IES TM-30-18 Colour Rendition Report*.

    Parameters
    ----------
    specification : ColourQuality_Specification_ANSIIESTM3018
        *ANSI/IES TM-30-18 Colour Rendition Report* specification.

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

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

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)
    >>> plot_spectra_ANSIIESTM3018(specification)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)
    """

    settings = kwargs.copy()

    _figure, axes = artist(**settings)

    Y_reference = sd_to_XYZ(specification.sd_reference)[1]
    Y_test = sd_to_XYZ(specification.sd_test)[1]

    axes.plot(specification.sd_reference.wavelengths,
              specification.sd_reference.values / Y_reference,
              'black',
              label='Reference')
    axes.plot(specification.sd_test.wavelengths,
              specification.sd_test.values / Y_test,
              '#F05046',
              label='Test')
    axes.tick_params(axis='y', which='both', length=0)
    axes.set_yticklabels([])

    settings = {
        'axes': axes,
        'legend': True,
        'legend_columns': 2,
        'x_label': 'Wavelength (nm)',
        'y_label': 'Radiant Power\n(Equal Luminous Flux)',
    }
    settings.update(kwargs)

    return render(**settings)
Пример #5
0
def vs_colorimetry_data(
    sd_test: SpectralDistribution,
    sd_reference: SpectralDistribution,
    sds_vs: Dict[str, SpectralDistribution],
    cmfs: MultiSpectralDistributions,
    chromatic_adaptation: Boolean = False,
) -> Tuple[VS_ColorimetryData, ...]:
    """
    Return the *VS test colour samples* colorimetry data.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    sd_reference
        Reference spectral distribution.
    sds_vs
        *VS test colour samples* spectral distributions.
    cmfs
        Standard observer colour matching functions.
    chromatic_adaptation
        Whether to perform chromatic adaptation.

    Returns
    -------
    :class:`tuple`
        *VS test colour samples* colorimetry data.
    """

    XYZ_t = sd_to_XYZ(sd_test, cmfs)
    XYZ_t /= XYZ_t[1]

    XYZ_r = sd_to_XYZ(sd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    xy_r = XYZ_to_xy(XYZ_r)

    vs_data = []
    for _key, value in sorted(INDEXES_TO_NAMES_VS.items()):
        sd_vs = sds_vs[value]

        with domain_range_scale("1"):
            XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test)

        if chromatic_adaptation:
            XYZ_vs = chromatic_adaptation_VonKries(XYZ_vs,
                                                   XYZ_t,
                                                   XYZ_r,
                                                   transform="CMCCAT2000")

        Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r)
        _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs)

        vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs))

    return tuple(vs_data)
Пример #6
0
def vs_colorimetry_data(sd_test,
                        sd_reference,
                        sds_vs,
                        cmfs,
                        chromatic_adaptation=False):
    """
    Returns the *VS test colour samples* colorimetry data.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    sd_reference : SpectralDistribution
        Reference spectral distribution.
    sds_vs : dict
        *VS test colour samples* spectral distributions.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    chromatic_adaptation : bool, optional
        Perform chromatic adaptation.

    Returns
    -------
    list
        *VS test colour samples* colorimetry data.
    """

    XYZ_t = sd_to_XYZ(sd_test, cmfs)
    XYZ_t /= XYZ_t[1]

    XYZ_r = sd_to_XYZ(sd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    xy_r = XYZ_to_xy(XYZ_r)

    vs_data = []
    for _key, value in sorted(VS_INDEXES_TO_NAMES.items()):
        sd_vs = sds_vs[value]

        with domain_range_scale('1'):
            XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test)

        if chromatic_adaptation:
            XYZ_vs = chromatic_adaptation_VonKries(XYZ_vs,
                                                   XYZ_t,
                                                   XYZ_r,
                                                   transform='CMCCAT2000')

        Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r)
        _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs)

        vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs))
    return vs_data
Пример #7
0
def _CCT_to_uv_Ohno2013(
        CCT_D_uv,
        cmfs=MSDS_CMFS_STANDARD_OBSERVER['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_D_uv : ndarray
        Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

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

    CCT, D_uv = tsplit(CCT_D_uv)

    cmfs = cmfs.copy().trim(SPECTRAL_SHAPE_DEFAULT)

    shape = cmfs.shape

    delta = 0.01

    sd = sd_blackbody(CCT, shape)
    XYZ = sd_to_XYZ(sd, 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:
        sd = sd_blackbody(CCT + delta, shape)
        XYZ = sd_to_XYZ(sd, 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.hypot(du, dv))
        v = v0 + D_uv * (du / np.hypot(du, dv))

        return np.array([u, v])
Пример #8
0
def _CCT_to_uv_Ohno2013(
        CCT_D_uv,
        cmfs=STANDARD_OBSERVERS_CMFS['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_D_uv : ndarray
        Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
    cmfs : XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions.

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

    CCT, D_uv = tsplit(CCT_D_uv)

    cmfs = cmfs.copy().trim(ASTME30815_PRACTISE_SHAPE)

    shape = cmfs.shape

    delta = 0.01

    sd = sd_blackbody(CCT, shape)
    XYZ = sd_to_XYZ(sd, 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:
        sd = sd_blackbody(CCT + delta, shape)
        XYZ = sd_to_XYZ(sd, 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.hypot(du, dv))
        v = v0 + D_uv * (du / np.hypot(du, dv))

        return np.array([u, v])
Пример #9
0
def vs_colorimetry_data(sd_test,
                        sd_reference,
                        sds_vs,
                        cmfs,
                        chromatic_adaptation=False):
    """
    Returns the *VS test colour samples* colorimetry data.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    sd_reference : SpectralDistribution
        Reference spectral distribution.
    sds_vs : dict
        *VS test colour samples* spectral distributions.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    chromatic_adaptation : bool, optional
        Perform chromatic adaptation.

    Returns
    -------
    list
        *VS test colour samples* colorimetry data.
    """

    XYZ_t = sd_to_XYZ(sd_test, cmfs)
    XYZ_t /= XYZ_t[1]

    XYZ_r = sd_to_XYZ(sd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    xy_r = XYZ_to_xy(XYZ_r)

    vs_data = []
    for _key, value in sorted(VS_INDEXES_TO_NAMES.items()):
        sd_vs = sds_vs[value]

        with domain_range_scale('1'):
            XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test)

        if chromatic_adaptation:
            XYZ_vs = chromatic_adaptation_VonKries(
                XYZ_vs, XYZ_t, XYZ_r, transform='CMCCAT2000')

        Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r)
        _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs)

        vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs))
    return vs_data
Пример #10
0
def _CCT_to_uv_Ohno2013(
        CCT_D_uv: ArrayLike,
        cmfs: Optional[MultiSpectralDistributions] = None) -> NDArray:
    """
    Return 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_D_uv
        Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
    cmfs
        Standard observer colour matching functions, default to the
        *CIE 1931 2 Degree Standard Observer*.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE UCS* colourspace *uv* chromaticity coordinates.
    """

    CCT, D_uv = tsplit(CCT_D_uv)

    cmfs, _illuminant = handle_spectral_arguments(cmfs)

    delta = 0.01

    sd = sd_blackbody(CCT, cmfs.shape)
    XYZ = sd_to_XYZ(sd, 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:
        sd = sd_blackbody(CCT + delta, cmfs.shape)
        XYZ = sd_to_XYZ(sd, 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.hypot(du, dv))
        v = v0 + D_uv * (du / np.hypot(du, dv))

        return np.array([u, v])
Пример #11
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)
Пример #12
0
def CCT_reference_illuminant(sd: SpectralDistribution) -> NDArray:
    """
    Compute the reference illuminant correlated colour temperature
    :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral
    distribution using *Ohno (2013)* method.

    Parameters
    ----------
    sd
        Test spectral distribution.

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

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> CCT_reference_illuminant(sd)  # doctest: +ELLIPSIS
    array([  4.2244697...e+03,   1.7871111...e-03])
    """

    XYZ = sd_to_XYZ(sd)

    return uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ)))
Пример #13
0
    def setUp(self):
        """Initialise the common tests attributes."""

        self._shape = SPECTRAL_SHAPE_OTSU2018
        self._cmfs, self._sd_D65 = handle_spectral_arguments(
            shape_default=self._shape
        )

        self._reflectances = sds_and_msds_to_msds(
            list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values())
            + list(SDS_COLOURCHECKERS["BabelColor Average"].values())
        )

        self._tree = Tree_Otsu2018(
            self._reflectances, self._cmfs, self._sd_D65
        )

        self._XYZ_D65 = sd_to_XYZ(self._sd_D65)
        self._xy_D65 = XYZ_to_xy(self._XYZ_D65)

        self._temporary_directory = tempfile.mkdtemp()

        self._path = os.path.join(
            self._temporary_directory, "Test_Otsu2018.npz"
        )
Пример #14
0
def CCT_reference_illuminant(sd):
    """
    Computes the reference illuminant correlated colour temperature
    :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral
    distribution using *Ohno (2013)* method.

    Parameters
    ----------
    sd : SpectralDistribution
        Test spectral distribution.

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

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> CCT_reference_illuminant(sd)  # doctest: +ELLIPSIS
    (4224.4697052..., 0.0017871...)
    """

    XYZ = sd_to_XYZ(sd)

    CCT, D_uv = uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ)))

    return CCT, D_uv
Пример #15
0
    def setUp(self):
        """Initialise the common tests attributes."""

        self._shape = SPECTRAL_SHAPE_OTSU2018
        self._cmfs, self._sd_D65 = handle_spectral_arguments(
            shape_default=self._shape
        )
        self._XYZ_D65 = sd_to_XYZ(self._sd_D65)
        self._xy_D65 = XYZ_to_xy(self._XYZ_D65)
Пример #16
0
    def test_sd_to_XYZ(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ`
        definition.
        """

        # Testing that the cache returns a copy of the data.
        XYZ = sd_to_XYZ(self._sd, self._cmfs, self._A)

        np.testing.assert_almost_equal(
            XYZ, np.array([14.46372680, 10.85832950, 2.04663200]), decimal=7)

        XYZ *= 10

        np.testing.assert_almost_equal(
            sd_to_XYZ(self._sd, self._cmfs, self._A),
            np.array([14.46372680, 10.85832950, 2.04663200]),
            decimal=7,
        )
Пример #17
0
    def test_domain_range_scale_XYZ_to_sd_Jakob2019(self):
        """
        Tests :func:`colour.recovery.jakob2019.XYZ_to_sd_Jakob2019` definition
        domain and range scale support.
        """

        XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952])
        XYZ_o = sd_to_XYZ(XYZ_to_sd_Jakob2019(XYZ_i, self._cmfs, self._sd_D65),
                          self._cmfs, self._sd_D65)

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(sd_to_XYZ(
                    XYZ_to_sd_Jakob2019(XYZ_i * factor_a, self._cmfs,
                                        self._sd_D65), self._cmfs,
                    self._sd_D65),
                                               XYZ_o * factor_b,
                                               decimal=7)
Пример #18
0
    def check_basis_functions(self):
        """
        Test :func:`colour.recovery.RGB_to_sd_Mallett2019` definition or the
        more specialised :func:`colour.recovery.RGB_to_sd_Mallett2019`
        definition.
        """

        # Make sure the white point is reconstructed as a perfectly flat
        # spectrum.
        RGB = np.full(3, 1.0)
        sd = RGB_to_sd_Mallett2019(RGB, self._basis)
        self.assertLess(np.var(sd.values), 1e-5)

        # Check if the primaries or their combination exceeds the [0, 1] range.
        lower = np.zeros_like(sd.values) - 1e-12
        upper = np.ones_like(sd.values) + 1e12
        for RGB in [[1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]]:
            sd = RGB_to_sd_Mallett2019(RGB, self._basis)
            np.testing.assert_array_less(sd.values, upper)
            np.testing.assert_array_less(lower, sd.values)

        # Check Delta E's using a colour checker.
        for name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)
            RGB = XYZ_to_RGB(
                XYZ,
                self._RGB_colourspace.whitepoint,
                self._xy_D65,
                self._RGB_colourspace.matrix_XYZ_to_RGB,
            )

            recovered_sd = RGB_to_sd_Mallett2019(RGB, self._basis)
            recovered_XYZ = (
                sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
            )
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = delta_E_CIE1976(Lab, recovered_Lab)

            if error > 4 * JND_CIE1976 / 100:  # pragma: no cover
                self.fail(f'Delta E for "{name}" is {error}!')
Пример #19
0
    def test_NodeTree_Otsu2018_and_Dataset_Otsu2018(self):
        """
        Tests :class:`colour.recovery.otsu2018.NodeTree_Otsu2018` dataset
        generation and :class:`colour.recovery.otsu2018.Dataset_Otsu2018`
        input and output. The generated dataset is also tested for
        reconstruction errors.
        """

        reflectances = []
        for colourchecker in ['ColorChecker N Ohta', 'BabelColor Average']:
            for sd in SDS_COLOURCHECKERS[colourchecker].values():
                reflectances.append(sd.copy().align(self._shape).values)

        node_tree = NodeTree_Otsu2018(reflectances, self._cmfs, self._sd_D65)
        node_tree.optimise(iterations=2)

        path = os.path.join(self._temporary_directory, 'Test_Otsu2018.npz')
        dataset = node_tree.to_dataset()
        dataset.write(path)

        dataset = Dataset_Otsu2018()
        dataset.read(path)

        for sd in SDS_COLOURCHECKERS['ColorChecker N Ohta'].values():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = XYZ_to_sd_Otsu2018(XYZ, self._cmfs, self._sd_D65,
                                              dataset, False)
            recovered_XYZ = sd_to_XYZ(recovered_sd, self._cmfs,
                                      self._sd_D65) / 100
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = metric_mse(sd.copy().align(SPECTRAL_SHAPE_OTSU2018).values,
                               recovered_sd.values)
            self.assertLess(error, 0.075)

            delta_E = delta_E_CIE1976(Lab, recovered_Lab)
            self.assertLess(delta_E, 1e-12)
Пример #20
0
    def test_XYZ_to_sd_Otsu2018(self):
        """Test :func:`colour.recovery.otsu2018.XYZ_to_sd_Otsu2018` definition."""

        # Tests the round-trip with values of a colour checker.
        for _name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = XYZ_to_sd_Otsu2018(
                XYZ, self._cmfs, self._sd_D65, clip=False
            )
            recovered_XYZ = (
                sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
            )
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = metric_mse(
                reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values,
                recovered_sd.values,
            )
            self.assertLess(error, 0.02)

            delta_E = delta_E_CIE1976(Lab, recovered_Lab)
            self.assertLess(delta_E, 1e-12)
Пример #21
0
    def test_XYZ_to_sd_Jakob2019(self):
        """
        Tests :func:`colour.recovery.jakob2019.XYZ_to_sd_Jakob2019` definition.
        """

        # Tests the round-trip with values of a colour checker.
        for name, sd in SDS_COLOURCHECKERS['ColorChecker N Ohta'].items():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100

            _recovered_sd, error = XYZ_to_sd_Jakob2019(XYZ,
                                                       self._cmfs,
                                                       self._sd_D65,
                                                       additional_data=True)

            if error > JND_CIE1976 / 100:
                self.fail('Delta E for \'{0}\' is {1}!'.format(name, error))
Пример #22
0
    def setUp(self):
        """
        Initialises common tests attributes.
        """

        self._shape = SPECTRAL_SHAPE_JAKOB2019
        self._cmfs = MSDS_CMFS_STANDARD_OBSERVER[
            'CIE 1931 2 Degree Standard Observer'].copy().align(self._shape)

        self._sd_D65 = SDS_ILLUMINANTS['D65'].copy().align(self._shape)
        self._XYZ_D65 = sd_to_XYZ(self._sd_D65)
        self._XYZ_D65 /= self._XYZ_D65[1]
        self._xy_D65 = CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][
            'D65']

        self._Lab_e = np.array([72, -20, 61])
Пример #23
0
    def test_intermediates(self):
        """
        Tests intermediate results of
        :func:`colour.recovery.jakob2019.error_function` with
        :func:`colour.sd_to_XYZ`, :func:`colour.XYZ_to_Lab` and checks if the
        error is computed correctly by comparing it with
        :func:`colour.difference.delta_E_CIE1976`.
        """

        # Quoted names refer to colours from ColorChecker N Ohta (using D65).
        coefficient_list = [
            np.array([0, 0, 0]),  # 50% gray
            np.array([0, 0, -1e+9]),  # Pure black
            np.array([0, 0, +1e+9]),  # Pure white
            np.array([1e+9, -1e+9, 2.1e+8]),  # A pathological example
            np.array([2.2667394, -7.6313081, 1.03185]),  # 'blue'
            np.array([-31.377077, 26.810094, -6.1139927]),  # 'green'
            np.array([25.064246, -16.072039, 0.10431365]),  # 'red'
            np.array([-19.325667, 22.242319, -5.8144924]),  # 'yellow'
            np.array([21.909902, -17.227963, 2.142351]),  # 'magenta'
            np.array([-15.864009, 8.6735071, -1.4012552]),  # 'cyan'
        ]

        for coefficients in coefficient_list:
            error, _derror, R, XYZ, Lab = error_function(coefficients,
                                                         self._Lab_e,
                                                         self._cmfs,
                                                         self._sd_D65,
                                                         additional_data=True)

            sd = sd_Jakob2019(
                dimensionalise_coefficients(coefficients, self._shape),
                self._shape)

            sd_XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            sd_Lab = XYZ_to_Lab(XYZ, self._xy_D65)
            error_reference = delta_E_CIE1976(self._Lab_e, Lab)

            np.testing.assert_allclose(sd.values, R, atol=1e-14)
            np.testing.assert_allclose(XYZ, sd_XYZ, atol=1e-14)

            self.assertLess(abs(error_reference - error), JND_CIE1976 / 100)
            self.assertLess(delta_E_CIE1976(Lab, sd_Lab), JND_CIE1976 / 100)
Пример #24
0
    def test_LUT3D_Jakob2019(self):
        """
        Tests the entirety of the
        :class:`colour.recovery.jakob2019.LUT3D_Jakob2019`class.
        """

        LUT = LUT3D_Jakob2019()
        LUT.generate(self._RGB_colourspace, self._cmfs, self._sd_D65, 5)

        path = os.path.join(self._temporary_directory, 'Test_Jakob2019.coeff')

        LUT.write(path)
        LUT.read(path)

        for RGB in [
                np.array([1, 0, 0]),
                np.array([0, 1, 0]),
                np.array([0, 0, 1]),
                zeros(3),
                full(3, 0.5),
                ones(3),
        ]:
            XYZ = RGB_to_XYZ(RGB, self._RGB_colourspace.whitepoint,
                             self._xy_D65,
                             self._RGB_colourspace.matrix_RGB_to_XYZ)
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = LUT.RGB_to_sd(RGB)
            recovered_XYZ = sd_to_XYZ(recovered_sd, self._cmfs,
                                      self._sd_D65) / 100
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = delta_E_CIE1976(Lab, recovered_Lab)

            if error > 2 * JND_CIE1976 / 100:
                self.fail(
                    'Delta E for RGB={0} in colourspace {1} is {2}!'.format(
                        RGB, self._RGB_colourspace.name, error))
Пример #25
0
def plot_blackbody_colours(
        shape=SpectralShape(150, 12500, 50),
        cmfs='CIE 1931 2 Degree Standard Observer',
        **kwargs):
    """
    Plots blackbody colours.

    Parameters
    ----------
    shape : SpectralShape, optional
        Spectral shape to use as plot boundaries.
    cmfs : unicode, optional
        Standard observer colour matching functions.

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

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

    Examples
    --------
    >>> plot_blackbody_colours(SpectralShape(150, 12500, 50))  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png
        :align: center
        :alt: plot_blackbody_colours
    """

    _figure, axes = artist(**kwargs)

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

    colours = []
    temperatures = []

    for temperature in shape:
        sd = sd_blackbody(temperature, cmfs.shape)

        with domain_range_scale('1'):
            XYZ = sd_to_XYZ(sd, cmfs)

        RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ))

        colours.append(RGB)
        temperatures.append(temperature)

    x_min, x_max = min(temperatures), max(temperatures)
    y_min, y_max = 0, 1

    padding = 0.1
    axes.bar(
        x=np.array(temperatures) - padding,
        height=1,
        width=shape.interval + (padding * shape.interval),
        color=colours,
        align='edge')

    settings = {
        'axes': axes,
        'bounding_box': (x_min, x_max, y_min, y_max),
        'title': 'Blackbody Colours',
        'x_label': 'Temperature K',
        'y_label': None,
    }
    settings.update(kwargs)

    return render(**settings)
Пример #26
0
def plot_blackbody_spectral_radiance(
        temperature=3500,
        cmfs='CIE 1931 2 Degree Standard Observer',
        blackbody='VY Canis Major',
        **kwargs):
    """
    Plots given blackbody spectral radiance.

    Parameters
    ----------
    temperature : numeric, optional
        Blackbody temperature.
    cmfs : unicode, optional
        Standard observer colour matching functions.
    blackbody : unicode, optional
        Blackbody name.

    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.

    Examples
    --------
    >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major')
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png
        :align: center
        :alt: plot_blackbody_spectral_radiance
    """

    figure = plt.figure()

    figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2)

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

    sd = sd_blackbody(temperature, cmfs.shape)

    axes = figure.add_subplot(211)
    settings = {
        'axes': axes,
        'title': '{0} - Spectral Radiance'.format(blackbody),
        'y_label': 'W / (sr m$^2$) / m',
    }
    settings.update(kwargs)
    settings['standalone'] = False

    plot_single_sd(sd, cmfs.name, **settings)

    axes = figure.add_subplot(212)

    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd, cmfs)

    RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ))

    settings = {
        'axes': axes,
        'aspect': None,
        'title': '{0} - Colour'.format(blackbody),
        'x_label': '{0}K'.format(temperature),
        'y_label': '',
        'x_ticker': False,
        'y_ticker': False,
    }
    settings.update(kwargs)
    settings['standalone'] = False

    figure, axes = plot_single_colour_swatch(
        ColourSwatch(name='', RGB=RGB), **settings)

    settings = {'axes': axes, 'standalone': True}
    settings.update(kwargs)

    return render(**settings)
Пример #27
0
def plot_multi_sds(sds,
                   cmfs='CIE 1931 2 Degree Standard Observer',
                   use_sds_colours=False,
                   normalise_sds_colours=False,
                   **kwargs):
    """
    Plots given spectral distributions.

    Parameters
    ----------
    sds : array_like or MultiSpectralDistribution
        Spectral distributions or multi-spectral distributions to
        plot. `sds` can be a single
        :class:`colour.MultiSpectralDistribution` class instance, a list
        of :class:`colour.MultiSpectralDistribution` class instances or a
        list of :class:`colour.SpectralDistribution` class instances.
    cmfs : unicode, optional
        Standard observer colour matching functions used for spectrum creation.
    use_sds_colours : bool, optional
        Whether to use spectral distributions colours.
    normalise_sds_colours : bool
        Whether to normalise spectral distributions colours.

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

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

    Examples
    --------
    >>> from colour import SpectralDistribution
    >>> data_1 = {
    ...     500: 0.004900,
    ...     510: 0.009300,
    ...     520: 0.063270,
    ...     530: 0.165500,
    ...     540: 0.290400,
    ...     550: 0.433450,
    ...     560: 0.594500
    ... }
    >>> data_2 = {
    ...     500: 0.323000,
    ...     510: 0.503000,
    ...     520: 0.710000,
    ...     530: 0.862000,
    ...     540: 0.954000,
    ...     550: 0.994950,
    ...     560: 0.995000
    ... }
    >>> spd1 = SpectralDistribution(data_1, name='Custom 1')
    >>> spd2 = SpectralDistribution(data_2, name='Custom 2')
    >>> plot_multi_sds([spd1, spd2])  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Multi_SDs.png
        :align: center
        :alt: plot_multi_sds
    """

    _figure, axes = artist(**kwargs)

    if isinstance(sds, MultiSpectralDistribution):
        sds = sds.to_sds()
    else:
        sds = list(sds)
        for i, sd in enumerate(sds[:]):
            if isinstance(sd, MultiSpectralDistribution):
                sds.remove(sd)
                sds[i:i] = sd.to_sds()

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

    illuminant = ILLUMINANTS_SDS[
        COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant]

    x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
    for sd in sds:
        wavelengths, values = sd.wavelengths, sd.values

        shape = sd.shape
        x_limit_min.append(shape.start)
        x_limit_max.append(shape.end)
        y_limit_min.append(min(values))
        y_limit_max.append(max(values))

        if use_sds_colours:
            with domain_range_scale('1'):
                XYZ = sd_to_XYZ(sd, cmfs, illuminant)

            if normalise_sds_colours:
                XYZ = normalise_maximum(XYZ, clip=False)

            RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1)

            axes.plot(wavelengths, values, color=RGB, label=sd.strict_name)
        else:
            axes.plot(wavelengths, values, label=sd.strict_name)

    bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min),
                    max(y_limit_max) + max(y_limit_max) * 0.05)
    settings = {
        'axes': axes,
        'bounding_box': bounding_box,
        'legend': True,
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Spectral Distribution',
    }
    settings.update(kwargs)

    return render(**settings)
Пример #28
0
def colour_rendering_index(sd_test, additional_data=False):
    """
    Returns the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral
    distribution.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.

    Returns
    -------
    numeric or CRI_Specification
        *Colour Rendering Index* (CRI).

    References
    ----------
    :cite:`Ohno2008a`

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> sd = ILLUMINANTS_SDS['FL2']
    >>> colour_rendering_index(sd)  # doctest: +ELLIPSIS
    64.1515202...
    """

    cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy(
    ).trim(DEFAULT_SPECTRAL_SHAPE)

    shape = cmfs.shape
    sd_test = sd_test.copy().align(shape)
    tcs_sds = {sd.name: sd.copy().align(shape) for sd in TCS_SDS.values()}

    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd_test, cmfs)

    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Robertson1968(uv)

    if CCT < 5000:
        sd_reference = sd_blackbody(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_reference = sd_CIE_illuminant_D_series(xy)
        sd_reference.align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test,
                                                     sd_reference,
                                                     tcs_sds,
                                                     cmfs,
                                                     chromatic_adaptation=True)

    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sd_reference, tcs_sds, 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(
            sd_test.name, Q_a, Q_as,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data))
    else:
        return Q_a
Пример #29
0
def tcs_colorimetry_data(sd_t,
                         sd_r,
                         sds_tcs,
                         cmfs,
                         chromatic_adaptation=False):
    """
    Returns the *test colour samples* colorimetry data.

    Parameters
    ----------
    sd_t : SpectralDistribution
        Test spectral distribution.
    sd_r : SpectralDistribution
        Reference spectral distribution.
    sds_tcs : dict
        *Test colour samples* spectral distributions.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    chromatic_adaptation : bool, optional
        Perform chromatic adaptation.

    Returns
    -------
    list
        *Test colour samples* colorimetry data.
    """

    XYZ_t = sd_to_XYZ(sd_t, cmfs)
    uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t))
    u_t, v_t = uv_t[0], uv_t[1]

    XYZ_r = sd_to_XYZ(sd_r, cmfs)
    uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r))
    u_r, v_r = uv_r[0], uv_r[1]

    tcs_data = []
    for _key, value in sorted(TCS_INDEXES_TO_NAMES.items()):
        sd_tcs = sds_tcs[value]
        XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t)
        xyY_tcs = XYZ_to_xyY(XYZ_tcs)
        uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs))
        u_tcs, v_tcs = uv_tcs[0], uv_tcs[1]

        if chromatic_adaptation:

            def c(x, y):
                """
                Computes the :math:`c` term.
                """

                return (4 - x - 10 * y) / y

            def d(x, y):
                """
                Computes the :math:`d` term.
                """

                return (1.708 * y + 0.404 - 1.481 * x) / y

            c_t, d_t = c(u_t, v_t), d(u_t, v_t)
            c_r, d_r = c(u_r, v_r), d(u_r, v_r)
            tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs)
            u_tcs = (
                (10.872 + 0.404 * c_r / c_t * tcs_c - 4 * d_r / d_t * tcs_d) /
                (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d))
            v_tcs = (5.52 /
                     (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d))

        W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17
        U_tcs = 13 * W_tcs * (u_tcs - u_r)
        V_tcs = 13 * W_tcs * (v_tcs - v_r)

        tcs_data.append(
            TCS_ColorimetryData(sd_tcs.name, XYZ_tcs, uv_tcs,
                                np.array([U_tcs, V_tcs, W_tcs])))

    return tcs_data
Пример #30
0
def generate_documentation_plots(output_directory):
    """
    Generates documentation plots.

    Parameters
    ----------
    output_directory : unicode
        Output directory.
    """

    filter_warnings()

    colour_style()

    np.random.seed(0)

    # *************************************************************************
    # "README.rst"
    # *************************************************************************
    filename = os.path.join(output_directory,
                            'Examples_Colour_Automatic_Conversion_Graph.png')
    plot_automatic_colour_conversion_graph(filename)

    arguments = {
        'tight_layout':
        True,
        'transparent_background':
        True,
        'filename':
        os.path.join(output_directory,
                     'Examples_Plotting_Visible_Spectrum.png')
    }
    plt.close(
        plot_visible_spectrum('CIE 1931 2 Degree Standard Observer',
                              **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_Illuminant_F1_SD.png')
    plt.close(plot_single_illuminant_sd('FL1', **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Examples_Plotting_Blackbodies.png')
    blackbody_sds = [
        sd_blackbody(i, SpectralShape(0, 10000, 10))
        for i in range(1000, 15000, 1000)
    ]
    plt.close(
        plot_multi_sds(blackbody_sds,
                       y_label='W / (sr m$^2$) / m',
                       use_sds_colours=True,
                       normalise_sds_colours=True,
                       legend_location='upper right',
                       bounding_box=(0, 1250, 0, 2.5e15),
                       **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_Cone_Fundamentals.png')
    plt.close(
        plot_single_cmfs('Stockman & Sharpe 2 Degree Cone Fundamentals',
                         y_label='Sensitivity',
                         bounding_box=(390, 870, 0, 1.1),
                         **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_Luminous_Efficiency.png')
    plt.close(
        plot_multi_sds((sd_mesopic_luminous_efficiency_function(0.2),
                        PHOTOPIC_LEFS['CIE 1924 Photopic Standard Observer'],
                        SCOTOPIC_LEFS['CIE 1951 Scotopic Standard Observer']),
                       y_label='Luminous Efficiency',
                       legend_location='upper right',
                       y_tighten=True,
                       margins=(0, 0, 0, .1),
                       **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_BabelColor_Average.png')
    plt.close(
        plot_multi_sds(COLOURCHECKERS_SDS['BabelColor Average'].values(),
                       use_sds_colours=True,
                       title=('BabelColor Average - '
                              'Spectral Distributions'),
                       **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_ColorChecker_2005.png')
    plt.close(
        plot_single_colour_checker('ColorChecker 2005',
                                   text_parameters={'visible': False},
                                   **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Examples_Plotting_Chromaticities_Prediction.png')
    plt.close(
        plot_corresponding_chromaticities_prediction(2, 'Von Kries', 'Bianco',
                                                     **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Examples_Plotting_CCT_CIE_1960_UCS_Chromaticity_Diagram.png')
    plt.close(
        plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS(
            ['A', 'B', 'C'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Examples_Plotting_Chromaticities_CIE_1931_Chromaticity_Diagram.png')
    RGB = np.random.random((32, 32, 3))
    plt.close(
        plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
            RGB,
            'ITU-R BT.709',
            colourspaces=['ACEScg', 'S-Gamut'],
            show_pointer_gamut=True,
            **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Examples_Plotting_CRI.png')
    plt.close(
        plot_single_sd_colour_rendering_index_bars(ILLUMINANTS_SDS['FL2'],
                                                   **arguments)[0])

    # *************************************************************************
    # Documentation
    # *************************************************************************
    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_CVD_Simulation_Machado2009.png')
    plt.close(plot_cvd_simulation_Machado2009(RGB, **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Colour_Checker.png')
    plt.close(plot_single_colour_checker('ColorChecker 2005', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Colour_Checkers.png')
    plt.close(
        plot_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005'],
                                   **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Single_SD.png')
    data = {
        500: 0.0651,
        520: 0.0705,
        540: 0.0772,
        560: 0.0870,
        580: 0.1128,
        600: 0.1360
    }
    sd = SpectralDistribution(data, name='Custom')
    plt.close(plot_single_sd(sd, **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Multi_SDS.png')
    data_1 = {
        500: 0.004900,
        510: 0.009300,
        520: 0.063270,
        530: 0.165500,
        540: 0.290400,
        550: 0.433450,
        560: 0.594500
    }
    data_2 = {
        500: 0.323000,
        510: 0.503000,
        520: 0.710000,
        530: 0.862000,
        540: 0.954000,
        550: 0.994950,
        560: 0.995000
    }
    spd1 = SpectralDistribution(data_1, name='Custom 1')
    spd2 = SpectralDistribution(data_2, name='Custom 2')
    plt.close(plot_multi_sds([spd1, spd2], **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Single_CMFS.png')
    plt.close(
        plot_single_cmfs('CIE 1931 2 Degree Standard Observer',
                         **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Multi_CMFS.png')
    cmfs = ('CIE 1931 2 Degree Standard Observer',
            'CIE 1964 10 Degree Standard Observer')
    plt.close(plot_multi_cmfs(cmfs, **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Illuminant_SD.png')
    plt.close(plot_single_illuminant_sd('A', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Illuminant_SDS.png')
    plt.close(plot_multi_illuminant_sds(['A', 'B', 'C'], **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Visible_Spectrum.png')
    plt.close(plot_visible_spectrum(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Lightness_Function.png')
    plt.close(plot_single_lightness_function('CIE 1976', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Lightness_Functions.png')
    plt.close(
        plot_multi_lightness_functions(['CIE 1976', 'Wyszecki 1963'],
                                       **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Luminance_Function.png')
    plt.close(plot_single_luminance_function('CIE 1976', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Luminance_Functions.png')
    plt.close(
        plot_multi_luminance_functions(['CIE 1976', 'Newhall 1943'],
                                       **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Blackbody_Spectral_Radiance.png')
    plt.close(
        plot_blackbody_spectral_radiance(3500,
                                         blackbody='VY Canis Major',
                                         **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Blackbody_Colours.png')
    plt.close(
        plot_blackbody_colours(SpectralShape(150, 12500, 50), **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Colour_Swatch.png')
    RGB = ColourSwatch(RGB=(0.45620519, 0.03081071, 0.04091952))
    plt.close(plot_single_colour_swatch(RGB, **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Colour_Swatches.png')
    RGB_1 = ColourSwatch(RGB=(0.45293517, 0.31732158, 0.26414773))
    RGB_2 = ColourSwatch(RGB=(0.77875824, 0.57726450, 0.50453169))
    plt.close(plot_multi_colour_swatches([RGB_1, RGB_2], **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Single_Function.png')
    plt.close(plot_single_function(lambda x: x**(1 / 2.2), **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Multi_Functions.png')
    functions = {
        'Gamma 2.2': lambda x: x**(1 / 2.2),
        'Gamma 2.4': lambda x: x**(1 / 2.4),
        'Gamma 2.6': lambda x: x**(1 / 2.6),
    }
    plt.close(plot_multi_functions(functions, **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Image.png')
    path = os.path.join(output_directory, 'Logo_Medium_001.png')
    plt.close(plot_image(read_image(str(path)), **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Corresponding_Chromaticities_Prediction.png')
    plt.close(
        plot_corresponding_chromaticities_prediction(1, 'Von Kries', 'CAT02',
                                                     **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Spectral_Locus.png')
    plt.close(
        plot_spectral_locus(spectral_locus_colours='RGB', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Chromaticity_Diagram_Colours.png')
    plt.close(plot_chromaticity_diagram_colours(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Chromaticity_Diagram.png')
    plt.close(plot_chromaticity_diagram(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1931.png')
    plt.close(plot_chromaticity_diagram_CIE1931(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(plot_chromaticity_diagram_CIE1960UCS(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1976UCS.png')
    plt.close(plot_chromaticity_diagram_CIE1976UCS(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_SDS_In_Chromaticity_Diagram.png')
    A = ILLUMINANTS_SDS['A']
    D65 = ILLUMINANTS_SDS['D65']
    plt.close(plot_sds_in_chromaticity_diagram([A, D65], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1931.png')
    plt.close(
        plot_sds_in_chromaticity_diagram_CIE1931([A, D65], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(
        plot_sds_in_chromaticity_diagram_CIE1960UCS([A, D65], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1976UCS.png')
    plt.close(
        plot_sds_in_chromaticity_diagram_CIE1976UCS([A, D65], **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Pointer_Gamut.png')
    plt.close(plot_pointer_gamut(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png')
    plt.close(
        plot_RGB_colourspaces_in_chromaticity_diagram(
            ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png')
    plt.close(
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931(
            ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Colourspaces_In_'
        'Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS(
            ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Colourspaces_In_'
        'Chromaticity_Diagram_CIE1976UCS.png')
    plt.close(
        plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS(
            ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Chromaticities_In_'
        'Chromaticity_Diagram.png')
    RGB = np.random.random((128, 128, 3))
    plt.close(
        plot_RGB_chromaticities_in_chromaticity_diagram(
            RGB, 'ITU-R BT.709', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Chromaticities_In_'
        'Chromaticity_Diagram_CIE1931.png')
    plt.close(
        plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931(
            RGB, 'ITU-R BT.709', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Chromaticities_In_'
        'Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(
        plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS(
            RGB, 'ITU-R BT.709', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Chromaticities_In_'
        'Chromaticity_Diagram_CIE1976UCS.png')
    plt.close(
        plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS(
            RGB, 'ITU-R BT.709', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png')
    plt.close(
        plot_ellipses_MacAdam1942_in_chromaticity_diagram(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_'
        'Chromaticity_Diagram_CIE1931.png')
    plt.close(
        plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931(
            **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_'
        'Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(
        plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS(
            **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_'
        'Chromaticity_Diagram_CIE1976UCS.png')
    plt.close(
        plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS(
            **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Single_CCTF.png')
    plt.close(plot_single_cctf('ITU-R BT.709', **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Multi_CCTFs.png')
    plt.close(plot_multi_cctfs(['ITU-R BT.709', 'sRGB'], **arguments)[0])

    data = np.array([
        [
            None,
            np.array([0.95010000, 1.00000000, 1.08810000]),
            np.array([0.40920000, 0.28120000, 0.30600000]),
            np.array([
                [0.02495100, 0.01908600, 0.02032900],
                [0.10944300, 0.06235900, 0.06788100],
                [0.27186500, 0.18418700, 0.19565300],
                [0.48898900, 0.40749400, 0.44854600],
            ]),
            None,
        ],
        [
            None,
            np.array([0.95010000, 1.00000000, 1.08810000]),
            np.array([0.30760000, 0.48280000, 0.42770000]),
            np.array([
                [0.02108000, 0.02989100, 0.02790400],
                [0.06194700, 0.11251000, 0.09334400],
                [0.15255800, 0.28123300, 0.23234900],
                [0.34157700, 0.56681300, 0.47035300],
            ]),
            None,
        ],
        [
            None,
            np.array([0.95010000, 1.00000000, 1.08810000]),
            np.array([0.39530000, 0.28120000, 0.18450000]),
            np.array([
                [0.02436400, 0.01908600, 0.01468800],
                [0.10331200, 0.06235900, 0.02854600],
                [0.26311900, 0.18418700, 0.12109700],
                [0.43158700, 0.40749400, 0.39008600],
            ]),
            None,
        ],
        [
            None,
            np.array([0.95010000, 1.00000000, 1.08810000]),
            np.array([0.20510000, 0.18420000, 0.57130000]),
            np.array([
                [0.03039800, 0.02989100, 0.06123300],
                [0.08870000, 0.08498400, 0.21843500],
                [0.18405800, 0.18418700, 0.40111400],
                [0.32550100, 0.34047200, 0.50296900],
                [0.53826100, 0.56681300, 0.80010400],
            ]),
            None,
        ],
        [
            None,
            np.array([0.95010000, 1.00000000, 1.08810000]),
            np.array([0.35770000, 0.28120000, 0.11250000]),
            np.array([
                [0.03678100, 0.02989100, 0.01481100],
                [0.17127700, 0.11251000, 0.01229900],
                [0.30080900, 0.28123300, 0.21229800],
                [0.52976000, 0.40749400, 0.11720000],
            ]),
            None,
        ],
    ])
    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Constant_Hue_Loci.png')
    plt.close(plot_constant_hue_loci(data, 'IPT', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_Munsell_Value_Function.png')
    plt.close(plot_single_munsell_value_function('ASTM D1535', **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Multi_Munsell_Value_Functions.png')
    plt.close(
        plot_multi_munsell_value_functions(['ASTM D1535', 'McCamy 1987'],
                                           **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Single_SD_Rayleigh_Scattering.png')
    plt.close(plot_single_sd_rayleigh_scattering(**arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_The_Blue_Sky.png')
    plt.close(plot_the_blue_sky(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Colour_Quality_Bars.png')
    illuminant = ILLUMINANTS_SDS['FL2']
    light_source = LIGHT_SOURCES_SDS['Kinoton 75P']
    light_source = light_source.copy().align(SpectralShape(360, 830, 1))
    cqs_i = colour_quality_scale(illuminant, additional_data=True)
    cqs_l = colour_quality_scale(light_source, additional_data=True)
    plt.close(plot_colour_quality_bars([cqs_i, cqs_l], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Single_SD_Colour_Rendering_Index_Bars.png')
    illuminant = ILLUMINANTS_SDS['FL2']
    plt.close(
        plot_single_sd_colour_rendering_index_bars(illuminant, **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Multi_SDS_Colour_Rendering_Indexes_Bars.png')
    light_source = LIGHT_SOURCES_SDS['Kinoton 75P']
    plt.close(
        plot_multi_sds_colour_rendering_indexes_bars(
            [illuminant, light_source], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Single_SD_Colour_Quality_Scale_Bars.png')
    illuminant = ILLUMINANTS_SDS['FL2']
    plt.close(
        plot_single_sd_colour_quality_scale_bars(illuminant, **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Multi_SDS_Colour_Quality_Scales_Bars.png')
    light_source = LIGHT_SOURCES_SDS['Kinoton 75P']
    plt.close(
        plot_multi_sds_colour_quality_scales_bars([illuminant, light_source],
                                                  **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_Planckian_Locus.png')
    plt.close(plot_planckian_locus(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Planckian_Locus_CIE1931.png')
    plt.close(plot_planckian_locus_CIE1931(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_Planckian_Locus_CIE1960UCS.png')
    plt.close(plot_planckian_locus_CIE1960UCS(**arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram.png')
    plt.close(
        plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C'],
                                                     **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1931.png')
    plt.close(
        plot_planckian_locus_in_chromaticity_diagram_CIE1931(['A', 'B', 'C'],
                                                             **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory,
        'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1960UCS.png')
    plt.close(
        plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS(
            ['A', 'B', 'C'], **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Colourspaces_Gamuts.png')
    plt.close(
        plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'],
                                     **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Plotting_Plot_RGB_Colourspaces_Gamuts.png')
    plt.close(
        plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'],
                                     **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Plotting_Plot_RGB_Scatter.png')
    plt.close(plot_RGB_scatter(RGB, 'ITU-R BT.709', **arguments)[0])

    filename = os.path.join(
        output_directory,
        'Plotting_Plot_Colour_Automatic_Conversion_Graph.png')
    plot_automatic_colour_conversion_graph(filename)

    # *************************************************************************
    # "tutorial.rst"
    # *************************************************************************
    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_Visible_Spectrum.png')
    plt.close(plot_visible_spectrum(**arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_Sample_SD.png')
    sample_sd_data = {
        380: 0.048,
        385: 0.051,
        390: 0.055,
        395: 0.060,
        400: 0.065,
        405: 0.068,
        410: 0.068,
        415: 0.067,
        420: 0.064,
        425: 0.062,
        430: 0.059,
        435: 0.057,
        440: 0.055,
        445: 0.054,
        450: 0.053,
        455: 0.053,
        460: 0.052,
        465: 0.052,
        470: 0.052,
        475: 0.053,
        480: 0.054,
        485: 0.055,
        490: 0.057,
        495: 0.059,
        500: 0.061,
        505: 0.062,
        510: 0.065,
        515: 0.067,
        520: 0.070,
        525: 0.072,
        530: 0.074,
        535: 0.075,
        540: 0.076,
        545: 0.078,
        550: 0.079,
        555: 0.082,
        560: 0.087,
        565: 0.092,
        570: 0.100,
        575: 0.107,
        580: 0.115,
        585: 0.122,
        590: 0.129,
        595: 0.134,
        600: 0.138,
        605: 0.142,
        610: 0.146,
        615: 0.150,
        620: 0.154,
        625: 0.158,
        630: 0.163,
        635: 0.167,
        640: 0.173,
        645: 0.180,
        650: 0.188,
        655: 0.196,
        660: 0.204,
        665: 0.213,
        670: 0.222,
        675: 0.231,
        680: 0.242,
        685: 0.251,
        690: 0.261,
        695: 0.271,
        700: 0.282,
        705: 0.294,
        710: 0.305,
        715: 0.318,
        720: 0.334,
        725: 0.354,
        730: 0.372,
        735: 0.392,
        740: 0.409,
        745: 0.420,
        750: 0.436,
        755: 0.450,
        760: 0.462,
        765: 0.465,
        770: 0.448,
        775: 0.432,
        780: 0.421
    }

    sd = SpectralDistribution(sample_sd_data, name='Sample')
    plt.close(plot_single_sd(sd, **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_SD_Interpolation.png')
    sd_copy = sd.copy()
    sd_copy.interpolate(SpectralShape(400, 770, 1))
    plt.close(
        plot_multi_sds([sd, sd_copy],
                       bounding_box=[730, 780, 0.25, 0.5],
                       **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_Sample_Swatch.png')
    sd = SpectralDistribution(sample_sd_data)
    cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
    illuminant = ILLUMINANTS_SDS['D65']
    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd, cmfs, illuminant)
        RGB = XYZ_to_sRGB(XYZ)
    plt.close(
        plot_single_colour_swatch(ColourSwatch('Sample', RGB),
                                  text_parameters={'size': 'x-large'},
                                  **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_Neutral5.png')
    patch_name = 'neutral 5 (.70 D)'
    patch_sd = COLOURCHECKERS_SDS['ColorChecker N Ohta'][patch_name]
    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(patch_sd, cmfs, illuminant)
        RGB = XYZ_to_sRGB(XYZ)
    plt.close(
        plot_single_colour_swatch(ColourSwatch(patch_name.title(), RGB),
                                  text_parameters={'size': 'x-large'},
                                  **arguments)[0])

    arguments['filename'] = os.path.join(output_directory,
                                         'Tutorial_Colour_Checker.png')
    plt.close(
        plot_single_colour_checker(colour_checker='ColorChecker 2005',
                                   text_parameters={'visible': False},
                                   **arguments)[0])

    arguments['filename'] = os.path.join(
        output_directory, 'Tutorial_CIE_1931_Chromaticity_Diagram.png')
    xy = XYZ_to_xy(XYZ)
    plot_chromaticity_diagram_CIE1931(standalone=False)
    x, y = xy
    plt.plot(x, y, 'o-', color='white')
    # Annotating the plot.
    plt.annotate(patch_sd.name.title(),
                 xy=xy,
                 xytext=(-50, 30),
                 textcoords='offset points',
                 arrowprops=dict(arrowstyle='->',
                                 connectionstyle='arc3, rad=-0.2'))
    plt.close(
        render(standalone=True,
               limits=(-0.1, 0.9, -0.1, 0.9),
               x_tighten=True,
               y_tighten=True,
               **arguments)[0])

    # *************************************************************************
    # "basics.rst"
    # *************************************************************************
    arguments['filename'] = os.path.join(output_directory,
                                         'Basics_Logo_Small_001_CIE_XYZ.png')
    RGB = read_image(os.path.join(output_directory, 'Logo_Small_001.png'))[...,
                                                                           0:3]
    XYZ = sRGB_to_XYZ(RGB)
    plt.close(
        plot_image(XYZ, text_parameters={'text': 'sRGB to XYZ'},
                   **arguments)[0])
Пример #31
0
def plot_sds_in_chromaticity_diagram(
        sds,
        cmfs='CIE 1931 2 Degree Standard Observer',
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        annotate_kwargs=None,
        plot_kwargs=None,
        **kwargs):
    """
    Plots given spectral distribution chromaticity coordinates into the
    *Chromaticity Diagram* using given method.

    Parameters
    ----------
    sds : array_like or MultiSpectralDistributions
        Spectral distributions or multi-spectral distributions to
        plot. `sds` can be a single
        :class:`colour.MultiSpectralDistributions` class instance, a list
        of :class:`colour.MultiSpectralDistributions` class instances or a
        list of :class:`colour.SpectralDistribution` class instances.
    cmfs : unicode or XYZ_ColourMatchingFunctions, optional
        Standard observer colour matching functions used for computing the
        spectral locus boundaries. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    chromaticity_diagram_callable : callable, optional
        Callable responsible for drawing the *Chromaticity Diagram*.
    method : unicode, optional
        **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**,
        *Chromaticity Diagram* method.
    annotate_kwargs : dict or array_like, optional
        Keyword arguments for the :func:`plt.annotate` definition, used to
        annotate the resulting chromaticity coordinates with their respective
        spectral distribution names. ``annotate_kwargs`` can be either a single
        dictionary applied to all the arrows with same settings or a sequence
        of dictionaries with different settings for each spectral distribution.
        The following special keyword arguments can also be used:

        -   *annotate* : bool, whether to annotate the spectral distributions.
    plot_kwargs : dict or array_like, optional
        Keyword arguments for the :func:`plt.plot` definition, used to control
        the style of the plotted spectral distributions. ``plot_kwargs`` can be
        either a single dictionary applied to all the plotted spectral
        distributions with same settings or a sequence of dictionaries with
        different settings for each plotted spectral distributions.
        The following special keyword arguments can also be used:

        -   *illuminant* : unicode or :class:`colour.SpectralDistribution`, the
            illuminant used to compute the spectral distributions colours. The
            default is the illuminant associated with the whitepoint of the
            default plotting colourspace. ``illuminant`` can be of any type or
            form supported by the :func:`colour.plotting.filter_cmfs`
            definition.
        -   *cmfs* : unicode, the standard observer colour matching functions
            used for computing the spectral distributions colours. ``cmfs`` can
            be of any type or form supported by the
            :func:`colour.plotting.filter_cmfs` definition.
        -   *normalise_sd_colours* : bool, whether to normalise the computed
            spectral distributions colours. The default is *True*.
        -   *use_sd_colours* : bool, whether to use the computed spectral
            distributions colours under the plotting colourspace illuminant.
            Alternatively, it is possible to use the :func:`plt.plot`
            definition ``color`` argument with pre-computed values. The default
            is *True*.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.
        Also handles keywords arguments for deprecation management.

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

    Examples
    --------
    >>> A = SDS_ILLUMINANTS['A']
    >>> D65 = SDS_ILLUMINANTS['D65']
    >>> annotate_kwargs = [
    ...     {'xytext': (-25, 15), 'arrowprops':{'arrowstyle':'-'}},
    ...     {}
    ... ]
    >>> plot_kwargs = [
    ...     {
    ...         'illuminant': SDS_ILLUMINANTS['E'],
    ...         'markersize' : 15,
    ...         'normalise_sd_colours': True,
    ...         'use_sd_colours': True
    ...     },
    ...     {'illuminant': SDS_ILLUMINANTS['E']},
    ... ]
    >>> plot_sds_in_chromaticity_diagram(
    ...     [A, D65], annotate_kwargs=annotate_kwargs, plot_kwargs=plot_kwargs)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_SDS_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_sds_in_chromaticity_diagram
    """

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

    sds = sds_and_msds_to_sds(sds)

    settings = {'uniform': True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    method = method.upper()

    settings.update({
        'axes': axes,
        'standalone': False,
        'method': method,
        'cmfs': cmfs,
    })

    chromaticity_diagram_callable(**settings)

    if method == 'CIE 1931':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return XYZ_to_xy(XYZ)

        bounding_box = (-0.1, 0.9, -0.1, 0.9)
    elif method == 'CIE 1960 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return UCS_to_uv(XYZ_to_UCS(XYZ))

        bounding_box = (-0.1, 0.7, -0.2, 0.6)

    elif method == 'CIE 1976 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return Luv_to_uv(XYZ_to_Luv(XYZ))

        bounding_box = (-0.1, 0.7, -0.1, 0.7)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '[\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\']'.format(
                method))

    annotate_settings_collection = [{
        'annotate': True,
        'xytext': (-50, 30),
        'textcoords': 'offset points',
        'arrowprops': CONSTANTS_ARROW_STYLE,
    } for _ in range(len(sds))]

    if annotate_kwargs is not None:
        update_settings_collection(annotate_settings_collection,
                                   annotate_kwargs, len(sds))

    plot_settings_collection = [{
        'color':
        CONSTANTS_COLOUR_STYLE.colour.brightest,
        'label':
        '{0}'.format(sd.strict_name),
        'marker':
        'o',
        'markeredgecolor':
        CONSTANTS_COLOUR_STYLE.colour.dark,
        'markeredgewidth':
        CONSTANTS_COLOUR_STYLE.geometry.short * 0.75,
        'markersize': (CONSTANTS_COLOUR_STYLE.geometry.short * 6 +
                       CONSTANTS_COLOUR_STYLE.geometry.short * 0.75),
        'cmfs':
        cmfs,
        'illuminant':
        SDS_ILLUMINANTS[
            CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint_name],
        'use_sd_colours':
        False,
        'normalise_sd_colours':
        False,
    } for sd in sds]

    if plot_kwargs is not None:
        update_settings_collection(plot_settings_collection, plot_kwargs,
                                   len(sds))

    for i, sd in enumerate(sds):
        plot_settings = plot_settings_collection[i]

        cmfs = first_item(filter_cmfs(plot_settings.pop('cmfs')).values())
        illuminant = first_item(
            filter_illuminants(plot_settings.pop('illuminant')).values())
        normalise_sd_colours = plot_settings.pop('normalise_sd_colours')
        use_sd_colours = plot_settings.pop('use_sd_colours')

        with domain_range_scale('1'):
            XYZ = sd_to_XYZ(sd, cmfs, illuminant)

        if use_sd_colours:
            if normalise_sd_colours:
                XYZ /= XYZ[..., 1]

            plot_settings['color'] = np.clip(XYZ_to_plotting_colourspace(XYZ),
                                             0, 1)

        ij = XYZ_to_ij(XYZ)

        axes.plot(ij[0], ij[1], **plot_settings)

        if (sd.name is not None
                and annotate_settings_collection[i]['annotate']):
            annotate_settings = annotate_settings_collection[i]
            annotate_settings.pop('annotate')

            axes.annotate(sd.name, xy=ij, **annotate_settings)

    settings.update({'standalone': True, 'bounding_box': bounding_box})
    settings.update(kwargs)

    return render(**settings)
Пример #32
0
def plot_the_blue_sky(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs):
    """
    Plots the blue sky.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.plot_single_sd`,
        :func:`colour.plotting.plot_multi_colour_swatches`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_The_Blue_Sky.png
        :align: center
        :alt: plot_the_blue_sky
    """

    figure = plt.figure()

    figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2)

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

    ASTM_G_173_sd = ASTM_G_173_ETR.copy()
    rayleigh_sd = sd_rayleigh_scattering()
    ASTM_G_173_sd.align(rayleigh_sd.shape)

    sd = rayleigh_sd * ASTM_G_173_sd

    axes = figure.add_subplot(211)

    settings = {
        'axes': axes,
        'title': 'The Blue Sky - Synthetic Spectral Distribution',
        'y_label': u'W / m-2 / nm-1',
    }
    settings.update(kwargs)
    settings['standalone'] = False

    plot_single_sd(sd, cmfs, **settings)

    axes = figure.add_subplot(212)

    x_label = ('The sky is blue because molecules in the atmosphere '
               'scatter shorter wavelengths more than longer ones.\n'
               'The synthetic spectral distribution is computed as '
               'follows: '
               '(ASTM G-173 ETR * Standard Air Rayleigh Scattering).')

    settings = {
        'axes': axes,
        'aspect': None,
        'title': 'The Blue Sky - Colour',
        'x_label': x_label,
        'y_label': '',
        'x_ticker': False,
        'y_ticker': False,
    }
    settings.update(kwargs)
    settings['standalone'] = False

    blue_sky_color = XYZ_to_plotting_colourspace(sd_to_XYZ(sd))

    figure, axes = plot_single_colour_swatch(
        ColourSwatch('', normalise_maximum(blue_sky_color)), **settings)

    settings = {'axes': axes, 'standalone': True}
    settings.update(kwargs)

    return render(**settings)
Пример #33
0
def plot_blackbody_colours(shape=SpectralShape(150, 12500, 50),
                           cmfs='CIE 1931 2 Degree Standard Observer',
                           **kwargs):
    """
    Plots blackbody colours.

    Parameters
    ----------
    shape : SpectralShape, optional
        Spectral shape to use as plot boundaries.
    cmfs : unicode, optional
        Standard observer colour matching functions.

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

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

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

    .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png
        :align: center
        :alt: plot_blackbody_colours
    """

    _figure, axes = artist(**kwargs)

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

    colours = []
    temperatures = []

    for temperature in shape:
        sd = sd_blackbody(temperature, cmfs.shape)

        with domain_range_scale('1'):
            XYZ = sd_to_XYZ(sd, cmfs)

        RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ))

        colours.append(RGB)
        temperatures.append(temperature)

    x_min, x_max = min(temperatures), max(temperatures)
    y_min, y_max = 0, 1

    padding = 0.1
    axes.bar(x=np.array(temperatures) - padding,
             height=1,
             width=shape.interval + (padding * shape.interval),
             color=colours,
             align='edge')

    settings = {
        'axes': axes,
        'bounding_box': (x_min, x_max, y_min, y_max),
        'title': 'Blackbody Colours',
        'x_label': 'Temperature K',
        'y_label': None,
    }
    settings.update(kwargs)

    return render(**settings)
Пример #34
0
def plot_multi_sds(sds,
                   cmfs='CIE 1931 2 Degree Standard Observer',
                   use_sds_colours=False,
                   normalise_sds_colours=False,
                   **kwargs):
    """
    Plots given spectral distributions.

    Parameters
    ----------
    sds : array_like or MultiSpectralDistributions
        Spectral distributions or multi-spectral distributions to
        plot. `sds` can be a single
        :class:`colour.MultiSpectralDistributions` class instance, a list
        of :class:`colour.MultiSpectralDistributions` class instances or a
        list of :class:`colour.SpectralDistribution` class instances.
    cmfs : unicode, optional
        Standard observer colour matching functions used for spectrum creation.
    use_sds_colours : bool, optional
        Whether to use spectral distributions colours.
    normalise_sds_colours : bool
        Whether to normalise spectral distributions colours.

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

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

    Examples
    --------
    >>> from colour import SpectralDistribution
    >>> data_1 = {
    ...     500: 0.004900,
    ...     510: 0.009300,
    ...     520: 0.063270,
    ...     530: 0.165500,
    ...     540: 0.290400,
    ...     550: 0.433450,
    ...     560: 0.594500
    ... }
    >>> data_2 = {
    ...     500: 0.323000,
    ...     510: 0.503000,
    ...     520: 0.710000,
    ...     530: 0.862000,
    ...     540: 0.954000,
    ...     550: 0.994950,
    ...     560: 0.995000
    ... }
    >>> sd_1 = SpectralDistribution(data_1, name='Custom 1')
    >>> sd_2 = SpectralDistribution(data_2, name='Custom 2')
    >>> plot_multi_sds([sd_1, sd_2])  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Multi_SDS.png
        :align: center
        :alt: plot_multi_sds
    """

    _figure, axes = artist(**kwargs)

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

    illuminant = ILLUMINANTS_SDS[
        COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant]

    x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
    for sd in sds:
        wavelengths, values = sd.wavelengths, sd.values

        shape = sd.shape
        x_limit_min.append(shape.start)
        x_limit_max.append(shape.end)
        y_limit_min.append(min(values))
        y_limit_max.append(max(values))

        if use_sds_colours:
            with domain_range_scale('1'):
                XYZ = sd_to_XYZ(sd, cmfs, illuminant)

            if normalise_sds_colours:
                XYZ = normalise_maximum(XYZ, clip=False)

            RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1)

            axes.plot(wavelengths, values, color=RGB, label=sd.strict_name)
        else:
            axes.plot(wavelengths, values, label=sd.strict_name)

    bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min),
                    max(y_limit_max) + max(y_limit_max) * 0.05)
    settings = {
        'axes': axes,
        'bounding_box': bounding_box,
        'legend': True,
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Spectral Distribution',
    }
    settings.update(kwargs)

    return render(**settings)
Пример #35
0
def tcs_colorimetry_data(sd_t, sd_r, sds_tcs, cmfs,
                         chromatic_adaptation=False):
    """
    Returns the *test colour samples* colorimetry data.

    Parameters
    ----------
    sd_t : SpectralDistribution
        Test spectral distribution.
    sd_r : SpectralDistribution
        Reference spectral distribution.
    sds_tcs : dict
        *Test colour samples* spectral distributions.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    chromatic_adaptation : bool, optional
        Perform chromatic adaptation.

    Returns
    -------
    list
        *Test colour samples* colorimetry data.
    """

    XYZ_t = sd_to_XYZ(sd_t, cmfs)
    uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t))
    u_t, v_t = uv_t[0], uv_t[1]

    XYZ_r = sd_to_XYZ(sd_r, cmfs)
    uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r))
    u_r, v_r = uv_r[0], uv_r[1]

    tcs_data = []
    for _key, value in sorted(TCS_INDEXES_TO_NAMES.items()):
        sd_tcs = sds_tcs[value]
        XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t)
        xyY_tcs = XYZ_to_xyY(XYZ_tcs)
        uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs))
        u_tcs, v_tcs = uv_tcs[0], uv_tcs[1]

        if chromatic_adaptation:

            def c(x, y):
                """
                Computes the :math:`c` term.
                """

                return (4 - x - 10 * y) / y

            def d(x, y):
                """
                Computes the :math:`d` term.
                """

                return (1.708 * y + 0.404 - 1.481 * x) / y

            c_t, d_t = c(u_t, v_t), d(u_t, v_t)
            c_r, d_r = c(u_r, v_r), d(u_r, v_r)
            tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs)
            u_tcs = (
                (10.872 + 0.404 * c_r / c_t * tcs_c - 4 * d_r / d_t * tcs_d) /
                (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d))
            v_tcs = (5.52 /
                     (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d))

        W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17
        U_tcs = 13 * W_tcs * (u_tcs - u_r)
        V_tcs = 13 * W_tcs * (v_tcs - v_r)

        tcs_data.append(
            TCS_ColorimetryData(sd_tcs.name, XYZ_tcs, uv_tcs,
                                np.array([U_tcs, V_tcs, W_tcs])))

    return tcs_data
Пример #36
0
def colour_rendering_index(sd_test, additional_data=False):
    """
    Returns the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral
    distribution.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.

    Returns
    -------
    numeric or CRI_Specification
        *Colour Rendering Index* (CRI).

    References
    ----------
    :cite:`Ohno2008a`

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> sd = ILLUMINANTS_SDS['FL2']
    >>> colour_rendering_index(sd)  # doctest: +ELLIPSIS
    64.1515202...
    """

    cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy(
    ).trim(ASTME30815_PRACTISE_SHAPE)

    shape = cmfs.shape
    sd_test = sd_test.copy().align(shape)
    tcs_sds = {sd.name: sd.copy().align(shape) for sd in TCS_SDS.values()}

    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd_test, cmfs)

    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Robertson1968(uv)

    if CCT < 5000:
        sd_reference = sd_blackbody(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_reference = sd_CIE_illuminant_D_series(xy)
        sd_reference.align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True)

    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sd_reference, tcs_sds, 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(
            sd_test.name, Q_a, Q_as,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data))
    else:
        return Q_a
Пример #37
0
def colour_quality_scale(sd_test, additional_data=False,
                         method='NIST CQS 9.0'):
    """
    Returns the *Colour Quality Scale* (CQS) of given spectral distribution
    using given method.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.
    method : unicode, optional
        **{'NIST CQS 9.0', 'NIST CQS 7.4'}**,
        Computation method.

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

    References
    ----------
    :cite:`Davis2010a`, :cite:`Ohno2008a`, :cite:`Ohno2013`

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> sd = ILLUMINANTS_SDS['FL2']
    >>> colour_quality_scale(sd)  # doctest: +ELLIPSIS
    64.0172835...
    """

    method = method.lower()
    assert method.lower() in [
        m.lower() for m in COLOUR_QUALITY_SCALE_METHODS
    ], ('"{0}" method is invalid, must be one of {1}!'.format(
        method, COLOUR_QUALITY_SCALE_METHODS))

    cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy(
    ).trim(ASTME30815_PRACTISE_SHAPE)

    shape = cmfs.shape
    sd_test = sd_test.copy().align(shape)
    vs_sds = {
        sd.name: sd.copy().align(shape)
        for sd in VS_SDS[method].values()
    }

    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd_test, cmfs)

    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        sd_reference = sd_blackbody(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_reference = sd_CIE_illuminant_D_series(xy)
        sd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(
        sd_test, sd_reference, vs_sds, cmfs, chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        sd_reference, sd_reference, vs_sds, cmfs)

    if method == 'nist cqs 9.0':
        CCT_f = 1
        scaling_f = 3.2
    else:
        XYZ_r = sd_to_XYZ(sd_reference, cmfs)
        XYZ_r /= XYZ_r[1]
        CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)
        scaling_f = 3.104

    Q_as = colour_quality_scales(test_vs_colorimetry_data,
                                 reference_vs_colorimetry_data, scaling_f,
                                 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, scaling_f)

    if method == 'nist cqs 9.0':
        scaling_f = 2.93 * 1.0343
    else:
        scaling_f = 2.928

    Q_f = scale_conversion(D_E_RMS, CCT_f, scaling_f)

    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

    if method == 'nist cqs 9.0':
        Q_d = Q_p = None
    else:
        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)
        Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            sd_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
Пример #38
0
def sd_to_aces_relative_exposure_values(
        sd,
        illuminant=ILLUMINANTS_SDS['D65'],
        apply_chromatic_adaptation=False,
        chromatic_adaptation_transform='CAT02'):
    """
    Converts given spectral distribution to *ACES2065-1* colourspace relative
    exposure values.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution.
    illuminant : SpectralDistribution, optional
        *Illuminant* spectral distribution.
    apply_chromatic_adaptation : bool, optional
        Whether to apply chromatic adaptation using given transform.
    chromatic_adaptation_transform : unicode, optional
        **{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp',
        'Fairchild', 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco',
        'Bianco PC'}**,
        *Chromatic adaptation* transform.

    Returns
    -------
    ndarray, (3,)
        *ACES2065-1* colourspace relative exposure values array.

    Notes
    -----

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

    -   The chromatic adaptation method implemented here is a bit unusual
        as it involves building a new colourspace based on *ACES2065-1*
        colourspace primaries but using the whitepoint of the illuminant that
        the spectral distribution was measured under.

    References
    ----------
    :cite:`Forsythe2018`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014q`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014r`,
    :cite:`TheAcademyofMotionPictureArtsandSciencese`

    Examples
    --------
    >>> from colour import COLOURCHECKERS_SDS
    >>> sd = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
    >>> sd_to_aces_relative_exposure_values(sd)  # doctest: +ELLIPSIS
    array([ 0.1171785...,  0.0866347...,  0.0589707...])
    >>> sd_to_aces_relative_exposure_values(sd,
    ...     apply_chromatic_adaptation=True)  # doctest: +ELLIPSIS
    array([ 0.1180766...,  0.0869023...,  0.0589104...])
    """

    shape = ACES_RICD.shape
    if sd.shape != ACES_RICD.shape:
        sd = sd.copy().align(shape)

    if illuminant.shape != ACES_RICD.shape:
        illuminant = illuminant.copy().align(shape)

    s_v = sd.values
    i_v = illuminant.values

    r_bar, g_bar, b_bar = tsplit(ACES_RICD.values)

    def k(x, y):
        """
        Computes the :math:`K_r`, :math:`K_g` or :math:`K_b` scale factors.
        """

        return 1 / np.sum(x * y)

    k_r = k(i_v, r_bar)
    k_g = k(i_v, g_bar)
    k_b = k(i_v, b_bar)

    E_r = k_r * np.sum(i_v * s_v * r_bar)
    E_g = k_g * np.sum(i_v * s_v * g_bar)
    E_b = k_b * np.sum(i_v * s_v * b_bar)

    E_rgb = np.array([E_r, E_g, E_b])

    # Accounting for flare.
    E_rgb += FLARE_PERCENTAGE
    E_rgb *= S_FLARE_FACTOR

    if apply_chromatic_adaptation:
        xy = XYZ_to_xy(sd_to_XYZ(illuminant) / 100)
        NPM = normalised_primary_matrix(ACES_2065_1_COLOURSPACE.primaries, xy)
        XYZ = RGB_to_XYZ(E_rgb, xy, ACES_2065_1_COLOURSPACE.whitepoint, NPM,
                         chromatic_adaptation_transform)
        E_rgb = XYZ_to_RGB(XYZ, ACES_2065_1_COLOURSPACE.whitepoint,
                           ACES_2065_1_COLOURSPACE.whitepoint,
                           ACES_2065_1_COLOURSPACE.XYZ_to_RGB_matrix)

    return from_range_1(E_rgb)
Пример #39
0
def plot_blackbody_spectral_radiance(
        temperature=3500,
        cmfs='CIE 1931 2 Degree Standard Observer',
        blackbody='VY Canis Major',
        **kwargs):
    """
    Plots given blackbody spectral radiance.

    Parameters
    ----------
    temperature : numeric, optional
        Blackbody temperature.
    cmfs : unicode, optional
        Standard observer colour matching functions.
    blackbody : unicode, optional
        Blackbody name.

    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.

    Examples
    --------
    >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 2 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png
        :align: center
        :alt: plot_blackbody_spectral_radiance
    """

    figure = plt.figure()

    figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2)

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

    sd = sd_blackbody(temperature, cmfs.shape)

    axes = figure.add_subplot(211)
    settings = {
        'axes': axes,
        'title': '{0} - Spectral Radiance'.format(blackbody),
        'y_label': 'W / (sr m$^2$) / m',
    }
    settings.update(kwargs)
    settings['standalone'] = False

    plot_single_sd(sd, cmfs.name, **settings)

    axes = figure.add_subplot(212)

    with domain_range_scale('1'):
        XYZ = sd_to_XYZ(sd, cmfs)

    RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ))

    settings = {
        'axes': axes,
        'aspect': None,
        'title': '{0} - Colour'.format(blackbody),
        'x_label': '{0}K'.format(temperature),
        'y_label': '',
        'x_ticker': False,
        'y_ticker': False,
    }
    settings.update(kwargs)
    settings['standalone'] = False

    figure, axes = plot_single_colour_swatch(ColourSwatch(name='', RGB=RGB),
                                             **settings)

    settings = {'axes': axes, 'standalone': True}
    settings.update(kwargs)

    return render(**settings)
Пример #40
0
def planckian_table(uv, cmfs, start, end, count):
    """
    Returns a planckian table from given *CIE UCS* colourspace *uv*
    chromaticity coordinates, colour matching functions and temperature range
    using *Ohno (2013)* method.

    Parameters
    ----------
    uv : array_like
        *uv* chromaticity coordinates.
    cmfs : XYZ_ColourMatchingFunctions
        Standard observer colour matching functions.
    start : numeric
        Temperature range start in kelvins.
    end : numeric
        Temperature range end in kelvins.
    count : int
        Temperatures count in the planckian table.

    Returns
    -------
    list
        Planckian table.

    Examples
    --------
    >>> from colour import STANDARD_OBSERVERS_CMFS
    >>> from pprint import pprint
    >>> cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
    >>> uv = np.array([0.1978, 0.3122])
    >>> pprint(planckian_table(uv, cmfs, 1000, 1010, 10))
    ... # doctest: +ELLIPSIS
    [PlanckianTable_Tuvdi(Ti=1000.0, \
ui=0.4479628..., vi=0.3546296..., di=0.2537355...),
     PlanckianTable_Tuvdi(Ti=1001.1111111..., \
ui=0.4477030..., vi=0.3546521..., di=0.2534831...),
     PlanckianTable_Tuvdi(Ti=1002.2222222..., \
ui=0.4474434..., vi=0.3546746..., di=0.2532310...),
     PlanckianTable_Tuvdi(Ti=1003.3333333..., \
ui=0.4471842..., vi=0.3546970..., di=0.2529792...),
     PlanckianTable_Tuvdi(Ti=1004.4444444..., \
ui=0.4469252..., vi=0.3547194..., di=0.2527277...),
     PlanckianTable_Tuvdi(Ti=1005.5555555..., \
ui=0.4466666..., vi=0.3547417..., di=0.2524765...),
     PlanckianTable_Tuvdi(Ti=1006.6666666..., \
ui=0.4464083..., vi=0.3547640..., di=0.2522256...),
     PlanckianTable_Tuvdi(Ti=1007.7777777..., \
ui=0.4461502..., vi=0.3547862..., di=0.2519751...),
     PlanckianTable_Tuvdi(Ti=1008.8888888..., \
ui=0.4458925..., vi=0.3548084..., di=0.2517248...),
     PlanckianTable_Tuvdi(Ti=1010.0, \
ui=0.4456351..., vi=0.3548306..., di=0.2514749...)]
    """

    ux, vx = uv

    cmfs = cmfs.copy().trim(ASTME30815_PRACTISE_SHAPE)

    shape = cmfs.shape

    table = []
    for Ti in np.linspace(start, end, count):
        sd = sd_blackbody(Ti, shape)
        XYZ = sd_to_XYZ(sd, cmfs)
        XYZ /= np.max(XYZ)
        UVW = XYZ_to_UCS(XYZ)
        ui, vi = UCS_to_uv(UVW)
        di = np.hypot(ux - ui, vx - vi)
        table.append(PLANCKIAN_TABLE_TUVD(Ti, ui, vi, di))

    return table
Пример #41
0
def plot_sds_in_chromaticity_diagram(
        sds,
        cmfs='CIE 1931 2 Degree Standard Observer',
        annotate_parameters=None,
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        **kwargs):
    """
    Plots given spectral distribution chromaticity coordinates into the
    *Chromaticity Diagram* using given method.

    Parameters
    ----------
    sds : array_like, optional
        Spectral distributions to plot.
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    annotate_parameters : dict or array_like, optional
        Parameters for the :func:`plt.annotate` definition, used to annotate
        the resulting chromaticity coordinates with their respective spectral
        distribution names if ``annotate`` is set to *True*.
        ``annotate_parameters`` can be either a single dictionary applied to
        all the arrows with same settings or a sequence of dictionaries with
        different settings for each spectral distribution.
    chromaticity_diagram_callable : callable, optional
        Callable responsible for drawing the *Chromaticity Diagram*.
    method : unicode, optional
        **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**,
        *Chromaticity Diagram* method.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> A = ILLUMINANTS_SDS['A']
    >>> D65 = ILLUMINANTS_SDS['D65']
    >>> plot_sds_in_chromaticity_diagram([A, D65])  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_sds_in_chromaticity_diagram
    """

    settings = {'uniform': True}
    settings.update(kwargs)

    figure, axes = artist(**settings)

    method = method.upper()

    settings.update({
        'axes': axes,
        'standalone': False,
        'method': method,
        'cmfs': cmfs,
    })

    chromaticity_diagram_callable(**settings)

    if method == 'CIE 1931':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return XYZ_to_xy(XYZ)

        bounding_box = (-0.1, 0.9, -0.1, 0.9)
    elif method == 'CIE 1960 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return UCS_to_uv(XYZ_to_UCS(XYZ))

        bounding_box = (-0.1, 0.7, -0.2, 0.6)

    elif method == 'CIE 1976 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return Luv_to_uv(XYZ_to_Luv(XYZ))

        bounding_box = (-0.1, 0.7, -0.1, 0.7)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format(
                method))

    annotate_settings_collection = [{
        'annotate': True,
        'xytext': (-50, 30),
        'textcoords': 'offset points',
        'arrowprops': COLOUR_ARROW_STYLE,
    } for _ in range(len(sds))]

    if annotate_parameters is not None:
        if not isinstance(annotate_parameters, dict):
            assert len(annotate_parameters) == len(sds), (
                'Multiple annotate parameters defined, but they do not match '
                'the spectral distributions count!')

        for i, annotate_settings in enumerate(annotate_settings_collection):
            if isinstance(annotate_parameters, dict):
                annotate_settings.update(annotate_parameters)
            else:
                annotate_settings.update(annotate_parameters[i])

    for i, sd in enumerate(sds):
        with domain_range_scale('1'):
            XYZ = sd_to_XYZ(sd)

        ij = XYZ_to_ij(XYZ)

        axes.plot(
            ij[0],
            ij[1],
            'o',
            color=COLOUR_STYLE_CONSTANTS.colour.brightest,
            markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark,
            markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 +
                        COLOUR_STYLE_CONSTANTS.geometry.short * 0.75),
            markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75,
            label=sd.strict_name)

        if (sd.name is not None and
                annotate_settings_collection[i]['annotate']):
            annotate_settings = annotate_settings_collection[i]
            annotate_settings.pop('annotate')

            axes.annotate(sd.name, xy=ij, **annotate_settings)

    settings.update({'standalone': True, 'bounding_box': bounding_box})
    settings.update(kwargs)

    return render(**settings)
Пример #42
0
def plot_sds_in_chromaticity_diagram(
        sds,
        cmfs='CIE 1931 2 Degree Standard Observer',
        annotate_parameters=None,
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        **kwargs):
    """
    Plots given spectral distribution chromaticity coordinates into the
    *Chromaticity Diagram* using given method.

    Parameters
    ----------
    sds : array_like, optional
        Spectral distributions to plot.
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    annotate_parameters : dict or array_like, optional
        Parameters for the :func:`plt.annotate` definition, used to annotate
        the resulting chromaticity coordinates with their respective spectral
        distribution names if ``annotate`` is set to *True*.
        ``annotate_parameters`` can be either a single dictionary applied to
        all the arrows with same settings or a sequence of dictionaries with
        different settings for each spectral distribution.
    chromaticity_diagram_callable : callable, optional
        Callable responsible for drawing the *Chromaticity Diagram*.
    method : unicode, optional
        **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**,
        *Chromaticity Diagram* method.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour import ILLUMINANTS_SDS
    >>> A = ILLUMINANTS_SDS['A']
    >>> D65 = ILLUMINANTS_SDS['D65']
    >>> plot_sds_in_chromaticity_diagram([A, D65])  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_sds_in_chromaticity_diagram
    """

    settings = {'uniform': True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    method = method.upper()

    settings.update({
        'axes': axes,
        'standalone': False,
        'method': method,
        'cmfs': cmfs,
    })

    chromaticity_diagram_callable(**settings)

    if method == 'CIE 1931':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return XYZ_to_xy(XYZ)

        bounding_box = (-0.1, 0.9, -0.1, 0.9)
    elif method == 'CIE 1960 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return UCS_to_uv(XYZ_to_UCS(XYZ))

        bounding_box = (-0.1, 0.7, -0.2, 0.6)

    elif method == 'CIE 1976 UCS':

        def XYZ_to_ij(XYZ):
            """
            Converts given *CIE XYZ* tristimulus values to *ij* chromaticity
            coordinates.
            """

            return Luv_to_uv(XYZ_to_Luv(XYZ))

        bounding_box = (-0.1, 0.7, -0.1, 0.7)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format(
                method))

    annotate_settings_collection = [{
        'annotate': True,
        'xytext': (-50, 30),
        'textcoords': 'offset points',
        'arrowprops': COLOUR_ARROW_STYLE,
    } for _ in range(len(sds))]

    if annotate_parameters is not None:
        if not isinstance(annotate_parameters, dict):
            assert len(annotate_parameters) == len(sds), (
                'Multiple annotate parameters defined, but they do not match '
                'the spectral distributions count!')

        for i, annotate_settings in enumerate(annotate_settings_collection):
            if isinstance(annotate_parameters, dict):
                annotate_settings.update(annotate_parameters)
            else:
                annotate_settings.update(annotate_parameters[i])

    for i, sd in enumerate(sds):
        with domain_range_scale('1'):
            XYZ = sd_to_XYZ(sd)

        ij = XYZ_to_ij(XYZ)

        axes.plot(
            ij[0],
            ij[1],
            'o',
            color=COLOUR_STYLE_CONSTANTS.colour.brightest,
            markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark,
            markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 +
                        COLOUR_STYLE_CONSTANTS.geometry.short * 0.75),
            markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75,
            label=sd.strict_name)

        if (sd.name is not None and
                annotate_settings_collection[i]['annotate']):
            annotate_settings = annotate_settings_collection[i]
            annotate_settings.pop('annotate')

            axes.annotate(sd.name, xy=ij, **annotate_settings)

    settings.update({'standalone': True, 'bounding_box': bounding_box})
    settings.update(kwargs)

    return render(**settings)