mcap-codec-support 0.2.0__py3-none-any.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.
@@ -0,0 +1,473 @@
1
+ """Point cloud decoder factories.
2
+
3
+ Also exposes :class:`Pointcloud2DecoderFactory`, a small_mcap decoder
4
+ factory that decodes ``sensor_msgs/PointCloud2`` payloads directly into a
5
+ structured numpy array via :func:`pointcloud2.read_points`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from types import SimpleNamespace
11
+ from typing import TYPE_CHECKING, Any, Protocol
12
+
13
+ from mcap_codec_support.pointcloud.schemas import (
14
+ COMPRESSED_POINTCLOUD2_SCHEMA,
15
+ FOXGLOVE_COMPRESSED_POINTCLOUD_SCHEMA,
16
+ POINTCLOUD2_SCHEMAS,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable
21
+
22
+ import numpy as np
23
+ from pointcloud2 import HeaderMsg, PointFieldDict, PointFieldMsg
24
+ from pointcloud2.messages import Stamp
25
+ from pureini import PointcloudDecoder
26
+ from small_mcap import Schema
27
+
28
+ from mcap_codec_support._messages import Header, Pointcloud2Dict
29
+
30
+
31
+ class _RosCompressedPointcloud2Msg(Protocol):
32
+ """point_cloud_interfaces/msg/CompressedPointCloud2."""
33
+
34
+ header: HeaderMsg
35
+ height: int
36
+ width: int
37
+ fields: list[PointFieldMsg]
38
+ is_bigendian: bool
39
+ point_step: int
40
+ row_step: int
41
+ is_dense: bool
42
+ format: str | bytes
43
+ compressed_data: bytes
44
+
45
+
46
+ class _FoxgloveCompressedPointcloudMsg(Protocol):
47
+ """foxglove_msgs/msg/CompressedPointCloud — flattened header."""
48
+
49
+ timestamp: Stamp
50
+ frame_id: str
51
+ format: str | bytes
52
+ data: bytes
53
+
54
+
55
+ _COMPRESSED_POINTCLOUD_SCHEMAS = {
56
+ COMPRESSED_POINTCLOUD2_SCHEMA,
57
+ FOXGLOVE_COMPRESSED_POINTCLOUD_SCHEMA,
58
+ }
59
+
60
+
61
+ def _pointcloud_dict_to_array(cloud_dict: Pointcloud2Dict) -> np.ndarray:
62
+ from pointcloud2 import read_points # noqa: PLC0415
63
+
64
+ ns = SimpleNamespace(**{k: v for k, v in cloud_dict.items() if k != "header"})
65
+ return read_points(ns, skip_nans=True)
66
+
67
+
68
+ def _decode_format(fmt: str | bytes) -> str:
69
+ if isinstance(fmt, bytes):
70
+ fmt = fmt.decode()
71
+ return fmt.strip().lower()
72
+
73
+
74
+ def _as_bytes(payload: bytes | bytearray | memoryview) -> bytes:
75
+ return payload if isinstance(payload, bytes) else bytes(payload)
76
+
77
+
78
+ def _header_from_ros_msg(msg: _RosCompressedPointcloud2Msg) -> Header:
79
+ stamp = msg.header.stamp
80
+ return {
81
+ "stamp": {"sec": stamp.sec, "nanosec": stamp.nanosec},
82
+ "frame_id": msg.header.frame_id,
83
+ }
84
+
85
+
86
+ def _header_from_foxglove_msg(msg: _FoxgloveCompressedPointcloudMsg) -> Header:
87
+ return {
88
+ "stamp": {"sec": msg.timestamp.sec, "nanosec": msg.timestamp.nanosec},
89
+ "frame_id": msg.frame_id,
90
+ }
91
+
92
+
93
+ def _fields_from_msg(fields: list[PointFieldMsg]) -> list[PointFieldDict]:
94
+ return [
95
+ {
96
+ "name": field.name,
97
+ "offset": field.offset,
98
+ "datatype": field.datatype,
99
+ "count": field.count,
100
+ }
101
+ for field in fields
102
+ ]
103
+
104
+
105
+ def _fields_from_cloudini_info(info: Any) -> list[PointFieldDict]:
106
+ return [
107
+ {
108
+ "name": field.name,
109
+ "offset": field.offset,
110
+ "datatype": int(field.type),
111
+ "count": 1,
112
+ }
113
+ for field in info.fields
114
+ ]
115
+
116
+
117
+ def _pointcloud2_from_cloudini_ros(
118
+ msg: _RosCompressedPointcloud2Msg, pc_decoder: PointcloudDecoder
119
+ ) -> Pointcloud2Dict:
120
+ raw_bytes, _info = pc_decoder.decode(_as_bytes(msg.compressed_data))
121
+ return {
122
+ "header": _header_from_ros_msg(msg),
123
+ "height": int(msg.height),
124
+ "width": int(msg.width),
125
+ "fields": _fields_from_msg(msg.fields),
126
+ "is_bigendian": bool(msg.is_bigendian),
127
+ "point_step": int(msg.point_step),
128
+ "row_step": int(msg.row_step),
129
+ "data": raw_bytes,
130
+ "is_dense": bool(msg.is_dense),
131
+ }
132
+
133
+
134
+ def _pointcloud2_from_cloudini_foxglove(
135
+ msg: _FoxgloveCompressedPointcloudMsg, pc_decoder: PointcloudDecoder
136
+ ) -> Pointcloud2Dict:
137
+ raw_bytes, info = pc_decoder.decode(_as_bytes(msg.data))
138
+ point_step = info.point_step
139
+ width = info.width
140
+ return {
141
+ "header": _header_from_foxglove_msg(msg),
142
+ "height": info.height,
143
+ "width": width,
144
+ "fields": _fields_from_cloudini_info(info),
145
+ "is_bigendian": False,
146
+ "point_step": point_step,
147
+ "row_step": point_step * width,
148
+ "data": raw_bytes,
149
+ "is_dense": True,
150
+ }
151
+
152
+
153
+ def _decode_draco_payload(payload: bytes, header: Header) -> Pointcloud2Dict:
154
+ import DracoPy # noqa: PLC0415
155
+ import numpy as np # noqa: PLC0415
156
+ from pointcloud2 import fields_from_dtype # noqa: PLC0415
157
+
158
+ decoded = DracoPy.decode(_as_bytes(payload))
159
+ positions = np.asarray(decoded.points, dtype=np.float32)
160
+ if positions.ndim != 2 or positions.shape[1] < 2:
161
+ raise ValueError("Draco point cloud does not contain at least two coordinate fields")
162
+
163
+ point_count = int(positions.shape[0])
164
+ columns: list[tuple[str, np.ndarray]] = [
165
+ ("x", positions[:, 0].astype(np.float32, copy=False)),
166
+ ("y", positions[:, 1].astype(np.float32, copy=False)),
167
+ ]
168
+ if positions.shape[1] >= 3:
169
+ columns.append(("z", positions[:, 2].astype(np.float32, copy=False)))
170
+ else:
171
+ columns.append(("z", np.zeros(point_count, dtype=np.float32)))
172
+
173
+ for index, attr in enumerate(decoded.attributes):
174
+ if attr.get("attribute_type") == DracoPy.AttributeType.POSITION:
175
+ continue
176
+ name = attr.get("name") or f"attribute_{attr.get('unique_id', index)}"
177
+ if name in {"x", "y", "z"}:
178
+ continue
179
+ values = attr.get("data")
180
+ if values is None:
181
+ continue
182
+ arr = np.asarray(values)
183
+ if arr.shape[0] != point_count:
184
+ continue
185
+ if arr.dtype.kind == "f":
186
+ arr = arr.astype(np.float32, copy=False)
187
+ elif arr.dtype == np.uint8:
188
+ arr = arr.astype(np.uint8, copy=False)
189
+ elif arr.dtype == np.uint16:
190
+ arr = arr.astype(np.uint16, copy=False)
191
+ elif arr.dtype == np.uint32:
192
+ arr = arr.astype(np.uint32, copy=False)
193
+ else:
194
+ continue
195
+
196
+ if arr.ndim == 1 or arr.shape[1] == 1:
197
+ columns.append((str(name), arr.reshape(point_count)))
198
+ else:
199
+ columns.extend(
200
+ (f"{name}_{component}", arr[:, component]) for component in range(arr.shape[1])
201
+ )
202
+
203
+ dtype = np.dtype([(name, values.dtype) for name, values in columns])
204
+ point_data = np.empty(point_count, dtype=dtype)
205
+ for name, values in columns:
206
+ point_data[name] = values
207
+
208
+ fields: list[PointFieldDict] = [
209
+ {"name": field.name, "offset": field.offset, "datatype": field.datatype, "count": 1}
210
+ for field in fields_from_dtype(point_data.dtype)
211
+ ]
212
+
213
+ return {
214
+ "header": header,
215
+ "height": 1,
216
+ "width": point_count,
217
+ "fields": fields,
218
+ "is_bigendian": False,
219
+ "point_step": point_data.dtype.itemsize,
220
+ "row_step": point_data.dtype.itemsize * point_count,
221
+ "data": point_data.tobytes(),
222
+ "is_dense": True,
223
+ }
224
+
225
+
226
+ def _pointcloud2_from_draco_ros(msg: _RosCompressedPointcloud2Msg) -> Pointcloud2Dict:
227
+ return _decode_draco_payload(msg.compressed_data, _header_from_ros_msg(msg))
228
+
229
+
230
+ def _pointcloud2_from_draco_foxglove(msg: _FoxgloveCompressedPointcloudMsg) -> Pointcloud2Dict:
231
+ return _decode_draco_payload(msg.data, _header_from_foxglove_msg(msg))
232
+
233
+
234
+ # The two compressed-pointcloud schemas share ``format`` but differ in the
235
+ # payload field name (``compressed_data`` vs ``data``); branching on attribute
236
+ # presence is the only stable runtime discriminator.
237
+ def _is_ros_style_compressed_msg(
238
+ msg: _RosCompressedPointcloud2Msg | _FoxgloveCompressedPointcloudMsg,
239
+ ) -> bool:
240
+ return hasattr(msg, "compressed_data")
241
+
242
+
243
+ class CloudiniPointCloudDecompressFactory:
244
+ """Schema-only decoder factory: Cloudini compressed point cloud → PointCloud2.
245
+
246
+ Stateless — safe to share across all channels.
247
+ """
248
+
249
+ def __init__(self) -> None:
250
+ from mcap_ros2_support_fast.decoder import DecoderFactory # noqa: PLC0415
251
+ from pureini import PointcloudDecoder # noqa: PLC0415
252
+
253
+ self._cdr_factory = DecoderFactory()
254
+ self._pc_decoder: PointcloudDecoder = PointcloudDecoder()
255
+
256
+ def _decompress(
257
+ self,
258
+ msg: _RosCompressedPointcloud2Msg | _FoxgloveCompressedPointcloudMsg,
259
+ ) -> Pointcloud2Dict:
260
+ if _decode_format(msg.format) != "cloudini":
261
+ raise ValueError(f"unsupported compressed point cloud format: {msg.format!r}")
262
+ if _is_ros_style_compressed_msg(msg):
263
+ return _pointcloud2_from_cloudini_ros(msg, self._pc_decoder) # ty: ignore[invalid-argument-type]
264
+ return _pointcloud2_from_cloudini_foxglove(msg, self._pc_decoder) # ty: ignore[invalid-argument-type]
265
+
266
+ def decoder_for(
267
+ self,
268
+ message_encoding: str,
269
+ schema: Schema | None,
270
+ ) -> Callable[[bytes | memoryview], Pointcloud2Dict] | None:
271
+ if schema is None or schema.name not in _COMPRESSED_POINTCLOUD_SCHEMAS:
272
+ return None
273
+ cdr_decoder = self._cdr_factory.decoder_for(message_encoding, schema)
274
+ if cdr_decoder is None:
275
+ return None
276
+
277
+ def _decode(data: bytes | memoryview) -> Pointcloud2Dict:
278
+ return self._decompress(cdr_decoder(data))
279
+
280
+ return _decode
281
+
282
+
283
+ class DracoPointCloudDecompressFactory:
284
+ """Schema-only decoder factory: Draco compressed point cloud → PointCloud2."""
285
+
286
+ def __init__(self) -> None:
287
+ from mcap_ros2_support_fast.decoder import DecoderFactory # noqa: PLC0415
288
+
289
+ self._cdr_factory = DecoderFactory()
290
+
291
+ def _decompress(
292
+ self,
293
+ msg: _RosCompressedPointcloud2Msg | _FoxgloveCompressedPointcloudMsg,
294
+ ) -> Pointcloud2Dict:
295
+ if _decode_format(msg.format) != "draco":
296
+ raise ValueError(f"unsupported compressed point cloud format: {msg.format!r}")
297
+ if _is_ros_style_compressed_msg(msg):
298
+ return _pointcloud2_from_draco_ros(msg) # ty: ignore[invalid-argument-type]
299
+ return _pointcloud2_from_draco_foxglove(msg) # ty: ignore[invalid-argument-type]
300
+
301
+ def decoder_for(
302
+ self,
303
+ message_encoding: str,
304
+ schema: Schema | None,
305
+ ) -> Callable[[bytes | memoryview], Pointcloud2Dict] | None:
306
+ if schema is None or schema.name not in _COMPRESSED_POINTCLOUD_SCHEMAS:
307
+ return None
308
+ cdr_decoder = self._cdr_factory.decoder_for(message_encoding, schema)
309
+ if cdr_decoder is None:
310
+ return None
311
+
312
+ def _decode(data: bytes | memoryview) -> Pointcloud2Dict:
313
+ return self._decompress(cdr_decoder(data))
314
+
315
+ return _decode
316
+
317
+
318
+ class CompressedPointCloudDecompressFactory:
319
+ """Decode either compressed point cloud schema by dispatching on ``format``."""
320
+
321
+ def __init__(self) -> None:
322
+ from mcap_ros2_support_fast.decoder import DecoderFactory # noqa: PLC0415
323
+
324
+ self._cdr_factory = DecoderFactory()
325
+ self._cloudini_decoder: PointcloudDecoder | None = None
326
+
327
+ def _ensure_cloudini_decoder(self) -> PointcloudDecoder:
328
+ if self._cloudini_decoder is None:
329
+ from pureini import PointcloudDecoder # noqa: PLC0415
330
+
331
+ self._cloudini_decoder = PointcloudDecoder()
332
+ return self._cloudini_decoder
333
+
334
+ def _decompress(
335
+ self,
336
+ msg: _RosCompressedPointcloud2Msg | _FoxgloveCompressedPointcloudMsg,
337
+ ) -> Pointcloud2Dict:
338
+ fmt = _decode_format(msg.format)
339
+ is_ros = _is_ros_style_compressed_msg(msg)
340
+ if fmt == "cloudini":
341
+ decoder = self._ensure_cloudini_decoder()
342
+ if is_ros:
343
+ return _pointcloud2_from_cloudini_ros(msg, decoder) # ty: ignore[invalid-argument-type]
344
+ return _pointcloud2_from_cloudini_foxglove(msg, decoder) # ty: ignore[invalid-argument-type]
345
+ if fmt == "draco":
346
+ if is_ros:
347
+ return _pointcloud2_from_draco_ros(msg) # ty: ignore[invalid-argument-type]
348
+ return _pointcloud2_from_draco_foxglove(msg) # ty: ignore[invalid-argument-type]
349
+ raise ValueError(f"unsupported compressed point cloud format: {msg.format!r}")
350
+
351
+ def decoder_for(
352
+ self,
353
+ message_encoding: str,
354
+ schema: Schema | None,
355
+ ) -> Callable[[bytes | memoryview], Pointcloud2Dict] | None:
356
+ if schema is None or schema.name not in _COMPRESSED_POINTCLOUD_SCHEMAS:
357
+ return None
358
+ cdr_decoder = self._cdr_factory.decoder_for(message_encoding, schema)
359
+ if cdr_decoder is None:
360
+ return None
361
+
362
+ def _decode(data: bytes | memoryview) -> Pointcloud2Dict:
363
+ return self._decompress(cdr_decoder(data))
364
+
365
+ return _decode
366
+
367
+
368
+ class CloudiniCompressedPointcloud2DecoderFactory:
369
+ """Decode Cloudini compressed point clouds to a structured numpy array.
370
+
371
+ Chains :class:`CloudiniPointCloudDecompressFactory` with
372
+ :func:`pointcloud2.read_points`, so the output has the same shape as the
373
+ regular PointCloud2 path — a numpy structured array ready to be packed
374
+ into an Arrow ``LIST<STRUCT<...>>``.
375
+
376
+ Requires ``pureini``; construction raises ``ImportError`` if missing.
377
+ """
378
+
379
+ def __init__(self) -> None:
380
+ self._decompress_factory = CloudiniPointCloudDecompressFactory()
381
+
382
+ def decoder_for(
383
+ self,
384
+ message_encoding: str,
385
+ schema: Schema | None,
386
+ ) -> Callable[[bytes | memoryview], np.ndarray] | None:
387
+ decoder = self._decompress_factory.decoder_for(message_encoding, schema)
388
+ if decoder is None:
389
+ return None
390
+
391
+ def _decode(data: bytes | memoryview) -> np.ndarray:
392
+ return _pointcloud_dict_to_array(decoder(data))
393
+
394
+ return _decode
395
+
396
+
397
+ class DracoCompressedPointcloudDecoderFactory:
398
+ """Decode Draco compressed point clouds to a structured numpy array."""
399
+
400
+ def __init__(self) -> None:
401
+ self._decompress_factory = DracoPointCloudDecompressFactory()
402
+
403
+ def decoder_for(
404
+ self,
405
+ message_encoding: str,
406
+ schema: Schema | None,
407
+ ) -> Callable[[bytes | memoryview], np.ndarray] | None:
408
+ decoder = self._decompress_factory.decoder_for(message_encoding, schema)
409
+ if decoder is None:
410
+ return None
411
+
412
+ def _decode(data: bytes | memoryview) -> np.ndarray:
413
+ return _pointcloud_dict_to_array(decoder(data))
414
+
415
+ return _decode
416
+
417
+
418
+ class CompressedPointCloudDecoderFactory:
419
+ """Decode either compressed point cloud schema to a structured numpy array."""
420
+
421
+ def __init__(self) -> None:
422
+ self._decompress_factory = CompressedPointCloudDecompressFactory()
423
+
424
+ def decoder_for(
425
+ self,
426
+ message_encoding: str,
427
+ schema: Schema | None,
428
+ ) -> Callable[[bytes | memoryview], np.ndarray] | None:
429
+ decoder = self._decompress_factory.decoder_for(message_encoding, schema)
430
+ if decoder is None:
431
+ return None
432
+
433
+ def _decode(data: bytes | memoryview) -> np.ndarray:
434
+ return _pointcloud_dict_to_array(decoder(data))
435
+
436
+ return _decode
437
+
438
+
439
+ PointCloudDecompressFactory = CompressedPointCloudDecompressFactory
440
+ CompressedPointcloud2DecoderFactory = CompressedPointCloudDecoderFactory
441
+
442
+
443
+ _SCHEMA_ENCODING_ROS2 = "ros2msg"
444
+ _MESSAGE_ENCODING_CDR = "cdr"
445
+
446
+
447
+ class Pointcloud2DecoderFactory:
448
+ def __init__(self) -> None:
449
+ from mcap_ros2_support_fast.decoder import DecoderFactory # noqa: PLC0415
450
+
451
+ self._ros2_decoder_factory = DecoderFactory()
452
+
453
+ def decoder_for(
454
+ self, message_encoding: str, schema: Schema | None
455
+ ) -> Callable[[bytes | memoryview], np.ndarray] | None:
456
+ if (
457
+ message_encoding != _MESSAGE_ENCODING_CDR
458
+ or schema is None
459
+ or schema.encoding != _SCHEMA_ENCODING_ROS2
460
+ or schema.name not in POINTCLOUD2_SCHEMAS
461
+ ):
462
+ return None
463
+
464
+ decoder = self._ros2_decoder_factory.decoder_for(message_encoding, schema)
465
+ if decoder is None:
466
+ return None
467
+
468
+ from pointcloud2 import read_points # noqa: PLC0415
469
+
470
+ def _decode(data: bytes | memoryview) -> np.ndarray:
471
+ return read_points(decoder(data), skip_nans=True)
472
+
473
+ return _decode
@@ -0,0 +1,111 @@
1
+ """Point cloud ROS schema names and definitions."""
2
+
3
+ POINTCLOUD2_SCHEMAS = {"sensor_msgs/msg/PointCloud2", "sensor_msgs/PointCloud2"}
4
+ COMPRESSED_POINTCLOUD2_SCHEMA = "point_cloud_interfaces/msg/CompressedPointCloud2"
5
+ CLOUDINI_COMPRESSED_POINTCLOUD2_SCHEMA = COMPRESSED_POINTCLOUD2_SCHEMA
6
+ FOXGLOVE_COMPRESSED_POINTCLOUD_SCHEMA = "foxglove_msgs/msg/CompressedPointCloud"
7
+
8
+ COMPRESSED_POINTCLOUD2 = """\
9
+ std_msgs/Header header
10
+ uint32 height
11
+ uint32 width
12
+ sensor_msgs/PointField[] fields
13
+ bool is_bigendian
14
+ uint32 point_step
15
+ uint32 row_step
16
+ uint8[] compressed_data
17
+ bool is_dense
18
+ string format
19
+
20
+ ================================================================================
21
+ MSG: sensor_msgs/PointField
22
+ uint8 INT8 = 1
23
+ uint8 UINT8 = 2
24
+ uint8 INT16 = 3
25
+ uint8 UINT16 = 4
26
+ uint8 INT32 = 5
27
+ uint8 UINT32 = 6
28
+ uint8 FLOAT32 = 7
29
+ uint8 FLOAT64 = 8
30
+ string name
31
+ uint32 offset
32
+ uint8 datatype
33
+ uint32 count
34
+
35
+ ================================================================================
36
+ MSG: std_msgs/Header
37
+ builtin_interfaces/Time stamp
38
+ string frame_id
39
+
40
+ ================================================================================
41
+ MSG: builtin_interfaces/Time
42
+ int32 sec
43
+ uint32 nanosec"""
44
+
45
+ CLOUDINI_COMPRESSED_POINTCLOUD2 = COMPRESSED_POINTCLOUD2
46
+
47
+ POINTCLOUD2 = """\
48
+ std_msgs/Header header
49
+ uint32 height
50
+ uint32 width
51
+ sensor_msgs/PointField[] fields
52
+ bool is_bigendian
53
+ uint32 point_step
54
+ uint32 row_step
55
+ uint8[] data
56
+ bool is_dense
57
+
58
+ ================================================================================
59
+ MSG: sensor_msgs/PointField
60
+ uint8 INT8 = 1
61
+ uint8 UINT8 = 2
62
+ uint8 INT16 = 3
63
+ uint8 UINT16 = 4
64
+ uint8 INT32 = 5
65
+ uint8 UINT32 = 6
66
+ uint8 FLOAT32 = 7
67
+ uint8 FLOAT64 = 8
68
+ string name
69
+ uint32 offset
70
+ uint8 datatype
71
+ uint32 count
72
+
73
+ ================================================================================
74
+ MSG: std_msgs/Header
75
+ builtin_interfaces/Time stamp
76
+ string frame_id
77
+
78
+ ================================================================================
79
+ MSG: builtin_interfaces/Time
80
+ int32 sec
81
+ uint32 nanosec"""
82
+
83
+ FOXGLOVE_COMPRESSED_POINTCLOUD = """\
84
+ builtin_interfaces/Time timestamp
85
+ string frame_id
86
+ geometry_msgs/Pose pose
87
+ uint8[] data
88
+ string format
89
+
90
+ ================================================================================
91
+ MSG: geometry_msgs/Pose
92
+ geometry_msgs/Point position
93
+ geometry_msgs/Quaternion orientation
94
+
95
+ ================================================================================
96
+ MSG: geometry_msgs/Point
97
+ float64 x
98
+ float64 y
99
+ float64 z
100
+
101
+ ================================================================================
102
+ MSG: geometry_msgs/Quaternion
103
+ float64 x
104
+ float64 y
105
+ float64 z
106
+ float64 w
107
+
108
+ ================================================================================
109
+ MSG: builtin_interfaces/Time
110
+ int32 sec
111
+ uint32 nanosec"""
File without changes
@@ -0,0 +1,55 @@
1
+ """Video MCAP factories, schema constants, and backend helpers."""
2
+
3
+ from mcap_codec_support._protocols import VideoCompressionBackend
4
+ from mcap_codec_support.video.common import (
5
+ EncoderBackend,
6
+ EncoderConfig,
7
+ EncoderMode,
8
+ VideoCodec,
9
+ VideoEncoderError,
10
+ calculate_downscale_dimensions,
11
+ get_software_encoder,
12
+ raw_image_to_array,
13
+ )
14
+ from mcap_codec_support.video.compression import (
15
+ create_video_compression_backend,
16
+ encode_raw_image_to_jpeg,
17
+ prefetch_image_decodes,
18
+ )
19
+ from mcap_codec_support.video.factories import VideoDecompressFactory
20
+ from mcap_codec_support.video.file_writer import (
21
+ VideoFileWriterSession,
22
+ create_video_file_writer,
23
+ )
24
+ from mcap_codec_support.video.schemas import (
25
+ COMPRESSED_IMAGE,
26
+ COMPRESSED_VIDEO_SCHEMA,
27
+ FOXGLOVE_COMPRESSED_VIDEO,
28
+ IMAGE,
29
+ IMAGE_SCHEMAS,
30
+ RAW_SCHEMAS,
31
+ )
32
+
33
+ __all__ = [
34
+ "COMPRESSED_IMAGE",
35
+ "COMPRESSED_VIDEO_SCHEMA",
36
+ "FOXGLOVE_COMPRESSED_VIDEO",
37
+ "IMAGE",
38
+ "IMAGE_SCHEMAS",
39
+ "RAW_SCHEMAS",
40
+ "EncoderBackend",
41
+ "EncoderConfig",
42
+ "EncoderMode",
43
+ "VideoCodec",
44
+ "VideoCompressionBackend",
45
+ "VideoDecompressFactory",
46
+ "VideoEncoderError",
47
+ "VideoFileWriterSession",
48
+ "calculate_downscale_dimensions",
49
+ "create_video_compression_backend",
50
+ "create_video_file_writer",
51
+ "encode_raw_image_to_jpeg",
52
+ "get_software_encoder",
53
+ "prefetch_image_decodes",
54
+ "raw_image_to_array",
55
+ ]