Skip to content

GSP Datoviz API Reference

The GSP Datoviz backend provides high-performance rendering using the Datoviz library.

Overview

gsp_datoviz

GSP Datoviz package.

Renderer Module

The renderer module contains the main Datoviz renderer implementation and specialized renderers for different visual types.

gsp_datoviz.renderer

Renderer module for GSP Datoviz.

Datoviz Renderer

gsp_datoviz.renderer.datoviz_renderer

Datoviz renderer implementation.

  • DVZ_LOG_LEVEL environment variable can be set to control datoviz logging level.
  • e.g. DVZ_LOG_LEVEL=4 to mute all logs

DatovizRenderer

Bases: gsp.types.renderer_base.RendererBase

Datoviz renderer implementation.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
class DatovizRenderer(RendererBase):
    """Datoviz renderer implementation."""

    def __init__(self, canvas: Canvas, offscreen: bool = False) -> None:
        """Initialize the Datoviz renderer.

        Args:
            canvas (Canvas): The GSP canvas to render on.
            offscreen (bool, optional): Whether to run the datoviz App in offscreen mode. Defaults to False.
        """
        self._canvas = canvas
        self._dvz_app: dvz.App = dvz.App(background="white", offscreen=offscreen)
        self._dvz_offscreen = offscreen
        self._dvz_figure: _DvzFigure = self._dvz_app.figure(
            width=canvas.get_width(),
            height=canvas.get_height(),
        )
        self._dvz_panels: dict[str, _DvzPanel] = {}
        """datoviz panel per gsp viewport UUID"""
        self._dvz_visuals: dict[str, _DvzVisual] = {}
        """datoviz visual per gsp visual group UUID"""
        self._dvz_textures: dict[str, _DvzTexture] = {}
        """datoviz texture per gsp texture UUID"""

        self._group_count: dict[str, int] = {}
        """group count per visual UUID"""

    def close(self) -> None:
        """Close the datoviz renderer and its app."""
        self._dvz_app.destroy()

    def get_canvas(self) -> Canvas:
        """Get the GSP canvas associated with the renderer."""
        return self._canvas

    def get_dvz_app(self) -> dvz.App:
        """Get the datoviz App associated with the renderer."""
        return self._dvz_app

    def get_dvz_figure(self) -> _DvzFigure:
        """Get the datoviz Figure associated with the renderer."""
        return self._dvz_figure

    def show(self) -> None:
        """Show the datoviz window and start the app."""
        # handle non-interactive mode for tests
        in_test = os.environ.get("GSP_TEST") == "True"
        if in_test:
            return

        # listen to keyboard events - if 'q' is pressed, stop the app
        @self._dvz_app.connect(self._dvz_figure)
        def on_keyboard(event):
            # print(f"{event.key_event()} key {event.key()} ({event.key_name()})")
            if event.key_event() == "press" and event.key_name() == "q":
                self._dvz_app.stop()

        # run the datoviz app to show the window
        self._dvz_app.run()

    # =============================================================================
    # .render() function
    # =============================================================================
    def render(
        self,
        viewports: Sequence[Viewport],
        visuals: Sequence[VisualBase],
        model_matrices: Sequence[TransBuf],
        cameras: Sequence[Camera],
        return_image: bool = True,  # NOTE: make False by default. datoviz screenshot can cause segmentation fault in some cases
        image_format: str = "png",
    ) -> bytes:
        """Render the given viewports and visuals using datoviz.

        Args:
            viewports (Sequence[Viewport]): Sequence of viewports to render.
            visuals (Sequence[VisualBase]): Sequence of visual objects to render.
            model_matrices (Sequence[TransBuf]): Sequence of transformation buffers for the visuals.
            cameras (Sequence[Camera]): Sequence of cameras for each viewport.
            return_image (bool, optional): Whether to return the rendered image as bytes. Defaults to True.
            image_format (str, optional): The image format to return ("png"). Defaults to "png".

        Returns:
            bytes: The rendered image data if return_image is True, else empty bytes.
        """
        # =============================================================================
        # Create all viewport if needed
        # =============================================================================

        for viewport in viewports:
            _dvz_panel = self._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Render all visual
        # =============================================================================

        for viewport, visual, model_matrix, camera in zip(viewports, visuals, model_matrices, cameras):
            self._render_visual(viewport, visual, model_matrix, camera)

        # =============================================================================
        # Return an image if needed
        # =============================================================================

        # sanity check
        has_offscreen = bool(self._dvz_app.c_flags | dvz.APP_FLAGS_OFFSCREEN)
        if return_image and not has_offscreen:
            raise Exception("DatovizRenderer.render(): cannot return image when datoviz App is not in offscreen mode")

        rendered_image = b""
        if return_image:
            if self._dvz_offscreen is True:
                assert image_format in ["png"], f"Unsupported image format: {image_format}"
                image_path = pathlib.Path(__file__).parent / "_datoviz_offscreen_python.png"
                self._dvz_app.screenshot(self._dvz_figure, str(image_path))
                with open(image_path, "rb") as file_reader:
                    rendered_image = file_reader.read()
                image_path.unlink()
            else:
                # NOTE: datoviz requires the datoviz App to be in offscreen mode to capture screenshot
                # - this is a workaround to init a temporary offscreen datoviz App to capture the image

                # Init a temporary offscreen datoviz renderer to capture the image
                _renderer_offscreen = DatovizRenderer(self._canvas, offscreen=True)
                # do render call
                rendered_image = _renderer_offscreen.render(viewports, visuals, model_matrices, cameras, return_image=True)
                # close the offscreen renderer
                _renderer_offscreen.close()

        return rendered_image

    # =============================================================================
    # ._render_pixels()
    # =============================================================================

    def _render_visual(self, viewport: Viewport, visual: VisualBase, model_matrix: TransBuf, camera: Camera) -> None:
        if isinstance(visual, Image):
            from .datoviz_renderer_image import DatovizRendererImage

            DatovizRendererImage.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Pixels):
            from .datoviz_renderer_pixels import DatovizRendererPixels

            DatovizRendererPixels.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Points):
            from .datoviz_renderer_points import DatovizRendererPoints

            DatovizRendererPoints.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Paths):
            from .datoviz_renderer_paths import DatovizRendererPaths

            DatovizRendererPaths.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Markers):
            from .datoviz_renderer_markers import DatovizRendererMarkers

            DatovizRendererMarkers.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Segments):
            from .datoviz_renderer_segments import DatovizRendererSegments

            DatovizRendererSegments.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Texts):
            from .datoviz_renderer_texts import DatovizRendererTexts

            DatovizRendererTexts.render(self, viewport, visual, model_matrix, camera)
        else:
            raise NotImplementedError(f"DatovizRenderer.render() does not support visual of type {type(visual)}")

    # =============================================================================
    # Get or create datoviz panel for viewport
    # =============================================================================

    def _getOrCreateDvzPanel(self, viewport: Viewport) -> _DvzPanel:
        viewport_uuid = viewport.get_uuid()
        # if it already exists, return it
        if viewport_uuid in self._dvz_panels:
            return self._dvz_panels[viewport_uuid]

        # create the datoviz panel
        dvz_offset = (viewport.get_x(), self.get_canvas().get_height() - viewport.get_y() - viewport.get_height())
        # dvz_offset = (viewport.get_x(), viewport.get_y())
        dvz_size = (viewport.get_width(), viewport.get_height())
        dvz_panel = self._dvz_figure.panel(
            offset=dvz_offset,
            size=dvz_size,
        )

        # store it
        self._dvz_panels[viewport_uuid] = dvz_panel

        # return newly created panel
        return dvz_panel

__init__(canvas: Canvas, offscreen: bool = False) -> None

Initialize the Datoviz renderer.

Parameters:

Name Type Description Default
canvas gsp.core.canvas.Canvas

The GSP canvas to render on.

required
offscreen bool

Whether to run the datoviz App in offscreen mode. Defaults to False.

False
Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(self, canvas: Canvas, offscreen: bool = False) -> None:
    """Initialize the Datoviz renderer.

    Args:
        canvas (Canvas): The GSP canvas to render on.
        offscreen (bool, optional): Whether to run the datoviz App in offscreen mode. Defaults to False.
    """
    self._canvas = canvas
    self._dvz_app: dvz.App = dvz.App(background="white", offscreen=offscreen)
    self._dvz_offscreen = offscreen
    self._dvz_figure: _DvzFigure = self._dvz_app.figure(
        width=canvas.get_width(),
        height=canvas.get_height(),
    )
    self._dvz_panels: dict[str, _DvzPanel] = {}
    """datoviz panel per gsp viewport UUID"""
    self._dvz_visuals: dict[str, _DvzVisual] = {}
    """datoviz visual per gsp visual group UUID"""
    self._dvz_textures: dict[str, _DvzTexture] = {}
    """datoviz texture per gsp texture UUID"""

    self._group_count: dict[str, int] = {}
    """group count per visual UUID"""

close() -> None

Close the datoviz renderer and its app.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
70
71
72
def close(self) -> None:
    """Close the datoviz renderer and its app."""
    self._dvz_app.destroy()

get_canvas() -> Canvas

Get the GSP canvas associated with the renderer.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
74
75
76
def get_canvas(self) -> Canvas:
    """Get the GSP canvas associated with the renderer."""
    return self._canvas

get_dvz_app() -> dvz.App

Get the datoviz App associated with the renderer.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
78
79
80
def get_dvz_app(self) -> dvz.App:
    """Get the datoviz App associated with the renderer."""
    return self._dvz_app

get_dvz_figure() -> _DvzFigure

Get the datoviz Figure associated with the renderer.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
82
83
84
def get_dvz_figure(self) -> _DvzFigure:
    """Get the datoviz Figure associated with the renderer."""
    return self._dvz_figure

render(viewports: Sequence[Viewport], visuals: Sequence[VisualBase], model_matrices: Sequence[TransBuf], cameras: Sequence[Camera], return_image: bool = True, image_format: str = 'png') -> bytes

Render the given viewports and visuals using datoviz.

Parameters:

Name Type Description Default
viewports typing.Sequence[gsp.core.viewport.Viewport]

Sequence of viewports to render.

required
visuals typing.Sequence[gsp.types.visual_base.VisualBase]

Sequence of visual objects to render.

required
model_matrices typing.Sequence[gsp.types.transbuf.TransBuf]

Sequence of transformation buffers for the visuals.

required
cameras typing.Sequence[gsp.core.camera.Camera]

Sequence of cameras for each viewport.

required
return_image bool

Whether to return the rendered image as bytes. Defaults to True.

True
image_format str

The image format to return ("png"). Defaults to "png".

'png'

Returns:

Name Type Description
bytes bytes

The rendered image data if return_image is True, else empty bytes.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def render(
    self,
    viewports: Sequence[Viewport],
    visuals: Sequence[VisualBase],
    model_matrices: Sequence[TransBuf],
    cameras: Sequence[Camera],
    return_image: bool = True,  # NOTE: make False by default. datoviz screenshot can cause segmentation fault in some cases
    image_format: str = "png",
) -> bytes:
    """Render the given viewports and visuals using datoviz.

    Args:
        viewports (Sequence[Viewport]): Sequence of viewports to render.
        visuals (Sequence[VisualBase]): Sequence of visual objects to render.
        model_matrices (Sequence[TransBuf]): Sequence of transformation buffers for the visuals.
        cameras (Sequence[Camera]): Sequence of cameras for each viewport.
        return_image (bool, optional): Whether to return the rendered image as bytes. Defaults to True.
        image_format (str, optional): The image format to return ("png"). Defaults to "png".

    Returns:
        bytes: The rendered image data if return_image is True, else empty bytes.
    """
    # =============================================================================
    # Create all viewport if needed
    # =============================================================================

    for viewport in viewports:
        _dvz_panel = self._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Render all visual
    # =============================================================================

    for viewport, visual, model_matrix, camera in zip(viewports, visuals, model_matrices, cameras):
        self._render_visual(viewport, visual, model_matrix, camera)

    # =============================================================================
    # Return an image if needed
    # =============================================================================

    # sanity check
    has_offscreen = bool(self._dvz_app.c_flags | dvz.APP_FLAGS_OFFSCREEN)
    if return_image and not has_offscreen:
        raise Exception("DatovizRenderer.render(): cannot return image when datoviz App is not in offscreen mode")

    rendered_image = b""
    if return_image:
        if self._dvz_offscreen is True:
            assert image_format in ["png"], f"Unsupported image format: {image_format}"
            image_path = pathlib.Path(__file__).parent / "_datoviz_offscreen_python.png"
            self._dvz_app.screenshot(self._dvz_figure, str(image_path))
            with open(image_path, "rb") as file_reader:
                rendered_image = file_reader.read()
            image_path.unlink()
        else:
            # NOTE: datoviz requires the datoviz App to be in offscreen mode to capture screenshot
            # - this is a workaround to init a temporary offscreen datoviz App to capture the image

            # Init a temporary offscreen datoviz renderer to capture the image
            _renderer_offscreen = DatovizRenderer(self._canvas, offscreen=True)
            # do render call
            rendered_image = _renderer_offscreen.render(viewports, visuals, model_matrices, cameras, return_image=True)
            # close the offscreen renderer
            _renderer_offscreen.close()

    return rendered_image

show() -> None

Show the datoviz window and start the app.

Source code in src/gsp_datoviz/renderer/datoviz_renderer.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def show(self) -> None:
    """Show the datoviz window and start the app."""
    # handle non-interactive mode for tests
    in_test = os.environ.get("GSP_TEST") == "True"
    if in_test:
        return

    # listen to keyboard events - if 'q' is pressed, stop the app
    @self._dvz_app.connect(self._dvz_figure)
    def on_keyboard(event):
        # print(f"{event.key_event()} key {event.key()} ({event.key_name()})")
        if event.key_event() == "press" and event.key_name() == "q":
            self._dvz_app.stop()

    # run the datoviz app to show the window
    self._dvz_app.run()

Markers Renderer

gsp_datoviz.renderer.datoviz_renderer_markers

Datoviz renderer for Markers visuals.

DatovizRendererMarkers

Datoviz renderer for Markers visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_markers.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class DatovizRendererMarkers:
    """Datoviz renderer for Markers visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        markers: Markers,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Markers visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            markers (Markers): The Markers visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(markers.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Convert all attributes to numpy arrays
        # =============================================================================

        sizes_buffer = TransBufUtils.to_buffer(markers.get_sizes())
        face_colors_buffer = TransBufUtils.to_buffer(markers.get_face_colors())
        edge_colors_buffer = TransBufUtils.to_buffer(markers.get_edge_colors())
        edge_widths_buffer = TransBufUtils.to_buffer(markers.get_edge_widths())

        # Convert buffers to numpy arrays)
        sizes_pt2_numpy = Bufferx.to_numpy(sizes_buffer)
        face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)
        edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer)
        edge_widths_pt_numpy = Bufferx.to_numpy(edge_widths_buffer)

        # Convert sizes from point^2 to pixel
        radius_pt_numpy = np.sqrt(sizes_pt2_numpy / np.pi)
        radius_px_numpy = UnitUtils.point_to_pixel_numpy(radius_pt_numpy, renderer.get_canvas().get_dpi())
        diameter_px_numpy = radius_px_numpy * 2.0 * UnitUtils.device_pixel_ratio()
        diameter_px_numpy = diameter_px_numpy.reshape(-1)

        edge_widths_px_numpy = UnitUtils.point_to_pixel_numpy(edge_widths_pt_numpy, renderer.get_canvas().get_dpi())
        edge_widths_px_numpy = edge_widths_px_numpy * UnitUtils.device_pixel_ratio()
        edge_widths_px_numpy = edge_widths_px_numpy.reshape(-1)

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Markers.sanity_check_attributes_buffer(
            markers.get_marker_shape(),
            vertices_buffer,
            sizes_buffer,
            face_colors_buffer,
            edge_colors_buffer,
            edge_widths_buffer,
        )

        # =============================================================================
        # Create the datoviz visual if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{markers.get_uuid()}"

        # Create datoviz_visual if they do not exist
        if artist_uuid not in renderer._dvz_visuals:
            dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
            dvz_markers = renderer._dvz_app.marker(position=dummy_position_numpy)
            renderer._dvz_visuals[artist_uuid] = dvz_markers
            # Add the new visual to the panel
            dvz_panel.add(dvz_markers)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        # get the datoviz visual
        dvz_markers = typing.cast(_DvzMarkers, renderer._dvz_visuals[artist_uuid])

        # set attributes
        dvz_markers.set_position(vertices_3d)

        # Set the proper parameters
        dvz_markers.set_size(diameter_px_numpy)
        dvz_markers.set_color(face_colors_numpy)
        dvz_markers.set_linewidth(edge_widths_px_numpy[0])
        dvz_markers.set_edgecolor(edge_colors_numpy[0].tolist())  # datoviz only supports a single edge color

        # sanity check - if edge_widths_px_numpy are not all the same, warn the user
        if not np.all(edge_widths_px_numpy == edge_widths_px_numpy[0]):
            warnings.warn("DatovizRendererMarkers: edge widths per marker are not fully supported by datoviz. " "Using the first edge width for all markers.")
        # sanity check - if edge_colors_numpy are not all the same, warn the user
        if not np.all(edge_colors_numpy == edge_colors_numpy[0]):
            warnings.warn("DatovizRendererMarkers: edge colors per marker are not fully supported by datoviz. " "Using the first edge color for all markers.")

        # Set mode, shape and aspect
        dvz_markers.set_mode("code")
        dvz_markers.set_shape(ConverterUtils.marker_shape_gsp_to_dvz(markers.get_marker_shape()))

        # Determine the `aspect` depending on face and edge colors/widths
        is_face_color_transparent = bool(np.all(face_colors_numpy[:, 3] == 0))
        is_edge_color_transparent = bool(np.all(edge_colors_numpy[:, 3] == 0))
        is_edge_width_zero = bool(np.all(edge_widths_px_numpy == 0))
        has_edge = not is_edge_color_transparent and not is_edge_width_zero
        if not is_face_color_transparent:
            if has_edge:
                dvz_markers.set_aspect("outline")
            else:
                dvz_markers.set_aspect("filled")
        else:
            dvz_markers.set_aspect("stroke")

render(renderer: DatovizRenderer, viewport: Viewport, markers: Markers, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Markers visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
markers gsp.visuals.markers.Markers

The Markers visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_markers.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    markers: Markers,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Markers visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        markers (Markers): The Markers visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(markers.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Convert all attributes to numpy arrays
    # =============================================================================

    sizes_buffer = TransBufUtils.to_buffer(markers.get_sizes())
    face_colors_buffer = TransBufUtils.to_buffer(markers.get_face_colors())
    edge_colors_buffer = TransBufUtils.to_buffer(markers.get_edge_colors())
    edge_widths_buffer = TransBufUtils.to_buffer(markers.get_edge_widths())

    # Convert buffers to numpy arrays)
    sizes_pt2_numpy = Bufferx.to_numpy(sizes_buffer)
    face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)
    edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer)
    edge_widths_pt_numpy = Bufferx.to_numpy(edge_widths_buffer)

    # Convert sizes from point^2 to pixel
    radius_pt_numpy = np.sqrt(sizes_pt2_numpy / np.pi)
    radius_px_numpy = UnitUtils.point_to_pixel_numpy(radius_pt_numpy, renderer.get_canvas().get_dpi())
    diameter_px_numpy = radius_px_numpy * 2.0 * UnitUtils.device_pixel_ratio()
    diameter_px_numpy = diameter_px_numpy.reshape(-1)

    edge_widths_px_numpy = UnitUtils.point_to_pixel_numpy(edge_widths_pt_numpy, renderer.get_canvas().get_dpi())
    edge_widths_px_numpy = edge_widths_px_numpy * UnitUtils.device_pixel_ratio()
    edge_widths_px_numpy = edge_widths_px_numpy.reshape(-1)

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Markers.sanity_check_attributes_buffer(
        markers.get_marker_shape(),
        vertices_buffer,
        sizes_buffer,
        face_colors_buffer,
        edge_colors_buffer,
        edge_widths_buffer,
    )

    # =============================================================================
    # Create the datoviz visual if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{markers.get_uuid()}"

    # Create datoviz_visual if they do not exist
    if artist_uuid not in renderer._dvz_visuals:
        dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
        dvz_markers = renderer._dvz_app.marker(position=dummy_position_numpy)
        renderer._dvz_visuals[artist_uuid] = dvz_markers
        # Add the new visual to the panel
        dvz_panel.add(dvz_markers)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    # get the datoviz visual
    dvz_markers = typing.cast(_DvzMarkers, renderer._dvz_visuals[artist_uuid])

    # set attributes
    dvz_markers.set_position(vertices_3d)

    # Set the proper parameters
    dvz_markers.set_size(diameter_px_numpy)
    dvz_markers.set_color(face_colors_numpy)
    dvz_markers.set_linewidth(edge_widths_px_numpy[0])
    dvz_markers.set_edgecolor(edge_colors_numpy[0].tolist())  # datoviz only supports a single edge color

    # sanity check - if edge_widths_px_numpy are not all the same, warn the user
    if not np.all(edge_widths_px_numpy == edge_widths_px_numpy[0]):
        warnings.warn("DatovizRendererMarkers: edge widths per marker are not fully supported by datoviz. " "Using the first edge width for all markers.")
    # sanity check - if edge_colors_numpy are not all the same, warn the user
    if not np.all(edge_colors_numpy == edge_colors_numpy[0]):
        warnings.warn("DatovizRendererMarkers: edge colors per marker are not fully supported by datoviz. " "Using the first edge color for all markers.")

    # Set mode, shape and aspect
    dvz_markers.set_mode("code")
    dvz_markers.set_shape(ConverterUtils.marker_shape_gsp_to_dvz(markers.get_marker_shape()))

    # Determine the `aspect` depending on face and edge colors/widths
    is_face_color_transparent = bool(np.all(face_colors_numpy[:, 3] == 0))
    is_edge_color_transparent = bool(np.all(edge_colors_numpy[:, 3] == 0))
    is_edge_width_zero = bool(np.all(edge_widths_px_numpy == 0))
    has_edge = not is_edge_color_transparent and not is_edge_width_zero
    if not is_face_color_transparent:
        if has_edge:
            dvz_markers.set_aspect("outline")
        else:
            dvz_markers.set_aspect("filled")
    else:
        dvz_markers.set_aspect("stroke")

Paths Renderer

gsp_datoviz.renderer.datoviz_renderer_paths

Datoviz renderer for Paths visuals.

DatovizRendererPaths

Datoviz renderer for Paths visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_paths.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class DatovizRendererPaths:
    """Datoviz renderer for Paths visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        paths: Paths,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Paths visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            paths (Paths): The Paths visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)
        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(paths.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Get attributes
        # =============================================================================

        # get attributes from TransBuf to buffer
        path_sizes_buffer = TransBufUtils.to_buffer(paths.get_path_sizes())
        colors_buffer = TransBufUtils.to_buffer(paths.get_colors())
        line_widths_buffer = TransBufUtils.to_buffer(paths.get_line_widths())

        # convert buffers to numpy arrays
        path_sizes_numpy = Bufferx.to_numpy(path_sizes_buffer)
        colors_numpy = Bufferx.to_numpy(colors_buffer)
        line_widths_pt_numpy = Bufferx.to_numpy(line_widths_buffer)

        # Convert sizes from point^2 to pixel diameter
        line_widths_px_numpy = UnitUtils.point_to_pixel_numpy(line_widths_pt_numpy, renderer.get_canvas().get_dpi())

        path_sizes_numpy = path_sizes_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input
        line_widths_px_numpy = line_widths_px_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Paths.sanity_check_attributes_buffer(
            vertices_buffer,
            path_sizes_buffer,
            colors_buffer,
            line_widths_buffer,
            paths.get_cap_style(),
            paths.get_join_style(),
        )

        # =============================================================================
        # Create the datoviz visual if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{paths.get_uuid()}"

        # Create datoviz_visual if they do not exist
        if artist_uuid not in renderer._dvz_visuals:
            dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
            dummy_path_sizes_numpy = np.array([1], dtype=np.uint32)
            dvz_paths = renderer._dvz_app.path()
            dvz_paths.set_position(dummy_position_numpy, groups=dummy_path_sizes_numpy)
            renderer._dvz_visuals[artist_uuid] = dvz_paths
            # Add the new visual to the panel
            dvz_panel.add(dvz_paths)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        # get the datoviz visual
        dvz_paths = typing.cast(_DvzPaths, renderer._dvz_visuals[artist_uuid])

        dvz_paths.set_position(vertices_3d, groups=path_sizes_numpy)
        dvz_paths.set_color(colors_numpy)
        dvz_paths.set_linewidth(line_widths_px_numpy)
        dvz_paths.set_cap(ConverterUtils.cap_style_gsp_to_dvz(paths.get_cap_style()))
        dvz_paths.set_join(ConverterUtils.join_style_gsp_to_dvz(paths.get_join_style()))

render(renderer: DatovizRenderer, viewport: Viewport, paths: Paths, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Paths visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
paths gsp.visuals.paths.Paths

The Paths visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_paths.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    paths: Paths,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Paths visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        paths (Paths): The Paths visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)
    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(paths.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Get attributes
    # =============================================================================

    # get attributes from TransBuf to buffer
    path_sizes_buffer = TransBufUtils.to_buffer(paths.get_path_sizes())
    colors_buffer = TransBufUtils.to_buffer(paths.get_colors())
    line_widths_buffer = TransBufUtils.to_buffer(paths.get_line_widths())

    # convert buffers to numpy arrays
    path_sizes_numpy = Bufferx.to_numpy(path_sizes_buffer)
    colors_numpy = Bufferx.to_numpy(colors_buffer)
    line_widths_pt_numpy = Bufferx.to_numpy(line_widths_buffer)

    # Convert sizes from point^2 to pixel diameter
    line_widths_px_numpy = UnitUtils.point_to_pixel_numpy(line_widths_pt_numpy, renderer.get_canvas().get_dpi())

    path_sizes_numpy = path_sizes_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input
    line_widths_px_numpy = line_widths_px_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Paths.sanity_check_attributes_buffer(
        vertices_buffer,
        path_sizes_buffer,
        colors_buffer,
        line_widths_buffer,
        paths.get_cap_style(),
        paths.get_join_style(),
    )

    # =============================================================================
    # Create the datoviz visual if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{paths.get_uuid()}"

    # Create datoviz_visual if they do not exist
    if artist_uuid not in renderer._dvz_visuals:
        dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
        dummy_path_sizes_numpy = np.array([1], dtype=np.uint32)
        dvz_paths = renderer._dvz_app.path()
        dvz_paths.set_position(dummy_position_numpy, groups=dummy_path_sizes_numpy)
        renderer._dvz_visuals[artist_uuid] = dvz_paths
        # Add the new visual to the panel
        dvz_panel.add(dvz_paths)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    # get the datoviz visual
    dvz_paths = typing.cast(_DvzPaths, renderer._dvz_visuals[artist_uuid])

    dvz_paths.set_position(vertices_3d, groups=path_sizes_numpy)
    dvz_paths.set_color(colors_numpy)
    dvz_paths.set_linewidth(line_widths_px_numpy)
    dvz_paths.set_cap(ConverterUtils.cap_style_gsp_to_dvz(paths.get_cap_style()))
    dvz_paths.set_join(ConverterUtils.join_style_gsp_to_dvz(paths.get_join_style()))

Pixels Renderer

gsp_datoviz.renderer.datoviz_renderer_pixels

Datoviz renderer for Pixels visuals.

DatovizRendererPixels

Datoviz renderer for Pixels visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_pixels.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class DatovizRendererPixels:
    """Datoviz renderer for Pixels visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        pixels: Pixels,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Pixels visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            pixels (Pixels): The Pixels visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(pixels.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Get attributes
        # =============================================================================

        # get attributes from TransBuf to buffer
        colors_buffer = TransBufUtils.to_buffer(pixels.get_colors())

        # convert buffers to numpy arrays
        colors_numpy = Bufferx.to_numpy(colors_buffer)

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Pixels.sanity_check_attributes_buffer(vertices_buffer, colors_buffer, pixels.get_groups())

        # =============================================================================
        #   Compute indices_per_group for groups depending on the type of groups
        # =============================================================================

        indices_per_group = GroupUtils.compute_indices_per_group(vertices_3d.__len__(), pixels.get_groups())
        group_count = GroupUtils.get_group_count(vertices_3d.__len__(), pixels.get_groups())

        # =============================================================================
        # Create the datoviz pixels if needed
        # =============================================================================

        artist_uuid_prefix = f"{viewport.get_uuid()}_{pixels.get_uuid()}"

        # update stored group count
        old_group_count = None
        if artist_uuid_prefix in renderer._group_count:
            old_group_count = renderer._group_count[artist_uuid_prefix]
        renderer._group_count[artist_uuid_prefix] = group_count

        # If the group count has changed, destroy old datoviz_visuals
        if old_group_count is not None and old_group_count != group_count:
            for group_index in range(old_group_count):
                group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
                if group_uuid in renderer._dvz_visuals:
                    dvz_pixels = typing.cast(_DvzPixel, renderer._dvz_visuals[group_uuid])
                    dvz_panel.remove(dvz_pixels)
                    del renderer._dvz_visuals[group_uuid]

        # Create datoviz_visual if they do not exist
        sample_group_uuid = f"{artist_uuid_prefix}_group_0"
        if sample_group_uuid not in renderer._dvz_visuals:
            for group_index in range(group_count):
                dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
                dummy_color_numpy = np.array([[255, 0, 0, 255]], dtype=np.uint8).reshape((-1, 4))
                dvz_pixels = renderer._dvz_app.pixel(
                    position=dummy_position_numpy,
                    color=dummy_color_numpy,
                )
                group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
                renderer._dvz_visuals[group_uuid] = dvz_pixels
                # Add the new pixels to the panel
                dvz_panel.add(dvz_pixels)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        for group_index in range(group_count):
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"

            # get the datoviz pixels
            dvz_pixels = typing.cast(_DvzPixel, renderer._dvz_visuals[group_uuid])

            # set attributes
            group_vertices = vertices_3d[indices_per_group[group_index]]
            dvz_pixels.set_position(group_vertices)

            # set group_colors
            group_colors = np.tile(colors_numpy[group_index], group_vertices.__len__()).reshape((-1, 4))
            dvz_pixels.set_color(group_colors)

render(renderer: DatovizRenderer, viewport: Viewport, pixels: Pixels, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Pixels visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
pixels gsp.visuals.pixels.Pixels

The Pixels visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_pixels.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    pixels: Pixels,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Pixels visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        pixels (Pixels): The Pixels visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(pixels.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Get attributes
    # =============================================================================

    # get attributes from TransBuf to buffer
    colors_buffer = TransBufUtils.to_buffer(pixels.get_colors())

    # convert buffers to numpy arrays
    colors_numpy = Bufferx.to_numpy(colors_buffer)

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Pixels.sanity_check_attributes_buffer(vertices_buffer, colors_buffer, pixels.get_groups())

    # =============================================================================
    #   Compute indices_per_group for groups depending on the type of groups
    # =============================================================================

    indices_per_group = GroupUtils.compute_indices_per_group(vertices_3d.__len__(), pixels.get_groups())
    group_count = GroupUtils.get_group_count(vertices_3d.__len__(), pixels.get_groups())

    # =============================================================================
    # Create the datoviz pixels if needed
    # =============================================================================

    artist_uuid_prefix = f"{viewport.get_uuid()}_{pixels.get_uuid()}"

    # update stored group count
    old_group_count = None
    if artist_uuid_prefix in renderer._group_count:
        old_group_count = renderer._group_count[artist_uuid_prefix]
    renderer._group_count[artist_uuid_prefix] = group_count

    # If the group count has changed, destroy old datoviz_visuals
    if old_group_count is not None and old_group_count != group_count:
        for group_index in range(old_group_count):
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
            if group_uuid in renderer._dvz_visuals:
                dvz_pixels = typing.cast(_DvzPixel, renderer._dvz_visuals[group_uuid])
                dvz_panel.remove(dvz_pixels)
                del renderer._dvz_visuals[group_uuid]

    # Create datoviz_visual if they do not exist
    sample_group_uuid = f"{artist_uuid_prefix}_group_0"
    if sample_group_uuid not in renderer._dvz_visuals:
        for group_index in range(group_count):
            dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
            dummy_color_numpy = np.array([[255, 0, 0, 255]], dtype=np.uint8).reshape((-1, 4))
            dvz_pixels = renderer._dvz_app.pixel(
                position=dummy_position_numpy,
                color=dummy_color_numpy,
            )
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
            renderer._dvz_visuals[group_uuid] = dvz_pixels
            # Add the new pixels to the panel
            dvz_panel.add(dvz_pixels)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    for group_index in range(group_count):
        group_uuid = f"{artist_uuid_prefix}_group_{group_index}"

        # get the datoviz pixels
        dvz_pixels = typing.cast(_DvzPixel, renderer._dvz_visuals[group_uuid])

        # set attributes
        group_vertices = vertices_3d[indices_per_group[group_index]]
        dvz_pixels.set_position(group_vertices)

        # set group_colors
        group_colors = np.tile(colors_numpy[group_index], group_vertices.__len__()).reshape((-1, 4))
        dvz_pixels.set_color(group_colors)

Points Renderer

gsp_datoviz.renderer.datoviz_renderer_points

Datoviz renderer for Points visuals.

DatovizRendererPoints

Datoviz renderer for Points visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_points.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class DatovizRendererPoints:
    """Datoviz renderer for Points visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        points: Points,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Points visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            points (Points): The Points visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(points.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Get attributes
        # =============================================================================

        # get attributes from TransBuf to buffer
        sizes_buffer = TransBufUtils.to_buffer(points.get_sizes())
        face_colors_buffer = TransBufUtils.to_buffer(points.get_face_colors())
        edge_colors_buffer = TransBufUtils.to_buffer(points.get_edge_colors())
        edge_widths_buffer = TransBufUtils.to_buffer(points.get_edge_widths())

        # convert buffers to numpy arrays
        face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)
        edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer) / 255.0  # normalize to [0, 1] range
        edge_widths_numpy = Bufferx.to_numpy(edge_widths_buffer).flatten()

        # Convert sizes from point^2 to pixel diameter
        sizes_pt2_numpy = Bufferx.to_numpy(sizes_buffer)
        radius_pt_numpy = np.sqrt(sizes_pt2_numpy / np.pi)
        radius_px_numpy = UnitUtils.point_to_pixel_numpy(radius_pt_numpy, renderer.get_canvas().get_dpi())
        diameter_px_numpy = radius_px_numpy * 2.0 * UnitUtils.device_pixel_ratio()

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Points.sanity_check_attributes_buffer(
            vertices_buffer,
            sizes_buffer,
            face_colors_buffer,
            edge_colors_buffer,
            edge_widths_buffer,
        )

        # =============================================================================
        # Create the datoviz visual if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{points.get_uuid()}"

        # Create datoviz_visual if they do not exist
        if artist_uuid not in renderer._dvz_visuals:
            dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
            dummy_color_numpy = np.array([[255, 0, 0, 255]], dtype=np.uint8).reshape((-1, 4))
            dummy_size_numpy = np.array([1], dtype=np.float32).reshape((-1, 1)).reshape(-1)
            dvz_points = renderer._dvz_app.point(
                position=dummy_position_numpy,
                color=dummy_color_numpy,
                size=dummy_size_numpy,
            )
            renderer._dvz_visuals[artist_uuid] = dvz_points
            # Add the new visual to the panel
            dvz_panel.add(dvz_points)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        # get the datoviz visual
        dvz_points = typing.cast(_DvzPoints, renderer._dvz_visuals[artist_uuid])

        # set attributes
        dvz_points.set_position(vertices_3d)

        # set group_sizes
        group_sizes = diameter_px_numpy.reshape((-1, 1))
        group_sizes = group_sizes.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input
        dvz_points.set_size(group_sizes)

        # set group_colors
        group_colors = face_colors_numpy.reshape((-1, 4))
        dvz_points.set_color(group_colors)

render(renderer: DatovizRenderer, viewport: Viewport, points: Points, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Points visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
points gsp.visuals.points.Points

The Points visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_points.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    points: Points,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Points visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        points (Points): The Points visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(points.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Get attributes
    # =============================================================================

    # get attributes from TransBuf to buffer
    sizes_buffer = TransBufUtils.to_buffer(points.get_sizes())
    face_colors_buffer = TransBufUtils.to_buffer(points.get_face_colors())
    edge_colors_buffer = TransBufUtils.to_buffer(points.get_edge_colors())
    edge_widths_buffer = TransBufUtils.to_buffer(points.get_edge_widths())

    # convert buffers to numpy arrays
    face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)
    edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer) / 255.0  # normalize to [0, 1] range
    edge_widths_numpy = Bufferx.to_numpy(edge_widths_buffer).flatten()

    # Convert sizes from point^2 to pixel diameter
    sizes_pt2_numpy = Bufferx.to_numpy(sizes_buffer)
    radius_pt_numpy = np.sqrt(sizes_pt2_numpy / np.pi)
    radius_px_numpy = UnitUtils.point_to_pixel_numpy(radius_pt_numpy, renderer.get_canvas().get_dpi())
    diameter_px_numpy = radius_px_numpy * 2.0 * UnitUtils.device_pixel_ratio()

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Points.sanity_check_attributes_buffer(
        vertices_buffer,
        sizes_buffer,
        face_colors_buffer,
        edge_colors_buffer,
        edge_widths_buffer,
    )

    # =============================================================================
    # Create the datoviz visual if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{points.get_uuid()}"

    # Create datoviz_visual if they do not exist
    if artist_uuid not in renderer._dvz_visuals:
        dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
        dummy_color_numpy = np.array([[255, 0, 0, 255]], dtype=np.uint8).reshape((-1, 4))
        dummy_size_numpy = np.array([1], dtype=np.float32).reshape((-1, 1)).reshape(-1)
        dvz_points = renderer._dvz_app.point(
            position=dummy_position_numpy,
            color=dummy_color_numpy,
            size=dummy_size_numpy,
        )
        renderer._dvz_visuals[artist_uuid] = dvz_points
        # Add the new visual to the panel
        dvz_panel.add(dvz_points)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    # get the datoviz visual
    dvz_points = typing.cast(_DvzPoints, renderer._dvz_visuals[artist_uuid])

    # set attributes
    dvz_points.set_position(vertices_3d)

    # set group_sizes
    group_sizes = diameter_px_numpy.reshape((-1, 1))
    group_sizes = group_sizes.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input
    dvz_points.set_size(group_sizes)

    # set group_colors
    group_colors = face_colors_numpy.reshape((-1, 4))
    dvz_points.set_color(group_colors)

Segments Renderer

gsp_datoviz.renderer.datoviz_renderer_segments

Datoviz renderer for Segments visuals.

DatovizRendererSegments

Datoviz renderer for Segments visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_segments.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class DatovizRendererSegments:
    """Datoviz renderer for Segments visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        segments: Segments,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Segments visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            segments (Segments): The Segments visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(segments.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Get attributes
        # =============================================================================

        # get attributes from TransBuf to buffer
        colors_buffer = TransBufUtils.to_buffer(segments.get_colors())
        line_widths_buffer = TransBufUtils.to_buffer(segments.get_line_widths())

        # convert buffers to numpy arrays
        colors_numpy = Bufferx.to_numpy(colors_buffer)
        line_widths_pt_numpy = Bufferx.to_numpy(line_widths_buffer)

        # Convert sizes from point to pixel diameter
        line_widths_px_numpy = UnitUtils.point_to_pixel_numpy(line_widths_pt_numpy, renderer.get_canvas().get_dpi())
        line_widths_px_numpy = line_widths_px_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Segments.sanity_check_attributes_buffer(
            vertices_buffer,
            line_widths_buffer,
            segments.get_cap_style(),
            colors_buffer,
        )

        # =============================================================================
        # Create the datoviz visual if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{segments.get_uuid()}"

        # Create datoviz_visual if they do not exist
        if artist_uuid not in renderer._dvz_visuals:
            dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
            dvz_segments = renderer._dvz_app.segment(dummy_position_numpy, dummy_position_numpy)
            renderer._dvz_visuals[artist_uuid] = dvz_segments
            # Add the new visual to the panel
            dvz_panel.add(dvz_segments)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        # get the datoviz visual
        dvz_segments = typing.cast(_DvzSegments, renderer._dvz_visuals[artist_uuid])

        # dvz_vertices_initial - the even indices are initial points
        dvz_initial_vertices = np.ascontiguousarray(vertices_3d[0::2])
        # dvz_vertices_terminal - the odd indices are terminal points
        dvz_terminal_vertices = np.ascontiguousarray(vertices_3d[1::2])
        dvz_initial_cap = ConverterUtils.cap_style_gsp_to_dvz(segments.get_cap_style())
        dvz_terminal_cap = dvz_initial_cap  # same cap for initial and terminal

        dvz_segments.set_position(dvz_initial_vertices, dvz_terminal_vertices)
        dvz_segments.set_color(colors_numpy)
        dvz_segments.set_linewidth(line_widths_px_numpy)
        dvz_segments.set_cap(dvz_initial_cap, dvz_terminal_cap)

render(renderer: DatovizRenderer, viewport: Viewport, segments: Segments, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Segments visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
segments gsp.visuals.segments.Segments

The Segments visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_segments.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    segments: Segments,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Segments visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        segments (Segments): The Segments visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(segments.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Get attributes
    # =============================================================================

    # get attributes from TransBuf to buffer
    colors_buffer = TransBufUtils.to_buffer(segments.get_colors())
    line_widths_buffer = TransBufUtils.to_buffer(segments.get_line_widths())

    # convert buffers to numpy arrays
    colors_numpy = Bufferx.to_numpy(colors_buffer)
    line_widths_pt_numpy = Bufferx.to_numpy(line_widths_buffer)

    # Convert sizes from point to pixel diameter
    line_widths_px_numpy = UnitUtils.point_to_pixel_numpy(line_widths_pt_numpy, renderer.get_canvas().get_dpi())
    line_widths_px_numpy = line_widths_px_numpy.reshape(-1)  # datoviz expects (N,) shape for (N, 1) input

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Segments.sanity_check_attributes_buffer(
        vertices_buffer,
        line_widths_buffer,
        segments.get_cap_style(),
        colors_buffer,
    )

    # =============================================================================
    # Create the datoviz visual if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{segments.get_uuid()}"

    # Create datoviz_visual if they do not exist
    if artist_uuid not in renderer._dvz_visuals:
        dummy_position_numpy = np.array([[0, 0, 0]], dtype=np.float32).reshape((-1, 3))
        dvz_segments = renderer._dvz_app.segment(dummy_position_numpy, dummy_position_numpy)
        renderer._dvz_visuals[artist_uuid] = dvz_segments
        # Add the new visual to the panel
        dvz_panel.add(dvz_segments)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    # get the datoviz visual
    dvz_segments = typing.cast(_DvzSegments, renderer._dvz_visuals[artist_uuid])

    # dvz_vertices_initial - the even indices are initial points
    dvz_initial_vertices = np.ascontiguousarray(vertices_3d[0::2])
    # dvz_vertices_terminal - the odd indices are terminal points
    dvz_terminal_vertices = np.ascontiguousarray(vertices_3d[1::2])
    dvz_initial_cap = ConverterUtils.cap_style_gsp_to_dvz(segments.get_cap_style())
    dvz_terminal_cap = dvz_initial_cap  # same cap for initial and terminal

    dvz_segments.set_position(dvz_initial_vertices, dvz_terminal_vertices)
    dvz_segments.set_color(colors_numpy)
    dvz_segments.set_linewidth(line_widths_px_numpy)
    dvz_segments.set_cap(dvz_initial_cap, dvz_terminal_cap)

Texts Renderer

gsp_datoviz.renderer.datoviz_renderer_texts

Datoviz renderer for Texts visuals.

DatovizRendererTexts

Datoviz renderer for Texts visuals.

Source code in src/gsp_datoviz/renderer/datoviz_renderer_texts.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class DatovizRendererTexts:
    """Datoviz renderer for Texts visuals."""

    @staticmethod
    def render(
        renderer: DatovizRenderer,
        viewport: Viewport,
        texts: Texts,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> None:
        """Render Texts visuals using Datoviz.

        Args:
            renderer (DatovizRenderer): The Datoviz renderer instance.
            viewport (Viewport): The viewport to render in.
            texts (Texts): The Texts visual to render.
            model_matrix (TransBuf): The model matrix for the visual.
            camera (Camera): The camera used for rendering.
        """
        dvz_panel = renderer._getOrCreateDvzPanel(viewport)

        # =============================================================================
        # Transform vertices with MVP matrix
        # =============================================================================

        vertices_buffer = TransBufUtils.to_buffer(texts.get_positions())
        model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
        view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
        projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

        # convert all necessary buffers to numpy arrays
        vertices_numpy = Bufferx.to_numpy(vertices_buffer)
        model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
        view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
        projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

        # Apply Model-View-Projection transformation to the vertices
        vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

        # Convert 3D vertices to 3d - shape (N, 3)
        vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

        # =============================================================================
        # Get attributes
        # =============================================================================

        # get attributes from TransBuf to buffer
        colors_buffer = TransBufUtils.to_buffer(texts.get_colors())
        font_sizes_buffer = TransBufUtils.to_buffer(texts.get_font_sizes())
        anchors_buffer = TransBufUtils.to_buffer(texts.get_anchors())
        angles_buffer = TransBufUtils.to_buffer(texts.get_angles())

        # convert buffers to numpy arrays
        colors_numpy = Bufferx.to_numpy(colors_buffer)
        font_sizes_numpy = Bufferx.to_numpy(font_sizes_buffer)
        anchors_numpy = Bufferx.to_numpy(anchors_buffer)
        angles_numpy = Bufferx.to_numpy(angles_buffer)

        # =============================================================================
        # Sanity checks attributes buffers
        # =============================================================================

        Texts.sanity_check_attributes_buffer(
            vertices_buffer,
            texts.get_strings(),
            colors_buffer,
            font_sizes_buffer,
            anchors_buffer,
            angles_buffer,
            texts.get_font_name(),
        )

        # =============================================================================
        # Create the datoviz visual if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{texts.get_uuid()}"

        # Create datoviz_visual if they do not exist
        if artist_uuid not in renderer._dvz_visuals:
            artist_uuid = f"{viewport.get_uuid()}_{texts.get_uuid()}"
            dvz_glyphs = renderer._dvz_app.glyph(font_size=30)
            # set dummy strings to initialize the visual
            dvz_glyphs.set_strings(["dummy string"], string_pos=np.array([[0.0, 0.0, 0.0]], dtype=np.float32), scales=np.array([1.0], dtype=np.float32))
            renderer._dvz_visuals[artist_uuid] = dvz_glyphs
            # Add the new visual to the panel
            dvz_panel.add(dvz_glyphs)

        # =============================================================================
        # Update all attributes
        # =============================================================================

        # # get the datoviz visual
        dvz_glyphs = typing.cast(_DvzGlyphs, renderer._dvz_visuals[artist_uuid])

        text_strings = texts.get_strings()
        text_count = len(text_strings)
        glyph_count = sum(map(len, text_strings))

        # build glyph scales from font sizes
        glyph_scales = np.zeros((glyph_count,), dtype=np.float32)
        for text_index in range(text_count):
            # TODO font-size is in typographic points, have to convert to pixels ? relation with the font_size of the dvz visual ?
            # glyph_scales[text_index] = font_sizes_numpy[text_index, 0]  # dvz visual default font size is 100
            for glyph_index in range(len(text_strings[text_index])):
                global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
                glyph_scales[global_glyph_index] = font_sizes_numpy[text_index, 0] / 15  # dvz visual default font size is 100

        # build glyph colors from text colors
        glyph_colors = np.zeros((glyph_count, 4), dtype=np.uint8)
        for text_index in range(text_count):
            for glyph_index in range(len(text_strings[text_index])):
                global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
                glyph_colors[global_glyph_index, :] = colors_numpy[text_index, :]

        glyphs_angles = np.zeros((glyph_count,), dtype=np.float32)
        for text_index in range(text_count):
            for glyph_index in range(len(text_strings[text_index])):
                global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
                glyphs_angles[global_glyph_index] = angles_numpy[text_index, 0] / 180 * np.pi  # convert to radians

        dvz_glyphs.set_strings(text_strings, string_pos=vertices_3d)
        dvz_glyphs.set_color(glyph_colors)
        dvz_glyphs.set_angle(glyphs_angles)
        dvz_glyphs.set_scale(glyph_scales)

render(renderer: DatovizRenderer, viewport: Viewport, texts: Texts, model_matrix: TransBuf, camera: Camera) -> None staticmethod

Render Texts visuals using Datoviz.

Parameters:

Name Type Description Default
renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The Datoviz renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render in.

required
texts gsp.visuals.texts.Texts

The Texts visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model matrix for the visual.

required
camera gsp.core.camera.Camera

The camera used for rendering.

required
Source code in src/gsp_datoviz/renderer/datoviz_renderer_texts.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
@staticmethod
def render(
    renderer: DatovizRenderer,
    viewport: Viewport,
    texts: Texts,
    model_matrix: TransBuf,
    camera: Camera,
) -> None:
    """Render Texts visuals using Datoviz.

    Args:
        renderer (DatovizRenderer): The Datoviz renderer instance.
        viewport (Viewport): The viewport to render in.
        texts (Texts): The Texts visual to render.
        model_matrix (TransBuf): The model matrix for the visual.
        camera (Camera): The camera used for rendering.
    """
    dvz_panel = renderer._getOrCreateDvzPanel(viewport)

    # =============================================================================
    # Transform vertices with MVP matrix
    # =============================================================================

    vertices_buffer = TransBufUtils.to_buffer(texts.get_positions())
    model_matrix_buffer = TransBufUtils.to_buffer(model_matrix)
    view_matrix_buffer = TransBufUtils.to_buffer(camera.get_view_matrix())
    projection_matrix_buffer = TransBufUtils.to_buffer(camera.get_projection_matrix())

    # convert all necessary buffers to numpy arrays
    vertices_numpy = Bufferx.to_numpy(vertices_buffer)
    model_matrix_numpy = Bufferx.to_numpy(model_matrix_buffer).squeeze()
    view_matrix_numpy = Bufferx.to_numpy(view_matrix_buffer).squeeze()
    projection_matrix_numpy = Bufferx.to_numpy(projection_matrix_buffer).squeeze()

    # Apply Model-View-Projection transformation to the vertices
    vertices_3d_transformed = MathUtils.apply_mvp_to_vertices(vertices_numpy, model_matrix_numpy, view_matrix_numpy, projection_matrix_numpy)

    # Convert 3D vertices to 3d - shape (N, 3)
    vertices_3d = np.ascontiguousarray(vertices_3d_transformed, dtype=np.float32)

    # =============================================================================
    # Get attributes
    # =============================================================================

    # get attributes from TransBuf to buffer
    colors_buffer = TransBufUtils.to_buffer(texts.get_colors())
    font_sizes_buffer = TransBufUtils.to_buffer(texts.get_font_sizes())
    anchors_buffer = TransBufUtils.to_buffer(texts.get_anchors())
    angles_buffer = TransBufUtils.to_buffer(texts.get_angles())

    # convert buffers to numpy arrays
    colors_numpy = Bufferx.to_numpy(colors_buffer)
    font_sizes_numpy = Bufferx.to_numpy(font_sizes_buffer)
    anchors_numpy = Bufferx.to_numpy(anchors_buffer)
    angles_numpy = Bufferx.to_numpy(angles_buffer)

    # =============================================================================
    # Sanity checks attributes buffers
    # =============================================================================

    Texts.sanity_check_attributes_buffer(
        vertices_buffer,
        texts.get_strings(),
        colors_buffer,
        font_sizes_buffer,
        anchors_buffer,
        angles_buffer,
        texts.get_font_name(),
    )

    # =============================================================================
    # Create the datoviz visual if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{texts.get_uuid()}"

    # Create datoviz_visual if they do not exist
    if artist_uuid not in renderer._dvz_visuals:
        artist_uuid = f"{viewport.get_uuid()}_{texts.get_uuid()}"
        dvz_glyphs = renderer._dvz_app.glyph(font_size=30)
        # set dummy strings to initialize the visual
        dvz_glyphs.set_strings(["dummy string"], string_pos=np.array([[0.0, 0.0, 0.0]], dtype=np.float32), scales=np.array([1.0], dtype=np.float32))
        renderer._dvz_visuals[artist_uuid] = dvz_glyphs
        # Add the new visual to the panel
        dvz_panel.add(dvz_glyphs)

    # =============================================================================
    # Update all attributes
    # =============================================================================

    # # get the datoviz visual
    dvz_glyphs = typing.cast(_DvzGlyphs, renderer._dvz_visuals[artist_uuid])

    text_strings = texts.get_strings()
    text_count = len(text_strings)
    glyph_count = sum(map(len, text_strings))

    # build glyph scales from font sizes
    glyph_scales = np.zeros((glyph_count,), dtype=np.float32)
    for text_index in range(text_count):
        # TODO font-size is in typographic points, have to convert to pixels ? relation with the font_size of the dvz visual ?
        # glyph_scales[text_index] = font_sizes_numpy[text_index, 0]  # dvz visual default font size is 100
        for glyph_index in range(len(text_strings[text_index])):
            global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
            glyph_scales[global_glyph_index] = font_sizes_numpy[text_index, 0] / 15  # dvz visual default font size is 100

    # build glyph colors from text colors
    glyph_colors = np.zeros((glyph_count, 4), dtype=np.uint8)
    for text_index in range(text_count):
        for glyph_index in range(len(text_strings[text_index])):
            global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
            glyph_colors[global_glyph_index, :] = colors_numpy[text_index, :]

    glyphs_angles = np.zeros((glyph_count,), dtype=np.float32)
    for text_index in range(text_count):
        for glyph_index in range(len(text_strings[text_index])):
            global_glyph_index = sum(len(s) for s in text_strings[:text_index]) + glyph_index
            glyphs_angles[global_glyph_index] = angles_numpy[text_index, 0] / 180 * np.pi  # convert to radians

    dvz_glyphs.set_strings(text_strings, string_pos=vertices_3d)
    dvz_glyphs.set_color(glyph_colors)
    dvz_glyphs.set_angle(glyphs_angles)
    dvz_glyphs.set_scale(glyph_scales)

Utils Module

The utils module provides converter utilities for the Datoviz backend.

gsp_datoviz.utils

Utility functions for converting GSP types to Datoviz types.

Converter Utils

gsp_datoviz.utils.converter_utils

Utility functions for converting GSP types to Datoviz types.

ConverterUtils

Utility class for converting GSP types to Datoviz types.

Source code in src/gsp_datoviz/utils/converter_utils.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class ConverterUtils:
    """Utility class for converting GSP types to Datoviz types."""

    @staticmethod
    def cap_style_gsp_to_dvz(cap_style: CapStyle) -> str:
        """Convert CapStyle enum to Datoviz string.

        Args:
            cap_style: The GSP CapStyle enum value.

        Returns:
            The corresponding Datoviz cap style string.
        """
        if cap_style == CapStyle.BUTT:
            return "butt"
        elif cap_style == CapStyle.ROUND:
            return "round"
        elif cap_style == CapStyle.PROJECTING:
            return "square"
        else:
            raise ValueError(f"Unsupported CapStyle: {cap_style}")

    @staticmethod
    def join_style_gsp_to_dvz(join_style: JoinStyle) -> str:
        """Convert JoinStyle enum to Datoviz string.

        Args:
            join_style: The GSP JoinStyle enum value.

        Returns:
            The corresponding Datoviz join style string.
        """
        if join_style == JoinStyle.MITER:
            raise ValueError(f"Unsupported JoinStyle in datoviz: {join_style}")
        elif join_style == JoinStyle.ROUND:
            return "round"
        elif join_style == JoinStyle.BEVEL:
            return "square"
        else:
            raise ValueError(f"Unsupported JoinStyle: {join_style}")

    @staticmethod
    def marker_shape_gsp_to_dvz(gsp_marker_shape: MarkerShape) -> str:
        """Convert GSP marker shape to Datoviz marker shape.

        Args:
            gsp_marker_shape: The GSP MarkerShape enum value.

        Returns:
            The corresponding Datoviz marker shape string.
        """
        if gsp_marker_shape == MarkerShape.disc:
            mpl_marker_shape = "disc"
        elif gsp_marker_shape == MarkerShape.square:
            mpl_marker_shape = "square"
        elif gsp_marker_shape == MarkerShape.club:
            mpl_marker_shape = "club"
        else:
            raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

        return mpl_marker_shape

cap_style_gsp_to_dvz(cap_style: CapStyle) -> str staticmethod

Convert CapStyle enum to Datoviz string.

Parameters:

Name Type Description Default
cap_style gsp.types.CapStyle

The GSP CapStyle enum value.

required

Returns:

Type Description
str

The corresponding Datoviz cap style string.

Source code in src/gsp_datoviz/utils/converter_utils.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@staticmethod
def cap_style_gsp_to_dvz(cap_style: CapStyle) -> str:
    """Convert CapStyle enum to Datoviz string.

    Args:
        cap_style: The GSP CapStyle enum value.

    Returns:
        The corresponding Datoviz cap style string.
    """
    if cap_style == CapStyle.BUTT:
        return "butt"
    elif cap_style == CapStyle.ROUND:
        return "round"
    elif cap_style == CapStyle.PROJECTING:
        return "square"
    else:
        raise ValueError(f"Unsupported CapStyle: {cap_style}")

join_style_gsp_to_dvz(join_style: JoinStyle) -> str staticmethod

Convert JoinStyle enum to Datoviz string.

Parameters:

Name Type Description Default
join_style gsp.types.JoinStyle

The GSP JoinStyle enum value.

required

Returns:

Type Description
str

The corresponding Datoviz join style string.

Source code in src/gsp_datoviz/utils/converter_utils.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@staticmethod
def join_style_gsp_to_dvz(join_style: JoinStyle) -> str:
    """Convert JoinStyle enum to Datoviz string.

    Args:
        join_style: The GSP JoinStyle enum value.

    Returns:
        The corresponding Datoviz join style string.
    """
    if join_style == JoinStyle.MITER:
        raise ValueError(f"Unsupported JoinStyle in datoviz: {join_style}")
    elif join_style == JoinStyle.ROUND:
        return "round"
    elif join_style == JoinStyle.BEVEL:
        return "square"
    else:
        raise ValueError(f"Unsupported JoinStyle: {join_style}")

marker_shape_gsp_to_dvz(gsp_marker_shape: MarkerShape) -> str staticmethod

Convert GSP marker shape to Datoviz marker shape.

Parameters:

Name Type Description Default
gsp_marker_shape gsp.types.MarkerShape

The GSP MarkerShape enum value.

required

Returns:

Type Description
str

The corresponding Datoviz marker shape string.

Source code in src/gsp_datoviz/utils/converter_utils.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@staticmethod
def marker_shape_gsp_to_dvz(gsp_marker_shape: MarkerShape) -> str:
    """Convert GSP marker shape to Datoviz marker shape.

    Args:
        gsp_marker_shape: The GSP MarkerShape enum value.

    Returns:
        The corresponding Datoviz marker shape string.
    """
    if gsp_marker_shape == MarkerShape.disc:
        mpl_marker_shape = "disc"
    elif gsp_marker_shape == MarkerShape.square:
        mpl_marker_shape = "square"
    elif gsp_marker_shape == MarkerShape.club:
        mpl_marker_shape = "club"
    else:
        raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

    return mpl_marker_shape

Viewport Events Datoviz

gsp_datoviz.viewport_events.viewport_events_datoviz

DatovizRenderer event handler for viewport events.

ViewportEventsDatoviz

Bases: gsp.types.viewport_events_base.ViewportEventsBase

DatovizRenderer event handler for viewport.

Source code in src/gsp_datoviz/viewport_events/viewport_events_datoviz.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
class ViewportEventsDatoviz(ViewportEventsBase):
    """DatovizRenderer event handler for viewport."""

    __slots__ = ("_renderer", "_viewport", "_has_key_focus", "_is_closed")

    def __init__(self, renderer: DatovizRenderer, viewport: Viewport) -> None:
        """Initialize the Datoviz viewport event handler."""
        self._renderer = renderer
        """MatplotlibRenderer associated with this event handler"""
        self._viewport = viewport
        """viewport associated with this event handler"""
        self._has_key_focus = False
        """True if this viewport has the keyboard focus"""
        self._is_closed = False
        """True if the event handler is closed"""

        # Intanciate events
        self.key_press_event = Event[KeyboardEventCallback]()
        self.key_release_event = Event[KeyboardEventCallback]()
        self.button_press_event = Event[MouseEventCallback]()
        self.button_release_event = Event[MouseEventCallback]()
        self.mouse_move_event = Event[MouseEventCallback]()
        self.mouse_scroll_event = Event[MouseEventCallback]()
        self.canvas_resize_event = Event[CanvasResizeEventCallback]()

        dvz_app: dvz.App = self._renderer.get_dvz_app()
        dvz_figure: _DvzFigure = self._renderer.get_dvz_figure()

        # =============================================================================
        # Connect keyboard in dvz_app
        # =============================================================================
        @dvz_app.connect(dvz_figure)
        def on_keyboard(dvz_keyboard_event: dvz.KeyboardEvent):
            # if this viewport is closed, ignore events (datoviz doesnt allow to disconnect events)
            if self._is_closed:
                return

            # Read dvz_keyboard_event properties
            dvz_event_name = dvz_keyboard_event.key_event()
            dvz_key_name = dvz_keyboard_event.key_name()

            # Convert fields to our MouseEvent
            if dvz_event_name == "press":
                event_type = EventType.KEY_PRESS
            elif dvz_event_name == "release":
                event_type = EventType.KEY_RELEASE
            else:
                return  # Unknown event

            key_name = dvz_key_name

            # Create our KeyEvent
            key_event = KeyEvent(
                viewport_uuid=self._viewport.get_uuid(),
                event_type=event_type,
                key_name=key_name,
            )

            # dispatch key_event to the proper handler
            if key_event.event_type == EventType.KEY_PRESS:
                self.key_press_event.dispatch(key_event)
            elif key_event.event_type == EventType.KEY_RELEASE:
                self.key_release_event.dispatch(key_event)
            else:
                raise ValueError(f"Unknown key event type: {key_event.event_type}")

        # =============================================================================
        # Connect mouse in dvz_app
        # =============================================================================
        @dvz_app.connect(dvz_figure)
        def on_mouse(dvz_mouse_event: dvz.MouseEvent):
            # if this viewport is closed, ignore events (datoviz doesnt allow to disconnect events)
            if self._is_closed:
                return

            # Set key focus to true if there is a mouse press is inside the viewport, otherwise remove key focus if mouse press is outside
            if dvz_mouse_event.mouse_event() == "press":
                if self._viewport_contains_dvz_mouse_event(dvz_mouse_event):
                    self._has_key_focus = True
                else:
                    self._has_key_focus = False

            # discard events outside the viewport
            if self._viewport_contains_dvz_mouse_event(dvz_mouse_event) is False:
                return

            # Read dvz_mouse_event properties
            dvz_event_name: str = dvz_mouse_event.mouse_event()
            dvz_mouse_pos: tuple[float, float] = dvz_mouse_event.pos()
            dvz_mouse_x_px: float = dvz_mouse_pos[0]
            dvz_mouse_y_px: float = self._renderer.get_canvas().get_height() - dvz_mouse_pos[1]
            dvz_button_name: str = dvz_mouse_event.button_name()
            dvz_wheel: float | None = dvz_mouse_event.wheel()

            # Convert fields to our MouseEvent
            if dvz_event_name == "press":
                event_type = EventType.BUTTON_PRESS
            elif dvz_event_name == "release":
                event_type = EventType.BUTTON_RELEASE
            elif dvz_event_name == "move" or dvz_event_name == "drag":
                event_type = EventType.MOUSE_MOVE
            elif dvz_event_name == "wheel":
                event_type = EventType.MOUSE_SCROLL
            else:
                # print(f'"Unknown dvz mouse event name: {dvz_event_name}"')
                return  # Unknown event

            event_x: float = (dvz_mouse_x_px - self._viewport.get_x()) / self._viewport.get_width() * 2.0 - 1.0
            event_y: float = (dvz_mouse_y_px - self._viewport.get_y()) / self._viewport.get_height() * 2.0 - 1.0

            # print(f"event_x: {event_x}, event_y: {event_y}")
            # print(
            #     f"dvz_mouse_x_px: {dvz_mouse_x_px}, dvz_mouse_y_px: {dvz_mouse_y_px}viewport x:{self._viewport.get_x()}, y:{self._viewport.get_y()}, w:{self._viewport.get_width()}, h:{self._viewport.get_height()}"
            # )

            left_button: bool = dvz_button_name == "left"
            middle_button: bool = dvz_button_name == "middle"
            right_button: bool = dvz_button_name == "right"

            event_scroll_steps: float = dvz_wheel if dvz_wheel is not None else 0.0

            # Create our MouseEvent
            mouse_event = MouseEvent(
                viewport_uuid=self._viewport.get_uuid(),
                event_type=event_type,
                x_ndc=event_x,
                y_ndc=event_y,
                left_button=left_button,
                middle_button=middle_button,
                right_button=right_button,
                scroll_steps=event_scroll_steps,
            )

            # print(
            #     f"mouse_event: type={mouse_event.event_type}, x_ndc={mouse_event.x_ndc}, y_ndc={mouse_event.y_ndc}, left={mouse_event.left_button}, middle={mouse_event.middle_button}, right={mouse_event.right_button}, scroll_steps={mouse_event.scroll_steps}"
            # )

            # dispatch mouse_event to the proper handler
            if mouse_event.event_type == EventType.BUTTON_PRESS:
                self.button_press_event.dispatch(mouse_event)
            elif mouse_event.event_type == EventType.BUTTON_RELEASE:
                self.button_release_event.dispatch(mouse_event)
            elif mouse_event.event_type == EventType.MOUSE_MOVE:
                self.mouse_move_event.dispatch(mouse_event)
            elif mouse_event.event_type == EventType.MOUSE_SCROLL:
                self.mouse_scroll_event.dispatch(mouse_event)
            else:
                raise ValueError(f"Unknown mouse event type: {mouse_event.event_type}")

        # =============================================================================
        # Connect resize in dvz_app
        # =============================================================================
        @dvz_app.connect(dvz_figure)
        def on_resize(dvz_resize_event: dvz.WindowEvent):
            canvas_width_px = dvz_resize_event.screen_width()  # TODO may be a good idea to rename .screen_width() to .canvas_width() or similar in datoviz
            canvas_height_px = dvz_resize_event.screen_height()
            # dispatch canvas resize event
            canvas_resize_event = CanvasResizeEvent(
                viewport_uuid=self._viewport.get_uuid(),
                event_type=EventType.CANVAS_RESIZE,
                canvas_width_px=canvas_width_px,
                canvas_height_px=canvas_height_px,
            )
            self.canvas_resize_event.dispatch(canvas_resize_event)

    def close(self):
        """Close the event handler and release resources."""
        # no more dispatch events (datoviz doesnt allow to disconnect events)
        self._is_closed = True

    # =============================================================================
    #
    # =============================================================================

    def _viewport_contains_dvz_mouse_event(self, dvz_mouse_event: dvz.MouseEvent) -> bool:
        """Check if the matplotlib mouse event is inside this viewport.

        Args:
            dvz_mouse_event: Datoviz mouse event.

        Returns:
            True if the mouse event is inside this viewport, False otherwise.
        """
        dvz_mouse_pos = dvz_mouse_event.pos()
        dvz_mouse_x = dvz_mouse_pos[0]
        dvz_mouse_y = self._renderer.get_canvas().get_height() - dvz_mouse_pos[1]

        # print(f"dvz_mouse_x: {dvz_mouse_x}, dvz_mouse_y: {dvz_mouse_y}")

        mouse_x = dvz_mouse_x
        mouse_y = dvz_mouse_y
        if mouse_x < self._viewport.get_x():
            return False
        if mouse_x >= self._viewport.get_x() + self._viewport.get_width():
            return False
        if mouse_y < self._viewport.get_y():
            return False
        if mouse_y >= self._viewport.get_y() + self._viewport.get_height():
            return False
        return True

__init__(renderer: DatovizRenderer, viewport: Viewport) -> None

Initialize the Datoviz viewport event handler.

Source code in src/gsp_datoviz/viewport_events/viewport_events_datoviz.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def __init__(self, renderer: DatovizRenderer, viewport: Viewport) -> None:
    """Initialize the Datoviz viewport event handler."""
    self._renderer = renderer
    """MatplotlibRenderer associated with this event handler"""
    self._viewport = viewport
    """viewport associated with this event handler"""
    self._has_key_focus = False
    """True if this viewport has the keyboard focus"""
    self._is_closed = False
    """True if the event handler is closed"""

    # Intanciate events
    self.key_press_event = Event[KeyboardEventCallback]()
    self.key_release_event = Event[KeyboardEventCallback]()
    self.button_press_event = Event[MouseEventCallback]()
    self.button_release_event = Event[MouseEventCallback]()
    self.mouse_move_event = Event[MouseEventCallback]()
    self.mouse_scroll_event = Event[MouseEventCallback]()
    self.canvas_resize_event = Event[CanvasResizeEventCallback]()

    dvz_app: dvz.App = self._renderer.get_dvz_app()
    dvz_figure: _DvzFigure = self._renderer.get_dvz_figure()

    # =============================================================================
    # Connect keyboard in dvz_app
    # =============================================================================
    @dvz_app.connect(dvz_figure)
    def on_keyboard(dvz_keyboard_event: dvz.KeyboardEvent):
        # if this viewport is closed, ignore events (datoviz doesnt allow to disconnect events)
        if self._is_closed:
            return

        # Read dvz_keyboard_event properties
        dvz_event_name = dvz_keyboard_event.key_event()
        dvz_key_name = dvz_keyboard_event.key_name()

        # Convert fields to our MouseEvent
        if dvz_event_name == "press":
            event_type = EventType.KEY_PRESS
        elif dvz_event_name == "release":
            event_type = EventType.KEY_RELEASE
        else:
            return  # Unknown event

        key_name = dvz_key_name

        # Create our KeyEvent
        key_event = KeyEvent(
            viewport_uuid=self._viewport.get_uuid(),
            event_type=event_type,
            key_name=key_name,
        )

        # dispatch key_event to the proper handler
        if key_event.event_type == EventType.KEY_PRESS:
            self.key_press_event.dispatch(key_event)
        elif key_event.event_type == EventType.KEY_RELEASE:
            self.key_release_event.dispatch(key_event)
        else:
            raise ValueError(f"Unknown key event type: {key_event.event_type}")

    # =============================================================================
    # Connect mouse in dvz_app
    # =============================================================================
    @dvz_app.connect(dvz_figure)
    def on_mouse(dvz_mouse_event: dvz.MouseEvent):
        # if this viewport is closed, ignore events (datoviz doesnt allow to disconnect events)
        if self._is_closed:
            return

        # Set key focus to true if there is a mouse press is inside the viewport, otherwise remove key focus if mouse press is outside
        if dvz_mouse_event.mouse_event() == "press":
            if self._viewport_contains_dvz_mouse_event(dvz_mouse_event):
                self._has_key_focus = True
            else:
                self._has_key_focus = False

        # discard events outside the viewport
        if self._viewport_contains_dvz_mouse_event(dvz_mouse_event) is False:
            return

        # Read dvz_mouse_event properties
        dvz_event_name: str = dvz_mouse_event.mouse_event()
        dvz_mouse_pos: tuple[float, float] = dvz_mouse_event.pos()
        dvz_mouse_x_px: float = dvz_mouse_pos[0]
        dvz_mouse_y_px: float = self._renderer.get_canvas().get_height() - dvz_mouse_pos[1]
        dvz_button_name: str = dvz_mouse_event.button_name()
        dvz_wheel: float | None = dvz_mouse_event.wheel()

        # Convert fields to our MouseEvent
        if dvz_event_name == "press":
            event_type = EventType.BUTTON_PRESS
        elif dvz_event_name == "release":
            event_type = EventType.BUTTON_RELEASE
        elif dvz_event_name == "move" or dvz_event_name == "drag":
            event_type = EventType.MOUSE_MOVE
        elif dvz_event_name == "wheel":
            event_type = EventType.MOUSE_SCROLL
        else:
            # print(f'"Unknown dvz mouse event name: {dvz_event_name}"')
            return  # Unknown event

        event_x: float = (dvz_mouse_x_px - self._viewport.get_x()) / self._viewport.get_width() * 2.0 - 1.0
        event_y: float = (dvz_mouse_y_px - self._viewport.get_y()) / self._viewport.get_height() * 2.0 - 1.0

        # print(f"event_x: {event_x}, event_y: {event_y}")
        # print(
        #     f"dvz_mouse_x_px: {dvz_mouse_x_px}, dvz_mouse_y_px: {dvz_mouse_y_px}viewport x:{self._viewport.get_x()}, y:{self._viewport.get_y()}, w:{self._viewport.get_width()}, h:{self._viewport.get_height()}"
        # )

        left_button: bool = dvz_button_name == "left"
        middle_button: bool = dvz_button_name == "middle"
        right_button: bool = dvz_button_name == "right"

        event_scroll_steps: float = dvz_wheel if dvz_wheel is not None else 0.0

        # Create our MouseEvent
        mouse_event = MouseEvent(
            viewport_uuid=self._viewport.get_uuid(),
            event_type=event_type,
            x_ndc=event_x,
            y_ndc=event_y,
            left_button=left_button,
            middle_button=middle_button,
            right_button=right_button,
            scroll_steps=event_scroll_steps,
        )

        # print(
        #     f"mouse_event: type={mouse_event.event_type}, x_ndc={mouse_event.x_ndc}, y_ndc={mouse_event.y_ndc}, left={mouse_event.left_button}, middle={mouse_event.middle_button}, right={mouse_event.right_button}, scroll_steps={mouse_event.scroll_steps}"
        # )

        # dispatch mouse_event to the proper handler
        if mouse_event.event_type == EventType.BUTTON_PRESS:
            self.button_press_event.dispatch(mouse_event)
        elif mouse_event.event_type == EventType.BUTTON_RELEASE:
            self.button_release_event.dispatch(mouse_event)
        elif mouse_event.event_type == EventType.MOUSE_MOVE:
            self.mouse_move_event.dispatch(mouse_event)
        elif mouse_event.event_type == EventType.MOUSE_SCROLL:
            self.mouse_scroll_event.dispatch(mouse_event)
        else:
            raise ValueError(f"Unknown mouse event type: {mouse_event.event_type}")

    # =============================================================================
    # Connect resize in dvz_app
    # =============================================================================
    @dvz_app.connect(dvz_figure)
    def on_resize(dvz_resize_event: dvz.WindowEvent):
        canvas_width_px = dvz_resize_event.screen_width()  # TODO may be a good idea to rename .screen_width() to .canvas_width() or similar in datoviz
        canvas_height_px = dvz_resize_event.screen_height()
        # dispatch canvas resize event
        canvas_resize_event = CanvasResizeEvent(
            viewport_uuid=self._viewport.get_uuid(),
            event_type=EventType.CANVAS_RESIZE,
            canvas_width_px=canvas_width_px,
            canvas_height_px=canvas_height_px,
        )
        self.canvas_resize_event.dispatch(canvas_resize_event)

close()

Close the event handler and release resources.

Source code in src/gsp_datoviz/viewport_events/viewport_events_datoviz.py
183
184
185
186
def close(self):
    """Close the event handler and release resources."""
    # no more dispatch events (datoviz doesnt allow to disconnect events)
    self._is_closed = True

Animator Datoviz

gsp_datoviz.animator.animator_datoviz

Animator for GSP scenes using a matplotlib renderer.

AnimatorDatoviz

Bases: gsp.types.animator_base.AnimatorBase

Animator for GSP scenes using a matplotlib renderer.

Source code in src/gsp_datoviz/animator/animator_datoviz.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
class AnimatorDatoviz(AnimatorBase):
    """Animator for GSP scenes using a matplotlib renderer."""

    def __init__(
        self,
        datoviz_renderer: DatovizRenderer,
        fps: int = 50,
        video_duration: float | None = None,
        video_path: str | None = None,
    ):
        """Initialize the animator.

        Args:
            datoviz_renderer (DatovizRenderer): The datoviz renderer to use for rendering.
            fps (int, optional): Frames per second. Defaults to 50.
            video_duration (float | None, optional): Duration of the video to save. Defaults to None.
            video_path (str | None, optional): Path to save the video. Defaults to None.
        """
        self._callbacks: list[AnimatorFunc] = []
        self._datoviz_renderer = datoviz_renderer
        self._fps = fps

        # sanity check - video not supported yet
        assert video_duration is None, "GspAnimatorDatoviz does not support video saving yet."
        assert video_path is None, "GspAnimatorDatoviz does not support video saving yet."

        self._canvas: Canvas | None = None
        self._viewports: Sequence[Viewport] | None = None
        self._visuals: Sequence[VisualBase] | None = None
        self._model_matrices: Sequence[TransBuf] | None = None
        self._cameras: Sequence[Camera] | None = None

        self.on_video_saved = Event[VideoSavedCalledback]()
        """Event triggered when the video is saved."""

    # =============================================================================
    # .add_callback/.remove_callback/.decorator
    # =============================================================================

    def add_callback(self, func: AnimatorFunc) -> None:
        """Add a callback to the animation loop."""
        self._callbacks.append(func)

    def remove_callback(self, func: AnimatorFunc) -> None:
        """Remove a callback from the animation loop."""
        self._callbacks.remove(func)

    def event_listener(self, func: AnimatorFunc) -> AnimatorFunc:
        """A decorator to add a callback to the animation loop.

        Usage:
            ```python
                @animation_loop.event_listener
                def my_callback(delta_time: float) -> Sequence[Object3D]:
                    ...

                # later, if needed
                animation_loop.remove_callback(my_callback)
            ```
        """
        self.add_callback(func)

        def wrapper(delta_time: float) -> Sequence[VisualBase]:
            # print("Before the function runs")
            result = func(delta_time)
            # print("After the function runs")
            return result

        return wrapper

    # =============================================================================
    # .start()
    # =============================================================================
    def start(self, viewports: Sequence[Viewport], visuals: Sequence[VisualBase], model_matrices: Sequence[TransBuf], cameras: Sequence[Camera]) -> None:
        """Animate the given canvas and camera using the provided callbacks to update visuals."""
        self._canvas = self._datoviz_renderer.get_canvas()
        self._viewports = viewports
        self._visuals = visuals
        self._model_matrices = model_matrices
        self._cameras = cameras
        self._time_last_update = time.time()

        # =============================================================================
        # Render the image once
        # =============================================================================

        self._datoviz_renderer.render(viewports, visuals, model_matrices, cameras)

        # =============================================================================
        # Handle GSP_TEST=True
        # =============================================================================

        # detect if we are in not interactive mode - used during testing
        in_test = "GSP_TEST" in os.environ and os.environ["GSP_TEST"] == "True"

        # if we are not in interactive mode, save a preview image and return
        if in_test == True:
            # notify all animator callbacks
            changed_visuals: list[VisualBase] = []
            for animator_callback in self._callbacks:
                _changed_visuals = animator_callback(1.0 / self._fps)
                changed_visuals.extend(_changed_visuals)

            # render the scene to get the new image
            image_png_data = self._datoviz_renderer.render(viewports, visuals, model_matrices, cameras, return_image=True, image_format="png")
            # get the main script name
            main_script_name = os.path.basename(__main__.__file__) if hasattr(__main__, "__file__") else "interactive"
            main_script_basename = os.path.splitext(main_script_name)[0]
            # buid the output image path
            image_path = os.path.join(__dirname__, "../../../examples/output", f"{main_script_basename}_animator_datoviz.png")
            image_path = os.path.abspath(image_path)
            # save image_png_data in a image file
            with open(image_path, "wb") as image_file:
                image_file.write(image_png_data)
            # log the event
            print(f"Saved animation preview image to: {image_path}")
            return

        # NOTE: here we are in interactive mode!!

        # =============================================================================
        # Initialize the animation
        # =============================================================================

        dvz_app = self._datoviz_renderer.get_dvz_app()

        @dvz_app.timer(period=1.0 / self._fps)
        def on_timer(event):
            self._dvz_animate()

        # =============================================================================
        # Show the animation
        # =============================================================================

        self._datoviz_renderer.show()

    # =============================================================================
    # .stop()
    # =============================================================================
    def stop(self):
        """Stop the animation."""
        self._canvas = None
        self._viewports = None
        self._time_last_update = None

        warning.warn("GspAnimatorDatoviz.stop() is not fully implemented yet.")

    def _dvz_animate(self) -> None:
        # sanity checks
        assert self._canvas is not None, "Canvas MUST be set during the animation"
        assert self._viewports is not None, "Viewports MUST be set during the animation"
        assert self._visuals is not None, "Visuals MUST be set during the animation"
        assert self._model_matrices is not None, "Model matrices MUST be set during the animation"
        assert self._cameras is not None, "Cameras MUST be set during the animation"

        # compute delta time
        present = time.time()
        delta_time = (present - self._time_last_update) if self._time_last_update is not None else (1 / self._fps)
        self._time_last_update = present

        # notify all animator callbacks
        changed_visuals: list[VisualBase] = []
        for callback in self._callbacks:
            _changed_visuals = callback(delta_time)
            changed_visuals.extend(_changed_visuals)

        # changed_visuals is not used by datoviz, but could be used in the future

        # Render the scene to update the visuals
        self._datoviz_renderer.render(self._viewports, self._visuals, self._model_matrices, self._cameras, return_image=False)

on_video_saved = Event[VideoSavedCalledback]() instance-attribute

Event triggered when the video is saved.

__init__(datoviz_renderer: DatovizRenderer, fps: int = 50, video_duration: float | None = None, video_path: str | None = None)

Initialize the animator.

Parameters:

Name Type Description Default
datoviz_renderer gsp_datoviz.renderer.datoviz_renderer.DatovizRenderer

The datoviz renderer to use for rendering.

required
fps int

Frames per second. Defaults to 50.

50
video_duration float | None

Duration of the video to save. Defaults to None.

None
video_path str | None

Path to save the video. Defaults to None.

None
Source code in src/gsp_datoviz/animator/animator_datoviz.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(
    self,
    datoviz_renderer: DatovizRenderer,
    fps: int = 50,
    video_duration: float | None = None,
    video_path: str | None = None,
):
    """Initialize the animator.

    Args:
        datoviz_renderer (DatovizRenderer): The datoviz renderer to use for rendering.
        fps (int, optional): Frames per second. Defaults to 50.
        video_duration (float | None, optional): Duration of the video to save. Defaults to None.
        video_path (str | None, optional): Path to save the video. Defaults to None.
    """
    self._callbacks: list[AnimatorFunc] = []
    self._datoviz_renderer = datoviz_renderer
    self._fps = fps

    # sanity check - video not supported yet
    assert video_duration is None, "GspAnimatorDatoviz does not support video saving yet."
    assert video_path is None, "GspAnimatorDatoviz does not support video saving yet."

    self._canvas: Canvas | None = None
    self._viewports: Sequence[Viewport] | None = None
    self._visuals: Sequence[VisualBase] | None = None
    self._model_matrices: Sequence[TransBuf] | None = None
    self._cameras: Sequence[Camera] | None = None

    self.on_video_saved = Event[VideoSavedCalledback]()
    """Event triggered when the video is saved."""

add_callback(func: AnimatorFunc) -> None

Add a callback to the animation loop.

Source code in src/gsp_datoviz/animator/animator_datoviz.py
63
64
65
def add_callback(self, func: AnimatorFunc) -> None:
    """Add a callback to the animation loop."""
    self._callbacks.append(func)

event_listener(func: AnimatorFunc) -> AnimatorFunc

A decorator to add a callback to the animation loop.

Usage
    @animation_loop.event_listener
    def my_callback(delta_time: float) -> Sequence[Object3D]:
        ...

    # later, if needed
    animation_loop.remove_callback(my_callback)
Source code in src/gsp_datoviz/animator/animator_datoviz.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def event_listener(self, func: AnimatorFunc) -> AnimatorFunc:
    """A decorator to add a callback to the animation loop.

    Usage:
        ```python
            @animation_loop.event_listener
            def my_callback(delta_time: float) -> Sequence[Object3D]:
                ...

            # later, if needed
            animation_loop.remove_callback(my_callback)
        ```
    """
    self.add_callback(func)

    def wrapper(delta_time: float) -> Sequence[VisualBase]:
        # print("Before the function runs")
        result = func(delta_time)
        # print("After the function runs")
        return result

    return wrapper

remove_callback(func: AnimatorFunc) -> None

Remove a callback from the animation loop.

Source code in src/gsp_datoviz/animator/animator_datoviz.py
67
68
69
def remove_callback(self, func: AnimatorFunc) -> None:
    """Remove a callback from the animation loop."""
    self._callbacks.remove(func)

start(viewports: Sequence[Viewport], visuals: Sequence[VisualBase], model_matrices: Sequence[TransBuf], cameras: Sequence[Camera]) -> None

Animate the given canvas and camera using the provided callbacks to update visuals.

Source code in src/gsp_datoviz/animator/animator_datoviz.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def start(self, viewports: Sequence[Viewport], visuals: Sequence[VisualBase], model_matrices: Sequence[TransBuf], cameras: Sequence[Camera]) -> None:
    """Animate the given canvas and camera using the provided callbacks to update visuals."""
    self._canvas = self._datoviz_renderer.get_canvas()
    self._viewports = viewports
    self._visuals = visuals
    self._model_matrices = model_matrices
    self._cameras = cameras
    self._time_last_update = time.time()

    # =============================================================================
    # Render the image once
    # =============================================================================

    self._datoviz_renderer.render(viewports, visuals, model_matrices, cameras)

    # =============================================================================
    # Handle GSP_TEST=True
    # =============================================================================

    # detect if we are in not interactive mode - used during testing
    in_test = "GSP_TEST" in os.environ and os.environ["GSP_TEST"] == "True"

    # if we are not in interactive mode, save a preview image and return
    if in_test == True:
        # notify all animator callbacks
        changed_visuals: list[VisualBase] = []
        for animator_callback in self._callbacks:
            _changed_visuals = animator_callback(1.0 / self._fps)
            changed_visuals.extend(_changed_visuals)

        # render the scene to get the new image
        image_png_data = self._datoviz_renderer.render(viewports, visuals, model_matrices, cameras, return_image=True, image_format="png")
        # get the main script name
        main_script_name = os.path.basename(__main__.__file__) if hasattr(__main__, "__file__") else "interactive"
        main_script_basename = os.path.splitext(main_script_name)[0]
        # buid the output image path
        image_path = os.path.join(__dirname__, "../../../examples/output", f"{main_script_basename}_animator_datoviz.png")
        image_path = os.path.abspath(image_path)
        # save image_png_data in a image file
        with open(image_path, "wb") as image_file:
            image_file.write(image_png_data)
        # log the event
        print(f"Saved animation preview image to: {image_path}")
        return

    # NOTE: here we are in interactive mode!!

    # =============================================================================
    # Initialize the animation
    # =============================================================================

    dvz_app = self._datoviz_renderer.get_dvz_app()

    @dvz_app.timer(period=1.0 / self._fps)
    def on_timer(event):
        self._dvz_animate()

    # =============================================================================
    # Show the animation
    # =============================================================================

    self._datoviz_renderer.show()

stop()

Stop the animation.

Source code in src/gsp_datoviz/animator/animator_datoviz.py
163
164
165
166
167
168
169
def stop(self):
    """Stop the animation."""
    self._canvas = None
    self._viewports = None
    self._time_last_update = None

    warning.warn("GspAnimatorDatoviz.stop() is not fully implemented yet.")