x4d-devkit 0.47.0__tar.gz → 0.48.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.47.0 → x4d_devkit-0.48.0rc1}/PKG-INFO +4 -1
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/README.md +3 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/pyproject.toml +1 -1
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_boundary.py +6 -0
- x4d_devkit-0.48.0rc1/tests/test_category_audit.py +272 -0
- x4d_devkit-0.48.0rc1/tests/test_image_clip.py +357 -0
- x4d_devkit-0.48.0rc1/tests/test_image_geometry.py +295 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/__init__.py +14 -0
- x4d_devkit-0.48.0rc1/x4d_devkit/_image_contract.py +118 -0
- x4d_devkit-0.48.0rc1/x4d_devkit/audit.py +302 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/cli.py +65 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/assets.py +16 -1
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/client.py +2 -0
- x4d_devkit-0.48.0rc1/x4d_devkit/client/image_clips.py +107 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/projects.py +6 -1
- x4d_devkit-0.48.0rc1/x4d_devkit/image_clip.py +689 -0
- x4d_devkit-0.48.0rc1/x4d_devkit/image_geometry.py +355 -0
- x4d_devkit-0.48.0rc1/x4d_devkit/image_schema.py +350 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit.egg-info/PKG-INFO +4 -1
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit.egg-info/SOURCES.txt +9 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_models.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.47.0 → x4d_devkit-0.48.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.48.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
|
|
@@ -549,6 +549,9 @@ boundary and complete workflow.
|
|
|
549
549
|
|
|
550
550
|
## Modules
|
|
551
551
|
|
|
552
|
+
Native 2D image contracts and the read-only typed image client are documented in
|
|
553
|
+
[Native image Clip v1](docs/image-clip-contract.md). Dataset 0.18 3D readers remain strict.
|
|
554
|
+
|
|
552
555
|
| Module | Description |
|
|
553
556
|
|--------|-------------|
|
|
554
557
|
| `core` | Data models, token generation, coordinate transforms, clip loader |
|
|
@@ -506,6 +506,9 @@ boundary and complete workflow.
|
|
|
506
506
|
|
|
507
507
|
## Modules
|
|
508
508
|
|
|
509
|
+
Native 2D image contracts and the read-only typed image client are documented in
|
|
510
|
+
[Native image Clip v1](docs/image-clip-contract.md). Dataset 0.18 3D readers remain strict.
|
|
511
|
+
|
|
509
512
|
| Module | Description |
|
|
510
513
|
|--------|-------------|
|
|
511
514
|
| `core` | Data models, token generation, coordinate transforms, clip loader |
|
|
@@ -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.48.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"
|
|
@@ -76,6 +76,12 @@ def test_top_level_exports_only_stable_sdk_surface():
|
|
|
76
76
|
import x4d_devkit
|
|
77
77
|
|
|
78
78
|
assert set(x4d_devkit.__all__) == {
|
|
79
|
+
"IMAGE_GEOMETRY_CONTRACT_VERSION",
|
|
80
|
+
"ImageAttributeRanges",
|
|
81
|
+
"ImageGeometry",
|
|
82
|
+
"ImageGeometryError",
|
|
83
|
+
"parse_image_attribute_ranges",
|
|
84
|
+
"parse_image_geometry",
|
|
79
85
|
"__version__",
|
|
80
86
|
"VECTOR_MAP_CONTRACT_VERSION",
|
|
81
87
|
"VectorMapDocument",
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from tests.current_clip_fixtures import current_clip_tables
|
|
11
|
+
from x4d_devkit import cli
|
|
12
|
+
from x4d_devkit.annotation_contract import build_clip_annotations
|
|
13
|
+
from x4d_devkit.audit import audit_category_mapping, collect_category_stats
|
|
14
|
+
from x4d_devkit.training_identity import build_training_identity_manifest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _clip(path: Path, categories: list[tuple[int, str]]) -> Path:
|
|
18
|
+
"""Write valid metadata only: sensor payloads deliberately do not exist."""
|
|
19
|
+
path.mkdir()
|
|
20
|
+
tables = current_clip_tables(clip_id=path.name, sample_count=2)
|
|
21
|
+
template = tables["annotation.json"]["annotations"][0]
|
|
22
|
+
rows = []
|
|
23
|
+
for index, (frame, category) in enumerate(categories):
|
|
24
|
+
row = copy.deepcopy(template)
|
|
25
|
+
row.update(
|
|
26
|
+
annotation_token=f"annotation-{index}", sample_token=f"sample-{frame}",
|
|
27
|
+
instance_token=f"instance-{index}", category=category,
|
|
28
|
+
attributes={} if category == "pedestrian" else {"vehicle.with_driver": True},
|
|
29
|
+
)
|
|
30
|
+
rows.append(row)
|
|
31
|
+
tables["annotation.json"] = build_clip_annotations(path.name, "base_link", rows).to_dict()
|
|
32
|
+
for name, content in tables.items():
|
|
33
|
+
(path / name).write_text(json.dumps(content))
|
|
34
|
+
return path
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def clips(tmp_path):
|
|
39
|
+
return [
|
|
40
|
+
_clip(tmp_path / "a", [(0, "car"), (0, "car"), (1, "car"), (0, "truck"), (1, "pedestrian")]),
|
|
41
|
+
_clip(tmp_path / "b", [(0, "car")]),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _manifest(path, clips):
|
|
46
|
+
entries = []
|
|
47
|
+
for index, clip in enumerate(clips):
|
|
48
|
+
annotations = json.loads((clip / "annotation.json").read_text())
|
|
49
|
+
meta = json.loads((clip / "meta.json").read_text())
|
|
50
|
+
entries.append({
|
|
51
|
+
"project_id": 10 + index, "clip_id": clip.name, "source_revision": "source",
|
|
52
|
+
"samples_revision": "sha256:" + "1" * 64,
|
|
53
|
+
"sweeps_revision": "sha256:" + "2" * 64,
|
|
54
|
+
"calibration_revision": "calibration", "pose_revision": "pose",
|
|
55
|
+
"annotation_revision": annotations["annotation_revision"],
|
|
56
|
+
"semantic_scene_revision": None, "label_schema": meta["label_schema"],
|
|
57
|
+
"clip_schema_version": "0.18", "materialization_policy": "full",
|
|
58
|
+
"clip_dir": clip.name, "split": "train" if index == 0 else "validation",
|
|
59
|
+
})
|
|
60
|
+
value = build_training_identity_manifest(
|
|
61
|
+
projects=[{"project_id": 10 + index, "project_name": f"project-{index}"}
|
|
62
|
+
for index in range(len(clips))],
|
|
63
|
+
clips=entries,
|
|
64
|
+
)
|
|
65
|
+
path.write_text(json.dumps(value))
|
|
66
|
+
return path
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_counts_use_occurrences_and_distinct_samples_without_media(clips):
|
|
70
|
+
report = collect_category_stats(clip_dirs=clips).to_dict()
|
|
71
|
+
|
|
72
|
+
assert report["summary"] == {"clip_count": 2, "sample_count": 4, "annotation_count": 6}
|
|
73
|
+
assert report["categories"] == [
|
|
74
|
+
{"category": "car", "annotation_count": 4, "sample_count": 3},
|
|
75
|
+
{"category": "pedestrian", "annotation_count": 1, "sample_count": 1},
|
|
76
|
+
{"category": "truck", "annotation_count": 1, "sample_count": 1},
|
|
77
|
+
]
|
|
78
|
+
assert len(report["clips"]) == 2
|
|
79
|
+
assert report["clips"][1]["summary"]["sample_count"] == 2
|
|
80
|
+
source = report["clips"][0]["source"]
|
|
81
|
+
assert source["annotation_revision"].startswith("sha256:")
|
|
82
|
+
assert source["sample_index_revision"].startswith("sha256:")
|
|
83
|
+
assert source["sample_ids"] is None
|
|
84
|
+
assert source["label_schema"]["schema_id"] == "synthetic_road_scene"
|
|
85
|
+
assert not list(clips[0].rglob("*.bin"))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_reports_are_deterministic_and_return_isolated_data(clips):
|
|
89
|
+
stats = collect_category_stats(clip_dirs=clips)
|
|
90
|
+
report = stats.to_dict()
|
|
91
|
+
assert report == collect_category_stats(clip_dirs=list(reversed(clips))).to_dict()
|
|
92
|
+
report["clips"][0]["source"]["label_schema"]["schema_id"] = "tampered"
|
|
93
|
+
assert stats.to_dict()["clips"][0]["source"]["label_schema"]["schema_id"] == "synthetic_road_scene"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_many_to_one_mapping_unions_frames_and_separates_explicit_drops(clips):
|
|
97
|
+
stats = collect_category_stats(clip_dirs=clips)
|
|
98
|
+
result = audit_category_mapping(stats, {"truck": "Vehicle", "car": "Vehicle", "pedestrian": None, "unused": "Other"})
|
|
99
|
+
|
|
100
|
+
assert result["mapped_categories"] == [{
|
|
101
|
+
"category": "Vehicle", "annotation_count": 5, "sample_count": 3,
|
|
102
|
+
"raw_categories": ["car", "truck"],
|
|
103
|
+
}]
|
|
104
|
+
assert result["dropped_categories"] == [{"category": "pedestrian", "annotation_count": 1, "sample_count": 1}]
|
|
105
|
+
assert result["unmapped_categories"] == []
|
|
106
|
+
assert result["unused_mapping_keys"] == ["unused"]
|
|
107
|
+
assert result["summary"] == {
|
|
108
|
+
"mapped_annotation_count": 5, "dropped_annotation_count": 1, "unmapped_annotation_count": 0,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_case_sensitive_mapping_does_not_infer_unknown_category_policy(clips):
|
|
113
|
+
stats = collect_category_stats(clip_dirs=clips)
|
|
114
|
+
result = audit_category_mapping(stats, {"Car": "Vehicle", "pedestrian": None})
|
|
115
|
+
assert result["summary"] == {
|
|
116
|
+
"mapped_annotation_count": 0, "dropped_annotation_count": 1, "unmapped_annotation_count": 5,
|
|
117
|
+
}
|
|
118
|
+
assert result["unused_mapping_keys"] == ["Car"]
|
|
119
|
+
assert [row["category"] for row in result["unmapped_categories"]] == ["car", "truck"]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_empty_annotations_include_selected_empty_frames(tmp_path):
|
|
123
|
+
clip = _clip(tmp_path / "empty", [])
|
|
124
|
+
stats = collect_category_stats(clip_dirs=[clip])
|
|
125
|
+
assert stats.to_dict()["summary"] == {"clip_count": 1, "sample_count": 2, "annotation_count": 0}
|
|
126
|
+
assert stats.to_dict()["categories"] == []
|
|
127
|
+
assert audit_category_mapping(stats, {})["mapped_categories"] == []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_sample_selection_is_exact_and_counts_empty_selected_frames(clips):
|
|
131
|
+
report = collect_category_stats(
|
|
132
|
+
clip_dirs=clips, sample_ids={"a": ["sample-1"], "b": ["sample-1"]}
|
|
133
|
+
).to_dict()
|
|
134
|
+
assert report["summary"] == {"clip_count": 2, "sample_count": 2, "annotation_count": 2}
|
|
135
|
+
assert report["clips"][0]["source"]["sample_ids"] == ["sample-1"]
|
|
136
|
+
assert report["clips"][1]["summary"]["annotation_count"] == 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.parametrize("selection, message", [
|
|
140
|
+
({"a": ["sample-0"]}, "missing Clip"),
|
|
141
|
+
({"a": ["sample-0"], "b": ["sample-0"], "extra": ["sample-0"]}, "outside"),
|
|
142
|
+
({"a": ["absent"], "b": ["sample-0"]}, "unknown keyframe"),
|
|
143
|
+
({"a": ["sample-0", "sample-0"], "b": ["sample-0"]}, "duplicate"),
|
|
144
|
+
({"a": [], "b": ["sample-0"]}, "non-empty"),
|
|
145
|
+
({"a": "sample-0", "b": ["sample-0"]}, "token list"),
|
|
146
|
+
({"a": [False], "b": ["sample-0"]}, "strings"),
|
|
147
|
+
])
|
|
148
|
+
def test_invalid_sample_selection_fails_instead_of_partial_report(clips, selection, message):
|
|
149
|
+
with pytest.raises(ValueError, match=message):
|
|
150
|
+
collect_category_stats(clip_dirs=clips, sample_ids=selection)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_manifest_resolves_relative_paths_and_exact_split_with_provenance(clips, tmp_path, monkeypatch):
|
|
154
|
+
manifest = _manifest(tmp_path / "training.json", clips)
|
|
155
|
+
monkeypatch.chdir("/tmp")
|
|
156
|
+
report = collect_category_stats(manifest=manifest, split="train").to_dict()
|
|
157
|
+
assert report["summary"] == {"clip_count": 1, "sample_count": 2, "annotation_count": 5}
|
|
158
|
+
assert report["manifest"]["path"] == str(manifest)
|
|
159
|
+
assert report["manifest"]["sha256"].startswith("sha256:")
|
|
160
|
+
assert report["groups"][0]["project_id"] == 10
|
|
161
|
+
assert report["groups"][0]["split"] == "train"
|
|
162
|
+
assert report["clips"][0]["source"]["clip_dir"] == str(clips[0])
|
|
163
|
+
with pytest.raises(ValueError, match="no Clips matched"):
|
|
164
|
+
collect_category_stats(manifest=manifest, split="Train")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@pytest.mark.parametrize("field,value", [
|
|
168
|
+
("annotation_revision", "sha256:" + "0" * 64),
|
|
169
|
+
("clip_id", "different-clip"),
|
|
170
|
+
])
|
|
171
|
+
def test_stale_manifest_identity_fails(clips, tmp_path, field, value):
|
|
172
|
+
manifest = _manifest(tmp_path / "training.json", clips)
|
|
173
|
+
data = json.loads(manifest.read_text())
|
|
174
|
+
data["clips"][0][field] = value
|
|
175
|
+
manifest.write_text(json.dumps(data))
|
|
176
|
+
with pytest.raises(ValueError, match=field + " does not match"):
|
|
177
|
+
collect_category_stats(manifest=manifest)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_stale_manifest_schema_identity_fails(clips, tmp_path):
|
|
181
|
+
manifest = _manifest(tmp_path / "training.json", clips)
|
|
182
|
+
data = json.loads(manifest.read_text())
|
|
183
|
+
data["clips"][0]["label_schema"]["fingerprint"] = "sha256:" + "0" * 64
|
|
184
|
+
manifest.write_text(json.dumps(data))
|
|
185
|
+
with pytest.raises(ValueError, match="label_schema does not match"):
|
|
186
|
+
collect_category_stats(manifest=manifest)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@pytest.mark.parametrize("mutation,message", [
|
|
190
|
+
(lambda data: data.update(manifest_version="x4d-training-identity-v4"), "invalid"),
|
|
191
|
+
(lambda data: data["clips"][0].pop("clip_dir"), "requires clip_dir"),
|
|
192
|
+
(lambda data: data["clips"][0].update(ready=False), "not ready"),
|
|
193
|
+
])
|
|
194
|
+
def test_manifest_rejects_legacy_missing_and_unready_inputs(clips, tmp_path, mutation, message):
|
|
195
|
+
manifest = _manifest(tmp_path / "training.json", clips)
|
|
196
|
+
data = json.loads(manifest.read_text())
|
|
197
|
+
mutation(data)
|
|
198
|
+
manifest.write_text(json.dumps(data))
|
|
199
|
+
with pytest.raises(ValueError, match=message):
|
|
200
|
+
collect_category_stats(manifest=manifest)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_invalid_duplicate_missing_and_legacy_clips_fail(clips, tmp_path):
|
|
204
|
+
with pytest.raises(ValueError, match="duplicate Clip ID"):
|
|
205
|
+
collect_category_stats(clip_dirs=[clips[0], clips[0]])
|
|
206
|
+
with pytest.raises(FileNotFoundError):
|
|
207
|
+
collect_category_stats(clip_dirs=[tmp_path / "absent"])
|
|
208
|
+
meta_path = clips[0] / "meta.json"
|
|
209
|
+
meta = json.loads(meta_path.read_text())
|
|
210
|
+
meta["schema_version"] = "0.17"
|
|
211
|
+
meta_path.write_text(json.dumps(meta))
|
|
212
|
+
with pytest.raises(ValueError, match="contract validation failed"):
|
|
213
|
+
collect_category_stats(clip_dirs=clips)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_annotation_schema_validation_cannot_be_bypassed(clips):
|
|
217
|
+
path = clips[0] / "annotation.json"
|
|
218
|
+
data = json.loads(path.read_text())
|
|
219
|
+
data["annotations"][0]["attributes"] = {"not_a_declared_attribute": True}
|
|
220
|
+
path.write_text(json.dumps(build_clip_annotations("a", "base_link", data["annotations"]).to_dict()))
|
|
221
|
+
with pytest.raises(ValueError, match="contract validation failed"):
|
|
222
|
+
collect_category_stats(clip_dirs=clips)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@pytest.mark.parametrize("mapping", [{"": "Vehicle"}, {"car ": "Vehicle"}, {"car": ""}, {"car": False}, {"car": 1}, {"car": ["Vehicle"]}, []])
|
|
226
|
+
def test_mapping_rejects_coercions_and_ambiguous_strings(clips, mapping):
|
|
227
|
+
with pytest.raises((ValueError, TypeError)):
|
|
228
|
+
audit_category_mapping(collect_category_stats(clip_dirs=clips), mapping)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_mapping_cannot_reconstruct_sample_overlap_from_aggregate_json(clips):
|
|
232
|
+
with pytest.raises(TypeError, match="result of collect_category_stats"):
|
|
233
|
+
audit_category_mapping(collect_category_stats(clip_dirs=clips).to_dict(), {})
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.parametrize("kwargs", [{}, {"clip_dirs": []}, {"clip_dirs": "directory"}, {"clip_dirs": [], "manifest": "file"}, {"clip_dirs": [], "split": "train"}])
|
|
237
|
+
def test_input_selection_is_explicit(kwargs):
|
|
238
|
+
with pytest.raises(ValueError):
|
|
239
|
+
collect_category_stats(**kwargs)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_cli_matches_api_for_categories_and_mapping(clips, tmp_path, monkeypatch, capsys):
|
|
243
|
+
monkeypatch.setattr(sys, "argv", ["x4d-devkit", "dataset", "categories", "--clips", *map(str, clips), "--format", "json"])
|
|
244
|
+
cli.main()
|
|
245
|
+
stats = collect_category_stats(clip_dirs=clips)
|
|
246
|
+
assert json.loads(capsys.readouterr().out) == stats.to_dict()
|
|
247
|
+
mapping = {"car": "Vehicle", "truck": "Vehicle", "pedestrian": None}
|
|
248
|
+
path = tmp_path / "mapping.json"
|
|
249
|
+
path.write_text(json.dumps(mapping))
|
|
250
|
+
monkeypatch.setattr(sys, "argv", ["x4d-devkit", "dataset", "audit-mapping", "--clips", *map(str, clips), "--mapping", str(path), "--format", "json"])
|
|
251
|
+
cli.main()
|
|
252
|
+
assert json.loads(capsys.readouterr().out) == audit_category_mapping(stats, mapping)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_cli_manifest_sample_selection_and_table(clips, tmp_path, monkeypatch, capsys):
|
|
256
|
+
manifest = _manifest(tmp_path / "training.json", clips)
|
|
257
|
+
samples = tmp_path / "samples.json"
|
|
258
|
+
samples.write_text(json.dumps({"a": ["sample-1"]}))
|
|
259
|
+
monkeypatch.setattr(sys, "argv", ["x4d-devkit", "dataset", "categories", "--manifest", str(manifest), "--split", "train", "--samples", str(samples)])
|
|
260
|
+
cli.main()
|
|
261
|
+
output = capsys.readouterr().out
|
|
262
|
+
assert "1 Clips, 1 samples, 2 annotations" in output
|
|
263
|
+
assert "pedestrian" in output
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_cli_rejects_duplicate_json_mapping_keys_without_partial_output(clips, tmp_path, monkeypatch, capsys):
|
|
267
|
+
path = tmp_path / "mapping.json"
|
|
268
|
+
path.write_text('{"car":"Vehicle","car":null}')
|
|
269
|
+
monkeypatch.setattr(sys, "argv", ["x4d-devkit", "dataset", "audit-mapping", "--clips", str(clips[0]), "--mapping", str(path), "--format", "json"])
|
|
270
|
+
with pytest.raises(SystemExit, match="duplicate JSON field"):
|
|
271
|
+
cli.main()
|
|
272
|
+
assert capsys.readouterr().out == ""
|