x4d-devkit 0.51.0rc2__tar.gz → 0.51.0rc4__tar.gz

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