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
 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
218
219
220
221
222
223
224
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._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, 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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._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
66
67
68
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
70
71
72
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
74
75
76
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
78
79
80
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
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
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
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)

        # =============================================================================
        # 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
 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
@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)

    # =============================================================================
    # 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
 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
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

        # =============================================================================
        # 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
 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
@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

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

        # =============================================================================
        #   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
 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
@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)

    # =============================================================================
    #   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
 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
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())

        # convert buffers to numpy arrays
        face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)

        # 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()

        # =============================================================================
        # 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 = np.tile(diameter_px_numpy, vertices_3d.__len__()).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 = np.tile(face_colors_numpy, vertices_3d.__len__()).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
 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
@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())

    # convert buffers to numpy arrays
    face_colors_numpy = Bufferx.to_numpy(face_colors_buffer)

    # 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()

    # =============================================================================
    # 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 = np.tile(diameter_px_numpy, vertices_3d.__len__()).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 = np.tile(face_colors_numpy, vertices_3d.__len__()).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
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

        # =============================================================================
        # 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
 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
@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

    # =============================================================================
    # 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