x4d-devkit 0.48.0rc2__tar.gz → 0.48.0rc4__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.48.0rc2 → x4d_devkit-0.48.0rc4}/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/pyproject.toml +1 -1
- x4d_devkit-0.48.0rc4/tests/test_image_preannotation.py +380 -0
- x4d_devkit-0.48.0rc4/tests/test_image_reference.py +211 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/client.py +2 -0
- x4d_devkit-0.48.0rc4/x4d_devkit/client/image_preannotations.py +117 -0
- x4d_devkit-0.48.0rc4/x4d_devkit/image_preannotation.py +330 -0
- x4d_devkit-0.48.0rc4/x4d_devkit/image_reference.py +438 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/SOURCES.txt +5 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/LICENSE +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/README.md +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/setup.cfg +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_box.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_loader.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_matching.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_models.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_token.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_transform.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_validation.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "x4d-devkit"
|
|
7
|
-
version = "0.48.
|
|
7
|
+
version = "0.48.0rc4"
|
|
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,380 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from x4d_devkit import X4DClient
|
|
8
|
+
from x4d_devkit._image_contract import fingerprint
|
|
9
|
+
from x4d_devkit.image_clip import build_image_identity, parse_image_clip
|
|
10
|
+
from x4d_devkit.image_preannotation import (
|
|
11
|
+
CURRENT_CONTRACT,
|
|
12
|
+
STATE_CONTRACT,
|
|
13
|
+
REQUEST_CONTRACT,
|
|
14
|
+
SERVICE_CONTRACT,
|
|
15
|
+
parse_image_preannotation_current,
|
|
16
|
+
parse_image_preannotation_state,
|
|
17
|
+
parse_image_preannotation_request,
|
|
18
|
+
parse_image_preannotation_service,
|
|
19
|
+
parse_preannotation_operation,
|
|
20
|
+
)
|
|
21
|
+
from x4d_devkit.image_reference import IMAGE_INFERENCE_PROTOCOL, image_reference_source
|
|
22
|
+
from tests.test_image_reference import artifact, clip_payload, profile
|
|
23
|
+
from tests.image_fixtures import line_object
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def context():
|
|
27
|
+
value = clip_payload()
|
|
28
|
+
return {
|
|
29
|
+
"clip_id": value["meta"]["clip_id"],
|
|
30
|
+
"project_id": value["meta"]["project_id"],
|
|
31
|
+
"view_id": "objects",
|
|
32
|
+
"source_revision": value["identity"]["source_revision"],
|
|
33
|
+
"schema_fingerprint": value["identity"]["schema_fingerprint"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def progress():
|
|
38
|
+
return {
|
|
39
|
+
"total": 1,
|
|
40
|
+
"processed": 0,
|
|
41
|
+
"predicted": 0,
|
|
42
|
+
"empty": 0,
|
|
43
|
+
"failed": 0,
|
|
44
|
+
"unprocessed": 1,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def operation():
|
|
49
|
+
return {
|
|
50
|
+
"operation_id": "op_1",
|
|
51
|
+
"status": "running",
|
|
52
|
+
"progress": progress(),
|
|
53
|
+
"error": None,
|
|
54
|
+
"created_at": "2026-10-26T00:00:00Z",
|
|
55
|
+
"completed_at": None,
|
|
56
|
+
"can_cancel": True,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def current():
|
|
61
|
+
result = artifact()
|
|
62
|
+
return {
|
|
63
|
+
**context(),
|
|
64
|
+
"contract_version": CURRENT_CONTRACT,
|
|
65
|
+
"availability": "ready",
|
|
66
|
+
"published_at": "2026-10-26T00:01:00Z",
|
|
67
|
+
"result_digest": fingerprint(result),
|
|
68
|
+
"artifact": result,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def state():
|
|
73
|
+
return {
|
|
74
|
+
**context(),
|
|
75
|
+
"contract_version": STATE_CONTRACT,
|
|
76
|
+
"service_state": "ready",
|
|
77
|
+
"result": {
|
|
78
|
+
"availability": "ready",
|
|
79
|
+
"digest": current()["result_digest"],
|
|
80
|
+
"published_at": current()["published_at"],
|
|
81
|
+
"coverage": {
|
|
82
|
+
"total": 1,
|
|
83
|
+
"processed": 1,
|
|
84
|
+
"predicted": 1,
|
|
85
|
+
"empty": 0,
|
|
86
|
+
"failed": 0,
|
|
87
|
+
"unprocessed": 0,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
"operation": operation(),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def receipt():
|
|
95
|
+
return {
|
|
96
|
+
**context(),
|
|
97
|
+
"contract_version": REQUEST_CONTRACT,
|
|
98
|
+
"disposition": "created",
|
|
99
|
+
"operation": operation(),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_current_is_separate_from_human_revision_and_is_a_detached_snapshot():
|
|
104
|
+
value = current()
|
|
105
|
+
parsed = parse_image_preannotation_current(value)
|
|
106
|
+
value["artifact"]["coverage"][0]["objects"].clear()
|
|
107
|
+
assert parsed.to_dict()["artifact"]["coverage"][0]["objects"]
|
|
108
|
+
assert "annotation_revision" not in parsed.to_dict()
|
|
109
|
+
human = clip_payload()
|
|
110
|
+
human["image_annotation"]["objects"] = [line_object()]
|
|
111
|
+
human["identity"] = build_image_identity(human)
|
|
112
|
+
assert (
|
|
113
|
+
image_reference_source(parse_image_clip(human)).to_dict()
|
|
114
|
+
== artifact()["source"]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_successful_zero_detection_is_ready_and_never_human_completion():
|
|
119
|
+
value = current()
|
|
120
|
+
value["artifact"]["coverage"][0].update(status="empty", objects=[])
|
|
121
|
+
value["result_digest"] = fingerprint(value["artifact"])
|
|
122
|
+
result = parse_image_preannotation_current(value).to_dict()
|
|
123
|
+
assert result["availability"] == "ready"
|
|
124
|
+
assert result["artifact"]["coverage"][0]["status"] == "empty"
|
|
125
|
+
assert "completion" not in result["artifact"]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@pytest.mark.parametrize("availability", ["none", "stale"])
|
|
129
|
+
def test_absence_is_explicit_and_cannot_carry_an_old_artifact(availability):
|
|
130
|
+
value = {
|
|
131
|
+
**context(),
|
|
132
|
+
"contract_version": CURRENT_CONTRACT,
|
|
133
|
+
"availability": availability,
|
|
134
|
+
"published_at": None,
|
|
135
|
+
"result_digest": None,
|
|
136
|
+
"artifact": None,
|
|
137
|
+
}
|
|
138
|
+
assert parse_image_preannotation_current(value).to_dict() == value
|
|
139
|
+
value["artifact"] = artifact()
|
|
140
|
+
with pytest.raises(ValueError):
|
|
141
|
+
parse_image_preannotation_current(value)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_partial_output_has_complete_failed_scopes_without_old_fill():
|
|
145
|
+
payload = clip_payload()
|
|
146
|
+
sample = copy.deepcopy(payload["sample"][0])
|
|
147
|
+
sample.update(
|
|
148
|
+
id="sample_b",
|
|
149
|
+
timestamp={"sec": 11, "nanosec": 20},
|
|
150
|
+
image_ids={"cam_a": "image_b"},
|
|
151
|
+
)
|
|
152
|
+
payload["sample"].append(sample)
|
|
153
|
+
image = copy.deepcopy(payload["sample_data"][0])
|
|
154
|
+
image.update(
|
|
155
|
+
id="image_b", sample_id="sample_b", timestamp={"sec": 11, "nanosec": 20}
|
|
156
|
+
)
|
|
157
|
+
image["asset"]["path"] = "samples/cam_a/11000000020.jpg"
|
|
158
|
+
payload["sample_data"].append(image)
|
|
159
|
+
payload["image_annotation"]["completion"] += [
|
|
160
|
+
{"image_id": "image_b", "view_id": view, "state": "unprocessed"}
|
|
161
|
+
for view in payload["meta"]["view_ids"]
|
|
162
|
+
]
|
|
163
|
+
payload["identity"] = build_image_identity(payload)
|
|
164
|
+
value = current()
|
|
165
|
+
value["artifact"]["source"] = image_reference_source(
|
|
166
|
+
parse_image_clip(payload)
|
|
167
|
+
).to_dict()
|
|
168
|
+
value["source_revision"] = payload["identity"]["source_revision"]
|
|
169
|
+
value["artifact"]["coverage"].append(
|
|
170
|
+
{
|
|
171
|
+
"image_id": "image_b",
|
|
172
|
+
"status": "failed",
|
|
173
|
+
"objects": [],
|
|
174
|
+
"error": {"code": "invalid_image", "message": "Image failed"},
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
value["availability"] = "partial"
|
|
178
|
+
value["result_digest"] = fingerprint(value["artifact"])
|
|
179
|
+
parsed = parse_image_preannotation_current(value).to_dict()
|
|
180
|
+
assert parsed["artifact"]["coverage"][1]["objects"] == []
|
|
181
|
+
value["availability"] = "ready"
|
|
182
|
+
with pytest.raises(ValueError):
|
|
183
|
+
parse_image_preannotation_current(value)
|
|
184
|
+
value["availability"] = "partial"
|
|
185
|
+
value["artifact"]["coverage"][0].update(
|
|
186
|
+
status="failed", objects=[], error={"code": "failed", "message": "Image failed"}
|
|
187
|
+
)
|
|
188
|
+
value["result_digest"] = fingerprint(value["artifact"])
|
|
189
|
+
with pytest.raises(ValueError, match="successes"):
|
|
190
|
+
parse_image_preannotation_current(value)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@pytest.mark.parametrize(
|
|
194
|
+
"change",
|
|
195
|
+
[
|
|
196
|
+
"digest",
|
|
197
|
+
"source",
|
|
198
|
+
"view",
|
|
199
|
+
"project",
|
|
200
|
+
"missing_image",
|
|
201
|
+
"extra_choice",
|
|
202
|
+
"all_failed",
|
|
203
|
+
"unknown_availability",
|
|
204
|
+
],
|
|
205
|
+
)
|
|
206
|
+
def test_reject_wrong_current_context_geometry_or_selectors(change):
|
|
207
|
+
value = current()
|
|
208
|
+
if change == "digest":
|
|
209
|
+
value["result_digest"] = "sha256:" + "0" * 64
|
|
210
|
+
elif change == "source":
|
|
211
|
+
value["source_revision"] = "sha256:" + "0" * 64
|
|
212
|
+
elif change == "view":
|
|
213
|
+
value["view_id"] = "semantics"
|
|
214
|
+
elif change == "project":
|
|
215
|
+
value["project_id"] = 99
|
|
216
|
+
elif change == "missing_image":
|
|
217
|
+
value["artifact"]["coverage"] = []
|
|
218
|
+
elif change == "extra_choice":
|
|
219
|
+
value["model_version"] = "latest"
|
|
220
|
+
elif change == "all_failed":
|
|
221
|
+
value["availability"] = "partial"
|
|
222
|
+
value["artifact"]["coverage"][0].update(
|
|
223
|
+
status="failed",
|
|
224
|
+
objects=[],
|
|
225
|
+
error={"code": "failed", "message": "Image failed"},
|
|
226
|
+
)
|
|
227
|
+
value["result_digest"] = fingerprint(value["artifact"])
|
|
228
|
+
else:
|
|
229
|
+
value["availability"] = {}
|
|
230
|
+
with pytest.raises(ValueError):
|
|
231
|
+
parse_image_preannotation_current(value)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@pytest.mark.parametrize(
|
|
235
|
+
"change",
|
|
236
|
+
[
|
|
237
|
+
"timestamp",
|
|
238
|
+
"boolean_count",
|
|
239
|
+
"bad_count",
|
|
240
|
+
"terminal_time",
|
|
241
|
+
"terminal_cancel",
|
|
242
|
+
"partial_without_success",
|
|
243
|
+
"unknown_state",
|
|
244
|
+
],
|
|
245
|
+
)
|
|
246
|
+
def test_operation_state_is_strict(change):
|
|
247
|
+
value = operation()
|
|
248
|
+
if change == "timestamp":
|
|
249
|
+
value["created_at"] = "yesterday"
|
|
250
|
+
elif change == "boolean_count":
|
|
251
|
+
value["progress"]["processed"] = True
|
|
252
|
+
elif change == "bad_count":
|
|
253
|
+
value["progress"]["processed"] = 1
|
|
254
|
+
elif change == "terminal_time":
|
|
255
|
+
value["status"] = "failed"
|
|
256
|
+
elif change == "terminal_cancel":
|
|
257
|
+
value.update(status="cancelled", completed_at="2026-10-26T00:01:00Z")
|
|
258
|
+
elif change == "partial_without_success":
|
|
259
|
+
value.update(
|
|
260
|
+
status="partial", completed_at="2026-10-26T00:01:00Z", can_cancel=False
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
value["status"] = {}
|
|
264
|
+
with pytest.raises(ValueError):
|
|
265
|
+
parse_preannotation_operation(value)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_state_keeps_existing_result_available_during_or_after_failed_rerun():
|
|
269
|
+
value = state()
|
|
270
|
+
assert parse_image_preannotation_state(value).to_dict() == value
|
|
271
|
+
value["operation"].update(
|
|
272
|
+
status="failed",
|
|
273
|
+
completed_at="2026-10-26T00:01:00Z",
|
|
274
|
+
can_cancel=False,
|
|
275
|
+
error={"code": "service_unavailable", "message": "Service failed"},
|
|
276
|
+
)
|
|
277
|
+
assert (
|
|
278
|
+
parse_image_preannotation_state(value).to_dict()["result"]["availability"]
|
|
279
|
+
== "ready"
|
|
280
|
+
)
|
|
281
|
+
value["result"]["availability"] = "none"
|
|
282
|
+
with pytest.raises(ValueError):
|
|
283
|
+
parse_image_preannotation_state(value)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_receipt_rejects_model_choice_and_available_creating_work():
|
|
287
|
+
value = receipt()
|
|
288
|
+
assert parse_image_preannotation_request(value).to_dict() == value
|
|
289
|
+
value["profile_id"] = profile().to_dict()["profile_id"]
|
|
290
|
+
with pytest.raises(ValueError):
|
|
291
|
+
parse_image_preannotation_request(value)
|
|
292
|
+
value = receipt()
|
|
293
|
+
value["disposition"] = "available"
|
|
294
|
+
with pytest.raises(ValueError):
|
|
295
|
+
parse_image_preannotation_request(value)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_service_is_ready_with_one_model_and_explicit_task_outputs():
|
|
299
|
+
value = {
|
|
300
|
+
"contract_version": SERVICE_CONTRACT,
|
|
301
|
+
"protocol_version": IMAGE_INFERENCE_PROTOCOL,
|
|
302
|
+
"status": "ready",
|
|
303
|
+
"service_name": "synthetic",
|
|
304
|
+
"service_version": "test",
|
|
305
|
+
"task_ids": ["test_objects"],
|
|
306
|
+
"model": profile().to_dict(),
|
|
307
|
+
"outputs": [{"category_id": "line", "geometry_type": "polyline_2d"}],
|
|
308
|
+
"max_concurrent_tasks": 1,
|
|
309
|
+
}
|
|
310
|
+
assert parse_image_preannotation_service(value).to_dict() == value
|
|
311
|
+
value["task_ids"] *= 2
|
|
312
|
+
with pytest.raises(ValueError):
|
|
313
|
+
parse_image_preannotation_service(value)
|
|
314
|
+
value["task_ids"] = ["test_objects"]
|
|
315
|
+
value["models"] = [profile().to_dict()]
|
|
316
|
+
with pytest.raises(ValueError):
|
|
317
|
+
parse_image_preannotation_service(value)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_typed_client_resolves_no_model_or_history_and_rejects_foreign_current():
|
|
321
|
+
calls = []
|
|
322
|
+
foreign = False
|
|
323
|
+
|
|
324
|
+
def handler(request):
|
|
325
|
+
calls.append(request)
|
|
326
|
+
if request.method == "POST" and request.url.path.endswith("/requests"):
|
|
327
|
+
body = json.loads(request.content)
|
|
328
|
+
assert set(body) == {
|
|
329
|
+
"clip_id",
|
|
330
|
+
"view_id",
|
|
331
|
+
"source_revision",
|
|
332
|
+
"schema_fingerprint",
|
|
333
|
+
"idempotency_key",
|
|
334
|
+
"recompute",
|
|
335
|
+
}
|
|
336
|
+
return httpx.Response(200, json=receipt())
|
|
337
|
+
if request.url.path.endswith("/current"):
|
|
338
|
+
value = current()
|
|
339
|
+
if foreign:
|
|
340
|
+
value["clip_id"] = "foreign"
|
|
341
|
+
return httpx.Response(200, json=value)
|
|
342
|
+
if request.url.path.endswith("/cancel"):
|
|
343
|
+
assert json.loads(request.content) == {"operation_id": "op_1"}
|
|
344
|
+
return httpx.Response(200, json=state())
|
|
345
|
+
|
|
346
|
+
with X4DClient("https://platform.test") as client:
|
|
347
|
+
client._http.close()
|
|
348
|
+
client._http = httpx.Client(
|
|
349
|
+
base_url="https://platform.test", transport=httpx.MockTransport(handler)
|
|
350
|
+
)
|
|
351
|
+
clip = parse_image_clip(clip_payload())
|
|
352
|
+
api = client.image_preannotations
|
|
353
|
+
assert not hasattr(client, "image_references")
|
|
354
|
+
assert (
|
|
355
|
+
api.request(
|
|
356
|
+
clip=clip, view_id="objects", idempotency_key="request_1"
|
|
357
|
+
).to_dict()
|
|
358
|
+
== receipt()
|
|
359
|
+
)
|
|
360
|
+
assert api.current(clip=clip, view_id="objects").to_dict() == current()
|
|
361
|
+
assert api.status(clip=clip, view_id="objects").to_dict() == state()
|
|
362
|
+
assert (
|
|
363
|
+
api.cancel(
|
|
364
|
+
clip_id=clip.clip_id, view_id="objects", operation_id="op_1"
|
|
365
|
+
).to_dict()
|
|
366
|
+
== state()
|
|
367
|
+
)
|
|
368
|
+
foreign = True
|
|
369
|
+
with pytest.raises(ValueError):
|
|
370
|
+
api.current(clip=clip, view_id="objects")
|
|
371
|
+
with pytest.raises(TypeError):
|
|
372
|
+
api.request(
|
|
373
|
+
clip=clip,
|
|
374
|
+
view_id="objects",
|
|
375
|
+
idempotency_key="request_1",
|
|
376
|
+
profile_id="forbidden",
|
|
377
|
+
)
|
|
378
|
+
for call in calls:
|
|
379
|
+
assert "image-references" not in call.url.path
|
|
380
|
+
assert "annotation_revision" not in str(call.url)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
from tests.image_fixtures import (
|
|
8
|
+
IMAGE_BYTES as FIXTURE_BYTES,
|
|
9
|
+
clip_payload as base_clip_payload,
|
|
10
|
+
line_object,
|
|
11
|
+
)
|
|
12
|
+
from x4d_devkit.image_clip import build_image_identity, parse_image_clip
|
|
13
|
+
from x4d_devkit.image_reference import (
|
|
14
|
+
IMAGE_INFERENCE_PROTOCOL,
|
|
15
|
+
IMAGE_REFERENCE_CONTRACT,
|
|
16
|
+
build_image_model_profile,
|
|
17
|
+
build_image_prediction_input,
|
|
18
|
+
image_reference_source,
|
|
19
|
+
parse_image_model_profile,
|
|
20
|
+
parse_image_prediction,
|
|
21
|
+
parse_image_prediction_input,
|
|
22
|
+
parse_image_reference,
|
|
23
|
+
parse_image_reference_source,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
IMAGE_BYTES = b"\xff\xd8\xff" + FIXTURE_BYTES
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def clip_payload():
|
|
31
|
+
value = base_clip_payload()
|
|
32
|
+
value["sample_data"][0]["asset"].update(
|
|
33
|
+
size=len(IMAGE_BYTES), sha256=hashlib.sha256(IMAGE_BYTES).hexdigest()
|
|
34
|
+
)
|
|
35
|
+
value["identity"] = build_image_identity(value)
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def profile():
|
|
40
|
+
return build_image_model_profile(
|
|
41
|
+
{
|
|
42
|
+
"model_name": "test",
|
|
43
|
+
"model_version": "1",
|
|
44
|
+
"artifact": {
|
|
45
|
+
"bucket": "models",
|
|
46
|
+
"object_key": "enet/1.tar.gz",
|
|
47
|
+
"object_version": None,
|
|
48
|
+
"archive_sha256": "a" * 64,
|
|
49
|
+
"weights_sha256": "b" * 64,
|
|
50
|
+
},
|
|
51
|
+
"adapter": "test-v1",
|
|
52
|
+
"parameters": {},
|
|
53
|
+
"runtime": "cpu-test",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def source():
|
|
59
|
+
return image_reference_source(parse_image_clip(clip_payload()))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def request():
|
|
63
|
+
return build_image_prediction_input(
|
|
64
|
+
source(),
|
|
65
|
+
image_id="image_a",
|
|
66
|
+
view_id="objects",
|
|
67
|
+
profile_id=profile().to_dict()["profile_id"],
|
|
68
|
+
image_bytes=IMAGE_BYTES,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def artifact():
|
|
73
|
+
return {
|
|
74
|
+
"contract_version": IMAGE_REFERENCE_CONTRACT,
|
|
75
|
+
"run_id": "ref_1",
|
|
76
|
+
"source": source().to_dict(),
|
|
77
|
+
"profile": profile().to_dict(),
|
|
78
|
+
"view_id": "objects",
|
|
79
|
+
"coverage": [
|
|
80
|
+
{
|
|
81
|
+
"image_id": "image_a",
|
|
82
|
+
"status": "predicted",
|
|
83
|
+
"objects": [line_object()],
|
|
84
|
+
"error": None,
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_source_and_prediction_never_include_human_data_or_its_revision():
|
|
91
|
+
value = clip_payload()
|
|
92
|
+
value["image_annotation"]["objects"] = [line_object()]
|
|
93
|
+
value["identity"] = build_image_identity(value)
|
|
94
|
+
with_labels = image_reference_source(parse_image_clip(value))
|
|
95
|
+
assert with_labels.to_dict() == source().to_dict()
|
|
96
|
+
assert (
|
|
97
|
+
not {"image_annotation", "annotation_revision", "completion"}
|
|
98
|
+
& with_labels.to_dict().keys()
|
|
99
|
+
)
|
|
100
|
+
encoded = request().to_dict()
|
|
101
|
+
assert "image_annotation" not in str(encoded)
|
|
102
|
+
assert "annotation_revision" not in str(encoded)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_profile_binds_all_weights_adapter_parameters_and_runtime():
|
|
106
|
+
value = profile().to_dict()
|
|
107
|
+
for key in ("model_version", "adapter", "runtime"):
|
|
108
|
+
changed = copy.deepcopy(value)
|
|
109
|
+
changed[key] += "new"
|
|
110
|
+
with pytest.raises(ValueError, match="fingerprint"):
|
|
111
|
+
parse_image_model_profile(changed)
|
|
112
|
+
value["parameters"]["threshold"] = 0.7
|
|
113
|
+
with pytest.raises(ValueError, match="fingerprint"):
|
|
114
|
+
parse_image_model_profile(value)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize(
|
|
118
|
+
"change", ["gt", "digest", "bytes", "size", "schema", "calibration", "view"]
|
|
119
|
+
)
|
|
120
|
+
def test_reject_source_or_prediction_identity_drift(change):
|
|
121
|
+
value = request().to_dict()
|
|
122
|
+
if change == "gt":
|
|
123
|
+
value["objects"] = []
|
|
124
|
+
if change == "digest":
|
|
125
|
+
value["image"]["asset"]["sha256"] = "0" * 64
|
|
126
|
+
if change == "bytes":
|
|
127
|
+
value["image_base64"] = "!invalid"
|
|
128
|
+
if change == "size":
|
|
129
|
+
value["image"]["asset"]["size"] += 1
|
|
130
|
+
if change == "schema":
|
|
131
|
+
value["context"]["schema_fingerprint"] = "sha256:" + "0" * 64
|
|
132
|
+
if change == "calibration":
|
|
133
|
+
value["calibrated_sensor"]["width"] += 1
|
|
134
|
+
if change == "view":
|
|
135
|
+
value["context"]["view_id"] = "semantics"
|
|
136
|
+
with pytest.raises(ValueError):
|
|
137
|
+
parse_image_prediction_input(value)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_source_rejects_revision_drift_and_extra_annotation():
|
|
141
|
+
value = source().to_dict()
|
|
142
|
+
value["sample_data"][0]["asset"]["sha256"] = "0" * 64
|
|
143
|
+
with pytest.raises(ValueError, match="source identity"):
|
|
144
|
+
parse_image_reference_source(value)
|
|
145
|
+
value = source().to_dict()
|
|
146
|
+
value["image_annotation"] = {}
|
|
147
|
+
with pytest.raises(ValueError):
|
|
148
|
+
parse_image_reference_source(value)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_prediction_echo_and_native_geometry_schema_validation():
|
|
152
|
+
req = request()
|
|
153
|
+
body = req.to_dict()
|
|
154
|
+
output = {
|
|
155
|
+
"protocol_version": IMAGE_INFERENCE_PROTOCOL,
|
|
156
|
+
"context": body["context"],
|
|
157
|
+
"profile_id": body["profile_id"],
|
|
158
|
+
"image_id": "image_a",
|
|
159
|
+
"objects": [line_object()],
|
|
160
|
+
}
|
|
161
|
+
assert parse_image_prediction(output, request=req).to_dict() == output
|
|
162
|
+
output["objects"][0]["geometry"]["vertices"][0]["x"] = 9000
|
|
163
|
+
with pytest.raises(ValueError):
|
|
164
|
+
parse_image_prediction(output, request=req)
|
|
165
|
+
output["objects"] = []
|
|
166
|
+
output["profile_id"] = "sha256:" + "0" * 64
|
|
167
|
+
with pytest.raises(ValueError, match="context"):
|
|
168
|
+
parse_image_prediction(output, request=req)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@pytest.mark.parametrize("state", ["predicted", "empty", "failed", "unprocessed"])
|
|
172
|
+
def test_reference_states_are_explicit_and_not_human_completion(state):
|
|
173
|
+
value = artifact()
|
|
174
|
+
row = value["coverage"][0]
|
|
175
|
+
row["status"] = state
|
|
176
|
+
if state != "predicted":
|
|
177
|
+
row["objects"] = []
|
|
178
|
+
if state == "failed":
|
|
179
|
+
row["error"] = {"code": "model_failed", "message": "Model execution failed"}
|
|
180
|
+
parsed = parse_image_reference(value)
|
|
181
|
+
assert parsed.to_dict() == value
|
|
182
|
+
assert "completion" not in parsed.to_dict()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@pytest.mark.parametrize(
|
|
186
|
+
"change",
|
|
187
|
+
[
|
|
188
|
+
"missing",
|
|
189
|
+
"duplicate",
|
|
190
|
+
"foreign-image",
|
|
191
|
+
"wrong-occupancy",
|
|
192
|
+
"missing-error",
|
|
193
|
+
"extra-gt",
|
|
194
|
+
],
|
|
195
|
+
)
|
|
196
|
+
def test_reference_rejects_incomplete_or_misleading_coverage(change):
|
|
197
|
+
value = artifact()
|
|
198
|
+
if change == "missing":
|
|
199
|
+
value["coverage"] = []
|
|
200
|
+
if change == "duplicate":
|
|
201
|
+
value["coverage"] *= 2
|
|
202
|
+
if change == "foreign-image":
|
|
203
|
+
value["coverage"][0]["image_id"] = "foreign"
|
|
204
|
+
if change == "wrong-occupancy":
|
|
205
|
+
value["coverage"][0]["status"] = "empty"
|
|
206
|
+
if change == "missing-error":
|
|
207
|
+
value["coverage"][0].update(status="failed", objects=[])
|
|
208
|
+
if change == "extra-gt":
|
|
209
|
+
value["completion"] = []
|
|
210
|
+
with pytest.raises(ValueError):
|
|
211
|
+
parse_image_reference(value)
|
|
@@ -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.48.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.48.0rc4"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -25,6 +25,7 @@ from x4d_devkit.client.dataset_identity import DatasetIdentityAPI
|
|
|
25
25
|
from x4d_devkit.client.label_schemas import LabelSchemasAPI
|
|
26
26
|
from x4d_devkit.client.image_clips import ImageClipsAPI
|
|
27
27
|
from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
|
|
28
|
+
from x4d_devkit.client.image_preannotations import ImagePreannotationsAPI
|
|
28
29
|
from x4d_devkit.client.semantic_scene import SemanticSceneAPI
|
|
29
30
|
from x4d_devkit.client.semantic_scene_authoring import (
|
|
30
31
|
SemanticSceneAuthoringWorkSessionsAPI,
|
|
@@ -74,6 +75,7 @@ class X4DClient:
|
|
|
74
75
|
self.clips = ClipsAPI(self)
|
|
75
76
|
self.image_clips = ImageClipsAPI(self)
|
|
76
77
|
self.image_work_sessions = ImageWorkSessionsAPI(self)
|
|
78
|
+
self.image_preannotations = ImagePreannotationsAPI(self)
|
|
77
79
|
self.label_schemas = LabelSchemasAPI(self)
|
|
78
80
|
self.semantic_scene = SemanticSceneAPI(self)
|
|
79
81
|
self.semantic_scene_authoring_work_sessions = (
|