x4d-devkit 0.51.0rc2__tar.gz → 0.51.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.51.0rc2 → x4d_devkit-0.51.0rc4}/PKG-INFO +1 -1
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/pyproject.toml +1 -1
- x4d_devkit-0.51.0rc4/tests/test_annotation_document_hash.py +111 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_parse_equivalence.py +6 -2
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/annotation_contract.py +84 -12
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/work_session.py +2 -1
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit.egg-info/SOURCES.txt +1 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/LICENSE +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/README.md +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/setup.cfg +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_box.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_groups.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_schema_upgrade.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_loader.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_matching.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_models.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_project_discovery.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_token.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_transform.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_validation.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_schemas.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_groups.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_schema_upgrade.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/project_discovery.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.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.51.
|
|
7
|
+
version = "0.51.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,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import hashlib
|
|
5
|
+
import random
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import rfc8785
|
|
9
|
+
|
|
10
|
+
from tests.test_annotation_contract import _annotation
|
|
11
|
+
from x4d_devkit.annotation_contract import (
|
|
12
|
+
ANNOTATION_CONTRACT_VERSION,
|
|
13
|
+
AnnotationContractError,
|
|
14
|
+
ClipAnnotationDocument,
|
|
15
|
+
build_clip_annotations,
|
|
16
|
+
parse_clip_annotations,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_EDGE_FLOATS = (0.0, -0.0, 1e21, 1e-7, 5e-324, 1.7976931348623157e308, 0.1 + 0.2, -123.456)
|
|
20
|
+
_EDGE_TEXT = ("", "é", "雪", "\U0001f697", "a\"b\\c", "
", "\x7f")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _sha(payload) -> str:
|
|
24
|
+
return f"sha256:{hashlib.sha256(rfc8785.dumps(payload)).hexdigest()}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _random_rows(rng: random.Random, count: int) -> list[dict]:
|
|
28
|
+
rows = []
|
|
29
|
+
for index in range(count):
|
|
30
|
+
row = _annotation(
|
|
31
|
+
f"ann-{rng.randrange(10**9):09d}-{index}",
|
|
32
|
+
f"sample-{rng.randrange(5)}",
|
|
33
|
+
instance=f"instance-{rng.randrange(4)}",
|
|
34
|
+
)
|
|
35
|
+
row["bbox_3d"]["translation"]["x"] = rng.choice(_EDGE_FLOATS)
|
|
36
|
+
row["bbox_3d"]["size"]["width"] = rng.random() * 10
|
|
37
|
+
row["attributes"] = {rng.choice(_EDGE_TEXT) or "k": rng.choice(_EDGE_TEXT)}
|
|
38
|
+
if rng.random() < 0.5:
|
|
39
|
+
row["velocity"] = {"vx": rng.choice(_EDGE_FLOATS), "vy": 0.0, "vz": -1.5}
|
|
40
|
+
rows.append(row)
|
|
41
|
+
for instance in {row["instance_token"] for row in rows}:
|
|
42
|
+
motion = rng.choice(("auto", "static", "dynamic"))
|
|
43
|
+
for row in rows:
|
|
44
|
+
if row["instance_token"] == instance:
|
|
45
|
+
row["motion"] = motion
|
|
46
|
+
return rows
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_single_pass_digests_match_full_rfc8785_canonicalization():
|
|
50
|
+
rng = random.Random(8785)
|
|
51
|
+
for count in (0, 1, 2, 17, 120):
|
|
52
|
+
document = build_clip_annotations("clip-a", "base_link", _random_rows(rng, count))
|
|
53
|
+
payload = document.to_dict()
|
|
54
|
+
revision_payload = {key: value for key, value in payload.items() if key != "annotation_revision"}
|
|
55
|
+
assert document.annotation_revision == _sha(revision_payload)
|
|
56
|
+
assert document.document_hash() == _sha(payload)
|
|
57
|
+
|
|
58
|
+
parsed = parse_clip_annotations(payload)
|
|
59
|
+
assert parsed.annotation_revision == document.annotation_revision
|
|
60
|
+
assert parsed.document_hash() == _sha(payload)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_document_hash_is_derived_for_directly_constructed_and_replaced_documents():
|
|
64
|
+
document = build_clip_annotations("clip-a", "base_link", _random_rows(random.Random(1), 9))
|
|
65
|
+
direct = ClipAnnotationDocument(
|
|
66
|
+
clip_id=document.clip_id,
|
|
67
|
+
annotation_frame_id=document.annotation_frame_id,
|
|
68
|
+
annotation_revision=document.annotation_revision,
|
|
69
|
+
annotations=document.annotations,
|
|
70
|
+
)
|
|
71
|
+
assert direct.document_hash() == document.document_hash()
|
|
72
|
+
|
|
73
|
+
replaced = dataclasses.replace(document, annotations=document.annotations[:3])
|
|
74
|
+
assert replaced.document_hash() == _sha(replaced.to_dict())
|
|
75
|
+
assert replaced.document_hash() != document.document_hash()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_document_hash_is_not_part_of_document_equality():
|
|
79
|
+
rows = _random_rows(random.Random(2), 5)
|
|
80
|
+
built = build_clip_annotations("clip-a", "base_link", rows)
|
|
81
|
+
parsed = parse_clip_annotations(built.to_dict())
|
|
82
|
+
plain = ClipAnnotationDocument(
|
|
83
|
+
clip_id=built.clip_id,
|
|
84
|
+
annotation_frame_id=built.annotation_frame_id,
|
|
85
|
+
annotation_revision=built.annotation_revision,
|
|
86
|
+
annotations=built.annotations,
|
|
87
|
+
)
|
|
88
|
+
assert built == parsed == plain
|
|
89
|
+
assert "_document_hash" not in {field.name for field in dataclasses.fields(built)}
|
|
90
|
+
assert built.to_dict()["contract_version"] == ANNOTATION_CONTRACT_VERSION
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_parse_reuses_only_documents_this_module_verified():
|
|
94
|
+
built = build_clip_annotations("clip-a", "base_link", _random_rows(random.Random(3), 4))
|
|
95
|
+
assert parse_clip_annotations(built) is built
|
|
96
|
+
assert parse_clip_annotations(parse_clip_annotations(built.to_dict())) is not built
|
|
97
|
+
|
|
98
|
+
forged = ClipAnnotationDocument(
|
|
99
|
+
clip_id=built.clip_id,
|
|
100
|
+
annotation_frame_id=built.annotation_frame_id,
|
|
101
|
+
annotation_revision="sha256:" + "0" * 64,
|
|
102
|
+
annotations=built.annotations,
|
|
103
|
+
)
|
|
104
|
+
forged.document_hash() # caching the hash must not mark the document verified
|
|
105
|
+
with pytest.raises(AnnotationContractError) as error:
|
|
106
|
+
parse_clip_annotations(forged)
|
|
107
|
+
assert error.value.code == "annotation_revision_mismatch"
|
|
108
|
+
|
|
109
|
+
replaced = dataclasses.replace(built, annotation_revision="sha256:" + "1" * 64)
|
|
110
|
+
with pytest.raises(AnnotationContractError):
|
|
111
|
+
parse_clip_annotations(replaced)
|
|
@@ -835,8 +835,12 @@ def test_annotation_entry_points_canonicalize_rows_only_once(monkeypatch, entry)
|
|
|
835
835
|
else:
|
|
836
836
|
assert annotation_revision(raw["clip_id"], raw["annotation_frame_id"],
|
|
837
837
|
raw["annotations"]) == raw["annotation_revision"]
|
|
838
|
-
|
|
839
|
-
|
|
838
|
+
# Revision and document hash share one serialization of every row; no call
|
|
839
|
+
# re-serializes the row list or a complete payload. A document this module
|
|
840
|
+
# already verified is returned without serializing anything.
|
|
841
|
+
row_calls = [value for value in calls if isinstance(value, dict)]
|
|
842
|
+
assert row_calls == ([] if entry == "parsed-document" else raw["annotations"])
|
|
843
|
+
assert all(isinstance(value, str) for value in calls if not isinstance(value, dict))
|
|
840
844
|
|
|
841
845
|
|
|
842
846
|
def test_parsed_annotation_documents_are_isolated_after_removing_document_deepcopy():
|
|
@@ -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.51.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.51.0rc4"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -38,6 +38,13 @@ _DOCUMENT_FIELDS = {
|
|
|
38
38
|
"annotations",
|
|
39
39
|
}
|
|
40
40
|
_REVISION_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
41
|
+
# Set only by build/parse after canonical verification; never by document_hash().
|
|
42
|
+
_VERIFIED_MARK = "_canonical_verified"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _mark_verified(document: "ClipAnnotationDocument", document_hash: str) -> None:
|
|
46
|
+
object.__setattr__(document, "_document_hash", document_hash)
|
|
47
|
+
object.__setattr__(document, _VERIFIED_MARK, True)
|
|
41
48
|
_FRAME_ID_RE = re.compile(r"^[a-z][a-z0-9_]{0,63}$")
|
|
42
49
|
|
|
43
50
|
|
|
@@ -74,6 +81,24 @@ class ClipAnnotationDocument:
|
|
|
74
81
|
tuple(_freeze_json(dict(row)) for row in self.annotations),
|
|
75
82
|
)
|
|
76
83
|
|
|
84
|
+
def document_hash(self) -> str:
|
|
85
|
+
"""Return ``sha256:`` of the RFC 8785 bytes of :meth:`to_dict`.
|
|
86
|
+
|
|
87
|
+
Documents built or parsed by this module carry it from the same single
|
|
88
|
+
canonicalization pass that produced ``annotation_revision``.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
digest = self.__dict__.get("_document_hash")
|
|
92
|
+
if digest is None:
|
|
93
|
+
_, digest = _annotation_digests(
|
|
94
|
+
self.clip_id,
|
|
95
|
+
self.annotation_frame_id,
|
|
96
|
+
_thaw_json(self.annotations),
|
|
97
|
+
stored_revision=self.annotation_revision,
|
|
98
|
+
)
|
|
99
|
+
object.__setattr__(self, "_document_hash", digest)
|
|
100
|
+
return digest
|
|
101
|
+
|
|
77
102
|
def to_dict(self) -> dict[str, Any]:
|
|
78
103
|
return {
|
|
79
104
|
"contract_version": ANNOTATION_CONTRACT_VERSION,
|
|
@@ -123,13 +148,15 @@ def build_clip_annotations(
|
|
|
123
148
|
annotation_frame_id,
|
|
124
149
|
path="annotation.annotation_frame_id",
|
|
125
150
|
)
|
|
126
|
-
revision =
|
|
127
|
-
|
|
151
|
+
revision, document_hash = _annotation_digests(parsed_clip_id, parsed_frame, canonical)
|
|
152
|
+
document = ClipAnnotationDocument(
|
|
128
153
|
clip_id=parsed_clip_id,
|
|
129
154
|
annotation_frame_id=parsed_frame,
|
|
130
155
|
annotation_revision=revision,
|
|
131
156
|
annotations=tuple(canonical),
|
|
132
157
|
)
|
|
158
|
+
_mark_verified(document, document_hash)
|
|
159
|
+
return document
|
|
133
160
|
|
|
134
161
|
|
|
135
162
|
def parse_clip_annotations(
|
|
@@ -139,6 +166,9 @@ def parse_clip_annotations(
|
|
|
139
166
|
|
|
140
167
|
i_json_checked = False
|
|
141
168
|
if isinstance(value, ClipAnnotationDocument):
|
|
169
|
+
if value.__dict__.get(_VERIFIED_MARK) is True:
|
|
170
|
+
# Built or parsed by this module: already canonical and verified.
|
|
171
|
+
return value
|
|
142
172
|
data = value.to_dict()
|
|
143
173
|
elif isinstance(value, Mapping):
|
|
144
174
|
data = dict(value)
|
|
@@ -205,7 +235,12 @@ def parse_clip_annotations(
|
|
|
205
235
|
"annotation.annotation_revision",
|
|
206
236
|
{"actual": revision},
|
|
207
237
|
)
|
|
208
|
-
expected =
|
|
238
|
+
expected, document_hash = _annotation_digests(
|
|
239
|
+
clip_id,
|
|
240
|
+
annotation_frame_id,
|
|
241
|
+
annotations,
|
|
242
|
+
stored_revision=revision,
|
|
243
|
+
)
|
|
209
244
|
if revision != expected:
|
|
210
245
|
raise AnnotationContractError(
|
|
211
246
|
"annotation_revision_mismatch",
|
|
@@ -213,12 +248,14 @@ def parse_clip_annotations(
|
|
|
213
248
|
"annotation.annotation_revision",
|
|
214
249
|
{"expected": expected, "actual": revision},
|
|
215
250
|
)
|
|
216
|
-
|
|
251
|
+
document = ClipAnnotationDocument(
|
|
217
252
|
clip_id=clip_id,
|
|
218
253
|
annotation_frame_id=annotation_frame_id,
|
|
219
254
|
annotation_revision=revision,
|
|
220
255
|
annotations=tuple(annotations),
|
|
221
256
|
)
|
|
257
|
+
_mark_verified(document, document_hash)
|
|
258
|
+
return document
|
|
222
259
|
|
|
223
260
|
|
|
224
261
|
def annotation_revision(
|
|
@@ -244,14 +281,49 @@ def _annotation_revision(
|
|
|
244
281
|
) -> str:
|
|
245
282
|
"""Hash already checked rows with exactly one canonicalization pass."""
|
|
246
283
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
284
|
+
return _annotation_digests(clip_id, annotation_frame_id, canonical)[0]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _annotation_digests(
|
|
288
|
+
clip_id: str,
|
|
289
|
+
annotation_frame_id: str,
|
|
290
|
+
canonical: list[dict[str, Any]],
|
|
291
|
+
*,
|
|
292
|
+
stored_revision: str | None = None,
|
|
293
|
+
) -> tuple[str, str]:
|
|
294
|
+
"""Return ``(annotation_revision, document_hash)`` from one row pass.
|
|
295
|
+
|
|
296
|
+
RFC 8785 serializes an array as its comma-joined members and an object as
|
|
297
|
+
its members in UTF-16 key order without whitespace, so both canonical
|
|
298
|
+
payloads are assembled from the same row bytes. The document hash covers
|
|
299
|
+
``stored_revision`` when given, otherwise the computed revision.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
rows = b"[" + b",".join(rfc8785.dumps(row) for row in canonical) + b"]"
|
|
303
|
+
frame = rfc8785.dumps(annotation_frame_id)
|
|
304
|
+
tail = (
|
|
305
|
+
b',"clip_id":'
|
|
306
|
+
+ rfc8785.dumps(clip_id)
|
|
307
|
+
+ b',"contract_version":'
|
|
308
|
+
+ rfc8785.dumps(ANNOTATION_CONTRACT_VERSION)
|
|
309
|
+
+ b"}"
|
|
310
|
+
)
|
|
311
|
+
revision_digest = hashlib.sha256()
|
|
312
|
+
for part in (b'{"annotation_frame_id":', frame, b',"annotations":', rows, tail):
|
|
313
|
+
revision_digest.update(part)
|
|
314
|
+
revision = f"sha256:{revision_digest.hexdigest()}"
|
|
315
|
+
document_digest = hashlib.sha256()
|
|
316
|
+
for part in (
|
|
317
|
+
b'{"annotation_frame_id":',
|
|
318
|
+
frame,
|
|
319
|
+
b',"annotation_revision":',
|
|
320
|
+
rfc8785.dumps(stored_revision if stored_revision is not None else revision),
|
|
321
|
+
b',"annotations":',
|
|
322
|
+
rows,
|
|
323
|
+
tail,
|
|
324
|
+
):
|
|
325
|
+
document_digest.update(part)
|
|
326
|
+
return revision, f"sha256:{document_digest.hexdigest()}"
|
|
255
327
|
|
|
256
328
|
|
|
257
329
|
def derive_annotation_views(
|
|
@@ -766,7 +766,8 @@ def parse_clip_work_session(
|
|
|
766
766
|
"calibrated_sensor.json": tables["calibrated_sensor"],
|
|
767
767
|
"sample.json": tables["samples"],
|
|
768
768
|
"sample_data.json": tables["sample_data"],
|
|
769
|
-
|
|
769
|
+
# The validator accepts the verified document without re-canonicalizing it.
|
|
770
|
+
"annotation.json": annotation_document,
|
|
770
771
|
"label_schema.json": label_schema.to_dict(),
|
|
771
772
|
}
|
|
772
773
|
if pose_revision is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{x4d_devkit-0.51.0rc2 → x4d_devkit-0.51.0rc4}/tests/test_current_state_identity_consumers_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|