예제 #1
0
def camera(**kwargs: Union[KwargsCamera, Any]) -> Tuple[plt.Figure, plt.Axes]:
    """
    Set the camera settings.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.common.KwargsCamera`},
        See the documentation of the previously listed class.

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

    figure = cast(plt.Figure, kwargs.get("figure", plt.gcf()))
    axes = cast(plt.Axes, kwargs.get("axes", plt.gca()))

    settings = Structure(**{
        "camera_aspect": "equal",
        "elevation": None,
        "azimuth": None
    })
    settings.update(kwargs)

    if settings.camera_aspect == "equal":
        uniform_axes3d(axes=axes)

    axes.view_init(elev=settings.elevation, azim=settings.azimuth)

    return figure, axes
예제 #2
0
def contour_centroid(contour: ArrayLike) -> Tuple[Floating, Floating]:
    """
    Return the centroid of given contour.

    Parameters
    ----------
    contour
        Contour to return the centroid of.

    Returns
    -------
    :class:`tuple`
        Contour centroid.

    Notes
    -----
    -   A :class:`tuple` class is returned instead of a :class:`ndarray` class
        for convenience with *OpenCV*.

    Examples
    --------
    >>> contour = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
    >>> contour_centroid(contour)
    (0.5, 0.5)
    """

    moments = cv2.moments(contour)
    centroid = (
        moments["m10"] / moments["m00"],
        moments["m01"] / moments["m00"],
    )

    return cast(Tuple[Floating, Floating], centroid)
예제 #3
0
def plot_single_illuminant_sd(
    illuminant: Union[SpectralDistribution, str],
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given single illuminant spectral distribution.

    Parameters
    ----------
    illuminant
        Illuminant to plot. ``illuminant`` can be of any type or form supported
        by the :func:`colour.plotting.filter_illuminants` definition.
    cmfs
        Standard observer colour matching functions used for computing the
        spectrum domain and colours. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.

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

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

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

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

    .. image:: ../_static/Plotting_Plot_Single_Illuminant_SD.png
        :align: center
        :alt: plot_single_illuminant_sd
    """

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

    title = f"Illuminant {illuminant} - {cmfs.strict_name}"

    illuminant = first_item(filter_illuminants(illuminant).values())

    settings: Dict[str, Any] = {"title": title, "y_label": "Relative Power"}
    settings.update(kwargs)

    return plot_single_sd(illuminant, **settings)
예제 #4
0
    def __init__(
        self,
        matrix: Optional[ArrayLike] = None,
        offset: Optional[ArrayLike] = None,
        *args: Any,
        **kwargs: Any,
    ):
        super().__init__(*args, **kwargs)

        # TODO: Remove pragma when https://github.com/python/mypy/issues/3004
        # is resolved.
        self._matrix: NDArray = np.diag(ones(4))
        self.matrix = cast(ArrayLike,
                           optional(matrix,
                                    self._matrix))  # type: ignore[assignment]
        self._offset: NDArray = zeros(4)
        self.offset = cast(ArrayLike,
                           optional(offset,
                                    self._offset))  # type: ignore[assignment]
예제 #5
0
def luminous_flux(
    sd: SpectralDistribution,
    lef: Optional[SpectralDistribution] = None,
    K_m: Floating = CONSTANT_K_M,
) -> Floating:
    """
    Return the *luminous flux* for given spectral distribution using given
    luminous efficiency function.

    Parameters
    ----------
    sd
        test spectral distribution
    lef
        :math:`V(\\lambda)` luminous efficiency function, default to the
        *CIE 1924 Photopic Standard Observer*.
    K_m
        :math:`lm\\cdot W^{-1}` maximum photopic luminous efficiency.

    Returns
    -------
    :class:`numpy.floating`
        Luminous flux.

    References
    ----------
    :cite:`Wikipedia2003b`

    Examples
    --------
    >>> from colour import SDS_LIGHT_SOURCES
    >>> sd = SDS_LIGHT_SOURCES['Neodimium Incandescent']
    >>> luminous_flux(sd)  # doctest: +ELLIPSIS
    23807.6555273...
    """

    lef = cast(
        SpectralDistribution,
        optional(lef,
                 SDS_LEFS_PHOTOPIC["CIE 1924 Photopic Standard Observer"]),
    )

    lef = reshape_sd(
        lef,
        sd.shape,
        extrapolator_kwargs={
            "method": "Constant",
            "left": 0,
            "right": 0
        },
    )

    flux = K_m * np.trapz(lef.values * sd.values, sd.wavelengths)

    return as_float_scalar(flux)
예제 #6
0
    def __init__(
        self,
        interpolator: Optional[TypeInterpolator] = None,
        method: Union[Literal["Linear", "Constant"], str] = "Linear",
        left: Optional[Number] = None,
        right: Optional[Number] = None,
        dtype: Optional[Type[DTypeNumber]] = None,
    ):
        dtype = cast(Type[DTypeNumber], optional(dtype, DEFAULT_FLOAT_DTYPE))

        self._interpolator: TypeInterpolator = NullInterpolator(
            np.array([-np.inf, np.inf]), np.array([-np.inf, np.inf]))
        self.interpolator = optional(interpolator, self._interpolator)
        self._method: Union[Literal["Linear", "Constant"], str] = "Linear"
        self.method = cast(
            Union[Literal["Linear", "Constant"], str],
            optional(method, self._method),
        )
        self._right: Optional[Number] = None
        self.right = right
        self._left: Optional[Number] = None
        self.left = left

        self._dtype: Type[DTypeNumber] = dtype
예제 #7
0
def plot_single_cmfs(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given colour matching functions.

    Parameters
    ----------
    cmfs
        Colour matching functions to plot. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.

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

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

    Examples
    --------
    >>> plot_single_cmfs('CIE 1931 2 Degree Standard Observer')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Single_CMFS.png
        :align: center
        :alt: plot_single_cmfs
    """

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

    settings: Dict[str, Any] = {
        "title": f"{cmfs.strict_name} - Colour Matching Functions"
    }
    settings.update(kwargs)

    return plot_multi_cmfs((cmfs, ), **settings)
예제 #8
0
    def test_read(self, sd: Optional[SpectralDistribution] = None):
        """
        Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.read`
        method.

        Parameters
        ----------
        sd
            Optional *IES TM-27-14* spectral distribution for read tests.
        """

        sd = cast(
            SpectralDistribution_IESTM2714,
            optional(
                sd,
                SpectralDistribution_IESTM2714(
                    os.path.join(RESOURCES_DIRECTORY,
                                 "Fluorescent.spdx")).read(),
            ),
        )

        sd_r = SpectralDistribution(FLUORESCENT_FILE_SPECTRAL_DATA)

        np.testing.assert_array_equal(sd_r.domain, sd.domain)
        np.testing.assert_almost_equal(sd_r.values, sd.values, decimal=7)

        test_read: List[Tuple[Dict,
                              Union[Header_IESTM2714,
                                    SpectralDistribution_IESTM2714]]] = [
                                        (FLUORESCENT_FILE_HEADER, sd.header),
                                        (FLUORESCENT_FILE_SPECTRAL_DESCRIPTION,
                                         sd),
                                    ]
        for test, read in test_read:
            for key, value in test.items():
                for specification in read.mapping.elements:
                    if key == specification.element:
                        self.assertEqual(
                            getattr(read, specification.attribute), value)
예제 #9
0
def sd_to_XYZ(
    sd: Union[ArrayLike, SpectralDistribution, MultiSpectralDistributions],
    cmfs: Optional[MultiSpectralDistributions] = None,
    illuminant: Optional[SpectralDistribution] = None,
    k: Optional[Number] = None,
    method: Union[Literal["ASTM E308", "Integration"], str] = "ASTM E308",
    **kwargs: Any,
) -> NDArray:
    """
    Convert given spectral distribution to *CIE XYZ* tristimulus values using
    given colour matching functions, illuminant and method.

    This placeholder docstring is replaced with the modified
    :func:`colour.sd_to_XYZ` definition docstring.
    """

    illuminant = cast(
        SpectralDistribution,
        optional(illuminant, SDS_ILLUMINANTS[_ILLUMINANT_DEFAULT]),
    )

    return colour.sd_to_XYZ(sd, cmfs, illuminant, k, method, **kwargs)
예제 #10
0
def chromatic_strength_function(
    theta: FloatingOrArrayLike, ) -> FloatingOrNDArray:
    """
    Define the chromatic strength function :math:`E_s(\\theta)` used to
    correct saturation scale as function of hue angle :math:`\\theta` in
    degrees.

    Parameters
    ----------
    theta
        Hue angle :math:`\\theta` in degrees.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.ndarray`
        Corrected saturation scale.

    Examples
    --------
    >>> h = 257.52322689806243
    >>> chromatic_strength_function(h)  # doctest: +ELLIPSIS
    1.2267869...
    """

    theta = np.radians(theta)

    E_s = cast(NDArray, 0.9394)
    E_s += -0.2478 * np.sin(1 * theta)
    E_s += -0.0743 * np.sin(2 * theta)
    E_s += +0.0666 * np.sin(3 * theta)
    E_s += -0.0186 * np.sin(4 * theta)
    E_s += -0.0055 * np.cos(1 * theta)
    E_s += -0.0521 * np.cos(2 * theta)
    E_s += -0.0573 * np.cos(3 * theta)
    E_s += -0.0061 * np.cos(4 * theta)

    return as_float(E_s)
예제 #11
0
def plot_colour_vector_graphic(
        specification: ColourQuality_Specification_ANSIIESTM3018,
        **kwargs: Any) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot *Color Vector Graphic* 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_vector_graphic(specification)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)
    """

    settings: Dict[str, Any] = dict(kwargs)
    settings["standalone"] = False

    # Background
    background_image = read_image(
        os.path.join(RESOURCES_DIRECTORY_ANSIIESTM3018, "CVG_Background.jpg"))
    _figure, axes = plot_image(
        background_image,
        imshow_kwargs={"extent": [-1.5, 1.5, -1.5, 1.5]},
        **settings,
    )

    # Lines dividing the hues in 16 equal parts along with bin numbers.
    axes.plot(0, 0, "+", color="#A6A6A6")
    for i in range(16):
        angle = 2 * np.pi * i / 16
        dx = np.cos(angle)
        dy = np.sin(angle)
        axes.plot(
            (0.15 * dx, 1.5 * dx),
            (0.15 * dy, 1.5 * dy),
            "--",
            color="#A6A6A6",
            lw=0.75,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
        )

        angle = 2 * np.pi * (i + 0.5) / 16
        axes.annotate(
            str(i + 1),
            color="#A6A6A6",
            ha="center",
            va="center",
            xy=(1.41 * np.cos(angle), 1.41 * np.sin(angle)),
            weight="bold",
            size=9,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,
        )

    # Circles.
    circle = plt.Circle(
        (0, 0),
        1,
        color="black",
        lw=1.25,
        fill=False,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
    )
    axes.add_artist(circle)
    for radius in [0.8, 0.9, 1.1, 1.2]:
        circle = plt.Circle(
            (0, 0),
            radius,
            color="white",
            lw=0.75,
            fill=False,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
        )
        axes.add_artist(circle)

    # -/+20% marks near the white circles.
    props = dict(ha="right", color="white", size=7)
    axes.annotate(
        "-20%",
        xy=(0, -0.8),
        va="bottom",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,
        **props,
    )
    axes.annotate(
        "+20%",
        xy=(0, -1.2),
        va="top",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,
        **props,
    )

    # Average "CAM02" h correlate for each bin, in radians.
    average_hues = np.radians([
        np.mean(
            as_float_array([
                cast(
                    Floating,
                    specification.colorimetry_data[1][i].CAM.h,
                ) for i in specification.bins[j]
            ])) for j in range(16)
    ])
    xy_reference = np.transpose(
        np.vstack([np.cos(average_hues),
                   np.sin(average_hues)]))

    # Arrow offsets as defined by the standard.
    offsets = (specification.averages_test - specification.averages_reference
               ) / specification.average_norms[:, np.newaxis]
    xy_test = xy_reference + offsets

    # Arrows.
    for i in range(16):
        axes.arrow(
            xy_reference[i, 0],
            xy_reference[i, 1],
            offsets[i, 0],
            offsets[i, 1],
            length_includes_head=True,
            width=0.005,
            head_width=0.04,
            linewidth=None,
            color=_COLOURS_BIN_ARROW[i],
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,
        )

    # Red (test) gamut shape.
    loop = np.append(xy_test, xy_test[0, np.newaxis], axis=0)
    axes.plot(
        loop[:, 0],
        loop[:, 1],
        "-",
        color="#F05046",
        lw=2,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
    )

    def corner_label_and_text(label: str, text: str, ha: str, va: str):
        """Draw a label and text in given corner."""

        x = -1.45 if ha == "left" else 1.45
        y = 1.45 if va == "top" else -1.45
        y_text = -15 if va == "top" else 15

        axes.annotate(
            text,
            xy=(x, y),
            color="black",
            ha=ha,
            va=va,
            weight="bold",
            size="larger",
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,
        )
        axes.annotate(
            label,
            xy=(x, y),
            color="black",
            xytext=(0, y_text),
            textcoords="offset points",
            ha=ha,
            va=va,
            size="small",
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,
        )

    corner_label_and_text("$R_f$", f"{specification.R_f:.0f}", "left", "top")
    corner_label_and_text("$R_g$", f"{specification.R_g:.0f}", "right", "top")
    corner_label_and_text("CCT", f"{specification.CCT:.0f} K", "left",
                          "bottom")
    corner_label_and_text("$D_{uv}$", f"{specification.D_uv:.4f}", "right",
                          "bottom")

    settings = {"standalone": True}
    settings.update(kwargs)

    return render(**settings)
예제 #12
0
def nadir_grid(
    limits: Optional[ArrayLike] = None,
    segments: Integer = 10,
    labels: Optional[Sequence[str]] = None,
    axes: Optional[plt.Axes] = None,
    **kwargs: Any,
) -> Tuple[NDArray, NDArray, NDArray]:
    """
    Return a grid on *CIE xy* plane made of quad geometric elements and its
    associated faces and edges colours. Ticks and labels are added to the
    given axes according to the extended grid settings.

    Parameters
    ----------
    limits
        Extended grid limits.
    segments
        Edge segments count for the extended grid.
    labels
        Axis labels.
    axes
        Axes to add the grid.

    Other Parameters
    ----------------
    grid_edge_alpha
        Grid edge opacity value such as `grid_edge_alpha = 0.5`.
    grid_edge_colours
        Grid edge colours array such as
        `grid_edge_colours = (0.25, 0.25, 0.25)`.
    grid_face_alpha
        Grid face opacity value such as `grid_face_alpha = 0.1`.
    grid_face_colours
        Grid face colours array such as
        `grid_face_colours = (0.25, 0.25, 0.25)`.
    ticks_and_label_location
        Location of the *X* and *Y* axis ticks and labels such as
        `ticks_and_label_location = ('-x', '-y')`.
    x_axis_colour
        *X* axis colour array such as `x_axis_colour = (0.0, 0.0, 0.0, 1.0)`.
    x_label_colour
        *X* axis label colour array such as
        `x_label_colour = (0.0, 0.0, 0.0, 0.85)`.
    x_ticks_colour
        *X* axis ticks colour array such as
        `x_ticks_colour = (0.0, 0.0, 0.0, 0.85)`.
    y_axis_colour
        *Y* axis colour array such as `y_axis_colour = (0.0, 0.0, 0.0, 1.0)`.
    y_label_colour
        *Y* axis label colour array such as
        `y_label_colour = (0.0, 0.0, 0.0, 0.85)`.
    y_ticks_colour
        *Y* axis ticks colour array such as
        `y_ticks_colour = (0.0, 0.0, 0.0, 0.85)`.

    Returns
    -------
    :class:`tuple`
        Grid quads, faces colours, edges colours.

    Examples
    --------
    >>> nadir_grid(segments=1)
    (array([[[-1.   , -1.   ,  0.   ],
            [ 1.   , -1.   ,  0.   ],
            [ 1.   ,  1.   ,  0.   ],
            [-1.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   , -1.   ,  0.   ],
            [ 0.   , -1.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ],
            [-1.   ,  0.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   ,  0.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ],
            [ 0.   ,  1.   ,  0.   ],
            [-1.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[ 0.   , -1.   ,  0.   ],
            [ 1.   , -1.   ,  0.   ],
            [ 1.   ,  0.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ]],
    <BLANKLINE>
           [[ 0.   ,  0.   ,  0.   ],
            [ 1.   ,  0.   ,  0.   ],
            [ 1.   ,  1.   ,  0.   ],
            [ 0.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   , -0.001,  0.   ],
            [ 1.   , -0.001,  0.   ],
            [ 1.   ,  0.001,  0.   ],
            [-1.   ,  0.001,  0.   ]],
    <BLANKLINE>
           [[-0.001, -1.   ,  0.   ],
            [ 0.001, -1.   ,  0.   ],
            [ 0.001,  1.   ,  0.   ],
            [-0.001,  1.   ,  0.   ]]]), array([[ 0.25,  0.25,  0.25,  0.1 ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ]]), array([[ 0.5 ,  0.5 ,  0.5 ,  0.5 ],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.  ,  0.  ,  0.  ,  1.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ]]))
    """

    limits = as_float_array(
        cast(ArrayLike, optional(limits, np.array([[-1, 1], [-1, 1]]))))
    labels = cast(Sequence, optional(labels, ("x", "y")))

    extent = np.max(np.abs(limits[..., 1] - limits[..., 0]))

    settings = Structure(
        **{
            "grid_face_colours": (0.25, 0.25, 0.25),
            "grid_edge_colours": (0.50, 0.50, 0.50),
            "grid_face_alpha": 0.1,
            "grid_edge_alpha": 0.5,
            "x_axis_colour": (0.0, 0.0, 0.0, 1.0),
            "y_axis_colour": (0.0, 0.0, 0.0, 1.0),
            "x_ticks_colour": (0.0, 0.0, 0.0, 0.85),
            "y_ticks_colour": (0.0, 0.0, 0.0, 0.85),
            "x_label_colour": (0.0, 0.0, 0.0, 0.85),
            "y_label_colour": (0.0, 0.0, 0.0, 0.85),
            "ticks_and_label_location": ("-x", "-y"),
        })
    settings.update(**kwargs)

    # Outer grid.
    quads_g = primitive_vertices_grid_mpl(
        origin=(-extent / 2, -extent / 2),
        width=extent,
        height=extent,
        height_segments=segments,
        width_segments=segments,
    )

    RGB_g = ones((quads_g.shape[0], quads_g.shape[-1]))
    RGB_gf = RGB_g * settings.grid_face_colours
    RGB_gf = np.hstack(
        [RGB_gf, full((RGB_gf.shape[0], 1), settings.grid_face_alpha)])
    RGB_ge = RGB_g * settings.grid_edge_colours
    RGB_ge = np.hstack(
        [RGB_ge, full((RGB_ge.shape[0], 1), settings.grid_edge_alpha)])

    # Inner grid.
    quads_gs = primitive_vertices_grid_mpl(
        origin=(-extent / 2, -extent / 2),
        width=extent,
        height=extent,
        height_segments=segments * 2,
        width_segments=segments * 2,
    )

    RGB_gs = ones((quads_gs.shape[0], quads_gs.shape[-1]))
    RGB_gsf = RGB_gs * 0
    RGB_gsf = np.hstack([RGB_gsf, full((RGB_gsf.shape[0], 1), 0)])
    RGB_gse = np.clip(RGB_gs * settings.grid_edge_colours * 1.5, 0, 1)
    RGB_gse = np.hstack(
        (RGB_gse, full((RGB_gse.shape[0], 1), settings.grid_edge_alpha / 2)))

    # Axis.
    thickness = extent / 1000
    quad_x = primitive_vertices_grid_mpl(origin=(limits[0, 0], -thickness / 2),
                                         width=extent,
                                         height=thickness)
    RGB_x = ones((quad_x.shape[0], quad_x.shape[-1] + 1))
    RGB_x = RGB_x * settings.x_axis_colour

    quad_y = primitive_vertices_grid_mpl(origin=(-thickness / 2, limits[1, 0]),
                                         width=thickness,
                                         height=extent)
    RGB_y = ones((quad_y.shape[0], quad_y.shape[-1] + 1))
    RGB_y = RGB_y * settings.y_axis_colour

    if axes is not None:
        # Ticks.
        x_s = 1 if "+x" in settings.ticks_and_label_location else -1
        y_s = 1 if "+y" in settings.ticks_and_label_location else -1
        for i, axis in enumerate("xy"):
            h_a = "center" if axis == "x" else "left" if x_s == 1 else "right"
            v_a = "center"

            ticks = list(sorted(set(quads_g[..., 0, i])))
            ticks += [ticks[-1] + ticks[-1] - ticks[-2]]
            for tick in ticks:
                x = (limits[1, 1 if x_s == 1 else 0] +
                     (x_s * extent / 25) if i else tick)
                y = (tick if i else limits[0, 1 if y_s == 1 else 0] +
                     (y_s * extent / 25))

                tick = as_int_scalar(tick) if is_integer(tick) else tick
                c = settings[f"{axis}_ticks_colour"]

                axes.text(
                    x,
                    y,
                    0,
                    tick,
                    "x",
                    horizontalalignment=h_a,
                    verticalalignment=v_a,
                    color=c,
                    clip_on=True,
                )

        # Labels.
        for i, axis in enumerate("xy"):
            h_a = "center" if axis == "x" else "left" if x_s == 1 else "right"
            v_a = "center"

            x = (limits[1, 1 if x_s == 1 else 0] +
                 (x_s * extent / 10) if i else 0)
            y = (0 if i else limits[0, 1 if y_s == 1 else 0] +
                 (y_s * extent / 10))

            c = settings[f"{axis}_label_colour"]

            axes.text(
                x,
                y,
                0,
                labels[i],
                "x",
                horizontalalignment=h_a,
                verticalalignment=v_a,
                color=c,
                size=20,
                clip_on=True,
            )

    quads = np.vstack([quads_g, quads_gs, quad_x, quad_y])
    RGB_f = np.vstack([RGB_gf, RGB_gsf, RGB_x, RGB_y])
    RGB_e = np.vstack([RGB_ge, RGB_gse, RGB_x, RGB_y])

    return quads, RGB_f, RGB_e
예제 #13
0
    def signal_unpack_data(
        data=Optional[Union[ArrayLike, dict, Series, "Signal"]],
        domain: Optional[ArrayLike] = None,
        dtype: Optional[Type[DTypeFloating]] = None,
    ) -> Tuple:
        """
        Unpack given data for continuous signal instantiation.

        Parameters
        ----------
        data
            Data to unpack for continuous signal instantiation.
        domain
            Values to initialise the :attr:`colour.continuous.Signal.domain`
            attribute with. If both ``data`` and ``domain`` arguments are
            defined, the latter will be used to initialise the
            :attr:`colour.continuous.Signal.domain` property.
        dtype
            Floating point data type.

        Returns
        -------
        :class:`tuple`
            Independent domain variable :math:`x` and corresponding range
            variable :math:`y` unpacked for continuous signal instantiation.

        Examples
        --------
        Unpacking using implicit *domain*:

        >>> range_ = np.linspace(10, 100, 10)
        >>> domain, range_ = Signal.signal_unpack_data(range_)
        >>> print(domain)
        [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using explicit *domain*:

        >>> domain = np.arange(100, 1100, 100)
        >>> domain, range = Signal.signal_unpack_data(range_, domain)
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a *dict*:

        >>> domain, range_ = Signal.signal_unpack_data(
        ...     dict(zip(domain, range_)))
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a *Pandas* :class:`pandas.Series`:

        >>> if is_pandas_installed():
        ...     from pandas import Series
        ...     domain, range = Signal.signal_unpack_data(
        ...         Series(dict(zip(domain, range_))))
        ... # doctest: +ELLIPSIS
        >>> print(domain)  # doctest: +SKIP
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)  # doctest: +SKIP
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a :class:`colour.continuous.Signal` class:

        >>> domain, range_ = Signal.signal_unpack_data(
        ...     Signal(range_, domain))
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]
        """

        dtype = cast(Type[DTypeFloating], optional(dtype, DEFAULT_FLOAT_DTYPE))

        domain_unpacked: NDArray = np.array([])
        range_unpacked: NDArray = np.array([])

        if isinstance(data, Signal):
            domain_unpacked = data.domain
            range_unpacked = data.range
        elif issubclass(type(data), Sequence) or isinstance(
            data, (tuple, list, np.ndarray, Iterator, ValuesView)
        ):
            data_array = tsplit(list(data))

            attest(data_array.ndim == 1, 'User "data" must be 1-dimensional!')

            domain_unpacked, range_unpacked = (
                np.arange(0, data_array.size, dtype=dtype),
                data_array,
            )
        elif issubclass(type(data), Mapping) or isinstance(data, dict):
            domain_unpacked, range_unpacked = tsplit(sorted(data.items()))
        elif is_pandas_installed():
            if isinstance(data, Series):
                domain_unpacked = data.index.values
                range_unpacked = data.values

        if domain is not None:
            domain_array = as_float_array(list(domain), dtype)  # type: ignore[arg-type]

            attest(
                len(domain_array) == len(range_unpacked),
                'User "domain" length is not compatible with unpacked "range"!',
            )

            domain_unpacked = domain_array

        if range_unpacked is not None:
            range_unpacked = as_float_array(range_unpacked, dtype)

        return domain_unpacked, range_unpacked
예제 #14
0
def plot_RGB_colourspaces_gamuts(
    colourspaces: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace,
                                                             str]]],
    reference_colourspace: 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",
    segments: Integer = 8,
    show_grid: Boolean = True,
    grid_segments: Integer = 10,
    show_spectral_locus: Boolean = False,
    spectral_locus_colour: Optional[Union[ArrayLike, str]] = None,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    chromatically_adapt: Boolean = False,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given *RGB* colourspaces gamuts in given reference colourspace.

    Parameters
    ----------
    colourspaces
        *RGB* colourspaces to plot the gamuts. ``colourspaces`` elements
        can be of any type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    reference_colourspace
        Reference colourspace model to plot the gamuts into, see
        :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported
        colourspace models.
    segments
        Edge segments count for each *RGB* colourspace cubes.
    show_grid
        Whether to show a grid at the bottom of the *RGB* colourspace cubes.
    grid_segments
        Edge segments count for the grid.
    show_spectral_locus
        Whether to show the spectral locus.
    spectral_locus_colour
        Spectral locus colour.
    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.
    chromatically_adapt
        Whether to chromatically adapt the *RGB* colourspaces given in
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

    Other Parameters
    ----------------
    edge_colours
        Edge colours array such as `edge_colours = (None, (0.5, 0.5, 1.0))`.
    edge_alpha
        Edge opacity value such as `edge_alpha = (0.0, 1.0)`.
    face_alpha
        Face opacity value such as `face_alpha = (0.5, 1.0)`.
    face_colours
        Face colours array such as `face_colours = (None, (0.5, 0.5, 1.0))`.
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.volume.nadir_grid`},
        See the documentation of the previously listed definitions.

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

    Examples
    --------
    >>> plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'])
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...Axes3DSubplot...>)

    .. image:: ../_static/Plotting_Plot_RGB_Colourspaces_Gamuts.png
        :align: center
        :alt: plot_RGB_colourspaces_gamuts
    """

    colourspaces = cast(
        List[RGB_Colourspace],
        list(filter_RGB_colourspaces(colourspaces).values()),
    )

    convert_kwargs = optional(convert_kwargs, {})

    count_c = len(colourspaces)

    title = (
        f"{', '.join([colourspace.name for colourspace in colourspaces])} "
        f"- {reference_colourspace} Reference Colourspace")

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    convert_settings = {"illuminant": illuminant}
    convert_settings.update(convert_kwargs)

    settings = Structure(
        **{
            "face_colours": [None] * count_c,
            "edge_colours": [None] * count_c,
            "face_alpha": [1] * count_c,
            "edge_alpha": [1] * count_c,
            "title": title,
        })
    settings.update(kwargs)

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

    points = zeros((4, 3))
    if show_spectral_locus:
        cmfs = cast(MultiSpectralDistributions,
                    first_item(filter_cmfs(cmfs).values()))
        XYZ = cmfs.values

        points = colourspace_model_axis_reorder(
            convert(XYZ, "CIE XYZ", reference_colourspace, **convert_settings),
            reference_colourspace,
        )

        points[np.isnan(points)] = 0

        c = ((0.0, 0.0, 0.0,
              0.5) if spectral_locus_colour is None else spectral_locus_colour)

        axes.plot(
            points[..., 0],
            points[..., 1],
            points[..., 2],
            color=c,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
        )
        axes.plot(
            (points[-1][0], points[0][0]),
            (points[-1][1], points[0][1]),
            (points[-1][2], points[0][2]),
            color=c,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
        )

    plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace

    quads_c: List = []
    RGB_cf: List = []
    RGB_ce: List = []
    for i, colourspace in enumerate(colourspaces):

        if chromatically_adapt and not np.array_equal(
                colourspace.whitepoint, plotting_colourspace.whitepoint):
            colourspace = colourspace.chromatically_adapt(
                plotting_colourspace.whitepoint,
                plotting_colourspace.whitepoint_name,
            )

        quads_cb, RGB = RGB_identity_cube(
            width_segments=segments,
            height_segments=segments,
            depth_segments=segments,
        )

        XYZ = RGB_to_XYZ(
            quads_cb,
            colourspace.whitepoint,
            colourspace.whitepoint,
            colourspace.matrix_RGB_to_XYZ,
        )

        convert_settings = {"illuminant": colourspace.whitepoint}
        convert_settings.update(convert_kwargs)

        quads_c.extend(
            colourspace_model_axis_reorder(
                convert(XYZ, "CIE XYZ", reference_colourspace,
                        **convert_settings),
                reference_colourspace,
            ))

        if settings.face_colours[i] is not None:
            RGB = ones(RGB.shape) * settings.face_colours[i]

        RGB_cf.extend(
            np.hstack([RGB,
                       full((RGB.shape[0], 1), settings.face_alpha[i])]))

        if settings.edge_colours[i] is not None:
            RGB = ones(RGB.shape) * settings.edge_colours[i]

        RGB_ce.extend(
            np.hstack([RGB,
                       full((RGB.shape[0], 1), settings.edge_alpha[i])]))

    quads = as_float_array(quads_c)
    RGB_f = as_float_array(RGB_cf)
    RGB_e = as_float_array(RGB_ce)

    quads[np.isnan(quads)] = 0

    if quads.size != 0:
        for i, axis in enumerate("xyz"):
            min_a = np.minimum(np.min(quads[..., i]), np.min(points[..., i]))
            max_a = np.maximum(np.max(quads[..., i]), np.max(points[..., i]))
            getattr(axes, f"set_{axis}lim")((min_a, max_a))

    labels = np.array(
        COLOURSPACE_MODELS_AXIS_LABELS[reference_colourspace])[as_int_array(
            colourspace_model_axis_reorder([0, 1, 2], reference_colourspace))]
    for i, axis in enumerate("xyz"):
        getattr(axes, f"set_{axis}label")(labels[i])

    if show_grid:
        limits = np.array([[-1.5, 1.5], [-1.5, 1.5]])

        quads_g, RGB_gf, RGB_ge = nadir_grid(limits, grid_segments, labels,
                                             axes, **settings)
        quads = np.vstack([quads_g, quads])
        RGB_f = np.vstack([RGB_gf, RGB_f])
        RGB_e = np.vstack([RGB_ge, RGB_e])

    collection = Poly3DCollection(quads)
    collection.set_facecolors(RGB_f)
    collection.set_edgecolors(RGB_e)

    axes.add_collection3d(collection)

    settings.update({
        "axes": axes,
        "axes_visible": False,
        "camera_aspect": "equal"
    })
    settings.update(kwargs)

    return render(**settings)
예제 #15
0
def plot_RGB_scatter(
    RGB: ArrayLike,
    colourspace: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace,
                                                            str]]],
    reference_colourspace: 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",
    colourspaces: Optional[Union[RGB_Colourspace, str,
                                 Sequence[Union[RGB_Colourspace,
                                                str]]]] = None,
    segments: Integer = 8,
    show_grid: Boolean = True,
    grid_segments: Integer = 10,
    show_spectral_locus: Boolean = False,
    spectral_locus_colour: Optional[Union[ArrayLike, str]] = None,
    points_size: Floating = 12,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    chromatically_adapt: Boolean = False,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given *RGB* colourspace array in a scatter plot.

    Parameters
    ----------
    RGB
        *RGB* colourspace array.
    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.
    reference_colourspace
        Reference colourspace model to plot the gamuts into, see
        :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported
        colourspace models.
    colourspaces
        *RGB* colourspaces to plot the gamuts. ``colourspaces`` elements
        can be of any type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    segments
        Edge segments count for each *RGB* colourspace cubes.
    show_grid
        Whether to show a grid at the bottom of the *RGB* colourspace cubes.
    grid_segments
        Edge segments count for the grid.
    show_spectral_locus
        Whether to show the spectral locus.
    spectral_locus_colour
        Spectral locus colour.
    points_size
        Scatter points size.
    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.
    chromatically_adapt
        Whether to chromatically adapt the *RGB* colourspaces given in
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

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

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

    Examples
    --------
    >>> RGB = np.random.random((128, 128, 3))
    >>> plot_RGB_scatter(RGB, 'ITU-R BT.709')  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...Axes3DSubplot...>)

    .. image:: ../_static/Plotting_Plot_RGB_Scatter.png
        :align: center
        :alt: plot_RGB_scatter
    """

    colourspace = cast(
        RGB_Colourspace,
        first_item(filter_RGB_colourspaces(colourspace).values()),
    )
    colourspaces = cast(List[str], optional(colourspaces, [colourspace.name]))

    convert_kwargs = optional(convert_kwargs, {})

    count_c = len(colourspaces)
    settings = Structure(
        **{
            "face_colours": [None] * count_c,
            "edge_colours": [(0.25, 0.25, 0.25)] * count_c,
            "face_alpha": [0.0] * count_c,
            "edge_alpha": [0.1] * count_c,
        })
    settings.update(kwargs)
    settings["standalone"] = False

    plot_RGB_colourspaces_gamuts(
        colourspaces=colourspaces,
        reference_colourspace=reference_colourspace,
        segments=segments,
        show_grid=show_grid,
        grid_segments=grid_segments,
        show_spectral_locus=show_spectral_locus,
        spectral_locus_colour=spectral_locus_colour,
        cmfs=cmfs,
        chromatically_adapt=chromatically_adapt,
        **settings,
    )

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

    convert_settings = {"illuminant": colourspace.whitepoint}
    convert_settings.update(convert_kwargs)

    points = colourspace_model_axis_reorder(
        convert(XYZ, "CIE XYZ", reference_colourspace, **convert_settings),
        reference_colourspace,
    )

    axes = plt.gca()
    axes.scatter(
        points[..., 0],
        points[..., 1],
        points[..., 2],
        color=np.reshape(RGB, (-1, 3)),
        s=points_size,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
    )

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

    return render(**settings)
예제 #16
0
def extract_colour_checkers_segmentation(image: ArrayLike,
                                         **kwargs: Any) -> Tuple[NDArray, ...]:
    """
    Extract the colour checkers sub-images in given image using segmentation.

    Parameters
    ----------
    image
        Image to extract the colours checkers sub-images from.

    Other Parameters
    ----------------
    aspect_ratio
        Colour checker aspect ratio, e.g. 1.5.
    aspect_ratio_minimum
        Minimum colour checker aspect ratio for detection: projective geometry
        might reduce the colour checker aspect ratio.
    aspect_ratio_maximum
        Maximum colour checker aspect ratio for detection: projective geometry
        might increase the colour checker aspect ratio.
    swatches
        Colour checker swatches total count.
    swatches_horizontal
        Colour checker swatches horizontal columns count.
    swatches_vertical
        Colour checker swatches vertical row count.
    swatches_count_minimum
        Minimum swatches count to be considered for the detection.
    swatches_count_maximum
        Maximum swatches count to be considered for the detection.
    swatches_chromatic_slice
        A `slice` instance defining chromatic swatches used to detect if the
        colour checker is upside down.
    swatches_achromatic_slice
        A `slice` instance defining achromatic swatches used to detect if the
        colour checker is upside down.
    swatch_minimum_area_factor
        Swatch minimum area factor :math:`f` with the minimum area :math:`m_a`
        expressed as follows: :math:`m_a = image_w * image_h / s_c / f` where
        :math:`image_w`, :math:`image_h` and :math:`s_c` are respectively the
        image width, height and the swatches count.
    swatch_contour_scale
        As the image is filtered, the swatches area will tend to shrink, the
        generated contours can thus be scaled.
    cluster_contour_scale
        As the swatches are clustered, it might be necessary to adjust the
        cluster scale so that the masks are centred better on the swatches.
    working_width
        Size the input image is resized to for detection.
    fast_non_local_means_denoising_kwargs
        Keyword arguments for :func:`cv2.fastNlMeansDenoising` definition.
    adaptive_threshold_kwargs
        Keyword arguments for :func:`cv2.adaptiveThreshold` definition.
    interpolation_method
        Interpolation method used when resizing the images, `cv2.INTER_CUBIC`
        and `cv2.INTER_LINEAR` methods are recommended.

    Returns
    -------
    :class:`tuple`
        Tuple of colour checkers sub-images.

    Examples
    --------
    >>> import os
    >>> from colour import read_image
    >>> from colour_checker_detection import TESTS_RESOURCES_DIRECTORY
    >>> path = os.path.join(TESTS_RESOURCES_DIRECTORY,
    ...                     'colour_checker_detection', 'detection',
    ...                     'IMG_1967.png')
    >>> image = read_image(path)
    >>> extract_colour_checkers_segmentation(image)
    ... # doctest: +SKIP
    (array([[[ 0.17908671,  0.14010708,  0.09243158],
            [ 0.17805016,  0.13058874,  0.09513047],
            [ 0.17175764,  0.13128328,  0.08811688],
            ...,
            [ 0.15934898,  0.13436384,  0.07479276],
            [ 0.17178158,  0.13138185,  0.07703256],
            [ 0.15082785,  0.11866678,  0.07680314]],
    <BLANKLINE>
           [[ 0.16597673,  0.13563241,  0.08780421],
            [ 0.16490564,  0.13110894,  0.08601525],
            [ 0.16939694,  0.12963502,  0.08783565],
            ...,
            [ 0.14708202,  0.12856133,  0.0814603 ],
            [ 0.16883563,  0.12862256,  0.08452422],
            [ 0.16781917,  0.12363558,  0.07361614]],
    <BLANKLINE>
           [[ 0.16326806,  0.13720085,  0.08925959],
            [ 0.16014062,  0.13585283,  0.08104862],
            [ 0.16657823,  0.12889633,  0.08870038],
            ...,
            [ 0.14619341,  0.13086307,  0.07367594],
            [ 0.16302426,  0.13062705,  0.07938427],
            [ 0.16618022,  0.1266259 ,  0.07200021]],
    <BLANKLINE>
           ...,
           [[ 0.1928642 ,  0.14578913,  0.11224515],
            [ 0.18931177,  0.14416392,  0.10288388],
            [ 0.17707473,  0.1436448 ,  0.09188452],
            ...,
            [ 0.16879168,  0.12867133,  0.09001681],
            [ 0.1699731 ,  0.1287041 ,  0.07616285],
            [ 0.17137891,  0.129711  ,  0.07517841]],
    <BLANKLINE>
           [[ 0.19514292,  0.1532704 ,  0.10375113],
            [ 0.18217109,  0.14982903,  0.10452617],
            [ 0.18830594,  0.1469499 ,  0.10896181],
            ...,
            [ 0.18234864,  0.12642328,  0.08047272],
            [ 0.17617388,  0.13000189,  0.06874527],
            [ 0.17108543,  0.13264084,  0.06309374]],
    <BLANKLINE>
           [[ 0.16243187,  0.14983535,  0.08954653],
            [ 0.155507  ,  0.14899652,  0.10273992],
            [ 0.17993385,  0.1498394 ,  0.1099571 ],
            ...,
            [ 0.18079454,  0.1253967 ,  0.07739887],
            [ 0.17239226,  0.13181566,  0.07806754],
            [ 0.17422497,  0.13277327,  0.07513551]]], dtype=float32),)
    """

    image = as_float_array(image, FLOAT_DTYPE_DEFAULT)[..., :3]

    settings = Structure(**SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC)
    settings.update(**kwargs)

    image = adjust_image(image, settings.working_width,
                         settings.interpolation_method)

    colour_checkers = []
    for rectangle in cast(
            List[NDArray],
            colour_checkers_coordinates_segmentation(image, **settings),
    ):
        colour_checker = crop_and_level_image_with_rectangle(
            image, cv2.minAreaRect(rectangle), settings.interpolation_method)
        width, height = (colour_checker.shape[1], colour_checker.shape[0])

        if width < height:
            colour_checker = cv2.rotate(colour_checker,
                                        cv2.ROTATE_90_CLOCKWISE)

        colour_checkers.append(colour_checker)

    return tuple(colour_checkers)
예제 #17
0
def XYZ_to_Hunt(
    XYZ: ArrayLike,
    XYZ_w: ArrayLike,
    XYZ_b: ArrayLike,
    L_A: FloatingOrArrayLike,
    surround: InductionFactors_Hunt = VIEWING_CONDITIONS_HUNT["Normal Scenes"],
    L_AS: Optional[FloatingOrArrayLike] = None,
    CCT_w: Optional[FloatingOrArrayLike] = None,
    XYZ_p: Optional[ArrayLike] = None,
    p: Optional[FloatingOrArrayLike] = None,
    S: Optional[FloatingOrArrayLike] = None,
    S_w: Optional[FloatingOrArrayLike] = None,
    helson_judd_effect: Boolean = False,
    discount_illuminant: Boolean = True,
) -> CAM_Specification_Hunt:
    """
    Compute the *Hunt* colour appearance model correlates.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values of test sample / stimulus.
    XYZ_w
        *CIE XYZ* tristimulus values of reference white.
    XYZ_b
        *CIE XYZ* tristimulus values of background.
    L_A
        Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`.
    surround
         Surround viewing conditions induction factors.
    L_AS
        Scotopic luminance :math:`L_{AS}` of the illuminant, approximated if
        not specified.
    CCT_w
        Correlated color temperature :math:`T_{cp}`: of the illuminant, needed
        to approximate :math:`L_{AS}`.
    XYZ_p
        *CIE XYZ* tristimulus values of proximal field, assumed to be equal to
        background if not specified.
    p
        Simultaneous contrast / assimilation factor :math:`p` with value
        normalised to domain [-1, 0] when simultaneous contrast occurs and
        normalised to domain [0, 1] when assimilation occurs.
    S
        Scotopic response :math:`S` to the stimulus, approximated using
        tristimulus values :math:`Y` of the stimulus if not specified.
    S_w
        Scotopic response :math:`S_w` for the reference white, approximated
        using the tristimulus values :math:`Y_w` of the reference white if not
        specified.
    helson_judd_effect
        Truth value indicating whether the *Helson-Judd* effect should be
        accounted for.
    discount_illuminant
       Truth value indicating if the illuminant should be discounted.

    Returns
    -------
    :class:`colour.CAM_Specification_Hunt`
        *Hunt* colour appearance model specification.

    Raises
    ------
    ValueError
        If an illegal argument combination is specified.

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

    +------------------------------+-----------------------+---------------+
    | **Range**                    | **Scale - Reference** | **Scale - 1** |
    +==============================+=======================+===============+
    | ``CAM_Specification_Hunt.h`` | [0, 360]              | [0, 1]        |
    +------------------------------+-----------------------+---------------+

    References
    ----------
    :cite:`Fairchild2013u`, :cite:`Hunt2004b`

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_w = np.array([95.05, 100.00, 108.88])
    >>> XYZ_b = np.array([95.05, 100.00, 108.88])
    >>> L_A = 318.31
    >>> surround = VIEWING_CONDITIONS_HUNT['Normal Scenes']
    >>> CCT_w = 6504
    >>> XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w)
    ... # doctest: +ELLIPSIS
    CAM_Specification_Hunt(J=30.0462678..., C=0.1210508..., h=269.2737594..., \
s=0.0199093..., Q=22.2097654..., M=0.1238964..., H=None, HC=None)
    """

    XYZ = to_domain_100(XYZ)
    XYZ_w = to_domain_100(XYZ_w)
    XYZ_b = to_domain_100(XYZ_b)
    _X, Y, _Z = tsplit(XYZ)
    _X_w, Y_w, _Z_w = tsplit(XYZ_w)
    X_b, Y_b, _Z_b = tsplit(XYZ_b)

    # Arguments handling.
    if XYZ_p is not None:
        X_p, Y_p, Z_p = tsplit(to_domain_100(XYZ_p))
    else:
        X_p = X_b
        Y_p = Y_b
        Z_p = Y_b
        usage_warning('Unspecified proximal field "XYZ_p" argument, using '
                      'background "XYZ_b" as approximation!')

    if surround.N_cb is None:
        N_cb = 0.725 * spow(Y_w / Y_b, 0.2)
        usage_warning(
            f'Unspecified "N_cb" argument, using approximation: "{N_cb}"')
    if surround.N_bb is None:
        N_bb = 0.725 * spow(Y_w / Y_b, 0.2)
        usage_warning(
            f'Unspecified "N_bb" argument, using approximation: "{N_bb}"')

    if L_AS is None and CCT_w is None:
        raise ValueError('Either the scotopic luminance "L_AS" of the '
                         "illuminant or its correlated colour temperature "
                         '"CCT_w" must be specified!')
    elif L_AS is None and CCT_w is not None:
        L_AS = illuminant_scotopic_luminance(L_A, CCT_w)
        usage_warning(
            f'Unspecified "L_AS" argument, using approximation from "CCT": '
            f'"{L_AS}"')

    if (S is None and S_w is not None) or (S is not None and S_w is None):
        raise ValueError('Either both stimulus scotopic response "S" and '
                         'reference white scotopic response "S_w" arguments '
                         "need to be specified or none of them!")
    elif S is None and S_w is None:
        S_p = Y
        S_w_p = Y_w
        usage_warning(
            f'Unspecified stimulus scotopic response "S" and reference white '
            f'scotopic response "S_w" arguments, using approximation: '
            f'"{S}", "{S_w}"')

    if p is None:
        usage_warning(
            'Unspecified simultaneous contrast / assimilation "p" '
            "argument, model will not account for simultaneous chromatic "
            "contrast!")

    XYZ_p = tstack([X_p, Y_p, Z_p])

    # Computing luminance level adaptation factor :math:`F_L`.
    F_L = luminance_level_adaptation_factor(L_A)

    # Computing test sample chromatic adaptation.
    rgb_a = chromatic_adaptation(
        XYZ,
        XYZ_w,
        XYZ_b,
        L_A,
        F_L,
        XYZ_p,
        p,
        helson_judd_effect,
        discount_illuminant,
    )

    # Computing reference white chromatic adaptation.
    rgb_aw = chromatic_adaptation(
        XYZ_w,
        XYZ_w,
        XYZ_b,
        L_A,
        F_L,
        XYZ_p,
        p,
        helson_judd_effect,
        discount_illuminant,
    )

    # Computing opponent colour dimensions.
    # Computing achromatic post adaptation signals.
    A_a = achromatic_post_adaptation_signal(rgb_a)
    A_aw = achromatic_post_adaptation_signal(rgb_aw)

    # Computing colour difference signals.
    C = colour_difference_signals(rgb_a)
    C_w = colour_difference_signals(rgb_aw)

    # -------------------------------------------------------------------------
    # Computing the *hue* angle :math:`h_s`.
    # -------------------------------------------------------------------------
    h = hue_angle(C)
    # hue_w = hue_angle(C_w)
    # TODO: Implement hue quadrature & composition computation.

    # -------------------------------------------------------------------------
    # Computing the correlate of *saturation* :math:`s`.
    # -------------------------------------------------------------------------
    # Computing eccentricity factors.
    e_s = eccentricity_factor(h)

    # Computing low luminance tritanopia factor :math:`F_t`.
    F_t = low_luminance_tritanopia_factor(L_A)

    M_yb = yellowness_blueness_response(C, e_s, surround.N_c, N_cb, F_t)
    M_rg = redness_greenness_response(C, e_s, surround.N_c, N_cb)
    M_yb_w = yellowness_blueness_response(C_w, e_s, surround.N_c, N_cb, F_t)
    M_rg_w = redness_greenness_response(C_w, e_s, surround.N_c, N_cb)

    # Computing overall chromatic response.
    M = overall_chromatic_response(M_yb, M_rg)
    M_w = overall_chromatic_response(M_yb_w, M_rg_w)

    s = saturation_correlate(M, rgb_a)

    # -------------------------------------------------------------------------
    # Computing the correlate of *brightness* :math:`Q`.
    # -------------------------------------------------------------------------
    # Computing achromatic signal :math:`A`.
    A = achromatic_signal(cast(FloatingOrNDArray, L_AS), S_p, S_w_p, N_bb, A_a)
    A_w = achromatic_signal(cast(FloatingOrNDArray, L_AS), S_w_p, S_w_p, N_bb,
                            A_aw)

    Q = brightness_correlate(A, A_w, M, surround.N_b)
    brightness_w = brightness_correlate(A_w, A_w, M_w, surround.N_b)
    # TODO: Implement whiteness-blackness :math:`Q_{wb}` computation.

    # -------------------------------------------------------------------------
    # Computing the correlate of *Lightness* :math:`J`.
    # -------------------------------------------------------------------------
    J = lightness_correlate(Y_b, Y_w, Q, brightness_w)

    # -------------------------------------------------------------------------
    # Computing the correlate of *chroma* :math:`C_{94}`.
    # -------------------------------------------------------------------------
    C_94 = chroma_correlate(s, Y_b, Y_w, Q, brightness_w)

    # -------------------------------------------------------------------------
    # Computing the correlate of *colourfulness* :math:`M_{94}`.
    # -------------------------------------------------------------------------
    M_94 = colourfulness_correlate(F_L, C_94)

    return CAM_Specification_Hunt(
        J,
        C_94,
        as_float(from_range_degrees(h)),
        s,
        Q,
        M_94,
        None,
        None,
    )
예제 #18
0
def plot_the_blue_sky(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the blue sky.

    Parameters
    ----------
    cmfs
        Standard observer colour matching functions used for computing the
        spectrum domain and colours. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.plot_single_sd`,
        :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_the_blue_sky()  # doctest: +ELLIPSIS
    (<Figure size ... with 2 Axes>, <...AxesSubplot...>)

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

    figure = plt.figure()

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

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

    ASTMG173_sd = cast(SpectralDistribution, SD_ASTMG173_ETR.copy())
    rayleigh_sd = sd_rayleigh_scattering()
    ASTMG173_sd.align(rayleigh_sd.shape)

    sd = rayleigh_sd * ASTMG173_sd

    axes = figure.add_subplot(211)

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

    plot_single_sd(sd, cmfs, **settings)

    axes = figure.add_subplot(212)

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

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

    blue_sky_color = XYZ_to_plotting_colourspace(
        sd_to_XYZ(cast(SpectralDistribution, sd)))

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

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

    return render(**settings)
예제 #19
0
__maintainer__ = "Colour Developers"
__email__ = "*****@*****.**"
__status__ = "Production"

__all__ = [
    "TestPlotSpectraANSIIESTM3018",
    "TestPlotColourVectorGraphic",
    "TestPlot16BinBars",
    "TestPlotLocalChromaShifts",
    "TestPlotLocalHueShifts",
    "TestPlotLocalColourFidelities",
    "TestPlotColourFidelityIndexes",
]

SPECIFICATION_ANSIIESTM3018: ColourQuality_Specification_ANSIIESTM3018 = cast(
    ColourQuality_Specification_ANSIIESTM3018,
    colour_fidelity_index_ANSIIESTM3018(SDS_ILLUMINANTS["FL2"], True),
)


class TestPlotSpectraANSIIESTM3018(unittest.TestCase):
    """
        Define :func:`colour.plotting.tm3018.components.
    plot_spectra_ANSIIESTM3018` definition unit tests methods.
    """

    def test_plot_spectra_ANSIIESTM3018(self):
        """
        Test :func:`colour.plotting.tm3018.components.\
plot_spectra_ANSIIESTM3018` definition.
        """
예제 #20
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)
예제 #21
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)
예제 #22
0
def plot_multi_sds_colour_rendering_indexes_bars(
    sds: Union[
        Sequence[Union[SpectralDistribution, MultiSpectralDistributions]],
        MultiSpectralDistributions,
    ],
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Colour Rendering Index* (CRI) of given illuminants or light
    sources spectral distributions.

    Parameters
    ----------
    sds
        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.

    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)
    >>> illuminant = SDS_ILLUMINANTS['FL2']
    >>> light_source = SDS_LIGHT_SOURCES['Kinoton 75P']
    >>> plot_multi_sds_colour_rendering_indexes_bars(
    ...     [illuminant, light_source])  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_\
Plot_Multi_SDS_Colour_Rendering_Indexes_Bars.png
        :align: center
        :alt: plot_multi_sds_colour_rendering_indexes_bars
    """

    sds_converted = sds_and_msds_to_sds(sds)

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

    specifications = cast(
        List[ColourRendering_Specification_CRI],
        [
            colour_rendering_index(sd, additional_data=True)
            for sd in sds_converted
        ],
    )

    # *colour rendering index* colorimetry data tristimulus values are
    # computed in [0, 100] domain however `plot_colour_quality_bars` expects
    # [0, 1] domain. As we want to keep `plot_colour_quality_bars` definition
    # agnostic from the colour quality data, we update the test sd
    # colorimetry data tristimulus values domain.
    for specification in specifications:
        colorimetry_data = specification.colorimetry_data
        for i in range(len(colorimetry_data[0])):
            colorimetry_data[0][i].XYZ /= 100

    _figure, axes = plot_colour_quality_bars(specifications, **settings)

    title = (
        f"Colour Rendering Index - "
        f"{', '.join([sd.strict_name for sd in sds_converted])}"
    )

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

    return render(**settings)
예제 #23
0
def plot_planckian_locus(
    planckian_locus_colours: Optional[Union[ArrayLike, str]] = None,
    planckian_locus_opacity: Floating = 1,
    planckian_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 *Planckian Locus* according to given method.

    Parameters
    ----------
    planckian_locus_colours
        Colours of the *Planckian Locus*, if ``planckian_locus_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    planckian_locus_opacity
       Opacity of the *Planckian Locus*.
    planckian_locus_labels
        Array of labels used to customise which iso-temperature lines will be
        drawn along the *Planckian Locus*. Passing an empty array will result
        in no iso-temperature lines 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_planckian_locus(planckian_locus_colours='RGB')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

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

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

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

    labels = cast(
        Tuple,
        optional(
            planckian_locus_labels,
            (10**6 / 600, 2000, 2500, 3000, 4000, 6000, 10**6 / 100),
        ),
    )
    D_uv = 0.05

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

    _figure, axes = artist(**settings)

    if method == "cie 1931":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return UCS_uv_to_xy(uv)

    elif method == "cie 1960 ucs":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return uv

    elif method == "cie 1976 ucs":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_Luv_uv(UCS_uv_to_xy(uv))

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

    start, end = 10**6 / 600, 10**6 / 10
    CCT = np.arange(start, end + 100, 100)
    CCT_D_uv = np.reshape(tstack([CCT, zeros(CCT.shape)]), (-1, 1, 2))
    ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))

    use_RGB_planckian_locus_colours = (
        str(planckian_locus_colours).upper() == "RGB")
    if use_RGB_planckian_locus_colours:
        pl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
    else:
        pl_colours = planckian_locus_colours

    line_collection = LineCollection(
        np.concatenate([ij[:-1], ij[1:]], axis=1),
        colors=pl_colours,
        alpha=planckian_locus_opacity,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
    )
    axes.add_collection(line_collection)

    for label in labels:
        CCT_D_uv = np.reshape(
            tstack([full(10, label),
                    np.linspace(-D_uv, D_uv, 10)]), (-1, 1, 2))

        if use_RGB_planckian_locus_colours:
            itl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
        else:
            itl_colours = planckian_locus_colours

        ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))

        line_collection = LineCollection(
            np.concatenate([ij[:-1], ij[1:]], axis=1),
            colors=itl_colours,
            alpha=planckian_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
        )
        axes.add_collection(line_collection)
        axes.annotate(
            f"{as_int_scalar(label)}K",
            xy=(ij[-1, :, 0], ij[-1, :, 1]),
            xytext=(0, CONSTANTS_COLOUR_STYLE.geometry.long / 2),
            textcoords="offset points",
            size="x-small",
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,
        )

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

    return render(**settings)
예제 #24
0
def plot_single_sd_rayleigh_scattering(
    CO2_concentration:
    FloatingOrArrayLike = CONSTANT_STANDARD_CO2_CONCENTRATION,
    temperature: FloatingOrArrayLike = CONSTANT_STANDARD_AIR_TEMPERATURE,
    pressure: FloatingOrArrayLike = CONSTANT_AVERAGE_PRESSURE_MEAN_SEA_LEVEL,
    latitude: FloatingOrArrayLike = CONSTANT_DEFAULT_LATITUDE,
    altitude: FloatingOrArrayLike = CONSTANT_DEFAULT_ALTITUDE,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot a single *Rayleigh* scattering spectral distribution.

    Parameters
    ----------
    CO2_concentration
        :math:`CO_2` concentration in parts per million (ppm).
    temperature
        Air temperature :math:`T[K]` in kelvin degrees.
    pressure
        Surface pressure :math:`P` of the measurement site.
    latitude
        Latitude of the site in degrees.
    altitude
        Altitude of the site in meters.
    cmfs
        Standard observer colour matching functions used for computing the
        spectrum domain and colours. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.

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

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

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

    .. image:: ../_static/Plotting_Plot_Single_SD_Rayleigh_Scattering.png
        :align: center
        :alt: plot_single_sd_rayleigh_scattering
    """

    title = "Rayleigh Scattering"

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

    settings: Dict[str, Any] = {"title": title, "y_label": "Optical Depth"}
    settings.update(kwargs)

    sd = sd_rayleigh_scattering(
        cmfs.shape,
        CO2_concentration,
        temperature,
        pressure,
        latitude,
        altitude,
    )

    return plot_single_sd(sd, **settings)
예제 #25
0
def plot_multi_sds_colour_quality_scales_bars(
    sds: Union[
        Sequence[Union[SpectralDistribution, MultiSpectralDistributions]],
        MultiSpectralDistributions,
    ],
    method: Union[
        Literal["NIST CQS 7.4", "NIST CQS 9.0"], str
    ] = "NIST CQS 9.0",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Colour Quality Scale* (CQS) of given illuminants or light
    sources spectral distributions.

    Parameters
    ----------
    sds
        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.
    method
        *Colour Quality Scale* (CQS) computation method.

    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)
    >>> illuminant = SDS_ILLUMINANTS['FL2']
    >>> light_source = SDS_LIGHT_SOURCES['Kinoton 75P']
    >>> plot_multi_sds_colour_quality_scales_bars([illuminant, light_source])
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_\
Plot_Multi_SDS_Colour_Quality_Scales_Bars.png
        :align: center
        :alt: plot_multi_sds_colour_quality_scales_bars
    """

    method = validate_method(method, COLOUR_QUALITY_SCALE_METHODS)

    sds_converted = sds_and_msds_to_sds(sds)

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

    specifications = cast(
        List[ColourRendering_Specification_CQS],
        [colour_quality_scale(sd, True, method) for sd in sds_converted],
    )

    _figure, axes = plot_colour_quality_bars(specifications, **settings)

    title = (
        f"Colour Quality Scale - "
        f"{', '.join([sd.strict_name for sd in sds_converted])}"
    )

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

    return render(**settings)
예제 #26
0
def colour_fidelity_index_ANSIIESTM3018(
    sd_test: SpectralDistribution,
    additional_data: Boolean = False
) -> Union[Floating, ColourQuality_Specification_ANSIIESTM3018,
           ColourRendering_Specification_CIE2017, ]:
    """
    Return the *ANSI/IES TM-30-18 Colour Fidelity Index* (CFI) :math:`R_f`
    of given spectral distribution.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    additional_data
        Whether to output additional data.

    Returns
    -------
    :class:`numpy.floating` or \
:class:`colour.quality.ColourQuality_Specification_ANSIIESTM3018`
        *ANSI/IES TM-30-18 Colour Fidelity Index* (CFI).

    References
    ----------
    :cite:`ANSI2018`

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_fidelity_index_ANSIIESTM3018(sd)  # doctest: +ELLIPSIS
    70.1208254...
    """

    if not additional_data:
        return colour_fidelity_index_CIE2017(sd_test, False)

    specification: (
        ColourRendering_Specification_CIE2017) = colour_fidelity_index_CIE2017(
            sd_test, True)  # type: ignore[assignment]

    # Setup bins based on where the reference a'b' points are located.
    bins: List[List[int]] = [[] for _i in range(16)]
    for i, sample in enumerate(specification.colorimetry_data[1]):
        bin_index = as_int_scalar(np.floor(
            cast(Floating, sample.CAM.h) / 22.5))
        bins[bin_index].append(i)

    # Per-bin a'b' averages.
    averages_test = np.empty([16, 2])
    averages_reference = np.empty([16, 2])
    for i in range(16):
        apbp_s = [
            specification.colorimetry_data[0][j].Jpapbp[[1, 2]]
            for j in bins[i]
        ]
        averages_test[i, :] = np.mean(apbp_s, axis=0)
        apbp_s = [
            specification.colorimetry_data[1][j].Jpapbp[[1, 2]]
            for j in bins[i]
        ]
        averages_reference[i, :] = np.mean(apbp_s, axis=0)

    # Gamut Index.
    R_g = 100 * (averages_area(averages_test) /
                 averages_area(averages_reference))

    # Local colour fidelity indexes, i.e. 16 CFIs for each bin.
    bin_delta_E_s = [
        np.mean([specification.delta_E_s[bins[i]]]) for i in range(16)
    ]
    R_fs = as_float_array(delta_E_to_R_f(bin_delta_E_s))

    # Angles bisecting the hue bins.
    angles = (22.5 * np.arange(16) + 11.25) / 180 * np.pi
    cosines = np.cos(angles)
    sines = np.sin(angles)

    average_norms = np.linalg.norm(averages_reference, axis=1)
    a_deltas = averages_test[:, 0] - averages_reference[:, 0]
    b_deltas = averages_test[:, 1] - averages_reference[:, 1]

    # Local chromaticity shifts, multiplied by 100 to obtain percentages.
    R_cs = 100 * (a_deltas * cosines + b_deltas * sines) / average_norms

    # Local hue shifts.
    R_hs = (-a_deltas * sines + b_deltas * cosines) / average_norms

    return ColourQuality_Specification_ANSIIESTM3018(
        specification.name,
        sd_test,
        specification.sd_reference,
        specification.R_f,
        specification.R_s,
        specification.CCT,
        specification.D_uv,
        specification.colorimetry_data,
        R_g,
        bins,
        averages_test,
        averages_reference,
        average_norms,
        R_fs,
        R_cs,
        R_hs,
    )
예제 #27
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)
예제 #28
0
def as_8_bit_BGR_image(image: ArrayLike) -> NDArray:
    """
    Convert and encodes given linear float *RGB* image to 8-bit *BGR* with
    *sRGB* reverse OETF.

    Parameters
    ----------
    image
        Image to convert.

    Returns
    -------
    :class:`numpy.ndarray`
        Converted image.

    Notes
    -----
    -   In the eventuality where the image is already an integer array, the
        conversion is by-passed.

    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [4, 2, 3])
    >>> print(image)
    [[[ 0.96702984  0.25298236  0.0089861 ]
      [ 0.54723225  0.43479153  0.38657128]]
    <BLANKLINE>
     [[ 0.97268436  0.77938292  0.04416006]
      [ 0.71481599  0.19768507  0.95665297]]
    <BLANKLINE>
     [[ 0.69772882  0.86299324  0.43614665]
      [ 0.2160895   0.98340068  0.94897731]]
    <BLANKLINE>
     [[ 0.97627445  0.16384224  0.78630599]
      [ 0.00623026  0.59733394  0.8662893 ]]]
    >>> image = as_8_bit_BGR_image(image)
    >>> print(image)
    [[[ 23 137 251]
      [167 176 195]]
    <BLANKLINE>
     [[ 59 228 251]
      [250 122 219]]
    <BLANKLINE>
     [[176 238 217]
      [249 253 128]]
    <BLANKLINE>
     [[229 112 252]
      [239 203  18]]]
    >>> as_8_bit_BGR_image(image)
    array([[[ 23, 137, 251],
            [167, 176, 195]],
    <BLANKLINE>
           [[ 59, 228, 251],
            [250, 122, 219]],
    <BLANKLINE>
           [[176, 238, 217],
            [249, 253, 128]],
    <BLANKLINE>
           [[229, 112, 252],
            [239, 203,  18]]], dtype=uint8)
    """

    image = np.asarray(image)[..., :3]

    if image.dtype == np.uint8:
        return image

    return cv2.cvtColor(
        cast(NDArray,
             cctf_encoding(image) * 255).astype(np.uint8),
        cv2.COLOR_RGB2BGR,
    )
예제 #29
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)
예제 #30
0
def adjust_image(
    image: ArrayLike,
    target_width: Integer,
    interpolation_method: Literal[  # type: ignore[misc]
        cv2.INTER_AREA, cv2.INTER_BITS, cv2.INTER_BITS2, cv2.INTER_CUBIC,
        cv2.INTER_LANCZOS4, cv2.INTER_LINEAR, ] = cv2.INTER_CUBIC,
) -> NDArray:
    """
    Adjust given image so that it is horizontal and resizes it to given target
    width.

    Parameters
    ----------
    image
        Image to adjust.
    target_width
        Width the image is resized to.
    interpolation_method
        Interpolation method.

    Returns
    -------
    :class:`numpy.ndarray`
        Resized image.

    Examples
    --------
    >>> from colour.algebra import random_triplet_generator
    >>> prng = np.random.RandomState(4)
    >>> image = list(random_triplet_generator(8, random_state=prng))
    >>> image = np.reshape(image, [2, 4, 3])
    >>> adjust_image(image, 5)  # doctest: +ELLIPSIS
    array([[[ 0.9925325...,  0.2419374..., -0.0139522...],
            [ 0.6174497...,  0.3460756...,  0.3189758...],
            [ 0.7447774...,  0.678666 ...,  0.1652180...],
            [ 0.9476452...,  0.6550805...,  0.2609945...],
            [ 0.6991505...,  0.1623470...,  1.0120867...]],
    <BLANKLINE>
           [[ 0.7269885...,  0.8556784...,  0.4049920...],
            [ 0.2666565...,  1.0401633...,  0.8238320...],
            [ 0.6419699...,  0.5442698...,  0.9082211...],
            [ 0.7894426...,  0.1944301...,  0.7906868...],
            [-0.0526997...,  0.6236685...,  0.8711483...]]], dtype=float32)
    """

    image = as_float_array(image, FLOAT_DTYPE_DEFAULT)[..., :3]

    width, height = image.shape[1], image.shape[0]
    if width < height:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        height, width = width, height

    ratio = width / target_width

    if np.allclose(ratio, 1):
        return cast(NDArray, image)
    else:
        return cv2.resize(
            image,
            (as_int(target_width), as_int(height / ratio)),
            interpolation=interpolation_method,
        )