x4d-devkit 0.48.0rc3__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.0rc3 → x4d_devkit-0.48.0rc4}/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc3 → 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.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_reference.py +0 -56
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/client.py +2 -2
- 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.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/SOURCES.txt +3 -1
- x4d_devkit-0.48.0rc3/x4d_devkit/client/image_references.py +0 -147
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/LICENSE +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/README.md +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/setup.cfg +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_box.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_loader.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_matching.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_models.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_token.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_transform.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_validation.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.48.0rc3 → 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)
|
|
@@ -209,59 +209,3 @@ def test_reference_rejects_incomplete_or_misleading_coverage(change):
|
|
|
209
209
|
value["completion"] = []
|
|
210
210
|
with pytest.raises(ValueError):
|
|
211
211
|
parse_image_reference(value)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def test_typed_client_pins_source_schema_profile_and_rejects_foreign_results():
|
|
215
|
-
from x4d_devkit.client.image_references import ImageReferencesAPI
|
|
216
|
-
|
|
217
|
-
clip = parse_image_clip(clip_payload())
|
|
218
|
-
identity = clip.to_dict()["identity"]
|
|
219
|
-
state = {
|
|
220
|
-
"run_id": "ref_1",
|
|
221
|
-
"project_id": clip.to_dict()["meta"]["project_id"],
|
|
222
|
-
"clip_id": clip.clip_id,
|
|
223
|
-
"service_id": "curb",
|
|
224
|
-
"profile_id": profile().to_dict()["profile_id"],
|
|
225
|
-
"view_id": "objects",
|
|
226
|
-
"source_revision": identity["source_revision"],
|
|
227
|
-
"schema_fingerprint": identity["schema_fingerprint"],
|
|
228
|
-
"status": "completed",
|
|
229
|
-
"progress": {
|
|
230
|
-
"total": 1,
|
|
231
|
-
"processed": 1,
|
|
232
|
-
"predicted": 1,
|
|
233
|
-
"empty": 0,
|
|
234
|
-
"failed": 0,
|
|
235
|
-
"unprocessed": 0,
|
|
236
|
-
},
|
|
237
|
-
"error": None,
|
|
238
|
-
"created_at": "2026-10-26T00:00:00Z",
|
|
239
|
-
"completed_at": "2026-10-26T00:01:00Z",
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
class Transport:
|
|
243
|
-
def post(self, path, *, json):
|
|
244
|
-
assert path == "/image-references/runs"
|
|
245
|
-
assert "base_annotation_revision" not in json
|
|
246
|
-
assert json["idempotency_key"] == "repeat-safe"
|
|
247
|
-
return state
|
|
248
|
-
|
|
249
|
-
def get(self, path, **kwargs):
|
|
250
|
-
return artifact() if path.endswith("/result") else state
|
|
251
|
-
|
|
252
|
-
client = ImageReferencesAPI(Transport())
|
|
253
|
-
assert (
|
|
254
|
-
client.create(
|
|
255
|
-
clip=clip,
|
|
256
|
-
service_id="curb",
|
|
257
|
-
profile_id=state["profile_id"],
|
|
258
|
-
view_id="objects",
|
|
259
|
-
idempotency_key="repeat-safe",
|
|
260
|
-
).to_dict()
|
|
261
|
-
== state
|
|
262
|
-
)
|
|
263
|
-
assert client.result("ref_1").to_dict() == artifact()
|
|
264
|
-
with pytest.raises(ValueError, match="foreign"):
|
|
265
|
-
client.result("ref_other")
|
|
266
|
-
with pytest.raises(ValueError, match="foreign"):
|
|
267
|
-
client.get("ref_other")
|
|
@@ -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,7 +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.
|
|
28
|
+
from x4d_devkit.client.image_preannotations import ImagePreannotationsAPI
|
|
29
29
|
from x4d_devkit.client.semantic_scene import SemanticSceneAPI
|
|
30
30
|
from x4d_devkit.client.semantic_scene_authoring import (
|
|
31
31
|
SemanticSceneAuthoringWorkSessionsAPI,
|
|
@@ -75,7 +75,7 @@ class X4DClient:
|
|
|
75
75
|
self.clips = ClipsAPI(self)
|
|
76
76
|
self.image_clips = ImageClipsAPI(self)
|
|
77
77
|
self.image_work_sessions = ImageWorkSessionsAPI(self)
|
|
78
|
-
self.
|
|
78
|
+
self.image_preannotations = ImagePreannotationsAPI(self)
|
|
79
79
|
self.label_schemas = LabelSchemasAPI(self)
|
|
80
80
|
self.semantic_scene = SemanticSceneAPI(self)
|
|
81
81
|
self.semantic_scene_authoring_work_sessions = (
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""One current native preannotation, with a server-resolved startup model."""
|
|
2
|
+
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
from x4d_devkit._image_contract import identifier, require
|
|
6
|
+
from x4d_devkit.image_clip import ImageClip
|
|
7
|
+
from x4d_devkit.image_preannotation import (
|
|
8
|
+
parse_image_preannotation_current,
|
|
9
|
+
parse_image_preannotation_request,
|
|
10
|
+
parse_image_preannotation_state,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _context(clip, view_id):
|
|
15
|
+
require(type(clip) is ImageClip, "expected a parsed native Clip", "clip")
|
|
16
|
+
value = clip.to_dict()
|
|
17
|
+
identifier(view_id, "view_id")
|
|
18
|
+
require(
|
|
19
|
+
view_id in value["meta"]["view_ids"],
|
|
20
|
+
"view is not applicable to this Clip",
|
|
21
|
+
"view_id",
|
|
22
|
+
)
|
|
23
|
+
return {
|
|
24
|
+
"clip_id": clip.clip_id,
|
|
25
|
+
"project_id": value["meta"]["project_id"],
|
|
26
|
+
"view_id": view_id,
|
|
27
|
+
"source_revision": value["identity"]["source_revision"],
|
|
28
|
+
"schema_fingerprint": value["identity"]["schema_fingerprint"],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _check(result, context):
|
|
33
|
+
value = result.to_dict()
|
|
34
|
+
require(
|
|
35
|
+
all(value[key] == expected for key, expected in context.items()),
|
|
36
|
+
"response context mismatch",
|
|
37
|
+
"context",
|
|
38
|
+
)
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _path(clip_id, view_id):
|
|
43
|
+
identifier(clip_id, "clip_id")
|
|
44
|
+
identifier(view_id, "view_id")
|
|
45
|
+
return f"/image-preannotations/clips/{quote(clip_id, safe='')}/views/{quote(view_id, safe='')}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ImagePreannotationsAPI:
|
|
49
|
+
def __init__(self, client):
|
|
50
|
+
self._client = client
|
|
51
|
+
|
|
52
|
+
def request(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
clip: ImageClip,
|
|
56
|
+
view_id: str,
|
|
57
|
+
idempotency_key: str,
|
|
58
|
+
recompute: bool = False,
|
|
59
|
+
):
|
|
60
|
+
context = _context(clip, view_id)
|
|
61
|
+
identifier(idempotency_key, "idempotency_key")
|
|
62
|
+
require(type(recompute) is bool, "expected an explicit boolean", "recompute")
|
|
63
|
+
body = {key: value for key, value in context.items() if key != "project_id"}
|
|
64
|
+
body.update(idempotency_key=idempotency_key, recompute=recompute)
|
|
65
|
+
return _check(
|
|
66
|
+
parse_image_preannotation_request(
|
|
67
|
+
self._client.post("/image-preannotations/requests", json=body)
|
|
68
|
+
),
|
|
69
|
+
context,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _read(self, *, clip, view_id, suffix, parser):
|
|
73
|
+
context = _context(clip, view_id)
|
|
74
|
+
value = self._client.get(
|
|
75
|
+
_path(clip.clip_id, view_id) + suffix,
|
|
76
|
+
params={
|
|
77
|
+
key: context[key] for key in ("source_revision", "schema_fingerprint")
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
return _check(parser(value), context)
|
|
81
|
+
|
|
82
|
+
def status(self, *, clip: ImageClip, view_id: str):
|
|
83
|
+
return self._read(
|
|
84
|
+
clip=clip,
|
|
85
|
+
view_id=view_id,
|
|
86
|
+
suffix="/status",
|
|
87
|
+
parser=parse_image_preannotation_state,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def current(self, *, clip: ImageClip, view_id: str):
|
|
91
|
+
return self._read(
|
|
92
|
+
clip=clip,
|
|
93
|
+
view_id=view_id,
|
|
94
|
+
suffix="/current",
|
|
95
|
+
parser=parse_image_preannotation_current,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def cancel(self, *, clip_id: str, view_id: str, operation_id: str):
|
|
99
|
+
identifier(operation_id, "operation_id")
|
|
100
|
+
value = parse_image_preannotation_state(
|
|
101
|
+
self._client.post(
|
|
102
|
+
_path(clip_id, view_id) + "/cancel", json={"operation_id": operation_id}
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
data = value.to_dict()
|
|
106
|
+
require(
|
|
107
|
+
data["clip_id"] == clip_id and data["view_id"] == view_id,
|
|
108
|
+
"foreign cancellation scope",
|
|
109
|
+
"context",
|
|
110
|
+
)
|
|
111
|
+
require(
|
|
112
|
+
data["operation"] is not None
|
|
113
|
+
and data["operation"]["operation_id"] == operation_id,
|
|
114
|
+
"foreign cancellation operation",
|
|
115
|
+
"operation",
|
|
116
|
+
)
|
|
117
|
+
return value
|