x4d-devkit 0.22.0rc2__tar.gz → 0.23.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.22.0rc2 → x4d_devkit-0.23.0rc1}/PKG-INFO +8 -6
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/README.md +7 -5
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/pyproject.toml +1 -1
- x4d_devkit-0.23.0rc1/tests/test_annotation_contract.py +193 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_boundary.py +8 -0
- x4d_devkit-0.23.0rc1/tests/test_contract_validation.py +382 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_current_state.py +3 -3
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_current_state_identity_consumers_example.py +5 -2
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_dataset_identity.py +18 -13
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_export_bundle.py +4 -4
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_loader.py +2 -1
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_models.py +2 -2
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_nuscenes_converter.py +23 -9
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_sdk_api.py +4 -4
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_training_identity.py +3 -3
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_validation.py +57 -7
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_work_session.py +41 -33
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/__init__.py +18 -0
- x4d_devkit-0.23.0rc1/x4d_devkit/annotation_contract.py +428 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/clip_work_sessions.py +1 -5
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/converters/nuscenes.py +27 -54
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/core/loader.py +39 -10
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/dataset_contract.py +2 -2
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/export_bundle.py +1 -1
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/materialize.py +5 -1
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/validation/contract.py +18 -365
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/validation/platform_pose.py +1 -1
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/validation/validator.py +54 -8
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/work_session.py +17 -8
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit.egg-info/PKG-INFO +8 -6
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit.egg-info/SOURCES.txt +2 -0
- x4d_devkit-0.22.0rc2/tests/test_contract_validation.py +0 -443
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_inference_builders.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/converters/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/builders.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.22.0rc2 → x4d_devkit-0.23.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.23.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
|
|
@@ -66,7 +66,7 @@ Design decisions for this boundary are tracked in GitHub issues, starting with:
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
pip install x4d-devkit==0.
|
|
69
|
+
pip install x4d-devkit==0.23.0rc1
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
Devkit is released independently as an immutable wheel. X-4D, XPoints, and
|
|
@@ -105,7 +105,10 @@ validation boundary.
|
|
|
105
105
|
Shared validation rules for platform submit, X-Points preflight, local clip
|
|
106
106
|
validation, and training consumers are documented in
|
|
107
107
|
[docs/shared-contract-validation.md](docs/shared-contract-validation.md).
|
|
108
|
-
|
|
108
|
+
The Dataset 0.12 single-file annotation authority, semantic revision, and
|
|
109
|
+
derived track views are documented in
|
|
110
|
+
[docs/annotation-contract.md](docs/annotation-contract.md).
|
|
111
|
+
Portable export producers and consumers share the exact `0.12` manifest
|
|
109
112
|
parser documented in
|
|
110
113
|
[docs/export-bundle-contract.md](docs/export-bundle-contract.md).
|
|
111
114
|
`examples/current_state_identity_consumers.py` provides an offline downstream
|
|
@@ -119,7 +122,7 @@ Included:
|
|
|
119
122
|
workflows such as clip listing/download, screening preview, model/checkpoint
|
|
120
123
|
registration, status queries, and capability discovery.
|
|
121
124
|
- Strict validation of `x4d-training-identity-v1` manifests.
|
|
122
|
-
- Strict producer-option and manifest parsing for export bundle `0.
|
|
125
|
+
- Strict producer-option and manifest parsing for export bundle `0.12`.
|
|
123
126
|
- External inference service wrappers for third-party model-serving projects.
|
|
124
127
|
|
|
125
128
|
Excluded:
|
|
@@ -233,7 +236,7 @@ comparison = compare_project_label_schema_snapshot(stored_snapshot, current)
|
|
|
233
236
|
assert comparison.status == "same"
|
|
234
237
|
```
|
|
235
238
|
|
|
236
|
-
The native `0.
|
|
239
|
+
The native `0.12` clip stores only the immutable identity in
|
|
237
240
|
`meta.label_schema`. It must not contain `label_schema.json`. The complete
|
|
238
241
|
registry record is explicit runtime context for `ClipLoader`, `validate_clip`,
|
|
239
242
|
conversion, and work-session parsing; its identity must exactly match
|
|
@@ -296,7 +299,6 @@ try:
|
|
|
296
299
|
response = client.clip_work_sessions.submit_annotations(
|
|
297
300
|
manifest=manifest,
|
|
298
301
|
annotations=manifest.annotations,
|
|
299
|
-
instances=manifest.instances,
|
|
300
302
|
)
|
|
301
303
|
print(response["annotation_revision"])
|
|
302
304
|
except WorkSessionConflictError as exc:
|
|
@@ -23,7 +23,7 @@ Design decisions for this boundary are tracked in GitHub issues, starting with:
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
pip install x4d-devkit==0.
|
|
26
|
+
pip install x4d-devkit==0.23.0rc1
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Devkit is released independently as an immutable wheel. X-4D, XPoints, and
|
|
@@ -62,7 +62,10 @@ validation boundary.
|
|
|
62
62
|
Shared validation rules for platform submit, X-Points preflight, local clip
|
|
63
63
|
validation, and training consumers are documented in
|
|
64
64
|
[docs/shared-contract-validation.md](docs/shared-contract-validation.md).
|
|
65
|
-
|
|
65
|
+
The Dataset 0.12 single-file annotation authority, semantic revision, and
|
|
66
|
+
derived track views are documented in
|
|
67
|
+
[docs/annotation-contract.md](docs/annotation-contract.md).
|
|
68
|
+
Portable export producers and consumers share the exact `0.12` manifest
|
|
66
69
|
parser documented in
|
|
67
70
|
[docs/export-bundle-contract.md](docs/export-bundle-contract.md).
|
|
68
71
|
`examples/current_state_identity_consumers.py` provides an offline downstream
|
|
@@ -76,7 +79,7 @@ Included:
|
|
|
76
79
|
workflows such as clip listing/download, screening preview, model/checkpoint
|
|
77
80
|
registration, status queries, and capability discovery.
|
|
78
81
|
- Strict validation of `x4d-training-identity-v1` manifests.
|
|
79
|
-
- Strict producer-option and manifest parsing for export bundle `0.
|
|
82
|
+
- Strict producer-option and manifest parsing for export bundle `0.12`.
|
|
80
83
|
- External inference service wrappers for third-party model-serving projects.
|
|
81
84
|
|
|
82
85
|
Excluded:
|
|
@@ -190,7 +193,7 @@ comparison = compare_project_label_schema_snapshot(stored_snapshot, current)
|
|
|
190
193
|
assert comparison.status == "same"
|
|
191
194
|
```
|
|
192
195
|
|
|
193
|
-
The native `0.
|
|
196
|
+
The native `0.12` clip stores only the immutable identity in
|
|
194
197
|
`meta.label_schema`. It must not contain `label_schema.json`. The complete
|
|
195
198
|
registry record is explicit runtime context for `ClipLoader`, `validate_clip`,
|
|
196
199
|
conversion, and work-session parsing; its identity must exactly match
|
|
@@ -253,7 +256,6 @@ try:
|
|
|
253
256
|
response = client.clip_work_sessions.submit_annotations(
|
|
254
257
|
manifest=manifest,
|
|
255
258
|
annotations=manifest.annotations,
|
|
256
|
-
instances=manifest.instances,
|
|
257
259
|
)
|
|
258
260
|
print(response["annotation_revision"])
|
|
259
261
|
except WorkSessionConflictError as exc:
|
|
@@ -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.23.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"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from x4d_devkit.annotation_contract import (
|
|
8
|
+
ANNOTATION_CONTRACT_VERSION,
|
|
9
|
+
AnnotationContractError,
|
|
10
|
+
annotation_revision,
|
|
11
|
+
build_clip_annotations,
|
|
12
|
+
derive_annotation_views,
|
|
13
|
+
parse_clip_annotations,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _annotation(
|
|
18
|
+
token: str,
|
|
19
|
+
sample: str,
|
|
20
|
+
instance: str = "instance-0",
|
|
21
|
+
category: str = "car",
|
|
22
|
+
) -> dict:
|
|
23
|
+
return {
|
|
24
|
+
"annotation_token": token,
|
|
25
|
+
"sample_token": sample,
|
|
26
|
+
"instance_token": instance,
|
|
27
|
+
"category": category,
|
|
28
|
+
"bbox_3d": {
|
|
29
|
+
"translation": {"x": 1.0, "y": 2.0, "z": 3.0},
|
|
30
|
+
"size": {"length": 4.0, "width": 2.0, "height": 1.5},
|
|
31
|
+
"rotation": {"qx": 0.0, "qy": 0.0, "qz": 0.0, "qw": 1.0},
|
|
32
|
+
},
|
|
33
|
+
"num_lidar_pts": 5,
|
|
34
|
+
"velocity": None,
|
|
35
|
+
"attributes": {},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _samples() -> list[dict]:
|
|
40
|
+
return [
|
|
41
|
+
{"sample_token": "sample-0", "timestamp_us": 100},
|
|
42
|
+
{"sample_token": "sample-1", "timestamp_us": 200},
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_build_and_parse_annotation_document_is_canonical_and_order_independent():
|
|
47
|
+
later = _annotation("annotation-1", "sample-1")
|
|
48
|
+
earlier = _annotation("annotation-0", "sample-0")
|
|
49
|
+
|
|
50
|
+
first = build_clip_annotations("clip-0", [later, earlier])
|
|
51
|
+
second = build_clip_annotations("clip-0", [earlier, later])
|
|
52
|
+
|
|
53
|
+
assert first == second
|
|
54
|
+
assert first.annotation_revision == annotation_revision(
|
|
55
|
+
"clip-0", [later, earlier]
|
|
56
|
+
)
|
|
57
|
+
assert first.to_dict() == {
|
|
58
|
+
"contract_version": ANNOTATION_CONTRACT_VERSION,
|
|
59
|
+
"clip_id": "clip-0",
|
|
60
|
+
"annotation_revision": first.annotation_revision,
|
|
61
|
+
"annotations": [earlier, later],
|
|
62
|
+
}
|
|
63
|
+
assert parse_clip_annotations(first.to_dict()) == first
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize(
|
|
67
|
+
("mutation", "code"),
|
|
68
|
+
[
|
|
69
|
+
(lambda doc: doc.update(extra=True), "invalid_annotation_document"),
|
|
70
|
+
(
|
|
71
|
+
lambda doc: doc.update(contract_version="x4d-clip-annotations-v0"),
|
|
72
|
+
"invalid_annotation_contract_version",
|
|
73
|
+
),
|
|
74
|
+
(
|
|
75
|
+
lambda doc: doc.update(annotation_revision="sha256:BAD"),
|
|
76
|
+
"invalid_annotation_revision",
|
|
77
|
+
),
|
|
78
|
+
(
|
|
79
|
+
lambda doc: doc["annotations"][0].update(category="truck"),
|
|
80
|
+
"annotation_revision_mismatch",
|
|
81
|
+
),
|
|
82
|
+
(
|
|
83
|
+
lambda doc: doc["annotations"].reverse(),
|
|
84
|
+
"noncanonical_annotation_order",
|
|
85
|
+
),
|
|
86
|
+
(
|
|
87
|
+
lambda doc: doc["annotations"][0].update(prev=None),
|
|
88
|
+
"invalid_annotation_shape",
|
|
89
|
+
),
|
|
90
|
+
(
|
|
91
|
+
lambda doc: doc["annotations"][0].pop("velocity"),
|
|
92
|
+
"invalid_annotation_shape",
|
|
93
|
+
),
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
def test_parse_annotation_document_fails_closed(mutation, code):
|
|
97
|
+
raw = build_clip_annotations(
|
|
98
|
+
"clip-0",
|
|
99
|
+
[
|
|
100
|
+
_annotation("annotation-0", "sample-0"),
|
|
101
|
+
_annotation("annotation-1", "sample-1"),
|
|
102
|
+
],
|
|
103
|
+
).to_dict()
|
|
104
|
+
mutation(raw)
|
|
105
|
+
|
|
106
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
107
|
+
parse_clip_annotations(raw)
|
|
108
|
+
|
|
109
|
+
assert exc.value.code == code
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_derive_annotation_views_uses_sample_time_not_input_order():
|
|
113
|
+
later = _annotation("annotation-a", "sample-1")
|
|
114
|
+
earlier = _annotation("annotation-z", "sample-0")
|
|
115
|
+
|
|
116
|
+
views = derive_annotation_views([later, earlier], _samples())
|
|
117
|
+
linked = {row["annotation_token"]: row for row in views.annotations}
|
|
118
|
+
|
|
119
|
+
assert linked["annotation-z"]["prev"] is None
|
|
120
|
+
assert linked["annotation-z"]["next"] == "annotation-a"
|
|
121
|
+
assert linked["annotation-a"]["prev"] == "annotation-z"
|
|
122
|
+
assert linked["annotation-a"]["next"] is None
|
|
123
|
+
assert views.instance_rows() == [
|
|
124
|
+
{
|
|
125
|
+
"instance_token": "instance-0",
|
|
126
|
+
"category": "car",
|
|
127
|
+
"num_annotations": 2,
|
|
128
|
+
"first_annotation_token": "annotation-z",
|
|
129
|
+
"last_annotation_token": "annotation-a",
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_derive_annotation_views_returns_defensive_copies():
|
|
135
|
+
views = derive_annotation_views(
|
|
136
|
+
[_annotation("annotation-0", "sample-0")],
|
|
137
|
+
_samples(),
|
|
138
|
+
)
|
|
139
|
+
rows = views.annotation_rows()
|
|
140
|
+
rows[0]["category"] = "truck"
|
|
141
|
+
|
|
142
|
+
assert views.annotations[0]["category"] == "car"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_annotation_documents_and_derived_views_are_recursively_immutable():
|
|
146
|
+
source = _annotation("annotation-0", "sample-0")
|
|
147
|
+
document = build_clip_annotations("clip-0", [source])
|
|
148
|
+
views = derive_annotation_views([source], _samples())
|
|
149
|
+
|
|
150
|
+
source["category"] = "truck"
|
|
151
|
+
assert document.annotations[0]["category"] == "car"
|
|
152
|
+
with pytest.raises(TypeError):
|
|
153
|
+
document.annotations[0]["bbox_3d"]["translation"]["x"] = 99.0
|
|
154
|
+
with pytest.raises(TypeError):
|
|
155
|
+
views.instances[0]["category"] = "truck"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.mark.parametrize(
|
|
159
|
+
("annotations", "samples", "code"),
|
|
160
|
+
[
|
|
161
|
+
(
|
|
162
|
+
[
|
|
163
|
+
_annotation("annotation-0", "sample-0"),
|
|
164
|
+
_annotation("annotation-1", "sample-1", category="truck"),
|
|
165
|
+
],
|
|
166
|
+
_samples(),
|
|
167
|
+
"instance_category_mismatch",
|
|
168
|
+
),
|
|
169
|
+
(
|
|
170
|
+
[_annotation("annotation-0", "missing")],
|
|
171
|
+
_samples(),
|
|
172
|
+
"annotation_sample_not_found",
|
|
173
|
+
),
|
|
174
|
+
(
|
|
175
|
+
[
|
|
176
|
+
_annotation("annotation-0", "sample-0"),
|
|
177
|
+
_annotation("annotation-1", "sample-1"),
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
{"sample_token": "sample-0", "timestamp_us": 100},
|
|
181
|
+
{"sample_token": "sample-1", "timestamp_us": 100},
|
|
182
|
+
],
|
|
183
|
+
"ambiguous_instance_order",
|
|
184
|
+
),
|
|
185
|
+
],
|
|
186
|
+
)
|
|
187
|
+
def test_derive_annotation_views_rejects_ambiguous_tracks(
|
|
188
|
+
annotations, samples, code
|
|
189
|
+
):
|
|
190
|
+
with pytest.raises(AnnotationContractError) as exc:
|
|
191
|
+
derive_annotation_views(copy.deepcopy(annotations), copy.deepcopy(samples))
|
|
192
|
+
|
|
193
|
+
assert exc.value.code == code
|
|
@@ -73,15 +73,19 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
73
73
|
|
|
74
74
|
assert set(x4d_devkit.__all__) == {
|
|
75
75
|
"__version__",
|
|
76
|
+
"ANNOTATION_CONTRACT_VERSION",
|
|
76
77
|
"Annotation",
|
|
78
|
+
"AnnotationContractError",
|
|
77
79
|
"AssetRef",
|
|
78
80
|
"CalibratedSensor",
|
|
79
81
|
"ClipContractValidationError",
|
|
82
|
+
"ClipAnnotationDocument",
|
|
80
83
|
"ClipLoader",
|
|
81
84
|
"ClipMeta",
|
|
82
85
|
"ClipWorkSessionManifest",
|
|
83
86
|
"COORDINATE_CONTRACT_VERSION",
|
|
84
87
|
"EgoPose",
|
|
88
|
+
"DerivedAnnotationViews",
|
|
85
89
|
"EXPORT_BUNDLE_SCHEMA_VERSION",
|
|
86
90
|
"ExportBundleClip",
|
|
87
91
|
"ExportBundleContractError",
|
|
@@ -101,12 +105,16 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
101
105
|
"WorkSessionCompareResult",
|
|
102
106
|
"X4DClient",
|
|
103
107
|
"compare_work_session",
|
|
108
|
+
"annotation_revision",
|
|
109
|
+
"build_clip_annotations",
|
|
110
|
+
"derive_annotation_views",
|
|
104
111
|
"generate_ego_pose_token",
|
|
105
112
|
"generate_token",
|
|
106
113
|
"get_build_info",
|
|
107
114
|
"get_training_coordinate_contract",
|
|
108
115
|
"materialize_work_session_clip",
|
|
109
116
|
"parse_clip_work_session",
|
|
117
|
+
"parse_clip_annotations",
|
|
110
118
|
"parse_export_bundle_manifest",
|
|
111
119
|
"parse_export_options",
|
|
112
120
|
"validate_clip",
|