x4d-devkit 0.48.0rc2__tar.gz → 0.48.0rc3__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.
Files changed (150) hide show
  1. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/PKG-INFO +1 -1
  2. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/pyproject.toml +1 -1
  3. x4d_devkit-0.48.0rc3/tests/test_image_reference.py +267 -0
  4. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_sdk_api.py +1 -1
  5. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/client.py +2 -0
  6. x4d_devkit-0.48.0rc3/x4d_devkit/client/image_references.py +147 -0
  7. x4d_devkit-0.48.0rc3/x4d_devkit/image_reference.py +438 -0
  8. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/PKG-INFO +1 -1
  9. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/SOURCES.txt +3 -0
  10. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/LICENSE +0 -0
  11. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/README.md +0 -0
  12. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/setup.cfg +0 -0
  13. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_annotation_contract.py +0 -0
  14. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_annotation_reference.py +0 -0
  15. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_assets_client.py +0 -0
  16. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_boundary.py +0 -0
  17. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_box.py +0 -0
  18. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_build_info.py +0 -0
  19. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_category_audit.py +0 -0
  20. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_clean_basemap.py +0 -0
  21. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_clean_basemap_client.py +0 -0
  22. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_cli_auth.py +0 -0
  23. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_client_json_contract.py +0 -0
  24. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_client_projects.py +0 -0
  25. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_clip_archives.py +0 -0
  26. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_contract_validation.py +0 -0
  27. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_coordinate_frames.py +0 -0
  28. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_current_state.py +0 -0
  29. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_current_state_identity_consumers_example.py +0 -0
  30. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_dataset_contract.py +0 -0
  31. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_dataset_identity.py +0 -0
  32. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_detection_eval.py +0 -0
  33. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_evaluate.py +0 -0
  34. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_export_bundle.py +0 -0
  35. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_image_clip.py +0 -0
  36. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_image_geometry.py +0 -0
  37. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_image_review.py +0 -0
  38. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_image_work_session.py +0 -0
  39. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_inference_input.py +0 -0
  40. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_inference_schemas.py +0 -0
  41. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_inference_service.py +0 -0
  42. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_label_schemas.py +0 -0
  43. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_lidar_materialization.py +0 -0
  44. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_loader.py +0 -0
  45. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_matching.py +0 -0
  46. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_metrics.py +0 -0
  47. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_models.py +0 -0
  48. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_platform_pose.py +0 -0
  49. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_preannotation_client.py +0 -0
  50. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_reconstruction_client.py +0 -0
  51. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_release_gate.py +0 -0
  52. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_roadmark_preview.py +0 -0
  53. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_semantic_scene_authoring.py +0 -0
  54. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_semantic_scene_client.py +0 -0
  55. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_semantic_scene_contract.py +0 -0
  56. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_semantic_scene_independent.py +0 -0
  57. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_semantic_scene_native_clip.py +0 -0
  58. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_source_attributes.py +0 -0
  59. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_time_contract.py +0 -0
  60. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_token.py +0 -0
  61. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_tracking_eval.py +0 -0
  62. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_training_identity.py +0 -0
  63. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_transform.py +0 -0
  64. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_transform_chain.py +0 -0
  65. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_validation.py +0 -0
  66. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_vector_map.py +0 -0
  67. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_vector_map_geometry.py +0 -0
  68. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_vector_map_session.py +0 -0
  69. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_work_session.py +0 -0
  70. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/tests/test_work_session_validation_reuse.py +0 -0
  71. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/__init__.py +0 -0
  72. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/_image_contract.py +0 -0
  73. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/annotation_contract.py +0 -0
  74. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/annotation_reference.py +0 -0
  75. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/audit.py +0 -0
  76. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/build_info.py +0 -0
  77. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/clean_basemap.py +0 -0
  78. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/cli.py +0 -0
  79. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/__init__.py +0 -0
  80. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/_origin.py +0 -0
  81. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/annotation_reference.py +0 -0
  82. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/assets.py +0 -0
  83. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/bags.py +0 -0
  84. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/capabilities.py +0 -0
  85. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/checkpoints.py +0 -0
  86. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/clean_basemap.py +0 -0
  87. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/clip_work_sessions.py +0 -0
  88. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/clips.py +0 -0
  89. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/dataset_identity.py +0 -0
  90. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/image_clips.py +0 -0
  91. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/image_review.py +0 -0
  92. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/image_work_sessions.py +0 -0
  93. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/label_schemas.py +0 -0
  94. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/preannotation.py +0 -0
  95. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/projects.py +0 -0
  96. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/reconstruction.py +0 -0
  97. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/semantic_scene.py +0 -0
  98. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  99. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/services.py +0 -0
  100. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/client/test_sets.py +0 -0
  101. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/consumer_identity.py +0 -0
  102. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/coordinate_contract.py +0 -0
  103. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/core/__init__.py +0 -0
  104. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/core/loader.py +0 -0
  105. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/core/models.py +0 -0
  106. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/core/token.py +0 -0
  107. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/core/transform.py +0 -0
  108. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/current_state.py +0 -0
  109. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/dataset_contract.py +0 -0
  110. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/dataset_identity.py +0 -0
  111. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/__init__.py +0 -0
  112. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/api.py +0 -0
  113. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/box.py +0 -0
  114. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/detection.py +0 -0
  115. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/matching.py +0 -0
  116. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/metrics.py +0 -0
  117. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/eval/tracking.py +0 -0
  118. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/export_bundle.py +0 -0
  119. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/image_clip.py +0 -0
  120. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/image_geometry.py +0 -0
  121. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/image_review.py +0 -0
  122. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/image_schema.py +0 -0
  123. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/image_work_session.py +0 -0
  124. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/__init__.py +0 -0
  125. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/input.py +0 -0
  126. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/models.py +0 -0
  127. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/schemas.py +0 -0
  128. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/service.py +0 -0
  129. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/inference/tasks.py +0 -0
  130. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/json_contract.py +0 -0
  131. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/label_schema.py +0 -0
  132. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/lidar_materialization.py +0 -0
  133. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/materialize.py +0 -0
  134. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/roadmark_preview.py +0 -0
  135. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/semantic_scene_authoring.py +0 -0
  136. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/semantic_scene_contract.py +0 -0
  137. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/time_contract.py +0 -0
  138. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/training_identity.py +0 -0
  139. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/validation/__init__.py +0 -0
  140. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/validation/contract.py +0 -0
  141. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/validation/platform_pose.py +0 -0
  142. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/validation/report.py +0 -0
  143. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/validation/validator.py +0 -0
  144. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/vector_map.py +0 -0
  145. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/vector_map_geometry.py +0 -0
  146. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit/work_session.py +0 -0
  147. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  148. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/entry_points.txt +0 -0
  149. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/requires.txt +0 -0
  150. {x4d_devkit-0.48.0rc2 → x4d_devkit-0.48.0rc3}/x4d_devkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.48.0rc2
3
+ Version: 0.48.0rc3
4
4
  Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
5
  Author: windzu
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.48.0rc2"
7
+ version = "0.48.0rc3"
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,267 @@
1
+ import copy
2
+
3
+ import pytest
4
+
5
+ import hashlib
6
+
7
+ from tests.image_fixtures import (
8
+ IMAGE_BYTES as FIXTURE_BYTES,
9
+ clip_payload as base_clip_payload,
10
+ line_object,
11
+ )
12
+ from x4d_devkit.image_clip import build_image_identity, parse_image_clip
13
+ from x4d_devkit.image_reference import (
14
+ IMAGE_INFERENCE_PROTOCOL,
15
+ IMAGE_REFERENCE_CONTRACT,
16
+ build_image_model_profile,
17
+ build_image_prediction_input,
18
+ image_reference_source,
19
+ parse_image_model_profile,
20
+ parse_image_prediction,
21
+ parse_image_prediction_input,
22
+ parse_image_reference,
23
+ parse_image_reference_source,
24
+ )
25
+
26
+
27
+ IMAGE_BYTES = b"\xff\xd8\xff" + FIXTURE_BYTES
28
+
29
+
30
+ def clip_payload():
31
+ value = base_clip_payload()
32
+ value["sample_data"][0]["asset"].update(
33
+ size=len(IMAGE_BYTES), sha256=hashlib.sha256(IMAGE_BYTES).hexdigest()
34
+ )
35
+ value["identity"] = build_image_identity(value)
36
+ return value
37
+
38
+
39
+ def profile():
40
+ return build_image_model_profile(
41
+ {
42
+ "model_name": "test",
43
+ "model_version": "1",
44
+ "artifact": {
45
+ "bucket": "models",
46
+ "object_key": "enet/1.tar.gz",
47
+ "object_version": None,
48
+ "archive_sha256": "a" * 64,
49
+ "weights_sha256": "b" * 64,
50
+ },
51
+ "adapter": "test-v1",
52
+ "parameters": {},
53
+ "runtime": "cpu-test",
54
+ }
55
+ )
56
+
57
+
58
+ def source():
59
+ return image_reference_source(parse_image_clip(clip_payload()))
60
+
61
+
62
+ def request():
63
+ return build_image_prediction_input(
64
+ source(),
65
+ image_id="image_a",
66
+ view_id="objects",
67
+ profile_id=profile().to_dict()["profile_id"],
68
+ image_bytes=IMAGE_BYTES,
69
+ )
70
+
71
+
72
+ def artifact():
73
+ return {
74
+ "contract_version": IMAGE_REFERENCE_CONTRACT,
75
+ "run_id": "ref_1",
76
+ "source": source().to_dict(),
77
+ "profile": profile().to_dict(),
78
+ "view_id": "objects",
79
+ "coverage": [
80
+ {
81
+ "image_id": "image_a",
82
+ "status": "predicted",
83
+ "objects": [line_object()],
84
+ "error": None,
85
+ }
86
+ ],
87
+ }
88
+
89
+
90
+ def test_source_and_prediction_never_include_human_data_or_its_revision():
91
+ value = clip_payload()
92
+ value["image_annotation"]["objects"] = [line_object()]
93
+ value["identity"] = build_image_identity(value)
94
+ with_labels = image_reference_source(parse_image_clip(value))
95
+ assert with_labels.to_dict() == source().to_dict()
96
+ assert (
97
+ not {"image_annotation", "annotation_revision", "completion"}
98
+ & with_labels.to_dict().keys()
99
+ )
100
+ encoded = request().to_dict()
101
+ assert "image_annotation" not in str(encoded)
102
+ assert "annotation_revision" not in str(encoded)
103
+
104
+
105
+ def test_profile_binds_all_weights_adapter_parameters_and_runtime():
106
+ value = profile().to_dict()
107
+ for key in ("model_version", "adapter", "runtime"):
108
+ changed = copy.deepcopy(value)
109
+ changed[key] += "new"
110
+ with pytest.raises(ValueError, match="fingerprint"):
111
+ parse_image_model_profile(changed)
112
+ value["parameters"]["threshold"] = 0.7
113
+ with pytest.raises(ValueError, match="fingerprint"):
114
+ parse_image_model_profile(value)
115
+
116
+
117
+ @pytest.mark.parametrize(
118
+ "change", ["gt", "digest", "bytes", "size", "schema", "calibration", "view"]
119
+ )
120
+ def test_reject_source_or_prediction_identity_drift(change):
121
+ value = request().to_dict()
122
+ if change == "gt":
123
+ value["objects"] = []
124
+ if change == "digest":
125
+ value["image"]["asset"]["sha256"] = "0" * 64
126
+ if change == "bytes":
127
+ value["image_base64"] = "!invalid"
128
+ if change == "size":
129
+ value["image"]["asset"]["size"] += 1
130
+ if change == "schema":
131
+ value["context"]["schema_fingerprint"] = "sha256:" + "0" * 64
132
+ if change == "calibration":
133
+ value["calibrated_sensor"]["width"] += 1
134
+ if change == "view":
135
+ value["context"]["view_id"] = "semantics"
136
+ with pytest.raises(ValueError):
137
+ parse_image_prediction_input(value)
138
+
139
+
140
+ def test_source_rejects_revision_drift_and_extra_annotation():
141
+ value = source().to_dict()
142
+ value["sample_data"][0]["asset"]["sha256"] = "0" * 64
143
+ with pytest.raises(ValueError, match="source identity"):
144
+ parse_image_reference_source(value)
145
+ value = source().to_dict()
146
+ value["image_annotation"] = {}
147
+ with pytest.raises(ValueError):
148
+ parse_image_reference_source(value)
149
+
150
+
151
+ def test_prediction_echo_and_native_geometry_schema_validation():
152
+ req = request()
153
+ body = req.to_dict()
154
+ output = {
155
+ "protocol_version": IMAGE_INFERENCE_PROTOCOL,
156
+ "context": body["context"],
157
+ "profile_id": body["profile_id"],
158
+ "image_id": "image_a",
159
+ "objects": [line_object()],
160
+ }
161
+ assert parse_image_prediction(output, request=req).to_dict() == output
162
+ output["objects"][0]["geometry"]["vertices"][0]["x"] = 9000
163
+ with pytest.raises(ValueError):
164
+ parse_image_prediction(output, request=req)
165
+ output["objects"] = []
166
+ output["profile_id"] = "sha256:" + "0" * 64
167
+ with pytest.raises(ValueError, match="context"):
168
+ parse_image_prediction(output, request=req)
169
+
170
+
171
+ @pytest.mark.parametrize("state", ["predicted", "empty", "failed", "unprocessed"])
172
+ def test_reference_states_are_explicit_and_not_human_completion(state):
173
+ value = artifact()
174
+ row = value["coverage"][0]
175
+ row["status"] = state
176
+ if state != "predicted":
177
+ row["objects"] = []
178
+ if state == "failed":
179
+ row["error"] = {"code": "model_failed", "message": "Model execution failed"}
180
+ parsed = parse_image_reference(value)
181
+ assert parsed.to_dict() == value
182
+ assert "completion" not in parsed.to_dict()
183
+
184
+
185
+ @pytest.mark.parametrize(
186
+ "change",
187
+ [
188
+ "missing",
189
+ "duplicate",
190
+ "foreign-image",
191
+ "wrong-occupancy",
192
+ "missing-error",
193
+ "extra-gt",
194
+ ],
195
+ )
196
+ def test_reference_rejects_incomplete_or_misleading_coverage(change):
197
+ value = artifact()
198
+ if change == "missing":
199
+ value["coverage"] = []
200
+ if change == "duplicate":
201
+ value["coverage"] *= 2
202
+ if change == "foreign-image":
203
+ value["coverage"][0]["image_id"] = "foreign"
204
+ if change == "wrong-occupancy":
205
+ value["coverage"][0]["status"] = "empty"
206
+ if change == "missing-error":
207
+ value["coverage"][0].update(status="failed", objects=[])
208
+ if change == "extra-gt":
209
+ value["completion"] = []
210
+ with pytest.raises(ValueError):
211
+ parse_image_reference(value)
212
+
213
+
214
+ def test_typed_client_pins_source_schema_profile_and_rejects_foreign_results():
215
+ from x4d_devkit.client.image_references import ImageReferencesAPI
216
+
217
+ clip = parse_image_clip(clip_payload())
218
+ identity = clip.to_dict()["identity"]
219
+ state = {
220
+ "run_id": "ref_1",
221
+ "project_id": clip.to_dict()["meta"]["project_id"],
222
+ "clip_id": clip.clip_id,
223
+ "service_id": "curb",
224
+ "profile_id": profile().to_dict()["profile_id"],
225
+ "view_id": "objects",
226
+ "source_revision": identity["source_revision"],
227
+ "schema_fingerprint": identity["schema_fingerprint"],
228
+ "status": "completed",
229
+ "progress": {
230
+ "total": 1,
231
+ "processed": 1,
232
+ "predicted": 1,
233
+ "empty": 0,
234
+ "failed": 0,
235
+ "unprocessed": 0,
236
+ },
237
+ "error": None,
238
+ "created_at": "2026-10-26T00:00:00Z",
239
+ "completed_at": "2026-10-26T00:01:00Z",
240
+ }
241
+
242
+ class Transport:
243
+ def post(self, path, *, json):
244
+ assert path == "/image-references/runs"
245
+ assert "base_annotation_revision" not in json
246
+ assert json["idempotency_key"] == "repeat-safe"
247
+ return state
248
+
249
+ def get(self, path, **kwargs):
250
+ return artifact() if path.endswith("/result") else state
251
+
252
+ client = ImageReferencesAPI(Transport())
253
+ assert (
254
+ client.create(
255
+ clip=clip,
256
+ service_id="curb",
257
+ profile_id=state["profile_id"],
258
+ view_id="objects",
259
+ idempotency_key="repeat-safe",
260
+ ).to_dict()
261
+ == state
262
+ )
263
+ assert client.result("ref_1").to_dict() == artifact()
264
+ with pytest.raises(ValueError, match="foreign"):
265
+ client.result("ref_other")
266
+ with pytest.raises(ValueError, match="foreign"):
267
+ client.get("ref_other")
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
11
11
 
12
12
 
13
13
  def test_package_version_is_current_release():
14
- assert x4d_devkit.__version__ == "0.48.0rc2"
14
+ assert x4d_devkit.__version__ == "0.48.0rc3"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
@@ -25,6 +25,7 @@ from x4d_devkit.client.dataset_identity import DatasetIdentityAPI
25
25
  from x4d_devkit.client.label_schemas import LabelSchemasAPI
26
26
  from x4d_devkit.client.image_clips import ImageClipsAPI
27
27
  from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
28
+ from x4d_devkit.client.image_references import ImageReferencesAPI
28
29
  from x4d_devkit.client.semantic_scene import SemanticSceneAPI
29
30
  from x4d_devkit.client.semantic_scene_authoring import (
30
31
  SemanticSceneAuthoringWorkSessionsAPI,
@@ -74,6 +75,7 @@ class X4DClient:
74
75
  self.clips = ClipsAPI(self)
75
76
  self.image_clips = ImageClipsAPI(self)
76
77
  self.image_work_sessions = ImageWorkSessionsAPI(self)
78
+ self.image_references = ImageReferencesAPI(self)
77
79
  self.label_schemas = LabelSchemasAPI(self)
78
80
  self.semantic_scene = SemanticSceneAPI(self)
79
81
  self.semantic_scene_authoring_work_sessions = (
@@ -0,0 +1,147 @@
1
+ """Explicit native 2D reference runs, independent of human annotation bases."""
2
+
3
+ from urllib.parse import quote
4
+
5
+ from x4d_devkit._image_contract import document, fields, identifier, require
6
+ from x4d_devkit.image_clip import ImageClip
7
+ from x4d_devkit.image_reference import parse_image_reference
8
+
9
+
10
+ def parse_image_reference_run(value):
11
+ fields(
12
+ value,
13
+ {
14
+ "run_id",
15
+ "project_id",
16
+ "clip_id",
17
+ "service_id",
18
+ "profile_id",
19
+ "view_id",
20
+ "source_revision",
21
+ "schema_fingerprint",
22
+ "status",
23
+ "progress",
24
+ "error",
25
+ "created_at",
26
+ "completed_at",
27
+ },
28
+ "image_reference_run",
29
+ )
30
+ identifier(value["run_id"], "run_id")
31
+ from x4d_devkit._image_contract import integer, text
32
+ from x4d_devkit.image_clip import _digest
33
+
34
+ integer(value["project_id"], "project_id", minimum=1)
35
+ for key in ("clip_id", "service_id", "view_id"):
36
+ identifier(value[key], key)
37
+ for key in ("profile_id", "source_revision", "schema_fingerprint"):
38
+ _digest(value[key], key)
39
+ text(value["created_at"], "created_at")
40
+ if value["completed_at"] is not None:
41
+ text(value["completed_at"], "completed_at")
42
+ if value["error"] is not None:
43
+ fields(value["error"], {"code", "message"}, "error")
44
+ for key in ("code", "message"):
45
+ text(value["error"][key], "error." + key)
46
+ require(
47
+ value["status"] in {"queued", "running", "completed", "failed", "cancelled"},
48
+ "invalid run state",
49
+ "status",
50
+ )
51
+ fields(
52
+ value["progress"],
53
+ {"processed", "total", "predicted", "empty", "failed", "unprocessed"},
54
+ "progress",
55
+ )
56
+ from x4d_devkit._image_contract import integer
57
+
58
+ for key, count in value["progress"].items():
59
+ integer(count, "progress." + key)
60
+ p = value["progress"]
61
+ require(
62
+ p["processed"] == p["predicted"] + p["empty"] + p["failed"]
63
+ and p["total"] == p["processed"] + p["unprocessed"],
64
+ "invalid run counters",
65
+ "progress",
66
+ )
67
+ return document(value)
68
+
69
+
70
+ class ImageReferencesAPI:
71
+ def __init__(self, client):
72
+ self._client = client
73
+
74
+ def create(
75
+ self,
76
+ *,
77
+ clip: ImageClip,
78
+ service_id: str,
79
+ profile_id: str,
80
+ view_id: str,
81
+ idempotency_key: str,
82
+ ):
83
+ require(type(clip) is ImageClip, "expected parsed native Clip", "clip")
84
+ value = clip.to_dict()
85
+ request = {
86
+ "clip_id": clip.clip_id,
87
+ "service_id": service_id,
88
+ "profile_id": profile_id,
89
+ "view_id": view_id,
90
+ "idempotency_key": idempotency_key,
91
+ "source_revision": value["identity"]["source_revision"],
92
+ "schema_fingerprint": value["identity"]["schema_fingerprint"],
93
+ }
94
+ result = parse_image_reference_run(
95
+ self._client.post("/image-references/runs", json=request)
96
+ )
97
+ for key, expected in request.items():
98
+ if key != "idempotency_key":
99
+ require(
100
+ result.to_dict()[key] == expected,
101
+ "run request context mismatch",
102
+ key,
103
+ )
104
+ require(
105
+ result.to_dict()["project_id"] == value["meta"]["project_id"],
106
+ "run project context mismatch",
107
+ "project_id",
108
+ )
109
+ return result
110
+
111
+ def list(self, *, clip_id: str, page: int = 1, page_size: int = 20) -> dict:
112
+ identifier(clip_id, "clip_id")
113
+ value = self._client.get(
114
+ "/image-references/runs",
115
+ params={"clip_id": clip_id, "page": page, "page_size": page_size},
116
+ )
117
+ fields(value, {"items", "total", "page", "page_size"}, "image_reference_runs")
118
+ for item in value["items"]:
119
+ parsed = parse_image_reference_run(item).to_dict()
120
+ require(parsed["clip_id"] == clip_id, "foreign Clip run", "clip_id")
121
+ return value
122
+
123
+ def get(self, run_id: str):
124
+ identifier(run_id, "run_id")
125
+ result = parse_image_reference_run(
126
+ self._client.get(f"/image-references/runs/{quote(run_id, safe='')}")
127
+ )
128
+ require(result.to_dict()["run_id"] == run_id, "foreign run", "run_id")
129
+ return result
130
+
131
+ def result(self, run_id: str):
132
+ identifier(run_id, "run_id")
133
+ result = parse_image_reference(
134
+ self._client.get(f"/image-references/runs/{quote(run_id, safe='')}/result")
135
+ )
136
+ require(result.to_dict()["run_id"] == run_id, "foreign reference run", "run_id")
137
+ return result
138
+
139
+ def cancel(self, run_id: str):
140
+ identifier(run_id, "run_id")
141
+ result = parse_image_reference_run(
142
+ self._client.post(
143
+ f"/image-references/runs/{quote(run_id, safe='')}/cancel", json={}
144
+ )
145
+ )
146
+ require(result.to_dict()["run_id"] == run_id, "foreign run", "run_id")
147
+ return result