x4d-devkit 0.42.0__tar.gz → 0.43.0rc1__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.42.0 → x4d_devkit-0.43.0rc1}/PKG-INFO +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/pyproject.toml +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_annotation_contract.py +48 -3
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_boundary.py +10 -2
- x4d_devkit-0.43.0rc1/tests/test_clean_basemap.py +391 -0
- x4d_devkit-0.43.0rc1/tests/test_clean_basemap_client.py +171 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_contract_validation.py +1 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_input.py +2 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_schemas.py +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_service.py +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_models.py +3 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_work_session.py +2 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/__init__.py +10 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/annotation_contract.py +22 -1
- x4d_devkit-0.43.0rc1/x4d_devkit/clean_basemap.py +879 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/__init__.py +2 -0
- x4d_devkit-0.43.0rc1/x4d_devkit/client/clean_basemap.py +192 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/reconstruction.py +151 -4
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/loader.py +2 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/models.py +5 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/semantic_scene_contract.py +11 -3
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/contract.py +1 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/work_session.py +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/SOURCES.txt +4 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/README.md +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/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.43.0rc1"
|
|
8
8
|
description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -31,6 +31,7 @@ def _annotation(
|
|
|
31
31
|
"rotation": {"qx": 0.0, "qy": 0.0, "qz": 0.0, "qw": 1.0},
|
|
32
32
|
},
|
|
33
33
|
"velocity": None,
|
|
34
|
+
"motion": "auto",
|
|
34
35
|
"attributes": {},
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -125,6 +126,7 @@ def test_derive_annotation_views_uses_sample_time_not_input_order():
|
|
|
125
126
|
{
|
|
126
127
|
"instance_token": "instance-0",
|
|
127
128
|
"category": "car",
|
|
129
|
+
"motion": "auto",
|
|
128
130
|
"num_annotations": 2,
|
|
129
131
|
"first_annotation_token": "annotation-z",
|
|
130
132
|
"last_annotation_token": "annotation-a",
|
|
@@ -191,10 +193,53 @@ def test_annotation_documents_and_derived_views_are_recursively_immutable():
|
|
|
191
193
|
),
|
|
192
194
|
],
|
|
193
195
|
)
|
|
194
|
-
def test_derive_annotation_views_rejects_ambiguous_tracks(
|
|
195
|
-
annotations, samples, code
|
|
196
|
-
):
|
|
196
|
+
def test_derive_annotation_views_rejects_ambiguous_tracks(annotations, samples, code):
|
|
197
197
|
with pytest.raises(AnnotationContractError) as exc:
|
|
198
198
|
derive_annotation_views(copy.deepcopy(annotations), copy.deepcopy(samples))
|
|
199
199
|
|
|
200
200
|
assert exc.value.code == code
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@pytest.mark.parametrize(
|
|
204
|
+
"motion", [None, True, 1, [], {}, "unknown", "Static", " static"]
|
|
205
|
+
)
|
|
206
|
+
def test_motion_is_explicit_and_canonical(motion):
|
|
207
|
+
row = _annotation("annotation-0", "sample-0")
|
|
208
|
+
row["motion"] = motion
|
|
209
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
210
|
+
build_clip_annotations("clip-0", "base_link", [row])
|
|
211
|
+
assert exc.value.code == "invalid_annotation_motion"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_motion_changes_revision_and_is_shared_by_the_entire_instance():
|
|
215
|
+
first = _annotation("annotation-0", "sample-0")
|
|
216
|
+
second = _annotation("annotation-1", "sample-1")
|
|
217
|
+
automatic = build_clip_annotations("clip-0", "base_link", [first, second])
|
|
218
|
+
first["motion"] = "dynamic"
|
|
219
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
220
|
+
build_clip_annotations("clip-0", "base_link", [first, second])
|
|
221
|
+
assert exc.value.code == "instance_motion_mismatch"
|
|
222
|
+
second["motion"] = "dynamic"
|
|
223
|
+
dynamic = build_clip_annotations("clip-0", "base_link", [first, second])
|
|
224
|
+
assert dynamic.annotation_revision != automatic.annotation_revision
|
|
225
|
+
assert (
|
|
226
|
+
derive_annotation_views(
|
|
227
|
+
dynamic.to_dict()["annotations"], _samples()
|
|
228
|
+
).instance_rows()[0]["motion"]
|
|
229
|
+
== "dynamic"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_missing_motion_and_previous_annotation_version_are_rejected():
|
|
234
|
+
row = _annotation("annotation-0", "sample-0")
|
|
235
|
+
row.pop("motion")
|
|
236
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
237
|
+
build_clip_annotations("clip-0", "base_link", [row])
|
|
238
|
+
assert exc.value.code == "invalid_annotation_shape"
|
|
239
|
+
document = build_clip_annotations(
|
|
240
|
+
"clip-0", "base_link", [_annotation("annotation-0", "sample-0")]
|
|
241
|
+
).to_dict()
|
|
242
|
+
document["contract_version"] = "x4d-clip-annotations-v3"
|
|
243
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
244
|
+
parse_clip_annotations(document)
|
|
245
|
+
assert exc.value.code == "invalid_annotation_contract_version"
|
|
@@ -20,7 +20,9 @@ def test_devkit_does_not_import_backend_app_modules():
|
|
|
20
20
|
if isinstance(node, ast.Import):
|
|
21
21
|
for alias in node.names:
|
|
22
22
|
if alias.name == "app" or alias.name.startswith("app."):
|
|
23
|
-
offenders.append(
|
|
23
|
+
offenders.append(
|
|
24
|
+
f"{path.relative_to(ROOT)} imports {alias.name}"
|
|
25
|
+
)
|
|
24
26
|
elif isinstance(node, ast.ImportFrom):
|
|
25
27
|
module = node.module or ""
|
|
26
28
|
if module == "app" or module.startswith("app."):
|
|
@@ -29,7 +31,9 @@ def test_devkit_does_not_import_backend_app_modules():
|
|
|
29
31
|
assert offenders == []
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
def _help_for(
|
|
34
|
+
def _help_for(
|
|
35
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], *args: str
|
|
36
|
+
) -> str:
|
|
33
37
|
monkeypatch.setattr(sys, "argv", ["x4d-devkit", *args, "--help"])
|
|
34
38
|
with pytest.raises(SystemExit) as exc:
|
|
35
39
|
cli.main()
|
|
@@ -74,6 +78,8 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
74
78
|
assert set(x4d_devkit.__all__) == {
|
|
75
79
|
"__version__",
|
|
76
80
|
"ANNOTATION_CONTRACT_VERSION",
|
|
81
|
+
"CLEAN_BASEMAP_CONTRACT_VERSION",
|
|
82
|
+
"CleanBasemapManifest",
|
|
77
83
|
"Annotation",
|
|
78
84
|
"AnnotationContractError",
|
|
79
85
|
"AssetRef",
|
|
@@ -136,6 +142,7 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
136
142
|
"load_semantic_scene_component",
|
|
137
143
|
"parse_clip_work_session",
|
|
138
144
|
"parse_clip_annotations",
|
|
145
|
+
"parse_clean_basemap_manifest",
|
|
139
146
|
"parse_clip_label_schema_snapshot",
|
|
140
147
|
"parse_semantic_scene_authoring_scene_seed_index",
|
|
141
148
|
"parse_semantic_scene_authoring_submit",
|
|
@@ -146,6 +153,7 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
146
153
|
"transform_concat_v1",
|
|
147
154
|
"transform_concat_source_v1",
|
|
148
155
|
"validate_clip",
|
|
156
|
+
"validate_clean_basemap_resource",
|
|
149
157
|
"validate_semantic_scene_authoring_semantic_upload",
|
|
150
158
|
"validate_platform_pose_tokens",
|
|
151
159
|
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
import zstandard
|
|
6
|
+
|
|
7
|
+
from tests.semantic_scene_contract_fixtures import current_semantic_scene_component
|
|
8
|
+
from x4d_devkit.annotation_contract import build_clip_annotations
|
|
9
|
+
from x4d_devkit.clean_basemap import (
|
|
10
|
+
CLEAN_BASEMAP_CONTRACT_VERSION,
|
|
11
|
+
CLEAN_BASEMAP_INPUT_FIELDS,
|
|
12
|
+
DECISION_CODES,
|
|
13
|
+
KEEP_GROUND,
|
|
14
|
+
MAPPING_DTYPE,
|
|
15
|
+
clean_basemap_scene_source,
|
|
16
|
+
compute_clean_basemap_dependency_identity,
|
|
17
|
+
compute_clean_basemap_revision,
|
|
18
|
+
decode_clean_basemap_decisions,
|
|
19
|
+
decode_clean_basemap_mapping,
|
|
20
|
+
encode_clean_basemap_decisions,
|
|
21
|
+
encode_clean_basemap_mapping,
|
|
22
|
+
parse_clean_basemap_manifest,
|
|
23
|
+
validate_clean_basemap_annotation,
|
|
24
|
+
validate_clean_basemap_native_binding,
|
|
25
|
+
validate_clean_basemap_resource,
|
|
26
|
+
)
|
|
27
|
+
from x4d_devkit.semantic_scene_contract import (
|
|
28
|
+
compute_annotation_dependency_revisions,
|
|
29
|
+
decode_scene_geometry,
|
|
30
|
+
encode_scene_geometry,
|
|
31
|
+
sha256_bytes,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fixture():
|
|
36
|
+
native = current_semantic_scene_component()
|
|
37
|
+
scene = clean_basemap_scene_source(native["scene_index"])
|
|
38
|
+
first = scene["chunks"][0]
|
|
39
|
+
geometry = decode_scene_geometry(
|
|
40
|
+
native["scene_geometry_payloads"][first["geometry"]],
|
|
41
|
+
point_count=3,
|
|
42
|
+
expected_payload_sha256=first["geometry_sha256"],
|
|
43
|
+
expected_raw_sha256=first["geometry_raw_sha256"],
|
|
44
|
+
)
|
|
45
|
+
xyz = np.vstack([geometry.xyz, [9.5, 0, 0]]).astype(np.float32)
|
|
46
|
+
rgb = np.vstack([geometry.rgb, [100, 110, 120]]).astype(np.uint8)
|
|
47
|
+
encoded = encode_scene_geometry(xyz, rgb)
|
|
48
|
+
descriptor = {
|
|
49
|
+
"path": f"generated/reconstruction/clean_basemap/geometry/{encoded.payload_sha256[7:]}.spc.zst",
|
|
50
|
+
"sha256": encoded.payload_sha256,
|
|
51
|
+
"raw_sha256": encoded.raw_sha256,
|
|
52
|
+
"size_bytes": len(encoded.payload),
|
|
53
|
+
}
|
|
54
|
+
decisions = [1, 0, 0, 2, 17]
|
|
55
|
+
source_hash = "sha256:" + "a" * 64
|
|
56
|
+
decision_payload = encode_clean_basemap_decisions(
|
|
57
|
+
asset_sha256=source_hash, decisions=decisions
|
|
58
|
+
)
|
|
59
|
+
mapping = encode_clean_basemap_mapping(
|
|
60
|
+
geometry_raw_sha256=encoded.raw_sha256,
|
|
61
|
+
sweep_indices=[0, 0, 0, 0],
|
|
62
|
+
raw_point_ordinals=[0, 1, 2, 3],
|
|
63
|
+
timestamps_ns=[100, 100, 100, 100],
|
|
64
|
+
owner_source_indices=[0, 0, 0, 1],
|
|
65
|
+
owner_point_ordinals=[0, 1, 2, 0],
|
|
66
|
+
decisions=decisions[:4],
|
|
67
|
+
)
|
|
68
|
+
instance_sources = [
|
|
69
|
+
{
|
|
70
|
+
key: row[key]
|
|
71
|
+
for key in (
|
|
72
|
+
"instance_token",
|
|
73
|
+
"point_count",
|
|
74
|
+
"geometry",
|
|
75
|
+
"geometry_sha256",
|
|
76
|
+
"geometry_raw_sha256",
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
| {"motion": "static"}
|
|
80
|
+
for row in native["instance_index"]["instances"]
|
|
81
|
+
]
|
|
82
|
+
profile = {
|
|
83
|
+
"algorithm": {"id": "clean_static_basemap", "version": "1.0.0"},
|
|
84
|
+
"voxel_size_m": 0.03,
|
|
85
|
+
"min_range_m": 2.0,
|
|
86
|
+
"max_range_m": 60.0,
|
|
87
|
+
"lidar_channels": ["scanner-a"],
|
|
88
|
+
"unknown_policy": "retain",
|
|
89
|
+
"point_deskew": True,
|
|
90
|
+
"foreground_policy": "joint_temporal_box_ground_v1",
|
|
91
|
+
"occlusion_policy": "original_observations",
|
|
92
|
+
"policies": {
|
|
93
|
+
name: {"id": name, "version": "1.0.0", "parameters": {}}
|
|
94
|
+
for name in ("geometry", "ground", "motion", "rgb")
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
inputs = {key: "sha256:" + "b" * 64 for key in CLEAN_BASEMAP_INPUT_FIELDS}
|
|
98
|
+
dependencies = compute_annotation_dependency_revisions(native["annotation"])
|
|
99
|
+
for key in inputs:
|
|
100
|
+
if key in dependencies:
|
|
101
|
+
inputs[key] = dependencies[key]
|
|
102
|
+
manifest = {
|
|
103
|
+
"contract_version": CLEAN_BASEMAP_CONTRACT_VERSION,
|
|
104
|
+
"clip_id": native["annotation"]["clip_id"],
|
|
105
|
+
"frame_id": "clip_world",
|
|
106
|
+
"basemap_revision": "sha256:" + "0" * 64,
|
|
107
|
+
"dependency_identity": compute_clean_basemap_dependency_identity(
|
|
108
|
+
inputs, profile
|
|
109
|
+
),
|
|
110
|
+
"input_identity": inputs,
|
|
111
|
+
"profile": profile,
|
|
112
|
+
"point_count": 4,
|
|
113
|
+
"bounds": [xyz.min(0).tolist(), xyz.max(0).tolist()],
|
|
114
|
+
"scene_source": scene,
|
|
115
|
+
"instance_sources": instance_sources,
|
|
116
|
+
"sweep_sources": [
|
|
117
|
+
{
|
|
118
|
+
"sample_data_token": "sweep-0",
|
|
119
|
+
"channel": "scanner-a",
|
|
120
|
+
"point_count": 5,
|
|
121
|
+
"asset_sha256": source_hash,
|
|
122
|
+
"time_bounds": [{"sec": 0, "nanosec": 100}, {"sec": 0, "nanosec": 100}],
|
|
123
|
+
"decisions": decision_payload.descriptor("decisions"),
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"chunks": [
|
|
127
|
+
{
|
|
128
|
+
"ordinal": 0,
|
|
129
|
+
"point_count": 4,
|
|
130
|
+
"bounds": [xyz.min(0).tolist(), xyz.max(0).tolist()],
|
|
131
|
+
"geometry": descriptor,
|
|
132
|
+
"mapping": mapping.descriptor("mapping"),
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"diagnostics": {
|
|
136
|
+
"source_point_count": 5,
|
|
137
|
+
"decision_counts": {
|
|
138
|
+
str(code): decisions.count(code) for code in DECISION_CODES
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
143
|
+
assets = dict(native["scene_geometry_payloads"], **native["instance_payloads"])
|
|
144
|
+
assets.update(
|
|
145
|
+
{
|
|
146
|
+
descriptor["path"]: encoded.payload,
|
|
147
|
+
mapping.descriptor("mapping")["path"]: mapping.payload,
|
|
148
|
+
decision_payload.descriptor("decisions")["path"]: decision_payload.payload,
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
return manifest, assets, native
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def validate(manifest, assets):
|
|
155
|
+
geometry = manifest["chunks"][0]["geometry"]
|
|
156
|
+
original_xyz = decode_scene_geometry(
|
|
157
|
+
assets[geometry["path"]],
|
|
158
|
+
point_count=4,
|
|
159
|
+
expected_payload_sha256=geometry["sha256"],
|
|
160
|
+
expected_raw_sha256=geometry["raw_sha256"],
|
|
161
|
+
).xyz
|
|
162
|
+
return validate_clean_basemap_resource(
|
|
163
|
+
manifest,
|
|
164
|
+
read_asset=assets.__getitem__,
|
|
165
|
+
observation_to_world=lambda sweep, ordinals, times: original_xyz[ordinals],
|
|
166
|
+
instance_to_world=lambda token, times, xyz: xyz + [10, 0, 0],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def remap(manifest, assets, **overrides):
|
|
171
|
+
chunk = manifest["chunks"][0]
|
|
172
|
+
mapping = decode_clean_basemap_mapping(
|
|
173
|
+
assets[chunk["mapping"]["path"]],
|
|
174
|
+
descriptor=chunk["mapping"],
|
|
175
|
+
geometry_raw_sha256=chunk["geometry"]["raw_sha256"],
|
|
176
|
+
point_count=chunk["point_count"],
|
|
177
|
+
)
|
|
178
|
+
arguments = {
|
|
179
|
+
"sweep_indices": mapping["sweep_index"],
|
|
180
|
+
"raw_point_ordinals": mapping["raw_point_ordinal"],
|
|
181
|
+
"timestamps_ns": mapping["timestamp_ns"],
|
|
182
|
+
"owner_source_indices": mapping["owner_source_index"],
|
|
183
|
+
"owner_point_ordinals": mapping["owner_point_ordinal"],
|
|
184
|
+
"decisions": mapping["decision"],
|
|
185
|
+
}
|
|
186
|
+
payload = encode_clean_basemap_mapping(
|
|
187
|
+
geometry_raw_sha256=chunk["geometry"]["raw_sha256"], **(arguments | overrides)
|
|
188
|
+
)
|
|
189
|
+
chunk["mapping"] = payload.descriptor("mapping")
|
|
190
|
+
assets[chunk["mapping"]["path"]] = payload.payload
|
|
191
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_complete_geometry_native_ownership_and_original_observation_roundtrip():
|
|
195
|
+
manifest, assets, native = fixture()
|
|
196
|
+
parsed = validate(manifest, assets)
|
|
197
|
+
assert parsed.to_dict() == manifest
|
|
198
|
+
validate_clean_basemap_annotation(parsed, native["annotation"])
|
|
199
|
+
validate_clean_basemap_native_binding(
|
|
200
|
+
parsed,
|
|
201
|
+
scene_index=native["scene_index"],
|
|
202
|
+
instance_index=native["instance_index"],
|
|
203
|
+
)
|
|
204
|
+
manifest["chunks"][0]["point_count"] = 9
|
|
205
|
+
assert parsed.data["chunks"][0]["point_count"] == 4
|
|
206
|
+
with pytest.raises(TypeError):
|
|
207
|
+
parsed.data["profile"]["voxel_size_m"] = 1
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@pytest.mark.parametrize(
|
|
211
|
+
"overrides, message",
|
|
212
|
+
[
|
|
213
|
+
({"sweep_indices": [1, 0, 0, 0]}, "source index"),
|
|
214
|
+
({"raw_point_ordinals": [5, 1, 2, 3]}, "original point ordinal"),
|
|
215
|
+
({"raw_point_ordinals": [0, 0, 2, 3]}, "duplicate original observation"),
|
|
216
|
+
({"raw_point_ordinals": [0, 1, 2, 4]}, "decision"),
|
|
217
|
+
({"timestamps_ns": [101, 100, 100, 100]}, "exact point time"),
|
|
218
|
+
({"owner_source_indices": [9, 0, 0, 1]}, "source index"),
|
|
219
|
+
({"owner_point_ordinals": [0, 1, 3, 0]}, "native point ordinal"),
|
|
220
|
+
({"owner_point_ordinals": [0, 1, 1, 0]}, "duplicate native scene"),
|
|
221
|
+
({"owner_source_indices": [0, 0, 1, 1]}, "instance correspondence"),
|
|
222
|
+
],
|
|
223
|
+
)
|
|
224
|
+
def test_valid_hash_cannot_hide_wrong_addresses_or_removed_sources(overrides, message):
|
|
225
|
+
manifest, assets, _ = fixture()
|
|
226
|
+
remap(manifest, assets, **overrides)
|
|
227
|
+
with pytest.raises(ValueError, match=message):
|
|
228
|
+
validate(manifest, assets)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_dynamic_instance_support_is_not_static_display_evidence():
|
|
232
|
+
manifest, assets, _ = fixture()
|
|
233
|
+
manifest["instance_sources"][0]["motion"] = "dynamic"
|
|
234
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
235
|
+
with pytest.raises(ValueError, match="dynamic instance"):
|
|
236
|
+
validate(manifest, assets)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@pytest.mark.parametrize("result", [np.zeros((2, 3)), np.full((1, 3), np.nan)])
|
|
240
|
+
def test_instance_transform_rejects_incomplete_or_nonfinite_results(result):
|
|
241
|
+
manifest, assets, _ = fixture()
|
|
242
|
+
descriptor = manifest["chunks"][0]["geometry"]
|
|
243
|
+
original_xyz = decode_scene_geometry(
|
|
244
|
+
assets[descriptor["path"]],
|
|
245
|
+
point_count=4,
|
|
246
|
+
expected_payload_sha256=descriptor["sha256"],
|
|
247
|
+
expected_raw_sha256=descriptor["raw_sha256"],
|
|
248
|
+
).xyz
|
|
249
|
+
with pytest.raises(ValueError, match="pinned time-aligned track"):
|
|
250
|
+
validate_clean_basemap_resource(
|
|
251
|
+
manifest,
|
|
252
|
+
read_asset=assets.__getitem__,
|
|
253
|
+
observation_to_world=lambda sweep, ordinals, times: original_xyz[ordinals],
|
|
254
|
+
instance_to_world=lambda token, times, xyz: result,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_native_geometry_agreement_cannot_hide_fabricated_original_observations():
|
|
259
|
+
manifest, assets, _ = fixture()
|
|
260
|
+
with pytest.raises(ValueError, match="differs from its original observations"):
|
|
261
|
+
validate_clean_basemap_resource(
|
|
262
|
+
manifest,
|
|
263
|
+
read_asset=assets.__getitem__,
|
|
264
|
+
observation_to_world=lambda sweep, ordinals, times: np.full(
|
|
265
|
+
(len(ordinals), 3), 999.0
|
|
266
|
+
),
|
|
267
|
+
instance_to_world=lambda token, times, xyz: xyz + [10, 0, 0],
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_pose_coverage_rejections_are_accounted_and_cannot_be_mapped():
|
|
272
|
+
manifest, assets, _ = fixture()
|
|
273
|
+
sweep = manifest["sweep_sources"][0]
|
|
274
|
+
carrier = encode_clean_basemap_decisions(
|
|
275
|
+
asset_sha256=sweep["asset_sha256"], decisions=[1, 0, 0, 2, 34]
|
|
276
|
+
)
|
|
277
|
+
sweep["decisions"] = carrier.descriptor("decisions")
|
|
278
|
+
assets[sweep["decisions"]["path"]] = carrier.payload
|
|
279
|
+
manifest["diagnostics"]["decision_counts"].update({"17": 0, "34": 1})
|
|
280
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
281
|
+
assert decode_clean_basemap_decisions(
|
|
282
|
+
carrier.payload,
|
|
283
|
+
descriptor=sweep["decisions"],
|
|
284
|
+
asset_sha256=sweep["asset_sha256"],
|
|
285
|
+
point_count=5,
|
|
286
|
+
).tolist() == [1, 0, 0, 2, 34]
|
|
287
|
+
validate(manifest, assets)
|
|
288
|
+
remap(manifest, assets, raw_point_ordinals=[0, 1, 2, 4])
|
|
289
|
+
with pytest.raises(ValueError, match="decision"):
|
|
290
|
+
validate(manifest, assets)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_auto_track_regrouping_invalidates_temporal_ownership():
|
|
294
|
+
_, _, native = fixture()
|
|
295
|
+
original = compute_annotation_dependency_revisions(native["annotation"])
|
|
296
|
+
rows = copy.deepcopy(native["annotation"]["annotations"])
|
|
297
|
+
rows[0]["instance_token"] = "regrouped-track"
|
|
298
|
+
edited = build_clip_annotations(
|
|
299
|
+
native["annotation"]["clip_id"],
|
|
300
|
+
native["annotation"]["annotation_frame_id"],
|
|
301
|
+
rows,
|
|
302
|
+
)
|
|
303
|
+
assert (
|
|
304
|
+
compute_annotation_dependency_revisions(edited)["scene_ownership_revision"]
|
|
305
|
+
!= original["scene_ownership_revision"]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_native_label_edit_keeps_geometry_binding_but_motion_or_source_edit_does_not():
|
|
310
|
+
manifest, _, native = fixture()
|
|
311
|
+
edited = copy.deepcopy(native["annotation"]["annotations"])
|
|
312
|
+
edited[0]["motion"] = "dynamic"
|
|
313
|
+
annotation = build_clip_annotations(
|
|
314
|
+
native["annotation"]["clip_id"],
|
|
315
|
+
native["annotation"]["annotation_frame_id"],
|
|
316
|
+
edited,
|
|
317
|
+
).to_dict()
|
|
318
|
+
with pytest.raises(ValueError, match="dependencies"):
|
|
319
|
+
validate_clean_basemap_annotation(manifest, annotation)
|
|
320
|
+
scene = copy.deepcopy(native["scene_index"])
|
|
321
|
+
scene["chunks"][0]["semantic_histogram"] = [{"code": 0, "count": 3}]
|
|
322
|
+
# Existing native parsing enforces the independent label revision too.
|
|
323
|
+
from x4d_devkit.semantic_scene_contract import compute_scene_semantics_revision
|
|
324
|
+
|
|
325
|
+
scene["scene_semantics_revision"] = compute_scene_semantics_revision(scene)
|
|
326
|
+
assert clean_basemap_scene_source(scene) == manifest["scene_source"]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@pytest.mark.parametrize(
|
|
330
|
+
"edit",
|
|
331
|
+
[
|
|
332
|
+
lambda m: m.update(extra=True),
|
|
333
|
+
lambda m: m.update(point_count=True),
|
|
334
|
+
lambda m: m["input_identity"].update(legacy="sha256:" + "f" * 64),
|
|
335
|
+
lambda m: m["chunks"][0]["geometry"].update(path="../other.spc.zst"),
|
|
336
|
+
lambda m: m["chunks"][0].update(ordinal=1),
|
|
337
|
+
lambda m: m["chunks"].append(copy.deepcopy(m["chunks"][0])),
|
|
338
|
+
lambda m: m["diagnostics"]["decision_counts"].update({"17": 0}),
|
|
339
|
+
lambda m: m["instance_sources"].reverse(),
|
|
340
|
+
lambda m: m["sweep_sources"].append(copy.deepcopy(m["sweep_sources"][0])),
|
|
341
|
+
lambda m: m["profile"].update(unknown_policy="delete"),
|
|
342
|
+
lambda m: m["profile"].update(point_deskew=False),
|
|
343
|
+
],
|
|
344
|
+
)
|
|
345
|
+
def test_manifest_rejects_partial_unknown_and_noncanonical_shapes(edit):
|
|
346
|
+
manifest, _, _ = fixture()
|
|
347
|
+
edit(manifest)
|
|
348
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
349
|
+
with pytest.raises(ValueError):
|
|
350
|
+
parse_clean_basemap_manifest(manifest)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def test_decoder_rejects_cross_geometry_reserved_bytes_and_extra_frames():
|
|
354
|
+
manifest, assets, _ = fixture()
|
|
355
|
+
chunk = manifest["chunks"][0]
|
|
356
|
+
payload = assets[chunk["mapping"]["path"]]
|
|
357
|
+
with pytest.raises(ValueError, match="different source geometry"):
|
|
358
|
+
decode_clean_basemap_mapping(
|
|
359
|
+
payload,
|
|
360
|
+
descriptor=chunk["mapping"],
|
|
361
|
+
geometry_raw_sha256="sha256:" + "f" * 64,
|
|
362
|
+
point_count=4,
|
|
363
|
+
)
|
|
364
|
+
raw = bytearray(zstandard.ZstdDecompressor().decompress(payload))
|
|
365
|
+
raw[44 + MAPPING_DTYPE.fields["reserved"][1]] = 1
|
|
366
|
+
malformed = zstandard.ZstdCompressor().compress(raw)
|
|
367
|
+
for body in (malformed, payload + payload):
|
|
368
|
+
descriptor = {
|
|
369
|
+
"path": f"generated/reconstruction/clean_basemap/mapping/{sha256_bytes(body)[7:]}.zst",
|
|
370
|
+
"sha256": sha256_bytes(body),
|
|
371
|
+
"raw_sha256": sha256_bytes(bytes(raw)),
|
|
372
|
+
"size_bytes": len(body),
|
|
373
|
+
}
|
|
374
|
+
with pytest.raises(ValueError):
|
|
375
|
+
decode_clean_basemap_mapping(
|
|
376
|
+
body,
|
|
377
|
+
descriptor=descriptor,
|
|
378
|
+
geometry_raw_sha256=chunk["geometry"]["raw_sha256"],
|
|
379
|
+
point_count=4,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def test_mapping_encoder_rejects_float_negative_and_removed_observations():
|
|
384
|
+
manifest, assets, _ = fixture()
|
|
385
|
+
for changes in (
|
|
386
|
+
{"raw_point_ordinals": [-1, 1, 2, 3]},
|
|
387
|
+
{"timestamps_ns": [100.0] * 4},
|
|
388
|
+
{"decisions": [KEEP_GROUND, 0, 0, 17]},
|
|
389
|
+
):
|
|
390
|
+
with pytest.raises(ValueError):
|
|
391
|
+
remap(manifest, assets, **changes)
|