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.
Files changed (127) hide show
  1. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/PKG-INFO +1 -1
  2. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/pyproject.toml +1 -1
  3. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_annotation_contract.py +48 -3
  4. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_boundary.py +10 -2
  5. x4d_devkit-0.43.0rc1/tests/test_clean_basemap.py +391 -0
  6. x4d_devkit-0.43.0rc1/tests/test_clean_basemap_client.py +171 -0
  7. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_contract_validation.py +1 -0
  8. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_input.py +2 -0
  9. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_schemas.py +1 -1
  10. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_inference_service.py +1 -1
  11. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_models.py +3 -1
  12. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_sdk_api.py +1 -1
  13. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_work_session.py +2 -0
  14. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/__init__.py +10 -0
  15. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/annotation_contract.py +22 -1
  16. x4d_devkit-0.43.0rc1/x4d_devkit/clean_basemap.py +879 -0
  17. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/__init__.py +2 -0
  18. x4d_devkit-0.43.0rc1/x4d_devkit/client/clean_basemap.py +192 -0
  19. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/reconstruction.py +151 -4
  20. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/loader.py +2 -0
  21. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/models.py +5 -0
  22. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/semantic_scene_contract.py +11 -3
  23. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/contract.py +1 -0
  24. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/work_session.py +1 -1
  25. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/PKG-INFO +1 -1
  26. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/SOURCES.txt +4 -0
  27. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/LICENSE +0 -0
  28. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/README.md +0 -0
  29. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/setup.cfg +0 -0
  30. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_annotation_reference.py +0 -0
  31. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_assets_client.py +0 -0
  32. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_box.py +0 -0
  33. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_build_info.py +0 -0
  34. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_cli_auth.py +0 -0
  35. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_client_json_contract.py +0 -0
  36. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_client_projects.py +0 -0
  37. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_clip_archives.py +0 -0
  38. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_coordinate_frames.py +0 -0
  39. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_current_state.py +0 -0
  40. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
  41. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_dataset_contract.py +0 -0
  42. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_dataset_identity.py +0 -0
  43. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_detection_eval.py +0 -0
  44. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_evaluate.py +0 -0
  45. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_export_bundle.py +0 -0
  46. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_image_review.py +0 -0
  47. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_label_schemas.py +0 -0
  48. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_lidar_materialization.py +0 -0
  49. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_loader.py +0 -0
  50. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_matching.py +0 -0
  51. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_metrics.py +0 -0
  52. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_platform_pose.py +0 -0
  53. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_preannotation_client.py +0 -0
  54. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_reconstruction_client.py +0 -0
  55. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_release_gate.py +0 -0
  56. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_roadmark_preview.py +0 -0
  57. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
  58. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_client.py +0 -0
  59. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_contract.py +0 -0
  60. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
  61. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_time_contract.py +0 -0
  62. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_token.py +0 -0
  63. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_tracking_eval.py +0 -0
  64. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_training_identity.py +0 -0
  65. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_transform.py +0 -0
  66. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_transform_chain.py +0 -0
  67. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/tests/test_validation.py +0 -0
  68. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/annotation_reference.py +0 -0
  69. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/build_info.py +0 -0
  70. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/cli.py +0 -0
  71. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/_origin.py +0 -0
  72. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
  73. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/assets.py +0 -0
  74. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/bags.py +0 -0
  75. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/capabilities.py +0 -0
  76. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
  77. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/client.py +0 -0
  78. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/clip_work_sessions.py +0 -0
  79. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/clips.py +0 -0
  80. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
  81. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/image_review.py +0 -0
  82. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
  83. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/preannotation.py +0 -0
  84. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/projects.py +0 -0
  85. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
  86. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  87. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/services.py +0 -0
  88. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/client/test_sets.py +0 -0
  89. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/consumer_identity.py +0 -0
  90. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
  91. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/__init__.py +0 -0
  92. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/token.py +0 -0
  93. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/core/transform.py +0 -0
  94. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/current_state.py +0 -0
  95. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/dataset_contract.py +0 -0
  96. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/dataset_identity.py +0 -0
  97. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/__init__.py +0 -0
  98. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/api.py +0 -0
  99. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/box.py +0 -0
  100. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/detection.py +0 -0
  101. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/matching.py +0 -0
  102. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/metrics.py +0 -0
  103. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/eval/tracking.py +0 -0
  104. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/export_bundle.py +0 -0
  105. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/image_review.py +0 -0
  106. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/__init__.py +0 -0
  107. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/input.py +0 -0
  108. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/models.py +0 -0
  109. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/schemas.py +0 -0
  110. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/service.py +0 -0
  111. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/inference/tasks.py +0 -0
  112. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/json_contract.py +0 -0
  113. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/label_schema.py +0 -0
  114. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
  115. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/materialize.py +0 -0
  116. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
  117. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
  118. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/time_contract.py +0 -0
  119. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/training_identity.py +0 -0
  120. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/__init__.py +0 -0
  121. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
  122. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/report.py +0 -0
  123. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit/validation/validator.py +0 -0
  124. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  125. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
  126. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
  127. {x4d_devkit-0.42.0 → x4d_devkit-0.43.0rc1}/x4d_devkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.42.0
3
+ Version: 0.43.0rc1
4
4
  Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
5
  Author: windzu
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.42.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(f"{path.relative_to(ROOT)} imports {alias.name}")
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(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], *args: str) -> str:
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)