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.
- {x4d_devkit-0.43.0rc4/x4d_devkit.egg-info → x4d_devkit-0.44.0rc1}/PKG-INFO +3 -3
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/README.md +2 -2
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/pyproject.toml +1 -1
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_boundary.py +5 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_current_state.py +2 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_dataset_identity.py +4 -1
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_schemas.py +1 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_label_schemas.py +2 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_sdk_api.py +1 -1
- x4d_devkit-0.44.0rc1/tests/test_vector_map.py +119 -0
- x4d_devkit-0.44.0rc1/tests/test_vector_map_geometry.py +76 -0
- x4d_devkit-0.44.0rc1/tests/test_vector_map_session.py +147 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_work_session.py +7 -1
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/__init__.py +12 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clip_work_sessions.py +12 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/loader.py +13 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/current_state.py +15 -8
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/dataset_identity.py +13 -7
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/label_schema.py +53 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/materialize.py +27 -1
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/validator.py +8 -1
- x4d_devkit-0.44.0rc1/x4d_devkit/vector_map.py +371 -0
- x4d_devkit-0.44.0rc1/x4d_devkit/vector_map_geometry.py +221 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/work_session.py +21 -1
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1/x4d_devkit.egg-info}/PKG-INFO +3 -3
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/SOURCES.txt +5 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clean_basemap_preannotation.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_models.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.43.0rc4 → x4d_devkit-0.44.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {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.
|
|
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-
|
|
268
|
-
`x4d-devkit dataset diff` writes `x4d-update-plan-
|
|
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-
|
|
225
|
-
`x4d-devkit dataset diff` writes `x4d-update-plan-
|
|
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.
|
|
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-
|
|
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.
|
|
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-
|
|
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
|
|