Esempio n. 1
0
    def test_artist(self):
        """Test :func:`colour.plotting.common.artist` definition."""

        figure_1, axes_1 = artist()

        self.assertIsInstance(figure_1, Figure)
        self.assertIsInstance(axes_1, Axes)

        _figure_2, axes_2 = artist(axes=axes_1, uniform=True)
        self.assertIs(axes_1, axes_2)

        figure_3, _axes_3 = artist(uniform=True)
        self.assertEqual(figure_3.get_figwidth(), figure_3.get_figheight())
Esempio n. 2
0
    def test_render(self):
        """
        Tests :func:`colour.plotting.common.render` definition.
        """

        figure, axes = artist()

        render(
            figure=figure,
            axes=axes,
            standalone=False,
            aspect='equal',
            axes_visible=True,
            bounding_box=[0, 1, 0, 1],
            tight_layout=False,
            legend=True,
            legend_columns=2,
            transparent_background=False,
            title='Render Unit Test',
            wrap_title=True,
            x_label='x Label',
            y_label='y Label',
            x_ticker=False,
            y_ticker=False,
        )

        render(standalone=True)

        render(filename=os.path.join(self._temporary_directory, 'render.png'),
               axes_visible=False)
Esempio n. 3
0
def plot_colour_fidelity_indexes(
        specification: ColourQuality_Specification_ANSIIESTM3018,
        **kwargs: Any) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the local chroma shifts according to
    *ANSI/IES TM-30-18 Colour Rendition Report*.

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

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`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_colour_fidelity_indexes(specification)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)
    """

    _figure, axes = artist(**kwargs)

    bar_count = len(_COLOURS_TCS_BAR)
    axes.bar(
        np.arange(bar_count) + 1,
        specification.R_s,
        color=_COLOURS_TCS_BAR,
        width=1,
        edgecolor="black",
        linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.set_xlim(0.5, bar_count + 0.5)
    axes.set_ylim(0, 100)
    axes.set_yticks(np.arange(0, 110, 10))
    axes.set_ylabel("Color Sample Fidelity ($R_{f,CESi}$)")

    ticks = list(range(1, bar_count + 1, 1))
    axes.set_xticks(ticks)

    labels = [
        f"CES{i:02d}" if i % 3 == 1 else "" for i in range(1, bar_count + 1)
    ]
    axes.set_xticklabels(labels, rotation=90)

    return render(**kwargs)
Esempio n. 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)
Esempio n. 5
0
def plot_colour_fidelity_indexes(specification, **kwargs):
    """
    Plots the local chroma shifts according to
    *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_colour_fidelity_indexes(specification)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)
    """

    _figure, axes = artist(**kwargs)

    bar_count = len(_TCS_BAR_COLOURS)
    axes.bar(np.arange(bar_count) + 1,
             specification.R_s,
             color=_TCS_BAR_COLOURS,
             width=1,
             edgecolor='black',
             linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3)
    axes.set_xlim(0.5, bar_count + 0.5)
    axes.set_ylim(0, 100)
    axes.set_yticks(np.arange(0, 110, 10))
    axes.set_ylabel('Color Sample Fidelity ($R_{f,CESi}$)')

    ticks = list(range(1, bar_count + 1, 1))
    axes.set_xticks(ticks)

    labels = [
        'CES{0:02d}'.format(i) if i % 3 == 1 else ''
        for i in range(1, bar_count + 1)
    ]
    axes.set_xticklabels(labels, rotation=90)

    return render(**kwargs)
Esempio n. 6
0
    def test_uniform_axes3d(self):
        """Test :func:`colour.plotting.common.uniform_axes3d` definition."""

        figure, _axes = artist()
        axes = figure.add_subplot(111, projection="3d")

        uniform_axes3d(axes=axes)

        self.assertEqual(axes.get_xlim(), axes.get_ylim())
        self.assertEqual(axes.get_xlim(), axes.get_zlim())
Esempio n. 7
0
    def test_camera(self):
        """Test :func:`colour.plotting.common.camera` definition."""

        figure, _axes = artist()
        axes = figure.add_subplot(111, projection="3d")

        _figure, axes = camera(axes=axes, elevation=45, azimuth=90)

        self.assertEqual(axes.elev, 45)
        self.assertEqual(axes.azim, 90)
Esempio n. 8
0
    def test_label_rectangles(self):
        """
        Tests :func:`colour.plotting.common.label_rectangles` definition.
        """

        figure, axes = artist()

        samples = np.linspace(0, 1, 10)

        _figure, axes = label_rectangles(
            samples, axes.bar(samples, 1), figure=figure, axes=axes)

        self.assertEqual(len(axes.texts), len(samples))
Esempio n. 9
0
def plot_visible_spectrum_section(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    illuminant: Union[SpectralDistribution, str] = "D65",
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    show_section_colours: Boolean = True,
    show_section_contour: Boolean = True,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the visible spectrum volume, i.e. *Rösch-MacAdam* colour solid,
    section colours along given axis and origin.

    Parameters
    ----------
    cmfs
        Standard observer colour matching functions, default to the
        *CIE 1931 2 Degree Standard Observer*.  ``cmfs`` can be of any type or
        form supported by the :func:`colour.plotting.filter_cmfs` definition.
    illuminant
        Illuminant spectral distribution, default to *CIE Illuminant D65*.
        ``illuminant`` can be of any type or form supported by the
        :func:`colour.plotting.filter_illuminants` definition.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    axis
        Axis the hull section will be normal to.
    origin
        Coordinate along ``axis`` at which to plot the hull section.
    normalise
        Whether to normalise ``axis`` to the extent of the hull along it.
    show_section_colours
        Whether to show the hull section colours.
    show_section_contour
        Whether to show the hull section contour.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`,
        :func:`colour.plotting.section.plot_hull_section_colours`
        :func:`colour.plotting.section.plot_hull_section_contour`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour.utilities import is_trimesh_installed
    >>> if is_trimesh_installed:
    ...     plot_visible_spectrum_section(
    ...         section_colours='RGB', section_opacity=0.15)
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Visible_Spectrum_Section.png
        :align: center
        :alt: plot_visible_spectrum_section
    """

    import trimesh

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    # pylint: disable=E1102
    cmfs = reshape_msds(first_item(filter_cmfs(cmfs).values()),
                        SpectralShape(360, 780, 1))
    illuminant = cast(
        SpectralDistribution,
        first_item(filter_illuminants(illuminant).values()),
    )

    vertices = solid_RoschMacAdam(
        cmfs,
        illuminant,
        point_order="Pulse Wave Width",
        filter_jagged_points=True,
    )
    mesh = trimesh.Trimesh(vertices)
    hull = trimesh.convex.convex_hull(mesh)

    if show_section_colours:
        settings = {"axes": axes}
        settings.update(kwargs)
        settings["standalone"] = False

        plot_hull_section_colours(hull, model, axis, origin, normalise,
                                  **settings)

    if show_section_contour:
        settings = {"axes": axes}
        settings.update(kwargs)
        settings["standalone"] = False

        plot_hull_section_contour(hull, model, axis, origin, normalise,
                                  **settings)

    title = (f"Visible Spectrum Section - "
             f"{f'{origin * 100}%' if normalise else origin} - "
             f"{model} - "
             f"{cmfs.strict_name}")

    plane = MAPPING_AXIS_TO_PLANE[axis]

    labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[as_int_array(
        colourspace_model_axis_reorder([0, 1, 2], model))]
    x_label, y_label = labels[plane[0]], labels[plane[1]]

    settings.update({
        "axes": axes,
        "standalone": True,
        "title": title,
        "x_label": x_label,
        "y_label": y_label,
    })
    settings.update(kwargs)

    return render(**settings)
Esempio n. 10
0
def plot_colour_quality_bars(
    specifications: Sequence[
        Union[
            ColourRendering_Specification_CQS,
            ColourRendering_Specification_CRI,
        ]
    ],
    labels: Boolean = True,
    hatching: Optional[Boolean] = None,
    hatching_repeat: Integer = 2,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the colour quality data of given illuminants or light sources colour
    quality specifications.

    Parameters
    ----------
    specifications
        Array of illuminants or light sources colour quality specifications.
    labels
        Add labels above bars.
    hatching
        Use hatching for the bars.
    hatching_repeat
        Hatching pattern repeat.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.quality.plot_colour_quality_bars`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour import (SDS_ILLUMINANTS,
    ...                     SDS_LIGHT_SOURCES, SpectralShape)
    >>> illuminant = SDS_ILLUMINANTS['FL2']
    >>> light_source = SDS_LIGHT_SOURCES['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)
    >>> plot_colour_quality_bars([cqs_i, cqs_l])  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png
        :align: center
        :alt: plot_colour_quality_bars
    """

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    bar_width = 0.5
    y_ticks_interval = 10
    count_s, count_Q_as = len(specifications), 0
    patterns = cycle(CONSTANTS_COLOUR_STYLE.hatch.patterns)
    if hatching is None:
        hatching = False if count_s == 1 else True

    for i, specification in enumerate(specifications):
        Q_a, Q_as, colorimetry_data = (
            specification.Q_a,
            specification.Q_as,
            specification.colorimetry_data,
        )

        count_Q_as = len(Q_as)
        RGB = [[1] * 3] + [
            np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1)
            for x in colorimetry_data[0]
        ]

        x = (
            as_float_array(
                i
                + np.arange(
                    0,
                    (count_Q_as + 1) * (count_s + 1),
                    (count_s + 1),
                    dtype=DEFAULT_FLOAT_DTYPE,
                )
            )
            * bar_width
        )
        y = as_float_array(
            [Q_a]
            + [
                s[1].Q_a  # type: ignore[attr-defined]
                for s in sorted(Q_as.items(), key=lambda s: s[0])
            ]
        )

        bars = axes.bar(
            x,
            np.abs(y),
            color=RGB,
            width=bar_width,
            edgecolor=CONSTANTS_COLOUR_STYLE.colour.dark,
            label=specification.name,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
        )

        hatches = (
            [next(patterns) * hatching_repeat] * (count_Q_as + 1)
            if hatching
            else list(
                np.where(y < 0, next(patterns), None)  # type: ignore[call-overload]
            )
        )

        for j, bar in enumerate(bars.patches):
            bar.set_hatch(hatches[j])

        if labels:
            label_rectangles(
                [f"{y_v:.1f}" for y_v in y],
                bars,
                rotation="horizontal" if count_s == 1 else "vertical",
                offset=(
                    0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000,
                    0.025,
                ),
                text_size=-5 / 7 * count_s + 12.5,
                axes=axes,
            )

    axes.axhline(
        y=100,
        color=CONSTANTS_COLOUR_STYLE.colour.dark,
        linestyle="--",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
    )

    axes.set_xticks(
        (
            np.arange(
                0,
                (count_Q_as + 1) * (count_s + 1),
                (count_s + 1),
                dtype=DEFAULT_FLOAT_DTYPE,
            )
            - bar_width
        )
        * bar_width
        + (count_s * bar_width / 2)
    )
    axes.set_xticklabels(
        ["Qa"] + [f"Q{index + 1}" for index in range(0, count_Q_as, 1)]
    )
    axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval))

    aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2))
    bounding_box = (
        -bar_width,
        ((count_Q_as + 1) * (count_s + 1)) / 2 - bar_width,
        0,
        120,
    )

    settings = {
        "axes": axes,
        "aspect": aspect,
        "bounding_box": bounding_box,
        "legend": hatching,
        "title": "Colour Quality",
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 11
0
def plot_multi_colour_checkers(
    colour_checkers: Union[ColourChecker, str, Sequence[Union[ColourChecker,
                                                              str]]],
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot and compares given colour checkers.

    Parameters
    ----------
    colour_checkers
        Color checker to plot, count must be less than or equal to 2.
        ``colour_checkers`` elements can be of any type or form supported by
        the :func:`colour.plotting.filter_colour_checkers` definition.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.plot_multi_colour_swatches`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005'])
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Multi_Colour_Checkers.png
        :align: center
        :alt: plot_multi_colour_checkers
    """

    filtered_colour_checkers = list(
        filter_colour_checkers(colour_checkers).values())

    attest(
        len(filtered_colour_checkers) <= 2,
        "Only two colour checkers can be compared at a time!",
    )

    _figure, axes = artist(**kwargs)

    compare_swatches = len(filtered_colour_checkers) == 2

    colour_swatches = []
    colour_checker_names = []
    for colour_checker in filtered_colour_checkers:
        colour_checker_names.append(colour_checker.name)
        for label, xyY in colour_checker.data.items():
            XYZ = xyY_to_XYZ(xyY)
            RGB = XYZ_to_plotting_colourspace(XYZ, colour_checker.illuminant)
            colour_swatches.append(
                ColourSwatch(np.clip(np.ravel(RGB), 0, 1), label.title()))

    if compare_swatches:
        colour_swatches = [
            swatch for pairs in zip(
                colour_swatches[0:len(colour_swatches) // 2],
                colour_swatches[len(colour_swatches) // 2:],
            ) for swatch in pairs
        ]

    background_colour = "0.1"
    width = height = 1.0
    spacing = 0.25
    columns = 6

    settings: Dict[str, Any] = {
        "axes": axes,
        "width": width,
        "height": height,
        "spacing": spacing,
        "columns": columns,
        "direction": "-y",
        "text_kwargs": {
            "size": 8
        },
        "background_colour": background_colour,
        "compare_swatches": "Stacked" if compare_swatches else None,
    }
    settings.update(kwargs)
    settings["standalone"] = False

    plot_multi_colour_swatches(colour_swatches, **settings)

    axes.text(
        0.5,
        0.005,
        (f"{', '.join(colour_checker_names)} - "
         f"{CONSTANTS_COLOUR_STYLE.colour.colourspace.name} - "
         f"Colour Rendition Chart"),
        transform=axes.transAxes,
        color=CONSTANTS_COLOUR_STYLE.colour.bright,
        ha="center",
        va="bottom",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,
    )

    settings.update({
        "axes": axes,
        "standalone": True,
        "title": ", ".join(colour_checker_names),
    })

    return render(**settings)
Esempio n. 12
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)
Esempio n. 13
0
def plot_colour_quality_bars(specifications,
                             labels=True,
                             hatching=None,
                             hatching_repeat=2,
                             **kwargs):
    """
    Plots the colour quality data of given illuminants or light sources colour
    quality specifications.

    Parameters
    ----------
    specifications : array_like
        Array of illuminants or light sources colour quality specifications.
    labels : bool, optional
        Add labels above bars.
    hatching : bool or None, optional
        Use hatching for the bars.
    hatching_repeat : int, optional
        Hatching pattern repeat.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.quality.plot_colour_quality_bars`,
        :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,
    ...                     LIGHT_SOURCES_SDS, SpectralShape)
    >>> 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)
    >>> plot_colour_quality_bars([cqs_i, cqs_l])  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png
        :align: center
        :alt: plot_colour_quality_bars
    """

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

    _figure, axes = artist(**settings)

    bar_width = 0.5
    y_ticks_interval = 10
    count_s, count_Q_as = len(specifications), 0
    patterns = cycle(COLOUR_STYLE_CONSTANTS.hatch.patterns)
    if hatching is None:
        hatching = False if count_s == 1 else True
    for i, specification in enumerate(specifications):
        Q_a, Q_as, colorimetry_data = (specification.Q_a, specification.Q_as,
                                       specification.colorimetry_data)

        count_Q_as = len(Q_as)
        colours = ([[1] * 3] + [
            np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1)
            for x in colorimetry_data[0]
        ])

        x = (i + np.arange(
            0, (count_Q_as + 1) * (count_s + 1), (count_s + 1),
            dtype=DEFAULT_FLOAT_DTYPE)) * bar_width
        y = [s[1].Q_a for s in sorted(Q_as.items(), key=lambda s: s[0])]
        y = np.array([Q_a] + list(y))

        bars = plt.bar(
            x,
            np.abs(y),
            color=colours,
            width=bar_width,
            edgecolor=COLOUR_STYLE_CONSTANTS.colour.dark,
            label=specification.name)

        hatches = ([next(patterns) * hatching_repeat] * (count_Q_as + 1)
                   if hatching else np.where(y < 0, next(patterns),
                                             None).tolist())

        for j, bar in enumerate(bars.patches):
            bar.set_hatch(hatches[j])

        if labels:
            label_rectangles(
                y,
                bars,
                rotation='horizontal' if count_s == 1 else 'vertical',
                offset=(0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000,
                        0.025),
                text_size=-5 / 7 * count_s + 12.5)

    axes.axhline(
        y=100, color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--')

    axes.set_xticks((np.arange(
        0, (count_Q_as + 1) * (count_s + 1), (count_s + 1),
        dtype=DEFAULT_FLOAT_DTYPE) * bar_width + (count_s * bar_width / 2)),
                    ['Qa'] + [
                        'Q{0}'.format(index + 1)
                        for index in range(0, count_Q_as + 1, 1)
                    ])
    axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval))

    aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2))
    bounding_box = (-bar_width, ((count_Q_as + 1) * (count_s + 1)) / 2, 0, 120)

    settings = {
        'axes': axes,
        'aspect': aspect,
        'bounding_box': bounding_box,
        'legend': hatching,
        'title': 'Colour Quality',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 14
0
def plot_chromaticity_diagram_colours(
        samples=256,
        diagram_opacity=1.0,
        diagram_clipping_path=None,
        cmfs='CIE 1931 2 Degree Standard Observer',
        method='CIE 1931',
        **kwargs):
    """
    Plots the *Chromaticity Diagram* colours according to given method.

    Parameters
    ----------
    samples : numeric, optional
        Samples count on one axis.
    diagram_opacity : numeric, optional
        Opacity of the *Chromaticity Diagram* colours.
    diagram_clipping_path : array_like, optional
        Path of points used to clip the *Chromaticity Diagram* colours.
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
        :align: center
        :alt: plot_chromaticity_diagram_colours
    """

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

    figure, axes = artist(**settings)

    method = method.upper()

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

    illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint

    ii, jj = np.meshgrid(
        np.linspace(0, 1, samples), np.linspace(1, 0, samples))
    ij = tstack([ii, jj])

    if method == 'CIE 1931':
        XYZ = xy_to_XYZ(ij)
        spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
    elif method == 'CIE 1960 UCS':
        XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
        spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
    elif method == 'CIE 1976 UCS':
        XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))
        spectral_locus = Luv_to_uv(
            XYZ_to_Luv(cmfs.values, illuminant), illuminant)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format(
                method))

    RGB = normalise_maximum(
        XYZ_to_plotting_colourspace(XYZ, illuminant), axis=-1)

    polygon = Polygon(
        spectral_locus
        if diagram_clipping_path is None else diagram_clipping_path,
        facecolor='none',
        edgecolor='none')
    axes.add_patch(polygon)
    # Preventing bounding box related issues as per
    # https://github.com/matplotlib/matplotlib/issues/10529
    image = axes.imshow(
        RGB,
        interpolation='bilinear',
        extent=(0, 1, 0, 1),
        clip_path=None,
        alpha=diagram_opacity)
    image.set_clip_path(polygon)

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

    return render(**kwargs)
Esempio n. 15
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)
Esempio n. 16
0
def plot_RGB_colourspaces_in_chromaticity_diagram(
        colourspaces=None,
        cmfs='CIE 1931 2 Degree Standard Observer',
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        show_whitepoints=True,
        show_pointer_gamut=False,
        **kwargs):
    """
    Plots given *RGB* colourspaces in the *Chromaticity Diagram* according
    to given method.

    Parameters
    ----------
    colourspaces : array_like, optional
        *RGB* colourspaces to plot.
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    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.
    show_whitepoints : bool, optional
        Whether to display the *RGB* colourspaces whitepoints.
    show_pointer_gamut : bool, optional
        Whether to display the *Pointer's Gamut*.

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

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

    Examples
    --------
    >>> plot_RGB_colourspaces_in_chromaticity_diagram(
    ...     ['ITU-R BT.709', 'ACEScg', 'S-Gamut'])
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_\
Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_RGB_colourspaces_in_chromaticity_diagram
    """

    if colourspaces is None:
        colourspaces = ['ITU-R BT.709', 'ACEScg', 'S-Gamut']

    colourspaces = filter_RGB_colourspaces(colourspaces).values()

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

    _figure, axes = artist(**settings)

    method = method.upper()

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

    title = '{0}\n{1} - {2} Chromaticity Diagram'.format(
        ', '.join([colourspace.name for colourspace in colourspaces]),
        cmfs.name, method)

    settings = {'axes': axes, 'title': title, 'method': method}
    settings.update(kwargs)
    settings['standalone'] = False

    chromaticity_diagram_callable(**settings)

    if show_pointer_gamut:
        settings = {'axes': axes, 'method': method}
        settings.update(kwargs)
        settings['standalone'] = False

        plot_pointer_gamut(**settings)

    if method == 'CIE 1931':

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy

        x_limit_min, x_limit_max = [-0.1], [0.9]
        y_limit_min, y_limit_max = [-0.1], [0.9]
    elif method == 'CIE 1960 UCS':

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_UCS_uv(xy)

        x_limit_min, x_limit_max = [-0.1], [0.7]
        y_limit_min, y_limit_max = [-0.2], [0.6]

    elif method == 'CIE 1976 UCS':

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_Luv_uv(xy)

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

    settings = {'colour_cycle_count': len(colourspaces)}
    settings.update(kwargs)

    cycle = colour_cycle(**settings)

    for colourspace in colourspaces:
        R, G, B, _A = next(cycle)

        # RGB colourspaces such as *ACES2065-1* have primaries with
        # chromaticity coordinates set to 0 thus we prevent nan from being
        # yield by zero division in later colour transformations.
        P = np.where(
            colourspace.primaries == 0,
            EPSILON,
            colourspace.primaries,
        )
        P = xy_to_ij(P)
        W = xy_to_ij(colourspace.whitepoint)

        axes.plot(
            (W[0], W[0]), (W[1], W[1]),
            color=(R, G, B),
            label=colourspace.name)

        if show_whitepoints:
            axes.plot((W[0], W[0]), (W[1], W[1]), 'o', color=(R, G, B))

        axes.plot(
            (P[0, 0], P[1, 0]), (P[0, 1], P[1, 1]), 'o-', color=(R, G, B))
        axes.plot(
            (P[1, 0], P[2, 0]), (P[1, 1], P[2, 1]), 'o-', color=(R, G, B))
        axes.plot(
            (P[2, 0], P[0, 0]), (P[2, 1], P[0, 1]), 'o-', color=(R, G, B))

        x_limit_min.append(np.amin(P[..., 0]) - 0.1)
        y_limit_min.append(np.amin(P[..., 1]) - 0.1)
        x_limit_max.append(np.amax(P[..., 0]) + 0.1)
        y_limit_max.append(np.amax(P[..., 1]) + 0.1)

    bounding_box = (
        min(x_limit_min),
        max(x_limit_max),
        min(y_limit_min),
        max(y_limit_max),
    )

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

    return render(**settings)
Esempio n. 17
0
def plot_corresponding_chromaticities_prediction(experiment=1,
                                                 model='Von Kries',
                                                 transform='CAT02',
                                                 **kwargs):
    """
    Plots given chromatic adaptation model corresponding chromaticities
    prediction.

    Parameters
    ----------
    experiment : int, optional
        Corresponding chromaticities prediction experiment number.
    model : unicode, optional
        Corresponding chromaticities prediction model name.
    transform : unicode, optional
        Transformation to use with *Von Kries* chromatic adaptation model.

    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
    --------
    >>> plot_corresponding_chromaticities_prediction(1, 'Von Kries', 'CAT02')
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_\
Plot_Corresponding_Chromaticities_Prediction.png
        :align: center
        :alt: plot_corresponding_chromaticities_prediction
    """

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

    _figure, axes = artist(**settings)

    title = (('Corresponding Chromaticities Prediction\n{0} ({1}) - '
              'Experiment {2}\nCIE 1976 UCS Chromaticity Diagram').format(
                  model, transform, experiment)
             if model.lower() in ('von kries', 'vonkries') else
             ('Corresponding Chromaticities Prediction\n{0} - '
              'Experiment {1}\nCIE 1976 UCS Chromaticity Diagram').format(
                  model, experiment))

    settings = {'axes': axes, 'title': title}
    settings.update(kwargs)
    settings['standalone'] = False

    plot_chromaticity_diagram_CIE1976UCS(**settings)

    results = corresponding_chromaticities_prediction(
        experiment, transform=transform)

    for result in results:
        _name, uvp_t, uvp_m, uvp_p = result
        axes.arrow(
            uvp_t[0],
            uvp_t[1],
            uvp_p[0] - uvp_t[0] - 0.1 * (uvp_p[0] - uvp_t[0]),
            uvp_p[1] - uvp_t[1] - 0.1 * (uvp_p[1] - uvp_t[1]),
            color=COLOUR_STYLE_CONSTANTS.colour.dark,
            head_width=0.005,
            head_length=0.005)
        axes.plot(
            uvp_t[0],
            uvp_t[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)
        axes.plot(
            uvp_m[0],
            uvp_m[1],
            '^',
            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)
        axes.plot(
            uvp_p[0], uvp_p[1], '^', color=COLOUR_STYLE_CONSTANTS.colour.dark)

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

    return render(**settings)
Esempio n. 18
0
def plot_hull_section_colours(
    hull: trimesh.Trimesh,  # type: ignore[name-defined]  # noqa
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    section_colours: Optional[Union[ArrayLike, str]] = None,
    section_opacity: Floating = 1,
    convert_kwargs: Optional[Dict] = None,
    samples: Integer = 256,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the section colours of given *trimesh* hull along given axis and
    origin.

    Parameters
    ----------
    hull
        *Trimesh* hull.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    axis
        Axis the hull section will be normal to.
    origin
        Coordinate along ``axis`` at which to plot the hull section.
    normalise
        Whether to normalise ``axis`` to the extent of the hull along it.
    section_colours
        Colours of the hull section, if ``section_colours`` is set to *RGB*,
        the colours will be computed according to the corresponding
        coordinates.
    section_opacity
        Opacity of the hull section colours.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.
    samples
        Samples count on one axis when computing the hull section colours.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour.models import RGB_COLOURSPACE_sRGB
    >>> from colour.utilities import is_trimesh_installed
    >>> vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64)
    >>> XYZ_vertices = RGB_to_XYZ(
    ...     vertices['position'] + 0.5,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.matrix_RGB_to_XYZ,
    ... )
    >>> if is_trimesh_installed:
    ...     import trimesh
    ...     hull = trimesh.Trimesh(XYZ_vertices, faces, process=False)
    ...     plot_hull_section_colours(hull, section_colours='RGB')
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Hull_Section_Colours.png
        :align: center
        :alt: plot_hull_section_colours
    """

    axis = validate_method(
        axis,
        ["+z", "+x", "+y"],
        '"{0}" axis is invalid, it must be one of {1}!',
    )

    hull = hull.copy()

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    section_colours = cast(
        ArrayLike,
        optional(section_colours,
                 HEX_to_RGB(CONSTANTS_COLOUR_STYLE.colour.average)),
    )

    convert_kwargs = optional(convert_kwargs, {})

    # Luminance / Lightness reordered along "z" axis.
    with suppress_warnings(python_warnings=True):
        ijk_vertices = colourspace_model_axis_reorder(
            convert(hull.vertices, "CIE XYZ", model, **convert_kwargs), model)
        ijk_vertices = np.nan_to_num(ijk_vertices)
        ijk_vertices *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]

    hull.vertices = ijk_vertices

    if axis == "+x":
        index_origin = 0
    elif axis == "+y":
        index_origin = 1
    elif axis == "+z":
        index_origin = 2
    plane = MAPPING_AXIS_TO_PLANE[axis]

    section = hull_section(hull, axis, origin, normalise)

    padding = 0.1 * np.mean(
        COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
    min_x = np.min(ijk_vertices[..., plane[0]]) - padding
    max_x = np.max(ijk_vertices[..., plane[0]]) + padding
    min_y = np.min(ijk_vertices[..., plane[1]]) - padding
    max_y = np.max(ijk_vertices[..., plane[1]]) + padding
    extent = (min_x, max_x, min_y, max_y)

    use_RGB_section_colours = str(section_colours).upper() == "RGB"
    if use_RGB_section_colours:
        ii, jj = np.meshgrid(
            np.linspace(min_x, max_x, samples),
            np.linspace(max_y, min_y, samples),
        )
        ij = tstack([ii, jj])
        ijk_section = full((samples, samples, 3),
                           np.median(section[..., index_origin]))
        ijk_section[..., plane] = ij
        ijk_section /= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]
        XYZ_section = convert(
            colourspace_model_axis_reorder(ijk_section, model, "Inverse"),
            model,
            "CIE XYZ",
            **convert_kwargs,
        )
        RGB_section = XYZ_to_plotting_colourspace(XYZ_section)
    else:
        section_colours = np.hstack([section_colours, section_opacity])

    facecolor = "none" if use_RGB_section_colours else section_colours
    polygon = Polygon(
        section[..., plane],
        facecolor=facecolor,
        edgecolor="none",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.add_patch(polygon)
    if use_RGB_section_colours:
        image = axes.imshow(
            np.clip(RGB_section, 0, 1),
            interpolation="bilinear",
            extent=extent,
            clip_path=None,
            alpha=section_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
        )
        image.set_clip_path(polygon)

    settings = {
        "axes": axes,
        "bounding_box": extent,
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 19
0
def plot_planckian_locus_in_chromaticity_diagram(
        illuminants=None,
        annotate_parameters=None,
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        **kwargs):
    """
    Plots the *Planckian Locus* and given illuminants in the
    *Chromaticity Diagram* according to given method.

    Parameters
    ----------
    illuminants : array_like, optional
        Factory illuminants to plot.
    annotate_parameters : dict or array_like, optional
        Parameters for the :func:`plt.annotate` definition, used to annotate
        the resulting chromaticity coordinates with their respective illuminant
        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
        illuminant.
    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.temperature.plot_planckian_locus`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C'])
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_\
Plot_Planckian_Locus_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_planckian_locus_in_chromaticity_diagram
    """

    cmfs = CMFS['CIE 1931 2 Degree Standard Observer']

    if illuminants is None:
        illuminants = ('A', 'B', 'C')

    illuminants = filter_passthrough(ILLUMINANTS.get(cmfs.name), illuminants)

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

    _figure, axes = artist(**settings)

    method = method.upper()

    settings = {'axes': axes, 'method': method}
    settings.update(kwargs)
    settings['standalone'] = False

    chromaticity_diagram_callable(**settings)

    plot_planckian_locus(**settings)

    if method == 'CIE 1931':

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy

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

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy)))

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

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

    if annotate_parameters is not None:
        if not isinstance(annotate_parameters, dict):
            assert len(annotate_parameters) == len(illuminants), (
                'Multiple annotate parameters defined, but they do not match '
                'the illuminants 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, (illuminant, xy) in enumerate(illuminants.items()):
        ij = xy_to_ij(xy)

        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=illuminant)

        if annotate_settings_collection[i]['annotate']:
            annotate_settings = annotate_settings_collection[i]
            annotate_settings.pop('annotate')

            plt.annotate(illuminant, xy=ij, **annotate_settings)

    title = (('{0} Illuminants - Planckian Locus\n'
              '{1} Chromaticity Diagram - '
              'CIE 1931 2 Degree Standard Observer').format(
                  ', '.join(illuminants), method) if illuminants else
             ('Planckian Locus\n{0} Chromaticity Diagram - '
              'CIE 1931 2 Degree Standard Observer'.format(method)))

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

    return render(**settings)
Esempio n. 20
0
def plot_RGB_chromaticities_in_chromaticity_diagram(
        RGB,
        colourspace='sRGB',
        chromaticity_diagram_callable=(
            plot_RGB_colourspaces_in_chromaticity_diagram),
        method='CIE 1931',
        scatter_parameters=None,
        **kwargs):
    """
    Plots given *RGB* colourspace array in the *Chromaticity Diagram* according
    to given method.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array.
    colourspace : optional, unicode
        *RGB* colourspace of the *RGB* array.
    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.
    scatter_parameters : dict, optional
        Parameters for the :func:`plt.scatter` definition, if ``c`` is set to
        *RGB*, the scatter will use given ``RGB`` colours.

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

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

    Examples
    --------
    >>> RGB = np.random.random((128, 128, 3))
    >>> plot_RGB_chromaticities_in_chromaticity_diagram(
    ...     RGB, 'ITU-R BT.709')
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_\
Plot_RGB_Chromaticities_In_Chromaticity_Diagram_Plot.png
        :align: center
        :alt: plot_RGB_chromaticities_in_chromaticity_diagram
    """

    RGB = as_float_array(RGB).reshape(-1, 3)

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

    _figure, axes = artist(**settings)

    method = method.upper()

    scatter_settings = {
        's': 40,
        'c': 'RGB',
        'marker': 'o',
        'alpha': 0.85,
    }
    if scatter_parameters is not None:
        scatter_settings.update(scatter_parameters)

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

    colourspace = first_item(filter_RGB_colourspaces(colourspace).values())
    settings['colourspaces'] = (
        ['^{0}$'.format(colourspace.name)] + settings.get('colourspaces', []))

    chromaticity_diagram_callable(**settings)

    use_RGB_colours = scatter_settings['c'].upper() == 'RGB'
    if use_RGB_colours:
        RGB = RGB[RGB[:, 1].argsort()]
        scatter_settings['c'] = np.clip(
            RGB_to_RGB(
                RGB,
                colourspace,
                COLOUR_STYLE_CONSTANTS.colour.colourspace,
                apply_encoding_cctf=True).reshape(-1, 3), 0, 1)

    XYZ = RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint,
                     colourspace.RGB_to_XYZ_matrix)

    if method == 'CIE 1931':
        ij = XYZ_to_xy(XYZ, colourspace.whitepoint)
    elif method == 'CIE 1960 UCS':
        ij = UCS_to_uv(XYZ_to_UCS(XYZ))

    elif method == 'CIE 1976 UCS':
        ij = Luv_to_uv(
            XYZ_to_Luv(XYZ, colourspace.whitepoint), colourspace.whitepoint)

    axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings)

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

    return render(**settings)
Esempio n. 21
0
def plot_single_sd(sd,
                   cmfs='CIE 1931 2 Degree Standard Observer',
                   out_of_gamut_clipping=True,
                   modulate_colours_with_sd_amplitude=False,
                   equalize_sd_amplitude=False,
                   **kwargs):
    """
    Plots given spectral distribution.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution to plot.
    out_of_gamut_clipping : bool, optional
        Whether to clip out of gamut colours otherwise, the colours will be
        offset by the absolute minimal colour leading to a rendering on
        gray background, less saturated and smoother.
    modulate_colours_with_sd_amplitude : bool, optional
        Whether to modulate the colours with the spectral distribution
        amplitude.
    equalize_sd_amplitude : bool, optional
        Whether to equalize the spectral distribution amplitude.
        Equalization occurs after the colours modulation thus setting both
        arguments to *True* will generate a spectrum strip where each
        wavelength colour is modulated by the spectral distribution amplitude.
        The usual 5% margin above the spectral distribution is also omitted.
    cmfs : unicode
        Standard observer colour matching functions used for spectrum creation.

    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.

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

    Examples
    --------
    >>> from colour import SpectralDistribution
    >>> data = {
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360
    ... }
    >>> sd = SpectralDistribution(data, name='Custom')
    >>> plot_single_sd(sd)  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Single_SD.png
        :align: center
        :alt: plot_single_sd
    """

    _figure, axes = artist(**kwargs)

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

    sd = sd.copy()
    sd.interpolator = LinearInterpolator
    wavelengths = cmfs.wavelengths[np.logical_and(
        cmfs.wavelengths >= max(min(cmfs.wavelengths), min(sd.wavelengths)),
        cmfs.wavelengths <= min(max(cmfs.wavelengths), max(sd.wavelengths)),
    )]
    values = sd[wavelengths]

    colours = XYZ_to_plotting_colourspace(
        wavelength_to_XYZ(wavelengths, cmfs),
        ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['E'],
        apply_cctf_encoding=False)

    if not out_of_gamut_clipping:
        colours += np.abs(np.min(colours))

    colours = normalise_maximum(colours)

    if modulate_colours_with_sd_amplitude:
        colours *= (values / np.max(values))[..., np.newaxis]

    colours = COLOUR_STYLE_CONSTANTS.colour.colourspace.cctf_encoding(colours)

    if equalize_sd_amplitude:
        values = np.ones(values.shape)

    margin = 0 if equalize_sd_amplitude else 0.05

    x_min, x_max = min(wavelengths), max(wavelengths)
    y_min, y_max = 0, max(values) + max(values) * margin

    polygon = Polygon(np.vstack([
        (x_min, 0),
        tstack([wavelengths, values]),
        (x_max, 0),
    ]),
                      facecolor='none',
                      edgecolor='none')
    axes.add_patch(polygon)

    padding = 0.1
    axes.bar(x=wavelengths - padding,
             height=max(values),
             width=1 + padding,
             color=colours,
             align='edge',
             clip_path=polygon)

    axes.plot(wavelengths, values, color=COLOUR_STYLE_CONSTANTS.colour.dark)

    settings = {
        'axes': axes,
        'bounding_box': (x_min, x_max, y_min, y_max),
        'title': '{0} - {1}'.format(sd.strict_name, cmfs.strict_name),
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Spectral Distribution',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 22
0
def plot_pointer_gamut(method='CIE 1931', **kwargs):
    """
    Plots *Pointer's Gamut* according to given method.

    Parameters
    ----------
    method : unicode, optional
        **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**,
        Plotting method.

    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_pointer_gamut()  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png
        :align: center
        :alt: plot_pointer_gamut
    """

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

    _figure, axes = artist(**settings)

    method = method.upper()

    if method == 'CIE 1931':

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

            return XYZ_to_xy(XYZ, *args)

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy

    elif method == 'CIE 1960 UCS':

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

            return UCS_to_uv(XYZ_to_UCS(XYZ))

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_UCS_uv(xy)

    elif method == 'CIE 1976 UCS':

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

            return Luv_to_uv(XYZ_to_Luv(XYZ, *args), *args)

        def xy_to_ij(xy):
            """
            Converts given *xy* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_Luv_uv(xy)

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

    ij = xy_to_ij(as_float_array(POINTER_GAMUT_BOUNDARIES))
    alpha_p = COLOUR_STYLE_CONSTANTS.opacity.high
    colour_p = COLOUR_STYLE_CONSTANTS.colour.darkest
    axes.plot(
        ij[..., 0],
        ij[..., 1],
        label='Pointer\'s Gamut',
        color=colour_p,
        alpha=alpha_p)
    axes.plot(
        (ij[-1][0], ij[0][0]), (ij[-1][1], ij[0][1]),
        color=colour_p,
        alpha=alpha_p)

    XYZ = Lab_to_XYZ(
        LCHab_to_Lab(POINTER_GAMUT_DATA), POINTER_GAMUT_ILLUMINANT)
    ij = XYZ_to_ij(XYZ, POINTER_GAMUT_ILLUMINANT)
    axes.scatter(
        ij[..., 0], ij[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+')

    settings.update({'axes': axes})
    settings.update(kwargs)

    return render(**settings)
Esempio n. 23
0
def plot_spectral_locus(cmfs='CIE 1931 2 Degree Standard Observer',
                        spectral_locus_colours=None,
                        spectral_locus_labels=None,
                        method='CIE 1931',
                        **kwargs):
    """
    Plots the *Spectral Locus* according to given method.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions defining the
        *Spectral Locus*.
    spectral_locus_colours : array_like or unicode, optional
        *Spectral Locus* colours, if ``spectral_locus_colours`` is set to
        *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    spectral_locus_labels : array_like, optional
        Array of wavelength labels used to customise which labels will be drawn
        around the spectral locus. Passing an empty array will result in no
        wavelength labels being drawn.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_spectral_locus(spectral_locus_colours='RGB')  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Spectral_Locus.png
        :align: center
        :alt: plot_spectral_locus
    """

    if spectral_locus_colours is None:
        spectral_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark

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

    figure, axes = artist(**settings)

    method = method.upper()

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

    illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint

    wavelengths = cmfs.wavelengths
    equal_energy = np.array([1 / 3] * 2)

    if method == 'CIE 1931':
        ij = XYZ_to_xy(cmfs.values, illuminant)
        labels = ((390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600,
                   620, 700)
                  if spectral_locus_labels is None else spectral_locus_labels)
    elif method == 'CIE 1960 UCS':
        ij = UCS_to_uv(XYZ_to_UCS(cmfs.values))
        labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540,
                   550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680)
                  if spectral_locus_labels is None else spectral_locus_labels)
    elif method == 'CIE 1976 UCS':
        ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant)
        labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540,
                   550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680)
                  if spectral_locus_labels is None else spectral_locus_labels)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format(
                method))

    pl_ij = tstack([
        np.linspace(ij[0][0], ij[-1][0], 20),
        np.linspace(ij[0][1], ij[-1][1], 20)
    ]).reshape(-1, 1, 2)
    sl_ij = np.copy(ij).reshape(-1, 1, 2)

    if spectral_locus_colours.upper() == 'RGB':
        spectral_locus_colours = normalise_maximum(
            XYZ_to_plotting_colourspace(cmfs.values), axis=-1)

        if method == 'CIE 1931':
            XYZ = xy_to_XYZ(pl_ij)
        elif method == 'CIE 1960 UCS':
            XYZ = xy_to_XYZ(UCS_uv_to_xy(pl_ij))
        elif method == 'CIE 1976 UCS':
            XYZ = xy_to_XYZ(Luv_uv_to_xy(pl_ij))
        purple_line_colours = normalise_maximum(
            XYZ_to_plotting_colourspace(XYZ.reshape(-1, 3)), axis=-1)
    else:
        purple_line_colours = spectral_locus_colours

    for slp_ij, slp_colours in ((pl_ij, purple_line_colours),
                                (sl_ij, spectral_locus_colours)):
        line_collection = LineCollection(
            np.concatenate([slp_ij[:-1], slp_ij[1:]], axis=1),
            colors=slp_colours)
        axes.add_collection(line_collection)

    wl_ij = dict(tuple(zip(wavelengths, ij)))
    for label in labels:
        i, j = wl_ij[label]

        index = bisect.bisect(wavelengths, label)
        left = wavelengths[index - 1] if index >= 0 else wavelengths[index]
        right = (wavelengths[index]
                 if index < len(wavelengths) else wavelengths[-1])

        dx = wl_ij[right][0] - wl_ij[left][0]
        dy = wl_ij[right][1] - wl_ij[left][1]

        ij = np.array([i, j])
        direction = np.array([-dy, dx])

        normal = (np.array([-dy, dx]) if np.dot(
            normalise_vector(ij - equal_energy), normalise_vector(direction)) >
                  0 else np.array([dy, -dx]))
        normal = normalise_vector(normal) / 30

        label_colour = (spectral_locus_colours
                        if is_string(spectral_locus_colours) else
                        spectral_locus_colours[index])
        axes.plot(
            (i, i + normal[0] * 0.75), (j, j + normal[1] * 0.75),
            color=label_colour)

        axes.plot(i, j, 'o', color=label_colour)

        axes.text(
            i + normal[0],
            j + normal[1],
            label,
            clip_on=True,
            ha='left' if normal[0] >= 0 else 'right',
            va='center',
            fontdict={'size': 'small'})

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

    return render(**kwargs)
Esempio n. 24
0
def plot_ellipses_MacAdam1942_in_chromaticity_diagram(
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        method='CIE 1931',
        chromaticity_diagram_clipping=False,
        ellipse_parameters=None,
        **kwargs):
    """
    Plots *MacAdam (1942) Ellipses (Observer PGN)* in the
    *Chromaticity Diagram* according to given method.

    Parameters
    ----------
    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.
    chromaticity_diagram_clipping : bool, optional,
        Whether to clip the *Chromaticity Diagram* colours with the ellipses.
    ellipse_parameters : dict or array_like, optional
        Parameters for the :class:`Ellipse` class, ``ellipse_parameters`` can
        be either a single dictionary applied to all the ellipses with same
        settings or a sequence of dictionaries with different settings for each
        ellipse.

    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
    --------
    >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram()  # doctest: +SKIP

    .. image:: ../_static/\
Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram
    """

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

    _figure, axes = artist(**settings)

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

    ellipses_coefficients = ellipses_MacAdam1942(method=method)

    if chromaticity_diagram_clipping:
        diagram_clipping_path_x = []
        diagram_clipping_path_y = []
        for coefficients in ellipses_coefficients:
            coefficients = np.copy(coefficients)

            coefficients[2:4] /= 2

            x, y = tsplit(
                point_at_angle_on_ellipse(
                    np.linspace(0, 360, 36),
                    coefficients,
                ))
            diagram_clipping_path_x.append(x)
            diagram_clipping_path_y.append(y)

        diagram_clipping_path = np.rollaxis(
            np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3)
        diagram_clipping_path = Path.make_compound_path_from_polys(
            diagram_clipping_path).vertices
        settings.update({'diagram_clipping_path': diagram_clipping_path})

    chromaticity_diagram_callable(**settings)

    ellipse_settings_collection = [{
        'color': COLOUR_STYLE_CONSTANTS.colour.cycle[4],
        'alpha': 0.4,
        'edgecolor': COLOUR_STYLE_CONSTANTS.colour.cycle[1],
        'linewidth': colour_style()['lines.linewidth']
    } for _ellipses_coefficient in ellipses_coefficients]

    if ellipse_parameters is not None:
        if not isinstance(ellipse_parameters, dict):
            assert len(ellipse_parameters) == len(ellipses_coefficients), (
                'Multiple ellipse parameters defined, but they do not match '
                'the ellipses count!')

        for i, ellipse_settings in enumerate(ellipse_settings_collection):
            if isinstance(ellipse_parameters, dict):
                ellipse_settings.update(ellipse_parameters)
            else:
                ellipse_settings.update(ellipse_parameters[i])

    for i, coefficients in enumerate(ellipses_coefficients):
        x_c, y_c, a_a, a_b, theta_e = coefficients
        ellipse = Ellipse((x_c, y_c), a_a, a_b, theta_e,
                          **ellipse_settings_collection[i])
        axes.add_artist(ellipse)

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

    return render(**settings)
Esempio n. 25
0
def plot_chromaticity_diagram_colours(
        samples=256,
        diagram_opacity=1.0,
        diagram_clipping_path=None,
        cmfs='CIE 1931 2 Degree Standard Observer',
        method='CIE 1931',
        **kwargs):
    """
    Plots the *Chromaticity Diagram* colours according to given method.

    Parameters
    ----------
    samples : numeric, optional
        Samples count on one axis.
    diagram_opacity : numeric, optional
        Opacity of the *Chromaticity Diagram* colours.
    diagram_clipping_path : array_like, optional
        Path of points used to clip the *Chromaticity Diagram* colours.
    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.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
        :align: center
        :alt: plot_chromaticity_diagram_colours
    """

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

    _figure, axes = artist(**settings)

    method = method.upper()

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

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    ii, jj = np.meshgrid(np.linspace(0, 1, samples),
                         np.linspace(1, 0, samples))
    ij = tstack([ii, jj])

    # NOTE: Various values in the grid have potential to generate
    # zero-divisions, they could be avoided by perturbing the grid, e.g. adding
    # a small epsilon. It was decided instead to disable warnings.
    with suppress_warnings(python_warnings=True):
        if method == 'CIE 1931':
            XYZ = xy_to_XYZ(ij)
            spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
        elif method == 'CIE 1960 UCS':
            XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
            spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
        elif method == 'CIE 1976 UCS':
            XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))
            spectral_locus = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant),
                                       illuminant)
        else:
            raise ValueError(
                'Invalid method: "{0}", must be one of '
                '[\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\']'.format(
                    method))

    RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ, illuminant),
                            axis=-1)

    polygon = Polygon(spectral_locus if diagram_clipping_path is None else
                      diagram_clipping_path,
                      facecolor='none',
                      edgecolor='none')
    axes.add_patch(polygon)
    # Preventing bounding box related issues as per
    # https://github.com/matplotlib/matplotlib/issues/10529
    image = axes.imshow(RGB,
                        interpolation='bilinear',
                        extent=(0, 1, 0, 1),
                        clip_path=None,
                        alpha=diagram_opacity)
    image.set_clip_path(polygon)

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

    return render(**kwargs)
Esempio n. 26
0
def plot_chromaticity_diagram_colours(
        samples=256,
        diagram_opacity=1.0,
        diagram_clipping_path=None,
        cmfs='CIE 1931 2 Degree Standard Observer',
        method='CIE 1931',
        **kwargs):
    """
    Plots the *Chromaticity Diagram* colours according to given method.

    Parameters
    ----------
    samples : numeric, optional
        Samples count on one axis.
    diagram_opacity : numeric, optional
        Opacity of the *Chromaticity Diagram* colours.
    diagram_clipping_path : array_like, optional
        Path of points used to clip the *Chromaticity Diagram* colours.
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
        :align: center
        :alt: plot_chromaticity_diagram_colours
    """

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

    _figure, axes = artist(**settings)

    method = method.upper()

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

    illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint

    ii, jj = np.meshgrid(
        np.linspace(0, 1, samples), np.linspace(1, 0, samples))
    ij = tstack([ii, jj])
    # Avoiding zero division in later colour transformations.
    ij = np.where(ij == 0, EPSILON, ij)

    if method == 'CIE 1931':
        XYZ = xy_to_XYZ(ij)
        spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
    elif method == 'CIE 1960 UCS':
        XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
        spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
    elif method == 'CIE 1976 UCS':
        XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))
        spectral_locus = Luv_to_uv(
            XYZ_to_Luv(cmfs.values, illuminant), illuminant)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format(
                method))

    RGB = normalise_maximum(
        XYZ_to_plotting_colourspace(XYZ, illuminant), axis=-1)

    polygon = Polygon(
        spectral_locus
        if diagram_clipping_path is None else diagram_clipping_path,
        facecolor='none',
        edgecolor='none')
    axes.add_patch(polygon)
    # Preventing bounding box related issues as per
    # https://github.com/matplotlib/matplotlib/issues/10529
    image = axes.imshow(
        RGB,
        interpolation='bilinear',
        extent=(0, 1, 0, 1),
        clip_path=None,
        alpha=diagram_opacity)
    image.set_clip_path(polygon)

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

    return render(**kwargs)
Esempio n. 27
0
def plot_colour_quality_bars(specifications,
                             labels=True,
                             hatching=None,
                             hatching_repeat=2,
                             **kwargs):
    """
    Plots the colour quality data of given illuminants or light sources colour
    quality specifications.

    Parameters
    ----------
    specifications : array_like
        Array of illuminants or light sources colour quality specifications.
    labels : bool, optional
        Add labels above bars.
    hatching : bool or None, optional
        Use hatching for the bars.
    hatching_repeat : int, optional
        Hatching pattern repeat.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.quality.plot_colour_quality_bars`,
        :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,
    ...                     LIGHT_SOURCES_SDS, SpectralShape)
    >>> 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)
    >>> plot_colour_quality_bars([cqs_i, cqs_l])  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png
        :align: center
        :alt: plot_colour_quality_bars
    """

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

    _figure, axes = artist(**settings)

    bar_width = 0.5
    y_ticks_interval = 10
    count_s, count_Q_as = len(specifications), 0
    patterns = cycle(COLOUR_STYLE_CONSTANTS.hatch.patterns)
    if hatching is None:
        hatching = False if count_s == 1 else True
    for i, specification in enumerate(specifications):
        Q_a, Q_as, colorimetry_data = (specification.Q_a, specification.Q_as,
                                       specification.colorimetry_data)

        count_Q_as = len(Q_as)
        colours = ([[1] * 3] + [
            np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1)
            for x in colorimetry_data[0]
        ])

        x = (i + np.arange(0, (count_Q_as + 1) * (count_s + 1), (count_s + 1),
                           dtype=DEFAULT_FLOAT_DTYPE)) * bar_width
        y = [s[1].Q_a for s in sorted(Q_as.items(), key=lambda s: s[0])]
        y = np.array([Q_a] + list(y))

        bars = axes.bar(x,
                        np.abs(y),
                        color=colours,
                        width=bar_width,
                        edgecolor=COLOUR_STYLE_CONSTANTS.colour.dark,
                        label=specification.name)

        hatches = ([next(patterns) * hatching_repeat] *
                   (count_Q_as + 1) if hatching else np.where(
                       y < 0, next(patterns), None).tolist())

        for j, bar in enumerate(bars.patches):
            bar.set_hatch(hatches[j])

        if labels:
            label_rectangles(
                ['{0:.1f}'.format(y_v) for y_v in y],
                bars,
                rotation='horizontal' if count_s == 1 else 'vertical',
                offset=(0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000,
                        0.025),
                text_size=-5 / 7 * count_s + 12.5,
                axes=axes)

    axes.axhline(y=100,
                 color=COLOUR_STYLE_CONSTANTS.colour.dark,
                 linestyle='--')

    axes.set_xticks(
        (np.arange(0, (count_Q_as + 1) * (count_s + 1), (count_s + 1),
                   dtype=DEFAULT_FLOAT_DTYPE) * bar_width +
         (count_s * bar_width / 2)), ['Qa'] +
        ['Q{0}'.format(index + 1) for index in range(0, count_Q_as + 1, 1)])
    axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval))

    aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2))
    bounding_box = (-bar_width, ((count_Q_as + 1) * (count_s + 1)) / 2, 0, 120)

    settings = {
        'axes': axes,
        'aspect': aspect,
        'bounding_box': bounding_box,
        'legend': hatching,
        'title': 'Colour Quality',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 28
0
def plot_chromaticity_diagram(cmfs='CIE 1931 2 Degree Standard Observer',
                              show_diagram_colours=True,
                              show_spectral_locus=True,
                              method='CIE 1931',
                              **kwargs):
    """
    Plots the *Chromaticity Diagram* according to given method.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    show_diagram_colours : bool, optional
        Whether to display the *Chromaticity Diagram* background colours.
    show_spectral_locus : bool, optional
        Whether to display the *Spectral Locus*.
    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_spectral_locus`,
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram_colours`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram.png
        :align: center
        :alt: plot_chromaticity_diagram
    """

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

    _figure, axes = artist(**settings)

    method = method.upper()

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

    if show_diagram_colours:
        settings = {'axes': axes, 'method': method}
        settings.update(kwargs)
        settings['standalone'] = False

        plot_chromaticity_diagram_colours(**settings)

    if show_spectral_locus:
        settings = {'axes': axes, 'method': method}
        settings.update(kwargs)
        settings['standalone'] = False

        plot_spectral_locus(**settings)

    if method == 'CIE 1931':
        x_label, y_label = 'CIE x', 'CIE y'
    elif method == 'CIE 1960 UCS':
        x_label, y_label = 'CIE u', 'CIE v'
    elif method == 'CIE 1976 UCS':
        x_label, y_label = 'CIE u\'', 'CIE v\'',
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format(
                method))

    title = '{0} Chromaticity Diagram - {1}'.format(method, cmfs.strict_name)

    settings.update({
        'axes': axes,
        'standalone': True,
        'bounding_box': (0, 1, 0, 1),
        'title': title,
        'x_label': x_label,
        'y_label': y_label,
    })
    settings.update(kwargs)

    return render(**settings)
Esempio n. 29
0
def plot_spectral_locus(cmfs='CIE 1931 2 Degree Standard Observer',
                        spectral_locus_colours=None,
                        spectral_locus_labels=None,
                        method='CIE 1931',
                        **kwargs):
    """
    Plots the *Spectral Locus* according to given method.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions defining the
        *Spectral Locus*.
    spectral_locus_colours : array_like or unicode, optional
        *Spectral Locus* colours, if ``spectral_locus_colours`` is set to
        *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    spectral_locus_labels : array_like, optional
        Array of wavelength labels used to customise which labels will be drawn
        around the spectral locus. Passing an empty array will result in no
        wavelength labels being drawn.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_spectral_locus(spectral_locus_colours='RGB')  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Spectral_Locus.png
        :align: center
        :alt: plot_spectral_locus
    """

    if spectral_locus_colours is None:
        spectral_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark

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

    _figure, axes = artist(**settings)

    method = method.upper()

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

    illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint

    wavelengths = cmfs.wavelengths
    equal_energy = np.array([1 / 3] * 2)

    if method == 'CIE 1931':
        ij = XYZ_to_xy(cmfs.values, illuminant)
        labels = ((390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600,
                   620, 700)
                  if spectral_locus_labels is None else spectral_locus_labels)
    elif method == 'CIE 1960 UCS':
        ij = UCS_to_uv(XYZ_to_UCS(cmfs.values))
        labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540,
                   550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680)
                  if spectral_locus_labels is None else spectral_locus_labels)
    elif method == 'CIE 1976 UCS':
        ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant)
        labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540,
                   550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680)
                  if spectral_locus_labels is None else spectral_locus_labels)
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format(
                method))

    pl_ij = tstack([
        np.linspace(ij[0][0], ij[-1][0], 20),
        np.linspace(ij[0][1], ij[-1][1], 20)
    ]).reshape(-1, 1, 2)
    sl_ij = np.copy(ij).reshape(-1, 1, 2)

    if spectral_locus_colours.upper() == 'RGB':
        spectral_locus_colours = normalise_maximum(
            XYZ_to_plotting_colourspace(cmfs.values), axis=-1)

        if method == 'CIE 1931':
            XYZ = xy_to_XYZ(pl_ij)
        elif method == 'CIE 1960 UCS':
            XYZ = xy_to_XYZ(UCS_uv_to_xy(pl_ij))
        elif method == 'CIE 1976 UCS':
            XYZ = xy_to_XYZ(Luv_uv_to_xy(pl_ij))
        purple_line_colours = normalise_maximum(
            XYZ_to_plotting_colourspace(XYZ.reshape(-1, 3)), axis=-1)
    else:
        purple_line_colours = spectral_locus_colours

    for slp_ij, slp_colours in ((pl_ij, purple_line_colours),
                                (sl_ij, spectral_locus_colours)):
        line_collection = LineCollection(
            np.concatenate([slp_ij[:-1], slp_ij[1:]], axis=1),
            colors=slp_colours)
        axes.add_collection(line_collection)

    wl_ij = dict(tuple(zip(wavelengths, ij)))
    for label in labels:
        i, j = wl_ij[label]

        index = bisect.bisect(wavelengths, label)
        left = wavelengths[index - 1] if index >= 0 else wavelengths[index]
        right = (wavelengths[index]
                 if index < len(wavelengths) else wavelengths[-1])

        dx = wl_ij[right][0] - wl_ij[left][0]
        dy = wl_ij[right][1] - wl_ij[left][1]

        ij = np.array([i, j])
        direction = np.array([-dy, dx])

        normal = (np.array([-dy, dx]) if np.dot(
            normalise_vector(ij - equal_energy), normalise_vector(direction)) >
                  0 else np.array([dy, -dx]))
        normal = normalise_vector(normal) / 30

        label_colour = (spectral_locus_colours
                        if is_string(spectral_locus_colours) else
                        spectral_locus_colours[index])
        axes.plot(
            (i, i + normal[0] * 0.75), (j, j + normal[1] * 0.75),
            color=label_colour)

        axes.plot(i, j, 'o', color=label_colour)

        axes.text(
            i + normal[0],
            j + normal[1],
            label,
            clip_on=True,
            ha='left' if normal[0] >= 0 else 'right',
            va='center',
            fontdict={'size': 'small'})

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

    return render(**kwargs)
Esempio n. 30
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)
Esempio n. 31
0
def plot_hull_section_contour(
    hull: trimesh.Trimesh,  # type: ignore[name-defined]  # noqa
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    contour_colours: Optional[Union[ArrayLike, str]] = None,
    contour_opacity: Floating = 1,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the section contour of given *trimesh* hull along given axis and
    origin.

    Parameters
    ----------
    hull
        *Trimesh* hull.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    axis
        Axis the hull section will be normal to.
    origin
        Coordinate along ``axis`` at which to plot the hull section.
    normalise
        Whether to normalise ``axis`` to the extent of the hull along it.
    contour_colours
        Colours of the hull section contour, if ``contour_colours`` is set to
        *RGB*, the colours will be computed according to the corresponding
        coordinates.
    contour_opacity
        Opacity of the hull section contour.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour.models import RGB_COLOURSPACE_sRGB
    >>> from colour.utilities import is_trimesh_installed
    >>> vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64)
    >>> XYZ_vertices = RGB_to_XYZ(
    ...     vertices['position'] + 0.5,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.matrix_RGB_to_XYZ,
    ... )
    >>> if is_trimesh_installed:
    ...     import trimesh
    ...     hull = trimesh.Trimesh(XYZ_vertices, faces, process=False)
    ...     plot_hull_section_contour(hull, contour_colours='RGB')
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Hull_Section_Contour.png
        :align: center
        :alt: plot_hull_section_contour
    """

    hull = hull.copy()

    contour_colours = cast(
        Union[ArrayLike, str],
        optional(contour_colours, CONSTANTS_COLOUR_STYLE.colour.dark),
    )

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    convert_kwargs = optional(convert_kwargs, {})

    # Luminance / Lightness is re-ordered along "z-up" axis.
    with suppress_warnings(python_warnings=True):
        ijk_vertices = colourspace_model_axis_reorder(
            convert(hull.vertices, "CIE XYZ", model, **convert_kwargs), model)
        ijk_vertices = np.nan_to_num(ijk_vertices)
        ijk_vertices *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]

    hull.vertices = ijk_vertices

    plane = MAPPING_AXIS_TO_PLANE[axis]

    padding = 0.1 * np.mean(
        COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
    min_x = np.min(ijk_vertices[..., plane[0]]) - padding
    max_x = np.max(ijk_vertices[..., plane[0]]) + padding
    min_y = np.min(ijk_vertices[..., plane[1]]) - padding
    max_y = np.max(ijk_vertices[..., plane[1]]) + padding
    extent = (min_x, max_x, min_y, max_y)

    use_RGB_contour_colours = str(contour_colours).upper() == "RGB"
    section = hull_section(hull, axis, origin, normalise)
    if use_RGB_contour_colours:
        ijk_section = section / (
            COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
        XYZ_section = convert(
            colourspace_model_axis_reorder(ijk_section, model, "Inverse"),
            model,
            "CIE XYZ",
            **convert_kwargs,
        )
        contour_colours = np.clip(XYZ_to_plotting_colourspace(XYZ_section), 0,
                                  1)

    section = np.reshape(section[..., plane], (-1, 1, 2))
    line_collection = LineCollection(
        np.concatenate([section[:-1], section[1:]], axis=1),
        colors=contour_colours,
        alpha=contour_opacity,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
    )
    axes.add_collection(line_collection)

    settings = {
        "axes": axes,
        "bounding_box": extent,
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 32
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)
Esempio n. 33
0
def plot_RGB_colourspace_section(
    colourspace: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace,
                                                            str]]],
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    show_section_colours: Boolean = True,
    show_section_contour: Boolean = True,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given *RGB* colourspace section colours along given axis and origin.

    Parameters
    ----------
    colourspace
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
        type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    axis
        Axis the hull section will be normal to.
    origin
        Coordinate along ``axis`` at which to plot the hull section.
    normalise
        Whether to normalise ``axis`` to the extent of the hull along it.
    show_section_colours
        Whether to show the hull section colours.
    show_section_contour
        Whether to show the hull section contour.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`,
        :func:`colour.plotting.section.plot_hull_section_colours`
        :func:`colour.plotting.section.plot_hull_section_contour`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> from colour.utilities import is_trimesh_installed
    >>> if is_trimesh_installed:
    ...     plot_RGB_colourspace_section(
    ...         'sRGB', section_colours='RGB', section_opacity=0.15)
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_RGB_Colourspace_Section.png
        :align: center
        :alt: plot_RGB_colourspace_section
    """

    import trimesh

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    colourspace = cast(
        RGB_Colourspace,
        first_item(filter_RGB_colourspaces(colourspace).values()),
    )

    vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64)
    XYZ_vertices = RGB_to_XYZ(
        vertices["position"] + 0.5,
        colourspace.whitepoint,
        colourspace.whitepoint,
        colourspace.matrix_RGB_to_XYZ,
    )
    hull = trimesh.Trimesh(XYZ_vertices, faces, process=False)

    if show_section_colours:
        settings = {"axes": axes}
        settings.update(kwargs)
        settings["standalone"] = False

        plot_hull_section_colours(hull, model, axis, origin, normalise,
                                  **settings)

    if show_section_contour:
        settings = {"axes": axes}
        settings.update(kwargs)
        settings["standalone"] = False

        plot_hull_section_contour(hull, model, axis, origin, normalise,
                                  **settings)

    title = (f"{colourspace.name} Section - "
             f"{f'{origin * 100}%' if normalise else origin} - "
             f"{model}")

    plane = MAPPING_AXIS_TO_PLANE[axis]

    labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[as_int_array(
        colourspace_model_axis_reorder([0, 1, 2], model))]
    x_label, y_label = labels[plane[0]], labels[plane[1]]

    settings.update({
        "axes": axes,
        "standalone": True,
        "title": title,
        "x_label": x_label,
        "y_label": y_label,
    })
    settings.update(kwargs)

    return render(**settings)
Esempio n. 34
0
def plot_single_sd(sd,
                   cmfs='CIE 1931 2 Degree Standard Observer',
                   out_of_gamut_clipping=True,
                   modulate_colours_with_sd_amplitude=False,
                   equalize_sd_amplitude=False,
                   **kwargs):
    """
    Plots given spectral distribution.

    Parameters
    ----------
    sd : SpectralDistribution
        Spectral distribution to plot.
    out_of_gamut_clipping : bool, optional
        Whether to clip out of gamut colours otherwise, the colours will be
        offset by the absolute minimal colour leading to a rendering on
        gray background, less saturated and smoother.
    modulate_colours_with_sd_amplitude : bool, optional
        Whether to modulate the colours with the spectral distribution
        amplitude.
    equalize_sd_amplitude : bool, optional
        Whether to equalize the spectral distribution amplitude.
        Equalization occurs after the colours modulation thus setting both
        arguments to *True* will generate a spectrum strip where each
        wavelength colour is modulated by the spectral distribution amplitude.
        The usual 5% margin above the spectral distribution is also omitted.
    cmfs : unicode
        Standard observer colour matching functions used for spectrum creation.

    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.

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

    Examples
    --------
    >>> from colour import SpectralDistribution
    >>> data = {
    ...     500: 0.0651,
    ...     520: 0.0705,
    ...     540: 0.0772,
    ...     560: 0.0870,
    ...     580: 0.1128,
    ...     600: 0.1360
    ... }
    >>> sd = SpectralDistribution(data, name='Custom')
    >>> plot_single_sd(sd)  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Single_SD.png
        :align: center
        :alt: plot_single_sd
    """

    _figure, axes = artist(**kwargs)

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

    sd = sd.copy()
    sd.interpolator = LinearInterpolator
    wavelengths = cmfs.wavelengths[np.logical_and(
        cmfs.wavelengths >= max(min(cmfs.wavelengths), min(sd.wavelengths)),
        cmfs.wavelengths <= min(max(cmfs.wavelengths), max(sd.wavelengths)),
    )]
    values = sd[wavelengths]

    colours = XYZ_to_plotting_colourspace(
        wavelength_to_XYZ(wavelengths, cmfs),
        ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['E'],
        apply_encoding_cctf=False)

    if not out_of_gamut_clipping:
        colours += np.abs(np.min(colours))

    colours = normalise_maximum(colours)

    if modulate_colours_with_sd_amplitude:
        colours *= (values / np.max(values))[..., np.newaxis]

    colours = COLOUR_STYLE_CONSTANTS.colour.colourspace.encoding_cctf(colours)

    if equalize_sd_amplitude:
        values = np.ones(values.shape)

    margin = 0 if equalize_sd_amplitude else 0.05

    x_min, x_max = min(wavelengths), max(wavelengths)
    y_min, y_max = 0, max(values) + max(values) * margin

    polygon = Polygon(
        np.vstack([
            (x_min, 0),
            tstack([wavelengths, values]),
            (x_max, 0),
        ]),
        facecolor='none',
        edgecolor='none')
    axes.add_patch(polygon)

    padding = 0.1
    axes.bar(
        x=wavelengths - padding,
        height=max(values),
        width=1 + padding,
        color=colours,
        align='edge',
        clip_path=polygon)

    axes.plot(wavelengths, values, color=COLOUR_STYLE_CONSTANTS.colour.dark)

    settings = {
        'axes': axes,
        'bounding_box': (x_min, x_max, y_min, y_max),
        'title': '{0} - {1}'.format(sd.strict_name, cmfs.strict_name),
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Spectral Distribution',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 35
0
def plot_planckian_locus_in_chromaticity_diagram(
        illuminants=None,
        annotate_parameters=None,
        chromaticity_diagram_callable=plot_chromaticity_diagram,
        planckian_locus_callable=plot_planckian_locus,
        method='CIE 1931',
        **kwargs):
    """
    Plots the *Planckian Locus* and given illuminants in the
    *Chromaticity Diagram* according to given method.

    Parameters
    ----------
    illuminants : array_like, optional
        Factory illuminants to plot.
    annotate_parameters : dict or array_like, optional
        Parameters for the :func:`plt.annotate` definition, used to annotate
        the resulting chromaticity coordinates with their respective illuminant
        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
        illuminant.
    chromaticity_diagram_callable : callable, optional
        Callable responsible for drawing the *Chromaticity Diagram*.
    planckian_locus_callable : callable, optional
        Callable responsible for drawing the *Planckian Locus*.
    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.temperature.plot_planckian_locus`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C'])
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_\
Plot_Planckian_Locus_In_Chromaticity_Diagram.png
        :align: center
        :alt: plot_planckian_locus_in_chromaticity_diagram
    """

    cmfs = CMFS['CIE 1931 2 Degree Standard Observer']

    if illuminants is None:
        illuminants = ('A', 'B', 'C')

    illuminants = filter_passthrough(ILLUMINANTS.get(cmfs.name), illuminants)

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

    _figure, axes = artist(**settings)

    method = method.upper()

    settings = {'axes': axes, 'method': method}
    settings.update(kwargs)
    settings['standalone'] = False

    chromaticity_diagram_callable(**settings)

    planckian_locus_callable(**settings)

    if method == 'CIE 1931':

        def xy_to_ij(xy):
            """
            Converts given *CIE xy* chromaticity coordinates to *ij*
            chromaticity coordinates.
            """

            return xy

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

        def xy_to_ij(xy):
            """
            Converts given *CIE xy* chromaticity coordinates to *ij*
            chromaticity coordinates.
            """

            return UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy)))

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

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

    if annotate_parameters is not None:
        if not isinstance(annotate_parameters, dict):
            assert len(annotate_parameters) == len(illuminants), (
                'Multiple annotate parameters defined, but they do not match '
                'the illuminants 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, (illuminant, xy) in enumerate(illuminants.items()):
        ij = xy_to_ij(xy)

        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=illuminant)

        if annotate_settings_collection[i]['annotate']:
            annotate_settings = annotate_settings_collection[i]
            annotate_settings.pop('annotate')

            axes.annotate(illuminant, xy=ij, **annotate_settings)

    title = (('{0} Illuminants - Planckian Locus\n'
              '{1} Chromaticity Diagram - '
              'CIE 1931 2 Degree Standard Observer').format(
                  ', '.join(illuminants), method) if illuminants else
             ('Planckian Locus\n{0} Chromaticity Diagram - '
              'CIE 1931 2 Degree Standard Observer'.format(method)))

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

    return render(**settings)
Esempio n. 36
0
def plot_planckian_locus(planckian_locus_colours=None,
                         method='CIE 1931',
                         **kwargs):
    """
    Plots the *Planckian Locus* according to given method.

    Parameters
    ----------
    planckian_locus_colours : array_like or unicode, optional
        *Planckian Locus* colours.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Planckian_Locus.png
        :align: center
        :alt: plot_planckian_locus
    """

    if planckian_locus_colours is None:
        planckian_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark

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

    _figure, axes = artist(**settings)

    if method == 'CIE 1931':

        def uv_to_ij(uv):
            """
            Converts given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return UCS_uv_to_xy(uv)

        D_uv = 0.025
    elif method == 'CIE 1960 UCS':

        def uv_to_ij(uv):
            """
            Converts given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return uv

        D_uv = 0.025
    else:
        raise ValueError('Invalid method: "{0}", must be one of '
                         '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method))

    start, end = 1667, 100000
    CCT = np.arange(start, end + 250, 250)
    CCT_D_uv = tstack([CCT, np.zeros(CCT.shape)])
    ij = uv_to_ij(CCT_to_uv(CCT_D_uv, 'Robertson 1968'))

    axes.plot(ij[..., 0], ij[..., 1], color=planckian_locus_colours)

    for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000):
        i0, j0 = uv_to_ij(CCT_to_uv(np.array([i, -D_uv]), 'Robertson 1968'))
        i1, j1 = uv_to_ij(CCT_to_uv(np.array([i, D_uv]), 'Robertson 1968'))
        axes.plot((i0, i1), (j0, j1), color=planckian_locus_colours)
        axes.annotate(
            '{0}K'.format(i),
            xy=(i0, j0),
            xytext=(0, -10),
            textcoords='offset points',
            size='x-small')

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

    return render(**settings)
Esempio n. 37
0
def plot_multi_cmfs(cmfs=None, **kwargs):
    """
    Plots given colour matching functions.

    Parameters
    ----------
    cmfs : array_like, optional
        Colour matching functions to plot.

    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
    --------
    >>> cmfs = ('CIE 1931 2 Degree Standard Observer',
    ...         'CIE 1964 10 Degree Standard Observer')
    >>> plot_multi_cmfs(cmfs)  # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Multi_CMFS.png
        :align: center
        :alt: plot_multi_cmfs
    """

    if cmfs is None:
        cmfs = ('CIE 1931 2 Degree Standard Observer',
                'CIE 1964 10 Degree Standard Observer')

    cmfs = filter_cmfs(cmfs).values()

    _figure, axes = artist(**kwargs)

    axes.axhline(color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--')

    x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
    for i, cmfs_i in enumerate(cmfs):
        for j, RGB in enumerate([(1, 0, 0), (0, 1, 0), (0, 0, 1)]):
            RGB = [reduce(lambda y, _: y * 0.5, range(i), x) for x in RGB]
            values = cmfs_i.values[:, j]

            shape = cmfs_i.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))

            axes.plot(
                cmfs_i.wavelengths,
                values,
                color=RGB,
                label='{0} - {1}'.format(cmfs_i.strict_labels[j],
                                         cmfs_i.strict_name))

    bounding_box = (min(x_limit_min), max(x_limit_max),
                    min(y_limit_min) - abs(min(y_limit_min)) * 0.05,
                    max(y_limit_max) + abs(max(y_limit_max)) * 0.05)
    title = '{0} - Colour Matching Functions'.format(', '.join(
        [cmfs_i.strict_name for cmfs_i in cmfs]))

    settings = {
        'axes': axes,
        'bounding_box': bounding_box,
        'legend': True,
        'title': title,
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Tristimulus Values',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 38
0
def plot_planckian_locus(planckian_locus_colours=None,
                         method='CIE 1931',
                         **kwargs):
    """
    Plots the *Planckian Locus* according to given method.

    Parameters
    ----------
    planckian_locus_colours : array_like or unicode, optional
        *Planckian Locus* colours.
    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.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Planckian_Locus.png
        :align: center
        :alt: plot_planckian_locus
    """

    if planckian_locus_colours is None:
        planckian_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark

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

    _figure, axes = artist(**settings)

    if method == 'CIE 1931':

        def uv_to_ij(uv):
            """
            Converts given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return UCS_uv_to_xy(uv)

        D_uv = 0.025
    elif method == 'CIE 1960 UCS':

        def uv_to_ij(uv):
            """
            Converts given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return uv

        D_uv = 0.025
    else:
        raise ValueError('Invalid method: "{0}", must be one of '
                         '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method))

    start, end = 1667, 100000
    CCT = np.arange(start, end + 250, 250)
    CCT_D_uv = tstack([CCT, np.zeros(CCT.shape)])
    ij = uv_to_ij(CCT_to_uv(CCT_D_uv, 'Robertson 1968'))

    axes.plot(ij[..., 0], ij[..., 1], color=planckian_locus_colours)

    for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000):
        i0, j0 = uv_to_ij(CCT_to_uv(np.array([i, -D_uv]), 'Robertson 1968'))
        i1, j1 = uv_to_ij(CCT_to_uv(np.array([i, D_uv]), 'Robertson 1968'))
        axes.plot((i0, i1), (j0, j1), color=planckian_locus_colours)
        axes.annotate('{0}K'.format(i),
                      xy=(i0, j0),
                      xytext=(0, -10),
                      textcoords='offset points',
                      size='x-small')

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

    return render(**settings)
Esempio n. 39
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)
Esempio n. 40
0
def plot_multi_colour_checkers(colour_checkers=None, **kwargs):
    """
    Plots and compares given colour checkers.

    Parameters
    ----------
    colour_checkers : array_like, optional
        Color checker names, must be less than or equal to 2 names.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {:func:`colour.plotting.artist`,
        :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_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005'])
    ... # doctest: +SKIP

    .. image:: ../_static/Plotting_Plot_Multi_Colour_Checkers.png
        :align: center
        :alt: plot_multi_colour_checkers
    """

    if colour_checkers is None:
        colour_checkers = ['ColorChecker 1976', 'ColorChecker 2005']
    else:
        assert len(colour_checkers) <= 2, (
            'Only two colour checkers can be compared at a time!')

    colour_checkers = filter_colour_checkers(colour_checkers).values()

    _figure, axes = artist(**kwargs)

    compare_swatches = len(colour_checkers) == 2

    colour_swatches = []
    colour_checker_names = []
    for colour_checker in colour_checkers:
        colour_checker_names.append(colour_checker.name)
        for label, xyY in colour_checker.data.items():
            XYZ = xyY_to_XYZ(xyY)
            RGB = XYZ_to_plotting_colourspace(XYZ, colour_checker.illuminant)
            colour_swatches.append(
                ColourSwatch(label.title(), np.clip(np.ravel(RGB), 0, 1)))

    if compare_swatches:
        colour_swatches = [
            swatch
            for pairs in zip(colour_swatches[0:len(colour_swatches) // 2],
                             colour_swatches[len(colour_swatches) // 2:])
            for swatch in pairs
        ]

    background_colour = '0.1'
    width = height = 1.0
    spacing = 0.25
    columns = 6

    settings = {
        'axes': axes,
        'width': width,
        'height': height,
        'spacing': spacing,
        'columns': columns,
        'text_parameters': {
            'size': 8
        },
        'background_colour': background_colour,
        'compare_swatches': 'Stacked' if compare_swatches else None,
    }
    settings.update(kwargs)
    settings['standalone'] = False

    plot_multi_colour_swatches(colour_swatches, **settings)

    axes.text(
        0.5,
        0.005,
        '{0} - {1} - Colour Rendition Chart'.format(
            ', '.join(colour_checker_names),
            COLOUR_STYLE_CONSTANTS.colour.colourspace.name),
        transform=axes.transAxes,
        color=COLOUR_STYLE_CONSTANTS.colour.bright,
        ha='center',
        va='bottom')

    settings.update({
        'axes': axes,
        'standalone': True,
        'title': ', '.join(colour_checker_names),
    })

    return render(**settings)
Esempio n. 41
0
def plot_multi_cmfs(cmfs=None, **kwargs):
    """
    Plots given colour matching functions.

    Parameters
    ----------
    cmfs : array_like, optional
        Colour matching functions to plot.

    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
    --------
    >>> cmfs = ('CIE 1931 2 Degree Standard Observer',
    ...         'CIE 1964 10 Degree Standard Observer')
    >>> plot_multi_cmfs(cmfs)  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, \
<matplotlib.axes._subplots.AxesSubplot object at 0x...>)

    .. image:: ../_static/Plotting_Plot_Multi_CMFS.png
        :align: center
        :alt: plot_multi_cmfs
    """

    if cmfs is None:
        cmfs = ('CIE 1931 2 Degree Standard Observer',
                'CIE 1964 10 Degree Standard Observer')

    cmfs = filter_cmfs(cmfs).values()

    _figure, axes = artist(**kwargs)

    axes.axhline(color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--')

    x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], []
    for i, cmfs_i in enumerate(cmfs):
        for j, RGB in enumerate([(1, 0, 0), (0, 1, 0), (0, 0, 1)]):
            RGB = [reduce(lambda y, _: y * 0.5, range(i), x) for x in RGB]
            values = cmfs_i.values[:, j]

            shape = cmfs_i.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))

            axes.plot(cmfs_i.wavelengths,
                      values,
                      color=RGB,
                      label='{0} - {1}'.format(cmfs_i.strict_labels[j],
                                               cmfs_i.strict_name))

    bounding_box = (min(x_limit_min), max(x_limit_max),
                    min(y_limit_min) - abs(min(y_limit_min)) * 0.05,
                    max(y_limit_max) + abs(max(y_limit_max)) * 0.05)
    title = '{0} - Colour Matching Functions'.format(', '.join(
        [cmfs_i.strict_name for cmfs_i in cmfs]))

    settings = {
        'axes': axes,
        'bounding_box': bounding_box,
        'legend': True,
        'title': title,
        'x_label': 'Wavelength $\\lambda$ (nm)',
        'y_label': 'Tristimulus Values',
    }
    settings.update(kwargs)

    return render(**settings)
Esempio n. 42
0
def plot_spectra_ANSIIESTM3018(
        specification: ColourQuality_Specification_ANSIIESTM3018,
        **kwargs: Any) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot 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
        *ANSI/IES TM-30-18 Colour Rendition Report* specification.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`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: Dict[str, Any] = dict(kwargs)

    _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",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
    )
    axes.plot(
        specification.sd_test.wavelengths,
        specification.sd_test.values / Y_test,
        "#F05046",
        label="Test",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
    )
    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)
Esempio n. 43
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)
Esempio n. 44
0
def plot_16_bin_bars(
    values: ArrayLike,
    label_template: str,
    x_ticker: Boolean = False,
    label_orientation: Union[Literal["Horizontal", "Vertical"],
                             str] = "Vertical",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the 16 bin bars for given values according to
    *ANSI/IES TM-30-18 Colour Rendition Report*.

    Parameters
    ----------
    values
        Values to generate the bin bars for.
    label_template
        Template to format the labels.
    x_ticker
        Whether to show the *X* axis ticker and the associated label.
    label_orientation
        Orientation of the labels.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_16_bin_bars(np.arange(16), '{0}')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)
    """

    values = as_float_array(values)

    label_orientation = validate_method(label_orientation,
                                        ["Horizontal", "Vertical"])

    _figure, axes = artist(**kwargs)

    bar_count = len(_COLOURS_BIN_BAR)
    axes.bar(
        np.arange(bar_count) + 1,
        values,
        color=_COLOURS_BIN_BAR,
        width=1,
        edgecolor="black",
        linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.set_xlim(0.5, bar_count + 0.5)
    if x_ticker:
        axes.set_xticks(np.arange(1, bar_count + 1))
        axes.set_xlabel("Hue-Angle Bin (j)")
    else:
        axes.set_xticks([])

    label_orientation = label_orientation.lower()
    value_max = np.max(values)
    for i, value in enumerate(values):
        if label_orientation == "vertical":
            va, vo = (("bottom", value_max * 0.15) if value > 0 else
                      ("top", -value_max * 0.15))
            axes.annotate(
                label_template.format(value),
                xy=(i + 1, value + vo),
                rotation=90,
                fontsize="xx-small",
                ha="center",
                va=va,
                zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,
            )
        elif label_orientation == "horizontal":
            va, vo = (("bottom", value_max * 0.025) if value < 90 else
                      ("top", -value_max * 0.025))
            axes.annotate(
                label_template.format(value),
                xy=(i + 1, value + vo),
                fontsize="xx-small",
                ha="center",
                va=va,
                zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,
            )

    return render(**kwargs)
Esempio n. 45
0
def plot_chromaticity_diagram(cmfs='CIE 1931 2 Degree Standard Observer',
                              show_diagram_colours=True,
                              show_spectral_locus=True,
                              method='CIE 1931',
                              **kwargs):
    """
    Plots the *Chromaticity Diagram* according to given method.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions used for
        *Chromaticity Diagram* bounds.
    show_diagram_colours : bool, optional
        Whether to display the *Chromaticity Diagram* background colours.
    show_spectral_locus : bool, optional
        Whether to display the *Spectral Locus*.
    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_spectral_locus`,
        :func:`colour.plotting.diagrams.plot_chromaticity_diagram_colours`,
        :func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definitions.

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

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

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram.png
        :align: center
        :alt: plot_chromaticity_diagram
    """

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

    figure, axes = artist(**settings)

    method = method.upper()

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

    if show_diagram_colours:
        settings = {'axes': axes, 'method': method}
        settings.update(kwargs)
        settings['standalone'] = False

        plot_chromaticity_diagram_colours(**settings)

    if show_spectral_locus:
        settings = {'axes': axes, 'method': method}
        settings.update(kwargs)
        settings['standalone'] = False

        plot_spectral_locus(**settings)

    if method == 'CIE 1931':
        x_label, y_label = 'CIE x', 'CIE y'
    elif method == 'CIE 1960 UCS':
        x_label, y_label = 'CIE u', 'CIE v'
    elif method == 'CIE 1976 UCS':
        x_label, y_label = 'CIE u\'', 'CIE v\'',
    else:
        raise ValueError(
            'Invalid method: "{0}", must be one of '
            '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format(
                method))

    title = '{0} Chromaticity Diagram - {1}'.format(method, cmfs.strict_name)

    settings.update({
        'axes': axes,
        'standalone': True,
        'bounding_box': (0, 1, 0, 1),
        'title': title,
        'x_label': x_label,
        'y_label': y_label,
    })
    settings.update(kwargs)

    return render(**settings)
Esempio n. 46
0
def fraunhofer_lines_plot(image,
                          measured_Fraunhofer_lines,
                          show_luminance_spd=True,
                          **kwargs):
    """
    Plots the *Fraunhofer* lines of given image.

    Parameters
    ----------
    image : unicode
        Path to read the image from.
    measured_Fraunhofer_lines : dict, optional
        Measured *Fraunhofer* lines locations.
    show_luminance_spd : bool, optional
        Whether to show the *Luminance* sd for given image.

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

    settings = {}
    settings.update(kwargs)

    figure, axes = artist(**settings)

    spectrum = calibrated_RGB_spectrum(image, FRAUNHOFER_LINES_PUBLISHED,
                                       measured_Fraunhofer_lines)

    wavelengths = spectrum.wavelengths
    input, output = wavelengths[0], wavelengths[-1]

    width, height = figure.get_size_inches()
    ratio = width / height
    height = (output - input) * (1 / ratio)

    axes.imshow(COLOUR_STYLE_CONSTANTS.colour.colourspace.cctf_encoding(
        np.clip(spectrum.values[np.newaxis, ...], 0, 1)),
                extent=[input, output, 0, height])

    sd = luminance_sd(spectrum).normalise(height - height * 0.05)
    if show_luminance_spd:
        axes.plot(sd.wavelengths, sd.values, color='black', linewidth=1)

    fraunhofer_wavelengths = np.array(
        sorted(FRAUNHOFER_LINES_PUBLISHED.values()))
    fraunhofer_wavelengths = fraunhofer_wavelengths[np.where(
        np.logical_and(fraunhofer_wavelengths >= input,
                       fraunhofer_wavelengths <= output))]
    fraunhofer_lines_labels = [
        tuple(FRAUNHOFER_LINES_PUBLISHED.keys())[tuple(
            FRAUNHOFER_LINES_PUBLISHED.values()).index(i)]
        for i in fraunhofer_wavelengths
    ]

    y0, y1 = 0, height * .5
    for i, label in enumerate(fraunhofer_lines_labels):

        # Trick to cluster siblings *Fraunhofer* lines.
        from_siblings = False
        for pattern, (first, siblings,
                      specific_label) in FRAUNHOFER_LINES_CLUSTERED.items():
            if re.match(pattern, label):
                if label in siblings:
                    from_siblings = True

                label = specific_label
                break

        power = bisect.bisect_left(wavelengths, fraunhofer_wavelengths[i])
        scale = (sd[wavelengths[power]] / height)

        is_large_line = label in FRAUNHOFER_LINES_NOTABLE

        axes.vlines(fraunhofer_wavelengths[i],
                    y0,
                    y1 * scale,
                    linewidth=1 if is_large_line else 1)

        axes.vlines(fraunhofer_wavelengths[i],
                    y0,
                    height,
                    linewidth=1 if is_large_line else 1,
                    alpha=0.075)

        if not from_siblings:
            axes.text(fraunhofer_wavelengths[i],
                      y1 * scale + (y1 * 0.025),
                      label,
                      clip_on=True,
                      ha='center',
                      va='bottom',
                      fontdict={'size': 'large' if is_large_line else 'small'})

    r = lambda x: int(x / 100) * 100
    plt.xticks(np.arange(r(input), r(output * 1.5), 20))
    plt.yticks([])

    settings = {
        'title': 'The Solar Spectrum - Fraunhofer Lines',
        'bounding_box': [input, output, 0, height],
        'x_label': u'Wavelength λ (nm)',
        'y_label': False,
    }
    settings.update(**kwargs)

    return render(**settings)
Esempio n. 47
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)
def fraunhofer_lines_plot(image,
                          measured_Fraunhofer_lines,
                          show_luminance_spd=True,
                          **kwargs):
    """
    Plots the *Fraunhofer* lines of given image.

    Parameters
    ----------
    image : unicode
        Path to read the image from.
    measured_Fraunhofer_lines : dict, optional
        Measured *Fraunhofer* lines locations.
    show_luminance_spd : bool, optional
        Whether to show the *Luminance* sd for given image.

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

    settings = {}
    settings.update(kwargs)

    figure, axes = artist(**settings)

    spectrum = calibrated_RGB_spectrum(image, FRAUNHOFER_LINES_PUBLISHED,
                                       measured_Fraunhofer_lines)

    wavelengths = spectrum.wavelengths
    input, output = wavelengths[0], wavelengths[-1]

    width, height = figure.get_size_inches()
    ratio = width / height
    height = (output - input) * (1 / ratio)

    axes.imshow(
        COLOUR_STYLE_CONSTANTS.colour.colourspace.encoding_cctf(
            np.clip(spectrum.values[np.newaxis, ...], 0, 1)),
        extent=[input, output, 0, height])

    sd = luminance_sd(spectrum).normalise(height - height * 0.05)
    if show_luminance_spd:
        axes.plot(sd.wavelengths, sd.values, color='black', linewidth=1)

    fraunhofer_wavelengths = np.array(
        sorted(FRAUNHOFER_LINES_PUBLISHED.values()))
    fraunhofer_wavelengths = fraunhofer_wavelengths[np.where(
        np.logical_and(fraunhofer_wavelengths >= input,
                       fraunhofer_wavelengths <= output))]
    fraunhofer_lines_labels = [
        tuple(FRAUNHOFER_LINES_PUBLISHED.keys())[tuple(
            FRAUNHOFER_LINES_PUBLISHED.values()).index(i)]
        for i in fraunhofer_wavelengths
    ]

    y0, y1 = 0, height * .5
    for i, label in enumerate(fraunhofer_lines_labels):

        # Trick to cluster siblings *Fraunhofer* lines.
        from_siblings = False
        for pattern, (first, siblings,
                      specific_label) in FRAUNHOFER_LINES_CLUSTERED.items():
            if re.match(pattern, label):
                if label in siblings:
                    from_siblings = True

                label = specific_label
                break

        power = bisect.bisect_left(wavelengths, fraunhofer_wavelengths[i])
        scale = (sd[wavelengths[power]] / height)

        is_large_line = label in FRAUNHOFER_LINES_NOTABLE

        axes.vlines(
            fraunhofer_wavelengths[i],
            y0,
            y1 * scale,
            linewidth=1 if is_large_line else 1)

        axes.vlines(
            fraunhofer_wavelengths[i],
            y0,
            height,
            linewidth=1 if is_large_line else 1,
            alpha=0.075)

        if not from_siblings:
            axes.text(
                fraunhofer_wavelengths[i],
                y1 * scale + (y1 * 0.025),
                label,
                clip_on=True,
                ha='center',
                va='bottom',
                fontdict={'size': 'large' if is_large_line else 'small'})

    r = lambda x: int(x / 100) * 100
    plt.xticks(np.arange(r(input), r(output * 1.5), 20))
    plt.yticks([])

    settings = {
        'title': 'The Solar Spectrum - Fraunhofer Lines',
        'bounding_box': [input, output, 0, height],
        'x_label': u'Wavelength λ (nm)',
        'y_label': False,
    }
    settings.update(**kwargs)

    return render(**settings)