x4d-devkit 0.53.0rc2__tar.gz → 0.54.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.53.0rc2 → x4d_devkit-0.54.0rc1}/PKG-INFO +1 -1
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/pyproject.toml +1 -1
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_schema_migration.py +45 -0
- x4d_devkit-0.54.0rc1/tests/test_image_source.py +237 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/cli.py +61 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_clip.py +34 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_schema_migration.py +10 -3
- x4d_devkit-0.54.0rc1/x4d_devkit/image_source.py +490 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit.egg-info/SOURCES.txt +2 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/LICENSE +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/README.md +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/setup.cfg +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_annotation_document_hash.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_box.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_conditional_rules.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_enum_set.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_groups.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_schema_registry_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_schema_upgrade.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_models.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_parse_equivalence.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_project_discovery.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_token.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_schemas.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_groups.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_schema_upgrade.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/project_discovery.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.53.0rc2 → x4d_devkit-0.54.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.54.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"
|
|
@@ -281,3 +281,48 @@ def test_typed_client_verifies_the_exact_preview_and_receipt():
|
|
|
281
281
|
with pytest.raises(ValueError, match="another request"):
|
|
282
282
|
client.image_schemas.commit_migration(request)
|
|
283
283
|
assert build_image_schema_migration_request(parsed, "k").to_dict()["preview"] == preview
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def box_object(attributes=None):
|
|
287
|
+
return {
|
|
288
|
+
"id": "box_a",
|
|
289
|
+
"image_id": "image_a",
|
|
290
|
+
"view_id": "objects",
|
|
291
|
+
"category_id": "box",
|
|
292
|
+
"geometry": {"geometry_type": "box_2d", "box": {"x_min": 10, "y_min": 20, "x_max": 40, "y_max": 60}},
|
|
293
|
+
"attributes": attributes or {},
|
|
294
|
+
"attribute_ranges": [],
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_box_objects_migrate_alongside_lines():
|
|
299
|
+
clip = clip_payload()
|
|
300
|
+
clip["image_annotation"]["objects"] = [line_object(), box_object()]
|
|
301
|
+
clip["identity"] = build_image_identity(clip)
|
|
302
|
+
candidate, _ = image_schema_migration_candidate(
|
|
303
|
+
clip, target(clip), mapping(VISIBLE_TO_RANGE, DISCARD_STYLE)
|
|
304
|
+
)
|
|
305
|
+
assert candidate.to_dict()["image_annotation"]["objects"][1] == box_object()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def test_a_box_value_cannot_become_a_range():
|
|
309
|
+
clip = clip_payload()
|
|
310
|
+
body = deepcopy(clip["label_schema"]["body"])
|
|
311
|
+
body["categories"][1]["attributes"] = [
|
|
312
|
+
{"id": "flag", "display_name": "Flag", "type": "boolean", "required": False, "scopes": ["object"], "values": []}
|
|
313
|
+
]
|
|
314
|
+
clip["label_schema"] = snapshot(body)
|
|
315
|
+
clip["image_annotation"]["objects"] = [box_object({"flag": True})]
|
|
316
|
+
clip["identity"] = build_image_identity(clip)
|
|
317
|
+
new = deepcopy(body)
|
|
318
|
+
new["version"] = "2.0.0"
|
|
319
|
+
new["categories"][1]["attributes"] = []
|
|
320
|
+
rules = mapping(
|
|
321
|
+
{
|
|
322
|
+
"match": {"category_id": "box", "scope": "object", "attribute_id": "flag", "value": True},
|
|
323
|
+
"action": {"kind": "range", "attribute_id": "flag", "value": True},
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
with pytest.raises(ImageSchemaMigrationConflict) as caught:
|
|
327
|
+
image_schema_migration_candidate(clip, snapshot(new), rules)
|
|
328
|
+
assert [row["code"] for row in caught.value.conflicts] == ["mapped_value_invalid"]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import struct
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import zlib
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from x4d_devkit._image_contract import ImageContractError, fingerprint
|
|
12
|
+
from x4d_devkit.image_clip import build_image_identity, parse_image_clip
|
|
13
|
+
from x4d_devkit.image_source import (
|
|
14
|
+
IMAGE_SOURCE_BUNDLE_VERSION,
|
|
15
|
+
IMAGE_SOURCE_PROVENANCE_VERSION,
|
|
16
|
+
build_image_source_provenance,
|
|
17
|
+
bundle_calibrations,
|
|
18
|
+
bundles_from_images,
|
|
19
|
+
image_dimensions,
|
|
20
|
+
parse_image_source_bundle,
|
|
21
|
+
read_image_source_bundle,
|
|
22
|
+
synthetic_undistorted_pinhole,
|
|
23
|
+
time_ns_dict,
|
|
24
|
+
)
|
|
25
|
+
from tests.image_fixtures import clip_payload
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def jpeg(width, height, extra=b""):
|
|
29
|
+
# SOI, an APP0 segment, then a baseline SOF0 frame header.
|
|
30
|
+
app0 = b"\xff\xe0" + struct.pack(">H", 16) + b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"
|
|
31
|
+
sof = b"\xff\xc0" + struct.pack(">HBHHB", 11, 8, height, width, 1) + b"\x01\x11\x00"
|
|
32
|
+
return b"\xff\xd8" + app0 + sof + extra + b"\xff\xd9"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def png(width, height):
|
|
36
|
+
ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
|
|
37
|
+
chunk = struct.pack(">I", len(ihdr)) + b"IHDR" + ihdr + struct.pack(">I", zlib.crc32(b"IHDR" + ihdr))
|
|
38
|
+
return b"\x89PNG\r\n\x1a\n" + chunk
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def manifest(images, *, channel="cam_a", width=8, height=6):
|
|
42
|
+
calibration = synthetic_undistorted_pinhole(width=width, height=height, frame_id="camera", parent_frame="base")
|
|
43
|
+
samples = []
|
|
44
|
+
for ns, data in images:
|
|
45
|
+
samples.append({"timestamp": time_ns_dict(ns), "images": {channel: {
|
|
46
|
+
"timestamp": time_ns_dict(ns), "path": f"images/{channel}/{ns}.jpg", "size": len(data),
|
|
47
|
+
"sha256": hashlib.sha256(data).hexdigest(), "width": width, "height": height, "encoding": "jpeg"}}})
|
|
48
|
+
return {
|
|
49
|
+
"contract_version": IMAGE_SOURCE_BUNDLE_VERSION,
|
|
50
|
+
"bundle_id": "bundle_a",
|
|
51
|
+
"origin": {"label": "vendor delivery", "attributes": {"batch": "1104"}},
|
|
52
|
+
"timestamp_source": "filename",
|
|
53
|
+
"channels": [{"channel": channel, "calibration": calibration, "calibration_source": "synthetic"}],
|
|
54
|
+
"reference_channel": channel,
|
|
55
|
+
"sync_window_ns": 0,
|
|
56
|
+
"samples": samples,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def write_bundle(root, value, media):
|
|
61
|
+
root.mkdir()
|
|
62
|
+
(root / "manifest.json").write_text(json.dumps(value))
|
|
63
|
+
for path, data in media.items():
|
|
64
|
+
(root / path).parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
(root / path).write_bytes(data)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_image_dimensions_read_jpeg_and_png_headers():
|
|
69
|
+
assert image_dimensions(jpeg(1920, 1536)) == ("jpeg", 1920, 1536)
|
|
70
|
+
assert image_dimensions(png(5, 7)) == ("png", 5, 7)
|
|
71
|
+
with pytest.raises(ImageContractError):
|
|
72
|
+
image_dimensions(b"GIF89a")
|
|
73
|
+
with pytest.raises(ImageContractError):
|
|
74
|
+
image_dimensions(b"\xff\xd8\xff\xd9")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_synthetic_calibration_closes_the_native_calibration_grammar():
|
|
78
|
+
value = synthetic_undistorted_pinhole(width=1920, height=1536, frame_id="cam", parent_frame="base_link")
|
|
79
|
+
assert value["intrinsics"] == {"model": "pinhole", "fx": 1920.0, "fy": 1920.0, "cx": 960.0, "cy": 768.0}
|
|
80
|
+
assert value["distortion"] == {"model": "plumb_bob", "coefficients": [0.0] * 5}
|
|
81
|
+
assert value["camera_info"]["frame_id"] == value["extrinsics"]["child_frame"] == "cam"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_bundle_grammar_is_closed_and_ordered():
|
|
85
|
+
data = jpeg(8, 6)
|
|
86
|
+
bundle = parse_image_source_bundle(manifest([(10, data), (20, data)]))
|
|
87
|
+
assert len(bundle.images) == 2 and bundle.fingerprint.startswith("sha256:")
|
|
88
|
+
|
|
89
|
+
for mutate in (
|
|
90
|
+
lambda v: v.update(extra=1),
|
|
91
|
+
lambda v: v.update(contract_version="x4d-image-source-bundle-v0"),
|
|
92
|
+
lambda v: v["samples"].reverse(),
|
|
93
|
+
lambda v: v["samples"][0]["images"]["cam_a"].update(path="images/cam_a/11.jpg"),
|
|
94
|
+
lambda v: v["samples"][0]["images"]["cam_a"].update(width=9),
|
|
95
|
+
lambda v: v["channels"][0].update(calibration_source="guessed"),
|
|
96
|
+
lambda v: v["channels"][0]["calibration"].update(id="calibration_cam_a"),
|
|
97
|
+
lambda v: v.update(sync_window_ns=5),
|
|
98
|
+
lambda v: v.update(timestamp_source="exif"),
|
|
99
|
+
lambda v: v.update(reference_channel="cam_b"),
|
|
100
|
+
lambda v: v.update(samples=[]),
|
|
101
|
+
):
|
|
102
|
+
value = manifest([(10, data), (20, data)])
|
|
103
|
+
mutate(value)
|
|
104
|
+
with pytest.raises(ImageContractError):
|
|
105
|
+
parse_image_source_bundle(value)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_multi_camera_bundle_uses_reference_time_and_window():
|
|
109
|
+
data = jpeg(8, 6)
|
|
110
|
+
value = manifest([(1_000, data)])
|
|
111
|
+
second = copy.deepcopy(value["channels"][0])
|
|
112
|
+
second["channel"] = "cam_b"
|
|
113
|
+
value["channels"].append(second)
|
|
114
|
+
value["sync_window_ns"] = 50
|
|
115
|
+
value["samples"][0]["images"]["cam_b"] = {**value["samples"][0]["images"]["cam_a"],
|
|
116
|
+
"timestamp": time_ns_dict(1_040), "path": "images/cam_b/1040.jpg"}
|
|
117
|
+
parse_image_source_bundle(value)
|
|
118
|
+
value["samples"][0]["images"]["cam_b"].update(timestamp=time_ns_dict(1_060), path="images/cam_b/1060.jpg")
|
|
119
|
+
with pytest.raises(ImageContractError, match="synchronization window"):
|
|
120
|
+
parse_image_source_bundle(value)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_directory_validation_checks_file_set_and_every_byte(tmp_path):
|
|
124
|
+
data = jpeg(8, 6)
|
|
125
|
+
value = manifest([(10, data)])
|
|
126
|
+
write_bundle(tmp_path / "ok", value, {"images/cam_a/10.jpg": data})
|
|
127
|
+
assert read_image_source_bundle(tmp_path / "ok").to_dict() == value
|
|
128
|
+
|
|
129
|
+
write_bundle(tmp_path / "extra", value, {"images/cam_a/10.jpg": data, "notes.txt": b"x"})
|
|
130
|
+
with pytest.raises(ImageContractError, match="file set"):
|
|
131
|
+
read_image_source_bundle(tmp_path / "extra")
|
|
132
|
+
|
|
133
|
+
tampered = jpeg(8, 6, b"\x00")
|
|
134
|
+
write_bundle(tmp_path / "tampered", value, {"images/cam_a/10.jpg": tampered})
|
|
135
|
+
with pytest.raises(ImageContractError, match="size"):
|
|
136
|
+
read_image_source_bundle(tmp_path / "tampered")
|
|
137
|
+
assert read_image_source_bundle(tmp_path / "tampered", verify_media=False)
|
|
138
|
+
|
|
139
|
+
resized = jpeg(9, 6)
|
|
140
|
+
value_resized = manifest([(10, resized)])
|
|
141
|
+
write_bundle(tmp_path / "resized", value_resized, {"images/cam_a/10.jpg": resized})
|
|
142
|
+
value_resized["samples"][0]["images"]["cam_a"]["sha256"] = hashlib.sha256(resized).hexdigest()
|
|
143
|
+
with pytest.raises(ImageContractError, match="dimensions"):
|
|
144
|
+
read_image_source_bundle(tmp_path / "resized")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_converter_splits_by_gap_and_limit_with_explicit_synthetic_calibration(tmp_path):
|
|
148
|
+
source = tmp_path / "images"
|
|
149
|
+
source.mkdir()
|
|
150
|
+
for ns in (1_000_000_000, 2_000_000_000, 3_000_000_000, 100_000_000_000):
|
|
151
|
+
(source / f"{ns}.jpg").write_bytes(jpeg(8, 6))
|
|
152
|
+
bundles = bundles_from_images(source, tmp_path / "out", channel="cam_a", origin_label="loose images",
|
|
153
|
+
synthetic_pinhole=True, split_gap_s=60, max_images=2)
|
|
154
|
+
assert [len(row.images) for row in bundles] == [2, 1, 1]
|
|
155
|
+
first = bundles[0].to_dict()
|
|
156
|
+
assert first["timestamp_source"] == "filename"
|
|
157
|
+
assert first["channels"][0]["calibration_source"] == "synthetic"
|
|
158
|
+
for row in bundles:
|
|
159
|
+
assert read_image_source_bundle(tmp_path / "out" / row.to_dict()["bundle_id"]).to_dict() == row.to_dict()
|
|
160
|
+
|
|
161
|
+
with pytest.raises(ImageContractError):
|
|
162
|
+
bundles_from_images(source, tmp_path / "none", channel="cam_a", origin_label="x")
|
|
163
|
+
(source / "readme.txt").write_text("x")
|
|
164
|
+
with pytest.raises(ImageContractError):
|
|
165
|
+
bundles_from_images(source, tmp_path / "bad", channel="cam_a", origin_label="x", synthetic_pinhole=True)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def provenance_clip():
|
|
169
|
+
data = jpeg(1920, 1536)
|
|
170
|
+
value = manifest([(10_000_000_020, data)], width=1920, height=1536)
|
|
171
|
+
value["channels"][0]["calibration"]["extrinsics"]["child_frame"] = "camera"
|
|
172
|
+
bundle = parse_image_source_bundle(value)
|
|
173
|
+
clip = clip_payload()
|
|
174
|
+
provenance = build_image_source_provenance(bundle)
|
|
175
|
+
provenance["calibrations"][0]["calibration_id"] = "calibration_a"
|
|
176
|
+
clip["meta"]["source_provenance"] = provenance
|
|
177
|
+
clip["meta"]["source_bindings"][0]["source_fingerprint"] = provenance["bundle_fingerprint"]
|
|
178
|
+
return clip, bundle
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_absent_provenance_keeps_existing_clips_and_identity_unchanged():
|
|
182
|
+
value = clip_payload()
|
|
183
|
+
before = copy.deepcopy(value["identity"])
|
|
184
|
+
clip = parse_image_clip(value)
|
|
185
|
+
assert clip.source_provenance is None and clip.source_kind == "bag_admission"
|
|
186
|
+
assert clip.calibration_is_measured("calibration_a") is True
|
|
187
|
+
assert build_image_identity(value) == before
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_provenance_fragment_marks_synthetic_calibration_and_binds_the_bundle():
|
|
191
|
+
value, bundle = provenance_clip()
|
|
192
|
+
value["identity"] = build_image_identity(value)
|
|
193
|
+
clip = parse_image_clip(value)
|
|
194
|
+
assert clip.source_kind == "external_bundle"
|
|
195
|
+
assert clip.source_provenance["contract_version"] == IMAGE_SOURCE_PROVENANCE_VERSION
|
|
196
|
+
assert clip.source_provenance["bundle_fingerprint"] == fingerprint(bundle.to_dict())
|
|
197
|
+
assert clip.calibration_is_measured("calibration_a") is False
|
|
198
|
+
with pytest.raises(KeyError):
|
|
199
|
+
clip.calibration_is_measured("missing")
|
|
200
|
+
assert value["identity"]["source_revision"] != clip_payload()["identity"]["source_revision"]
|
|
201
|
+
|
|
202
|
+
for mutate in (
|
|
203
|
+
lambda v: v["meta"]["source_provenance"].update(kind="bag"),
|
|
204
|
+
lambda v: v["meta"]["source_provenance"]["calibrations"][0].update(source="unknown"),
|
|
205
|
+
lambda v: v["meta"]["source_provenance"].update(calibrations=[]),
|
|
206
|
+
lambda v: v["meta"]["source_bindings"][0].update(source_fingerprint="sha256:" + "9" * 64),
|
|
207
|
+
lambda v: v["meta"]["source_provenance"].update(extra=True),
|
|
208
|
+
):
|
|
209
|
+
broken = copy.deepcopy(value)
|
|
210
|
+
mutate(broken)
|
|
211
|
+
broken["identity"] = build_image_identity(broken)
|
|
212
|
+
with pytest.raises(ImageContractError):
|
|
213
|
+
parse_image_clip(broken)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_bundle_calibrations_are_native_rows():
|
|
217
|
+
bundle = parse_image_source_bundle(manifest([(10, jpeg(8, 6))]))
|
|
218
|
+
rows = bundle_calibrations(bundle)
|
|
219
|
+
assert rows[0]["id"] == "calibration_cam_a" and rows[0]["channel"] == "cam_a"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_cli_converts_and_validates(tmp_path):
|
|
223
|
+
source = tmp_path / "images"
|
|
224
|
+
source.mkdir()
|
|
225
|
+
(source / "5000000000.jpg").write_bytes(jpeg(8, 6))
|
|
226
|
+
|
|
227
|
+
def run(*args):
|
|
228
|
+
return subprocess.run([sys.executable, "-m", "x4d_devkit.cli", *args], capture_output=True, text=True)
|
|
229
|
+
|
|
230
|
+
built = run("image-source", "from-images", str(source), "--out", str(tmp_path / "out"),
|
|
231
|
+
"--channel", "cam_a", "--origin-label", "loose", "--synthetic-pinhole")
|
|
232
|
+
assert built.returncode == 0, built.stderr
|
|
233
|
+
path = json.loads(built.stdout)["bundles"][0]["path"]
|
|
234
|
+
checked = run("image-source", "validate", path)
|
|
235
|
+
assert checked.returncode == 0 and json.loads(checked.stdout)["image_count"] == 1
|
|
236
|
+
(tmp_path / "out" / json.loads(built.stdout)["bundles"][0]["bundle_id"] / "x.txt").write_text("x")
|
|
237
|
+
assert run("image-source", "validate", path).returncode == 1
|
|
@@ -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.54.0rc1"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -426,6 +426,45 @@ def cmd_validate_image_export(args):
|
|
|
426
426
|
print_json({"ok": True, "summary": summary})
|
|
427
427
|
|
|
428
428
|
|
|
429
|
+
def cmd_image_source_validate(args):
|
|
430
|
+
from x4d_devkit._image_contract import ImageContractError
|
|
431
|
+
from x4d_devkit.image_source import read_image_source_bundle
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
bundle = read_image_source_bundle(args.path, verify_media=not args.metadata_only)
|
|
435
|
+
except (OSError, ValueError, ImageContractError) as exc:
|
|
436
|
+
print_json({"ok": False, "error": str(exc)})
|
|
437
|
+
raise SystemExit(1) from exc
|
|
438
|
+
value = bundle.to_dict()
|
|
439
|
+
print_json({"ok": True, "bundle_id": value["bundle_id"], "bundle_fingerprint": bundle.fingerprint,
|
|
440
|
+
"sample_count": len(value["samples"]), "image_count": len(bundle.images),
|
|
441
|
+
"bytes": sum(row["size"] for row in bundle.images), "media_verified": not args.metadata_only})
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def cmd_image_source_from_images(args):
|
|
445
|
+
from x4d_devkit._image_contract import ImageContractError
|
|
446
|
+
from x4d_devkit.image_source import bundles_from_images
|
|
447
|
+
|
|
448
|
+
calibration = None
|
|
449
|
+
try:
|
|
450
|
+
if args.calibration:
|
|
451
|
+
from x4d_devkit.json_contract import parse_i_json
|
|
452
|
+
|
|
453
|
+
calibration = parse_i_json(Path(args.calibration).read_bytes(), path=args.calibration)
|
|
454
|
+
bundles = bundles_from_images(
|
|
455
|
+
args.images_dir, args.out, channel=args.channel, origin_label=args.origin_label,
|
|
456
|
+
calibration=calibration, synthetic_pinhole=args.synthetic_pinhole, focal_px=args.focal_px,
|
|
457
|
+
frame_id=args.frame_id, parent_frame=args.parent_frame, split_gap_s=args.split_gap_s,
|
|
458
|
+
max_images=args.max_images, bundle_prefix=args.bundle_prefix,
|
|
459
|
+
)
|
|
460
|
+
except (OSError, ValueError, ImageContractError) as exc:
|
|
461
|
+
print_json({"ok": False, "error": str(exc)})
|
|
462
|
+
raise SystemExit(1) from exc
|
|
463
|
+
print_json({"ok": True, "bundles": [
|
|
464
|
+
{"bundle_id": row.to_dict()["bundle_id"], "path": str(Path(args.out) / row.to_dict()["bundle_id"]),
|
|
465
|
+
"bundle_fingerprint": row.fingerprint, "image_count": len(row.images)} for row in bundles]})
|
|
466
|
+
|
|
467
|
+
|
|
429
468
|
def cmd_validate(args):
|
|
430
469
|
from x4d_devkit.validation import validate_clip
|
|
431
470
|
|
|
@@ -772,6 +811,28 @@ def main():
|
|
|
772
811
|
p_image_export.add_argument("--max-json-bytes", type=int, default=64 * 1024 * 1024)
|
|
773
812
|
p_image_export.set_defaults(func=cmd_validate_image_export)
|
|
774
813
|
|
|
814
|
+
p_source = sub.add_parser("image-source", help="Platform-neutral image source bundles")
|
|
815
|
+
p_source_sub = p_source.add_subparsers(dest="subcommand")
|
|
816
|
+
p_source_validate = p_source_sub.add_parser("validate", help="Validate a bundle directory and every image byte")
|
|
817
|
+
p_source_validate.add_argument("path")
|
|
818
|
+
p_source_validate.add_argument("--metadata-only", action="store_true", help="Check manifest and file set only")
|
|
819
|
+
p_source_validate.set_defaults(func=cmd_image_source_validate)
|
|
820
|
+
p_source_build = p_source_sub.add_parser("from-images", help="Convert <epoch_ns>.jpg|png files into bundles")
|
|
821
|
+
p_source_build.add_argument("images_dir")
|
|
822
|
+
p_source_build.add_argument("--out", required=True)
|
|
823
|
+
p_source_build.add_argument("--channel", required=True)
|
|
824
|
+
p_source_build.add_argument("--origin-label", required=True)
|
|
825
|
+
group = p_source_build.add_mutually_exclusive_group(required=True)
|
|
826
|
+
group.add_argument("--calibration", help="Declared calibration JSON (calibrated_sensor body without id/channel)")
|
|
827
|
+
group.add_argument("--synthetic-pinhole", action="store_true", help="Explicit undistorted placeholder calibration")
|
|
828
|
+
p_source_build.add_argument("--focal-px", type=float)
|
|
829
|
+
p_source_build.add_argument("--frame-id")
|
|
830
|
+
p_source_build.add_argument("--parent-frame", default="base_link")
|
|
831
|
+
p_source_build.add_argument("--split-gap-s", type=float)
|
|
832
|
+
p_source_build.add_argument("--max-images", type=int, default=2000)
|
|
833
|
+
p_source_build.add_argument("--bundle-prefix")
|
|
834
|
+
p_source_build.set_defaults(func=cmd_image_source_from_images)
|
|
835
|
+
|
|
775
836
|
p_val = sub.add_parser("validate", help="Validate clip directory")
|
|
776
837
|
p_val.add_argument("path", help="Clip directory or parent directory")
|
|
777
838
|
p_val.set_defaults(func=cmd_validate)
|
|
@@ -588,6 +588,31 @@ class ImageClip(ImageDocument):
|
|
|
588
588
|
def images(self) -> tuple[dict, ...]:
|
|
589
589
|
return tuple(self.to_dict()["sample_data"])
|
|
590
590
|
|
|
591
|
+
@property
|
|
592
|
+
def source_provenance(self) -> dict | None:
|
|
593
|
+
"""External-source fragment; ``None`` means platform Bag admission."""
|
|
594
|
+
|
|
595
|
+
return self.to_dict()["meta"].get("source_provenance")
|
|
596
|
+
|
|
597
|
+
@property
|
|
598
|
+
def source_kind(self) -> str:
|
|
599
|
+
provenance = self.source_provenance
|
|
600
|
+
return "bag_admission" if provenance is None else provenance["kind"]
|
|
601
|
+
|
|
602
|
+
def calibration_is_measured(self, calibration_id: str) -> bool:
|
|
603
|
+
"""False only for an explicitly synthetic calibration."""
|
|
604
|
+
|
|
605
|
+
ids = {row["id"] for row in self.to_dict()["calibrated_sensor"]}
|
|
606
|
+
if calibration_id not in ids:
|
|
607
|
+
raise KeyError(calibration_id)
|
|
608
|
+
provenance = self.source_provenance
|
|
609
|
+
if provenance is None:
|
|
610
|
+
return True
|
|
611
|
+
return next(
|
|
612
|
+
row["source"] for row in provenance["calibrations"]
|
|
613
|
+
if row["calibration_id"] == calibration_id
|
|
614
|
+
) != "synthetic"
|
|
615
|
+
|
|
591
616
|
|
|
592
617
|
def parse_image_clip(value: Any, *, expected_clip_id: str | None = None) -> ImageClip:
|
|
593
618
|
canonical(value)
|
|
@@ -609,6 +634,7 @@ def parse_image_clip(value: Any, *, expected_clip_id: str | None = None) -> Imag
|
|
|
609
634
|
"processing_fingerprint",
|
|
610
635
|
},
|
|
611
636
|
"meta",
|
|
637
|
+
optional={"source_provenance"},
|
|
612
638
|
)
|
|
613
639
|
require(
|
|
614
640
|
meta["contract_version"] == IMAGE_CLIP_CONTRACT_VERSION
|
|
@@ -679,6 +705,14 @@ def parse_image_clip(value: Any, *, expected_clip_id: str | None = None) -> Imag
|
|
|
679
705
|
"calibration channel scope mismatch",
|
|
680
706
|
"calibrated_sensor",
|
|
681
707
|
)
|
|
708
|
+
if "source_provenance" in meta:
|
|
709
|
+
from x4d_devkit.image_source import validate_image_source_provenance
|
|
710
|
+
|
|
711
|
+
validate_image_source_provenance(
|
|
712
|
+
meta["source_provenance"],
|
|
713
|
+
calibration_ids=set(calibrations),
|
|
714
|
+
source_bindings=meta["source_bindings"],
|
|
715
|
+
)
|
|
682
716
|
samples = unique_ids(value["sample"], "sample")
|
|
683
717
|
images = unique_ids(value["sample_data"], "sample_data")
|
|
684
718
|
previous = None
|
|
@@ -181,7 +181,7 @@ def migrate_image_annotation(annotation, *, target_schema, mapping):
|
|
|
181
181
|
changes, removed, conflicts = [], [], []
|
|
182
182
|
carried = 0
|
|
183
183
|
|
|
184
|
-
def convert(object_id, category, scope, values, range_id, base_path):
|
|
184
|
+
def convert(object_id, category, scope, values, range_id, base_path, polyline):
|
|
185
185
|
nonlocal carried
|
|
186
186
|
output, added = {}, []
|
|
187
187
|
for attribute_id in sorted(values):
|
|
@@ -230,6 +230,11 @@ def migrate_image_annotation(annotation, *, target_schema, mapping):
|
|
|
230
230
|
)
|
|
231
231
|
continue
|
|
232
232
|
change["after"] = {"attribute_id": action["attribute_id"], "value": action["value"]}
|
|
233
|
+
if action["kind"] == "range" and not polyline:
|
|
234
|
+
conflicts.append(
|
|
235
|
+
_conflict("mapped_value_invalid", f"rule {index}: only a polyline can carry ranges", path, **context)
|
|
236
|
+
)
|
|
237
|
+
continue
|
|
233
238
|
if action["kind"] == "range":
|
|
234
239
|
change["range_id"] = _range_id(object_id, index)
|
|
235
240
|
added.append((change["range_id"], action))
|
|
@@ -256,8 +261,9 @@ def migrate_image_annotation(annotation, *, target_schema, mapping):
|
|
|
256
261
|
_conflict("object_unrepresentable", "target category geometry differs", path, object_id=object_id)
|
|
257
262
|
)
|
|
258
263
|
continue
|
|
264
|
+
polyline = category["geometry_type"] == "polyline_2d"
|
|
259
265
|
row["attributes"], added = convert(
|
|
260
|
-
object_id, category, "object", row["attributes"], None, path + ".attributes"
|
|
266
|
+
object_id, category, "object", row["attributes"], None, path + ".attributes", polyline
|
|
261
267
|
)
|
|
262
268
|
ranges = []
|
|
263
269
|
for range_index, interval in enumerate(row["attribute_ranges"]):
|
|
@@ -268,14 +274,15 @@ def migrate_image_annotation(annotation, *, target_schema, mapping):
|
|
|
268
274
|
interval["attributes"],
|
|
269
275
|
interval["range_id"],
|
|
270
276
|
f"{path}.attribute_ranges[{range_index}].attributes",
|
|
277
|
+
polyline,
|
|
271
278
|
)
|
|
272
279
|
assert not nested # range actions are object-scope only
|
|
273
280
|
if values:
|
|
274
281
|
ranges.append({**interval, "attributes": values})
|
|
275
282
|
else:
|
|
276
283
|
removed.append({"object_id": object_id, "range_id": interval["range_id"]})
|
|
277
|
-
vertices = row["geometry"]["vertices"]
|
|
278
284
|
for range_id, action in added:
|
|
285
|
+
vertices = row["geometry"]["vertices"]
|
|
279
286
|
ranges.append(
|
|
280
287
|
{
|
|
281
288
|
"range_id": range_id,
|