Exemple #1
0
    def test_n_dimensional_UCS_uv_to_xy(self):
        """
        Tests :func:`colour.models.cie_ucs.UCS_uv_to_xy` definition
        n-dimensional arrays support.
        """

        uv = np.array([0.15085309, 0.32355314])
        xy = np.array([0.26414771, 0.37770001])
        np.testing.assert_almost_equal(UCS_uv_to_xy(uv), xy, decimal=7)

        uv = np.tile(uv, (6, 1))
        xy = np.tile(xy, (6, 1))
        np.testing.assert_almost_equal(UCS_uv_to_xy(uv), xy, decimal=7)

        uv = np.reshape(uv, (2, 3, 2))
        xy = np.reshape(xy, (2, 3, 2))
        np.testing.assert_almost_equal(UCS_uv_to_xy(uv), xy, decimal=7)
Exemple #2
0
def chromaticity_diagram_colours_CIE1960UCS(
        samples=4096,
        cmfs='CIE 1931 2 Degree Standard Observer',
        antialiasing=True):
    """
    Plots the *CIE 1960 UCS Chromaticity Diagram* colours.

    Parameters
    ----------
    samples : numeric, optional
        Samples count on one axis.
    cmfs : unicode, optional
        Standard observer colour matching functions used for diagram bounds.
    antialiasing : bool, optional
        Whether to apply anti-aliasing to the image.

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

    Returns
    -------
    Figure
        Current figure or None.

    Examples
    --------
    >>> chromaticity_diagram_colours_CIE1960UCS()  # doctest: +SKIP
    """

    cmfs = get_cmfs(cmfs)

    illuminant = DEFAULT_PLOTTING_ILLUMINANT

    triangulation = Delaunay(UCS_to_uv(XYZ_to_UCS(cmfs.values)),
                             qhull_options='Qu QJ')
    xx, yy = np.meshgrid(np.linspace(0, 1, samples),
                         np.linspace(1, 0, samples))
    xy = tstack((xx, yy))
    mask = (triangulation.find_simplex(xy) < 0).astype(DEFAULT_FLOAT_DTYPE)
    if antialiasing:
        kernel = np.array([
            [0, 1, 0],
            [1, 2, 1],
            [0, 1, 0],
        ]).astype(DEFAULT_FLOAT_DTYPE)
        kernel /= np.sum(kernel)
        mask = convolve(mask, kernel)

    mask = 1 - mask[:, :, np.newaxis]

    XYZ = xy_to_XYZ(UCS_uv_to_xy(xy))

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

    return np.dstack([RGB, mask])
Exemple #3
0
    def test_UCS_uv_to_xy(self):
        """
        Tests :func:`colour.models.cie_ucs.UCS_uv_to_xy` definition.
        """

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            np.array([0.37720213, 0.33413508])),
                                       np.array([0.54369555, 0.32107941]),
                                       decimal=7)

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            np.array([0.14536327, 0.35328046])),
                                       np.array([0.29777734, 0.48246445]),
                                       decimal=7)

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            np.array([0.16953602, 0.20026156])),
                                       np.array([0.18582823, 0.14633764]),
                                       decimal=7)
Exemple #4
0
    def test_UCS_uv_to_xy(self):
        """
        Tests :func:`colour.models.cie_ucs.UCS_uv_to_xy` definition.
        """

        np.testing.assert_almost_equal(
            UCS_uv_to_xy(np.array([0.15085309, 0.32355314])),
            np.array([0.26414771, 0.37770001]),
            decimal=7)

        np.testing.assert_almost_equal(
            UCS_uv_to_xy(np.array([0.31125983, 0.34646688])),
            np.array([0.50453169, 0.37440000]),
            decimal=7)

        np.testing.assert_almost_equal(
            UCS_uv_to_xy(np.array([0.30069388, 0.33863231])),
            np.array([0.47670437, 0.35789998]),
            decimal=7)
Exemple #5
0
    def test_UCS_uv_to_xy(self):
        """
        Tests :func:`colour.models.cie_ucs.UCS_uv_to_xy` definition.
        """

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            (0.2033733344733139,
             0.3140500001549052)), (0.32207410281368043, 0.33156550013623537),
                                       decimal=7)

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            (0.20873418102926322,
             0.32457285063327812)), (0.3439000000209443, 0.35650000010917804),
                                       decimal=7)

        np.testing.assert_almost_equal(UCS_uv_to_xy(
            (0.25585459629500179,
             0.34952813701502972)), (0.4474327628361858, 0.40749796251018744),
                                       decimal=7)
Exemple #6
0
    def test_nan_UCS_uv_to_xy(self):
        """
        Tests :func:`colour.models.cie_ucs.UCS_uv_to_xy` definition nan
        support.
        """

        cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
        cases = set(permutations(cases * 3, r=2))
        for case in cases:
            uv = np.array(case)
            UCS_uv_to_xy(uv)
Exemple #7
0
    def CCT_D_uv_to_plotting_colourspace(CCT_D_uv):
        """
        Convert given *uv* chromaticity coordinates to the default plotting
        colourspace.
        """

        return normalise_maximum(
            XYZ_to_plotting_colourspace(
                xy_to_XYZ(UCS_uv_to_xy(CCT_to_uv(CCT_D_uv,
                                                 "Robertson 1968")))),
            axis=-1,
        )
Exemple #8
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)
Exemple #9
0
def CIE_1960_UCS_chromaticity_diagram_colours_plot(
        surface=1,
        samples=4096,
        cmfs='CIE 1931 2 Degree Standard Observer',
        **kwargs):
    """
    Plots the *CIE 1960 UCS Chromaticity Diagram* colours.

    Parameters
    ----------
    surface : numeric, optional
        Generated markers surface.
    samples : numeric, optional
        Samples count on one axis.
    cmfs : unicode, optional
        Standard observer colour matching functions used for diagram bounds.
    \**kwargs : dict, optional
        Keywords arguments.

    Returns
    -------
    bool
        Definition success.

    Examples
    --------
    >>> CIE_1960_UCS_chromaticity_diagram_colours_plot()  # doctest: +SKIP
    True
    """

    if is_scipy_installed(raise_exception=True):
        from scipy.spatial import Delaunay

        settings = {'figure_size': (64, 64)}
        settings.update(kwargs)

        canvas(**settings)

        cmfs = get_cmfs(cmfs)

        illuminant = DEFAULT_PLOTTING_ILLUMINANT

        triangulation = Delaunay(UCS_to_uv(XYZ_to_UCS(cmfs.values)),
                                 qhull_options='QJ')
        xx, yy = np.meshgrid(np.linspace(0, 1, samples),
                             np.linspace(0, 1, samples))
        xy = tstack((xx, yy))
        xy = xy[triangulation.find_simplex(xy) > 0]

        XYZ = xy_to_XYZ(UCS_uv_to_xy(xy))

        RGB = normalise(XYZ_to_sRGB(XYZ, illuminant), axis=-1)

        x_dot, y_dot = tsplit(xy)

        pylab.scatter(x_dot, y_dot, color=RGB, s=surface)

        settings.update({
            'x_ticker': False,
            'y_ticker': False,
            'bounding_box': (0, 1, 0, 1),
            'bbox_inches': 'tight',
            'pad_inches': 0
        })
        settings.update(kwargs)

        ax = matplotlib.pyplot.gca()
        matplotlib.pyplot.setp(ax, frame_on=False)

        boundaries(**settings)
        decorate(**settings)

        return display(**settings)
Exemple #10
0
def planckian_locus_CIE_1931_chromaticity_diagram_plot(
        illuminants=None,
        **kwargs):
    """
    Plots the planckian locus and given illuminants in
    *CIE 1931 Chromaticity Diagram*.

    Parameters
    ----------
    illuminants : array_like, optional
        Factory illuminants to plot.
    \**kwargs : dict, optional
        Keywords arguments.

    Returns
    -------
    Figure
        Current figure or None.

    Raises
    ------
    KeyError
        If one of the given illuminant is not found in the factory illuminants.

    Examples
    --------
    >>> ils = ['A', 'B', 'C']
    >>> planckian_locus_CIE_1931_chromaticity_diagram_plot(
    ...     ils)  # doctest: +SKIP
    """

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

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

    settings = {
        'title': ('{0} Illuminants - Planckian Locus\n'
                  'CIE 1931 Chromaticity Diagram - '
                  'CIE 1931 2 Degree Standard Observer').format(
            ', '.join(illuminants))
        if illuminants else
        ('Planckian Locus\nCIE 1931 Chromaticity Diagram - '
         'CIE 1931 2 Degree Standard Observer'),
        'standalone': False}
    settings.update(kwargs)

    CIE_1931_chromaticity_diagram_plot(**settings)

    start, end = 1667, 100000
    xy = np.array([UCS_uv_to_xy(CCT_to_uv(x, 0, method='Robertson 1968'))
                   for x in np.arange(start, end + 250, 250)])

    pylab.plot(xy[..., 0], xy[..., 1], color='black', linewidth=2)

    for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000):
        x0, y0 = UCS_uv_to_xy(CCT_to_uv(i, -0.025, method='Robertson 1968'))
        x1, y1 = UCS_uv_to_xy(CCT_to_uv(i, 0.025, method='Robertson 1968'))
        pylab.plot((x0, x1), (y0, y1), color='black', linewidth=2)
        pylab.annotate('{0}K'.format(i),
                       xy=(x0, y0),
                       xytext=(0, -10),
                       color='black',
                       textcoords='offset points',
                       size='x-small')

    for illuminant in illuminants:
        xy = ILLUMINANTS.get(cmfs.name).get(illuminant)
        if xy is None:
            raise KeyError(
                ('Illuminant "{0}" not found in factory illuminants: '
                 '"{1}".').format(illuminant,
                                  sorted(ILLUMINANTS.get(cmfs.name).keys())))

        pylab.plot(xy[0], xy[1], 'o', color='white', linewidth=2)

        pylab.annotate(illuminant,
                       xy=(xy[0], xy[1]),
                       xytext=(-50, 30),
                       color='black',
                       textcoords='offset points',
                       arrowprops=dict(arrowstyle='->',
                                       connectionstyle='arc3, rad=-0.2'))

    settings.update({
        'x_tighten': True,
        'y_tighten': True,
        'limits': (-0.1, 0.9, -0.1, 0.9),
        'standalone': True})
    settings.update(kwargs)

    boundaries(**settings)
    decorate(**settings)

    return display(**settings)
Exemple #11
0
def UVW_to_XYZ(
        UVW,
        illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']):
    """
    Converts *CIE 1964 U\\*V\\*W\\** colourspace to *CIE XYZ* tristimulus
    values.

    Parameters
    ----------
    UVW : array_like
        *CIE 1964 U\\*V\\*W\\** colourspace array.
    illuminant : array_like, optional
        Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY*
        colourspace array.

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

    Warning
    -------
    The input domain and output range of that definition are non standard!

    Notes
    -----

    +----------------+-----------------------+-----------------+
    | **Domain**     | **Scale - Reference** | **Scale - 1**   |
    +================+=======================+=================+
    | ``UVW``        | ``U`` : [-100, 100]   | ``U`` : [-1, 1] |
    |                |                       |                 |
    |                | ``V`` : [-100, 100]   | ``V`` : [-1, 1] |
    |                |                       |                 |
    |                | ``W`` : [0, 100]      | ``W`` : [0, 1]  |
    +----------------+-----------------------+-----------------+
    | ``illuminant`` | [0, 1]                | [0, 1]          |
    +----------------+-----------------------+-----------------+

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

    References
    ----------
    :cite:`Wikipedia2008a`

    Examples
    --------
    >>> import numpy as np
    >>> UVW = np.array([94.55035725, 11.55536523, 40.54757405])
    >>> UVW_to_XYZ(UVW)
    array([ 20.654008,  12.197225,   5.136952])
    """

    U, V, W = tsplit(to_domain_100(UVW))

    u_0, v_0 = tsplit(xy_to_UCS_uv(xyY_to_xy(illuminant)))

    Y = ((W + 17) / 25)**3
    u = U / (13 * W) + u_0
    v = V / (13 * W) + v_0

    x, y = tsplit(UCS_uv_to_xy(tstack([u, v])))

    XYZ = xyY_to_XYZ(tstack([x, y, Y]))

    return from_range_100(XYZ)
Exemple #12
0
def plot_spectral_locus(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    spectral_locus_colours: Optional[Union[ArrayLike, str]] = None,
    spectral_locus_opacity: Floating = 1,
    spectral_locus_labels: Optional[Sequence] = None,
    method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"],
                  str] = "CIE 1931",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Spectral Locus* according to given method.

    Parameters
    ----------
    cmfs
        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.
    spectral_locus_colours
        Colours of the *Spectral Locus*, if ``spectral_locus_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    spectral_locus_opacity
        Opacity of the *Spectral Locus*.
    spectral_locus_labels
        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
        *Chromaticity Diagram* method.

    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_spectral_locus(spectral_locus_colours='RGB')  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

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

    method = validate_method(method,
                             ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"])

    spectral_locus_colours = optional(spectral_locus_colours,
                                      CONSTANTS_COLOUR_STYLE.colour.dark)

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

    _figure, axes = artist(**settings)

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

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

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

    if method == "cie 1931":
        ij = XYZ_to_xy(cmfs.values, illuminant)
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    390,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    540,
                    560,
                    580,
                    600,
                    620,
                    700,
                ),
            ),
        )
    elif method == "cie 1960 ucs":
        ij = UCS_to_uv(XYZ_to_UCS(cmfs.values))
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    420,
                    440,
                    450,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    530,
                    540,
                    550,
                    560,
                    570,
                    580,
                    590,
                    600,
                    610,
                    620,
                    630,
                    645,
                    680,
                ),
            ),
        )
    elif method == "cie 1976 ucs":
        ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant)
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    420,
                    440,
                    450,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    530,
                    540,
                    550,
                    560,
                    570,
                    580,
                    590,
                    600,
                    610,
                    620,
                    630,
                    645,
                    680,
                ),
            ),
        )

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

    purple_line_colours: Optional[Union[ArrayLike, str]]
    if str(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(
            np.reshape(XYZ, (-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,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
        )
        axes.add_collection(line_collection)

    wl_ij = dict(zip(wavelengths, ij))
    for label in labels:
        ij_l = wl_ij.get(label)

        if ij_l is None:
            continue

        ij_l = as_float_array([ij_l])
        i, j = tsplit(ij_l)

        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]

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

        normal = (np.array([-dy, dx]) if np.dot(
            normalise_vector(ij_l - 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]  # type: ignore[index]
        )
        axes.plot(
            (i, i + normal[0] * 0.75),
            (j, j + normal[1] * 0.75),
            color=label_colour,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
        )

        axes.plot(
            i,
            j,
            "o",
            color=label_colour,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
        )

        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"},
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_label,
        )

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

    return render(**kwargs)
Exemple #13
0
def plot_chromaticity_diagram_colours(
    samples: Integer = 256,
    diagram_colours: Optional[Union[ArrayLike, str]] = None,
    diagram_opacity: Floating = 1,
    diagram_clipping_path: Optional[ArrayLike] = None,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"],
                  str] = "CIE 1931",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Chromaticity Diagram* colours according to given method.

    Parameters
    ----------
    samples
        Samples count on one axis when computing the *Chromaticity Diagram*
        colours.
    diagram_colours
        Colours of the *Chromaticity Diagram*, if ``diagram_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        coordinates.
    diagram_opacity
        Opacity of the *Chromaticity Diagram*.
    diagram_clipping_path
        Path of points used to clip the *Chromaticity Diagram* colours.
    cmfs
        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
        *Chromaticity Diagram* method.

    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_chromaticity_diagram_colours(diagram_colours='RGB')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

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

    method = validate_method(method,
                             ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"])

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

    _figure, axes = artist(**settings)

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

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

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    if method == "cie 1931":
        spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
    elif method == "cie 1960 ucs":
        spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
    elif method == "cie 1976 ucs":
        spectral_locus = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant),
                                   illuminant)

    use_RGB_diagram_colours = str(diagram_colours).upper() == "RGB"
    if use_RGB_diagram_colours:
        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)
            elif method == "cie 1960 ucs":
                XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
            elif method == "cie 1976 ucs":
                XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))

        diagram_colours = 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" if use_RGB_diagram_colours else np.hstack(
            [diagram_colours, diagram_opacity]),
        edgecolor="none" if use_RGB_diagram_colours else np.hstack(
            [diagram_colours, diagram_opacity]),
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.add_patch(polygon)

    if use_RGB_diagram_colours:
        # Preventing bounding box related issues as per
        # https://github.com/matplotlib/matplotlib/issues/10529
        image = axes.imshow(
            diagram_colours,
            interpolation="bilinear",
            extent=(0, 1, 0, 1),
            clip_path=None,
            alpha=diagram_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
        )
        image.set_clip_path(polygon)

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

    return render(**kwargs)
Exemple #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 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)
Exemple #15
0
def CIE_1960_UCS_chromaticity_diagram_colours_plot(
        surface=1.25,
        spacing=0.00075,
        cmfs='CIE 1931 2 Degree Standard Observer',
        **kwargs):
    """
    Plots the *CIE 1960 UCS Chromaticity Diagram* colours.

    Parameters
    ----------
    surface : numeric, optional
        Generated markers surface.
    spacing : numeric, optional
        Spacing between markers.
    cmfs : unicode, optional
        Standard observer colour matching functions used for diagram bounds.
    \*\*kwargs : \*\*
        Keywords arguments.

    Returns
    -------
    bool
        Definition success.

    Examples
    --------
    >>> CIE_1960_UCS_chromaticity_diagram_colours_plot()  # doctest: +SKIP
    True
    """

    cmfs, name = get_cmfs(cmfs), cmfs

    illuminant = ILLUMINANTS.get('CIE 1931 2 Degree Standard Observer').get(
        'E')

    UVWs = [XYZ_to_UCS(value) for key, value in cmfs]

    u, v = tuple(zip(*([UCS_to_uv(x) for x in UVWs])))

    path = matplotlib.path.Path(tuple(zip(u, v)))
    x_dot, y_dot, colours = [], [], []
    for i in np.arange(0, 1, spacing):
        for j in np.arange(0, 1, spacing):
            if path.contains_path(matplotlib.path.Path([[i, j], [i, j]])):
                x_dot.append(i)
                y_dot.append(j)

                XYZ = xy_to_XYZ(UCS_uv_to_xy((i, j)))
                RGB = normalise(XYZ_to_sRGB(XYZ, illuminant))

                colours.append(RGB)

    pylab.scatter(x_dot, y_dot, color=colours, s=surface)

    settings = {
        'no_ticks': True,
        'bounding_box': [0, 1, 0, 1],
        'bbox_inches': 'tight',
        'pad_inches': 0
    }
    settings.update(kwargs)

    bounding_box(**settings)
    aspect(**settings)

    return display(**settings)
Exemple #16
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)
Exemple #17
0
def planckian_locus_CIE_1931_chromaticity_diagram_plot(
        illuminants=None,
        **kwargs):
    """
    Plots the planckian locus and given illuminants in
    *CIE 1931 Chromaticity Diagram*.

    Parameters
    ----------
    illuminants : array_like, optional
        Factory illuminants to plot.
    \*\*kwargs : \*\*
        Keywords arguments.

    Returns
    -------
    bool
        Definition success.

    Raises
    ------
    KeyError
        If one of the given illuminant is not found in the factory illuminants.

    Examples
    --------
    >>> ils = ['A', 'B', 'C']
    >>> planckian_locus_CIE_1931_chromaticity_diagram_plot(ils)  # noqa  # doctest: +SKIP
    True
    """

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

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

    settings = {
        'title': ('{0} Illuminants - Planckian Locus\n'
                  'CIE 1931 Chromaticity Diagram - '
                  'CIE 1931 2 Degree Standard Observer').format(
            ', '.join(illuminants))
        if illuminants else
        ('Planckian Locus\nCIE 1931 Chromaticity Diagram - '
         'CIE 1931 2 Degree Standard Observer'),
        'standalone': False}
    settings.update(kwargs)

    if not CIE_1931_chromaticity_diagram_plot(**settings):
        return

    start, end = 1667, 100000
    x, y = tuple(zip(*[UCS_uv_to_xy(CCT_to_uv(x, 0, cmfs=cmfs))
                       for x in np.arange(start, end + 250, 250)]))

    pylab.plot(x, y, color='black', linewidth=2)

    for i in [1667, 2000, 2500, 3000, 4000, 6000, 10000]:
        x0, y0 = UCS_uv_to_xy(CCT_to_uv(i, -0.025, cmfs=cmfs))
        x1, y1 = UCS_uv_to_xy(CCT_to_uv(i, 0.025, cmfs=cmfs))
        pylab.plot([x0, x1], [y0, y1], color='black', linewidth=2)
        pylab.annotate('{0}K'.format(i),
                       xy=(x0, y0),
                       xytext=(0, -10),
                       textcoords='offset points',
                       size='x-small')

    for illuminant in illuminants:
        xy = ILLUMINANTS.get(cmfs.name).get(illuminant)
        if xy is None:
            raise KeyError(
                ('Illuminant "{0}" not found in factory illuminants: '
                 '"{1}".').format(illuminant,
                                  sorted(ILLUMINANTS.get(cmfs.name).keys())))

        pylab.plot(xy[0], xy[1], 'o', color='white', linewidth=2)

        pylab.annotate(illuminant,
                       xy=(xy[0], xy[1]),
                       xytext=(-50, 30),
                       textcoords='offset points',
                       arrowprops=dict(arrowstyle='->',
                                       connectionstyle='arc3, rad=-0.2'))

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

    return display(**settings)
Exemple #18
0
def CIE_1960_UCS_chromaticity_diagram_colours_plot(
        surface=1,
        samples=4096,
        cmfs='CIE 1931 2 Degree Standard Observer',
        **kwargs):
    """
    Plots the *CIE 1960 UCS Chromaticity Diagram* colours.

    Parameters
    ----------
    surface : numeric, optional
        Generated markers surface.
    samples : numeric, optional
        Samples count on one axis.
    cmfs : unicode, optional
        Standard observer colour matching functions used for diagram bounds.

    Other Parameters
    ----------------
    \**kwargs : dict, optional
        {:func:`boundaries`, :func:`canvas`, :func:`decorate`,
        :func:`display`},
        Please refer to the documentation of the previously listed definitions.

    Returns
    -------
    Figure
        Current figure or None.

    Examples
    --------
    >>> CIE_1960_UCS_chromaticity_diagram_colours_plot()  # doctest: +SKIP
    """

    settings = {'figure_size': (64, 64)}
    settings.update(kwargs)

    canvas(**settings)

    cmfs = get_cmfs(cmfs)

    illuminant = DEFAULT_PLOTTING_ILLUMINANT

    triangulation = Delaunay(UCS_to_uv(XYZ_to_UCS(cmfs.values)),
                             qhull_options='QJ')
    xx, yy = np.meshgrid(np.linspace(0, 1, samples),
                         np.linspace(0, 1, samples))
    xy = tstack((xx, yy))
    xy = xy[triangulation.find_simplex(xy) > 0]

    XYZ = xy_to_XYZ(UCS_uv_to_xy(xy))

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

    x_dot, y_dot = tsplit(xy)

    pylab.scatter(x_dot, y_dot, color=RGB, s=surface)

    settings.update({
        'x_ticker': False,
        'y_ticker': False,
        'bounding_box': (0, 1, 0, 1)
    })
    settings.update(kwargs)

    ax = matplotlib.pyplot.gca()
    matplotlib.pyplot.setp(ax, frame_on=False)

    boundaries(**settings)
    decorate(**settings)

    return display(**settings)
Exemple #19
0
def planckian_locus_chromaticity_diagram_plot_CIE1931(
        illuminants=None,
        chromaticity_diagram_callable_CIE1931=(
            chromaticity_diagram_plot_CIE1931),
        **kwargs):
    """
    Plots the planckian locus and given illuminants in
    *CIE 1931 Chromaticity Diagram*.

    Parameters
    ----------
    illuminants : array_like, optional
        Factory illuminants to plot.
    chromaticity_diagram_callable_CIE1931 : callable, optional
        Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*.

    Other Parameters
    ----------------
    \**kwargs : dict, optional
        {:func:`colour.plotting.render`},
        Please refer to the documentation of the previously listed definition.
    show_diagram_colours : bool, optional
        {:func:`colour.plotting.chromaticity_diagram_plot_CIE1931`},
        Whether to display the chromaticity diagram background colours.
    use_cached_diagram_colours : bool, optional
        {:func:`colour.plotting.chromaticity_diagram_plot_CIE1931`},
        Whether to used the cached chromaticity diagram background colours
        image.

    Returns
    -------
    Figure
        Current figure or None.

    Raises
    ------
    KeyError
        If one of the given illuminant is not found in the factory illuminants.

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

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

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

    settings = {
        'title': ('{0} Illuminants - Planckian Locus\n'
                  'CIE 1931 Chromaticity Diagram - '
                  'CIE 1931 2 Degree Standard Observer').format(
                      ', '.join(illuminants)) if illuminants else
        ('Planckian Locus\nCIE 1931 Chromaticity Diagram - '
         'CIE 1931 2 Degree Standard Observer'),
        'standalone':
        False
    }
    settings.update(kwargs)

    chromaticity_diagram_callable_CIE1931(**settings)

    start, end = 1667, 100000
    xy = np.array(
        [UCS_uv_to_xy(CCT_to_uv(x, 'Robertson 1968', D_uv=0))
         for x in np.arange(start, end + 250, 250)])  # yapf: disable

    pylab.plot(xy[..., 0], xy[..., 1], color='black', linewidth=1)

    for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000):
        x0, y0 = UCS_uv_to_xy(CCT_to_uv(i, 'Robertson 1968', D_uv=-0.025))
        x1, y1 = UCS_uv_to_xy(CCT_to_uv(i, 'Robertson 1968', D_uv=0.025))
        pylab.plot((x0, x1), (y0, y1), color='black', linewidth=1)
        pylab.annotate('{0}K'.format(i),
                       xy=(x0, y0),
                       xytext=(0, -10),
                       color='black',
                       textcoords='offset points',
                       size='x-small')

    for illuminant in illuminants:
        xy = ILLUMINANTS.get(cmfs.name).get(illuminant)
        if xy is None:
            raise KeyError(
                ('Illuminant "{0}" not found in factory illuminants: '
                 '"{1}".').format(illuminant,
                                  sorted(ILLUMINANTS[cmfs.name].keys())))

        pylab.plot(xy[0], xy[1], 'o', color='white', linewidth=1)

        pylab.annotate(illuminant,
                       xy=(xy[0], xy[1]),
                       xytext=(-50, 30),
                       color='black',
                       textcoords='offset points',
                       arrowprops=dict(arrowstyle='->',
                                       connectionstyle='arc3, rad=-0.2'))

    settings.update({
        'x_tighten': True,
        'y_tighten': True,
        'limits': (-0.1, 0.9, -0.1, 0.9),
        'standalone': True
    })
    settings.update(kwargs)

    return render(**settings)