x4d-devkit 0.48.0rc1__tar.gz → 0.48.0rc2__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 (147) hide show
  1. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/PKG-INFO +1 -1
  2. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/pyproject.toml +1 -1
  3. x4d_devkit-0.48.0rc2/tests/test_image_work_session.py +228 -0
  4. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_sdk_api.py +1 -1
  5. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/__init__.py +2 -0
  6. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/client.py +2 -0
  7. x4d_devkit-0.48.0rc2/x4d_devkit/client/image_work_sessions.py +80 -0
  8. x4d_devkit-0.48.0rc2/x4d_devkit/image_work_session.py +161 -0
  9. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit.egg-info/PKG-INFO +1 -1
  10. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit.egg-info/SOURCES.txt +3 -0
  11. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/LICENSE +0 -0
  12. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/README.md +0 -0
  13. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/setup.cfg +0 -0
  14. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_annotation_contract.py +0 -0
  15. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_annotation_reference.py +0 -0
  16. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_assets_client.py +0 -0
  17. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_boundary.py +0 -0
  18. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_box.py +0 -0
  19. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_build_info.py +0 -0
  20. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_category_audit.py +0 -0
  21. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_clean_basemap.py +0 -0
  22. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_clean_basemap_client.py +0 -0
  23. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_cli_auth.py +0 -0
  24. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_client_json_contract.py +0 -0
  25. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_client_projects.py +0 -0
  26. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_clip_archives.py +0 -0
  27. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_contract_validation.py +0 -0
  28. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_coordinate_frames.py +0 -0
  29. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_current_state.py +0 -0
  30. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_current_state_identity_consumers_example.py +0 -0
  31. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_dataset_contract.py +0 -0
  32. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_dataset_identity.py +0 -0
  33. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_detection_eval.py +0 -0
  34. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_evaluate.py +0 -0
  35. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_export_bundle.py +0 -0
  36. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_image_clip.py +0 -0
  37. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_image_geometry.py +0 -0
  38. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_image_review.py +0 -0
  39. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_inference_input.py +0 -0
  40. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_inference_schemas.py +0 -0
  41. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_inference_service.py +0 -0
  42. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_label_schemas.py +0 -0
  43. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_lidar_materialization.py +0 -0
  44. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_loader.py +0 -0
  45. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_matching.py +0 -0
  46. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_metrics.py +0 -0
  47. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_models.py +0 -0
  48. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_platform_pose.py +0 -0
  49. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_preannotation_client.py +0 -0
  50. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_reconstruction_client.py +0 -0
  51. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_release_gate.py +0 -0
  52. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_roadmark_preview.py +0 -0
  53. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_semantic_scene_authoring.py +0 -0
  54. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_semantic_scene_client.py +0 -0
  55. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_semantic_scene_contract.py +0 -0
  56. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_semantic_scene_independent.py +0 -0
  57. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_semantic_scene_native_clip.py +0 -0
  58. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_source_attributes.py +0 -0
  59. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_time_contract.py +0 -0
  60. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_token.py +0 -0
  61. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_tracking_eval.py +0 -0
  62. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_training_identity.py +0 -0
  63. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_transform.py +0 -0
  64. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_transform_chain.py +0 -0
  65. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_validation.py +0 -0
  66. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_vector_map.py +0 -0
  67. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_vector_map_geometry.py +0 -0
  68. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_vector_map_session.py +0 -0
  69. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_work_session.py +0 -0
  70. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/tests/test_work_session_validation_reuse.py +0 -0
  71. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/__init__.py +0 -0
  72. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/_image_contract.py +0 -0
  73. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/annotation_contract.py +0 -0
  74. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/annotation_reference.py +0 -0
  75. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/audit.py +0 -0
  76. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/build_info.py +0 -0
  77. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/clean_basemap.py +0 -0
  78. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/cli.py +0 -0
  79. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/_origin.py +0 -0
  80. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/annotation_reference.py +0 -0
  81. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/assets.py +0 -0
  82. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/bags.py +0 -0
  83. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/capabilities.py +0 -0
  84. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/checkpoints.py +0 -0
  85. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/clean_basemap.py +0 -0
  86. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/clip_work_sessions.py +0 -0
  87. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/clips.py +0 -0
  88. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/dataset_identity.py +0 -0
  89. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/image_clips.py +0 -0
  90. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/image_review.py +0 -0
  91. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/label_schemas.py +0 -0
  92. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/preannotation.py +0 -0
  93. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/projects.py +0 -0
  94. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/reconstruction.py +0 -0
  95. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/semantic_scene.py +0 -0
  96. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  97. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/services.py +0 -0
  98. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/client/test_sets.py +0 -0
  99. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/consumer_identity.py +0 -0
  100. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/coordinate_contract.py +0 -0
  101. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/core/__init__.py +0 -0
  102. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/core/loader.py +0 -0
  103. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/core/models.py +0 -0
  104. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/core/token.py +0 -0
  105. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/core/transform.py +0 -0
  106. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/current_state.py +0 -0
  107. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/dataset_contract.py +0 -0
  108. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/dataset_identity.py +0 -0
  109. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/__init__.py +0 -0
  110. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/api.py +0 -0
  111. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/box.py +0 -0
  112. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/detection.py +0 -0
  113. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/matching.py +0 -0
  114. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/metrics.py +0 -0
  115. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/eval/tracking.py +0 -0
  116. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/export_bundle.py +0 -0
  117. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/image_clip.py +0 -0
  118. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/image_geometry.py +0 -0
  119. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/image_review.py +0 -0
  120. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/image_schema.py +0 -0
  121. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/__init__.py +0 -0
  122. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/input.py +0 -0
  123. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/models.py +0 -0
  124. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/schemas.py +0 -0
  125. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/service.py +0 -0
  126. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/inference/tasks.py +0 -0
  127. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/json_contract.py +0 -0
  128. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/label_schema.py +0 -0
  129. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/lidar_materialization.py +0 -0
  130. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/materialize.py +0 -0
  131. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/roadmark_preview.py +0 -0
  132. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/semantic_scene_authoring.py +0 -0
  133. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/semantic_scene_contract.py +0 -0
  134. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/time_contract.py +0 -0
  135. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/training_identity.py +0 -0
  136. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/validation/__init__.py +0 -0
  137. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/validation/contract.py +0 -0
  138. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/validation/platform_pose.py +0 -0
  139. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/validation/report.py +0 -0
  140. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/validation/validator.py +0 -0
  141. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/vector_map.py +0 -0
  142. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/vector_map_geometry.py +0 -0
  143. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit/work_session.py +0 -0
  144. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  145. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit.egg-info/entry_points.txt +0 -0
  146. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/x4d_devkit.egg-info/requires.txt +0 -0
  147. {x4d_devkit-0.48.0rc1 → x4d_devkit-0.48.0rc2}/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.0rc1
3
+ Version: 0.48.0rc2
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.0rc1"
7
+ version = "0.48.0rc2"
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,228 @@
1
+ import copy
2
+
3
+ import httpx
4
+ import pytest
5
+
6
+ from x4d_devkit import X4DClient
7
+ from x4d_devkit.client import WorkSessionConflictError
8
+ from x4d_devkit.image_clip import build_image_identity
9
+ from x4d_devkit.image_work_session import (
10
+ IMAGE_SUBMIT_RECEIPT_VERSION,
11
+ IMAGE_WORK_SESSION_VERSION,
12
+ build_image_submit,
13
+ image_candidate,
14
+ parse_image_submit,
15
+ parse_image_submit_receipt,
16
+ parse_image_work_session,
17
+ )
18
+ from tests.image_fixtures import clip_payload, line_object
19
+
20
+
21
+ def session():
22
+ return parse_image_work_session(
23
+ {"contract_version": IMAGE_WORK_SESSION_VERSION, "clip": clip_payload()}
24
+ )
25
+
26
+
27
+ def candidate():
28
+ annotation = session().clip.to_dict()["image_annotation"]
29
+ annotation["objects"] = [line_object()]
30
+ annotation["completion"][0]["state"] = "complete"
31
+ return annotation
32
+
33
+
34
+ def receipt(annotation=None):
35
+ ws = session()
36
+ value = image_candidate(ws.clip, annotation or candidate()).to_dict()
37
+ return {
38
+ "contract_version": IMAGE_SUBMIT_RECEIPT_VERSION,
39
+ "project_id": value["meta"]["project_id"],
40
+ "clip_id": value["meta"]["clip_id"],
41
+ **{
42
+ key: value["identity"][key]
43
+ for key in (
44
+ "source_revision",
45
+ "schema_fingerprint",
46
+ "annotation_revision",
47
+ "clip_revision",
48
+ )
49
+ },
50
+ "changed": True,
51
+ "projection_state": "ready",
52
+ }
53
+
54
+
55
+ def client(handler):
56
+ value = X4DClient("https://platform.test", token="private")
57
+ value._http.close()
58
+ value._http = httpx.Client(
59
+ base_url="https://platform.test", transport=httpx.MockTransport(handler)
60
+ )
61
+ return value
62
+
63
+
64
+ def test_complete_candidate_preserves_source_schema_other_views_and_stable_ranges():
65
+ ws = session()
66
+ request = build_image_submit(ws, candidate()).to_dict()
67
+ parsed = parse_image_submit(request)
68
+ assert parsed.to_dict()["image_annotation"] == candidate()
69
+ value = image_candidate(ws.clip, candidate()).to_dict()
70
+ assert (
71
+ value["identity"]["source_revision"]
72
+ == ws.clip.to_dict()["identity"]["source_revision"]
73
+ )
74
+ assert value["identity"]["schema_fingerprint"] == request["base_schema_fingerprint"]
75
+ assert (
76
+ value["image_annotation"]["objects"][0]["attribute_ranges"]
77
+ == line_object()["attribute_ranges"]
78
+ )
79
+ assert ws.clip.to_dict()["image_annotation"]["objects"] == []
80
+ request["image_annotation"]["objects"].clear()
81
+ assert parsed.to_dict()["image_annotation"]["objects"]
82
+
83
+
84
+ @pytest.mark.parametrize(
85
+ "change",
86
+ [
87
+ "version",
88
+ "extra",
89
+ "image-version",
90
+ "invalid-clip",
91
+ "missing-range",
92
+ "missing-scope",
93
+ "foreign-object",
94
+ ],
95
+ )
96
+ def test_strict_session_and_candidate_rejection(change):
97
+ value = session().to_dict()
98
+ if change == "version":
99
+ value["contract_version"] = "x4d-clip-work-session-v10"
100
+ elif change == "extra":
101
+ value["ready"] = True
102
+ elif change == "image-version":
103
+ value["clip"]["meta"]["schema_version"] = "0.18"
104
+ elif change == "invalid-clip":
105
+ value["clip"]["identity"]["clip_revision"] = "sha256:" + "0" * 64
106
+ else:
107
+ ann = candidate()
108
+ if change == "missing-range":
109
+ ann["objects"][0]["attribute_ranges"][0]["start_vertex_id"] = "missing"
110
+ elif change == "missing-scope":
111
+ ann["completion"].pop()
112
+ else:
113
+ ann["objects"][0]["image_id"] = "foreign"
114
+ with pytest.raises(ValueError):
115
+ build_image_submit(session(), ann)
116
+ return
117
+ with pytest.raises(ValueError):
118
+ parse_image_work_session(value)
119
+
120
+
121
+ @pytest.mark.parametrize(
122
+ "key",
123
+ [
124
+ "contract_version",
125
+ "project_id",
126
+ "clip_id",
127
+ "base_source_revision",
128
+ "base_schema_fingerprint",
129
+ "base_annotation_revision",
130
+ "image_annotation",
131
+ ],
132
+ )
133
+ def test_request_rejects_missing_fields_and_no_coercion(key):
134
+ value = build_image_submit(session(), candidate()).to_dict()
135
+ del value[key]
136
+ with pytest.raises(ValueError):
137
+ parse_image_submit(value)
138
+
139
+
140
+ def test_typed_open_submit_and_exact_retry_request():
141
+ requests = []
142
+
143
+ def handler(req):
144
+ import json
145
+
146
+ requests.append(json.loads(req.content))
147
+ return httpx.Response(
148
+ 200,
149
+ json=session().to_dict() if req.url.path.endswith("open") else receipt(),
150
+ )
151
+
152
+ with client(handler) as c:
153
+ ws = c.image_work_sessions.open(project_id=7, clip_id="clip_test")
154
+ accepted = c.image_work_sessions.submit(
155
+ manifest=ws, image_annotation=candidate()
156
+ )
157
+ assert accepted.to_dict() == receipt()
158
+ c.image_work_sessions.submit(manifest=ws, image_annotation=candidate())
159
+ assert (
160
+ requests[1]
161
+ == requests[2]
162
+ == build_image_submit(session(), candidate()).to_dict()
163
+ )
164
+
165
+
166
+ @pytest.mark.parametrize(
167
+ "key",
168
+ [
169
+ "project_id",
170
+ "clip_id",
171
+ "source_revision",
172
+ "schema_fingerprint",
173
+ "annotation_revision",
174
+ "clip_revision",
175
+ "changed",
176
+ "projection_state",
177
+ ],
178
+ )
179
+ def test_typed_client_rejects_unrelated_or_invalid_receipt(key):
180
+ value = receipt()
181
+ value[key] = {
182
+ "project_id": 8,
183
+ "clip_id": "foreign",
184
+ "changed": 1,
185
+ "projection_state": "done",
186
+ }.get(key, "sha256:" + "0" * 64)
187
+ with client(lambda req: httpx.Response(200, json=value)) as c:
188
+ with pytest.raises(ValueError):
189
+ c.image_work_sessions.submit(
190
+ manifest=session(), image_annotation=candidate()
191
+ )
192
+
193
+
194
+ def test_conflict_is_structured_and_no_unvalidated_candidate_is_sent():
195
+ conflict = {"annotation_revision": {"base": "old", "current": "new"}}
196
+ with client(
197
+ lambda req: httpx.Response(
198
+ 409,
199
+ json={
200
+ "detail": {
201
+ "code": "work_session_conflict",
202
+ "extra": {"conflicts": conflict},
203
+ }
204
+ },
205
+ )
206
+ ) as c:
207
+ with pytest.raises(WorkSessionConflictError) as exc:
208
+ c.image_work_sessions.submit(
209
+ manifest=session(), image_annotation=candidate()
210
+ )
211
+ assert exc.value.conflicts == conflict
212
+ with client(lambda req: pytest.fail("invalid candidate reached transport")) as c:
213
+ ann = copy.deepcopy(candidate())
214
+ ann["completion"] = []
215
+ with pytest.raises(ValueError):
216
+ c.image_work_sessions.submit(manifest=session(), image_annotation=ann)
217
+
218
+
219
+ def test_receipt_and_session_reject_invalid_identity_types():
220
+ value = receipt()
221
+ value["project_id"] = True
222
+ with pytest.raises(ValueError):
223
+ parse_image_submit_receipt(value)
224
+ value = session().to_dict()
225
+ value["clip"]["sample_data"][0]["width"] = 10
226
+ value["clip"]["identity"] = build_image_identity(value["clip"])
227
+ with pytest.raises(ValueError):
228
+ parse_image_work_session(value)
@@ -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.0rc1"
14
+ assert x4d_devkit.__version__ == "0.48.0rc2"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
@@ -9,6 +9,7 @@ from x4d_devkit.client.clip_work_sessions import (
9
9
  WorkSessionConflictError,
10
10
  )
11
11
  from x4d_devkit.client.client import X4DClient
12
+ from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
12
13
  from x4d_devkit.client.clips import ClipsAPI
13
14
  from x4d_devkit.client.dataset_identity import DatasetIdentityAPI
14
15
  from x4d_devkit.client.label_schemas import (
@@ -41,6 +42,7 @@ from x4d_devkit.client.services import ServicesAPI
41
42
  from x4d_devkit.client.test_sets import TestSetsAPI
42
43
 
43
44
  __all__ = [
45
+ "ImageWorkSessionsAPI",
44
46
  "CleanBasemapState",
45
47
  "AssetsAPI",
46
48
  "BagsAPI",
@@ -24,6 +24,7 @@ from x4d_devkit.client.clips import ClipsAPI
24
24
  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
+ from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
27
28
  from x4d_devkit.client.semantic_scene import SemanticSceneAPI
28
29
  from x4d_devkit.client.semantic_scene_authoring import (
29
30
  SemanticSceneAuthoringWorkSessionsAPI,
@@ -72,6 +73,7 @@ class X4DClient:
72
73
  self.reconstruction = ReconstructionAPI(self)
73
74
  self.clips = ClipsAPI(self)
74
75
  self.image_clips = ImageClipsAPI(self)
76
+ self.image_work_sessions = ImageWorkSessionsAPI(self)
75
77
  self.label_schemas = LabelSchemasAPI(self)
76
78
  self.semantic_scene = SemanticSceneAPI(self)
77
79
  self.semantic_scene_authoring_work_sessions = (
@@ -0,0 +1,80 @@
1
+ """Typed whole-Clip image authoring with verified acceptance receipts."""
2
+
3
+ import httpx
4
+
5
+ from x4d_devkit._image_contract import require
6
+ from x4d_devkit.client.clip_work_sessions import WorkSessionConflictError, _error_detail
7
+ from x4d_devkit.image_work_session import (
8
+ ImageWorkSession,
9
+ build_image_submit,
10
+ image_candidate,
11
+ parse_image_submit_receipt,
12
+ parse_image_work_session,
13
+ )
14
+
15
+
16
+ class ImageWorkSessionsAPI:
17
+ def __init__(self, client):
18
+ self._client = client
19
+
20
+ def open(self, *, project_id: int, clip_id: str) -> ImageWorkSession:
21
+ from x4d_devkit._image_contract import identifier, integer
22
+
23
+ integer(project_id, "project_id", minimum=1)
24
+ identifier(clip_id, "clip_id")
25
+ value = parse_image_work_session(
26
+ self._client.post(
27
+ "/image-work-sessions/open",
28
+ json={"project_id": project_id, "clip_id": clip_id},
29
+ )
30
+ )
31
+ context = value.base_identity()
32
+ require(
33
+ context["project_id"] == project_id and context["clip_id"] == clip_id,
34
+ "image session belongs to another project/Clip",
35
+ "image_work_session.clip.meta",
36
+ )
37
+ return value
38
+
39
+ def submit(self, *, manifest: ImageWorkSession | dict, image_annotation: dict):
40
+ session = (
41
+ manifest
42
+ if type(manifest) is ImageWorkSession
43
+ else parse_image_work_session(manifest)
44
+ )
45
+ request = build_image_submit(session, image_annotation).to_dict()
46
+ candidate = image_candidate(session.clip, image_annotation).to_dict()
47
+ try:
48
+ response = self._client.post("/image-work-sessions/submit", json=request)
49
+ except httpx.HTTPStatusError as exc:
50
+ detail = _error_detail(exc)
51
+ if (
52
+ isinstance(detail, dict)
53
+ and detail.get("code") == "work_session_conflict"
54
+ ):
55
+ raise WorkSessionConflictError(
56
+ detail.get("message") or "Image work-session baseline is stale",
57
+ conflicts=(detail.get("extra") or {}).get("conflicts", {}),
58
+ status_code=exc.response.status_code,
59
+ ) from exc
60
+ raise
61
+ receipt = parse_image_submit_receipt(response)
62
+ result = receipt.to_dict()
63
+ for key in ("project_id", "clip_id"):
64
+ require(
65
+ result[key] == request[key],
66
+ "receipt belongs to another context",
67
+ "receipt." + key,
68
+ )
69
+ for key in (
70
+ "source_revision",
71
+ "schema_fingerprint",
72
+ "annotation_revision",
73
+ "clip_revision",
74
+ ):
75
+ require(
76
+ result[key] == candidate["identity"][key],
77
+ "receipt does not accept the submitted candidate",
78
+ "receipt." + key,
79
+ )
80
+ return receipt
@@ -0,0 +1,161 @@
1
+ """Native image authoring grammar, independent of platform storage and 3D."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from x4d_devkit._image_contract import (
8
+ ImageDocument,
9
+ canonical,
10
+ document,
11
+ fields,
12
+ identifier,
13
+ integer,
14
+ require,
15
+ )
16
+ from x4d_devkit.image_clip import (
17
+ ImageClip,
18
+ _digest,
19
+ build_image_identity,
20
+ parse_image_clip,
21
+ )
22
+
23
+
24
+ IMAGE_WORK_SESSION_VERSION = "x4d-image-work-session-v1"
25
+ IMAGE_SUBMIT_VERSION = "x4d-image-submit-v1"
26
+ IMAGE_SUBMIT_RECEIPT_VERSION = "x4d-image-submit-receipt-v1"
27
+ _BASE_KEYS = {
28
+ "base_source_revision",
29
+ "base_schema_fingerprint",
30
+ "base_annotation_revision",
31
+ }
32
+
33
+
34
+ class ImageWorkSession(ImageDocument):
35
+ @property
36
+ def clip(self) -> ImageClip:
37
+ return parse_image_clip(self.to_dict()["clip"])
38
+
39
+ def base_identity(self) -> dict:
40
+ clip = self.to_dict()["clip"]
41
+ identity = clip["identity"]
42
+ return {
43
+ "project_id": clip["meta"]["project_id"],
44
+ "clip_id": clip["meta"]["clip_id"],
45
+ "base_source_revision": identity["source_revision"],
46
+ "base_schema_fingerprint": identity["schema_fingerprint"],
47
+ "base_annotation_revision": identity["annotation_revision"],
48
+ }
49
+
50
+
51
+ def parse_image_work_session(value: Any) -> ImageWorkSession:
52
+ canonical(value)
53
+ fields(value, {"contract_version", "clip"}, "image_work_session")
54
+ require(
55
+ value["contract_version"] == IMAGE_WORK_SESSION_VERSION,
56
+ "unsupported image work-session version",
57
+ "image_work_session.contract_version",
58
+ )
59
+ parse_image_clip(value["clip"])
60
+ return document(value, ImageWorkSession)
61
+
62
+
63
+ def image_candidate(clip: ImageClip, image_annotation: Any) -> ImageClip:
64
+ """Validate one complete replacement in its exact immutable source context."""
65
+ require(type(clip) is ImageClip, "expected a validated image Clip", "clip")
66
+ value = clip.to_dict()
67
+ value["image_annotation"] = image_annotation
68
+ value["identity"] = build_image_identity(value)
69
+ return parse_image_clip(value)
70
+
71
+
72
+ def parse_image_submit(value: Any) -> ImageDocument:
73
+ canonical(value)
74
+ fields(
75
+ value,
76
+ {"contract_version", "project_id", "clip_id", "image_annotation", *_BASE_KEYS},
77
+ "image_submit",
78
+ )
79
+ require(
80
+ value["contract_version"] == IMAGE_SUBMIT_VERSION,
81
+ "unsupported image submit version",
82
+ "image_submit.contract_version",
83
+ )
84
+ integer(value["project_id"], "image_submit.project_id", minimum=1)
85
+ identifier(value["clip_id"], "image_submit.clip_id")
86
+ for key in _BASE_KEYS:
87
+ _digest(value[key], "image_submit." + key)
88
+ fields(
89
+ value["image_annotation"],
90
+ {"contract_version", "clip_id", "objects", "descriptions", "completion"},
91
+ "image_submit.image_annotation",
92
+ )
93
+ require(
94
+ value["image_annotation"]["clip_id"] == value["clip_id"],
95
+ "candidate belongs to another Clip",
96
+ "image_submit.image_annotation.clip_id",
97
+ )
98
+ return document(value)
99
+
100
+
101
+ def build_image_submit(
102
+ manifest: ImageWorkSession | dict, image_annotation: Any
103
+ ) -> ImageDocument:
104
+ session = (
105
+ manifest
106
+ if type(manifest) is ImageWorkSession
107
+ else parse_image_work_session(manifest)
108
+ )
109
+ candidate = image_candidate(session.clip, image_annotation)
110
+ return parse_image_submit(
111
+ {
112
+ "contract_version": IMAGE_SUBMIT_VERSION,
113
+ **session.base_identity(),
114
+ "image_annotation": candidate.to_dict()["image_annotation"],
115
+ }
116
+ )
117
+
118
+
119
+ def parse_image_submit_receipt(value: Any) -> ImageDocument:
120
+ canonical(value)
121
+ fields(
122
+ value,
123
+ {
124
+ "contract_version",
125
+ "project_id",
126
+ "clip_id",
127
+ "source_revision",
128
+ "schema_fingerprint",
129
+ "annotation_revision",
130
+ "clip_revision",
131
+ "changed",
132
+ "projection_state",
133
+ },
134
+ "image_submit_receipt",
135
+ )
136
+ require(
137
+ value["contract_version"] == IMAGE_SUBMIT_RECEIPT_VERSION,
138
+ "unsupported image receipt version",
139
+ "image_submit_receipt.contract_version",
140
+ )
141
+ integer(value["project_id"], "image_submit_receipt.project_id", minimum=1)
142
+ identifier(value["clip_id"], "image_submit_receipt.clip_id")
143
+ for key in (
144
+ "source_revision",
145
+ "schema_fingerprint",
146
+ "annotation_revision",
147
+ "clip_revision",
148
+ ):
149
+ _digest(value[key], "image_submit_receipt." + key)
150
+ require(
151
+ type(value["changed"]) is bool,
152
+ "changed must be boolean",
153
+ "image_submit_receipt.changed",
154
+ )
155
+ require(
156
+ type(value["projection_state"]) is str
157
+ and value["projection_state"] in {"ready", "pending"},
158
+ "invalid projection state",
159
+ "image_submit_receipt.projection_state",
160
+ )
161
+ return document(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.48.0rc1
3
+ Version: 0.48.0rc2
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
@@ -26,6 +26,7 @@ tests/test_export_bundle.py
26
26
  tests/test_image_clip.py
27
27
  tests/test_image_geometry.py
28
28
  tests/test_image_review.py
29
+ tests/test_image_work_session.py
29
30
  tests/test_inference_input.py
30
31
  tests/test_inference_schemas.py
31
32
  tests/test_inference_service.py
@@ -77,6 +78,7 @@ x4d_devkit/image_clip.py
77
78
  x4d_devkit/image_geometry.py
78
79
  x4d_devkit/image_review.py
79
80
  x4d_devkit/image_schema.py
81
+ x4d_devkit/image_work_session.py
80
82
  x4d_devkit/json_contract.py
81
83
  x4d_devkit/label_schema.py
82
84
  x4d_devkit/lidar_materialization.py
@@ -109,6 +111,7 @@ x4d_devkit/client/clips.py
109
111
  x4d_devkit/client/dataset_identity.py
110
112
  x4d_devkit/client/image_clips.py
111
113
  x4d_devkit/client/image_review.py
114
+ x4d_devkit/client/image_work_sessions.py
112
115
  x4d_devkit/client/label_schemas.py
113
116
  x4d_devkit/client/preannotation.py
114
117
  x4d_devkit/client/projects.py
File without changes
File without changes
File without changes