Source code for colour_visuals.pointer_gamut

# !/usr/bin/env python
"""
Pointer Gamut Visuals
=====================

Define the *Pointer's Gamut* visuals:

-   :class:`colour_visuals.VisualPointerGamut2D`
"""

from __future__ import annotations

import typing

import numpy as np
import pygfx as gfx

if typing.TYPE_CHECKING:
    from colour.hints import Any, ArrayLike, Literal, LiteralColourspaceModel

from colour.models import LCHab_to_Lab  # pyright: ignore
from colour.models import (
    CCS_ILLUMINANT_POINTER_GAMUT,
    DATA_POINTER_GAMUT_VOLUME,
    Lab_to_XYZ,
)
from colour.plotting import (
    CONSTANTS_COLOUR_STYLE,
    XYZ_to_plotting_colourspace,
    colourspace_model_axis_reorder,
    lines_pointer_gamut,
)

from colour_visuals.common import (
    DEFAULT_FLOAT_DTYPE_WGPU,
    XYZ_to_colourspace_model,
    append_channel,
    as_contiguous_array,
)
from colour_visuals.visual import (
    MixinPropertyColour,
    MixinPropertyKwargs,
    MixinPropertyMethod,
    MixinPropertyModel,
    MixinPropertyOpacity,
    MixinPropertyThickness,
    Visual,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2023 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = ["VisualPointerGamut2D", "VisualPointerGamut3D"]


[docs] class VisualPointerGamut2D( MixinPropertyColour, MixinPropertyMethod, MixinPropertyOpacity, MixinPropertyThickness, Visual, ): """ Create a 2D *Pointer's Gamut* visual. Parameters ---------- method *Chromaticity Diagram* method. colour Colour of the visual, if *None*, the colour is computed from the visual geometry. opacity Opacity of the visual. thickness Thickness of the visual lines. Attributes ---------- - :attr:`~colour_visuals.VisualPointerGamut2D.method` - :attr:`~colour_visuals.VisualPointerGamut2D.colour` - :attr:`~colour_visuals.VisualPointerGamut2D.opacity` - :attr:`~colour_visuals.VisualPointerGamut2D.thickness` Methods ------- - :meth:`~colour_visuals.VisualPointerGamut2D.__init__` - :meth:`~colour_visuals.VisualPointerGamut2D.update` Examples -------- >>> import os >>> from colour.utilities import suppress_stdout >>> from wgpu.gui.auto import WgpuCanvas >>> with suppress_stdout(): ... canvas = WgpuCanvas(size=(960, 540)) ... scene = gfx.Scene() ... scene.add( ... gfx.Background( ... None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18])) ... ) ... ) ... visual = VisualPointerGamut2D() ... camera = gfx.PerspectiveCamera(50, 16 / 9) ... camera.show_object(visual, up=np.array([0, 0, 1]), scale=1.25) ... scene.add(visual) ... if os.environ.get("CI") is None: ... gfx.show(scene, camera=camera, canvas=canvas) .. image:: ../_static/Plotting_VisualPointerGamut2D.png :align: center :alt: visual-pointer-gamut-2-d """
[docs] def __init__( self, method: ( Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str ) = "CIE 1931", colour: ArrayLike | None = None, opacity: float = 1, thickness: float = 1, ) -> None: super().__init__() self._pointer_gamut_boundary = None self._pointer_gamut_volume = None with self.block_update(): self.method = method self.colour = colour self.opacity = opacity self.thickness = thickness self.update()
[docs] def update(self) -> None: """Update the visual.""" if self._is_update_blocked: return self.clear() lines_b, lines_v = lines_pointer_gamut(self._method) # Boundary positions = np.reshape( np.concatenate([lines_b["position"][:-1], lines_b["position"][1:]], axis=1), (-1, 2), ) positions = np.hstack( [ positions, np.full((positions.shape[0], 1), 0, DEFAULT_FLOAT_DTYPE_WGPU), ] ) if self._colour is None: colour_b = np.reshape( np.concatenate([lines_b["colour"][:-1], lines_b["colour"][1:]], axis=1), (-1, 3), ) else: colour_b = np.tile(self._colour, (positions.shape[0], 1)) self._pointer_gamut_boundary = gfx.Line( gfx.Geometry( positions=as_contiguous_array(positions), colors=as_contiguous_array(append_channel(colour_b, self._opacity)), ), gfx.LineSegmentMaterial(thickness=self._thickness, color_mode="vertex"), ) self.add(self._pointer_gamut_boundary) # Volume positions = np.hstack( [ lines_v["position"], np.full( (lines_v["position"].shape[0], 1), 0, DEFAULT_FLOAT_DTYPE_WGPU, ), ] ) if self._colour is None: colour_v = lines_v["colour"] else: colour_v = np.tile(self._colour, (lines_v["colour"].shape[0], 1)) self._pointer_gamut_volume = gfx.Points( gfx.Geometry( positions=as_contiguous_array(positions), sizes=as_contiguous_array( np.full(positions.shape[0], self._thickness * 5) ), colors=as_contiguous_array(append_channel(colour_v, self._opacity)), ), gfx.PointsMaterial( color_mode="vertex", size_mode="vertex", size_space="screen" ), ) self.add(self._pointer_gamut_volume)
[docs] class VisualPointerGamut3D( MixinPropertyColour, MixinPropertyKwargs, MixinPropertyModel, MixinPropertyOpacity, MixinPropertyThickness, Visual, ): """ Create a 3D *Pointer's Gamut* visual. Parameters ---------- model Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported colourspace models. colour Colour of the visual, if *None*, the colour is computed from the visual geometry. opacity Opacity of the visual. thickness Thickness of the visual lines. Attributes ---------- - :attr:`~colour_visuals.VisualPointerGamut3D.model` - :attr:`~colour_visuals.VisualPointerGamut3D.colour` - :attr:`~colour_visuals.VisualPointerGamut3D.opacity` - :attr:`~colour_visuals.VisualPointerGamut3D.thickness` Methods ------- - :meth:`~colour_visuals.VisualPointerGamut3D.__init__` - :meth:`~colour_visuals.VisualPointerGamut3D.update` Other Parameters ---------------- kwargs See the documentation of the supported conversion definitions. Examples -------- >>> import os >>> from colour.utilities import suppress_stdout >>> from wgpu.gui.auto import WgpuCanvas >>> with suppress_stdout(): ... canvas = WgpuCanvas(size=(960, 540)) ... scene = gfx.Scene() ... scene.add( ... gfx.Background( ... None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18])) ... ) ... ) ... visual = VisualPointerGamut3D() ... camera = gfx.PerspectiveCamera(50, 16 / 9) ... camera.show_object(visual, up=np.array([0, 0, 1]), scale=1.25) ... scene.add(visual) ... if os.environ.get("CI") is None: ... gfx.show(scene, camera=camera, canvas=canvas) .. image:: ../_static/Plotting_VisualPointerGamut3D.png :align: center :alt: visual-pointer-gamut-3-d """
[docs] def __init__( self, model: LiteralColourspaceModel | str = "CIE xyY", colour: ArrayLike | None = None, opacity: float = 0.5, thickness: float = 1, **kwargs: Any, ) -> None: super().__init__() self._pointer_gamut_boundary = None self._pointer_gamut_volume = None with self.block_update(): self.model = model self.colour = colour self.opacity = opacity self.thickness = thickness self.kwargs = kwargs self.update()
[docs] def update(self) -> None: """Update the visual.""" if self._is_update_blocked: return self.clear() illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint data_pointer_gamut = np.reshape( Lab_to_XYZ( LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME), CCS_ILLUMINANT_POINTER_GAMUT, ), (16, -1, 3), ) sections = [] for i in range(16): section = np.vstack([data_pointer_gamut[i], data_pointer_gamut[i][0, ...]]) sections.append( np.reshape(np.concatenate([section[:-1], section[1:]], axis=1), (-1, 3)) ) positions = np.reshape( colourspace_model_axis_reorder( XYZ_to_colourspace_model( sections, CCS_ILLUMINANT_POINTER_GAMUT, self._model, **self._kwargs, ), self._model, ), (-1, 3), ) if self._colour is None: colour = np.reshape( XYZ_to_plotting_colourspace(sections, illuminant), (-1, 3) ) else: colour = np.tile(self._colour, (positions.shape[0], 1)) self._pointer_gamut = gfx.Line( gfx.Geometry( positions=as_contiguous_array(positions), colors=as_contiguous_array(append_channel(colour, self._opacity)), ), gfx.LineSegmentMaterial(thickness=self._thickness, color_mode="vertex"), ) self.add(self._pointer_gamut)
if __name__ == "__main__": from colour_visuals.diagrams import VisualSpectralLocus2D scene = gfx.Scene() scene.add( gfx.Background(None, gfx.BackgroundMaterial(np.array([0.18, 0.18, 0.18]))) ) visual_1 = VisualSpectralLocus2D() scene.add(visual_1) visual_2 = VisualPointerGamut2D() scene.add(visual_2) visual_3 = VisualPointerGamut2D(colour=np.array([0.5, 0.5, 0.5])) visual_3.local.position = np.array([1, 0, 0]) scene.add(visual_3) visual_4 = VisualPointerGamut3D() visual_4.local.position = np.array([2, 0, 0]) scene.add(visual_4) gfx.show(scene, up=np.array([0, 0, 1]))