Skip to content

GSP Extra API Reference

The GSP Extra package provides additional utilities, helpers, and high-level components that extend the core GSP functionality, including animation, 3D object management, camera controls, and viewport event handling.

Overview

gsp_extra

Bufferx Module

Extended buffer utilities with numpy integration.

gsp_extra.bufferx

Bufferx extra module re-exporting Bufferx from gsp_matplotlib.extra.bufferx.

Object3D Module

3D object management with transformations, visuals, and cameras.

gsp_extra.object3d

Object3D class for managing 3D objects, their transformations, visuals, and cameras.

Object3D

Class representing a 3D object with transformation, visuals, and cameras.

Source code in src/gsp_extra/object3d.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
class Object3D:
    """Class representing a 3D object with transformation, visuals, and cameras."""

    def __init__(self, name: str | None = None):
        """Initialize the Object3D.

        Args:
            name (str | None): Optional name for the Object3D. If None, a default name is generated.
        """
        self.uuid = UuidUtils.generate_uuid()
        """uuid of the visual being wrapped."""

        self.name: str = name if name is not None else f"a {self.__class__.__name__} - {self.uuid}"
        """name of this Object3D."""

        self.matrix_world: np.ndarray = np.eye(4, dtype=np.float32)
        """matrix world of this Object3D"""
        self.matrix_local: np.ndarray = np.eye(4, dtype=np.float32)
        """matrix local of this Object3D"""

        self.dont_update_matrix_world: bool = False
        """if True, the world matrix won't be updated."""
        self.dont_update_matrix_local: bool = False
        """if True, the local matrix won't be updated."""

        self.rotation_order: str = "XYZ"
        """rotation order for euler angles."""
        self.position: np.ndarray = np.zeros(3, dtype=np.float32)
        """position of this Object3D."""
        self.euler: np.ndarray = np.zeros(3, dtype=np.float32)
        """euler angles in radians of this Object3D."""
        self.scale: np.ndarray = np.ones(3, dtype=np.float32)
        """scale of this Object3D."""

        self.children: list[Object3D] = []
        """list of children Object3D."""

        self.visuals: list[VisualBase] = []
        """list of visuals attached to this Object3D."""
        self.cameras: list[Camera] = []
        """list of cameras attached to this Object3D."""

    def __repr__(self) -> str:
        """String representation of the Object3D."""
        return f"Object3D(name={self.name})"

    # =============================================================================
    # .add/.remove
    # =============================================================================

    def add(self, child: "Object3D"):
        """Add a child Object3D to this Object3D.

        Args:
            child (Object3D): child to add.
        """
        self.children.append(child)

    def remove(self, child: "Object3D"):
        """Remove a child Object3D from this Object3D.

        Args:
            child (Object3D): child to remove.
        """
        self.children.remove(child)

    # =============================================================================
    # .attach_visual/.detach_visual
    # =============================================================================

    def attach_visual(self, visual: VisualBase):
        """Add a visual to this Object3D.

        Args:
            visual (VisualBase): visual to add.
        """
        self.visuals.append(visual)

    def detach_visual(self, visual: VisualBase):
        """Remove a visual from this Object3D.

        Args:
            visual (VisualBase): visual to remove.
        """
        self.visuals.remove(visual)

    # =============================================================================
    # .attach_camera/.detach_camera
    # =============================================================================

    def attach_camera(self, camera: Camera):
        """Add a camera to this Object3D.

        Args:
            camera (Camera): camera to add.
        """
        self.cameras.append(camera)

    def detach_camera(self, camera: Camera):
        """Remove a camera from this Object3D.

        Args:
            camera (Camera): camera to remove.
        """
        self.cameras.remove(camera)

    # =============================================================================
    # .traverse
    # =============================================================================

    def traverse(self):
        """Generator to traverse the Object3D hierarchy."""
        yield self
        for child in self.children:
            yield from child.traverse()

    # =============================================================================
    # .update_matrix_*
    # =============================================================================

    def update_matrix_local(self, force_update: bool = False) -> None:
        """Upload the local matrix from position, euler and scale.

        Args:
            force_update (bool): if True, forces the update even if dont_update_matrix_local is True. Defaults to False.
        """
        # honor dont_update_matrix_local flag
        if self.dont_update_matrix_local and not force_update:
            return

        # compute the scale matrix
        scale_matrix = glm.scale(self.scale)

        # compute the rotation matrix in the specified order
        rotation_matrix = np.eye(4, dtype=np.float32)
        for axis in self.rotation_order:
            if axis == "X":
                rotation_matrix = rotation_matrix @ glm.xrotate(self.euler[0] / np.pi * 180.0)
            elif axis == "Y":
                rotation_matrix = rotation_matrix @ glm.yrotate(self.euler[1] / np.pi * 180.0)
            elif axis == "Z":
                rotation_matrix = rotation_matrix @ glm.zrotate(self.euler[2] / np.pi * 180.0)

        # compute the translation matrix
        translation_matrix = glm.translate(self.position)

        # set the local matrix
        self.matrix_local = translation_matrix @ rotation_matrix @ scale_matrix

    def update_matrix_world(self, parent_matrix_world: np.ndarray | None = None, force_update: bool = False) -> None:
        """Compute the world matrix from the local matrix and the parent's world matrix.

        Args:
            parent_matrix_world (np.ndarray | None): parent's world matrix. Defaults to None.
            force_update (bool): if True, forces the update even if dont_update_matrix_world is True. Defaults to False.
        """
        # update local matrix
        self.update_matrix_local(force_update=force_update)

        # update world matrix - honor dont_update_matrix_world flag
        if parent_matrix_world is None:
            self.matrix_world = self.matrix_local
        elif not self.dont_update_matrix_world or force_update:
            self.matrix_world = parent_matrix_world @ self.matrix_local

        # update children
        for child in self.children:
            child.update_matrix_world(self.matrix_world)

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

    @staticmethod
    def update_camera_view_matrices(scene: "Object3D") -> None:
        """Update the view matrices of all cameras attached to the Object3D hierarchy.

        Args:
            scene (Object3D): root Object3D of the scene.
        """
        for object3d in scene.traverse():
            for camera in object3d.cameras:
                view_matrix_numpy = np.linalg.inv(object3d.matrix_world)
                view_matrix_buffer = Bufferx.from_numpy(np.array([view_matrix_numpy]), BufferType.mat4)
                camera.set_view_matrix(view_matrix_buffer)

    @staticmethod
    def to_render_args(viewport: Viewport, scene: "Object3D", camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]:
        """Render the scene using the provided renderer.

        Args:
            viewport (Viewport): viewport to render to.
            scene (Object3D): root Object3D of the scene.
            camera (Camera): camera to use for rendering.

        Returns:
            tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.
        """
        # gather all visuals, model matrices and cameras
        visuals = [visual for object3d in scene.traverse() for visual in object3d.visuals]
        model_matrices_numpy = [object3d.matrix_world for object3d in scene.traverse() for _ in object3d.visuals]
        model_matrices_buffer = [Bufferx.from_numpy(np.array([model_matrix_numpy]), BufferType.mat4) for model_matrix_numpy in model_matrices_numpy]
        model_matrices = [typing.cast(TransBuf, model_matrix_buffer) for model_matrix_buffer in model_matrices_buffer]
        viewports = [viewport for _ in range(len(visuals))]
        cameras = [camera for _ in range(len(visuals))]

        return viewports, visuals, model_matrices, cameras

    @staticmethod
    def pre_render(viewport: Viewport, scene: "Object3D", camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]:
        """Prepare the scene for rendering by updating matrices and gathering render arguments.

        Args:
            viewport (Viewport): viewport to render to.
            scene (Object3D): root Object3D of the scene.
            camera (Camera): camera to use for rendering.

        Returns:
            tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.
        """
        # update all world matrices
        scene.update_matrix_world()

        # update camera view matrices
        Object3D.update_camera_view_matrices(scene)

        # gather all visuals, model matrices and cameras
        viewports, visuals, model_matrices_buffer, cameras = Object3D.to_render_args(viewport, scene, camera)

        return viewports, visuals, model_matrices_buffer, cameras

cameras: list[Camera] = [] instance-attribute

list of cameras attached to this Object3D.

children: list[Object3D] = [] instance-attribute

list of children Object3D.

dont_update_matrix_local: bool = False instance-attribute

if True, the local matrix won't be updated.

dont_update_matrix_world: bool = False instance-attribute

if True, the world matrix won't be updated.

euler: np.ndarray = np.zeros(3, dtype=(np.float32)) instance-attribute

euler angles in radians of this Object3D.

matrix_local: np.ndarray = np.eye(4, dtype=(np.float32)) instance-attribute

matrix local of this Object3D

matrix_world: np.ndarray = np.eye(4, dtype=(np.float32)) instance-attribute

matrix world of this Object3D

name: str = name if name is not None else f'a {self.__class__.__name__} - {self.uuid}' instance-attribute

name of this Object3D.

position: np.ndarray = np.zeros(3, dtype=(np.float32)) instance-attribute

position of this Object3D.

rotation_order: str = 'XYZ' instance-attribute

rotation order for euler angles.

scale: np.ndarray = np.ones(3, dtype=(np.float32)) instance-attribute

scale of this Object3D.

uuid = UuidUtils.generate_uuid() instance-attribute

uuid of the visual being wrapped.

visuals: list[VisualBase] = [] instance-attribute

list of visuals attached to this Object3D.

__init__(name: str | None = None)

Initialize the Object3D.

Parameters:

Name Type Description Default
name str | None

Optional name for the Object3D. If None, a default name is generated.

None
Source code in src/gsp_extra/object3d.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
def __init__(self, name: str | None = None):
    """Initialize the Object3D.

    Args:
        name (str | None): Optional name for the Object3D. If None, a default name is generated.
    """
    self.uuid = UuidUtils.generate_uuid()
    """uuid of the visual being wrapped."""

    self.name: str = name if name is not None else f"a {self.__class__.__name__} - {self.uuid}"
    """name of this Object3D."""

    self.matrix_world: np.ndarray = np.eye(4, dtype=np.float32)
    """matrix world of this Object3D"""
    self.matrix_local: np.ndarray = np.eye(4, dtype=np.float32)
    """matrix local of this Object3D"""

    self.dont_update_matrix_world: bool = False
    """if True, the world matrix won't be updated."""
    self.dont_update_matrix_local: bool = False
    """if True, the local matrix won't be updated."""

    self.rotation_order: str = "XYZ"
    """rotation order for euler angles."""
    self.position: np.ndarray = np.zeros(3, dtype=np.float32)
    """position of this Object3D."""
    self.euler: np.ndarray = np.zeros(3, dtype=np.float32)
    """euler angles in radians of this Object3D."""
    self.scale: np.ndarray = np.ones(3, dtype=np.float32)
    """scale of this Object3D."""

    self.children: list[Object3D] = []
    """list of children Object3D."""

    self.visuals: list[VisualBase] = []
    """list of visuals attached to this Object3D."""
    self.cameras: list[Camera] = []
    """list of cameras attached to this Object3D."""

__repr__() -> str

String representation of the Object3D.

Source code in src/gsp_extra/object3d.py
62
63
64
def __repr__(self) -> str:
    """String representation of the Object3D."""
    return f"Object3D(name={self.name})"

add(child: Object3D)

Add a child Object3D to this Object3D.

Parameters:

Name Type Description Default
child gsp_extra.object3d.Object3D

child to add.

required
Source code in src/gsp_extra/object3d.py
70
71
72
73
74
75
76
def add(self, child: "Object3D"):
    """Add a child Object3D to this Object3D.

    Args:
        child (Object3D): child to add.
    """
    self.children.append(child)

attach_camera(camera: Camera)

Add a camera to this Object3D.

Parameters:

Name Type Description Default
camera gsp.core.camera.Camera

camera to add.

required
Source code in src/gsp_extra/object3d.py
110
111
112
113
114
115
116
def attach_camera(self, camera: Camera):
    """Add a camera to this Object3D.

    Args:
        camera (Camera): camera to add.
    """
    self.cameras.append(camera)

attach_visual(visual: VisualBase)

Add a visual to this Object3D.

Parameters:

Name Type Description Default
visual gsp.types.visual_base.VisualBase

visual to add.

required
Source code in src/gsp_extra/object3d.py
90
91
92
93
94
95
96
def attach_visual(self, visual: VisualBase):
    """Add a visual to this Object3D.

    Args:
        visual (VisualBase): visual to add.
    """
    self.visuals.append(visual)

detach_camera(camera: Camera)

Remove a camera from this Object3D.

Parameters:

Name Type Description Default
camera gsp.core.camera.Camera

camera to remove.

required
Source code in src/gsp_extra/object3d.py
118
119
120
121
122
123
124
def detach_camera(self, camera: Camera):
    """Remove a camera from this Object3D.

    Args:
        camera (Camera): camera to remove.
    """
    self.cameras.remove(camera)

detach_visual(visual: VisualBase)

Remove a visual from this Object3D.

Parameters:

Name Type Description Default
visual gsp.types.visual_base.VisualBase

visual to remove.

required
Source code in src/gsp_extra/object3d.py
 98
 99
100
101
102
103
104
def detach_visual(self, visual: VisualBase):
    """Remove a visual from this Object3D.

    Args:
        visual (VisualBase): visual to remove.
    """
    self.visuals.remove(visual)

pre_render(viewport: Viewport, scene: Object3D, camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]] staticmethod

Prepare the scene for rendering by updating matrices and gathering render arguments.

Parameters:

Name Type Description Default
viewport gsp.core.Viewport

viewport to render to.

required
scene gsp_extra.object3d.Object3D

root Object3D of the scene.

required
camera gsp.core.camera.Camera

camera to use for rendering.

required

Returns:

Type Description
tuple[list[gsp.core.Viewport], list[gsp.types.visual_base.VisualBase], list[gsp.types.transbuf.TransBuf], list[gsp.core.camera.Camera]]

tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.

Source code in src/gsp_extra/object3d.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
@staticmethod
def pre_render(viewport: Viewport, scene: "Object3D", camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]:
    """Prepare the scene for rendering by updating matrices and gathering render arguments.

    Args:
        viewport (Viewport): viewport to render to.
        scene (Object3D): root Object3D of the scene.
        camera (Camera): camera to use for rendering.

    Returns:
        tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.
    """
    # update all world matrices
    scene.update_matrix_world()

    # update camera view matrices
    Object3D.update_camera_view_matrices(scene)

    # gather all visuals, model matrices and cameras
    viewports, visuals, model_matrices_buffer, cameras = Object3D.to_render_args(viewport, scene, camera)

    return viewports, visuals, model_matrices_buffer, cameras

remove(child: Object3D)

Remove a child Object3D from this Object3D.

Parameters:

Name Type Description Default
child gsp_extra.object3d.Object3D

child to remove.

required
Source code in src/gsp_extra/object3d.py
78
79
80
81
82
83
84
def remove(self, child: "Object3D"):
    """Remove a child Object3D from this Object3D.

    Args:
        child (Object3D): child to remove.
    """
    self.children.remove(child)

to_render_args(viewport: Viewport, scene: Object3D, camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]] staticmethod

Render the scene using the provided renderer.

Parameters:

Name Type Description Default
viewport gsp.core.Viewport

viewport to render to.

required
scene gsp_extra.object3d.Object3D

root Object3D of the scene.

required
camera gsp.core.camera.Camera

camera to use for rendering.

required

Returns:

Type Description
tuple[list[gsp.core.Viewport], list[gsp.types.visual_base.VisualBase], list[gsp.types.transbuf.TransBuf], list[gsp.core.camera.Camera]]

tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.

Source code in src/gsp_extra/object3d.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
@staticmethod
def to_render_args(viewport: Viewport, scene: "Object3D", camera: Camera) -> tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]:
    """Render the scene using the provided renderer.

    Args:
        viewport (Viewport): viewport to render to.
        scene (Object3D): root Object3D of the scene.
        camera (Camera): camera to use for rendering.

    Returns:
        tuple[list[Viewport], list[VisualBase], list[TransBuf], list[Camera]]: viewports, visuals, model matrices and cameras.
    """
    # gather all visuals, model matrices and cameras
    visuals = [visual for object3d in scene.traverse() for visual in object3d.visuals]
    model_matrices_numpy = [object3d.matrix_world for object3d in scene.traverse() for _ in object3d.visuals]
    model_matrices_buffer = [Bufferx.from_numpy(np.array([model_matrix_numpy]), BufferType.mat4) for model_matrix_numpy in model_matrices_numpy]
    model_matrices = [typing.cast(TransBuf, model_matrix_buffer) for model_matrix_buffer in model_matrices_buffer]
    viewports = [viewport for _ in range(len(visuals))]
    cameras = [camera for _ in range(len(visuals))]

    return viewports, visuals, model_matrices, cameras

traverse()

Generator to traverse the Object3D hierarchy.

Source code in src/gsp_extra/object3d.py
130
131
132
133
134
def traverse(self):
    """Generator to traverse the Object3D hierarchy."""
    yield self
    for child in self.children:
        yield from child.traverse()

update_camera_view_matrices(scene: Object3D) -> None staticmethod

Update the view matrices of all cameras attached to the Object3D hierarchy.

Parameters:

Name Type Description Default
scene gsp_extra.object3d.Object3D

root Object3D of the scene.

required
Source code in src/gsp_extra/object3d.py
193
194
195
196
197
198
199
200
201
202
203
204
@staticmethod
def update_camera_view_matrices(scene: "Object3D") -> None:
    """Update the view matrices of all cameras attached to the Object3D hierarchy.

    Args:
        scene (Object3D): root Object3D of the scene.
    """
    for object3d in scene.traverse():
        for camera in object3d.cameras:
            view_matrix_numpy = np.linalg.inv(object3d.matrix_world)
            view_matrix_buffer = Bufferx.from_numpy(np.array([view_matrix_numpy]), BufferType.mat4)
            camera.set_view_matrix(view_matrix_buffer)

update_matrix_local(force_update: bool = False) -> None

Upload the local matrix from position, euler and scale.

Parameters:

Name Type Description Default
force_update bool

if True, forces the update even if dont_update_matrix_local is True. Defaults to False.

False
Source code in src/gsp_extra/object3d.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def update_matrix_local(self, force_update: bool = False) -> None:
    """Upload the local matrix from position, euler and scale.

    Args:
        force_update (bool): if True, forces the update even if dont_update_matrix_local is True. Defaults to False.
    """
    # honor dont_update_matrix_local flag
    if self.dont_update_matrix_local and not force_update:
        return

    # compute the scale matrix
    scale_matrix = glm.scale(self.scale)

    # compute the rotation matrix in the specified order
    rotation_matrix = np.eye(4, dtype=np.float32)
    for axis in self.rotation_order:
        if axis == "X":
            rotation_matrix = rotation_matrix @ glm.xrotate(self.euler[0] / np.pi * 180.0)
        elif axis == "Y":
            rotation_matrix = rotation_matrix @ glm.yrotate(self.euler[1] / np.pi * 180.0)
        elif axis == "Z":
            rotation_matrix = rotation_matrix @ glm.zrotate(self.euler[2] / np.pi * 180.0)

    # compute the translation matrix
    translation_matrix = glm.translate(self.position)

    # set the local matrix
    self.matrix_local = translation_matrix @ rotation_matrix @ scale_matrix

update_matrix_world(parent_matrix_world: np.ndarray | None = None, force_update: bool = False) -> None

Compute the world matrix from the local matrix and the parent's world matrix.

Parameters:

Name Type Description Default
parent_matrix_world numpy.ndarray | None

parent's world matrix. Defaults to None.

None
force_update bool

if True, forces the update even if dont_update_matrix_world is True. Defaults to False.

False
Source code in src/gsp_extra/object3d.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def update_matrix_world(self, parent_matrix_world: np.ndarray | None = None, force_update: bool = False) -> None:
    """Compute the world matrix from the local matrix and the parent's world matrix.

    Args:
        parent_matrix_world (np.ndarray | None): parent's world matrix. Defaults to None.
        force_update (bool): if True, forces the update even if dont_update_matrix_world is True. Defaults to False.
    """
    # update local matrix
    self.update_matrix_local(force_update=force_update)

    # update world matrix - honor dont_update_matrix_world flag
    if parent_matrix_world is None:
        self.matrix_world = self.matrix_local
    elif not self.dont_update_matrix_world or force_update:
        self.matrix_world = parent_matrix_world @ self.matrix_local

    # update children
    for child in self.children:
        child.update_matrix_world(self.matrix_world)

Camera Controls Module

Camera control utilities for interactive 3D navigation.

Object Controls AWSD

gsp_extra.camera_controls.object_controls_awsd

Implements camera controls using AWSD keys for movement and mouse for orientation.

ObjectControlAwsd

Implements camera controls using AWSD keys for movement and mouse for orientation.

Source code in src/gsp_extra/camera_controls/object_controls_awsd.py
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
class ObjectControlAwsd:
    """Implements camera controls using AWSD keys for movement and mouse for orientation."""

    def __init__(self, model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase):
        """Initialize the ObjectControlAwsd.

        Args:
            model_matrix_buffer (Buffer): The buffer containing the model matrix to control.
            viewport_events (ViewportEventsBase): The viewport events to subscribe to for keyboard input.
        """
        # sanity checks
        assert model_matrix_buffer.get_type() == BufferType.mat4, "model_matrix must be of type mat4"
        assert model_matrix_buffer.get_count() == 1, "model_matrix must have a count of 1"

        # copy arguments
        self._model_matrix_buffer = model_matrix_buffer
        self._model_matrix_numpy = Bufferx.to_numpy(self._model_matrix_buffer)[0]
        self._viewport_events = viewport_events
        self._speed_x = 0.1
        self._speed_z = 0.1

        # Subscribe to keyboard and mouse events
        self._viewport_events.key_press_event.subscribe(self._on_key_event)
        self._viewport_events.key_release_event.subscribe(self._on_key_event)

    def close(self):
        """Unsubscribe from events."""
        self._viewport_events.key_press_event.unsubscribe(self._on_key_event)
        self._viewport_events.key_release_event.unsubscribe(self._on_key_event)

    def _on_key_event(self, key_event: KeyEvent):
        print(f"ObjectControlAwsd: key_event: {key_event}")
        if key_event.event_type == EventType.KEY_PRESS:
            translate_vector = np.array([0, 0, 0], dtype=np.float32)

            if key_event.key_name == "w":
                translate_vector[2] -= self._speed_z
            elif key_event.key_name == "s":
                translate_vector[2] += self._speed_z
            elif key_event.key_name == "a":
                translate_vector[0] -= self._speed_x
            elif key_event.key_name == "d":
                translate_vector[0] += self._speed_x

            # generate translate matrix
            translate_matrix = glm.translate(translate_vector)
            # update model_matrix_numpy
            self._model_matrix_numpy = np.matmul(translate_matrix, self._model_matrix_numpy)
            # update model_matrix_buffer
            self._model_matrix_buffer.set_data(bytearray(self._model_matrix_numpy.tobytes()), 0, 1)

__init__(model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase)

Initialize the ObjectControlAwsd.

Parameters:

Name Type Description Default
model_matrix_buffer gsp.types.buffer.Buffer

The buffer containing the model matrix to control.

required
viewport_events gsp.types.viewport_events_base.ViewportEventsBase

The viewport events to subscribe to for keyboard input.

required
Source code in src/gsp_extra/camera_controls/object_controls_awsd.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def __init__(self, model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase):
    """Initialize the ObjectControlAwsd.

    Args:
        model_matrix_buffer (Buffer): The buffer containing the model matrix to control.
        viewport_events (ViewportEventsBase): The viewport events to subscribe to for keyboard input.
    """
    # sanity checks
    assert model_matrix_buffer.get_type() == BufferType.mat4, "model_matrix must be of type mat4"
    assert model_matrix_buffer.get_count() == 1, "model_matrix must have a count of 1"

    # copy arguments
    self._model_matrix_buffer = model_matrix_buffer
    self._model_matrix_numpy = Bufferx.to_numpy(self._model_matrix_buffer)[0]
    self._viewport_events = viewport_events
    self._speed_x = 0.1
    self._speed_z = 0.1

    # Subscribe to keyboard and mouse events
    self._viewport_events.key_press_event.subscribe(self._on_key_event)
    self._viewport_events.key_release_event.subscribe(self._on_key_event)

close()

Unsubscribe from events.

Source code in src/gsp_extra/camera_controls/object_controls_awsd.py
41
42
43
44
def close(self):
    """Unsubscribe from events."""
    self._viewport_events.key_press_event.unsubscribe(self._on_key_event)
    self._viewport_events.key_release_event.unsubscribe(self._on_key_event)

Object Controls Trackball

gsp_extra.camera_controls.object_controls_trackball

Implements camera controls using AWSD keys for movement and mouse for orientation.

ObjectControlsTrackball

Implements camera controls using AWSD keys for movement and mouse for orientation.

Source code in src/gsp_extra/camera_controls/object_controls_trackball.py
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 ObjectControlsTrackball:
    """Implements camera controls using AWSD keys for movement and mouse for orientation."""

    def __init__(self, model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase):
        """Initialize the ObjectControlsTrackball.

        Args:
            model_matrix_buffer (Buffer): The buffer containing the model matrix to control.
            viewport_events (ViewportEventsBase): The viewport events to subscribe to for mouse input.
        """
        # sanity checks
        assert model_matrix_buffer.get_type() == BufferType.mat4, "model_matrix must be of type mat4"
        assert model_matrix_buffer.get_count() == 1, "model_matrix must have a count of 1"

        # copy arguments
        self._model_matrix_buffer = model_matrix_buffer
        self._model_matrix_numpy: np.ndarray = Bufferx.to_numpy(self._model_matrix_buffer)[0]
        self._viewport_events = viewport_events
        self._button_pressed: bool = False
        self._trackball = Trackball()

        self._start_x: float = 0.0
        self._start_y: float = 0.0

        # Subscribe to keyboard and mouse events
        self._viewport_events.button_press_event.subscribe(self._on_button_press)
        self._viewport_events.button_release_event.subscribe(self._on_button_release)
        self._viewport_events.mouse_move_event.subscribe(self._on_mouse_move)

    def close(self):
        """Unsubscribe from events."""
        self._viewport_events.button_press_event.unsubscribe(self._on_button_press)
        self._viewport_events.button_release_event.unsubscribe(self._on_button_release)
        self._viewport_events.mouse_move_event.unsubscribe(self._on_mouse_move)

    def _on_button_press(self, mouse_event: MouseEvent):
        self._button_pressed = True
        self._start_x = mouse_event.x_ndc
        self._start_y = mouse_event.y_ndc

    def _on_button_release(self, mouse_event: MouseEvent):
        self._button_pressed = False

    def _on_mouse_move(self, mouse_event: MouseEvent):
        # ignore if no button is pressed
        if self._button_pressed is False:
            return

        dx = mouse_event.x_ndc - self._start_x
        dy = mouse_event.y_ndc - self._start_y
        self._trackball.drag_to(self._start_x, self._start_y, dx, dy)
        self._start_x = mouse_event.x_ndc
        self._start_y = mouse_event.y_ndc
        # update the model matrix
        np.copyto(self._model_matrix_numpy, self._trackball.model.T)
        self._model_matrix_buffer.set_data(bytearray(self._model_matrix_numpy.tobytes()), 0, 1)

__init__(model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase)

Initialize the ObjectControlsTrackball.

Parameters:

Name Type Description Default
model_matrix_buffer gsp.types.buffer.Buffer

The buffer containing the model matrix to control.

required
viewport_events gsp.types.viewport_events_base.ViewportEventsBase

The viewport events to subscribe to for mouse input.

required
Source code in src/gsp_extra/camera_controls/object_controls_trackball.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __init__(self, model_matrix_buffer: Buffer, viewport_events: ViewportEventsBase):
    """Initialize the ObjectControlsTrackball.

    Args:
        model_matrix_buffer (Buffer): The buffer containing the model matrix to control.
        viewport_events (ViewportEventsBase): The viewport events to subscribe to for mouse input.
    """
    # sanity checks
    assert model_matrix_buffer.get_type() == BufferType.mat4, "model_matrix must be of type mat4"
    assert model_matrix_buffer.get_count() == 1, "model_matrix must have a count of 1"

    # copy arguments
    self._model_matrix_buffer = model_matrix_buffer
    self._model_matrix_numpy: np.ndarray = Bufferx.to_numpy(self._model_matrix_buffer)[0]
    self._viewport_events = viewport_events
    self._button_pressed: bool = False
    self._trackball = Trackball()

    self._start_x: float = 0.0
    self._start_y: float = 0.0

    # Subscribe to keyboard and mouse events
    self._viewport_events.button_press_event.subscribe(self._on_button_press)
    self._viewport_events.button_release_event.subscribe(self._on_button_release)
    self._viewport_events.mouse_move_event.subscribe(self._on_mouse_move)

close()

Unsubscribe from events.

Source code in src/gsp_extra/camera_controls/object_controls_trackball.py
44
45
46
47
48
def close(self):
    """Unsubscribe from events."""
    self._viewport_events.button_press_event.unsubscribe(self._on_button_press)
    self._viewport_events.button_release_event.unsubscribe(self._on_button_release)
    self._viewport_events.mouse_move_event.unsubscribe(self._on_mouse_move)

Miscellaneous Module

Various utility helpers and render item definitions.

Render Item

gsp_extra.misc.render_item

Render item definition.

RenderItem dataclass

Render item is a dataclasss containing all necessary information for rendering a visual in a viewport.

Source code in src/gsp_extra/misc/render_item.py
13
14
15
16
17
18
19
20
21
22
23
24
@dataclass
class RenderItem:
    """Render item is a dataclasss containing all necessary information for rendering a visual in a viewport."""

    viewport: Viewport
    """Viewport where the visual will be rendered."""
    visual_base: VisualBase
    """Visual to be rendered."""
    model_matrix: Buffer
    """Model matrix for transforming the visual."""
    camera: Camera
    """Camera used for rendering the visual."""

camera: Camera instance-attribute

Camera used for rendering the visual.

model_matrix: Buffer instance-attribute

Model matrix for transforming the visual.

viewport: Viewport instance-attribute

Viewport where the visual will be rendered.

visual_base: VisualBase instance-attribute

Visual to be rendered.

Colorama Utils

gsp_extra.misc.colorama_utils

Colorama utility functions.

text_cyan(text: str) -> str

Return the given text string colored in cyan.

Source code in src/gsp_extra/misc/colorama_utils.py
10
11
12
def text_cyan(text: str) -> str:
    """Return the given text string colored in cyan."""
    return colorama.Fore.CYAN + text + colorama.Style.RESET_ALL

text_magenta(text: str) -> str

Return the given text string colored in magenta.

Source code in src/gsp_extra/misc/colorama_utils.py
15
16
17
def text_magenta(text: str) -> str:
    """Return the given text string colored in magenta."""
    return colorama.Fore.MAGENTA + text + colorama.Style.RESET_ALL

MPL3D Module

3D mathematics utilities for graphics operations.

GLM

gsp_extra.mpl3d.glm

OpenGL Mathematics (GLM) utilities for numpy.

This module provides mathematical functions commonly used in 3D graphics, including matrix transformations, projections, rotations, and vector operations. All operations are implemented using numpy for efficient computation.

align(U: np.ndarray, V: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Return the rotation matrix that aligns vector U to vector V.

Parameters:

Name Type Description Default
U numpy.ndarray

First vector

required
V numpy.ndarray

Second vector

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Rotation matrix

Source code in src/gsp_extra/mpl3d/glm.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def align(U: np.ndarray, V: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Return the rotation matrix that aligns vector U to vector V.

    Args:
        U (np.ndarray): First vector
        V (np.ndarray): Second vector
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Rotation matrix
    """
    a, b = normalize(U), normalize(V)
    v = np.cross(a, b)
    c = np.dot(a, b)
    s = np.linalg.norm(v)
    K = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
    R = np.zeros((4, 4), dtype=dtype)
    R[:3, :3] = np.eye(3) + K + K @ K * ((1 - c) / (s**2))
    R[3, 3] = 1

    return R

camera(xrotation: float = 25.0, yrotation: float = 45.0, zoom: float = 1.0, mode: Literal['perspective', 'ortho'] = 'perspective') -> np.ndarray

Create a camera transformation matrix.

Parameters:

Name Type Description Default
xrotation float

Rotation around the X axis in degrees.

25.0
yrotation float

Rotation around the Y axis in degrees.

45.0
zoom float

Zoom factor.

1.0
mode typing.Literal['perspective', 'ortho']

Camera mode.

'perspective'

Returns:

Type Description
numpy.ndarray

np.ndarray: Camera matrix

Raises:

Type Description
ValueError

If an unknown camera mode is provided.

Source code in src/gsp_extra/mpl3d/glm.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
def camera(xrotation: float = 25.0, yrotation: float = 45.0, zoom: float = 1.0, mode: Literal["perspective", "ortho"] = "perspective") -> np.ndarray:
    """Create a camera transformation matrix.

    Args:
        xrotation (float): Rotation around the X axis in degrees.
        yrotation (float): Rotation around the Y axis in degrees.
        zoom (float): Zoom factor.
        mode (Literal["perspective", "ortho"]): Camera mode.

    Returns:
        np.ndarray: Camera matrix

    Raises:
        ValueError: If an unknown camera mode is provided.
    """
    xrotation = min(max(xrotation, 0), 90)
    yrotation = min(max(yrotation, 0), 90)
    zoom = max(0.1, zoom)
    scale_vector = np.array([zoom, zoom, zoom], dtype=np.float32)
    model = scale(scale_vector) @ xrotate(xrotation) @ yrotate(yrotation)
    translate_vector = np.array([0, 0, -4.5], dtype=np.float32)
    view = translate(translate_vector)
    if mode == "ortho":
        proj = ortho(-1, +1, -1, +1, 1, 100)
    elif mode == "perspective":
        proj = perspective(25, 1, 1, 100)
    else:
        raise ValueError("Unknown camera mode: " + mode)
    return proj @ view @ model

center(vertices: np.ndarray) -> np.ndarray

Center vertices around the origin.

Parameters:

Name Type Description Default
vertices numpy.ndarray

Vertices to center

required

Returns:

Type Description
numpy.ndarray

np.ndarray: vertices centered

Source code in src/gsp_extra/mpl3d/glm.py
245
246
247
248
249
250
251
252
253
254
255
256
def center(vertices: np.ndarray) -> np.ndarray:
    """Center vertices around the origin.

    Args:
        vertices (np.ndarray): Vertices to center

    Returns:
        np.ndarray: vertices centered
    """
    vmin = vertices.min(axis=0)
    vmax = vertices.max(axis=0)
    return vertices - (vmax + vmin) / 2

clamp(V: np.ndarray, vmin: float = 0, vmax: float = 1) -> np.ndarray

Clamp values between minimum and maximum bounds.

Parameters:

Name Type Description Default
V numpy.ndarray

Array of values to clamp.

required
vmin float

Minimum value (default: 0).

0
vmax float

Maximum value (default: 1).

1

Returns:

Type Description
numpy.ndarray

Array with values clamped to [vmin, vmax].

Source code in src/gsp_extra/mpl3d/glm.py
27
28
29
30
31
32
33
34
35
36
37
38
def clamp(V: np.ndarray, vmin: float = 0, vmax: float = 1) -> np.ndarray:
    """Clamp values between minimum and maximum bounds.

    Args:
        V: Array of values to clamp.
        vmin: Minimum value (default: 0).
        vmax: Maximum value (default: 1).

    Returns:
        Array with values clamped to [vmin, vmax].
    """
    return np.minimum(np.maximum(V, vmin), vmax)

fit(vertices: np.ndarray) -> np.ndarray

Fit vertices to the normalized cube.

Parameters:

Name Type Description Default
vertices numpy.ndarray

Vertices to fit

required

Returns:

Type Description
numpy.ndarray

np.ndarray: vertices contained in the normalize cube

Source code in src/gsp_extra/mpl3d/glm.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def fit(vertices: np.ndarray) -> np.ndarray:
    """Fit vertices to the normalized cube.

    Args:
        vertices (np.ndarray): Vertices to fit

    Returns:
        np.ndarray: vertices contained in the normalize cube
    """
    Vmin = vertices.min(axis=0)
    Vmax = vertices.max(axis=0)
    # return 2*(vertices-vmin) / max(vmax-vmin)-1
    V = 2 * (vertices - Vmin) / max(Vmax - Vmin) - 1
    return V - (V.min(axis=0) + V.max(axis=0)) / 2

frontback(triangles: np.ndarray) -> tuple[np.ndarray, np.ndarray]

Sort front and back facing triangles.

Parameters:

Name Type Description Default
triangles numpy.ndarray

Triangles to sort

required

Returns:

Type Description
tuple[numpy.ndarray, numpy.ndarray]

tuple[np.ndarray, np.ndarray]: front and back facing triangles as (n1,3) and (n2,3) arrays (n1+n2=n)

Source code in src/gsp_extra/mpl3d/glm.py
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def frontback(triangles: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """Sort front and back facing triangles.

    Args:
        triangles (np.ndarray): Triangles to sort

    Returns:
        tuple[np.ndarray, np.ndarray]: front and back facing triangles as (n1,3) and (n2,3) arrays (n1+n2=n)
    """
    Z = (
        (triangles[:, 1, 0] - triangles[:, 0, 0]) * (triangles[:, 1, 1] + triangles[:, 0, 1])
        + (triangles[:, 2, 0] - triangles[:, 1, 0]) * (triangles[:, 2, 1] + triangles[:, 1, 1])
        + (triangles[:, 0, 0] - triangles[:, 2, 0]) * (triangles[:, 0, 1] + triangles[:, 2, 1])
    )
    return Z < 0, Z >= 0

frustum(left: float, right: float, bottom: float, top: float, znear: float, zfar: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a view frustum projection matrix.

Parameters:

Name Type Description Default
left float

Left coordinate of the field of view.

required
right float

Right coordinate of the field of view.

required
bottom float

Bottom coordinate of the field of view.

required
top float

Top coordinate of the field of view.

required
znear float

Near coordinate of the field of view.

required
zfar float

Far coordinate of the field of view.

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: View frustum matrix

Source code in src/gsp_extra/mpl3d/glm.py
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
def frustum(
    left: float,
    right: float,
    bottom: float,
    top: float,
    znear: float,
    zfar: float,
    dtype: np.dtype = np.dtype(np.float32),
) -> np.ndarray:
    r"""Create a view frustum projection matrix.

    Args:
        left (float): Left coordinate of the field of view.
        right (float): Right coordinate of the field of view.
        bottom (float): Bottom coordinate of the field of view.
        top (float): Top coordinate of the field of view.
        znear (float): Near coordinate of the field of view.
        zfar (float): Far coordinate of the field of view.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: View frustum matrix
    """
    M = np.zeros((4, 4), dtype=dtype)
    M[0, 0] = +2.0 * znear / (right - left)
    M[1, 1] = +2.0 * znear / (top - bottom)
    M[2, 2] = -(zfar + znear) / (zfar - znear)
    M[0, 2] = (right + left) / (right - left)
    M[2, 1] = (top + bottom) / (top - bottom)
    M[2, 3] = -2.0 * znear * zfar / (zfar - znear)
    M[3, 2] = -1.0

    return M

lookat(eye: tuple[float, float, float] = (0, 0, 4.5), center: tuple[float, float, float] = (0, 0, 0), up: tuple[float, float, float] = (0, 0, 1), dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a viewing matrix derived from an eye point, reference point, and up vector.

Parameters:

Name Type Description Default
eye tuple[float, float, float]

Eye point

(0, 0, 4.5)
center tuple[float, float, float]

Reference point

(0, 0, 0)
up tuple[float, float, float]

Up vector

(0, 0, 1)
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: View matrix

Source code in src/gsp_extra/mpl3d/glm.py
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
def lookat(
    eye: tuple[float, float, float] = (0, 0, 4.5),
    center: tuple[float, float, float] = (0, 0, 0),
    up: tuple[float, float, float] = (0, 0, 1),
    dtype: np.dtype = np.dtype(np.float32),
) -> np.ndarray:
    """Create a viewing matrix derived from an eye point, reference point, and up vector.

    Args:
        eye (tuple[float, float, float]): Eye point
        center (tuple[float, float, float]): Reference point
        up (tuple[float, float, float]): Up vector
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: View matrix
    """
    eye_np = np.array(eye)
    center_np = np.array(center)
    up_np = np.array(up)

    Z = normalize(eye_np - center_np)
    Y = up_np
    X = normalize(np.cross(Y, Z))
    Y = normalize(np.cross(Z, X))
    return np.array(
        [
            [X[0], X[1], X[2], -np.dot(X, eye_np)],
            [Y[0], Y[1], Y[2], -np.dot(Y, eye_np)],
            [Z[0], Z[1], Z[2], -np.dot(Z, eye_np)],
            [0, 0, 0, 1],
        ],
        dtype=dtype,
    )

normalize(V: np.ndarray) -> np.ndarray

Normalize a vector or array of vectors to unit length.

Parameters:

Name Type Description Default
V numpy.ndarray

Vector or array of vectors to normalize.

required

Returns:

Type Description
numpy.ndarray

Normalized vector(s) with unit length.

Source code in src/gsp_extra/mpl3d/glm.py
15
16
17
18
19
20
21
22
23
24
def normalize(V: np.ndarray) -> np.ndarray:
    """Normalize a vector or array of vectors to unit length.

    Args:
        V: Vector or array of vectors to normalize.

    Returns:
        Normalized vector(s) with unit length.
    """
    return V / (1e-16 + np.sqrt((np.array(V) ** 2).sum(axis=-1)))[..., np.newaxis]

ortho(left: float, right: float, bottom: float, top: float, znear: float, zfar: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create an orthographic projection matrix.

Parameters:

Name Type Description Default
left float

Left coordinate of the field of view.

required
right float

Right coordinate of the field of view.

required
bottom float

Bottom coordinate of the field of view.

required
top float

Top coordinate of the field of view.

required
znear float

Near coordinate of the field of view.

required
zfar float

Far coordinate of the field of view.

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Orthographic projection matrix

Source code in src/gsp_extra/mpl3d/glm.py
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
def ortho(left: float, right: float, bottom: float, top: float, znear: float, zfar: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create an orthographic projection matrix.

    Args:
        left (float): Left coordinate of the field of view.
        right (float): Right coordinate of the field of view.
        bottom (float): Bottom coordinate of the field of view.
        top (float): Top coordinate of the field of view.
        znear (float): Near coordinate of the field of view.
        zfar (float): Far coordinate of the field of view.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Orthographic projection matrix
    """
    M = np.zeros((4, 4), dtype=dtype)
    M[0, 0] = +2.0 / (right - left)
    M[1, 1] = +2.0 / (top - bottom)
    M[2, 2] = -2.0 / (zfar - znear)
    M[3, 3] = 1.0
    M[0, 2] = -(right + left) / float(right - left)
    M[1, 3] = -(top + bottom) / float(top - bottom)
    M[2, 3] = -(zfar + znear) / float(zfar - znear)

    return M

perspective(fovy: float, aspect: float, znear: float, zfar: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a perspective projection matrix.

Parameters:

Name Type Description Default
fovy float

The field of view along the y axis.

required
aspect float

Aspect ratio of the view.

required
znear float

Near coordinate of the field of view.

required
zfar float

Far coordinate of the field of view.

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Perspective projection matrix

Source code in src/gsp_extra/mpl3d/glm.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def perspective(
    fovy: float,
    aspect: float,
    znear: float,
    zfar: float,
    dtype: np.dtype = np.dtype(np.float32),
) -> np.ndarray:
    """Create a perspective projection matrix.

    Args:
        fovy (float): The field of view along the y axis.
        aspect (float): Aspect ratio of the view.
        znear (float): Near coordinate of the field of view.
        zfar (float): Far coordinate of the field of view.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Perspective projection matrix
    """
    h = np.tan(0.5 * np.radians(fovy)) * znear
    w = h * aspect
    return frustum(-w, w, -h, h, znear, zfar, dtype)

rotate(angle: float, axis: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a rotation matrix around an arbitrary axis.

Parameters:

Name Type Description Default
angle float

Specifies the angle of rotation, in degrees.

required
axis numpy.ndarray

Axis of rotation

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Rotation matrix

Source code in src/gsp_extra/mpl3d/glm.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def rotate(angle: float, axis: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a rotation matrix around an arbitrary axis.

    Args:
        angle (float): Specifies the angle of rotation, in degrees.
        axis (np.ndarray): Axis of rotation
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Rotation matrix
    """
    t = np.radians(angle)

    axis = normalize(np.array(axis))
    a = np.cos(t / 2)
    b, c, d = -axis * np.sin(t / 2)
    aa, bb, cc, dd = a * a, b * b, c * c, d * d
    bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
    R = np.array(
        [
            [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac), 0],
            [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab), 0],
            [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc, 0],
            [0, 0, 0, 1],
        ],
        dtype=dtype,
    )

    return R

scale(scale: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a non-uniform scaling matrix along the x, y, and z axes.

Parameters:

Name Type Description Default
scale numpy.ndarray

Scaling vector

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Scaling matrix

Source code in src/gsp_extra/mpl3d/glm.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def scale(scale: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a non-uniform scaling matrix along the x, y, and z axes.

    Args:
        scale (np.ndarray): Scaling vector
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Scaling matrix
    """
    x, y, z = np.array(scale)
    S = np.array(
        [
            [x, 0, 0, 0],
            [0, y, 0, 0],
            [0, 0, z, 0],
            [0, 0, 0, 1],
        ],
        dtype=dtype,
    )

    return S

translate(translate: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a translation matrix by a given vector.

Parameters:

Name Type Description Default
translate numpy.ndarray

Translation vector.

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Translation matrix

Source code in src/gsp_extra/mpl3d/glm.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def translate(translate: np.ndarray, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a translation matrix by a given vector.

    Args:
        translate (np.ndarray): Translation vector.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Translation matrix
    """
    x, y, z = np.array(translate)
    T = np.array([[1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1]], dtype=dtype)

    return T

viewport(x: int, y: int, w: int, h: int, d: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a viewport transformation matrix.

Parameters:

Name Type Description Default
x int

X origin (pixels) of the viewport (lower left)

required
y int

Y origin (pixels) of the viewport (lower left)

required
w int

Width (pixels) of the viewport

required
h int

Height (pixels) of the viewport

required
d float

Depth of the viewport.

required
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Viewport matrix

Source code in src/gsp_extra/mpl3d/glm.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def viewport(x: int, y: int, w: int, h: int, d: float, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a viewport transformation matrix.

    Args:
        x (int): X origin (pixels) of the viewport (lower left)
        y (int): Y origin (pixels) of the viewport (lower left)
        w (int): Width (pixels) of the viewport
        h (int): Height (pixels) of the viewport
        d (float): Depth of the viewport.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Viewport matrix
    """
    M = np.array(
        [
            [w / 2, 0, 0, x + w / 2],
            [0, h / 2, 0, y + h / 2],
            [0, 0, d / 2, d / 2],
            [0, 0, 0, 1],
        ],
        dtype=dtype,
    )
    return M

xrotate(angle_x: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a rotation matrix about the X axis.

Parameters:

Name Type Description Default
angle_x float

Specifies the angle of rotation, in degrees.

0.0
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Rotation matrix

Source code in src/gsp_extra/mpl3d/glm.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
def xrotate(angle_x: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a rotation matrix about the X axis.

    Args:
        angle_x (float):
            Specifies the angle of rotation, in degrees.
        dtype (np.dtype):
            dtype of the resulting array

    Returns:
       np.ndarray: Rotation matrix
    """
    t = np.radians(angle_x)
    c, s = np.cos(t), np.sin(t)
    R = np.array([[1, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, 1]], dtype=dtype)

    return R

yrotate(angle_y: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a rotation matrix about the Y axis.

Parameters:

Name Type Description Default
angle_y float

Specifies the angle of rotation, in degrees.

0.0
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Rotation matrix

Source code in src/gsp_extra/mpl3d/glm.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def yrotate(angle_y: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a rotation matrix about the Y axis.

    Args:
        angle_y (float): Specifies the angle of rotation, in degrees.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Rotation matrix
    """
    t = np.radians(angle_y)
    c, s = np.cos(t), np.sin(t)
    R = np.array([[c, 0, s, 0], [0, 1, 0, 0], [-s, 0, c, 0], [0, 0, 0, 1]], dtype=dtype)

    return R

zrotate(angle_z: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray

Create a rotation matrix about the Z axis.

Parameters:

Name Type Description Default
angle_z float

Specifies the angle of rotation, in degrees.

0.0
dtype numpy.dtype

dtype of the resulting array

numpy.dtype(numpy.float32)

Returns:

Type Description
numpy.ndarray

np.ndarray: Rotation matrix

Source code in src/gsp_extra/mpl3d/glm.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def zrotate(angle_z: float = 0.0, dtype: np.dtype = np.dtype(np.float32)) -> np.ndarray:
    """Create a rotation matrix about the Z axis.

    Args:
        angle_z (float): Specifies the angle of rotation, in degrees.
        dtype (np.dtype): dtype of the resulting array

    Returns:
        np.ndarray: Rotation matrix
    """
    t = np.radians(angle_z)
    c, s = np.cos(t), np.sin(t)
    R = np.array([[c, -s, 0, 0], [s, c, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], dtype=dtype)

    return R

Trackball

gsp_extra.mpl3d.trackball

Provides a virtual trackball for 3D scene viewing.

Example usage:

trackball = Trackball(45,30)

@window.event def on_mouse_drag(x, y, dx, dy, button): trackball.drag(x,y,dx,dy)

@window.event def on_resize(width,height): glViewport(0, 0, window.width, window.height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45, window.width / float(window.height), .1, 1000) glMatrixMode (GL_MODELVIEW) glLoadIdentity () glTranslatef (0, 0, -3) glMultMatrixf(trackball.model)

You can also set trackball orientation directly by setting theta and phi value expressed in degrees. Theta relates to the rotation angle around X axis while phi relates to the rotation angle around Z axis.

Trackball

Bases: object

Virtual trackball for 3D scene viewing.

Source code in src/gsp_extra/mpl3d/trackball.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
class Trackball(object):
    """Virtual trackball for 3D scene viewing."""

    def __init__(self, theta: float = 0, phi: float = 0):
        """Build a new trackball with specified view.

        Args:
            theta: Initial rotation angle around the X axis in degrees.
            phi: Initial rotation angle around the Z axis in degrees.
        """
        self._rotation = [0, 0, 0, 1]
        self._count = 0
        self._model = np.zeros((4, 4), float)
        self._RENORMCOUNT = 97
        self._TRACKBALLSIZE = 0.8
        self._set_orientation(theta, phi)

    def drag_to(self, x: float, y: float, dx: float, dy: float):
        """Move trackball view from x,y to x+dx,y+dy.

        Updates the trackball rotation based on mouse drag movement.

        Args:
            x: Current x position in normalized coordinates [-1, 1].
            y: Current y position in normalized coordinates [-1, 1].
            dx: Change in x position.
            dy: Change in y position.
        """
        q = self._rotate(x, y, dx, dy)
        self._rotation = _q_add(q, self._rotation)
        self._count += 1
        if self._count > self._RENORMCOUNT:
            self._rotation = _q_normalize(self._rotation)
            self._count = 0
        self._model = _q_rotmatrix(self._rotation)

    @property
    def model(self) -> np.ndarray:
        """Model transformation (read-only).

        Returns:
            The current model transformation matrix as a 4x4 numpy array.
        """
        return self._model

    @property
    def theta(self):
        """Angle (in degrees) around the z axis."""
        self._theta, _ = self._get_orientation()
        return self._theta

    @theta.setter
    def theta(self, theta: float):
        self._set_orientation(math.fmod(theta, 360.0), math.fmod(self._phi, 360.0))

    @property
    def phi(self):
        """Angle (in degrees) around the x axis."""
        _, self._phi = self._get_orientation()
        return self._phi

    @phi.setter
    def phi(self, phi: float):
        self._set_orientation(math.fmod(self._theta, 360.0), math.fmod(phi, 360.0))

    def _get_orientation(self):
        """Return current computed orientation (theta,phi)."""
        q0, q1, q2, q3 = self._rotation
        ax = math.atan(2 * (q0 * q1 + q2 * q3) / (1 - 2 * (q1 * q1 + q2 * q2))) * 180.0 / math.pi
        az = math.atan(2 * (q0 * q3 + q1 * q2) / (1 - 2 * (q2 * q2 + q3 * q3))) * 180.0 / math.pi
        return -az, ax

    def _set_orientation(self, theta: float, phi: float):
        """Computes rotation corresponding to theta and phi."""
        self._theta = theta
        self._phi = phi
        angle = self._theta * (math.pi / 180.0)
        sine = math.sin(0.5 * angle)
        xrot = [1 * sine, 0, 0, math.cos(0.5 * angle)]
        angle = self._phi * (math.pi / 180.0)
        sine = math.sin(0.5 * angle)
        zrot = [0, 0, sine, math.cos(0.5 * angle)]
        self._rotation = _q_add(xrot, zrot)
        self._model = _q_rotmatrix(self._rotation)

    def _project(self, r: float, x: float, y: float):
        """Project an x,y pair onto a sphere of radius r or a hyperbolic sheet.

        Projects onto a hyperbolic sheet if we are away from the center of the sphere.

        Args:
            r: Sphere radius.
            x: X coordinate.
            y: Y coordinate.

        Returns:
            The z coordinate of the projection.
        """
        d = math.sqrt(x * x + y * y)
        if d < r * 0.70710678118654752440:  # Inside sphere
            z = math.sqrt(r * r - d * d)
        else:  # On hyperbola
            t = r / 1.41421356237309504880
            z = t * t / d
        return z

    def _rotate(self, x: float, y: float, dx: float, dy: float):
        """Simulate a track-ball.

        Project the points onto the virtual trackball, then figure out the
        axis of rotation, which is the cross product of x,y and x+dx,y+dy.

        Note: This is a deformed trackball-- this is a trackball in the
        center, but is deformed into a hyperbolic sheet of rotation away
        from the center.  This particular function was chosen after trying
        out several variations.

        Args:
            x: Current x position.
            y: Current y position.
            dx: Change in x position.
            dy: Change in y position.
        """
        if not dx and not dy:
            return [0.0, 0.0, 0.0, 1.0]
        last = [x, y, self._project(self._TRACKBALLSIZE, x, y)]
        new = [x + dx, y + dy, self._project(self._TRACKBALLSIZE, x + dx, y + dy)]
        a = _v_cross(new, last)
        d = _v_sub(last, new)
        t = _v_length(d) / (2.0 * self._TRACKBALLSIZE)
        if t > 1.0:
            t = 1.0
        if t < -1.0:
            t = -1.0
        phi = 2.0 * math.asin(t)
        return _q_from_axis_angle(a, phi)

model: np.ndarray property

Model transformation (read-only).

Returns:

Type Description
numpy.ndarray

The current model transformation matrix as a 4x4 numpy array.

phi property writable

Angle (in degrees) around the x axis.

theta property writable

Angle (in degrees) around the z axis.

__init__(theta: float = 0, phi: float = 0)

Build a new trackball with specified view.

Parameters:

Name Type Description Default
theta float

Initial rotation angle around the X axis in degrees.

0
phi float

Initial rotation angle around the Z axis in degrees.

0
Source code in src/gsp_extra/mpl3d/trackball.py
289
290
291
292
293
294
295
296
297
298
299
300
301
def __init__(self, theta: float = 0, phi: float = 0):
    """Build a new trackball with specified view.

    Args:
        theta: Initial rotation angle around the X axis in degrees.
        phi: Initial rotation angle around the Z axis in degrees.
    """
    self._rotation = [0, 0, 0, 1]
    self._count = 0
    self._model = np.zeros((4, 4), float)
    self._RENORMCOUNT = 97
    self._TRACKBALLSIZE = 0.8
    self._set_orientation(theta, phi)

drag_to(x: float, y: float, dx: float, dy: float)

Move trackball view from x,y to x+dx,y+dy.

Updates the trackball rotation based on mouse drag movement.

Parameters:

Name Type Description Default
x float

Current x position in normalized coordinates [-1, 1].

required
y float

Current y position in normalized coordinates [-1, 1].

required
dx float

Change in x position.

required
dy float

Change in y position.

required
Source code in src/gsp_extra/mpl3d/trackball.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def drag_to(self, x: float, y: float, dx: float, dy: float):
    """Move trackball view from x,y to x+dx,y+dy.

    Updates the trackball rotation based on mouse drag movement.

    Args:
        x: Current x position in normalized coordinates [-1, 1].
        y: Current y position in normalized coordinates [-1, 1].
        dx: Change in x position.
        dy: Change in y position.
    """
    q = self._rotate(x, y, dx, dy)
    self._rotation = _q_add(q, self._rotation)
    self._count += 1
    if self._count > self._RENORMCOUNT:
        self._rotation = _q_normalize(self._rotation)
        self._count = 0
    self._model = _q_rotmatrix(self._rotation)

Transform link utilities for loading and network operations.

"TransformLink that loads data from a URI into a Buffer.

Transform Load

gsp_extra.transform_links.transform_load

TransformLink that loads data from a URI into a Buffer.

TransformLoad

Bases: gsp.transforms.transform_link_base.TransformLinkBase

Load data from a URI into a Buffer. previous buffer is ignored.

Source code in src/gsp_extra/transform_links/transform_load.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
class TransformLoad(TransformLinkBase):
    """Load data from a URI into a Buffer. previous buffer is ignored."""

    __slots__ = ["_uri", "_buffer_type"]

    def __init__(self, uri: str, buffer_type: BufferType) -> None:
        """Initialize the TransformLoad.

        Args:
            uri (str): The URI to load data from.
            buffer_type (BufferType): The type of buffer to create.
        """
        self._uri = uri
        self._buffer_type = buffer_type

    def apply(self, buffer_src: Buffer | None) -> Buffer:
        """Load data from the URI into a new Buffer.

        Args:
            buffer_src (Buffer | None): Ignored.

        Returns:
            Buffer: The loaded buffer.
        """
        item_size = BufferType.get_item_size(self._buffer_type)

        is_image = os.path.splitext(self._uri)[1].lower() in [".png", ".jpg", ".jpeg", ".bmp", ".tiff"]
        is_npy = os.path.splitext(self._uri)[1].lower() in [".npy"]
        if is_npy:
            # If the URI points to a .npy file, use numpy to load it

            # 1. Create a session object
            requests_session = requests.Session()
            # 2. Mount the FileAdapter for the 'file://' scheme
            requests_session.mount("file://", requests_file.FileAdapter())

            # Load numpy array
            response = requests_session.get(self._uri)

            response.raise_for_status()
            array = np.load(BytesIO(response.content))

            # sanity check
            assert array.nbytes % item_size == 0, f"Numpy array data size {array.nbytes} is not aligned with buffer type item size {item_size}"

            # Build a new buffer
            count = array.nbytes // item_size
            new_buffer = Buffer(count, self._buffer_type)
            new_buffer.set_data(bytearray(array.tobytes()), 0, count)
            return new_buffer
        elif is_image:
            # If the URI points to an image, use imageio to load it

            # Load image data
            image_data = imageio.v3.imread(self._uri)

            # sanity check
            assert image_data.nbytes % item_size == 0, f"Image data size {image_data.nbytes} is not aligned with buffer type item size {item_size}"

            # Build a new buffer
            count = image_data.nbytes // item_size
            new_buffer = Buffer(count, self._buffer_type)
            new_buffer.set_data(bytearray(image_data.tobytes()), 0, count)
            return new_buffer
        else:
            # Load data from URI
            response = requests.get(self._uri)
            response.raise_for_status()
            content = response.content

            # sanity check
            assert len(content) % item_size == 0, f"Data size {len(content)} is not a multiple of item size {item_size} for buffer type {self._buffer_type}"

            count = len(content) // item_size
            new_buffer = Buffer(count, self._buffer_type)
            new_buffer.set_data(bytearray(content), 0, count)
            return new_buffer

    # =============================================================================
    # Serialization functions
    # =============================================================================

    def serialize(self) -> dict[str, Any]:
        """Serialize the TransformLoad to a dictionary.

        Returns:
            dict[str, Any]: The serialized TransformLoad.
        """
        return {
            "link_type": "TransformLoad",
            "link_data": {
                "uri": self._uri,
                "buffer_type": self._buffer_type.name,
            },
        }

    @staticmethod
    def deserialize(data: dict[str, Any]) -> "TransformLoad":
        """Deserialize a TransformLoad from a dictionary.

        Args:
            data (dict[str, Any]): The serialized TransformLoad.

        Returns:
            TransformLoad: The deserialized TransformLoad instance.
        """
        assert data["link_type"] == "TransformLoad", "Invalid type for TransformLoad deserialization"
        uri: str = data["link_data"]["uri"]
        buffer_type_str: str = data["link_data"]["buffer_type"]
        buffer_type = BufferType[buffer_type_str]
        return TransformLoad(uri, buffer_type)

__init__(uri: str, buffer_type: BufferType) -> None

Initialize the TransformLoad.

Parameters:

Name Type Description Default
uri str

The URI to load data from.

required
buffer_type gsp.types.buffer_type.BufferType

The type of buffer to create.

required
Source code in src/gsp_extra/transform_links/transform_load.py
25
26
27
28
29
30
31
32
33
def __init__(self, uri: str, buffer_type: BufferType) -> None:
    """Initialize the TransformLoad.

    Args:
        uri (str): The URI to load data from.
        buffer_type (BufferType): The type of buffer to create.
    """
    self._uri = uri
    self._buffer_type = buffer_type

apply(buffer_src: Buffer | None) -> Buffer

Load data from the URI into a new Buffer.

Parameters:

Name Type Description Default
buffer_src gsp.types.buffer.Buffer | None

Ignored.

required

Returns:

Name Type Description
Buffer gsp.types.buffer.Buffer

The loaded buffer.

Source code in src/gsp_extra/transform_links/transform_load.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
def apply(self, buffer_src: Buffer | None) -> Buffer:
    """Load data from the URI into a new Buffer.

    Args:
        buffer_src (Buffer | None): Ignored.

    Returns:
        Buffer: The loaded buffer.
    """
    item_size = BufferType.get_item_size(self._buffer_type)

    is_image = os.path.splitext(self._uri)[1].lower() in [".png", ".jpg", ".jpeg", ".bmp", ".tiff"]
    is_npy = os.path.splitext(self._uri)[1].lower() in [".npy"]
    if is_npy:
        # If the URI points to a .npy file, use numpy to load it

        # 1. Create a session object
        requests_session = requests.Session()
        # 2. Mount the FileAdapter for the 'file://' scheme
        requests_session.mount("file://", requests_file.FileAdapter())

        # Load numpy array
        response = requests_session.get(self._uri)

        response.raise_for_status()
        array = np.load(BytesIO(response.content))

        # sanity check
        assert array.nbytes % item_size == 0, f"Numpy array data size {array.nbytes} is not aligned with buffer type item size {item_size}"

        # Build a new buffer
        count = array.nbytes // item_size
        new_buffer = Buffer(count, self._buffer_type)
        new_buffer.set_data(bytearray(array.tobytes()), 0, count)
        return new_buffer
    elif is_image:
        # If the URI points to an image, use imageio to load it

        # Load image data
        image_data = imageio.v3.imread(self._uri)

        # sanity check
        assert image_data.nbytes % item_size == 0, f"Image data size {image_data.nbytes} is not aligned with buffer type item size {item_size}"

        # Build a new buffer
        count = image_data.nbytes // item_size
        new_buffer = Buffer(count, self._buffer_type)
        new_buffer.set_data(bytearray(image_data.tobytes()), 0, count)
        return new_buffer
    else:
        # Load data from URI
        response = requests.get(self._uri)
        response.raise_for_status()
        content = response.content

        # sanity check
        assert len(content) % item_size == 0, f"Data size {len(content)} is not a multiple of item size {item_size} for buffer type {self._buffer_type}"

        count = len(content) // item_size
        new_buffer = Buffer(count, self._buffer_type)
        new_buffer.set_data(bytearray(content), 0, count)
        return new_buffer

deserialize(data: dict[str, Any]) -> TransformLoad staticmethod

Deserialize a TransformLoad from a dictionary.

Parameters:

Name Type Description Default
data dict[str, typing.Any]

The serialized TransformLoad.

required

Returns:

Name Type Description
TransformLoad gsp_extra.transform_links.transform_load.TransformLoad

The deserialized TransformLoad instance.

Source code in src/gsp_extra/transform_links/transform_load.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@staticmethod
def deserialize(data: dict[str, Any]) -> "TransformLoad":
    """Deserialize a TransformLoad from a dictionary.

    Args:
        data (dict[str, Any]): The serialized TransformLoad.

    Returns:
        TransformLoad: The deserialized TransformLoad instance.
    """
    assert data["link_type"] == "TransformLoad", "Invalid type for TransformLoad deserialization"
    uri: str = data["link_data"]["uri"]
    buffer_type_str: str = data["link_data"]["buffer_type"]
    buffer_type = BufferType[buffer_type_str]
    return TransformLoad(uri, buffer_type)

serialize() -> dict[str, Any]

Serialize the TransformLoad to a dictionary.

Returns:

Type Description
dict[str, typing.Any]

dict[str, Any]: The serialized TransformLoad.

Source code in src/gsp_extra/transform_links/transform_load.py
102
103
104
105
106
107
108
109
110
111
112
113
114
def serialize(self) -> dict[str, Any]:
    """Serialize the TransformLoad to a dictionary.

    Returns:
        dict[str, Any]: The serialized TransformLoad.
    """
    return {
        "link_type": "TransformLoad",
        "link_data": {
            "uri": self._uri,
            "buffer_type": self._buffer_type.name,
        },
    }

Transform Network Server

gsp_extra.transform_links.transform_network_server

Starts a network server with user-space transforms registered.