foxglove-sdk 0.15.3__cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of foxglove-sdk might be problematic. Click here for more details.
- foxglove/__init__.py +241 -0
- foxglove/_foxglove_py/__init__.pyi +210 -0
- foxglove/_foxglove_py/channels.pyi +2792 -0
- foxglove/_foxglove_py/cloud.pyi +9 -0
- foxglove/_foxglove_py/mcap.pyi +96 -0
- foxglove/_foxglove_py/schemas.pyi +1009 -0
- foxglove/_foxglove_py/schemas_wkt.pyi +85 -0
- foxglove/_foxglove_py/websocket.pyi +321 -0
- foxglove/_foxglove_py.cpython-39-powerpc64le-linux-gnu.so +0 -0
- foxglove/benchmarks/test_mcap_serialization.py +160 -0
- foxglove/channel.py +241 -0
- foxglove/channels/__init__.py +94 -0
- foxglove/cloud.py +61 -0
- foxglove/mcap.py +12 -0
- foxglove/notebook/__init__.py +0 -0
- foxglove/notebook/foxglove_widget.py +100 -0
- foxglove/notebook/notebook_buffer.py +114 -0
- foxglove/notebook/static/widget.js +1 -0
- foxglove/py.typed +0 -0
- foxglove/schemas/__init__.py +163 -0
- foxglove/tests/__init__.py +0 -0
- foxglove/tests/test_channel.py +243 -0
- foxglove/tests/test_context.py +10 -0
- foxglove/tests/test_logging.py +62 -0
- foxglove/tests/test_mcap.py +199 -0
- foxglove/tests/test_parameters.py +178 -0
- foxglove/tests/test_schemas.py +17 -0
- foxglove/tests/test_server.py +112 -0
- foxglove/tests/test_time.py +137 -0
- foxglove/websocket.py +205 -0
- foxglove_sdk-0.15.3.dist-info/METADATA +53 -0
- foxglove_sdk-0.15.3.dist-info/RECORD +33 -0
- foxglove_sdk-0.15.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
class Duration:
|
|
4
|
+
"""
|
|
5
|
+
A duration in seconds and nanoseconds
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
sec: int,
|
|
11
|
+
nsec: int | None = None,
|
|
12
|
+
) -> None: ...
|
|
13
|
+
@property
|
|
14
|
+
def sec(self) -> int: ...
|
|
15
|
+
@property
|
|
16
|
+
def nsec(self) -> int: ...
|
|
17
|
+
@staticmethod
|
|
18
|
+
def from_secs(secs: float) -> "Duration":
|
|
19
|
+
"""
|
|
20
|
+
Creates a :py:class:`Duration` from seconds.
|
|
21
|
+
|
|
22
|
+
Raises `OverflowError` if the duration cannot be represented.
|
|
23
|
+
|
|
24
|
+
:param secs: Seconds
|
|
25
|
+
"""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def from_timedelta(td: datetime.timedelta) -> "Duration":
|
|
30
|
+
"""
|
|
31
|
+
Creates a :py:class:`Duration` from a timedelta.
|
|
32
|
+
|
|
33
|
+
Raises `OverflowError` if the duration cannot be represented.
|
|
34
|
+
|
|
35
|
+
:param td: Timedelta
|
|
36
|
+
"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
class Timestamp:
|
|
40
|
+
"""
|
|
41
|
+
A timestamp in seconds and nanoseconds
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
sec: int,
|
|
47
|
+
nsec: int | None = None,
|
|
48
|
+
) -> None: ...
|
|
49
|
+
@property
|
|
50
|
+
def sec(self) -> int: ...
|
|
51
|
+
@property
|
|
52
|
+
def nsec(self) -> int: ...
|
|
53
|
+
@staticmethod
|
|
54
|
+
def from_epoch_secs(timestamp: float) -> "Timestamp":
|
|
55
|
+
"""
|
|
56
|
+
Creates a :py:class:`Timestamp` from an epoch timestamp, such as is
|
|
57
|
+
returned by :py:func:`time.time` or :py:func:`datetime.datetime.timestamp`.
|
|
58
|
+
|
|
59
|
+
Raises `OverflowError` if the timestamp cannot be represented.
|
|
60
|
+
|
|
61
|
+
:param timestamp: Seconds since epoch
|
|
62
|
+
"""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def from_datetime(dt: datetime.datetime) -> "Timestamp":
|
|
67
|
+
"""
|
|
68
|
+
Creates a UNIX epoch :py:class:`Timestamp` from a datetime object.
|
|
69
|
+
|
|
70
|
+
Naive datetime objects are presumed to be in the local timezone.
|
|
71
|
+
|
|
72
|
+
Raises `OverflowError` if the timestamp cannot be represented.
|
|
73
|
+
|
|
74
|
+
:param dt: Datetime
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def now() -> "Timestamp":
|
|
80
|
+
"""
|
|
81
|
+
Creates a :py:class:`Timestamp` from the current system time.
|
|
82
|
+
|
|
83
|
+
Raises `OverflowError` if the timestamp cannot be represented.
|
|
84
|
+
"""
|
|
85
|
+
...
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from foxglove import Schema
|
|
4
|
+
from foxglove.websocket import (
|
|
5
|
+
AnyNativeParameterValue,
|
|
6
|
+
AnyParameterValue,
|
|
7
|
+
ServiceHandler,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
class Capability(Enum):
|
|
11
|
+
"""
|
|
12
|
+
An enumeration of capabilities that the websocket server can advertise to its clients.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
ClientPublish = ...
|
|
16
|
+
"""Allow clients to advertise channels to send data messages to the server."""
|
|
17
|
+
|
|
18
|
+
ConnectionGraph = ...
|
|
19
|
+
"""Allow clients to subscribe and make connection graph updates"""
|
|
20
|
+
|
|
21
|
+
Parameters = ...
|
|
22
|
+
"""Allow clients to get & set parameters."""
|
|
23
|
+
|
|
24
|
+
Services = ...
|
|
25
|
+
"""Allow clients to call services."""
|
|
26
|
+
|
|
27
|
+
Time = ...
|
|
28
|
+
"""Inform clients about the latest server time."""
|
|
29
|
+
|
|
30
|
+
class Client:
|
|
31
|
+
"""
|
|
32
|
+
A client that is connected to a running websocket server.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id: int = ...
|
|
36
|
+
|
|
37
|
+
class ChannelView:
|
|
38
|
+
"""
|
|
39
|
+
Information about a channel.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
id: int = ...
|
|
43
|
+
topic: str = ...
|
|
44
|
+
|
|
45
|
+
class ClientChannel:
|
|
46
|
+
"""
|
|
47
|
+
Information about a channel advertised by a client.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
id: int = ...
|
|
51
|
+
topic: str = ...
|
|
52
|
+
encoding: str = ...
|
|
53
|
+
schema_name: str = ...
|
|
54
|
+
schema_encoding: str | None = ...
|
|
55
|
+
schema: bytes | None = ...
|
|
56
|
+
|
|
57
|
+
class ConnectionGraph:
|
|
58
|
+
"""
|
|
59
|
+
A graph of connections between clients.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self) -> None: ...
|
|
63
|
+
def set_published_topic(self, topic: str, publisher_ids: list[str]) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Set a published topic and its associated publisher ids. Overwrites any existing topic with
|
|
66
|
+
the same name.
|
|
67
|
+
|
|
68
|
+
:param topic: The topic name.
|
|
69
|
+
:param publisher_ids: The set of publisher ids.
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def set_subscribed_topic(self, topic: str, subscriber_ids: list[str]) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Set a subscribed topic and its associated subscriber ids. Overwrites any existing topic with
|
|
76
|
+
the same name.
|
|
77
|
+
|
|
78
|
+
:param topic: The topic name.
|
|
79
|
+
:param subscriber_ids: The set of subscriber ids.
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def set_advertised_service(self, service: str, provider_ids: list[str]) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Set an advertised service and its associated provider ids Overwrites any existing service
|
|
86
|
+
with the same name.
|
|
87
|
+
|
|
88
|
+
:param service: The service name.
|
|
89
|
+
:param provider_ids: The set of provider ids.
|
|
90
|
+
"""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
class MessageSchema:
|
|
94
|
+
"""
|
|
95
|
+
A service request or response schema.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
encoding: str
|
|
99
|
+
schema: Schema
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
encoding: str,
|
|
105
|
+
schema: Schema,
|
|
106
|
+
) -> None: ...
|
|
107
|
+
|
|
108
|
+
class Parameter:
|
|
109
|
+
"""
|
|
110
|
+
A parameter which can be sent to a client.
|
|
111
|
+
|
|
112
|
+
:param name: The parameter name.
|
|
113
|
+
:type name: str
|
|
114
|
+
:param value: Optional value, represented as a native python object, or a ParameterValue.
|
|
115
|
+
:type value: None|bool|int|float|str|bytes|list|dict|ParameterValue
|
|
116
|
+
:param type: Optional parameter type. This is automatically derived when passing a native
|
|
117
|
+
python object as the value.
|
|
118
|
+
:type type: ParameterType|None
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
name: str
|
|
122
|
+
type: ParameterType | None
|
|
123
|
+
value: AnyParameterValue | None
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
name: str,
|
|
128
|
+
*,
|
|
129
|
+
value: AnyNativeParameterValue | None = None,
|
|
130
|
+
type: ParameterType | None = None,
|
|
131
|
+
) -> None: ...
|
|
132
|
+
def get_value(self) -> AnyNativeParameterValue | None:
|
|
133
|
+
"""Returns the parameter value as a native python object."""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
class ParameterType(Enum):
|
|
137
|
+
"""
|
|
138
|
+
The type of a parameter.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
ByteArray = ...
|
|
142
|
+
"""A byte array."""
|
|
143
|
+
|
|
144
|
+
Float64 = ...
|
|
145
|
+
"""A floating-point value that can be represented as a `float64`."""
|
|
146
|
+
|
|
147
|
+
Float64Array = ...
|
|
148
|
+
"""An array of floating-point values that can be represented as `float64`s."""
|
|
149
|
+
|
|
150
|
+
class ParameterValue:
|
|
151
|
+
"""
|
|
152
|
+
A parameter value.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
class Integer:
|
|
156
|
+
"""An integer value."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, value: int) -> None: ...
|
|
159
|
+
|
|
160
|
+
class Bool:
|
|
161
|
+
"""A boolean value."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, value: bool) -> None: ...
|
|
164
|
+
|
|
165
|
+
class Float64:
|
|
166
|
+
"""A floating-point value."""
|
|
167
|
+
|
|
168
|
+
def __init__(self, value: float) -> None: ...
|
|
169
|
+
|
|
170
|
+
class String:
|
|
171
|
+
"""
|
|
172
|
+
A string value.
|
|
173
|
+
|
|
174
|
+
For parameters of type :py:attr:ParameterType.ByteArray, this is a
|
|
175
|
+
base64 encoding of the byte array.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(self, value: str) -> None: ...
|
|
179
|
+
|
|
180
|
+
class Array:
|
|
181
|
+
"""An array of parameter values."""
|
|
182
|
+
|
|
183
|
+
def __init__(self, value: list[AnyParameterValue]) -> None: ...
|
|
184
|
+
|
|
185
|
+
class Dict:
|
|
186
|
+
"""An associative map of parameter values."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, value: dict[str, AnyParameterValue]) -> None: ...
|
|
189
|
+
|
|
190
|
+
class ServiceRequest:
|
|
191
|
+
"""
|
|
192
|
+
A websocket service request.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
service_name: str
|
|
196
|
+
client_id: int
|
|
197
|
+
call_id: int
|
|
198
|
+
encoding: str
|
|
199
|
+
payload: bytes
|
|
200
|
+
|
|
201
|
+
class Service:
|
|
202
|
+
"""
|
|
203
|
+
A websocket service.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
name: str
|
|
207
|
+
schema: ServiceSchema
|
|
208
|
+
handler: ServiceHandler
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
*,
|
|
213
|
+
name: str,
|
|
214
|
+
schema: ServiceSchema,
|
|
215
|
+
handler: ServiceHandler,
|
|
216
|
+
): ...
|
|
217
|
+
|
|
218
|
+
class ServiceSchema:
|
|
219
|
+
"""
|
|
220
|
+
A websocket service schema.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
name: str
|
|
224
|
+
request: MessageSchema | None
|
|
225
|
+
response: MessageSchema | None
|
|
226
|
+
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
*,
|
|
230
|
+
name: str,
|
|
231
|
+
request: MessageSchema | None = None,
|
|
232
|
+
response: MessageSchema | None = None,
|
|
233
|
+
): ...
|
|
234
|
+
|
|
235
|
+
class StatusLevel(Enum):
|
|
236
|
+
"""A level for `WebSocketServer.publish_status`"""
|
|
237
|
+
|
|
238
|
+
Info = ...
|
|
239
|
+
Warning = ...
|
|
240
|
+
Error = ...
|
|
241
|
+
|
|
242
|
+
class WebSocketServer:
|
|
243
|
+
"""
|
|
244
|
+
A websocket server for live visualization.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __init__(self) -> None: ...
|
|
248
|
+
@property
|
|
249
|
+
def port(self) -> int:
|
|
250
|
+
"""Get the port on which the server is listening."""
|
|
251
|
+
...
|
|
252
|
+
|
|
253
|
+
def app_url(
|
|
254
|
+
self,
|
|
255
|
+
*,
|
|
256
|
+
layout_id: str | None = None,
|
|
257
|
+
open_in_desktop: bool = False,
|
|
258
|
+
) -> str | None:
|
|
259
|
+
"""
|
|
260
|
+
Returns a web app URL to open the websocket as a data source.
|
|
261
|
+
|
|
262
|
+
Returns None if the server has been stopped.
|
|
263
|
+
|
|
264
|
+
:param layout_id: An optional layout ID to include in the URL.
|
|
265
|
+
:param open_in_desktop: Opens the foxglove desktop app.
|
|
266
|
+
"""
|
|
267
|
+
...
|
|
268
|
+
|
|
269
|
+
def stop(self) -> None:
|
|
270
|
+
"""Explicitly stop the server."""
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
def clear_session(self, session_id: str | None = None) -> None:
|
|
274
|
+
"""
|
|
275
|
+
Sets a new session ID and notifies all clients, causing them to reset their state.
|
|
276
|
+
If no session ID is provided, generates a new one based on the current timestamp.
|
|
277
|
+
If the server has been stopped, this has no effect.
|
|
278
|
+
"""
|
|
279
|
+
...
|
|
280
|
+
|
|
281
|
+
def broadcast_time(self, timestamp_nanos: int) -> None:
|
|
282
|
+
"""
|
|
283
|
+
Publishes the current server timestamp to all clients.
|
|
284
|
+
If the server has been stopped, this has no effect.
|
|
285
|
+
"""
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
def publish_parameter_values(self, parameters: list[Parameter]) -> None:
|
|
289
|
+
"""Publishes parameter values to all subscribed clients."""
|
|
290
|
+
...
|
|
291
|
+
|
|
292
|
+
def publish_status(
|
|
293
|
+
self, message: str, level: StatusLevel, id: str | None = None
|
|
294
|
+
) -> None:
|
|
295
|
+
"""
|
|
296
|
+
Send a status message to all clients. If the server has been stopped, this has no effect.
|
|
297
|
+
"""
|
|
298
|
+
...
|
|
299
|
+
|
|
300
|
+
def remove_status(self, ids: list[str]) -> None:
|
|
301
|
+
"""
|
|
302
|
+
Remove status messages by id from all clients. If the server has been stopped, this has no
|
|
303
|
+
effect.
|
|
304
|
+
"""
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
def add_services(self, services: list[Service]) -> None:
|
|
308
|
+
"""Add services to the server."""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
def remove_services(self, names: list[str]) -> None:
|
|
312
|
+
"""Removes services that were previously advertised."""
|
|
313
|
+
...
|
|
314
|
+
|
|
315
|
+
def publish_connection_graph(self, graph: ConnectionGraph) -> None:
|
|
316
|
+
"""
|
|
317
|
+
Publishes a connection graph update to all subscribed clients. An update is published to
|
|
318
|
+
clients as a difference from the current graph to the replacement graph. When a client first
|
|
319
|
+
subscribes to connection graph updates, it receives the current graph.
|
|
320
|
+
"""
|
|
321
|
+
...
|
|
Binary file
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
import struct
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Generator, List
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from foxglove import Channel, open_mcap
|
|
11
|
+
from foxglove.channels import PointCloudChannel, SceneUpdateChannel
|
|
12
|
+
from foxglove.schemas import (
|
|
13
|
+
Color,
|
|
14
|
+
CubePrimitive,
|
|
15
|
+
Duration,
|
|
16
|
+
PackedElementField,
|
|
17
|
+
PackedElementFieldNumericType,
|
|
18
|
+
PointCloud,
|
|
19
|
+
Pose,
|
|
20
|
+
Quaternion,
|
|
21
|
+
SceneEntity,
|
|
22
|
+
SceneUpdate,
|
|
23
|
+
Vector3,
|
|
24
|
+
)
|
|
25
|
+
from pytest_benchmark.fixture import BenchmarkFixture # type: ignore
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def tmp_mcap(tmpdir: os.PathLike[str]) -> Generator[Path, None, None]:
|
|
30
|
+
dir = Path(tmpdir)
|
|
31
|
+
mcap = dir / "test.mcap"
|
|
32
|
+
yield mcap
|
|
33
|
+
mcap.unlink()
|
|
34
|
+
dir.rmdir()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_entities(entity_count: int) -> List[SceneEntity]:
|
|
38
|
+
assert entity_count > 0
|
|
39
|
+
return [
|
|
40
|
+
SceneEntity(
|
|
41
|
+
id=f"box_{i}",
|
|
42
|
+
frame_id="box",
|
|
43
|
+
lifetime=Duration(10, nsec=int(100 * 1e6)),
|
|
44
|
+
cubes=[
|
|
45
|
+
CubePrimitive(
|
|
46
|
+
pose=Pose(
|
|
47
|
+
position=Vector3(x=0.0, y=0.0, z=3.0),
|
|
48
|
+
orientation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0),
|
|
49
|
+
),
|
|
50
|
+
size=Vector3(x=1.0, y=1.0, z=1.0),
|
|
51
|
+
color=Color(r=1.0, g=0.0, b=0.0, a=1.0),
|
|
52
|
+
)
|
|
53
|
+
],
|
|
54
|
+
)
|
|
55
|
+
for i in range(entity_count)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def write_scene_entity_mcap(
|
|
60
|
+
tmp_mcap: Path, channel: SceneUpdateChannel, entities: List[SceneEntity]
|
|
61
|
+
) -> None:
|
|
62
|
+
with open_mcap(tmp_mcap, allow_overwrite=True):
|
|
63
|
+
for _ in range(100):
|
|
64
|
+
channel.log(SceneUpdate(entities=entities))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def make_point_cloud(point_count: int) -> PointCloud:
|
|
68
|
+
"""
|
|
69
|
+
https://foxglove.dev/blog/visualizing-point-clouds-with-custom-colors
|
|
70
|
+
"""
|
|
71
|
+
point_struct = struct.Struct("<fffBBBB")
|
|
72
|
+
f32 = PackedElementFieldNumericType.Float32
|
|
73
|
+
u32 = PackedElementFieldNumericType.Uint32
|
|
74
|
+
|
|
75
|
+
t = time.time()
|
|
76
|
+
count = math.ceil(math.sqrt(point_count))
|
|
77
|
+
points = [
|
|
78
|
+
(x + math.cos(t + y / 5), y, 0) for x in range(count) for y in range(count)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
buffer = bytearray(point_struct.size * len(points))
|
|
82
|
+
for i, point in enumerate(points):
|
|
83
|
+
x, y, z = point
|
|
84
|
+
r = g = b = a = 128
|
|
85
|
+
point_struct.pack_into(buffer, i * point_struct.size, x, y, z, b, g, r, a)
|
|
86
|
+
|
|
87
|
+
return PointCloud(
|
|
88
|
+
frame_id="points",
|
|
89
|
+
pose=Pose(
|
|
90
|
+
position=Vector3(x=0, y=0, z=0),
|
|
91
|
+
orientation=Quaternion(x=0, y=0, z=0, w=1),
|
|
92
|
+
),
|
|
93
|
+
point_stride=16, # 4 fields * 4 bytes
|
|
94
|
+
fields=[
|
|
95
|
+
PackedElementField(name="x", offset=0, type=f32),
|
|
96
|
+
PackedElementField(name="y", offset=4, type=f32),
|
|
97
|
+
PackedElementField(name="z", offset=8, type=f32),
|
|
98
|
+
PackedElementField(name="rgba", offset=12, type=u32),
|
|
99
|
+
],
|
|
100
|
+
data=bytes(buffer),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def write_point_cloud_mcap(
|
|
105
|
+
tmp_mcap: Path, channel: PointCloudChannel, point_cloud: PointCloud
|
|
106
|
+
) -> None:
|
|
107
|
+
with open_mcap(tmp_mcap, allow_overwrite=True):
|
|
108
|
+
for _ in range(10):
|
|
109
|
+
channel.log(point_cloud)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def write_untyped_channel_mcap(
|
|
113
|
+
tmp_mcap: Path, channel: Channel, messages: List[bytes]
|
|
114
|
+
) -> None:
|
|
115
|
+
with open_mcap(tmp_mcap, allow_overwrite=True):
|
|
116
|
+
for message in messages:
|
|
117
|
+
channel.log(message)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.benchmark
|
|
121
|
+
@pytest.mark.parametrize("entity_count", [1, 2, 4, 8])
|
|
122
|
+
def test_write_scene_update_mcap(
|
|
123
|
+
benchmark: BenchmarkFixture,
|
|
124
|
+
entity_count: int,
|
|
125
|
+
tmp_mcap: Path,
|
|
126
|
+
) -> None:
|
|
127
|
+
channel = SceneUpdateChannel(f"/scene_{entity_count}")
|
|
128
|
+
entities = build_entities(entity_count)
|
|
129
|
+
benchmark(write_scene_entity_mcap, tmp_mcap, channel, entities)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.benchmark
|
|
133
|
+
@pytest.mark.parametrize("point_count", [100, 1000, 10000])
|
|
134
|
+
def test_write_point_cloud_mcap(
|
|
135
|
+
benchmark: BenchmarkFixture,
|
|
136
|
+
point_count: int,
|
|
137
|
+
tmp_mcap: Path,
|
|
138
|
+
) -> None:
|
|
139
|
+
print("test_write_point_cloud_mcap")
|
|
140
|
+
channel = PointCloudChannel(f"/point_cloud_{point_count}")
|
|
141
|
+
point_cloud = make_point_cloud(point_count)
|
|
142
|
+
benchmark(write_point_cloud_mcap, tmp_mcap, channel, point_cloud)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.benchmark
|
|
146
|
+
@pytest.mark.parametrize("message_count", [10, 100, 1000])
|
|
147
|
+
def test_write_untyped_channel_mcap(
|
|
148
|
+
benchmark: BenchmarkFixture,
|
|
149
|
+
message_count: int,
|
|
150
|
+
tmp_mcap: Path,
|
|
151
|
+
) -> None:
|
|
152
|
+
channel = Channel(
|
|
153
|
+
f"/untyped_{message_count}",
|
|
154
|
+
schema={"type": "object", "additionalProperties": True},
|
|
155
|
+
)
|
|
156
|
+
messages = [
|
|
157
|
+
json.dumps({"message": f"hello_{i}"}).encode("utf-8")
|
|
158
|
+
for i in range(message_count)
|
|
159
|
+
]
|
|
160
|
+
benchmark(write_untyped_channel_mcap, tmp_mcap, channel, messages)
|