x4d-devkit 0.43.0rc4__tar.gz → 0.44.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 (133) hide show
  1. {x4d_devkit-0.43.0rc4/x4d_devkit.egg-info → x4d_devkit-0.44.0rc1}/PKG-INFO +3 -3
  2. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/README.md +2 -2
  3. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/pyproject.toml +1 -1
  4. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_boundary.py +5 -0
  5. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_current_state.py +2 -0
  6. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_dataset_identity.py +4 -1
  7. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_schemas.py +1 -0
  8. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_label_schemas.py +2 -0
  9. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_sdk_api.py +1 -1
  10. x4d_devkit-0.44.0rc1/tests/test_vector_map.py +119 -0
  11. x4d_devkit-0.44.0rc1/tests/test_vector_map_geometry.py +76 -0
  12. x4d_devkit-0.44.0rc1/tests/test_vector_map_session.py +147 -0
  13. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_work_session.py +7 -1
  14. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/__init__.py +12 -0
  15. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clip_work_sessions.py +12 -0
  16. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/loader.py +13 -0
  17. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/current_state.py +15 -8
  18. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/dataset_identity.py +13 -7
  19. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/label_schema.py +53 -0
  20. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/materialize.py +27 -1
  21. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/validator.py +8 -1
  22. x4d_devkit-0.44.0rc1/x4d_devkit/vector_map.py +371 -0
  23. x4d_devkit-0.44.0rc1/x4d_devkit/vector_map_geometry.py +221 -0
  24. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/work_session.py +21 -1
  25. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1/x4d_devkit.egg-info}/PKG-INFO +3 -3
  26. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/SOURCES.txt +5 -0
  27. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/LICENSE +0 -0
  28. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/setup.cfg +0 -0
  29. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_annotation_contract.py +0 -0
  30. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_annotation_reference.py +0 -0
  31. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_assets_client.py +0 -0
  32. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_box.py +0 -0
  33. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_build_info.py +0 -0
  34. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap.py +0 -0
  35. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap_client.py +0 -0
  36. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap_preannotation.py +0 -0
  37. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_cli_auth.py +0 -0
  38. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_client_json_contract.py +0 -0
  39. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_client_projects.py +0 -0
  40. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clip_archives.py +0 -0
  41. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_contract_validation.py +0 -0
  42. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_coordinate_frames.py +0 -0
  43. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
  44. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_dataset_contract.py +0 -0
  45. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_detection_eval.py +0 -0
  46. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_evaluate.py +0 -0
  47. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_export_bundle.py +0 -0
  48. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_image_review.py +0 -0
  49. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_input.py +0 -0
  50. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_service.py +0 -0
  51. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_lidar_materialization.py +0 -0
  52. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_loader.py +0 -0
  53. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_matching.py +0 -0
  54. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_metrics.py +0 -0
  55. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_models.py +0 -0
  56. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_platform_pose.py +0 -0
  57. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_preannotation_client.py +0 -0
  58. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_reconstruction_client.py +0 -0
  59. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_release_gate.py +0 -0
  60. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_roadmark_preview.py +0 -0
  61. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
  62. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_client.py +0 -0
  63. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_contract.py +0 -0
  64. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
  65. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_time_contract.py +0 -0
  66. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_token.py +0 -0
  67. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_tracking_eval.py +0 -0
  68. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_training_identity.py +0 -0
  69. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_transform.py +0 -0
  70. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_transform_chain.py +0 -0
  71. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_validation.py +0 -0
  72. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/annotation_contract.py +0 -0
  73. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/annotation_reference.py +0 -0
  74. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/build_info.py +0 -0
  75. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/clean_basemap.py +0 -0
  76. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/cli.py +0 -0
  77. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/__init__.py +0 -0
  78. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/_origin.py +0 -0
  79. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
  80. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/assets.py +0 -0
  81. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/bags.py +0 -0
  82. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/capabilities.py +0 -0
  83. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
  84. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
  85. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/client.py +0 -0
  86. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clips.py +0 -0
  87. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
  88. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/image_review.py +0 -0
  89. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
  90. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/preannotation.py +0 -0
  91. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/projects.py +0 -0
  92. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
  93. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
  94. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  95. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/services.py +0 -0
  96. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/test_sets.py +0 -0
  97. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/consumer_identity.py +0 -0
  98. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
  99. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/__init__.py +0 -0
  100. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/models.py +0 -0
  101. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/token.py +0 -0
  102. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/transform.py +0 -0
  103. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/dataset_contract.py +0 -0
  104. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/__init__.py +0 -0
  105. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/api.py +0 -0
  106. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/box.py +0 -0
  107. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/detection.py +0 -0
  108. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/matching.py +0 -0
  109. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/metrics.py +0 -0
  110. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/tracking.py +0 -0
  111. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/export_bundle.py +0 -0
  112. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/image_review.py +0 -0
  113. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/__init__.py +0 -0
  114. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/input.py +0 -0
  115. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/models.py +0 -0
  116. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/schemas.py +0 -0
  117. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/service.py +0 -0
  118. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/tasks.py +0 -0
  119. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/json_contract.py +0 -0
  120. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
  121. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
  122. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
  123. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/semantic_scene_contract.py +0 -0
  124. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/time_contract.py +0 -0
  125. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/training_identity.py +0 -0
  126. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/__init__.py +0 -0
  127. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/contract.py +0 -0
  128. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
  129. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/report.py +0 -0
  130. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  131. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
  132. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
  133. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.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.43.0rc4
3
+ Version: 0.44.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
@@ -264,8 +264,8 @@ identity manifests, and update plans use one schema field only:
264
264
  `clip_label_schema` are not aliases and are rejected. A ready platform record
265
265
  without the complete identity is also rejected.
266
266
 
267
- `x4d-devkit dataset inspect` writes `x4d-remote-identity-v6`, and
268
- `x4d-devkit dataset diff` writes `x4d-update-plan-v8`. The v8 plan records the
267
+ `x4d-devkit dataset inspect` writes `x4d-remote-identity-v7`, and
268
+ `x4d-devkit dataset diff` writes `x4d-update-plan-v9`. The v8 plan records the
269
269
  requested materialization policy, each cache's reviewed samples/sweeps
270
270
  materialization revisions, and every Clip's reviewed target and local
271
271
  predecessor identities.
@@ -221,8 +221,8 @@ identity manifests, and update plans use one schema field only:
221
221
  `clip_label_schema` are not aliases and are rejected. A ready platform record
222
222
  without the complete identity is also rejected.
223
223
 
224
- `x4d-devkit dataset inspect` writes `x4d-remote-identity-v6`, and
225
- `x4d-devkit dataset diff` writes `x4d-update-plan-v8`. The v8 plan records the
224
+ `x4d-devkit dataset inspect` writes `x4d-remote-identity-v7`, and
225
+ `x4d-devkit dataset diff` writes `x4d-update-plan-v9`. The v8 plan records the
226
226
  requested materialization policy, each cache's reviewed samples/sweeps
227
227
  materialization revisions, and every Clip's reviewed target and local
228
228
  predecessor identities.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.43.0rc4"
7
+ version = "0.44.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"
@@ -77,6 +77,11 @@ def test_top_level_exports_only_stable_sdk_surface():
77
77
 
78
78
  assert set(x4d_devkit.__all__) == {
79
79
  "__version__",
80
+ "VECTOR_MAP_CONTRACT_VERSION",
81
+ "VectorMapDocument",
82
+ "parse_vector_map",
83
+ "parse_vector_map_change",
84
+ "vector_map_geometry_rules",
80
85
  "ANNOTATION_CONTRACT_VERSION",
81
86
  "CLEAN_BASEMAP_CONTRACT_VERSION",
82
87
  "CleanBasemapManifest",
@@ -31,6 +31,7 @@ def _identity(**overrides):
31
31
  "pose_revision": "pose",
32
32
  "annotation_revision": current_work_session_payload()["annotation_revision"],
33
33
  "semantic_scene_revision": None,
34
+ "vector_map_revision": None,
34
35
  "label_schema": schema_identity(),
35
36
  "business_metadata_revision": "business-metadata",
36
37
  "clip_schema_version": "0.17",
@@ -209,6 +210,7 @@ def test_clip_identity_direct_constructor_requires_explicit_pose_revision():
209
210
  label_schema=label_schema,
210
211
  pose_revision=None,
211
212
  semantic_scene_revision=None,
213
+ vector_map_revision=None,
212
214
  )
213
215
  assert identity.to_dict()["pose_revision"] is None
214
216
 
@@ -46,6 +46,7 @@ def _identity(clip_id: str = "clip-a", **overrides):
46
46
  "pose_revision": "sha256:pose",
47
47
  "annotation_revision": "sha256:annotation",
48
48
  "semantic_scene_revision": None,
49
+ "vector_map_revision": None,
49
50
  "label_schema": schema_identity(),
50
51
  "business_metadata_revision": "sha256:business-metadata",
51
52
  "clip_schema_version": "0.17",
@@ -289,6 +290,7 @@ def test_update_plan_maps_each_change_to_independent_portable_action():
289
290
  "refresh_pose": ["clip-a"],
290
291
  "refresh_annotation": ["clip-a"],
291
292
  "refresh_semantic_scene": [],
293
+ "refresh_vector_map": [],
292
294
  "refresh_label_schema": ["clip-a"],
293
295
  "refresh_business_metadata": ["clip-a"],
294
296
  "rematerialize_clip": [],
@@ -459,6 +461,7 @@ def _local_from_work_session(payload):
459
461
  "pose_revision",
460
462
  "annotation_revision",
461
463
  "semantic_scene_revision",
464
+ "vector_map_revision",
462
465
  "label_schema",
463
466
  "business_metadata_revision",
464
467
  "clip_schema_version",
@@ -855,7 +858,7 @@ def test_sync_composes_schema_and_annotation_into_one_clip_update(tmp_path):
855
858
  result = sync_update_plan(client, plan=plan, data_root=tmp_path, workers=1)
856
859
 
857
860
  assert result["schema_version"] == SYNC_RESULT_SCHEMA_VERSION
858
- assert result["schema_version"] == "x4d-sync-result-v8"
861
+ assert result["schema_version"] == "x4d-sync-result-v9"
859
862
  assert result["source_transfer"] == {
860
863
  "planned_objects": 0,
861
864
  "planned_bytes": 0,
@@ -106,6 +106,7 @@ def test_source_identity_projects_only_pure_inference_dependencies():
106
106
  "pose_revision": "pose-1",
107
107
  "annotation_revision": _digest("a"),
108
108
  "semantic_scene_revision": _digest("d"),
109
+ "vector_map_revision": None,
109
110
  "label_schema": schema_identity(),
110
111
  "clip_schema_version": "0.17",
111
112
  "ready": True,
@@ -282,6 +282,7 @@ def test_batch_resolution_deduplicates_by_task_free_binding():
282
282
  "pose_revision": "pose-a",
283
283
  "annotation_revision": "ann-a",
284
284
  "semantic_scene_revision": None,
285
+ "vector_map_revision": None,
285
286
  "business_metadata_revision": "business-a",
286
287
  "label_schema": identity,
287
288
  "clip_schema_version": "0.17",
@@ -298,6 +299,7 @@ def test_batch_resolution_deduplicates_by_task_free_binding():
298
299
  "pose_revision": "pose-b",
299
300
  "annotation_revision": "ann-b",
300
301
  "semantic_scene_revision": None,
302
+ "vector_map_revision": None,
301
303
  "business_metadata_revision": "business-b",
302
304
  "label_schema": identity,
303
305
  "clip_schema_version": "0.17",
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
11
11
 
12
12
 
13
13
  def test_package_version_is_current_release():
14
- assert x4d_devkit.__version__ == "0.43.0rc4"
14
+ assert x4d_devkit.__version__ == "0.44.0rc1"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
@@ -0,0 +1,119 @@
1
+ import copy
2
+ import pytest
3
+
4
+ from tests.vector_map_fixtures import document, map_schema, map_schema_body
5
+ from tests.test_vector_map_geometry import square
6
+ from x4d_devkit.label_schema import parse_label_schema_body
7
+ from x4d_devkit.vector_map import parse_vector_map, parse_vector_map_change
8
+
9
+
10
+ def parse(value):
11
+ return parse_vector_map(value, map_schema(), clip_id="clip-a")
12
+
13
+
14
+ def test_shared_boundaries_and_directed_neighbor_survive_canonical_roundtrip():
15
+ value = document()
16
+ parsed = parse(value)
17
+ assert parsed.to_dict() == value
18
+ assert parse(parsed).revision == parsed.revision
19
+ value["features"][0]["geometry"]["coordinates"][0][2] = 100
20
+ assert parsed.to_dict()["features"][0]["geometry"]["coordinates"][0][2] == 2
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "change",
25
+ [
26
+ "opposite",
27
+ "swapped",
28
+ "range",
29
+ "dangling",
30
+ "duplicate",
31
+ "foreign_clip",
32
+ "wrong_frame",
33
+ "schema",
34
+ ],
35
+ )
36
+ def test_invalid_native_references_geometry_or_identity_fail_closed(change):
37
+ value = document()
38
+ if change == "opposite":
39
+ value["lane_segments"][0]["right_boundary"]["reverse"] = True
40
+ elif change == "swapped":
41
+ lane = value["lane_segments"][0]
42
+ lane["left_boundary"], lane["right_boundary"] = (
43
+ lane["right_boundary"],
44
+ lane["left_boundary"],
45
+ )
46
+ elif change == "range":
47
+ value["lane_segments"][0]["left_boundary"]["end_s_m"] = 100
48
+ elif change == "dangling":
49
+ value["relations"][0]["to_token"] = "missing"
50
+ elif change == "duplicate":
51
+ value["relations"].append({**value["relations"][0], "token": "another"})
52
+ elif change == "foreign_clip":
53
+ value["clip_id"] = "other"
54
+ elif change == "wrong_frame":
55
+ value["frame_id"] = "world"
56
+ elif change == "schema":
57
+ value["provenance"]["label_schema_fingerprint"] = "sha256:" + "0" * 64
58
+ with pytest.raises(ValueError):
59
+ parse(value)
60
+
61
+
62
+ def test_explicit_successor_cycle_is_legal_without_inferred_reciprocal_edges():
63
+ value = document()
64
+ value["relations"] = [
65
+ {
66
+ "token": "cycle",
67
+ "kind": "lane_successor",
68
+ "from_token": "lane-a",
69
+ "to_token": "lane-a",
70
+ "side": None,
71
+ "source_tokens": [],
72
+ }
73
+ ]
74
+ assert len(parse(value).to_dict()["relations"]) == 1
75
+
76
+
77
+ def test_complete_zero_feature_coverage_is_preserved_and_same_category_overlap_rejected():
78
+ value = document()
79
+ value.update(features=[], lane_segments=[], relations=[])
80
+ region = {
81
+ "token": "inspected",
82
+ "geometry": square(),
83
+ "categories": [{"category_id": "area", "state": "complete"}],
84
+ }
85
+ value["coverage"] = [region]
86
+ assert parse(value).to_dict()["coverage"][0]["categories"][0]["state"] == "complete"
87
+ value["coverage"].append({**copy.deepcopy(region), "token": "duplicate-region"})
88
+ with pytest.raises(ValueError, match="overlaps"):
89
+ parse(value)
90
+ value["coverage"][1]["categories"][0]["category_id"] = "boundary"
91
+ parse(value)
92
+
93
+
94
+ def test_schema_geometry_rules_are_mandatory_complete_and_do_not_supply_defaults():
95
+ body = map_schema_body()
96
+ del body["task_views"][0]["geometry_rules"]
97
+ with pytest.raises(ValueError):
98
+ parse_label_schema_body(body)
99
+ body = map_schema_body()
100
+ body["task_views"][0]["geometry_rules"][0]["geometry_types"] = ["polygon", "point"]
101
+ with pytest.raises(ValueError):
102
+ parse_label_schema_body(body)
103
+ value = document()
104
+ value["features"][0]["category_id"] = "area"
105
+ with pytest.raises(ValueError, match="does not permit"):
106
+ parse(value)
107
+
108
+
109
+ def test_component_retain_replace_and_clear_are_distinct_and_explicit():
110
+ for operation in ("retain", "clear"):
111
+ value = {"operation": operation, "base_revision": None, "document": None}
112
+ assert parse_vector_map_change(value, map_schema(), clip_id="clip-a") == value
113
+ value = {"operation": "replace", "base_revision": None, "document": document()}
114
+ assert (
115
+ parse_vector_map_change(value, map_schema(), clip_id="clip-a")["document"]
116
+ == document()
117
+ )
118
+ with pytest.raises(ValueError):
119
+ parse_vector_map_change({"document": None}, map_schema(), clip_id="clip-a")
@@ -0,0 +1,76 @@
1
+ import pytest
2
+
3
+ from x4d_devkit.vector_map_geometry import (
4
+ curve_sample,
5
+ polygon_interiors_overlap,
6
+ polygon_rings,
7
+ polyline_length,
8
+ validate_geometry,
9
+ )
10
+
11
+
12
+ def square(x=0, y=0, size=4):
13
+ return {
14
+ "type": "polygon",
15
+ "outer": [
16
+ [x, y, 2],
17
+ [x + size, y, 3],
18
+ [x + size, y + size, 4],
19
+ [x, y + size, 5],
20
+ ],
21
+ "holes": [],
22
+ }
23
+
24
+
25
+ def test_measured_xyz_arc_length_and_interpolation_preserve_height():
26
+ geometry = {"type": "polyline", "coordinates": [[0, 0, 0], [0, 0, 3], [4, 0, 3]]}
27
+ assert polyline_length(geometry) == 7
28
+ assert curve_sample(geometry, 5) == [2, 0, 3]
29
+
30
+
31
+ def test_polygon_holes_and_height_are_preserved_without_normalization():
32
+ geometry = square()
33
+ geometry["holes"] = [[[1, 1, 7], [1, 2, 8], [2, 2, 9], [2, 1, 10]]]
34
+ assert validate_geometry(geometry) == "polygon"
35
+ assert len(polygon_rings(geometry)) == 2
36
+ assert geometry["holes"][0][3][2] == 10
37
+
38
+
39
+ @pytest.mark.parametrize(
40
+ "outer",
41
+ [
42
+ [[0, 0, 0], [0, 4, 0], [4, 4, 0], [4, 0, 0]],
43
+ [[0, 0, 0], [4, 4, 0], [0, 4, 0], [4, 0, 0]],
44
+ [[0, 0, 0], [4, 0, 0], [4, 4, 0], [0, 4, 0], [0, 0, 1]],
45
+ ],
46
+ )
47
+ def test_wrong_direction_crossings_and_duplicate_xy_are_rejected(outer):
48
+ with pytest.raises(ValueError):
49
+ polygon_rings({"type": "polygon", "outer": outer, "holes": []})
50
+
51
+
52
+ def test_touching_or_external_holes_are_rejected():
53
+ geometry = square()
54
+ geometry["holes"] = [[[0, 1, 0], [0, 2, 0], [1, 2, 0], [1, 1, 0]]]
55
+ with pytest.raises(ValueError, match="strictly inside"):
56
+ polygon_rings(geometry)
57
+
58
+
59
+ def test_coverage_interior_overlap_includes_identical_regions_but_not_touching():
60
+ assert polygon_interiors_overlap(square(), square())
61
+ assert polygon_interiors_overlap(square(), square(3, 1))
62
+ assert not polygon_interiors_overlap(square(), square(4, 0))
63
+ assert not polygon_interiors_overlap(square(), square(5, 0))
64
+
65
+
66
+ def test_region_inside_another_regions_hole_is_not_overlapping():
67
+ geometry = square(size=10)
68
+ geometry["holes"] = [[[1, 1, 0], [1, 8, 0], [8, 8, 0], [8, 1, 0]]]
69
+ assert not polygon_interiors_overlap(geometry, square(2, 2, 2))
70
+ assert polygon_interiors_overlap(geometry, square(0, 0, 2))
71
+
72
+
73
+ @pytest.mark.parametrize("coordinates", [[0, True, 1], [0, 1, float("nan")], [0, 1]])
74
+ def test_points_reject_non_coordinates_without_coercion(coordinates):
75
+ with pytest.raises(ValueError, match="measured XYZ"):
76
+ validate_geometry({"type": "point", "coordinates": coordinates})
@@ -0,0 +1,147 @@
1
+ import copy
2
+ import json
3
+ from types import SimpleNamespace
4
+
5
+ import pytest
6
+
7
+ from tests.current_clip_fixtures import current_work_session_payload
8
+ from tests.vector_map_fixtures import document, map_schema_body
9
+ from tests.schema_contract_fixtures import schema_body
10
+ from x4d_devkit import (
11
+ ClipLabelSchemaSnapshot,
12
+ ClipLoader,
13
+ materialize_work_session_clip,
14
+ parse_clip_work_session,
15
+ parse_vector_map,
16
+ validate_clip,
17
+ )
18
+ from x4d_devkit.client.clip_work_sessions import ClipWorkSessionsAPI
19
+ from x4d_devkit.current_state import identity_from_work_session, diff_clip_identity
20
+ from x4d_devkit.label_schema import LabelSchemaBody, LabelSchemaRecord
21
+
22
+
23
+ def session():
24
+ value = current_work_session_payload()
25
+ body = schema_body()
26
+ addition = map_schema_body()
27
+ body["classes"].extend(addition["classes"])
28
+ body["task_views"].extend(addition["task_views"])
29
+ schema = ClipLabelSchemaSnapshot.from_record(
30
+ LabelSchemaRecord(
31
+ status="active",
32
+ fingerprint=LabelSchemaBody.from_dict(body).fingerprint,
33
+ body=body,
34
+ )
35
+ )
36
+ value["label_schema"] = schema.to_dict()
37
+ value["vector_map"] = document()
38
+ value["vector_map"]["provenance"]["label_schema_fingerprint"] = schema.fingerprint
39
+ value["vector_map_revision"] = parse_vector_map(
40
+ value["vector_map"], schema, clip_id=value["clip_id"]
41
+ ).revision
42
+ return value
43
+
44
+
45
+ def test_work_session_loader_preserves_native_map_and_rejects_mismatched_revision():
46
+ value = session()
47
+ ws = parse_clip_work_session(value)
48
+ assert ws.to_dict()["vector_map"] == value["vector_map"]
49
+ assert (
50
+ ClipLoader.from_work_session(ws).vector_map.revision == ws.vector_map_revision
51
+ )
52
+ value["vector_map_revision"] = "sha256:" + "0" * 64
53
+ with pytest.raises(ValueError, match="does not match"):
54
+ parse_clip_work_session(value)
55
+
56
+
57
+ def test_absent_component_and_previous_wire_version_fail_closed():
58
+ for mutation in ("missing", "revision", "version"):
59
+ value = current_work_session_payload()
60
+ if mutation == "missing":
61
+ del value["vector_map"]
62
+ elif mutation == "revision":
63
+ value["vector_map_revision"] = "sha256:" + "0" * 64
64
+ else:
65
+ value["contract_version"] = "x4d-clip-work-session-v9"
66
+ with pytest.raises(ValueError):
67
+ parse_clip_work_session(value)
68
+
69
+
70
+ def test_map_only_refresh_and_clear_perform_zero_asset_requests(tmp_path):
71
+ import httpx
72
+
73
+ value = session()
74
+
75
+ def download(request):
76
+ return httpx.Response(200, content=bytes(96))
77
+
78
+ with httpx.Client(transport=httpx.MockTransport(download)) as client:
79
+ result = materialize_work_session_clip(
80
+ value, output_dir=tmp_path, api_url="http://fixture", client=SimpleNamespace(_http=client)
81
+ )
82
+ assert not result["errors"], result
83
+ initial = identity_from_work_session(value)
84
+ value["vector_map"]["features"][0]["attributes"] = {}
85
+ value["vector_map"]["features"][0]["geometry"]["coordinates"][0][2] += 0.1
86
+ value["vector_map_revision"] = parse_vector_map(
87
+ value["vector_map"],
88
+ parse_clip_work_session(session()).label_schema,
89
+ clip_id=value["clip_id"],
90
+ ).revision
91
+ assert diff_clip_identity(initial, identity_from_work_session(value)).changed == (
92
+ "vector_map_changed",
93
+ )
94
+
95
+ class NoMedia:
96
+ def get(self, *args, **kwargs):
97
+ raise AssertionError("map refresh requested media")
98
+
99
+ for clear in (False, True):
100
+ if clear:
101
+ value.update(vector_map=None, vector_map_revision=None)
102
+ result = materialize_work_session_clip(
103
+ value, output_dir=tmp_path, client=NoMedia(), components={"vector_map"}
104
+ )
105
+ assert not result["errors"], result
106
+ assert result["downloaded_assets"] == []
107
+ loader = ClipLoader(tmp_path)
108
+ assert (loader.vector_map is None) == clear
109
+ assert validate_clip(tmp_path).ok
110
+ assert not (tmp_path / "vector_map.json").exists()
111
+ assert (
112
+ json.loads((tmp_path / ".cache_identity.json").read_text())[
113
+ "vector_map_revision"
114
+ ]
115
+ is None
116
+ )
117
+
118
+
119
+ def test_submit_requires_explicit_component_operation_and_pins_map_base():
120
+ ws = parse_clip_work_session(session())
121
+
122
+ class Client:
123
+ calls = []
124
+
125
+ def post(self, path, json):
126
+ self.calls.append(json)
127
+ return {"ok": True}
128
+
129
+ client = Client()
130
+ api = ClipWorkSessionsAPI(client)
131
+ with pytest.raises(TypeError):
132
+ api.submit_annotations(manifest=ws, annotations=ws.annotations)
133
+ change = {
134
+ "operation": "retain",
135
+ "base_revision": ws.vector_map_revision,
136
+ "document": None,
137
+ }
138
+ api.submit_annotations(
139
+ manifest=ws, annotations=ws.annotations, vector_map_change=change
140
+ )
141
+ assert client.calls[-1]["vector_map_change"] == change
142
+ invalid = copy.deepcopy(change)
143
+ invalid["base_revision"] = None
144
+ with pytest.raises(ValueError, match="base revision"):
145
+ api.submit_annotations(
146
+ manifest=ws, annotations=ws.annotations, vector_map_change=invalid
147
+ )
@@ -45,6 +45,8 @@ def _manifest() -> dict:
45
45
  "calibration_revision": "sha256:calib",
46
46
  "pose_revision": "sha256:pose",
47
47
  "semantic_scene_revision": None,
48
+ "vector_map_revision": None,
49
+ "vector_map": None,
48
50
  "business_metadata_revision": "sha256:business-metadata",
49
51
  "label_schema": clip_schema_snapshot(),
50
52
  "meta": {
@@ -554,7 +556,7 @@ def test_adopt_work_session_cache_only_replaces_current_identity_markers(
554
556
  assert adopted["verified_source_bytes"] > 0
555
557
  assert json.loads((tmp_path / ".cache_identity.json").read_text())[
556
558
  "schema_version"
557
- ] == "x4d-cache-identity-v1"
559
+ ] == "x4d-cache-identity-v2"
558
560
  assert json.loads((tmp_path / ".x4d_identity.json").read_text())[
559
561
  "samples_revision"
560
562
  ] == source["samples_revision"]
@@ -600,6 +602,7 @@ def test_adopt_clip_identity_cache_accepts_only_current_portable_identity(
600
602
  {
601
603
  **current_identity,
602
604
  "semantic_scene_revision": "sha256:" + "f" * 64,
605
+ "vector_map_revision": None,
603
606
  },
604
607
  output_dir=tmp_path,
605
608
  materialization_policy="full",
@@ -926,6 +929,7 @@ def test_client_open_and_submit_use_manifest_conditional_identity():
926
929
  api = ClipWorkSessionsAPI(client)
927
930
  manifest = api.open(7, "clip-a")
928
931
  result = api.submit_annotations(
932
+ vector_map_change={"operation": "retain", "base_revision": None, "document": None},
929
933
  manifest=manifest,
930
934
  annotations=_manifest()["annotations"],
931
935
  )
@@ -948,6 +952,7 @@ def test_client_submit_requires_explicit_strict_annotations():
948
952
 
949
953
  with pytest.raises(TypeError, match="annotations must be a list"):
950
954
  api.submit_annotations(
955
+ vector_map_change={"operation": "retain", "base_revision": None, "document": None},
951
956
  manifest=manifest,
952
957
  annotations=None,
953
958
  )
@@ -965,6 +970,7 @@ def test_client_submit_preserves_explicit_empty_annotations():
965
970
  client = Client()
966
971
  api = ClipWorkSessionsAPI(client)
967
972
  api.submit_annotations(
973
+ vector_map_change={"operation": "retain", "base_revision": None, "document": None},
968
974
  manifest=parse_clip_work_session(_manifest()),
969
975
  annotations=[],
970
976
  )
@@ -80,6 +80,13 @@ from x4d_devkit.semantic_scene_authoring import (
80
80
  parse_semantic_scene_authoring_work_session,
81
81
  validate_semantic_scene_authoring_semantic_upload,
82
82
  )
83
+ from x4d_devkit.vector_map import (
84
+ VECTOR_MAP_CONTRACT_VERSION,
85
+ VectorMapDocument,
86
+ parse_vector_map,
87
+ parse_vector_map_change,
88
+ vector_map_geometry_rules,
89
+ )
83
90
  from x4d_devkit.time_contract import TimeNs, canonical_epoch_ns, parse_time_ns
84
91
  from x4d_devkit.materialize import (
85
92
  adopt_clip_identity_cache,
@@ -103,6 +110,11 @@ from x4d_devkit.work_session import (
103
110
  )
104
111
 
105
112
  __all__ = [
113
+ "VECTOR_MAP_CONTRACT_VERSION",
114
+ "VectorMapDocument",
115
+ "parse_vector_map",
116
+ "parse_vector_map_change",
117
+ "vector_map_geometry_rules",
106
118
  "CLEAN_BASEMAP_CONTRACT_VERSION",
107
119
  "CleanBasemapManifest",
108
120
  "parse_clean_basemap_manifest",
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any
8
8
  import httpx
9
9
 
10
10
  from x4d_devkit.work_session import ClipWorkSessionManifest, parse_clip_work_session
11
+ from x4d_devkit.vector_map import parse_vector_map_change
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from x4d_devkit.client.client import X4DClient
@@ -105,6 +106,7 @@ class ClipWorkSessionsAPI:
105
106
  base_annotation_revision: str | None = None,
106
107
  base_label_schema_fingerprint: str | None = None,
107
108
  annotations: list[dict[str, Any]],
109
+ vector_map_change: dict[str, Any],
108
110
  ) -> dict[str, Any]:
109
111
  """Conditionally submit one complete clip annotation state.
110
112
 
@@ -146,6 +148,15 @@ class ClipWorkSessionsAPI:
146
148
  raise ValueError(f"missing required work-session submit fields: {', '.join(missing)}")
147
149
  if type(annotations) is not list:
148
150
  raise TypeError("annotations must be a list")
151
+ if ws is None:
152
+ # Without a schema snapshot only retain/clear can be validated locally.
153
+ if not isinstance(vector_map_change, dict) or vector_map_change.get("operation") == "replace":
154
+ raise ValueError("map replacement requires the current complete manifest")
155
+ map_change = parse_vector_map_change(
156
+ vector_map_change, ws.label_schema if ws else None, clip_id=clip_id
157
+ )
158
+ if ws is not None and map_change["base_revision"] != ws.vector_map_revision:
159
+ raise ValueError("map change base revision differs from its work session")
149
160
  try:
150
161
  return self._client.post(
151
162
  "/clip-work-sessions/submit-annotations",
@@ -156,6 +167,7 @@ class ClipWorkSessionsAPI:
156
167
  "base_annotation_revision": base_annotation_revision,
157
168
  "base_label_schema_fingerprint": base_label_schema_fingerprint,
158
169
  "annotations": annotations,
170
+ "vector_map_change": map_change,
159
171
  },
160
172
  )
161
173
  except httpx.HTTPStatusError as exc:
@@ -120,6 +120,8 @@ def _tables_from_work_session(manifest: ClipWorkSessionManifest) -> dict[str, An
120
120
  "annotation.json": annotation_document.to_dict(),
121
121
  "label_schema.json": manifest.label_schema.to_dict(),
122
122
  }
123
+ if manifest.vector_map is not None:
124
+ tables["vector_map.json"] = manifest.vector_map
123
125
  return tables
124
126
 
125
127
 
@@ -285,6 +287,17 @@ class ClipLoader:
285
287
  _api_url=api_url,
286
288
  )
287
289
 
290
+ @property
291
+ def vector_map(self) -> Any:
292
+ """Return the immutable native map, or None when explicitly absent."""
293
+ if "vector_map.json" not in self._tables:
294
+ return None
295
+ from x4d_devkit.vector_map import parse_vector_map
296
+
297
+ return parse_vector_map(
298
+ self._tables["vector_map.json"], self._label_schema, clip_id=self.meta.clip_id
299
+ )
300
+
288
301
  def _read_json(self, name: str) -> Any:
289
302
  return self._tables[name]
290
303