x4d-devkit 0.48.0rc3__tar.gz → 0.48.0rc4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/PKG-INFO +1 -1
  2. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/pyproject.toml +1 -1
  3. x4d_devkit-0.48.0rc4/tests/test_image_preannotation.py +380 -0
  4. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_reference.py +0 -56
  5. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_sdk_api.py +1 -1
  6. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/client.py +2 -2
  7. x4d_devkit-0.48.0rc4/x4d_devkit/client/image_preannotations.py +117 -0
  8. x4d_devkit-0.48.0rc4/x4d_devkit/image_preannotation.py +330 -0
  9. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/PKG-INFO +1 -1
  10. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/SOURCES.txt +3 -1
  11. x4d_devkit-0.48.0rc3/x4d_devkit/client/image_references.py +0 -147
  12. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/LICENSE +0 -0
  13. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/README.md +0 -0
  14. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/setup.cfg +0 -0
  15. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_annotation_contract.py +0 -0
  16. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_annotation_reference.py +0 -0
  17. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_assets_client.py +0 -0
  18. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_boundary.py +0 -0
  19. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_box.py +0 -0
  20. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_build_info.py +0 -0
  21. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_category_audit.py +0 -0
  22. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap.py +0 -0
  23. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clean_basemap_client.py +0 -0
  24. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_cli_auth.py +0 -0
  25. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_client_json_contract.py +0 -0
  26. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_client_projects.py +0 -0
  27. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_clip_archives.py +0 -0
  28. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_contract_validation.py +0 -0
  29. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_coordinate_frames.py +0 -0
  30. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_current_state.py +0 -0
  31. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_current_state_identity_consumers_example.py +0 -0
  32. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_dataset_contract.py +0 -0
  33. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_dataset_identity.py +0 -0
  34. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_detection_eval.py +0 -0
  35. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_evaluate.py +0 -0
  36. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_export_bundle.py +0 -0
  37. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_clip.py +0 -0
  38. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_geometry.py +0 -0
  39. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_review.py +0 -0
  40. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_image_work_session.py +0 -0
  41. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_input.py +0 -0
  42. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_schemas.py +0 -0
  43. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_inference_service.py +0 -0
  44. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_label_schemas.py +0 -0
  45. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_lidar_materialization.py +0 -0
  46. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_loader.py +0 -0
  47. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_matching.py +0 -0
  48. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_metrics.py +0 -0
  49. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_models.py +0 -0
  50. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_platform_pose.py +0 -0
  51. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_preannotation_client.py +0 -0
  52. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_reconstruction_client.py +0 -0
  53. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_release_gate.py +0 -0
  54. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_roadmark_preview.py +0 -0
  55. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_authoring.py +0 -0
  56. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_client.py +0 -0
  57. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_contract.py +0 -0
  58. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_independent.py +0 -0
  59. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_semantic_scene_native_clip.py +0 -0
  60. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_source_attributes.py +0 -0
  61. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_time_contract.py +0 -0
  62. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_token.py +0 -0
  63. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_tracking_eval.py +0 -0
  64. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_training_identity.py +0 -0
  65. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_transform.py +0 -0
  66. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_transform_chain.py +0 -0
  67. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_validation.py +0 -0
  68. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map.py +0 -0
  69. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_geometry.py +0 -0
  70. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_vector_map_session.py +0 -0
  71. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_work_session.py +0 -0
  72. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/tests/test_work_session_validation_reuse.py +0 -0
  73. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/__init__.py +0 -0
  74. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/_image_contract.py +0 -0
  75. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_contract.py +0 -0
  76. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/annotation_reference.py +0 -0
  77. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/audit.py +0 -0
  78. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/build_info.py +0 -0
  79. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/clean_basemap.py +0 -0
  80. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/cli.py +0 -0
  81. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/__init__.py +0 -0
  82. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/_origin.py +0 -0
  83. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/annotation_reference.py +0 -0
  84. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/assets.py +0 -0
  85. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/bags.py +0 -0
  86. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/capabilities.py +0 -0
  87. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/checkpoints.py +0 -0
  88. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clean_basemap.py +0 -0
  89. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clip_work_sessions.py +0 -0
  90. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/clips.py +0 -0
  91. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/dataset_identity.py +0 -0
  92. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_clips.py +0 -0
  93. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_review.py +0 -0
  94. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/image_work_sessions.py +0 -0
  95. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/label_schemas.py +0 -0
  96. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/preannotation.py +0 -0
  97. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/projects.py +0 -0
  98. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/reconstruction.py +0 -0
  99. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene.py +0 -0
  100. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  101. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/services.py +0 -0
  102. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/client/test_sets.py +0 -0
  103. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/consumer_identity.py +0 -0
  104. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/coordinate_contract.py +0 -0
  105. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/__init__.py +0 -0
  106. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/loader.py +0 -0
  107. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/models.py +0 -0
  108. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/token.py +0 -0
  109. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/core/transform.py +0 -0
  110. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/current_state.py +0 -0
  111. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_contract.py +0 -0
  112. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/dataset_identity.py +0 -0
  113. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/__init__.py +0 -0
  114. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/api.py +0 -0
  115. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/box.py +0 -0
  116. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/detection.py +0 -0
  117. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/matching.py +0 -0
  118. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/metrics.py +0 -0
  119. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/eval/tracking.py +0 -0
  120. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/export_bundle.py +0 -0
  121. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_clip.py +0 -0
  122. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_geometry.py +0 -0
  123. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_reference.py +0 -0
  124. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_review.py +0 -0
  125. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_schema.py +0 -0
  126. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/image_work_session.py +0 -0
  127. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/__init__.py +0 -0
  128. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/input.py +0 -0
  129. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/models.py +0 -0
  130. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/schemas.py +0 -0
  131. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/service.py +0 -0
  132. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/inference/tasks.py +0 -0
  133. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/json_contract.py +0 -0
  134. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/label_schema.py +0 -0
  135. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/lidar_materialization.py +0 -0
  136. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/materialize.py +0 -0
  137. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/roadmark_preview.py +0 -0
  138. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_authoring.py +0 -0
  139. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/semantic_scene_contract.py +0 -0
  140. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/time_contract.py +0 -0
  141. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/training_identity.py +0 -0
  142. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/__init__.py +0 -0
  143. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/contract.py +0 -0
  144. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/platform_pose.py +0 -0
  145. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/report.py +0 -0
  146. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/validation/validator.py +0 -0
  147. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map.py +0 -0
  148. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/vector_map_geometry.py +0 -0
  149. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit/work_session.py +0 -0
  150. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  151. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/entry_points.txt +0 -0
  152. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/x4d_devkit.egg-info/requires.txt +0 -0
  153. {x4d_devkit-0.48.0rc3 → x4d_devkit-0.48.0rc4}/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.0rc3
3
+ Version: 0.48.0rc4
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.0rc3"
7
+ version = "0.48.0rc4"
8
8
  description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,380 @@
1
+ import copy
2
+ import json
3
+
4
+ import httpx
5
+ import pytest
6
+
7
+ from x4d_devkit import X4DClient
8
+ from x4d_devkit._image_contract import fingerprint
9
+ from x4d_devkit.image_clip import build_image_identity, parse_image_clip
10
+ from x4d_devkit.image_preannotation import (
11
+ CURRENT_CONTRACT,
12
+ STATE_CONTRACT,
13
+ REQUEST_CONTRACT,
14
+ SERVICE_CONTRACT,
15
+ parse_image_preannotation_current,
16
+ parse_image_preannotation_state,
17
+ parse_image_preannotation_request,
18
+ parse_image_preannotation_service,
19
+ parse_preannotation_operation,
20
+ )
21
+ from x4d_devkit.image_reference import IMAGE_INFERENCE_PROTOCOL, image_reference_source
22
+ from tests.test_image_reference import artifact, clip_payload, profile
23
+ from tests.image_fixtures import line_object
24
+
25
+
26
+ def context():
27
+ value = clip_payload()
28
+ return {
29
+ "clip_id": value["meta"]["clip_id"],
30
+ "project_id": value["meta"]["project_id"],
31
+ "view_id": "objects",
32
+ "source_revision": value["identity"]["source_revision"],
33
+ "schema_fingerprint": value["identity"]["schema_fingerprint"],
34
+ }
35
+
36
+
37
+ def progress():
38
+ return {
39
+ "total": 1,
40
+ "processed": 0,
41
+ "predicted": 0,
42
+ "empty": 0,
43
+ "failed": 0,
44
+ "unprocessed": 1,
45
+ }
46
+
47
+
48
+ def operation():
49
+ return {
50
+ "operation_id": "op_1",
51
+ "status": "running",
52
+ "progress": progress(),
53
+ "error": None,
54
+ "created_at": "2026-10-26T00:00:00Z",
55
+ "completed_at": None,
56
+ "can_cancel": True,
57
+ }
58
+
59
+
60
+ def current():
61
+ result = artifact()
62
+ return {
63
+ **context(),
64
+ "contract_version": CURRENT_CONTRACT,
65
+ "availability": "ready",
66
+ "published_at": "2026-10-26T00:01:00Z",
67
+ "result_digest": fingerprint(result),
68
+ "artifact": result,
69
+ }
70
+
71
+
72
+ def state():
73
+ return {
74
+ **context(),
75
+ "contract_version": STATE_CONTRACT,
76
+ "service_state": "ready",
77
+ "result": {
78
+ "availability": "ready",
79
+ "digest": current()["result_digest"],
80
+ "published_at": current()["published_at"],
81
+ "coverage": {
82
+ "total": 1,
83
+ "processed": 1,
84
+ "predicted": 1,
85
+ "empty": 0,
86
+ "failed": 0,
87
+ "unprocessed": 0,
88
+ },
89
+ },
90
+ "operation": operation(),
91
+ }
92
+
93
+
94
+ def receipt():
95
+ return {
96
+ **context(),
97
+ "contract_version": REQUEST_CONTRACT,
98
+ "disposition": "created",
99
+ "operation": operation(),
100
+ }
101
+
102
+
103
+ def test_current_is_separate_from_human_revision_and_is_a_detached_snapshot():
104
+ value = current()
105
+ parsed = parse_image_preannotation_current(value)
106
+ value["artifact"]["coverage"][0]["objects"].clear()
107
+ assert parsed.to_dict()["artifact"]["coverage"][0]["objects"]
108
+ assert "annotation_revision" not in parsed.to_dict()
109
+ human = clip_payload()
110
+ human["image_annotation"]["objects"] = [line_object()]
111
+ human["identity"] = build_image_identity(human)
112
+ assert (
113
+ image_reference_source(parse_image_clip(human)).to_dict()
114
+ == artifact()["source"]
115
+ )
116
+
117
+
118
+ def test_successful_zero_detection_is_ready_and_never_human_completion():
119
+ value = current()
120
+ value["artifact"]["coverage"][0].update(status="empty", objects=[])
121
+ value["result_digest"] = fingerprint(value["artifact"])
122
+ result = parse_image_preannotation_current(value).to_dict()
123
+ assert result["availability"] == "ready"
124
+ assert result["artifact"]["coverage"][0]["status"] == "empty"
125
+ assert "completion" not in result["artifact"]
126
+
127
+
128
+ @pytest.mark.parametrize("availability", ["none", "stale"])
129
+ def test_absence_is_explicit_and_cannot_carry_an_old_artifact(availability):
130
+ value = {
131
+ **context(),
132
+ "contract_version": CURRENT_CONTRACT,
133
+ "availability": availability,
134
+ "published_at": None,
135
+ "result_digest": None,
136
+ "artifact": None,
137
+ }
138
+ assert parse_image_preannotation_current(value).to_dict() == value
139
+ value["artifact"] = artifact()
140
+ with pytest.raises(ValueError):
141
+ parse_image_preannotation_current(value)
142
+
143
+
144
+ def test_partial_output_has_complete_failed_scopes_without_old_fill():
145
+ payload = clip_payload()
146
+ sample = copy.deepcopy(payload["sample"][0])
147
+ sample.update(
148
+ id="sample_b",
149
+ timestamp={"sec": 11, "nanosec": 20},
150
+ image_ids={"cam_a": "image_b"},
151
+ )
152
+ payload["sample"].append(sample)
153
+ image = copy.deepcopy(payload["sample_data"][0])
154
+ image.update(
155
+ id="image_b", sample_id="sample_b", timestamp={"sec": 11, "nanosec": 20}
156
+ )
157
+ image["asset"]["path"] = "samples/cam_a/11000000020.jpg"
158
+ payload["sample_data"].append(image)
159
+ payload["image_annotation"]["completion"] += [
160
+ {"image_id": "image_b", "view_id": view, "state": "unprocessed"}
161
+ for view in payload["meta"]["view_ids"]
162
+ ]
163
+ payload["identity"] = build_image_identity(payload)
164
+ value = current()
165
+ value["artifact"]["source"] = image_reference_source(
166
+ parse_image_clip(payload)
167
+ ).to_dict()
168
+ value["source_revision"] = payload["identity"]["source_revision"]
169
+ value["artifact"]["coverage"].append(
170
+ {
171
+ "image_id": "image_b",
172
+ "status": "failed",
173
+ "objects": [],
174
+ "error": {"code": "invalid_image", "message": "Image failed"},
175
+ }
176
+ )
177
+ value["availability"] = "partial"
178
+ value["result_digest"] = fingerprint(value["artifact"])
179
+ parsed = parse_image_preannotation_current(value).to_dict()
180
+ assert parsed["artifact"]["coverage"][1]["objects"] == []
181
+ value["availability"] = "ready"
182
+ with pytest.raises(ValueError):
183
+ parse_image_preannotation_current(value)
184
+ value["availability"] = "partial"
185
+ value["artifact"]["coverage"][0].update(
186
+ status="failed", objects=[], error={"code": "failed", "message": "Image failed"}
187
+ )
188
+ value["result_digest"] = fingerprint(value["artifact"])
189
+ with pytest.raises(ValueError, match="successes"):
190
+ parse_image_preannotation_current(value)
191
+
192
+
193
+ @pytest.mark.parametrize(
194
+ "change",
195
+ [
196
+ "digest",
197
+ "source",
198
+ "view",
199
+ "project",
200
+ "missing_image",
201
+ "extra_choice",
202
+ "all_failed",
203
+ "unknown_availability",
204
+ ],
205
+ )
206
+ def test_reject_wrong_current_context_geometry_or_selectors(change):
207
+ value = current()
208
+ if change == "digest":
209
+ value["result_digest"] = "sha256:" + "0" * 64
210
+ elif change == "source":
211
+ value["source_revision"] = "sha256:" + "0" * 64
212
+ elif change == "view":
213
+ value["view_id"] = "semantics"
214
+ elif change == "project":
215
+ value["project_id"] = 99
216
+ elif change == "missing_image":
217
+ value["artifact"]["coverage"] = []
218
+ elif change == "extra_choice":
219
+ value["model_version"] = "latest"
220
+ elif change == "all_failed":
221
+ value["availability"] = "partial"
222
+ value["artifact"]["coverage"][0].update(
223
+ status="failed",
224
+ objects=[],
225
+ error={"code": "failed", "message": "Image failed"},
226
+ )
227
+ value["result_digest"] = fingerprint(value["artifact"])
228
+ else:
229
+ value["availability"] = {}
230
+ with pytest.raises(ValueError):
231
+ parse_image_preannotation_current(value)
232
+
233
+
234
+ @pytest.mark.parametrize(
235
+ "change",
236
+ [
237
+ "timestamp",
238
+ "boolean_count",
239
+ "bad_count",
240
+ "terminal_time",
241
+ "terminal_cancel",
242
+ "partial_without_success",
243
+ "unknown_state",
244
+ ],
245
+ )
246
+ def test_operation_state_is_strict(change):
247
+ value = operation()
248
+ if change == "timestamp":
249
+ value["created_at"] = "yesterday"
250
+ elif change == "boolean_count":
251
+ value["progress"]["processed"] = True
252
+ elif change == "bad_count":
253
+ value["progress"]["processed"] = 1
254
+ elif change == "terminal_time":
255
+ value["status"] = "failed"
256
+ elif change == "terminal_cancel":
257
+ value.update(status="cancelled", completed_at="2026-10-26T00:01:00Z")
258
+ elif change == "partial_without_success":
259
+ value.update(
260
+ status="partial", completed_at="2026-10-26T00:01:00Z", can_cancel=False
261
+ )
262
+ else:
263
+ value["status"] = {}
264
+ with pytest.raises(ValueError):
265
+ parse_preannotation_operation(value)
266
+
267
+
268
+ def test_state_keeps_existing_result_available_during_or_after_failed_rerun():
269
+ value = state()
270
+ assert parse_image_preannotation_state(value).to_dict() == value
271
+ value["operation"].update(
272
+ status="failed",
273
+ completed_at="2026-10-26T00:01:00Z",
274
+ can_cancel=False,
275
+ error={"code": "service_unavailable", "message": "Service failed"},
276
+ )
277
+ assert (
278
+ parse_image_preannotation_state(value).to_dict()["result"]["availability"]
279
+ == "ready"
280
+ )
281
+ value["result"]["availability"] = "none"
282
+ with pytest.raises(ValueError):
283
+ parse_image_preannotation_state(value)
284
+
285
+
286
+ def test_receipt_rejects_model_choice_and_available_creating_work():
287
+ value = receipt()
288
+ assert parse_image_preannotation_request(value).to_dict() == value
289
+ value["profile_id"] = profile().to_dict()["profile_id"]
290
+ with pytest.raises(ValueError):
291
+ parse_image_preannotation_request(value)
292
+ value = receipt()
293
+ value["disposition"] = "available"
294
+ with pytest.raises(ValueError):
295
+ parse_image_preannotation_request(value)
296
+
297
+
298
+ def test_service_is_ready_with_one_model_and_explicit_task_outputs():
299
+ value = {
300
+ "contract_version": SERVICE_CONTRACT,
301
+ "protocol_version": IMAGE_INFERENCE_PROTOCOL,
302
+ "status": "ready",
303
+ "service_name": "synthetic",
304
+ "service_version": "test",
305
+ "task_ids": ["test_objects"],
306
+ "model": profile().to_dict(),
307
+ "outputs": [{"category_id": "line", "geometry_type": "polyline_2d"}],
308
+ "max_concurrent_tasks": 1,
309
+ }
310
+ assert parse_image_preannotation_service(value).to_dict() == value
311
+ value["task_ids"] *= 2
312
+ with pytest.raises(ValueError):
313
+ parse_image_preannotation_service(value)
314
+ value["task_ids"] = ["test_objects"]
315
+ value["models"] = [profile().to_dict()]
316
+ with pytest.raises(ValueError):
317
+ parse_image_preannotation_service(value)
318
+
319
+
320
+ def test_typed_client_resolves_no_model_or_history_and_rejects_foreign_current():
321
+ calls = []
322
+ foreign = False
323
+
324
+ def handler(request):
325
+ calls.append(request)
326
+ if request.method == "POST" and request.url.path.endswith("/requests"):
327
+ body = json.loads(request.content)
328
+ assert set(body) == {
329
+ "clip_id",
330
+ "view_id",
331
+ "source_revision",
332
+ "schema_fingerprint",
333
+ "idempotency_key",
334
+ "recompute",
335
+ }
336
+ return httpx.Response(200, json=receipt())
337
+ if request.url.path.endswith("/current"):
338
+ value = current()
339
+ if foreign:
340
+ value["clip_id"] = "foreign"
341
+ return httpx.Response(200, json=value)
342
+ if request.url.path.endswith("/cancel"):
343
+ assert json.loads(request.content) == {"operation_id": "op_1"}
344
+ return httpx.Response(200, json=state())
345
+
346
+ with X4DClient("https://platform.test") as client:
347
+ client._http.close()
348
+ client._http = httpx.Client(
349
+ base_url="https://platform.test", transport=httpx.MockTransport(handler)
350
+ )
351
+ clip = parse_image_clip(clip_payload())
352
+ api = client.image_preannotations
353
+ assert not hasattr(client, "image_references")
354
+ assert (
355
+ api.request(
356
+ clip=clip, view_id="objects", idempotency_key="request_1"
357
+ ).to_dict()
358
+ == receipt()
359
+ )
360
+ assert api.current(clip=clip, view_id="objects").to_dict() == current()
361
+ assert api.status(clip=clip, view_id="objects").to_dict() == state()
362
+ assert (
363
+ api.cancel(
364
+ clip_id=clip.clip_id, view_id="objects", operation_id="op_1"
365
+ ).to_dict()
366
+ == state()
367
+ )
368
+ foreign = True
369
+ with pytest.raises(ValueError):
370
+ api.current(clip=clip, view_id="objects")
371
+ with pytest.raises(TypeError):
372
+ api.request(
373
+ clip=clip,
374
+ view_id="objects",
375
+ idempotency_key="request_1",
376
+ profile_id="forbidden",
377
+ )
378
+ for call in calls:
379
+ assert "image-references" not in call.url.path
380
+ assert "annotation_revision" not in str(call.url)
@@ -209,59 +209,3 @@ def test_reference_rejects_incomplete_or_misleading_coverage(change):
209
209
  value["completion"] = []
210
210
  with pytest.raises(ValueError):
211
211
  parse_image_reference(value)
212
-
213
-
214
- def test_typed_client_pins_source_schema_profile_and_rejects_foreign_results():
215
- from x4d_devkit.client.image_references import ImageReferencesAPI
216
-
217
- clip = parse_image_clip(clip_payload())
218
- identity = clip.to_dict()["identity"]
219
- state = {
220
- "run_id": "ref_1",
221
- "project_id": clip.to_dict()["meta"]["project_id"],
222
- "clip_id": clip.clip_id,
223
- "service_id": "curb",
224
- "profile_id": profile().to_dict()["profile_id"],
225
- "view_id": "objects",
226
- "source_revision": identity["source_revision"],
227
- "schema_fingerprint": identity["schema_fingerprint"],
228
- "status": "completed",
229
- "progress": {
230
- "total": 1,
231
- "processed": 1,
232
- "predicted": 1,
233
- "empty": 0,
234
- "failed": 0,
235
- "unprocessed": 0,
236
- },
237
- "error": None,
238
- "created_at": "2026-10-26T00:00:00Z",
239
- "completed_at": "2026-10-26T00:01:00Z",
240
- }
241
-
242
- class Transport:
243
- def post(self, path, *, json):
244
- assert path == "/image-references/runs"
245
- assert "base_annotation_revision" not in json
246
- assert json["idempotency_key"] == "repeat-safe"
247
- return state
248
-
249
- def get(self, path, **kwargs):
250
- return artifact() if path.endswith("/result") else state
251
-
252
- client = ImageReferencesAPI(Transport())
253
- assert (
254
- client.create(
255
- clip=clip,
256
- service_id="curb",
257
- profile_id=state["profile_id"],
258
- view_id="objects",
259
- idempotency_key="repeat-safe",
260
- ).to_dict()
261
- == state
262
- )
263
- assert client.result("ref_1").to_dict() == artifact()
264
- with pytest.raises(ValueError, match="foreign"):
265
- client.result("ref_other")
266
- with pytest.raises(ValueError, match="foreign"):
267
- client.get("ref_other")
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
11
11
 
12
12
 
13
13
  def test_package_version_is_current_release():
14
- assert x4d_devkit.__version__ == "0.48.0rc3"
14
+ assert x4d_devkit.__version__ == "0.48.0rc4"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
@@ -25,7 +25,7 @@ from x4d_devkit.client.dataset_identity import DatasetIdentityAPI
25
25
  from x4d_devkit.client.label_schemas import LabelSchemasAPI
26
26
  from x4d_devkit.client.image_clips import ImageClipsAPI
27
27
  from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
28
- from x4d_devkit.client.image_references import ImageReferencesAPI
28
+ from x4d_devkit.client.image_preannotations import ImagePreannotationsAPI
29
29
  from x4d_devkit.client.semantic_scene import SemanticSceneAPI
30
30
  from x4d_devkit.client.semantic_scene_authoring import (
31
31
  SemanticSceneAuthoringWorkSessionsAPI,
@@ -75,7 +75,7 @@ class X4DClient:
75
75
  self.clips = ClipsAPI(self)
76
76
  self.image_clips = ImageClipsAPI(self)
77
77
  self.image_work_sessions = ImageWorkSessionsAPI(self)
78
- self.image_references = ImageReferencesAPI(self)
78
+ self.image_preannotations = ImagePreannotationsAPI(self)
79
79
  self.label_schemas = LabelSchemasAPI(self)
80
80
  self.semantic_scene = SemanticSceneAPI(self)
81
81
  self.semantic_scene_authoring_work_sessions = (
@@ -0,0 +1,117 @@
1
+ """One current native preannotation, with a server-resolved startup model."""
2
+
3
+ from urllib.parse import quote
4
+
5
+ from x4d_devkit._image_contract import identifier, require
6
+ from x4d_devkit.image_clip import ImageClip
7
+ from x4d_devkit.image_preannotation import (
8
+ parse_image_preannotation_current,
9
+ parse_image_preannotation_request,
10
+ parse_image_preannotation_state,
11
+ )
12
+
13
+
14
+ def _context(clip, view_id):
15
+ require(type(clip) is ImageClip, "expected a parsed native Clip", "clip")
16
+ value = clip.to_dict()
17
+ identifier(view_id, "view_id")
18
+ require(
19
+ view_id in value["meta"]["view_ids"],
20
+ "view is not applicable to this Clip",
21
+ "view_id",
22
+ )
23
+ return {
24
+ "clip_id": clip.clip_id,
25
+ "project_id": value["meta"]["project_id"],
26
+ "view_id": view_id,
27
+ "source_revision": value["identity"]["source_revision"],
28
+ "schema_fingerprint": value["identity"]["schema_fingerprint"],
29
+ }
30
+
31
+
32
+ def _check(result, context):
33
+ value = result.to_dict()
34
+ require(
35
+ all(value[key] == expected for key, expected in context.items()),
36
+ "response context mismatch",
37
+ "context",
38
+ )
39
+ return result
40
+
41
+
42
+ def _path(clip_id, view_id):
43
+ identifier(clip_id, "clip_id")
44
+ identifier(view_id, "view_id")
45
+ return f"/image-preannotations/clips/{quote(clip_id, safe='')}/views/{quote(view_id, safe='')}"
46
+
47
+
48
+ class ImagePreannotationsAPI:
49
+ def __init__(self, client):
50
+ self._client = client
51
+
52
+ def request(
53
+ self,
54
+ *,
55
+ clip: ImageClip,
56
+ view_id: str,
57
+ idempotency_key: str,
58
+ recompute: bool = False,
59
+ ):
60
+ context = _context(clip, view_id)
61
+ identifier(idempotency_key, "idempotency_key")
62
+ require(type(recompute) is bool, "expected an explicit boolean", "recompute")
63
+ body = {key: value for key, value in context.items() if key != "project_id"}
64
+ body.update(idempotency_key=idempotency_key, recompute=recompute)
65
+ return _check(
66
+ parse_image_preannotation_request(
67
+ self._client.post("/image-preannotations/requests", json=body)
68
+ ),
69
+ context,
70
+ )
71
+
72
+ def _read(self, *, clip, view_id, suffix, parser):
73
+ context = _context(clip, view_id)
74
+ value = self._client.get(
75
+ _path(clip.clip_id, view_id) + suffix,
76
+ params={
77
+ key: context[key] for key in ("source_revision", "schema_fingerprint")
78
+ },
79
+ )
80
+ return _check(parser(value), context)
81
+
82
+ def status(self, *, clip: ImageClip, view_id: str):
83
+ return self._read(
84
+ clip=clip,
85
+ view_id=view_id,
86
+ suffix="/status",
87
+ parser=parse_image_preannotation_state,
88
+ )
89
+
90
+ def current(self, *, clip: ImageClip, view_id: str):
91
+ return self._read(
92
+ clip=clip,
93
+ view_id=view_id,
94
+ suffix="/current",
95
+ parser=parse_image_preannotation_current,
96
+ )
97
+
98
+ def cancel(self, *, clip_id: str, view_id: str, operation_id: str):
99
+ identifier(operation_id, "operation_id")
100
+ value = parse_image_preannotation_state(
101
+ self._client.post(
102
+ _path(clip_id, view_id) + "/cancel", json={"operation_id": operation_id}
103
+ )
104
+ )
105
+ data = value.to_dict()
106
+ require(
107
+ data["clip_id"] == clip_id and data["view_id"] == view_id,
108
+ "foreign cancellation scope",
109
+ "context",
110
+ )
111
+ require(
112
+ data["operation"] is not None
113
+ and data["operation"]["operation_id"] == operation_id,
114
+ "foreign cancellation operation",
115
+ "operation",
116
+ )
117
+ return value