x4d-devkit 0.52.0rc2__tar.gz → 0.53.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.52.0rc2 → x4d_devkit-0.53.0rc1}/PKG-INFO +1 -1
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/pyproject.toml +1 -1
- x4d_devkit-0.53.0rc1/tests/test_image_schema_migration.py +283 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_schemas.py +49 -1
- x4d_devkit-0.53.0rc1/x4d_devkit/image_schema_migration.py +550 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit.egg-info/SOURCES.txt +2 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/README.md +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_annotation_document_hash.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_conditional_rules.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_enum_set.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_groups.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_schema_upgrade.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_models.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_parse_equivalence.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_project_discovery.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_groups.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_schema_upgrade.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/project_discovery.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.52.0rc2 → x4d_devkit-0.53.0rc1}/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.
|
|
7
|
+
version = "0.53.0rc1"
|
|
8
8
|
description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
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
|
|
10
|
+
from x4d_devkit.image_schema import (
|
|
11
|
+
build_image_schema_snapshot,
|
|
12
|
+
parse_image_schema_body,
|
|
13
|
+
parse_image_schema_record,
|
|
14
|
+
)
|
|
15
|
+
from x4d_devkit.image_schema_migration import (
|
|
16
|
+
MAPPING_CONTRACT,
|
|
17
|
+
ImageSchemaMigrationConflict,
|
|
18
|
+
build_image_schema_migration_preview,
|
|
19
|
+
build_image_schema_migration_receipt,
|
|
20
|
+
build_image_schema_migration_request,
|
|
21
|
+
image_schema_migration_candidate,
|
|
22
|
+
migrate_image_annotation,
|
|
23
|
+
parse_image_schema_migration_mapping,
|
|
24
|
+
parse_image_schema_migration_preview,
|
|
25
|
+
parse_image_schema_migration_request,
|
|
26
|
+
)
|
|
27
|
+
from x4d_devkit.image_schema_upgrade import additive_image_schema_changes
|
|
28
|
+
from tests.image_fixtures import clip_payload, line_object
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def snapshot(body):
|
|
32
|
+
return build_image_schema_snapshot(
|
|
33
|
+
parse_image_schema_record(
|
|
34
|
+
{
|
|
35
|
+
"contract_version": "x4d-image-label-schema-record-v1",
|
|
36
|
+
"status": "active",
|
|
37
|
+
"body": body,
|
|
38
|
+
"fingerprint": parse_image_schema_body(body).identity.fingerprint,
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def target(clip, **changes):
|
|
45
|
+
"""Non-additive 2.0.0: whole-line `visible` becomes segment `occluded`,
|
|
46
|
+
`style` becomes object-only `pattern`, and lines become groupable."""
|
|
47
|
+
body = deepcopy(clip["label_schema"]["body"])
|
|
48
|
+
body["version"] = changes.get("version", "2.0.0")
|
|
49
|
+
body["schema_id"] = changes.get("schema_id", body["schema_id"])
|
|
50
|
+
line = body["categories"][0]
|
|
51
|
+
line["groupable"] = True
|
|
52
|
+
line["attributes"] = [
|
|
53
|
+
{
|
|
54
|
+
"id": "occluded",
|
|
55
|
+
"display_name": "Occluded",
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"required": False,
|
|
58
|
+
"scopes": ["segment"],
|
|
59
|
+
"values": [],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "pattern",
|
|
63
|
+
"display_name": "Pattern",
|
|
64
|
+
"type": "enum",
|
|
65
|
+
"required": False,
|
|
66
|
+
"scopes": ["object"],
|
|
67
|
+
"values": [
|
|
68
|
+
{"id": "a", "display_name": "A"},
|
|
69
|
+
{"id": "b", "display_name": "B"},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
return snapshot(body)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def mapping(*rules):
|
|
77
|
+
return {"contract_version": MAPPING_CONTRACT, "rules": list(rules)}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def rule(scope, attribute_id, value, action):
|
|
81
|
+
return {
|
|
82
|
+
"match": {
|
|
83
|
+
"category_id": "line",
|
|
84
|
+
"scope": scope,
|
|
85
|
+
"attribute_id": attribute_id,
|
|
86
|
+
"value": value,
|
|
87
|
+
},
|
|
88
|
+
"action": action,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def with_line(obj=None):
|
|
93
|
+
clip = clip_payload()
|
|
94
|
+
clip["image_annotation"]["objects"] = [obj or line_object()]
|
|
95
|
+
clip["identity"] = build_image_identity(clip)
|
|
96
|
+
return clip
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
VISIBLE_TO_RANGE = rule(
|
|
100
|
+
"object", "visible", False, {"kind": "range", "attribute_id": "occluded", "value": True}
|
|
101
|
+
)
|
|
102
|
+
DISCARD_STYLE = rule("segment", "style", "a", {"kind": "discard"})
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_target_is_not_an_additive_upgrade():
|
|
106
|
+
clip = with_line()
|
|
107
|
+
with pytest.raises(ValueError):
|
|
108
|
+
additive_image_schema_changes(clip["label_schema"]["body"], target(clip)["body"])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_unused_vocabulary_needs_no_rule_and_keeps_the_annotation_bytes():
|
|
112
|
+
clip = with_line({**line_object(), "attributes": {}, "attribute_ranges": []})
|
|
113
|
+
candidate, report = image_schema_migration_candidate(clip, target(clip), mapping())
|
|
114
|
+
result = candidate.to_dict()
|
|
115
|
+
assert result["image_annotation"] == clip["image_annotation"]
|
|
116
|
+
assert result["identity"]["annotation_revision"] == clip["identity"]["annotation_revision"]
|
|
117
|
+
assert result["identity"]["source_revision"] == clip["identity"]["source_revision"]
|
|
118
|
+
assert result["identity"]["schema_fingerprint"] != clip["identity"]["schema_fingerprint"]
|
|
119
|
+
assert report == {"changes": [], "removed_ranges": [], "rule_usage": [], "carried_values": 0}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_every_unrepresentable_occurrence_is_listed_with_its_location():
|
|
123
|
+
clip = with_line()
|
|
124
|
+
with pytest.raises(ImageSchemaMigrationConflict) as caught:
|
|
125
|
+
image_schema_migration_candidate(clip, target(clip), mapping())
|
|
126
|
+
assert [
|
|
127
|
+
(row["code"], row["object_id"], row["range_id"], row["attribute_id"], row["value"])
|
|
128
|
+
for row in caught.value.conflicts
|
|
129
|
+
] == [
|
|
130
|
+
("value_unmapped", "line_a", None, "visible", False),
|
|
131
|
+
("value_unmapped", "line_a", "r0", "style", "a"),
|
|
132
|
+
]
|
|
133
|
+
assert caught.value.details["conflicts"] == caught.value.conflicts
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_explicit_rules_convert_and_discard_while_preserving_identity_and_geometry():
|
|
137
|
+
clip = with_line()
|
|
138
|
+
rules = mapping(VISIBLE_TO_RANGE, DISCARD_STYLE)
|
|
139
|
+
candidate, report = image_schema_migration_candidate(clip, target(clip), rules)
|
|
140
|
+
result = candidate.to_dict()
|
|
141
|
+
before, after = clip["image_annotation"]["objects"][0], result["image_annotation"]["objects"][0]
|
|
142
|
+
assert {k: v for k, v in after.items() if k not in ("attributes", "attribute_ranges")} == {
|
|
143
|
+
k: v for k, v in before.items() if k not in ("attributes", "attribute_ranges")
|
|
144
|
+
}
|
|
145
|
+
assert after["attributes"] == {}
|
|
146
|
+
[added] = after["attribute_ranges"]
|
|
147
|
+
assert added["start_vertex_id"] == "p0" and added["end_vertex_id"] == "p1"
|
|
148
|
+
assert added["attributes"] == {"occluded": True}
|
|
149
|
+
assert report["removed_ranges"] == [{"object_id": "line_a", "range_id": "r0"}]
|
|
150
|
+
assert report["rule_usage"] == [1, 1]
|
|
151
|
+
assert [(c["kind"], c["before"], c["after"]) for c in report["changes"]] == [
|
|
152
|
+
("range", False, {"attribute_id": "occluded", "value": True}),
|
|
153
|
+
("discard", "a", None),
|
|
154
|
+
]
|
|
155
|
+
for name in ("meta", "sample", "sample_data", "calibrated_sensor"):
|
|
156
|
+
assert result[name] == clip[name]
|
|
157
|
+
again, _ = image_schema_migration_candidate(clip, target(clip), rules)
|
|
158
|
+
assert again.to_dict() == result # deterministic range IDs
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_draft_annotations_migrate_with_the_same_function():
|
|
162
|
+
clip = with_line()
|
|
163
|
+
draft, report = migrate_image_annotation(
|
|
164
|
+
clip["image_annotation"],
|
|
165
|
+
target_schema=target(clip)["body"],
|
|
166
|
+
mapping=mapping(VISIBLE_TO_RANGE, DISCARD_STYLE),
|
|
167
|
+
)
|
|
168
|
+
full, _ = image_schema_migration_candidate(
|
|
169
|
+
clip, target(clip), mapping(VISIBLE_TO_RANGE, DISCARD_STYLE)
|
|
170
|
+
)
|
|
171
|
+
assert draft == full.to_dict()["image_annotation"]
|
|
172
|
+
assert report["rule_usage"] == [1, 1]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_a_mapped_value_must_be_valid_in_the_target_scope():
|
|
176
|
+
clip = with_line()
|
|
177
|
+
bad = rule("segment", "style", "a", {"kind": "set", "attribute_id": "pattern", "value": "a"})
|
|
178
|
+
with pytest.raises(ImageSchemaMigrationConflict) as caught:
|
|
179
|
+
image_schema_migration_candidate(clip, target(clip), mapping(VISIBLE_TO_RANGE, bad))
|
|
180
|
+
assert [row["code"] for row in caught.value.conflicts] == ["mapped_value_invalid"]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_two_values_cannot_land_on_the_same_attribute():
|
|
184
|
+
obj = {**line_object(), "attributes": {"visible": False, "style": "b"}, "attribute_ranges": []}
|
|
185
|
+
clip = with_line(obj)
|
|
186
|
+
rules = mapping(
|
|
187
|
+
rule("object", "visible", False, {"kind": "set", "attribute_id": "pattern", "value": "a"}),
|
|
188
|
+
rule("object", "style", "b", {"kind": "set", "attribute_id": "pattern", "value": "b"}),
|
|
189
|
+
)
|
|
190
|
+
with pytest.raises(ImageSchemaMigrationConflict) as caught:
|
|
191
|
+
image_schema_migration_candidate(clip, target(clip), rules)
|
|
192
|
+
assert [row["code"] for row in caught.value.conflicts] == ["attribute_collision"]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@pytest.mark.parametrize(
|
|
196
|
+
"rules",
|
|
197
|
+
[
|
|
198
|
+
[VISIBLE_TO_RANGE, VISIBLE_TO_RANGE],
|
|
199
|
+
[rule("segment", "style", "a", {"kind": "range", "attribute_id": "occluded", "value": True})],
|
|
200
|
+
[rule("object", "visible", False, {"kind": "rename"})],
|
|
201
|
+
[rule("object", "visible", False, {"kind": "discard", "attribute_id": "x"})],
|
|
202
|
+
[rule("image", "visible", False, {"kind": "discard"})],
|
|
203
|
+
[rule("object", "visible", 3, {"kind": "discard"})],
|
|
204
|
+
],
|
|
205
|
+
)
|
|
206
|
+
def test_mapping_grammar_is_closed(rules):
|
|
207
|
+
with pytest.raises(ValueError):
|
|
208
|
+
parse_image_schema_migration_mapping(mapping(*rules))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@pytest.mark.parametrize("change", [{"schema_id": "another"}, {"version": "1.0.0"}])
|
|
212
|
+
def test_migration_keeps_the_schema_id_and_advances_the_version(change):
|
|
213
|
+
clip = with_line({**line_object(), "attributes": {}, "attribute_ranges": []})
|
|
214
|
+
with pytest.raises(ValueError):
|
|
215
|
+
image_schema_migration_candidate(clip, target(clip, **change), mapping())
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@pytest.mark.parametrize("part", ["digest", "source", "report", "usage", "mapping", "unknown"])
|
|
219
|
+
def test_preview_rejects_tampering_even_with_a_recomputed_fingerprint(part):
|
|
220
|
+
clip = with_line()
|
|
221
|
+
value = build_image_schema_migration_preview(
|
|
222
|
+
clip, target(clip), mapping(VISIBLE_TO_RANGE, DISCARD_STYLE)
|
|
223
|
+
).to_dict()
|
|
224
|
+
if part == "source":
|
|
225
|
+
value["after_identity"]["components"]["sample"] = "sha256:" + "a" * 64
|
|
226
|
+
if part == "report":
|
|
227
|
+
value["report"]["changes"] = []
|
|
228
|
+
value["report"]["removed_ranges"] = []
|
|
229
|
+
value["report"]["rule_usage"] = [0, 0]
|
|
230
|
+
if part == "usage":
|
|
231
|
+
value["report"]["rule_usage"] = [1]
|
|
232
|
+
if part == "mapping":
|
|
233
|
+
value["mapping"]["rules"].append(VISIBLE_TO_RANGE)
|
|
234
|
+
if part == "unknown":
|
|
235
|
+
value["automatic_repair"] = True
|
|
236
|
+
value["preview_fingerprint"] = (
|
|
237
|
+
"sha256:" + "0" * 64
|
|
238
|
+
if part == "digest"
|
|
239
|
+
else fingerprint({k: v for k, v in value.items() if k != "preview_fingerprint"})
|
|
240
|
+
)
|
|
241
|
+
with pytest.raises(ValueError):
|
|
242
|
+
parse_image_schema_migration_preview(value)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_typed_client_verifies_the_exact_preview_and_receipt():
|
|
246
|
+
clip = with_line()
|
|
247
|
+
snap = target(clip)
|
|
248
|
+
rules = mapping(VISIBLE_TO_RANGE, DISCARD_STYLE)
|
|
249
|
+
preview = build_image_schema_migration_preview(clip, snap, rules).to_dict()
|
|
250
|
+
bad = {"enabled": False}
|
|
251
|
+
|
|
252
|
+
def handler(request):
|
|
253
|
+
body = json.loads(request.content)
|
|
254
|
+
if request.url.path.endswith("/image-schema-migrations/preview"):
|
|
255
|
+
assert body == {
|
|
256
|
+
"project_id": 7,
|
|
257
|
+
"clip_id": "clip_test",
|
|
258
|
+
"target_schema": snap["identity"],
|
|
259
|
+
"mapping": rules,
|
|
260
|
+
}
|
|
261
|
+
return httpx.Response(200, json=preview)
|
|
262
|
+
assert request.url.path.endswith("/image-schema-migrations/commit")
|
|
263
|
+
receipt = build_image_schema_migration_receipt(body).to_dict()
|
|
264
|
+
if bad["enabled"]:
|
|
265
|
+
receipt["idempotency_key"] = "other-request"
|
|
266
|
+
return httpx.Response(200, json=receipt)
|
|
267
|
+
|
|
268
|
+
with X4DClient("http://x4d.test", token="test") as client:
|
|
269
|
+
client._http.close()
|
|
270
|
+
client._http = httpx.Client(
|
|
271
|
+
base_url="http://x4d.test", transport=httpx.MockTransport(handler)
|
|
272
|
+
)
|
|
273
|
+
parsed = client.image_schemas.preview_migration(
|
|
274
|
+
clip=clip, target_schema=snap, mapping=rules
|
|
275
|
+
)
|
|
276
|
+
request = client.image_schemas.migration_request(parsed, "explicit-migration")
|
|
277
|
+
assert parse_image_schema_migration_request(request.to_dict())
|
|
278
|
+
receipt = client.image_schemas.commit_migration(request).to_dict()
|
|
279
|
+
assert receipt["identity"] == preview["after_identity"]
|
|
280
|
+
bad["enabled"] = True
|
|
281
|
+
with pytest.raises(ValueError, match="another request"):
|
|
282
|
+
client.image_schemas.commit_migration(request)
|
|
283
|
+
assert build_image_schema_migration_request(parsed, "k").to_dict()["preview"] == preview
|
|
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_package_version_is_current_release():
|
|
14
|
-
assert x4d_devkit.__version__ == "0.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.53.0rc1"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Separate native image registry reads and explicit admin upgrade adapters."""
|
|
1
|
+
"""Separate native image registry reads and explicit admin upgrade/migration adapters."""
|
|
2
2
|
|
|
3
3
|
from x4d_devkit._image_contract import fields, integer, require
|
|
4
4
|
from x4d_devkit.image_schema import (
|
|
@@ -12,6 +12,14 @@ from x4d_devkit.image_schema_upgrade import (
|
|
|
12
12
|
parse_image_schema_upgrade_request,
|
|
13
13
|
parse_image_schema_upgrade_receipt,
|
|
14
14
|
)
|
|
15
|
+
from x4d_devkit.image_schema_migration import (
|
|
16
|
+
build_image_schema_migration_preview,
|
|
17
|
+
build_image_schema_migration_receipt,
|
|
18
|
+
build_image_schema_migration_request,
|
|
19
|
+
parse_image_schema_migration_preview,
|
|
20
|
+
parse_image_schema_migration_receipt,
|
|
21
|
+
parse_image_schema_migration_request,
|
|
22
|
+
)
|
|
15
23
|
from x4d_devkit.label_schema import LabelSchemaBinding
|
|
16
24
|
|
|
17
25
|
|
|
@@ -113,3 +121,43 @@ class ImageSchemasAPI:
|
|
|
113
121
|
|
|
114
122
|
def upgrade_request(self, preview, idempotency_key):
|
|
115
123
|
return build_image_schema_upgrade_request(preview, idempotency_key)
|
|
124
|
+
|
|
125
|
+
def preview_migration(self, *, clip, target_schema, mapping):
|
|
126
|
+
"""Server preview must equal the locally computed exact migration."""
|
|
127
|
+
expected = build_image_schema_migration_preview(
|
|
128
|
+
clip, target_schema, mapping
|
|
129
|
+
).to_dict()
|
|
130
|
+
result = parse_image_schema_migration_preview(
|
|
131
|
+
self._client.post(
|
|
132
|
+
"/image-schema-migrations/preview",
|
|
133
|
+
json={
|
|
134
|
+
"project_id": expected["project_id"],
|
|
135
|
+
"clip_id": expected["clip_id"],
|
|
136
|
+
"target_schema": target_schema["identity"],
|
|
137
|
+
"mapping": expected["mapping"],
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
require(
|
|
142
|
+
result.to_dict() == expected,
|
|
143
|
+
"Migration preview differs from the exact Clip, target and mapping",
|
|
144
|
+
"image_schemas.migration_preview",
|
|
145
|
+
)
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
def commit_migration(self, request):
|
|
149
|
+
body = parse_image_schema_migration_request(
|
|
150
|
+
request.to_dict() if hasattr(request, "to_dict") else request
|
|
151
|
+
).to_dict()
|
|
152
|
+
result = parse_image_schema_migration_receipt(
|
|
153
|
+
self._client.post("/image-schema-migrations/commit", json=body)
|
|
154
|
+
)
|
|
155
|
+
require(
|
|
156
|
+
result.to_dict() == build_image_schema_migration_receipt(body).to_dict(),
|
|
157
|
+
"Migration receipt belongs to another request",
|
|
158
|
+
"image_schemas.migration_receipt",
|
|
159
|
+
)
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def migration_request(self, preview, idempotency_key):
|
|
163
|
+
return build_image_schema_migration_request(preview, idempotency_key)
|