x4d-devkit 0.6.0__tar.gz → 0.7.0__tar.gz
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.
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/PKG-INFO +1 -1
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/pyproject.toml +1 -1
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_coordinate_frames.py +5 -2
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_models.py +79 -2
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/__init__.py +4 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/core/__init__.py +4 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/core/loader.py +12 -3
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/core/models.py +97 -6
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/LICENSE +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/README.md +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/setup.cfg +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_fusion.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_loader.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_matching.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_nuscenes_converter.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_token.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_transform.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_v06_api_additions.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/tests/test_validation.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/converters/__init__.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/converters/nuscenes.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/fusion.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/SOURCES.txt +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.6.0 → x4d_devkit-0.7.0}/x4d_devkit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "x4d-devkit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "X-4D dataset format SDK — load, validate, evaluate, and convert autonomous driving datasets"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -38,11 +38,14 @@ def _make_synthetic_clip(tmp_path: Path) -> Path:
|
|
|
38
38
|
"sensors": {
|
|
39
39
|
"LIDAR_TOP": {
|
|
40
40
|
"modality": "lidar",
|
|
41
|
-
"point_format": {
|
|
41
|
+
"point_format": {
|
|
42
|
+
"fields": ["x", "y", "z", "intensity"],
|
|
43
|
+
"types": ["float32"] * 4,
|
|
44
|
+
"bytes_per_point": 16,
|
|
45
|
+
},
|
|
42
46
|
},
|
|
43
47
|
"CAM_FRONT": {
|
|
44
48
|
"modality": "camera",
|
|
45
|
-
"point_format": None
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
}))
|
|
@@ -9,6 +9,8 @@ from x4d_devkit.core.models import (
|
|
|
9
9
|
SampleData,
|
|
10
10
|
Annotation,
|
|
11
11
|
Instance,
|
|
12
|
+
PointFormat,
|
|
13
|
+
SensorInfo,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -16,17 +18,21 @@ class TestClipMeta:
|
|
|
16
18
|
def test_construction(self):
|
|
17
19
|
meta = ClipMeta(
|
|
18
20
|
clip_id="20180724_n015_scene-0061_000",
|
|
19
|
-
schema_version="0.
|
|
21
|
+
schema_version="0.5",
|
|
20
22
|
session_id="n015-2018-07-24-11-22-45+0800",
|
|
21
23
|
vehicle_id="n015",
|
|
22
24
|
capture_time_utc="2018-07-24T00:00:00Z",
|
|
23
25
|
duration_s=19.5,
|
|
24
26
|
num_keyframes=39,
|
|
25
27
|
num_sweeps=305,
|
|
26
|
-
sensors={
|
|
28
|
+
sensors={
|
|
29
|
+
"CAM_FRONT": SensorInfo(modality="camera", resolution=(1600, 900)),
|
|
30
|
+
},
|
|
27
31
|
)
|
|
28
32
|
assert meta.clip_id == "20180724_n015_scene-0061_000"
|
|
29
33
|
assert meta.num_keyframes == 39
|
|
34
|
+
assert meta.sensors["CAM_FRONT"].modality == "camera"
|
|
35
|
+
assert meta.sensors["CAM_FRONT"].resolution == (1600, 900)
|
|
30
36
|
|
|
31
37
|
def test_frozen(self):
|
|
32
38
|
meta = ClipMeta(
|
|
@@ -153,3 +159,74 @@ class TestInstance:
|
|
|
153
159
|
first_annotation_token="a1", last_annotation_token="a5",
|
|
154
160
|
)
|
|
155
161
|
assert inst.num_annotations == 5
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TestPointFormat:
|
|
165
|
+
def test_construction_normalises_to_tuples(self):
|
|
166
|
+
pf = PointFormat(
|
|
167
|
+
fields=["x", "y", "z", "intensity", "time"],
|
|
168
|
+
types=["float32"] * 5,
|
|
169
|
+
bytes_per_point=20,
|
|
170
|
+
)
|
|
171
|
+
assert isinstance(pf.fields, tuple)
|
|
172
|
+
assert isinstance(pf.types, tuple)
|
|
173
|
+
assert pf.num_fields == 5
|
|
174
|
+
|
|
175
|
+
def test_mismatched_lengths_rejected(self):
|
|
176
|
+
with pytest.raises(ValueError, match="must have equal length"):
|
|
177
|
+
PointFormat(fields=["x", "y", "z"], types=["float32", "float32"], bytes_per_point=12)
|
|
178
|
+
|
|
179
|
+
def test_round_trip_via_dict(self):
|
|
180
|
+
d = {
|
|
181
|
+
"fields": ["x", "y", "z", "intensity", "time"],
|
|
182
|
+
"types": ["float32"] * 5,
|
|
183
|
+
"bytes_per_point": 20,
|
|
184
|
+
}
|
|
185
|
+
pf = PointFormat.from_dict(d)
|
|
186
|
+
assert pf.to_dict() == d
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class TestSensorInfo:
|
|
190
|
+
def test_lidar_with_point_format(self):
|
|
191
|
+
d = {
|
|
192
|
+
"modality": "lidar",
|
|
193
|
+
"point_format": {
|
|
194
|
+
"fields": ["x", "y", "z", "intensity", "time"],
|
|
195
|
+
"types": ["float32"] * 5,
|
|
196
|
+
"bytes_per_point": 20,
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
si = SensorInfo.from_dict(d)
|
|
200
|
+
assert si.modality == "lidar"
|
|
201
|
+
assert si.point_format is not None
|
|
202
|
+
assert si.point_format.num_fields == 5
|
|
203
|
+
assert si.resolution is None
|
|
204
|
+
assert si.to_dict() == d
|
|
205
|
+
|
|
206
|
+
def test_camera_with_resolution(self):
|
|
207
|
+
d = {"modality": "camera", "resolution": [1600, 900]}
|
|
208
|
+
si = SensorInfo.from_dict(d)
|
|
209
|
+
assert si.modality == "camera"
|
|
210
|
+
assert si.point_format is None
|
|
211
|
+
assert si.resolution == (1600, 900)
|
|
212
|
+
assert si.to_dict() == d
|
|
213
|
+
|
|
214
|
+
def test_extras_round_trip(self):
|
|
215
|
+
"""Unknown keys (e.g. producer-specific extras) survive a round-trip."""
|
|
216
|
+
d = {
|
|
217
|
+
"modality": "lidar",
|
|
218
|
+
"point_format": {
|
|
219
|
+
"fields": ["x", "y", "z"],
|
|
220
|
+
"types": ["float32", "float32", "float32"],
|
|
221
|
+
"bytes_per_point": 12,
|
|
222
|
+
},
|
|
223
|
+
"fused_from": ["lidar_top", "lidar_left"],
|
|
224
|
+
}
|
|
225
|
+
si = SensorInfo.from_dict(d)
|
|
226
|
+
assert si.extras == {"fused_from": ["lidar_top", "lidar_left"]}
|
|
227
|
+
out = si.to_dict()
|
|
228
|
+
assert out["fused_from"] == ["lidar_top", "lidar_left"]
|
|
229
|
+
|
|
230
|
+
def test_camera_no_point_format_in_dict(self):
|
|
231
|
+
si = SensorInfo.from_dict({"modality": "camera"})
|
|
232
|
+
assert si.point_format is None
|
|
@@ -15,9 +15,11 @@ from x4d_devkit.core import (
|
|
|
15
15
|
ClipMeta,
|
|
16
16
|
EgoPose,
|
|
17
17
|
Instance,
|
|
18
|
+
PointFormat,
|
|
18
19
|
Sample,
|
|
19
20
|
SampleData,
|
|
20
21
|
SchemaVersionMismatch,
|
|
22
|
+
SensorInfo,
|
|
21
23
|
Transform,
|
|
22
24
|
TransformChain,
|
|
23
25
|
generate_token,
|
|
@@ -47,9 +49,11 @@ __all__ = [
|
|
|
47
49
|
"DetectionResult",
|
|
48
50
|
"EgoPose",
|
|
49
51
|
"Instance",
|
|
52
|
+
"PointFormat",
|
|
50
53
|
"Sample",
|
|
51
54
|
"SampleData",
|
|
52
55
|
"SchemaVersionMismatch",
|
|
56
|
+
"SensorInfo",
|
|
53
57
|
"Transform",
|
|
54
58
|
"TransformChain",
|
|
55
59
|
"ValidationReport",
|
|
@@ -7,8 +7,10 @@ from x4d_devkit.core.models import (
|
|
|
7
7
|
ClipMeta,
|
|
8
8
|
EgoPose,
|
|
9
9
|
Instance,
|
|
10
|
+
PointFormat,
|
|
10
11
|
Sample,
|
|
11
12
|
SampleData,
|
|
13
|
+
SensorInfo,
|
|
12
14
|
)
|
|
13
15
|
from x4d_devkit.core.token import generate_token
|
|
14
16
|
from x4d_devkit.core.transform import Transform, TransformChain
|
|
@@ -20,9 +22,11 @@ __all__ = [
|
|
|
20
22
|
"ClipMeta",
|
|
21
23
|
"EgoPose",
|
|
22
24
|
"Instance",
|
|
25
|
+
"PointFormat",
|
|
23
26
|
"Sample",
|
|
24
27
|
"SampleData",
|
|
25
28
|
"SchemaVersionMismatch",
|
|
29
|
+
"SensorInfo",
|
|
26
30
|
"Transform",
|
|
27
31
|
"TransformChain",
|
|
28
32
|
"generate_token",
|
|
@@ -17,6 +17,7 @@ from x4d_devkit.core.models import (
|
|
|
17
17
|
SampleData,
|
|
18
18
|
Annotation,
|
|
19
19
|
Instance,
|
|
20
|
+
SensorInfo,
|
|
20
21
|
)
|
|
21
22
|
from x4d_devkit.core.transform import Transform, TransformChain, rotation_matrix_to_quat
|
|
22
23
|
|
|
@@ -122,6 +123,10 @@ class ClipLoader:
|
|
|
122
123
|
|
|
123
124
|
def _load_meta(self) -> ClipMeta:
|
|
124
125
|
d = self._read_json("meta.json")
|
|
126
|
+
sensors = {
|
|
127
|
+
channel: SensorInfo.from_dict(raw)
|
|
128
|
+
for channel, raw in (d.get("sensors") or {}).items()
|
|
129
|
+
}
|
|
125
130
|
return ClipMeta(
|
|
126
131
|
clip_id=d["clip_id"],
|
|
127
132
|
schema_version=d["schema_version"],
|
|
@@ -131,7 +136,7 @@ class ClipLoader:
|
|
|
131
136
|
duration_s=d["duration_s"],
|
|
132
137
|
num_keyframes=d["keyframe_count"],
|
|
133
138
|
num_sweeps=d["sweep_count"],
|
|
134
|
-
sensors=
|
|
139
|
+
sensors=sensors,
|
|
135
140
|
)
|
|
136
141
|
|
|
137
142
|
def _load_calibrated_sensors(self) -> list[CalibratedSensor]:
|
|
@@ -400,8 +405,12 @@ class ClipLoader:
|
|
|
400
405
|
raise ValueError(f"Invalid frame '{frame}', must be one of {_VALID_FRAMES}")
|
|
401
406
|
|
|
402
407
|
sensor_info = self._meta.sensors[sd.channel]
|
|
403
|
-
|
|
404
|
-
|
|
408
|
+
if sensor_info.point_format is None:
|
|
409
|
+
raise ValueError(
|
|
410
|
+
f"channel '{sd.channel}' has no point_format in meta.json — "
|
|
411
|
+
f"cannot load as a point cloud (modality={sensor_info.modality})"
|
|
412
|
+
)
|
|
413
|
+
num_fields = sensor_info.point_format.num_fields
|
|
405
414
|
|
|
406
415
|
path = self._clip_dir / sd.file_path
|
|
407
416
|
raw = np.fromfile(str(path), dtype=np.float32)
|
|
@@ -12,28 +12,119 @@ Coordinate conventions:
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
from dataclasses import dataclass
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class PointFormat:
|
|
23
|
+
"""Binary layout of a LiDAR point cloud .bin file.
|
|
24
|
+
|
|
25
|
+
Each point is laid out as ``len(fields)`` consecutive scalars of the
|
|
26
|
+
matching numpy dtype (``types[i]`` for ``fields[i]``). ``bytes_per_point``
|
|
27
|
+
is the on-disk stride in bytes.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
fields: Per-component field names, e.g. ``["x", "y", "z", "intensity", "time"]``.
|
|
31
|
+
types: Per-component numpy dtype names, e.g. ``["float32", ...]``.
|
|
32
|
+
bytes_per_point: Total bytes per point on disk (== sum of dtype sizes).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
fields: tuple[str, ...]
|
|
36
|
+
types: tuple[str, ...]
|
|
37
|
+
bytes_per_point: int
|
|
38
|
+
|
|
39
|
+
def __post_init__(self) -> None:
|
|
40
|
+
# Normalise sequences → immutable tuples for free hashability.
|
|
41
|
+
object.__setattr__(self, "fields", tuple(self.fields))
|
|
42
|
+
object.__setattr__(self, "types", tuple(self.types))
|
|
43
|
+
if len(self.fields) != len(self.types):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"PointFormat: fields ({len(self.fields)}) and types "
|
|
46
|
+
f"({len(self.types)}) must have equal length"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, d: dict[str, Any]) -> PointFormat:
|
|
51
|
+
return cls(
|
|
52
|
+
fields=tuple(d["fields"]),
|
|
53
|
+
types=tuple(d["types"]),
|
|
54
|
+
bytes_per_point=int(d["bytes_per_point"]),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict[str, Any]:
|
|
58
|
+
return {
|
|
59
|
+
"fields": list(self.fields),
|
|
60
|
+
"types": list(self.types),
|
|
61
|
+
"bytes_per_point": self.bytes_per_point,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def num_fields(self) -> int:
|
|
66
|
+
return len(self.fields)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class SensorInfo:
|
|
71
|
+
"""Per-channel sensor metadata embedded in ``meta.json``.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
modality: ``"lidar"`` or ``"camera"``.
|
|
75
|
+
point_format: Binary point layout (LiDAR only; ``None`` for cameras).
|
|
76
|
+
resolution: ``(width, height)`` in pixels (cameras only; ``None`` for LiDAR).
|
|
77
|
+
extras: Any additional fields the producer wrote into the sensor dict.
|
|
78
|
+
Preserved verbatim so ``to_dict()`` round-trips unknown keys.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
modality: str
|
|
82
|
+
point_format: PointFormat | None = None
|
|
83
|
+
resolution: tuple[int, int] | None = None
|
|
84
|
+
extras: dict[str, Any] = field(default_factory=dict)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_dict(cls, d: dict[str, Any]) -> SensorInfo:
|
|
88
|
+
modality = d["modality"]
|
|
89
|
+
pf_raw = d.get("point_format")
|
|
90
|
+
point_format = PointFormat.from_dict(pf_raw) if pf_raw is not None else None
|
|
91
|
+
res_raw = d.get("resolution")
|
|
92
|
+
resolution = (int(res_raw[0]), int(res_raw[1])) if res_raw is not None else None
|
|
93
|
+
known = {"modality", "point_format", "resolution"}
|
|
94
|
+
extras = {k: v for k, v in d.items() if k not in known}
|
|
95
|
+
return cls(
|
|
96
|
+
modality=modality,
|
|
97
|
+
point_format=point_format,
|
|
98
|
+
resolution=resolution,
|
|
99
|
+
extras=extras,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def to_dict(self) -> dict[str, Any]:
|
|
103
|
+
out: dict[str, Any] = {"modality": self.modality}
|
|
104
|
+
if self.point_format is not None:
|
|
105
|
+
out["point_format"] = self.point_format.to_dict()
|
|
106
|
+
if self.resolution is not None:
|
|
107
|
+
out["resolution"] = list(self.resolution)
|
|
108
|
+
out.update(self.extras)
|
|
109
|
+
return out
|
|
110
|
+
|
|
111
|
+
|
|
21
112
|
@dataclass(frozen=True)
|
|
22
113
|
class ClipMeta:
|
|
23
114
|
"""Metadata for an X4D clip (from meta.json).
|
|
24
115
|
|
|
25
116
|
Attributes:
|
|
26
117
|
clip_id: Unique clip identifier (e.g. "20180724_n015_scene-0061_000").
|
|
27
|
-
schema_version: X4D format version (e.g. "0.
|
|
118
|
+
schema_version: X4D format version (e.g. "0.5").
|
|
28
119
|
session_id: Recording session identifier.
|
|
29
120
|
vehicle_id: Vehicle identifier.
|
|
30
121
|
capture_time_utc: Capture timestamp in ISO 8601 format.
|
|
31
122
|
duration_s: Clip duration in seconds.
|
|
32
123
|
num_keyframes: Number of keyframes (annotated frames).
|
|
33
124
|
num_sweeps: Number of sweep frames (non-annotated intermediate frames).
|
|
34
|
-
sensors:
|
|
35
|
-
|
|
36
|
-
|
|
125
|
+
sensors: Per-channel sensor metadata, keyed by channel name. Each
|
|
126
|
+
value is a ``SensorInfo`` carrying modality + (for LiDAR) the
|
|
127
|
+
binary ``PointFormat`` describing the .bin file layout.
|
|
37
128
|
"""
|
|
38
129
|
|
|
39
130
|
clip_id: str
|
|
@@ -44,7 +135,7 @@ class ClipMeta:
|
|
|
44
135
|
duration_s: float
|
|
45
136
|
num_keyframes: int
|
|
46
137
|
num_sweeps: int
|
|
47
|
-
sensors: dict[str,
|
|
138
|
+
sensors: dict[str, SensorInfo]
|
|
48
139
|
|
|
49
140
|
|
|
50
141
|
@dataclass(frozen=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|