Skip to content

GSP Matplotlib API Reference

The GSP Matplotlib backend provides rendering using the Matplotlib library, enabling integration with the Python scientific visualization ecosystem.

Overview

gsp_matplotlib

GSP Matplotlib package initialization.

Renderer Module

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

gsp_matplotlib.renderer

Matplotlib Renderer Package.

MatplotlibRenderer

Bases: gsp.types.renderer_base.RendererBase

Matplotlib-based renderer for GSP visuals.

This renderer implements the GSP rendering interface using Matplotlib as the backend. It creates and manages a Matplotlib figure with multiple axes for different viewports, and renders various visual types (pixels, points, paths, markers, segments, texts) into them.

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

    This renderer implements the GSP rendering interface using Matplotlib as the backend.
    It creates and manages a Matplotlib figure with multiple axes for different viewports,
    and renders various visual types (pixels, points, paths, markers, segments, texts) into them.
    """

    def __init__(self, canvas: Canvas):
        """Initialize the Matplotlib renderer.

        Args:
            canvas: The canvas defining the rendering surface dimensions and DPI.
        """
        self.canvas = canvas
        # Store mapping of viewport UUIDs to axes
        self._axes: dict[str, matplotlib.axes.Axes] = {}
        # Store mapping of visual UUIDs to matplotlib artists
        self._artists: dict[str, matplotlib.artist.Artist] = {}
        # Store group count per visual UUID
        self._group_count: dict[str, int] = {}

        # Create a figure
        figure_width = canvas.get_width() / canvas.get_dpi()
        figure_height = canvas.get_height() / canvas.get_dpi()
        self._figure: matplotlib.figure.Figure = matplotlib.pyplot.figure(figsize=(figure_width, figure_height), dpi=canvas.get_dpi())
        assert self._figure.canvas.manager is not None, "matplotlib figure canvas manager is None"
        self._figure.canvas.manager.set_window_title("Matplotlib")

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

        Returns:
            The canvas instance.
        """
        return self.canvas

    def close(self) -> None:
        """Close the renderer and release resources.

        Stops the Matplotlib event loop and closes the figure.
        """
        # warnings.warn(f"Closing NetworkRenderer does not release any resources.", UserWarning)
        # stop the event loop if any - thus .show(block=True) will return
        self._figure.canvas.stop_event_loop()
        # close the figure
        matplotlib.pyplot.close(self._figure)
        self._figure = None  # type: ignore

    def show(self) -> None:
        """Display the rendered figure in an interactive window.

        This method shows the Matplotlib figure. It does nothing when running
        in test mode (GSP_TEST environment variable set to "True").
        """
        # handle non-interactive mode for tests
        in_test = os.environ.get("GSP_TEST") == "True"
        if in_test:
            return

        matplotlib.pyplot.show()

    def render(
        self,
        viewports: Sequence[Viewport],
        visuals: Sequence[VisualBase],
        model_matrices: Sequence[TransBuf],
        cameras: Sequence[Camera],
        return_image: bool = True,
        image_format: str = "png",
    ) -> bytes:
        """Render the scene to an image.

        Args:
            viewports: Sequence of viewport regions to render into.
            visuals: Sequence of visual elements to render.
            model_matrices: Sequence of model transformation matrices for each visual.
            cameras: Sequence of cameras defining view and projection for each visual.
            return_image: Whether to return the rendered image as bytes.
            image_format: Format for the output image (e.g., "png", "jpg").

        Returns:
            The rendered image as bytes in the specified format, or empty bytes if return_image is False.

        Raises:
            AssertionError: If the sequences don't all have the same length.
        """
        # =============================================================================
        # Sanity checks
        # =============================================================================

        assert (
            len(viewports) == len(visuals) == len(model_matrices) == len(cameras)
        ), f"All length MUST be equal. Mismatched lengths: {len(viewports)} viewports, {len(visuals)} visuals, {len(model_matrices)} model matrices, {len(cameras)} cameras"

        # =============================================================================
        # Create all the axes if needed
        # =============================================================================
        for viewport in viewports:
            if viewport.get_uuid() in self._axes:
                continue
            axes_rect = (
                viewport.get_x() / self.canvas.get_width(),
                viewport.get_y() / self.canvas.get_height(),
                viewport.get_width() / self.canvas.get_width(),
                viewport.get_height() / self.canvas.get_height(),
            )
            axes: matplotlib.axes.Axes = matplotlib.pyplot.axes(axes_rect)
            # this should be -1 to 1 - from normalized device coordinates - https://en.wikipedia.org/wiki/Graphics_pipeline
            # - https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection
            axes.set_xlim(-1, 1)
            axes.set_ylim(-1, 1)
            # hide the borders
            axes.axis("off")
            # store axes for this viewport
            self._axes[viewport.get_uuid()] = axes

        # =============================================================================
        # Render each visual
        # =============================================================================

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

        # =============================================================================
        # Render the output image
        # =============================================================================
        image_png_data = b""

        # honor return_image option
        if return_image:
            # Render the image to a PNG buffer
            image_png_buffer = io.BytesIO()
            self._figure.savefig(image_png_buffer, format=image_format, dpi=self.canvas.get_dpi())

            image_png_buffer.seek(0)
            image_png_data = image_png_buffer.getvalue()
            image_png_buffer.close()

        return image_png_data

    def _render_visual(self, viewport: Viewport, visual: VisualBase, model_matrix: TransBuf, camera: Camera):
        """Render a single visual in a given viewport using the specified camera."""
        if isinstance(visual, Pixels):
            from gsp_matplotlib.renderer.matplotlib_renderer_pixels import RendererPixels

            RendererPixels.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Points):
            from gsp_matplotlib.renderer.matplotlib_renderer_points import RendererPoints

            RendererPoints.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Paths):
            from gsp_matplotlib.renderer.matplotlib_renderer_paths import RendererPaths

            RendererPaths.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Markers):
            from gsp_matplotlib.renderer.matplotlib_renderer_markers import RendererMarkers

            RendererMarkers.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Segments):
            from gsp_matplotlib.renderer.matplotlib_renderer_segments import RendererSegments

            RendererSegments.render(self, viewport, visual, model_matrix, camera)

        elif isinstance(visual, Texts):
            from gsp_matplotlib.renderer.matplotlib_renderer_texts import RendererTexts

            RendererTexts.render(self, viewport, visual, model_matrix, camera)
        else:
            raise NotImplementedError(f"Rendering for visual type {type(visual)} is not implemented.")

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

    def get_mpl_axes_for_viewport(self, viewport: Viewport) -> matplotlib.axes.Axes:
        """Get the Matplotlib axes associated with a viewport.

        Args:
            viewport: The viewport to get axes for.

        Returns:
            The Matplotlib Axes object for the given viewport.
        """
        return self._axes[viewport.get_uuid()]

    def get_mpl_figure(self) -> matplotlib.figure.Figure:
        """Get the underlying Matplotlib figure.

        Returns:
            The Matplotlib Figure object used by this renderer.
        """
        return self._figure

__init__(canvas: Canvas)

Initialize the Matplotlib renderer.

Parameters:

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

The canvas defining the rendering surface dimensions and DPI.

required
Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(self, canvas: Canvas):
    """Initialize the Matplotlib renderer.

    Args:
        canvas: The canvas defining the rendering surface dimensions and DPI.
    """
    self.canvas = canvas
    # Store mapping of viewport UUIDs to axes
    self._axes: dict[str, matplotlib.axes.Axes] = {}
    # Store mapping of visual UUIDs to matplotlib artists
    self._artists: dict[str, matplotlib.artist.Artist] = {}
    # Store group count per visual UUID
    self._group_count: dict[str, int] = {}

    # Create a figure
    figure_width = canvas.get_width() / canvas.get_dpi()
    figure_height = canvas.get_height() / canvas.get_dpi()
    self._figure: matplotlib.figure.Figure = matplotlib.pyplot.figure(figsize=(figure_width, figure_height), dpi=canvas.get_dpi())
    assert self._figure.canvas.manager is not None, "matplotlib figure canvas manager is None"
    self._figure.canvas.manager.set_window_title("Matplotlib")

get_canvas() -> Canvas

Get the canvas associated with this renderer.

Returns:

Type Description
gsp.core.canvas.Canvas

The canvas instance.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
64
65
66
67
68
69
70
def get_canvas(self) -> Canvas:
    """Get the canvas associated with this renderer.

    Returns:
        The canvas instance.
    """
    return self.canvas

close() -> None

Close the renderer and release resources.

Stops the Matplotlib event loop and closes the figure.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
72
73
74
75
76
77
78
79
80
81
82
def close(self) -> None:
    """Close the renderer and release resources.

    Stops the Matplotlib event loop and closes the figure.
    """
    # warnings.warn(f"Closing NetworkRenderer does not release any resources.", UserWarning)
    # stop the event loop if any - thus .show(block=True) will return
    self._figure.canvas.stop_event_loop()
    # close the figure
    matplotlib.pyplot.close(self._figure)
    self._figure = None  # type: ignore

show() -> None

Display the rendered figure in an interactive window.

This method shows the Matplotlib figure. It does nothing when running in test mode (GSP_TEST environment variable set to "True").

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
84
85
86
87
88
89
90
91
92
93
94
95
def show(self) -> None:
    """Display the rendered figure in an interactive window.

    This method shows the Matplotlib figure. It does nothing when running
    in test mode (GSP_TEST environment variable set to "True").
    """
    # handle non-interactive mode for tests
    in_test = os.environ.get("GSP_TEST") == "True"
    if in_test:
        return

    matplotlib.pyplot.show()

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 scene to an image.

Parameters:

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

Sequence of viewport regions to render into.

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

Sequence of visual elements to render.

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

Sequence of model transformation matrices for each visual.

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

Sequence of cameras defining view and projection for each visual.

required
return_image bool

Whether to return the rendered image as bytes.

True
image_format str

Format for the output image (e.g., "png", "jpg").

'png'

Returns:

Type Description
bytes

The rendered image as bytes in the specified format, or empty bytes if return_image is False.

Raises:

Type Description
AssertionError

If the sequences don't all have the same length.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def render(
    self,
    viewports: Sequence[Viewport],
    visuals: Sequence[VisualBase],
    model_matrices: Sequence[TransBuf],
    cameras: Sequence[Camera],
    return_image: bool = True,
    image_format: str = "png",
) -> bytes:
    """Render the scene to an image.

    Args:
        viewports: Sequence of viewport regions to render into.
        visuals: Sequence of visual elements to render.
        model_matrices: Sequence of model transformation matrices for each visual.
        cameras: Sequence of cameras defining view and projection for each visual.
        return_image: Whether to return the rendered image as bytes.
        image_format: Format for the output image (e.g., "png", "jpg").

    Returns:
        The rendered image as bytes in the specified format, or empty bytes if return_image is False.

    Raises:
        AssertionError: If the sequences don't all have the same length.
    """
    # =============================================================================
    # Sanity checks
    # =============================================================================

    assert (
        len(viewports) == len(visuals) == len(model_matrices) == len(cameras)
    ), f"All length MUST be equal. Mismatched lengths: {len(viewports)} viewports, {len(visuals)} visuals, {len(model_matrices)} model matrices, {len(cameras)} cameras"

    # =============================================================================
    # Create all the axes if needed
    # =============================================================================
    for viewport in viewports:
        if viewport.get_uuid() in self._axes:
            continue
        axes_rect = (
            viewport.get_x() / self.canvas.get_width(),
            viewport.get_y() / self.canvas.get_height(),
            viewport.get_width() / self.canvas.get_width(),
            viewport.get_height() / self.canvas.get_height(),
        )
        axes: matplotlib.axes.Axes = matplotlib.pyplot.axes(axes_rect)
        # this should be -1 to 1 - from normalized device coordinates - https://en.wikipedia.org/wiki/Graphics_pipeline
        # - https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection
        axes.set_xlim(-1, 1)
        axes.set_ylim(-1, 1)
        # hide the borders
        axes.axis("off")
        # store axes for this viewport
        self._axes[viewport.get_uuid()] = axes

    # =============================================================================
    # Render each visual
    # =============================================================================

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

    # =============================================================================
    # Render the output image
    # =============================================================================
    image_png_data = b""

    # honor return_image option
    if return_image:
        # Render the image to a PNG buffer
        image_png_buffer = io.BytesIO()
        self._figure.savefig(image_png_buffer, format=image_format, dpi=self.canvas.get_dpi())

        image_png_buffer.seek(0)
        image_png_data = image_png_buffer.getvalue()
        image_png_buffer.close()

    return image_png_data

get_mpl_axes_for_viewport(viewport: Viewport) -> matplotlib.axes.Axes

Get the Matplotlib axes associated with a viewport.

Parameters:

Name Type Description Default
viewport gsp.core.viewport.Viewport

The viewport to get axes for.

required

Returns:

Type Description
matplotlib.axes.Axes

The Matplotlib Axes object for the given viewport.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
210
211
212
213
214
215
216
217
218
219
def get_mpl_axes_for_viewport(self, viewport: Viewport) -> matplotlib.axes.Axes:
    """Get the Matplotlib axes associated with a viewport.

    Args:
        viewport: The viewport to get axes for.

    Returns:
        The Matplotlib Axes object for the given viewport.
    """
    return self._axes[viewport.get_uuid()]

get_mpl_figure() -> matplotlib.figure.Figure

Get the underlying Matplotlib figure.

Returns:

Type Description
matplotlib.figure.Figure

The Matplotlib Figure object used by this renderer.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
221
222
223
224
225
226
227
def get_mpl_figure(self) -> matplotlib.figure.Figure:
    """Get the underlying Matplotlib figure.

    Returns:
        The Matplotlib Figure object used by this renderer.
    """
    return self._figure

Matplotlib Renderer

gsp_matplotlib.renderer.matplotlib_renderer

Matplotlib renderer for GSP visuals.

MatplotlibRenderer

Bases: gsp.types.renderer_base.RendererBase

Matplotlib-based renderer for GSP visuals.

This renderer implements the GSP rendering interface using Matplotlib as the backend. It creates and manages a Matplotlib figure with multiple axes for different viewports, and renders various visual types (pixels, points, paths, markers, segments, texts) into them.

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

    This renderer implements the GSP rendering interface using Matplotlib as the backend.
    It creates and manages a Matplotlib figure with multiple axes for different viewports,
    and renders various visual types (pixels, points, paths, markers, segments, texts) into them.
    """

    def __init__(self, canvas: Canvas):
        """Initialize the Matplotlib renderer.

        Args:
            canvas: The canvas defining the rendering surface dimensions and DPI.
        """
        self.canvas = canvas
        # Store mapping of viewport UUIDs to axes
        self._axes: dict[str, matplotlib.axes.Axes] = {}
        # Store mapping of visual UUIDs to matplotlib artists
        self._artists: dict[str, matplotlib.artist.Artist] = {}
        # Store group count per visual UUID
        self._group_count: dict[str, int] = {}

        # Create a figure
        figure_width = canvas.get_width() / canvas.get_dpi()
        figure_height = canvas.get_height() / canvas.get_dpi()
        self._figure: matplotlib.figure.Figure = matplotlib.pyplot.figure(figsize=(figure_width, figure_height), dpi=canvas.get_dpi())
        assert self._figure.canvas.manager is not None, "matplotlib figure canvas manager is None"
        self._figure.canvas.manager.set_window_title("Matplotlib")

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

        Returns:
            The canvas instance.
        """
        return self.canvas

    def close(self) -> None:
        """Close the renderer and release resources.

        Stops the Matplotlib event loop and closes the figure.
        """
        # warnings.warn(f"Closing NetworkRenderer does not release any resources.", UserWarning)
        # stop the event loop if any - thus .show(block=True) will return
        self._figure.canvas.stop_event_loop()
        # close the figure
        matplotlib.pyplot.close(self._figure)
        self._figure = None  # type: ignore

    def show(self) -> None:
        """Display the rendered figure in an interactive window.

        This method shows the Matplotlib figure. It does nothing when running
        in test mode (GSP_TEST environment variable set to "True").
        """
        # handle non-interactive mode for tests
        in_test = os.environ.get("GSP_TEST") == "True"
        if in_test:
            return

        matplotlib.pyplot.show()

    def render(
        self,
        viewports: Sequence[Viewport],
        visuals: Sequence[VisualBase],
        model_matrices: Sequence[TransBuf],
        cameras: Sequence[Camera],
        return_image: bool = True,
        image_format: str = "png",
    ) -> bytes:
        """Render the scene to an image.

        Args:
            viewports: Sequence of viewport regions to render into.
            visuals: Sequence of visual elements to render.
            model_matrices: Sequence of model transformation matrices for each visual.
            cameras: Sequence of cameras defining view and projection for each visual.
            return_image: Whether to return the rendered image as bytes.
            image_format: Format for the output image (e.g., "png", "jpg").

        Returns:
            The rendered image as bytes in the specified format, or empty bytes if return_image is False.

        Raises:
            AssertionError: If the sequences don't all have the same length.
        """
        # =============================================================================
        # Sanity checks
        # =============================================================================

        assert (
            len(viewports) == len(visuals) == len(model_matrices) == len(cameras)
        ), f"All length MUST be equal. Mismatched lengths: {len(viewports)} viewports, {len(visuals)} visuals, {len(model_matrices)} model matrices, {len(cameras)} cameras"

        # =============================================================================
        # Create all the axes if needed
        # =============================================================================
        for viewport in viewports:
            if viewport.get_uuid() in self._axes:
                continue
            axes_rect = (
                viewport.get_x() / self.canvas.get_width(),
                viewport.get_y() / self.canvas.get_height(),
                viewport.get_width() / self.canvas.get_width(),
                viewport.get_height() / self.canvas.get_height(),
            )
            axes: matplotlib.axes.Axes = matplotlib.pyplot.axes(axes_rect)
            # this should be -1 to 1 - from normalized device coordinates - https://en.wikipedia.org/wiki/Graphics_pipeline
            # - https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection
            axes.set_xlim(-1, 1)
            axes.set_ylim(-1, 1)
            # hide the borders
            axes.axis("off")
            # store axes for this viewport
            self._axes[viewport.get_uuid()] = axes

        # =============================================================================
        # Render each visual
        # =============================================================================

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

        # =============================================================================
        # Render the output image
        # =============================================================================
        image_png_data = b""

        # honor return_image option
        if return_image:
            # Render the image to a PNG buffer
            image_png_buffer = io.BytesIO()
            self._figure.savefig(image_png_buffer, format=image_format, dpi=self.canvas.get_dpi())

            image_png_buffer.seek(0)
            image_png_data = image_png_buffer.getvalue()
            image_png_buffer.close()

        return image_png_data

    def _render_visual(self, viewport: Viewport, visual: VisualBase, model_matrix: TransBuf, camera: Camera):
        """Render a single visual in a given viewport using the specified camera."""
        if isinstance(visual, Pixels):
            from gsp_matplotlib.renderer.matplotlib_renderer_pixels import RendererPixels

            RendererPixels.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Points):
            from gsp_matplotlib.renderer.matplotlib_renderer_points import RendererPoints

            RendererPoints.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Paths):
            from gsp_matplotlib.renderer.matplotlib_renderer_paths import RendererPaths

            RendererPaths.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Markers):
            from gsp_matplotlib.renderer.matplotlib_renderer_markers import RendererMarkers

            RendererMarkers.render(self, viewport, visual, model_matrix, camera)
        elif isinstance(visual, Segments):
            from gsp_matplotlib.renderer.matplotlib_renderer_segments import RendererSegments

            RendererSegments.render(self, viewport, visual, model_matrix, camera)

        elif isinstance(visual, Texts):
            from gsp_matplotlib.renderer.matplotlib_renderer_texts import RendererTexts

            RendererTexts.render(self, viewport, visual, model_matrix, camera)
        else:
            raise NotImplementedError(f"Rendering for visual type {type(visual)} is not implemented.")

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

    def get_mpl_axes_for_viewport(self, viewport: Viewport) -> matplotlib.axes.Axes:
        """Get the Matplotlib axes associated with a viewport.

        Args:
            viewport: The viewport to get axes for.

        Returns:
            The Matplotlib Axes object for the given viewport.
        """
        return self._axes[viewport.get_uuid()]

    def get_mpl_figure(self) -> matplotlib.figure.Figure:
        """Get the underlying Matplotlib figure.

        Returns:
            The Matplotlib Figure object used by this renderer.
        """
        return self._figure

__init__(canvas: Canvas)

Initialize the Matplotlib renderer.

Parameters:

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

The canvas defining the rendering surface dimensions and DPI.

required
Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(self, canvas: Canvas):
    """Initialize the Matplotlib renderer.

    Args:
        canvas: The canvas defining the rendering surface dimensions and DPI.
    """
    self.canvas = canvas
    # Store mapping of viewport UUIDs to axes
    self._axes: dict[str, matplotlib.axes.Axes] = {}
    # Store mapping of visual UUIDs to matplotlib artists
    self._artists: dict[str, matplotlib.artist.Artist] = {}
    # Store group count per visual UUID
    self._group_count: dict[str, int] = {}

    # Create a figure
    figure_width = canvas.get_width() / canvas.get_dpi()
    figure_height = canvas.get_height() / canvas.get_dpi()
    self._figure: matplotlib.figure.Figure = matplotlib.pyplot.figure(figsize=(figure_width, figure_height), dpi=canvas.get_dpi())
    assert self._figure.canvas.manager is not None, "matplotlib figure canvas manager is None"
    self._figure.canvas.manager.set_window_title("Matplotlib")

close() -> None

Close the renderer and release resources.

Stops the Matplotlib event loop and closes the figure.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
72
73
74
75
76
77
78
79
80
81
82
def close(self) -> None:
    """Close the renderer and release resources.

    Stops the Matplotlib event loop and closes the figure.
    """
    # warnings.warn(f"Closing NetworkRenderer does not release any resources.", UserWarning)
    # stop the event loop if any - thus .show(block=True) will return
    self._figure.canvas.stop_event_loop()
    # close the figure
    matplotlib.pyplot.close(self._figure)
    self._figure = None  # type: ignore

get_canvas() -> Canvas

Get the canvas associated with this renderer.

Returns:

Type Description
gsp.core.canvas.Canvas

The canvas instance.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
64
65
66
67
68
69
70
def get_canvas(self) -> Canvas:
    """Get the canvas associated with this renderer.

    Returns:
        The canvas instance.
    """
    return self.canvas

get_mpl_axes_for_viewport(viewport: Viewport) -> matplotlib.axes.Axes

Get the Matplotlib axes associated with a viewport.

Parameters:

Name Type Description Default
viewport gsp.core.viewport.Viewport

The viewport to get axes for.

required

Returns:

Type Description
matplotlib.axes.Axes

The Matplotlib Axes object for the given viewport.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
210
211
212
213
214
215
216
217
218
219
def get_mpl_axes_for_viewport(self, viewport: Viewport) -> matplotlib.axes.Axes:
    """Get the Matplotlib axes associated with a viewport.

    Args:
        viewport: The viewport to get axes for.

    Returns:
        The Matplotlib Axes object for the given viewport.
    """
    return self._axes[viewport.get_uuid()]

get_mpl_figure() -> matplotlib.figure.Figure

Get the underlying Matplotlib figure.

Returns:

Type Description
matplotlib.figure.Figure

The Matplotlib Figure object used by this renderer.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
221
222
223
224
225
226
227
def get_mpl_figure(self) -> matplotlib.figure.Figure:
    """Get the underlying Matplotlib figure.

    Returns:
        The Matplotlib Figure object used by this renderer.
    """
    return self._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 scene to an image.

Parameters:

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

Sequence of viewport regions to render into.

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

Sequence of visual elements to render.

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

Sequence of model transformation matrices for each visual.

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

Sequence of cameras defining view and projection for each visual.

required
return_image bool

Whether to return the rendered image as bytes.

True
image_format str

Format for the output image (e.g., "png", "jpg").

'png'

Returns:

Type Description
bytes

The rendered image as bytes in the specified format, or empty bytes if return_image is False.

Raises:

Type Description
AssertionError

If the sequences don't all have the same length.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def render(
    self,
    viewports: Sequence[Viewport],
    visuals: Sequence[VisualBase],
    model_matrices: Sequence[TransBuf],
    cameras: Sequence[Camera],
    return_image: bool = True,
    image_format: str = "png",
) -> bytes:
    """Render the scene to an image.

    Args:
        viewports: Sequence of viewport regions to render into.
        visuals: Sequence of visual elements to render.
        model_matrices: Sequence of model transformation matrices for each visual.
        cameras: Sequence of cameras defining view and projection for each visual.
        return_image: Whether to return the rendered image as bytes.
        image_format: Format for the output image (e.g., "png", "jpg").

    Returns:
        The rendered image as bytes in the specified format, or empty bytes if return_image is False.

    Raises:
        AssertionError: If the sequences don't all have the same length.
    """
    # =============================================================================
    # Sanity checks
    # =============================================================================

    assert (
        len(viewports) == len(visuals) == len(model_matrices) == len(cameras)
    ), f"All length MUST be equal. Mismatched lengths: {len(viewports)} viewports, {len(visuals)} visuals, {len(model_matrices)} model matrices, {len(cameras)} cameras"

    # =============================================================================
    # Create all the axes if needed
    # =============================================================================
    for viewport in viewports:
        if viewport.get_uuid() in self._axes:
            continue
        axes_rect = (
            viewport.get_x() / self.canvas.get_width(),
            viewport.get_y() / self.canvas.get_height(),
            viewport.get_width() / self.canvas.get_width(),
            viewport.get_height() / self.canvas.get_height(),
        )
        axes: matplotlib.axes.Axes = matplotlib.pyplot.axes(axes_rect)
        # this should be -1 to 1 - from normalized device coordinates - https://en.wikipedia.org/wiki/Graphics_pipeline
        # - https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection
        axes.set_xlim(-1, 1)
        axes.set_ylim(-1, 1)
        # hide the borders
        axes.axis("off")
        # store axes for this viewport
        self._axes[viewport.get_uuid()] = axes

    # =============================================================================
    # Render each visual
    # =============================================================================

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

    # =============================================================================
    # Render the output image
    # =============================================================================
    image_png_data = b""

    # honor return_image option
    if return_image:
        # Render the image to a PNG buffer
        image_png_buffer = io.BytesIO()
        self._figure.savefig(image_png_buffer, format=image_format, dpi=self.canvas.get_dpi())

        image_png_buffer.seek(0)
        image_png_data = image_png_buffer.getvalue()
        image_png_buffer.close()

    return image_png_data

show() -> None

Display the rendered figure in an interactive window.

This method shows the Matplotlib figure. It does nothing when running in test mode (GSP_TEST environment variable set to "True").

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer.py
84
85
86
87
88
89
90
91
92
93
94
95
def show(self) -> None:
    """Display the rendered figure in an interactive window.

    This method shows the Matplotlib figure. It does nothing when running
    in test mode (GSP_TEST environment variable set to "True").
    """
    # handle non-interactive mode for tests
    in_test = os.environ.get("GSP_TEST") == "True"
    if in_test:
        return

    matplotlib.pyplot.show()

Markers Renderer

gsp_matplotlib.renderer.matplotlib_renderer_markers

Matplotlib renderer for Markers objects.

RendererMarkers

Renderer for Markers objects using Matplotlib.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_markers.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class RendererMarkers:
    """Renderer for Markers objects using Matplotlib."""
    @staticmethod
    def render(
        renderer: MatplotlibRenderer,
        viewport: Viewport,
        markers: Markers,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> list[matplotlib.artist.Artist]:
        """Render the given Markers object onto the specified viewport using Matplotlib.

        Args:
            renderer (MatplotlibRenderer): The renderer instance.
            viewport (Viewport): The viewport to render onto.
            markers (Markers): The Markers object containing marker data.
            model_matrix (TransBuf): The model transformation matrix.           
            camera (Camera): The camera providing view and projection matrices.

        Returns:
            list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes to buffer
        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_numpy = Bufferx.to_numpy(sizes_buffer).reshape(-1)
        face_colors_numpy = Bufferx.to_numpy(face_colors_buffer) / 255.0  # normalize to [0, 1] range
        edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer) / 255.0  # normalize to [0, 1] range
        edge_widths_numpy = Bufferx.to_numpy(edge_widths_buffer).flatten()

        # =============================================================================
        # Create the artists if needed
        # =============================================================================

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

        if artist_uuid not in renderer._artists:
            axes = renderer.get_mpl_axes_for_viewport(viewport)
            mpl_marker_shape = ConverterUtils.marker_shape_gsp_to_mpl(markers.get_marker_shape())
            mpl_path_collection = axes.scatter([], [], marker=mpl_marker_shape)
            mpl_path_collection.set_visible(False)
            # hide until properly positioned and sized
            renderer._artists[artist_uuid] = mpl_path_collection
            axes.add_artist(mpl_path_collection)

        # =============================================================================
        # Get existing artists
        # =============================================================================

        mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[artist_uuid])
        mpl_path_collection.set_visible(True)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_path_collection.set_offsets(offsets=vertices_2d)
        mpl_path_collection.set_sizes(typing.cast(list, sizes_numpy))
        mpl_path_collection.set_facecolor(typing.cast(list, face_colors_numpy))
        mpl_path_collection.set_edgecolor(typing.cast(list, edge_colors_numpy))
        mpl_path_collection.set_linewidth(typing.cast(list, edge_widths_numpy))

        # Return the list of artists created/updated
        return [mpl_path_collection]

render(renderer: MatplotlibRenderer, viewport: Viewport, markers: Markers, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render the given Markers object onto the specified viewport using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.matplotlib_renderer.MatplotlibRenderer

The renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render onto.

required
markers gsp.visuals.Markers

The Markers object containing marker data.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix.

required
camera gsp.core.camera.Camera

The camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_markers.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
111
112
@staticmethod
def render(
    renderer: MatplotlibRenderer,
    viewport: Viewport,
    markers: Markers,
    model_matrix: TransBuf,
    camera: Camera,
) -> list[matplotlib.artist.Artist]:
    """Render the given Markers object onto the specified viewport using Matplotlib.

    Args:
        renderer (MatplotlibRenderer): The renderer instance.
        viewport (Viewport): The viewport to render onto.
        markers (Markers): The Markers object containing marker data.
        model_matrix (TransBuf): The model transformation matrix.           
        camera (Camera): The camera providing view and projection matrices.

    Returns:
        list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes to buffer
    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_numpy = Bufferx.to_numpy(sizes_buffer).reshape(-1)
    face_colors_numpy = Bufferx.to_numpy(face_colors_buffer) / 255.0  # normalize to [0, 1] range
    edge_colors_numpy = Bufferx.to_numpy(edge_colors_buffer) / 255.0  # normalize to [0, 1] range
    edge_widths_numpy = Bufferx.to_numpy(edge_widths_buffer).flatten()

    # =============================================================================
    # Create the artists if needed
    # =============================================================================

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

    if artist_uuid not in renderer._artists:
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        mpl_marker_shape = ConverterUtils.marker_shape_gsp_to_mpl(markers.get_marker_shape())
        mpl_path_collection = axes.scatter([], [], marker=mpl_marker_shape)
        mpl_path_collection.set_visible(False)
        # hide until properly positioned and sized
        renderer._artists[artist_uuid] = mpl_path_collection
        axes.add_artist(mpl_path_collection)

    # =============================================================================
    # Get existing artists
    # =============================================================================

    mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[artist_uuid])
    mpl_path_collection.set_visible(True)

    # =============================================================================
    # Update artists
    # =============================================================================

    mpl_path_collection.set_offsets(offsets=vertices_2d)
    mpl_path_collection.set_sizes(typing.cast(list, sizes_numpy))
    mpl_path_collection.set_facecolor(typing.cast(list, face_colors_numpy))
    mpl_path_collection.set_edgecolor(typing.cast(list, edge_colors_numpy))
    mpl_path_collection.set_linewidth(typing.cast(list, edge_widths_numpy))

    # Return the list of artists created/updated
    return [mpl_path_collection]

Paths Renderer

gsp_matplotlib.renderer.matplotlib_renderer_paths

Matplotlib renderer for Paths objects.

RendererPaths

Renderer for Paths objects using Matplotlib.

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

    @staticmethod
    def render(
        renderer: MatplotlibRenderer,
        viewport: Viewport,
        paths: Paths,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> list[matplotlib.artist.Artist]:
        """Render the given Paths object onto the specified viewport using Matplotlib.

        Args:
            renderer (MatplotlibRenderer): The renderer instance.
            viewport (Viewport): The viewport to render onto.
            paths (Paths): The Paths object containing path data.
            model_matrix (TransBuf): The model transformation matrix.
            camera (Camera): The camera providing view and projection matrices.

        Returns:
            list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes 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) / 255.0  # normalize to [0, 1] range
        line_widths_numpy = Bufferx.to_numpy(line_widths_buffer)
        line_widths_numpy = line_widths_numpy.reshape(-1)

        # =============================================================================
        #
        # =============================================================================
        # mpl_paths is of shape (M, 2, 2) where M is total number of line segments across all paths
        mpl_paths = np.zeros((0, 2, 2), dtype=np.float32)
        # mpl_colors is of shape (M, 4)
        mpl_colors = np.zeros((0, 4), dtype=np.float32)
        # mpl_line_widths is of shape (M,)
        mpl_line_widths = np.zeros((0,), dtype=np.float32)

        for path_index, path_size in enumerate(path_sizes_numpy):
            path_start = int(np.sum(path_sizes_numpy[:path_index]))
            path_size_int = int(path_size)
            path_vertices_2d = vertices_2d[path_start : path_start + path_size_int]

            # Create segments for this path
            path_mpl_paths = np.concatenate([path_vertices_2d[:-1].reshape(-1, 1, 2), path_vertices_2d[1:].reshape(-1, 1, 2)], axis=1)
            mpl_paths = np.vstack([mpl_paths, path_mpl_paths])

            mpl_colors = np.vstack([mpl_colors, colors_numpy[path_start : path_start + path_size_int - 1]])
            mpl_line_widths = np.hstack([mpl_line_widths, line_widths_numpy[path_start : path_start + path_size_int - 1]])

        # =============================================================================
        # Create the artists if needed
        # =============================================================================

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

        if artist_uuid not in renderer._artists:
            mpl_line_collection = matplotlib.collections.LineCollection([])
            mpl_line_collection.set_visible(False)
            # hide until properly positioned and sized
            renderer._artists[artist_uuid] = mpl_line_collection
            axes = renderer.get_mpl_axes_for_viewport(viewport)
            axes.add_artist(mpl_line_collection)

        # =============================================================================
        # Get existing artists
        # =============================================================================

        mpl_line_collection = typing.cast(matplotlib.collections.LineCollection, renderer._artists[artist_uuid])
        mpl_line_collection.set_visible(True)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_line_collection.set_paths(typing.cast(list, mpl_paths))
        mpl_line_collection.set_color(typing.cast(list, mpl_colors))
        mpl_line_collection.set_linewidth(typing.cast(list, mpl_line_widths))
        mpl_line_collection.set_capstyle(ConverterUtils.cap_style_gsp_to_mpl(paths.get_cap_style()))
        mpl_line_collection.set_joinstyle(ConverterUtils.join_style_gsp_to_mpl(paths.get_join_style()))

        # Return the list of artists created/updated
        changed_artists: list[matplotlib.artist.Artist] = []
        changed_artists.append(mpl_line_collection)
        return changed_artists

render(renderer: MatplotlibRenderer, viewport: Viewport, paths: Paths, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render the given Paths object onto the specified viewport using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.matplotlib_renderer.MatplotlibRenderer

The renderer instance.

required
viewport gsp.core.viewport.Viewport

The viewport to render onto.

required
paths gsp.visuals.paths.Paths

The Paths object containing path data.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix.

required
camera gsp.core.camera.Camera

The camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated

Source code in src/gsp_matplotlib/renderer/matplotlib_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
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
@staticmethod
def render(
    renderer: MatplotlibRenderer,
    viewport: Viewport,
    paths: Paths,
    model_matrix: TransBuf,
    camera: Camera,
) -> list[matplotlib.artist.Artist]:
    """Render the given Paths object onto the specified viewport using Matplotlib.

    Args:
        renderer (MatplotlibRenderer): The renderer instance.
        viewport (Viewport): The viewport to render onto.
        paths (Paths): The Paths object containing path data.
        model_matrix (TransBuf): The model transformation matrix.
        camera (Camera): The camera providing view and projection matrices.

    Returns:
        list[matplotlib.artist.Artist]: A list of Matplotlib artist objects created or updated
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes 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) / 255.0  # normalize to [0, 1] range
    line_widths_numpy = Bufferx.to_numpy(line_widths_buffer)
    line_widths_numpy = line_widths_numpy.reshape(-1)

    # =============================================================================
    #
    # =============================================================================
    # mpl_paths is of shape (M, 2, 2) where M is total number of line segments across all paths
    mpl_paths = np.zeros((0, 2, 2), dtype=np.float32)
    # mpl_colors is of shape (M, 4)
    mpl_colors = np.zeros((0, 4), dtype=np.float32)
    # mpl_line_widths is of shape (M,)
    mpl_line_widths = np.zeros((0,), dtype=np.float32)

    for path_index, path_size in enumerate(path_sizes_numpy):
        path_start = int(np.sum(path_sizes_numpy[:path_index]))
        path_size_int = int(path_size)
        path_vertices_2d = vertices_2d[path_start : path_start + path_size_int]

        # Create segments for this path
        path_mpl_paths = np.concatenate([path_vertices_2d[:-1].reshape(-1, 1, 2), path_vertices_2d[1:].reshape(-1, 1, 2)], axis=1)
        mpl_paths = np.vstack([mpl_paths, path_mpl_paths])

        mpl_colors = np.vstack([mpl_colors, colors_numpy[path_start : path_start + path_size_int - 1]])
        mpl_line_widths = np.hstack([mpl_line_widths, line_widths_numpy[path_start : path_start + path_size_int - 1]])

    # =============================================================================
    # Create the artists if needed
    # =============================================================================

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

    if artist_uuid not in renderer._artists:
        mpl_line_collection = matplotlib.collections.LineCollection([])
        mpl_line_collection.set_visible(False)
        # hide until properly positioned and sized
        renderer._artists[artist_uuid] = mpl_line_collection
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        axes.add_artist(mpl_line_collection)

    # =============================================================================
    # Get existing artists
    # =============================================================================

    mpl_line_collection = typing.cast(matplotlib.collections.LineCollection, renderer._artists[artist_uuid])
    mpl_line_collection.set_visible(True)

    # =============================================================================
    # Update artists
    # =============================================================================

    mpl_line_collection.set_paths(typing.cast(list, mpl_paths))
    mpl_line_collection.set_color(typing.cast(list, mpl_colors))
    mpl_line_collection.set_linewidth(typing.cast(list, mpl_line_widths))
    mpl_line_collection.set_capstyle(ConverterUtils.cap_style_gsp_to_mpl(paths.get_cap_style()))
    mpl_line_collection.set_joinstyle(ConverterUtils.join_style_gsp_to_mpl(paths.get_join_style()))

    # Return the list of artists created/updated
    changed_artists: list[matplotlib.artist.Artist] = []
    changed_artists.append(mpl_line_collection)
    return changed_artists

Pixels Renderer

gsp_matplotlib.renderer.matplotlib_renderer_pixels

"Renderer for Pixels using Matplotlib.

RendererPixels

Renderer for Pixels using Matplotlib.

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

        Args:
            renderer: The MatplotlibRenderer instance.
            viewport: The Viewport in which to render.
            pixels: The Pixels visual to render.
            model_matrix: The model transformation matrix as a TransBuf.
            camera: The Camera providing view and projection matrices.      

        Returns:
            list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes to buffer
        color_buffer = TransBufUtils.to_buffer(pixels.get_colors())

        # Convert buffers to numpy arrays
        colors_numpy = Bufferx.to_numpy(color_buffer) / 255.0  # normalize to [0, 1] range

        # Sanity check - check visual attributes
        Pixels.sanity_check_attribute_buffers(vertices_buffer, color_buffer, pixels.get_groups())

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

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

        # =============================================================================
        # Create the artists 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 artists
        if old_group_count is not None and old_group_count != group_count:
            RendererPixels.destroy_artists(renderer, viewport, pixels, old_group_count)

        # Create artists if they do not exist
        artist_uuid_sample = f"{artist_uuid_prefix}_group_0"
        if artist_uuid_sample not in renderer._artists:
            RendererPixels.create_artists(renderer, viewport, pixels, group_count)

        # =============================================================================
        # Update matplotlib for each group
        # =============================================================================

        changed_artists: list[matplotlib.artist.Artist] = []
        for group_index in range(group_count):
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"

            # =============================================================================
            # Get existing artists
            # =============================================================================

            mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[group_uuid])
            mpl_path_collection.set_visible(True)
            changed_artists.append(mpl_path_collection)

            # =============================================================================
            # Update artists
            # =============================================================================

            mpl_path_collection.set_offsets(offsets=vertices_2d[indices_per_group[group_index]])
            mpl_path_collection.set_facecolor(typing.cast(list, colors_numpy[group_index]))

        # Return the list of artists created/updated
        return changed_artists

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

    @staticmethod
    def create_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None:
        """Create the artists associated with the given visual and group count.

        Args:
            renderer: The Matplotlib renderer.
            viewport: The viewport for which to create the artists.
            visual: The visual for which to create the artists.
            group_count: The number of groups in the visual.
        """
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        artist_uuid_prefix = f"{viewport.get_uuid()}_{visual.get_uuid()}"
        # compute 1 pixel size in points squared for matplotlib sizing
        assert axes.figure.get_dpi() is not None, "Canvas DPI must be set for proper pixel sizing"
        size_pt = UnitUtils.pixel_to_point(1.0, axes.figure.get_dpi())
        size_squared_pt = size_pt * size_pt

        for group_index in range(group_count):
            mpl_path_collection = axes.scatter([], [], s=size_squared_pt, marker="o")
            mpl_path_collection.set_antialiased(True)
            mpl_path_collection.set_linewidth(0)
            mpl_path_collection.set_visible(False)
            # hide until properly positioned and sized
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
            renderer._artists[group_uuid] = mpl_path_collection
            axes.add_artist(mpl_path_collection)

    @staticmethod
    def destroy_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None:
        """Destroy the artists associated with the given visual and group count.

        Trigger a bug in matplotlib where artists are not properly removed from the axes.
        """
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        artist_uuid_prefix = f"{viewport.get_uuid()}_{visual.get_uuid()}"
        for group_index in range(group_count):
            group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
            mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[group_uuid])
            del renderer._artists[group_uuid]
            mpl_path_collection.remove()

            # axes.collections.remove(mpl_path_collection)
            # axes.collections.remove(axes.collections.index(mpl_path_collection))

            ax = axes
            artist = mpl_path_collection

            print("Artist:", artist)
            print("In ax.artists?", artist in ax.artists)
            print("In ax.patches?", artist in ax.patches)
            print("In ax.lines?", artist in ax.lines)
            print("In ax.collections?", artist in ax.collections)
            print("In ax.texts?", artist in ax.texts)
            print("Figure art?", artist in getattr(ax.figure, "artists", []))

create_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None staticmethod

Create the artists associated with the given visual and group count.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.MatplotlibRenderer

The Matplotlib renderer.

required
viewport gsp.core.viewport.Viewport

The viewport for which to create the artists.

required
visual gsp.types.visual_base.VisualBase

The visual for which to create the artists.

required
group_count int

The number of groups in the visual.

required
Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_pixels.py
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
@staticmethod
def create_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None:
    """Create the artists associated with the given visual and group count.

    Args:
        renderer: The Matplotlib renderer.
        viewport: The viewport for which to create the artists.
        visual: The visual for which to create the artists.
        group_count: The number of groups in the visual.
    """
    axes = renderer.get_mpl_axes_for_viewport(viewport)
    artist_uuid_prefix = f"{viewport.get_uuid()}_{visual.get_uuid()}"
    # compute 1 pixel size in points squared for matplotlib sizing
    assert axes.figure.get_dpi() is not None, "Canvas DPI must be set for proper pixel sizing"
    size_pt = UnitUtils.pixel_to_point(1.0, axes.figure.get_dpi())
    size_squared_pt = size_pt * size_pt

    for group_index in range(group_count):
        mpl_path_collection = axes.scatter([], [], s=size_squared_pt, marker="o")
        mpl_path_collection.set_antialiased(True)
        mpl_path_collection.set_linewidth(0)
        mpl_path_collection.set_visible(False)
        # hide until properly positioned and sized
        group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
        renderer._artists[group_uuid] = mpl_path_collection
        axes.add_artist(mpl_path_collection)

destroy_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None staticmethod

Destroy the artists associated with the given visual and group count.

Trigger a bug in matplotlib where artists are not properly removed from the axes.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_pixels.py
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
@staticmethod
def destroy_artists(renderer: MatplotlibRenderer, viewport: Viewport, visual: VisualBase, group_count: int) -> None:
    """Destroy the artists associated with the given visual and group count.

    Trigger a bug in matplotlib where artists are not properly removed from the axes.
    """
    axes = renderer.get_mpl_axes_for_viewport(viewport)
    artist_uuid_prefix = f"{viewport.get_uuid()}_{visual.get_uuid()}"
    for group_index in range(group_count):
        group_uuid = f"{artist_uuid_prefix}_group_{group_index}"
        mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[group_uuid])
        del renderer._artists[group_uuid]
        mpl_path_collection.remove()

        # axes.collections.remove(mpl_path_collection)
        # axes.collections.remove(axes.collections.index(mpl_path_collection))

        ax = axes
        artist = mpl_path_collection

        print("Artist:", artist)
        print("In ax.artists?", artist in ax.artists)
        print("In ax.patches?", artist in ax.patches)
        print("In ax.lines?", artist in ax.lines)
        print("In ax.collections?", artist in ax.collections)
        print("In ax.texts?", artist in ax.texts)
        print("Figure art?", artist in getattr(ax.figure, "artists", []))

render(renderer: MatplotlibRenderer, viewport: Viewport, pixels: Pixels, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render Pixels visual using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.MatplotlibRenderer

The MatplotlibRenderer instance.

required
viewport gsp.core.viewport.Viewport

The Viewport in which to render.

required
pixels gsp.visuals.pixels.Pixels

The Pixels visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix as a TransBuf.

required
camera gsp.core.camera.Camera

The Camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.

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

    Args:
        renderer: The MatplotlibRenderer instance.
        viewport: The Viewport in which to render.
        pixels: The Pixels visual to render.
        model_matrix: The model transformation matrix as a TransBuf.
        camera: The Camera providing view and projection matrices.      

    Returns:
        list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes to buffer
    color_buffer = TransBufUtils.to_buffer(pixels.get_colors())

    # Convert buffers to numpy arrays
    colors_numpy = Bufferx.to_numpy(color_buffer) / 255.0  # normalize to [0, 1] range

    # Sanity check - check visual attributes
    Pixels.sanity_check_attribute_buffers(vertices_buffer, color_buffer, pixels.get_groups())

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

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

    # =============================================================================
    # Create the artists 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 artists
    if old_group_count is not None and old_group_count != group_count:
        RendererPixels.destroy_artists(renderer, viewport, pixels, old_group_count)

    # Create artists if they do not exist
    artist_uuid_sample = f"{artist_uuid_prefix}_group_0"
    if artist_uuid_sample not in renderer._artists:
        RendererPixels.create_artists(renderer, viewport, pixels, group_count)

    # =============================================================================
    # Update matplotlib for each group
    # =============================================================================

    changed_artists: list[matplotlib.artist.Artist] = []
    for group_index in range(group_count):
        group_uuid = f"{artist_uuid_prefix}_group_{group_index}"

        # =============================================================================
        # Get existing artists
        # =============================================================================

        mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[group_uuid])
        mpl_path_collection.set_visible(True)
        changed_artists.append(mpl_path_collection)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_path_collection.set_offsets(offsets=vertices_2d[indices_per_group[group_index]])
        mpl_path_collection.set_facecolor(typing.cast(list, colors_numpy[group_index]))

    # Return the list of artists created/updated
    return changed_artists

Points Renderer

gsp_matplotlib.renderer.matplotlib_renderer_points

Renderer for Points using Matplotlib.

RendererPoints

Renderer for Points using Matplotlib.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_points.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class RendererPoints:
    """Renderer for Points using Matplotlib."""

    @staticmethod
    def render(
        renderer: MatplotlibRenderer,
        viewport: Viewport,
        points: Points,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> list[matplotlib.artist.Artist]:
        """Render Points visual using Matplotlib.

        Args:
            renderer: The MatplotlibRenderer instance.
            viewport: The Viewport in which to render.
            points: The Points visual to render.
            model_matrix: The model transformation matrix as a TransBuf.
            camera: The Camera providing view and projection matrices.

        Returns:
            list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes to buffer
        sizes_buffer = TransBufUtils.to_buffer(points.get_sizes())
        face_colors_buffer = TransBufUtils.to_buffer(points.get_face_colors())
        edge_colors_buffer = TransBufUtils.to_buffer(points.get_edge_colors())
        edge_widths_buffer = TransBufUtils.to_buffer(points.get_edge_widths())

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

        # =============================================================================
        # Create the artists if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{points.get_uuid()}"
        if artist_uuid not in renderer._artists:
            axes = renderer.get_mpl_axes_for_viewport(viewport)
            mpl_path_collection = axes.scatter([], [])
            mpl_path_collection.set_visible(False)
            # hide until properly positioned and sized
            renderer._artists[artist_uuid] = mpl_path_collection
            axes.add_artist(mpl_path_collection)

        # =============================================================================
        # Get existing artists
        # =============================================================================

        mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[artist_uuid])
        mpl_path_collection.set_visible(True)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_path_collection.set_offsets(offsets=vertices_2d)
        mpl_path_collection.set_sizes(typing.cast(list, sizes_numpy))
        mpl_path_collection.set_facecolor(typing.cast(list, face_colors_numpy))
        mpl_path_collection.set_edgecolor(typing.cast(list, edge_colors_numpy))
        mpl_path_collection.set_linewidth(typing.cast(list, edge_widths_numpy))

        # Return the list of artists created/updated
        changed_artists: list[matplotlib.artist.Artist] = []
        changed_artists.append(mpl_path_collection)
        return changed_artists

render(renderer: MatplotlibRenderer, viewport: Viewport, points: Points, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render Points visual using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.matplotlib_renderer.MatplotlibRenderer

The MatplotlibRenderer instance.

required
viewport gsp.core.viewport.Viewport

The Viewport in which to render.

required
points gsp.visuals.points.Points

The Points visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix as a TransBuf.

required
camera gsp.core.camera.Camera

The Camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_points.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
111
112
@staticmethod
def render(
    renderer: MatplotlibRenderer,
    viewport: Viewport,
    points: Points,
    model_matrix: TransBuf,
    camera: Camera,
) -> list[matplotlib.artist.Artist]:
    """Render Points visual using Matplotlib.

    Args:
        renderer: The MatplotlibRenderer instance.
        viewport: The Viewport in which to render.
        points: The Points visual to render.
        model_matrix: The model transformation matrix as a TransBuf.
        camera: The Camera providing view and projection matrices.

    Returns:
        list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes to buffer
    sizes_buffer = TransBufUtils.to_buffer(points.get_sizes())
    face_colors_buffer = TransBufUtils.to_buffer(points.get_face_colors())
    edge_colors_buffer = TransBufUtils.to_buffer(points.get_edge_colors())
    edge_widths_buffer = TransBufUtils.to_buffer(points.get_edge_widths())

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

    # =============================================================================
    # Create the artists if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{points.get_uuid()}"
    if artist_uuid not in renderer._artists:
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        mpl_path_collection = axes.scatter([], [])
        mpl_path_collection.set_visible(False)
        # hide until properly positioned and sized
        renderer._artists[artist_uuid] = mpl_path_collection
        axes.add_artist(mpl_path_collection)

    # =============================================================================
    # Get existing artists
    # =============================================================================

    mpl_path_collection = typing.cast(matplotlib.collections.PathCollection, renderer._artists[artist_uuid])
    mpl_path_collection.set_visible(True)

    # =============================================================================
    # Update artists
    # =============================================================================

    mpl_path_collection.set_offsets(offsets=vertices_2d)
    mpl_path_collection.set_sizes(typing.cast(list, sizes_numpy))
    mpl_path_collection.set_facecolor(typing.cast(list, face_colors_numpy))
    mpl_path_collection.set_edgecolor(typing.cast(list, edge_colors_numpy))
    mpl_path_collection.set_linewidth(typing.cast(list, edge_widths_numpy))

    # Return the list of artists created/updated
    changed_artists: list[matplotlib.artist.Artist] = []
    changed_artists.append(mpl_path_collection)
    return changed_artists

Segments Renderer

gsp_matplotlib.renderer.matplotlib_renderer_segments

Renderer for Segments using Matplotlib.

RendererSegments

Renderer for Segments using Matplotlib.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_segments.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
class RendererSegments:
    """Renderer for Segments using Matplotlib."""

    @staticmethod
    def render(
        renderer: MatplotlibRenderer,
        viewport: Viewport,
        segments: Segments,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> list[matplotlib.artist.Artist]:
        """Render Segments visual using Matplotlib.

        Args:
            renderer: The MatplotlibRenderer instance.
            viewport: The Viewport in which to render.
            segments: The Segments visual to render.
            model_matrix: The model transformation matrix as a TransBuf.
            camera: The Camera providing view and projection matrices.

        Returns:
            list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes 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
        positions_numpy = vertices_2d.reshape(-1, 2, 2)
        colors_numpy = Bufferx.to_numpy(colors_buffer) / 255.0  # normalize to [0, 1] range
        line_widths_numpy = Bufferx.to_numpy(line_widths_buffer)
        line_widths_numpy = line_widths_numpy.reshape(-1)

        # =============================================================================
        # Create the artists if needed
        # =============================================================================

        artist_uuid = f"{viewport.get_uuid()}_{segments.get_uuid()}"
        if artist_uuid not in renderer._artists:
            mpl_line_collection = matplotlib.collections.LineCollection([])
            mpl_line_collection.set_visible(False)
            # hide until properly positioned and sized
            renderer._artists[artist_uuid] = mpl_line_collection
            axes = renderer.get_mpl_axes_for_viewport(viewport)
            axes.add_artist(mpl_line_collection)

        # =============================================================================
        # Get existing artists
        # =============================================================================

        mpl_line_collection = typing.cast(matplotlib.collections.LineCollection, renderer._artists[artist_uuid])
        mpl_line_collection.set_visible(True)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_line_collection.set_paths(typing.cast(list, positions_numpy))
        mpl_line_collection.set_color(typing.cast(list, colors_numpy))
        mpl_line_collection.set_linewidth(typing.cast(list, line_widths_numpy))
        mpl_line_collection.set_capstyle(ConverterUtils.cap_style_gsp_to_mpl(segments.get_cap_style()))

        # Return the list of artists created/updated
        changed_artists: list[matplotlib.artist.Artist] = []
        changed_artists.append(mpl_line_collection)
        return changed_artists

render(renderer: MatplotlibRenderer, viewport: Viewport, segments: Segments, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render Segments visual using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.matplotlib_renderer.MatplotlibRenderer

The MatplotlibRenderer instance.

required
viewport gsp.core.viewport.Viewport

The Viewport in which to render.

required
segments gsp.visuals.segments.Segments

The Segments visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix as a TransBuf.

required
camera gsp.core.camera.Camera

The Camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.

Source code in src/gsp_matplotlib/renderer/matplotlib_renderer_segments.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
@staticmethod
def render(
    renderer: MatplotlibRenderer,
    viewport: Viewport,
    segments: Segments,
    model_matrix: TransBuf,
    camera: Camera,
) -> list[matplotlib.artist.Artist]:
    """Render Segments visual using Matplotlib.

    Args:
        renderer: The MatplotlibRenderer instance.
        viewport: The Viewport in which to render.
        segments: The Segments visual to render.
        model_matrix: The model transformation matrix as a TransBuf.
        camera: The Camera providing view and projection matrices.

    Returns:
        list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes 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
    positions_numpy = vertices_2d.reshape(-1, 2, 2)
    colors_numpy = Bufferx.to_numpy(colors_buffer) / 255.0  # normalize to [0, 1] range
    line_widths_numpy = Bufferx.to_numpy(line_widths_buffer)
    line_widths_numpy = line_widths_numpy.reshape(-1)

    # =============================================================================
    # Create the artists if needed
    # =============================================================================

    artist_uuid = f"{viewport.get_uuid()}_{segments.get_uuid()}"
    if artist_uuid not in renderer._artists:
        mpl_line_collection = matplotlib.collections.LineCollection([])
        mpl_line_collection.set_visible(False)
        # hide until properly positioned and sized
        renderer._artists[artist_uuid] = mpl_line_collection
        axes = renderer.get_mpl_axes_for_viewport(viewport)
        axes.add_artist(mpl_line_collection)

    # =============================================================================
    # Get existing artists
    # =============================================================================

    mpl_line_collection = typing.cast(matplotlib.collections.LineCollection, renderer._artists[artist_uuid])
    mpl_line_collection.set_visible(True)

    # =============================================================================
    # Update artists
    # =============================================================================

    mpl_line_collection.set_paths(typing.cast(list, positions_numpy))
    mpl_line_collection.set_color(typing.cast(list, colors_numpy))
    mpl_line_collection.set_linewidth(typing.cast(list, line_widths_numpy))
    mpl_line_collection.set_capstyle(ConverterUtils.cap_style_gsp_to_mpl(segments.get_cap_style()))

    # Return the list of artists created/updated
    changed_artists: list[matplotlib.artist.Artist] = []
    changed_artists.append(mpl_line_collection)
    return changed_artists

Texts Renderer

gsp_matplotlib.renderer.matplotlib_renderer_texts

Renderer for Texts using Matplotlib.

RendererTexts

Renderer for Texts using Matplotlib.

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

    @staticmethod
    def render(
        renderer: MatplotlibRenderer,
        viewport: Viewport,
        texts: Texts,
        model_matrix: TransBuf,
        camera: Camera,
    ) -> list[matplotlib.artist.Artist]:
        """Render Texts visual using Matplotlib.

        Args:
            renderer: The MatplotlibRenderer instance.
            viewport: The Viewport in which to render.
            texts: The Texts visual to render.
            model_matrix: The model transformation matrix as a TransBuf.
            camera: The Camera providing view and projection matrices.

        Returns:
            list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
        """
        # =============================================================================
        # 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 2D - shape (N, 2)
        vertices_2d = vertices_3d_transformed[:, :2]

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

        # Convert all attributes 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
        font_sizes_numpy = Bufferx.to_numpy(font_sizes_buffer).flatten()
        colors_numpy = Bufferx.to_numpy(colors_buffer) / 255.0  # normalize to [0, 1] range
        anchors_numpy = Bufferx.to_numpy(anchors_buffer)
        angles_numpy = Bufferx.to_numpy(angles_buffer).flatten()

        # =============================================================================
        # 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 artists if needed
        # =============================================================================

        artist_uuid_base = f"{viewport.get_uuid()}_{texts.get_uuid()}"
        for text_index in range(len(texts.get_strings())):
            artist_uuid = f"{artist_uuid_base}_{text_index}"
            if artist_uuid in renderer._artists:
                continue
            mpl_text = matplotlib.text.Text()
            mpl_text.set_visible(False)
            # hide until properly positioned and sized
            renderer._artists[artist_uuid] = mpl_text
            mpl_axes = renderer.get_mpl_axes_for_viewport(viewport)
            mpl_axes.add_artist(mpl_text)

        # Remove extra artists if the number of text strings has decreased
        existing_artist_uuids = [f"{artist_uuid_base}_{i}" for i in range(len(texts.get_strings()))]
        artists_to_remove = [uuid for uuid in renderer._artists.keys() if uuid.startswith(artist_uuid_base) and uuid not in existing_artist_uuids]
        for artist_uuid in artists_to_remove:
            mpl_text = typing.cast(matplotlib.text.Text, renderer._artists[artist_uuid])
            mpl_text.remove()  # remove from axes
            del renderer._artists[artist_uuid]  # remove from renderer's artist dict

        # =============================================================================
        # Get existing artists
        # =============================================================================

        changed_artists: list[matplotlib.artist.Artist] = []
        for text_index in range(len(texts.get_strings())):
            artist_uuid = f"{artist_uuid_base}_{text_index}"
            mpl_text = typing.cast(matplotlib.text.Text, renderer._artists[artist_uuid])
            mpl_text.set_visible(True)

            # =============================================================================
            # Update artists
            # =============================================================================

            mpl_text.set_x(vertices_2d[text_index, 0])
            mpl_text.set_y(vertices_2d[text_index, 1])
            mpl_text.set_text(texts.get_strings()[text_index])
            mpl_text.set_rotation(angles_numpy[text_index] / np.pi * 180.0)  # convert rad to deg
            # print(f"angles_numpy[{text_index}]: {angles_numpy[text_index]}")

            ha_label = "center" if anchors_numpy[text_index, 0] == 0.0 else "right" if anchors_numpy[text_index, 0] == 1.0 else "left"
            mpl_text.set_horizontalalignment(ha_label)
            va_label = "center" if anchors_numpy[text_index, 1] == 0.0 else "top" if anchors_numpy[text_index, 1] == 1.0 else "bottom"
            mpl_text.set_verticalalignment(va_label)

            mpl_text.set_fontfamily(texts.get_font_name())
            mpl_text.set_fontsize(font_sizes_numpy[text_index])
            mpl_text.set_color(typing.cast(tuple, colors_numpy[text_index]))

            # Return the list of artists created/updated
            changed_artists.append(mpl_text)

        return changed_artists

render(renderer: MatplotlibRenderer, viewport: Viewport, texts: Texts, model_matrix: TransBuf, camera: Camera) -> list[matplotlib.artist.Artist] staticmethod

Render Texts visual using Matplotlib.

Parameters:

Name Type Description Default
renderer gsp_matplotlib.renderer.matplotlib_renderer.MatplotlibRenderer

The MatplotlibRenderer instance.

required
viewport gsp.core.viewport.Viewport

The Viewport in which to render.

required
texts gsp.visuals.texts.Texts

The Texts visual to render.

required
model_matrix gsp.types.transbuf.TransBuf

The model transformation matrix as a TransBuf.

required
camera gsp.core.camera.Camera

The Camera providing view and projection matrices.

required

Returns:

Type Description
list[matplotlib.artist.Artist]

list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.

Source code in src/gsp_matplotlib/renderer/matplotlib_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
149
150
@staticmethod
def render(
    renderer: MatplotlibRenderer,
    viewport: Viewport,
    texts: Texts,
    model_matrix: TransBuf,
    camera: Camera,
) -> list[matplotlib.artist.Artist]:
    """Render Texts visual using Matplotlib.

    Args:
        renderer: The MatplotlibRenderer instance.
        viewport: The Viewport in which to render.
        texts: The Texts visual to render.
        model_matrix: The model transformation matrix as a TransBuf.
        camera: The Camera providing view and projection matrices.

    Returns:
        list[matplotlib.artist.Artist]: List of Matplotlib artists created/updated.
    """
    # =============================================================================
    # 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 2D - shape (N, 2)
    vertices_2d = vertices_3d_transformed[:, :2]

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

    # Convert all attributes 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
    font_sizes_numpy = Bufferx.to_numpy(font_sizes_buffer).flatten()
    colors_numpy = Bufferx.to_numpy(colors_buffer) / 255.0  # normalize to [0, 1] range
    anchors_numpy = Bufferx.to_numpy(anchors_buffer)
    angles_numpy = Bufferx.to_numpy(angles_buffer).flatten()

    # =============================================================================
    # 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 artists if needed
    # =============================================================================

    artist_uuid_base = f"{viewport.get_uuid()}_{texts.get_uuid()}"
    for text_index in range(len(texts.get_strings())):
        artist_uuid = f"{artist_uuid_base}_{text_index}"
        if artist_uuid in renderer._artists:
            continue
        mpl_text = matplotlib.text.Text()
        mpl_text.set_visible(False)
        # hide until properly positioned and sized
        renderer._artists[artist_uuid] = mpl_text
        mpl_axes = renderer.get_mpl_axes_for_viewport(viewport)
        mpl_axes.add_artist(mpl_text)

    # Remove extra artists if the number of text strings has decreased
    existing_artist_uuids = [f"{artist_uuid_base}_{i}" for i in range(len(texts.get_strings()))]
    artists_to_remove = [uuid for uuid in renderer._artists.keys() if uuid.startswith(artist_uuid_base) and uuid not in existing_artist_uuids]
    for artist_uuid in artists_to_remove:
        mpl_text = typing.cast(matplotlib.text.Text, renderer._artists[artist_uuid])
        mpl_text.remove()  # remove from axes
        del renderer._artists[artist_uuid]  # remove from renderer's artist dict

    # =============================================================================
    # Get existing artists
    # =============================================================================

    changed_artists: list[matplotlib.artist.Artist] = []
    for text_index in range(len(texts.get_strings())):
        artist_uuid = f"{artist_uuid_base}_{text_index}"
        mpl_text = typing.cast(matplotlib.text.Text, renderer._artists[artist_uuid])
        mpl_text.set_visible(True)

        # =============================================================================
        # Update artists
        # =============================================================================

        mpl_text.set_x(vertices_2d[text_index, 0])
        mpl_text.set_y(vertices_2d[text_index, 1])
        mpl_text.set_text(texts.get_strings()[text_index])
        mpl_text.set_rotation(angles_numpy[text_index] / np.pi * 180.0)  # convert rad to deg
        # print(f"angles_numpy[{text_index}]: {angles_numpy[text_index]}")

        ha_label = "center" if anchors_numpy[text_index, 0] == 0.0 else "right" if anchors_numpy[text_index, 0] == 1.0 else "left"
        mpl_text.set_horizontalalignment(ha_label)
        va_label = "center" if anchors_numpy[text_index, 1] == 0.0 else "top" if anchors_numpy[text_index, 1] == 1.0 else "bottom"
        mpl_text.set_verticalalignment(va_label)

        mpl_text.set_fontfamily(texts.get_font_name())
        mpl_text.set_fontsize(font_sizes_numpy[text_index])
        mpl_text.set_color(typing.cast(tuple, colors_numpy[text_index]))

        # Return the list of artists created/updated
        changed_artists.append(mpl_text)

    return changed_artists

Extra Module

The extra module provides additional utilities and extensions for Matplotlib rendering.

gsp_matplotlib.extra

GSP Matplotlib extra utilities package initialization.

Bufferx

Utility class for Buffer extended functionality using numpy.

Source code in src/gsp_matplotlib/extra/bufferx.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
 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
class Bufferx:
    """Utility class for Buffer extended functionality using numpy."""

    # =============================================================================
    # Matrix functions
    # =============================================================================

    @staticmethod
    def mat4_identity() -> Buffer:
        """Create a Buffer containing a 4x4 identity matrix."""
        mat4_numpy = np.asarray([np.identity(4, dtype=np.float32)])
        buffer = Bufferx.from_numpy(mat4_numpy, BufferType.mat4)
        return buffer

    # =============================================================================
    # .to_numpy/.from_numpy
    # =============================================================================
    @staticmethod
    def to_numpy(buffer: Buffer) -> np.ndarray:
        """Convert a Buffer to a numpy array."""
        if buffer.get_type() == BufferType.float32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 1))
        elif buffer.get_type() == BufferType.int8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.int8).reshape((count, 1))
        elif buffer.get_type() == BufferType.int32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.int32).reshape((count, 1))
        elif buffer.get_type() == BufferType.uint8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 1))
        elif buffer.get_type() == BufferType.uint32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint32).reshape((count, 1))
        elif buffer.get_type() == BufferType.vec2:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 2))
        elif buffer.get_type() == BufferType.vec3:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 3))
        elif buffer.get_type() == BufferType.vec4:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4))
        elif buffer.get_type() == BufferType.mat4:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4, 4))
        elif buffer.get_type() == BufferType.rgba8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 4))
        else:
            raise NotImplementedError(f"unable to convert buffer {buffer} to numpy array")

    @staticmethod
    def from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer:
        """Create a Buffer from a numpy array."""
        if bufferType == BufferType.float32:
            # sanity check
            assert array_numpy.dtype == np.float32, f"Numpy array must be of dtype float32, got {array_numpy.dtype}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.uint32:
            # sanity check
            assert array_numpy.dtype == np.uint32, f"Numpy array must be of dtype uint32, got {array_numpy.dtype}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.vec2:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 2, f"Numpy array must be of shape (2,), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.vec3:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 3, f"Numpy array must be of shape (3,), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.mat4:
            # sanity check
            assert (
                array_numpy.shape.__len__() == 3 and array_numpy.shape[1] == 4 and array_numpy.shape[2] == 4
            ), f"Numpy array must be of shape (4, 4), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, 1)
            return buffer
        elif bufferType == BufferType.rgba8:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 4, f"Numpy array must be of shape (4,), got {array_numpy.shape} "

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.uint8).tobytes()), 0, count)
            return buffer
        else:
            raise NotImplementedError(f"unable to create a {bufferType} buffer from numpy array of shape {array_numpy.shape} and dtype {array_numpy.dtype}")

mat4_identity() -> Buffer staticmethod

Create a Buffer containing a 4x4 identity matrix.

Source code in src/gsp_matplotlib/extra/bufferx.py
14
15
16
17
18
19
@staticmethod
def mat4_identity() -> Buffer:
    """Create a Buffer containing a 4x4 identity matrix."""
    mat4_numpy = np.asarray([np.identity(4, dtype=np.float32)])
    buffer = Bufferx.from_numpy(mat4_numpy, BufferType.mat4)
    return buffer

to_numpy(buffer: Buffer) -> np.ndarray staticmethod

Convert a Buffer to a numpy array.

Source code in src/gsp_matplotlib/extra/bufferx.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
@staticmethod
def to_numpy(buffer: Buffer) -> np.ndarray:
    """Convert a Buffer to a numpy array."""
    if buffer.get_type() == BufferType.float32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 1))
    elif buffer.get_type() == BufferType.int8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.int8).reshape((count, 1))
    elif buffer.get_type() == BufferType.int32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.int32).reshape((count, 1))
    elif buffer.get_type() == BufferType.uint8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 1))
    elif buffer.get_type() == BufferType.uint32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint32).reshape((count, 1))
    elif buffer.get_type() == BufferType.vec2:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 2))
    elif buffer.get_type() == BufferType.vec3:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 3))
    elif buffer.get_type() == BufferType.vec4:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4))
    elif buffer.get_type() == BufferType.mat4:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4, 4))
    elif buffer.get_type() == BufferType.rgba8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 4))
    else:
        raise NotImplementedError(f"unable to convert buffer {buffer} to numpy array")

from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer staticmethod

Create a Buffer from a numpy array.

Source code in src/gsp_matplotlib/extra/bufferx.py
 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
@staticmethod
def from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer:
    """Create a Buffer from a numpy array."""
    if bufferType == BufferType.float32:
        # sanity check
        assert array_numpy.dtype == np.float32, f"Numpy array must be of dtype float32, got {array_numpy.dtype}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.uint32:
        # sanity check
        assert array_numpy.dtype == np.uint32, f"Numpy array must be of dtype uint32, got {array_numpy.dtype}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.vec2:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 2, f"Numpy array must be of shape (2,), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.vec3:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 3, f"Numpy array must be of shape (3,), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.mat4:
        # sanity check
        assert (
            array_numpy.shape.__len__() == 3 and array_numpy.shape[1] == 4 and array_numpy.shape[2] == 4
        ), f"Numpy array must be of shape (4, 4), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, 1)
        return buffer
    elif bufferType == BufferType.rgba8:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 4, f"Numpy array must be of shape (4,), got {array_numpy.shape} "

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.uint8).tobytes()), 0, count)
        return buffer
    else:
        raise NotImplementedError(f"unable to create a {bufferType} buffer from numpy array of shape {array_numpy.shape} and dtype {array_numpy.dtype}")

Bufferx

gsp_matplotlib.extra.bufferx

Utility class for Buffer extended functionality using numpy.

Bufferx

Utility class for Buffer extended functionality using numpy.

Source code in src/gsp_matplotlib/extra/bufferx.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
 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
class Bufferx:
    """Utility class for Buffer extended functionality using numpy."""

    # =============================================================================
    # Matrix functions
    # =============================================================================

    @staticmethod
    def mat4_identity() -> Buffer:
        """Create a Buffer containing a 4x4 identity matrix."""
        mat4_numpy = np.asarray([np.identity(4, dtype=np.float32)])
        buffer = Bufferx.from_numpy(mat4_numpy, BufferType.mat4)
        return buffer

    # =============================================================================
    # .to_numpy/.from_numpy
    # =============================================================================
    @staticmethod
    def to_numpy(buffer: Buffer) -> np.ndarray:
        """Convert a Buffer to a numpy array."""
        if buffer.get_type() == BufferType.float32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 1))
        elif buffer.get_type() == BufferType.int8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.int8).reshape((count, 1))
        elif buffer.get_type() == BufferType.int32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.int32).reshape((count, 1))
        elif buffer.get_type() == BufferType.uint8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 1))
        elif buffer.get_type() == BufferType.uint32:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint32).reshape((count, 1))
        elif buffer.get_type() == BufferType.vec2:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 2))
        elif buffer.get_type() == BufferType.vec3:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 3))
        elif buffer.get_type() == BufferType.vec4:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4))
        elif buffer.get_type() == BufferType.mat4:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4, 4))
        elif buffer.get_type() == BufferType.rgba8:
            count = buffer.get_count()
            return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 4))
        else:
            raise NotImplementedError(f"unable to convert buffer {buffer} to numpy array")

    @staticmethod
    def from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer:
        """Create a Buffer from a numpy array."""
        if bufferType == BufferType.float32:
            # sanity check
            assert array_numpy.dtype == np.float32, f"Numpy array must be of dtype float32, got {array_numpy.dtype}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.uint32:
            # sanity check
            assert array_numpy.dtype == np.uint32, f"Numpy array must be of dtype uint32, got {array_numpy.dtype}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.vec2:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 2, f"Numpy array must be of shape (2,), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.vec3:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 3, f"Numpy array must be of shape (3,), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
            return buffer
        elif bufferType == BufferType.mat4:
            # sanity check
            assert (
                array_numpy.shape.__len__() == 3 and array_numpy.shape[1] == 4 and array_numpy.shape[2] == 4
            ), f"Numpy array must be of shape (4, 4), got {array_numpy.shape}"

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, 1)
            return buffer
        elif bufferType == BufferType.rgba8:
            # sanity check
            assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 4, f"Numpy array must be of shape (4,), got {array_numpy.shape} "

            count = array_numpy.shape[0]
            buffer = Buffer(count, bufferType)
            buffer.set_data(bytearray(array_numpy.astype(np.uint8).tobytes()), 0, count)
            return buffer
        else:
            raise NotImplementedError(f"unable to create a {bufferType} buffer from numpy array of shape {array_numpy.shape} and dtype {array_numpy.dtype}")

from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer staticmethod

Create a Buffer from a numpy array.

Source code in src/gsp_matplotlib/extra/bufferx.py
 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
@staticmethod
def from_numpy(array_numpy: np.ndarray, bufferType: BufferType) -> Buffer:
    """Create a Buffer from a numpy array."""
    if bufferType == BufferType.float32:
        # sanity check
        assert array_numpy.dtype == np.float32, f"Numpy array must be of dtype float32, got {array_numpy.dtype}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.uint32:
        # sanity check
        assert array_numpy.dtype == np.uint32, f"Numpy array must be of dtype uint32, got {array_numpy.dtype}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.vec2:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 2, f"Numpy array must be of shape (2,), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.vec3:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 3, f"Numpy array must be of shape (3,), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, count)
        return buffer
    elif bufferType == BufferType.mat4:
        # sanity check
        assert (
            array_numpy.shape.__len__() == 3 and array_numpy.shape[1] == 4 and array_numpy.shape[2] == 4
        ), f"Numpy array must be of shape (4, 4), got {array_numpy.shape}"

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.float32).tobytes()), 0, 1)
        return buffer
    elif bufferType == BufferType.rgba8:
        # sanity check
        assert array_numpy.shape.__len__() == 2 and array_numpy.shape[1] == 4, f"Numpy array must be of shape (4,), got {array_numpy.shape} "

        count = array_numpy.shape[0]
        buffer = Buffer(count, bufferType)
        buffer.set_data(bytearray(array_numpy.astype(np.uint8).tobytes()), 0, count)
        return buffer
    else:
        raise NotImplementedError(f"unable to create a {bufferType} buffer from numpy array of shape {array_numpy.shape} and dtype {array_numpy.dtype}")

mat4_identity() -> Buffer staticmethod

Create a Buffer containing a 4x4 identity matrix.

Source code in src/gsp_matplotlib/extra/bufferx.py
14
15
16
17
18
19
@staticmethod
def mat4_identity() -> Buffer:
    """Create a Buffer containing a 4x4 identity matrix."""
    mat4_numpy = np.asarray([np.identity(4, dtype=np.float32)])
    buffer = Bufferx.from_numpy(mat4_numpy, BufferType.mat4)
    return buffer

to_numpy(buffer: Buffer) -> np.ndarray staticmethod

Convert a Buffer to a numpy array.

Source code in src/gsp_matplotlib/extra/bufferx.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
@staticmethod
def to_numpy(buffer: Buffer) -> np.ndarray:
    """Convert a Buffer to a numpy array."""
    if buffer.get_type() == BufferType.float32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 1))
    elif buffer.get_type() == BufferType.int8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.int8).reshape((count, 1))
    elif buffer.get_type() == BufferType.int32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.int32).reshape((count, 1))
    elif buffer.get_type() == BufferType.uint8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 1))
    elif buffer.get_type() == BufferType.uint32:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint32).reshape((count, 1))
    elif buffer.get_type() == BufferType.vec2:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 2))
    elif buffer.get_type() == BufferType.vec3:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 3))
    elif buffer.get_type() == BufferType.vec4:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4))
    elif buffer.get_type() == BufferType.mat4:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.float32).reshape((count, 4, 4))
    elif buffer.get_type() == BufferType.rgba8:
        count = buffer.get_count()
        return np.frombuffer(buffer.to_bytearray(), dtype=np.uint8).reshape((count, 4))
    else:
        raise NotImplementedError(f"unable to convert buffer {buffer} to numpy array")

Utils Module

The utils module provides converter utilities for the Matplotlib backend.

gsp_matplotlib.utils

Utility module for GSP Matplotlib conversions.

ConverterUtils

Utility class for converting GSP types to Matplotlib types.

Source code in src/gsp_matplotlib/utils/converter_utils.py
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
68
69
70
class ConverterUtils:
    """Utility class for converting GSP types to Matplotlib types."""

    @staticmethod
    def cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal["butt", "round", "projecting"]:
        """Convert CapStyle enum to Matplotlib string.

        Args:
            gsp_cap_style (CapStyle): The GSP cap style.

        Returns:
            str: The corresponding Matplotlib cap style.
        """
        if gsp_cap_style == CapStyle.BUTT:
            return "butt"
        elif gsp_cap_style == CapStyle.ROUND:
            return "round"
        elif gsp_cap_style == CapStyle.PROJECTING:
            return "projecting"
        else:
            raise ValueError(f"Unsupported CapStyle: {gsp_cap_style}")

    @staticmethod
    def join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal["miter", "round", "bevel"]:
        """Convert JoinStyle enum to Matplotlib string.

        Args:
            gsp_join_style (JoinStyle): The GSP join style.

        Returns:
            str: The corresponding Matplotlib join style.
        """
        if gsp_join_style == JoinStyle.MITER:
            return "miter"
        elif gsp_join_style == JoinStyle.ROUND:
            return "round"
        elif gsp_join_style == JoinStyle.BEVEL:
            return "bevel"
        else:
            raise ValueError(f"Unsupported JoinStyle: {gsp_join_style}")

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

        Args:
            gsp_marker_shape (MarkerShape): The GSP marker shape.

        Returns:
            str: The corresponding Matplotlib marker shape.
        """
        if gsp_marker_shape == MarkerShape.disc:
            mpl_marker_shape = "o"
        elif gsp_marker_shape == MarkerShape.square:
            mpl_marker_shape = "s"
        elif gsp_marker_shape == MarkerShape.club:
            mpl_marker_shape = r"$\clubsuit$"
        else:
            raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

        return mpl_marker_shape

cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal['butt', 'round', 'projecting'] staticmethod

Convert CapStyle enum to Matplotlib string.

Parameters:

Name Type Description Default
gsp_cap_style gsp.types.CapStyle

The GSP cap style.

required

Returns:

Name Type Description
str typing.Literal['butt', 'round', 'projecting']

The corresponding Matplotlib cap style.

Source code in src/gsp_matplotlib/utils/converter_utils.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@staticmethod
def cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal["butt", "round", "projecting"]:
    """Convert CapStyle enum to Matplotlib string.

    Args:
        gsp_cap_style (CapStyle): The GSP cap style.

    Returns:
        str: The corresponding Matplotlib cap style.
    """
    if gsp_cap_style == CapStyle.BUTT:
        return "butt"
    elif gsp_cap_style == CapStyle.ROUND:
        return "round"
    elif gsp_cap_style == CapStyle.PROJECTING:
        return "projecting"
    else:
        raise ValueError(f"Unsupported CapStyle: {gsp_cap_style}")

join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal['miter', 'round', 'bevel'] staticmethod

Convert JoinStyle enum to Matplotlib string.

Parameters:

Name Type Description Default
gsp_join_style gsp.types.JoinStyle

The GSP join style.

required

Returns:

Name Type Description
str typing.Literal['miter', 'round', 'bevel']

The corresponding Matplotlib join style.

Source code in src/gsp_matplotlib/utils/converter_utils.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@staticmethod
def join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal["miter", "round", "bevel"]:
    """Convert JoinStyle enum to Matplotlib string.

    Args:
        gsp_join_style (JoinStyle): The GSP join style.

    Returns:
        str: The corresponding Matplotlib join style.
    """
    if gsp_join_style == JoinStyle.MITER:
        return "miter"
    elif gsp_join_style == JoinStyle.ROUND:
        return "round"
    elif gsp_join_style == JoinStyle.BEVEL:
        return "bevel"
    else:
        raise ValueError(f"Unsupported JoinStyle: {gsp_join_style}")

marker_shape_gsp_to_mpl(gsp_marker_shape: MarkerShape) -> str staticmethod

Convert GSP marker shape to Matplotlib marker shape.

Parameters:

Name Type Description Default
gsp_marker_shape gsp.types.MarkerShape

The GSP marker shape.

required

Returns:

Name Type Description
str str

The corresponding Matplotlib marker shape.

Source code in src/gsp_matplotlib/utils/converter_utils.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@staticmethod
def marker_shape_gsp_to_mpl(gsp_marker_shape: MarkerShape) -> str:
    """Convert GSP marker shape to Matplotlib marker shape.

    Args:
        gsp_marker_shape (MarkerShape): The GSP marker shape.

    Returns:
        str: The corresponding Matplotlib marker shape.
    """
    if gsp_marker_shape == MarkerShape.disc:
        mpl_marker_shape = "o"
    elif gsp_marker_shape == MarkerShape.square:
        mpl_marker_shape = "s"
    elif gsp_marker_shape == MarkerShape.club:
        mpl_marker_shape = r"$\clubsuit$"
    else:
        raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

    return mpl_marker_shape

Converter Utils

gsp_matplotlib.utils.converter_utils

Utility class for converting GSP types to Matplotlib types.

ConverterUtils

Utility class for converting GSP types to Matplotlib types.

Source code in src/gsp_matplotlib/utils/converter_utils.py
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
68
69
70
class ConverterUtils:
    """Utility class for converting GSP types to Matplotlib types."""

    @staticmethod
    def cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal["butt", "round", "projecting"]:
        """Convert CapStyle enum to Matplotlib string.

        Args:
            gsp_cap_style (CapStyle): The GSP cap style.

        Returns:
            str: The corresponding Matplotlib cap style.
        """
        if gsp_cap_style == CapStyle.BUTT:
            return "butt"
        elif gsp_cap_style == CapStyle.ROUND:
            return "round"
        elif gsp_cap_style == CapStyle.PROJECTING:
            return "projecting"
        else:
            raise ValueError(f"Unsupported CapStyle: {gsp_cap_style}")

    @staticmethod
    def join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal["miter", "round", "bevel"]:
        """Convert JoinStyle enum to Matplotlib string.

        Args:
            gsp_join_style (JoinStyle): The GSP join style.

        Returns:
            str: The corresponding Matplotlib join style.
        """
        if gsp_join_style == JoinStyle.MITER:
            return "miter"
        elif gsp_join_style == JoinStyle.ROUND:
            return "round"
        elif gsp_join_style == JoinStyle.BEVEL:
            return "bevel"
        else:
            raise ValueError(f"Unsupported JoinStyle: {gsp_join_style}")

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

        Args:
            gsp_marker_shape (MarkerShape): The GSP marker shape.

        Returns:
            str: The corresponding Matplotlib marker shape.
        """
        if gsp_marker_shape == MarkerShape.disc:
            mpl_marker_shape = "o"
        elif gsp_marker_shape == MarkerShape.square:
            mpl_marker_shape = "s"
        elif gsp_marker_shape == MarkerShape.club:
            mpl_marker_shape = r"$\clubsuit$"
        else:
            raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

        return mpl_marker_shape

cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal['butt', 'round', 'projecting'] staticmethod

Convert CapStyle enum to Matplotlib string.

Parameters:

Name Type Description Default
gsp_cap_style gsp.types.CapStyle

The GSP cap style.

required

Returns:

Name Type Description
str typing.Literal['butt', 'round', 'projecting']

The corresponding Matplotlib cap style.

Source code in src/gsp_matplotlib/utils/converter_utils.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@staticmethod
def cap_style_gsp_to_mpl(gsp_cap_style: CapStyle) -> Literal["butt", "round", "projecting"]:
    """Convert CapStyle enum to Matplotlib string.

    Args:
        gsp_cap_style (CapStyle): The GSP cap style.

    Returns:
        str: The corresponding Matplotlib cap style.
    """
    if gsp_cap_style == CapStyle.BUTT:
        return "butt"
    elif gsp_cap_style == CapStyle.ROUND:
        return "round"
    elif gsp_cap_style == CapStyle.PROJECTING:
        return "projecting"
    else:
        raise ValueError(f"Unsupported CapStyle: {gsp_cap_style}")

join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal['miter', 'round', 'bevel'] staticmethod

Convert JoinStyle enum to Matplotlib string.

Parameters:

Name Type Description Default
gsp_join_style gsp.types.JoinStyle

The GSP join style.

required

Returns:

Name Type Description
str typing.Literal['miter', 'round', 'bevel']

The corresponding Matplotlib join style.

Source code in src/gsp_matplotlib/utils/converter_utils.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@staticmethod
def join_style_gsp_to_mpl(gsp_join_style: JoinStyle) -> Literal["miter", "round", "bevel"]:
    """Convert JoinStyle enum to Matplotlib string.

    Args:
        gsp_join_style (JoinStyle): The GSP join style.

    Returns:
        str: The corresponding Matplotlib join style.
    """
    if gsp_join_style == JoinStyle.MITER:
        return "miter"
    elif gsp_join_style == JoinStyle.ROUND:
        return "round"
    elif gsp_join_style == JoinStyle.BEVEL:
        return "bevel"
    else:
        raise ValueError(f"Unsupported JoinStyle: {gsp_join_style}")

marker_shape_gsp_to_mpl(gsp_marker_shape: MarkerShape) -> str staticmethod

Convert GSP marker shape to Matplotlib marker shape.

Parameters:

Name Type Description Default
gsp_marker_shape gsp.types.MarkerShape

The GSP marker shape.

required

Returns:

Name Type Description
str str

The corresponding Matplotlib marker shape.

Source code in src/gsp_matplotlib/utils/converter_utils.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@staticmethod
def marker_shape_gsp_to_mpl(gsp_marker_shape: MarkerShape) -> str:
    """Convert GSP marker shape to Matplotlib marker shape.

    Args:
        gsp_marker_shape (MarkerShape): The GSP marker shape.

    Returns:
        str: The corresponding Matplotlib marker shape.
    """
    if gsp_marker_shape == MarkerShape.disc:
        mpl_marker_shape = "o"
    elif gsp_marker_shape == MarkerShape.square:
        mpl_marker_shape = "s"
    elif gsp_marker_shape == MarkerShape.club:
        mpl_marker_shape = r"$\clubsuit$"
    else:
        raise ValueError(f"Unsupported marker shape: {gsp_marker_shape}")

    return mpl_marker_shape