x4d-devkit 0.14.3__tar.gz → 0.17.0__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 (116) hide show
  1. x4d_devkit-0.17.0/PKG-INFO +535 -0
  2. x4d_devkit-0.17.0/README.md +500 -0
  3. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/pyproject.toml +10 -5
  4. x4d_devkit-0.17.0/tests/test_boundary.py +101 -0
  5. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_box.py +82 -7
  6. x4d_devkit-0.17.0/tests/test_build_info.py +119 -0
  7. x4d_devkit-0.17.0/tests/test_cli_auth.py +104 -0
  8. x4d_devkit-0.17.0/tests/test_client_projects.py +108 -0
  9. x4d_devkit-0.17.0/tests/test_clip_archives.py +45 -0
  10. x4d_devkit-0.17.0/tests/test_contract_validation.py +413 -0
  11. x4d_devkit-0.17.0/tests/test_coordinate_frames.py +81 -0
  12. x4d_devkit-0.17.0/tests/test_current_state.py +111 -0
  13. x4d_devkit-0.17.0/tests/test_current_state_identity_consumers_example.py +62 -0
  14. x4d_devkit-0.17.0/tests/test_dataset_identity.py +723 -0
  15. x4d_devkit-0.17.0/tests/test_detection_eval.py +23 -0
  16. x4d_devkit-0.17.0/tests/test_evaluate.py +477 -0
  17. x4d_devkit-0.17.0/tests/test_inference_builders.py +72 -0
  18. x4d_devkit-0.17.0/tests/test_inference_schemas.py +83 -0
  19. x4d_devkit-0.17.0/tests/test_inference_service.py +159 -0
  20. x4d_devkit-0.17.0/tests/test_label_schemas.py +453 -0
  21. x4d_devkit-0.17.0/tests/test_loader.py +115 -0
  22. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_matching.py +1 -1
  23. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_metrics.py +5 -5
  24. x4d_devkit-0.17.0/tests/test_models.py +167 -0
  25. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_nuscenes_converter.py +168 -15
  26. x4d_devkit-0.17.0/tests/test_sdk_api.py +47 -0
  27. x4d_devkit-0.17.0/tests/test_training_identity.py +207 -0
  28. x4d_devkit-0.17.0/tests/test_training_manifest.py +455 -0
  29. x4d_devkit-0.17.0/tests/test_transform_chain.py +51 -0
  30. x4d_devkit-0.17.0/tests/test_validation.py +92 -0
  31. x4d_devkit-0.17.0/tests/test_work_session.py +409 -0
  32. x4d_devkit-0.17.0/x4d_devkit/__init__.py +69 -0
  33. x4d_devkit-0.17.0/x4d_devkit/build_info.py +344 -0
  34. x4d_devkit-0.17.0/x4d_devkit/cli.py +1138 -0
  35. x4d_devkit-0.17.0/x4d_devkit/client/__init__.py +32 -0
  36. x4d_devkit-0.17.0/x4d_devkit/client/bags.py +28 -0
  37. x4d_devkit-0.17.0/x4d_devkit/client/capabilities.py +19 -0
  38. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/client/checkpoints.py +0 -3
  39. x4d_devkit-0.17.0/x4d_devkit/client/client.py +160 -0
  40. x4d_devkit-0.17.0/x4d_devkit/client/clip_work_sessions.py +181 -0
  41. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/client/clips.py +93 -11
  42. x4d_devkit-0.17.0/x4d_devkit/client/dataset_identity.py +51 -0
  43. x4d_devkit-0.17.0/x4d_devkit/client/label_schemas.py +70 -0
  44. x4d_devkit-0.17.0/x4d_devkit/client/projects.py +113 -0
  45. x4d_devkit-0.17.0/x4d_devkit/client/services.py +29 -0
  46. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/client/test_sets.py +1 -30
  47. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/converters/nuscenes.py +236 -90
  48. x4d_devkit-0.17.0/x4d_devkit/coordinate_contract.py +46 -0
  49. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/core/__init__.py +6 -1
  50. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/core/loader.py +252 -81
  51. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/core/models.py +173 -50
  52. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/core/transform.py +37 -90
  53. x4d_devkit-0.17.0/x4d_devkit/current_state.py +261 -0
  54. x4d_devkit-0.17.0/x4d_devkit/dataset_contract.py +408 -0
  55. x4d_devkit-0.17.0/x4d_devkit/dataset_identity.py +763 -0
  56. x4d_devkit-0.17.0/x4d_devkit/eval/__init__.py +42 -0
  57. x4d_devkit-0.17.0/x4d_devkit/eval/api.py +400 -0
  58. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/eval/box.py +95 -8
  59. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/eval/detection.py +37 -12
  60. x4d_devkit-0.17.0/x4d_devkit/eval/matching.py +542 -0
  61. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/eval/metrics.py +6 -4
  62. x4d_devkit-0.17.0/x4d_devkit/inference/__init__.py +54 -0
  63. x4d_devkit-0.17.0/x4d_devkit/inference/builders.py +97 -0
  64. x4d_devkit-0.17.0/x4d_devkit/inference/models.py +33 -0
  65. x4d_devkit-0.17.0/x4d_devkit/inference/schemas.py +192 -0
  66. x4d_devkit-0.17.0/x4d_devkit/inference/service.py +201 -0
  67. x4d_devkit-0.17.0/x4d_devkit/inference/tasks.py +47 -0
  68. x4d_devkit-0.17.0/x4d_devkit/label_schema.py +2093 -0
  69. x4d_devkit-0.17.0/x4d_devkit/manifest.py +960 -0
  70. x4d_devkit-0.17.0/x4d_devkit/materialize.py +375 -0
  71. x4d_devkit-0.17.0/x4d_devkit/training_identity.py +398 -0
  72. x4d_devkit-0.17.0/x4d_devkit/validation/__init__.py +20 -0
  73. x4d_devkit-0.17.0/x4d_devkit/validation/contract.py +1544 -0
  74. x4d_devkit-0.17.0/x4d_devkit/validation/validator.py +817 -0
  75. x4d_devkit-0.17.0/x4d_devkit/work_session.py +568 -0
  76. x4d_devkit-0.17.0/x4d_devkit.egg-info/PKG-INFO +535 -0
  77. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit.egg-info/SOURCES.txt +38 -5
  78. x4d_devkit-0.17.0/x4d_devkit.egg-info/entry_points.txt +2 -0
  79. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit.egg-info/requires.txt +6 -0
  80. x4d_devkit-0.14.3/PKG-INFO +0 -172
  81. x4d_devkit-0.14.3/README.md +0 -142
  82. x4d_devkit-0.14.3/tests/test_cli_auth.py +0 -62
  83. x4d_devkit-0.14.3/tests/test_client_projects.py +0 -42
  84. x4d_devkit-0.14.3/tests/test_coordinate_frames.py +0 -501
  85. x4d_devkit-0.14.3/tests/test_detection_eval.py +0 -155
  86. x4d_devkit-0.14.3/tests/test_evaluate.py +0 -154
  87. x4d_devkit-0.14.3/tests/test_fusion.py +0 -134
  88. x4d_devkit-0.14.3/tests/test_loader.py +0 -254
  89. x4d_devkit-0.14.3/tests/test_models.py +0 -232
  90. x4d_devkit-0.14.3/tests/test_transform_chain.py +0 -88
  91. x4d_devkit-0.14.3/tests/test_v06_api_additions.py +0 -162
  92. x4d_devkit-0.14.3/tests/test_v06_schema.py +0 -414
  93. x4d_devkit-0.14.3/tests/test_v10_schema.py +0 -238
  94. x4d_devkit-0.14.3/tests/test_validation.py +0 -421
  95. x4d_devkit-0.14.3/x4d_devkit/__init__.py +0 -72
  96. x4d_devkit-0.14.3/x4d_devkit/cli.py +0 -280
  97. x4d_devkit-0.14.3/x4d_devkit/client/__init__.py +0 -13
  98. x4d_devkit-0.14.3/x4d_devkit/client/client.py +0 -88
  99. x4d_devkit-0.14.3/x4d_devkit/client/projects.py +0 -59
  100. x4d_devkit-0.14.3/x4d_devkit/eval/__init__.py +0 -26
  101. x4d_devkit-0.14.3/x4d_devkit/eval/api.py +0 -205
  102. x4d_devkit-0.14.3/x4d_devkit/eval/matching.py +0 -179
  103. x4d_devkit-0.14.3/x4d_devkit/fusion.py +0 -104
  104. x4d_devkit-0.14.3/x4d_devkit/validation/__init__.py +0 -4
  105. x4d_devkit-0.14.3/x4d_devkit/validation/validator.py +0 -502
  106. x4d_devkit-0.14.3/x4d_devkit.egg-info/PKG-INFO +0 -172
  107. x4d_devkit-0.14.3/x4d_devkit.egg-info/entry_points.txt +0 -2
  108. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/LICENSE +0 -0
  109. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/setup.cfg +0 -0
  110. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_token.py +0 -0
  111. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/tests/test_transform.py +0 -0
  112. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/converters/__init__.py +0 -0
  113. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/core/token.py +0 -0
  114. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit/validation/report.py +0 -0
  115. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  116. {x4d_devkit-0.14.3 → x4d_devkit-0.17.0}/x4d_devkit.egg-info/top_level.txt +0 -0
@@ -0,0 +1,535 @@
1
+ Metadata-Version: 2.4
2
+ Name: x4d-devkit
3
+ Version: 0.17.0
4
+ Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
+ Author: windzu
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/windzu/x4d-devkit
8
+ Project-URL: Repository, https://github.com/windzu/x4d-devkit
9
+ Project-URL: Issues, https://github.com/windzu/x4d-devkit/issues
10
+ Keywords: autonomous-driving,dataset,evaluation,annotation,lidar,camera,cli,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=1.24
23
+ Requires-Dist: httpx>=0.27
24
+ Requires-Dist: rfc8785==0.1.4
25
+ Provides-Extra: converters
26
+ Requires-Dist: nuscenes-devkit>=1.1.0; extra == "converters"
27
+ Provides-Extra: inference
28
+ Requires-Dist: pydantic>=2.0; extra == "inference"
29
+ Requires-Dist: fastapi>=0.110; extra == "inference"
30
+ Requires-Dist: uvicorn>=0.27; extra == "inference"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # x4d-devkit
37
+
38
+ Canonical contract library and `x4d-devkit` CLI for consuming X-4D data and
39
+ public platform capabilities.
40
+
41
+ `x4d-devkit` owns X-4D data semantics, identity, coordinate/schema contracts,
42
+ platform IO adapters, and official evaluation primitives. It is the stable
43
+ interpretation layer between X-4D platform state and consumers such as training
44
+ platforms, X-Points, external inference services, evaluation scripts, and
45
+ automation.
46
+
47
+ It is not a downstream training, annotation-tool UI, experiment, deployment, or
48
+ framework-specific cache-policy package. It may call public platform APIs or
49
+ read standard X-4D files, but it must not import backend-private `app.*`
50
+ modules or mutate DB/MinIO internals.
51
+
52
+ Design decisions for this boundary are tracked in GitHub issues, starting with:
53
+
54
+ - [#42](https://github.com/windzu/x4d-devkit/issues/42) - official external SDK and CLI boundary
55
+ - [#43](https://github.com/windzu/x4d-devkit/issues/43) - private source install in X-4D runtime
56
+ - [#44](https://github.com/windzu/x4d-devkit/issues/44) - capability registry and CLI sync
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ pip install x4d-devkit
62
+ ```
63
+
64
+ X-4D platform runtime installs this package from the checked-out private source
65
+ repo/submodule, not from an unconstrained package-index version. External
66
+ environments may install from a pinned package release or directly from GitHub,
67
+ depending on deployment policy.
68
+
69
+ Optional heavy dependencies:
70
+
71
+ ```bash
72
+ # NuScenes format converter
73
+ pip install x4d-devkit[converters]
74
+
75
+ # External inference service SDK
76
+ pip install x4d-devkit[inference]
77
+ ```
78
+
79
+ The platform API client and `x4d-devkit` CLI are base package features.
80
+
81
+ ## Scope
82
+
83
+ See [docs/module-boundary.md](docs/module-boundary.md) for the module ownership
84
+ table and command boundary.
85
+
86
+ Internal consumers should use the current-state identity contract for
87
+ platform-to-platform data flow. See
88
+ [docs/current-state-identity.md](docs/current-state-identity.md) for the
89
+ generic identity model, and
90
+ [docs/training-current-state-data.md](docs/training-current-state-data.md) for
91
+ diff/sync, work-session materialization, training manifests, and conditional
92
+ annotation writeback flows built on that model.
93
+ Shared validation rules for platform submit, X-Points preflight, local clip
94
+ validation, and training consumers are documented in
95
+ [docs/shared-contract-validation.md](docs/shared-contract-validation.md).
96
+ `examples/current_state_identity_consumers.py` provides an offline downstream
97
+ fixture for OpenPCDet-style, mmdetection3d-style, and annotation/review
98
+ consumers.
99
+
100
+ Included:
101
+
102
+ - Local X-4D dataset loading, validation, transforms, annotations, calibration,
103
+ ego pose, manifests, converters, and evaluation helpers.
104
+ - Platform API client and public `x4d-devkit` CLI commands for external
105
+ workflows such as clip listing/download, screening preview, model/checkpoint
106
+ registration, status queries, and capability discovery.
107
+ - Training bridge workflows such as multi-project manifests, archive download,
108
+ split validation, and dataloader-friendly metadata.
109
+ - External inference service wrappers for third-party model-serving projects.
110
+
111
+ Excluded:
112
+
113
+ - Backend migrations, DB/MinIO repair scripts, cache rebuild internals,
114
+ emergency operations, and one-off delivery importers.
115
+ - Any code that imports backend-private `app.*`.
116
+ - Downstream experiment taxonomy, `CLASS_NAMES`, model-class mappings, sampler
117
+ policy, model acceptance thresholds, deployment policy, and framework-specific
118
+ artifacts such as OpenPCDet infos/dbinfos or mmdetection3d pickle views.
119
+
120
+ Promotion rule: an internal operation becomes an `x4d-devkit` command only
121
+ after it is productized as an external SDK/API workflow with explicit
122
+ permissions, side effects, idempotency, and failure modes. Data deletion, clip
123
+ production, and internal service dispatch stay out of devkit.
124
+
125
+ Training-specific convenience utilities must remain framework-neutral. Devkit
126
+ may expose raw category stats, identity fingerprints, coordinate/schema
127
+ validation, cache completeness checks, and official X-4D metrics; downstream
128
+ training repos own taxonomy mapping, training-format generation, sampler
129
+ configuration, and experiment lifecycle.
130
+
131
+ ## Quick Start
132
+
133
+ ### Connect to an X-4D platform
134
+
135
+ The CLI can authenticate with a bearer token passed explicitly, through
136
+ environment variables, or from the user config written by `x4d-devkit login`.
137
+
138
+ ```bash
139
+ x4d-devkit --api-url http://host:8000 --token <token> projects list
140
+ x4d-devkit --api-url http://host:8000 --token <token> clips list --project-id 1
141
+ x4d-devkit --api-url http://host:8000 --token <token> clips download --clip-ids clip_a,clip_b --output /data/x4d/clips
142
+ ```
143
+
144
+ ```bash
145
+ export X4D_API_URL=http://host:8000
146
+ export X4D_TOKEN=<token>
147
+ x4d-devkit projects list
148
+ x4d-devkit clips list --project-name nuscenes-mini --has-archive true --format json
149
+ x4d-devkit clips download --project-name nuscenes-mini --output /data/x4d/nuscenes-mini/clips --workers 8
150
+ x4d-devkit clips download --project-name nuscenes-mini --archive-profile keyframes_only --output /data/x4d/nuscenes-mini/keyframes --workers 8
151
+ ```
152
+
153
+ ```bash
154
+ x4d-devkit --api-url http://host:8000 login --username <user> --password <password>
155
+ ```
156
+
157
+ `login` stores `api_url` and the returned access token as `token` in
158
+ `~/.config/x4d/config.toml`. Archive downloads go through the platform file
159
+ proxy and extract standard X4D clip directories; training containers do not
160
+ need database or MinIO access.
161
+
162
+ Clip archive downloads are profile-aware. `--archive-profile keyframes_only` is
163
+ the default and requests the smaller archive with sweeps removed and metadata
164
+ filtered accordingly. Use `--archive-profile full` when a consumer needs the
165
+ complete clip payload including `sweeps/`. If the requested profile has not been
166
+ built, is stale, or failed to build, the devkit reports that profile-specific
167
+ reason instead of falling back to another tar.
168
+
169
+ ### Create a multi-project training manifest
170
+
171
+ Training projects can combine multiple X-4D projects by downloading standard
172
+ clip archives and generating one local manifest. The downstream training
173
+ framework consumes only the manifest and local clip files; it does not need
174
+ database, MinIO, or platform-private file paths.
175
+
176
+ ```bash
177
+ x4d-devkit manifest create \
178
+ --projects hg1.0 hg1.5 \
179
+ --data-root /data/x4d/cache/hg_mix \
180
+ --output /data/x4d/cache/hg_mix/manifest.json \
181
+ --download-missing \
182
+ --archive-profile keyframes_only \
183
+ --split-policy by_clip \
184
+ --val-ratio 0.2 \
185
+ --seed 42
186
+
187
+ x4d-devkit manifest validate \
188
+ --manifest /data/x4d/cache/hg_mix/manifest.json \
189
+ --data-root /data/x4d/cache/hg_mix
190
+ ```
191
+
192
+ Internal current-state consumers can snapshot remote clip identity before
193
+ reviewing diffs or syncing local caches:
194
+
195
+ ```bash
196
+ x4d-devkit dataset inspect \
197
+ --project-name hg1.5 \
198
+ --include-asset-revision \
199
+ --out remote_identity.json
200
+ ```
201
+
202
+ Training runs can validate their consumed-data identity record:
203
+
204
+ ```bash
205
+ x4d-devkit manifest identity-validate --manifest training_identity.json
206
+ ```
207
+
208
+ Dataset update planning can also compare consumer-owned training identity
209
+ fields, such as split membership, class-mapping fingerprint, data policy, or a
210
+ generic consumer identity fingerprint:
211
+
212
+ ```bash
213
+ x4d-devkit dataset diff \
214
+ --local-manifest current_training_identity.json \
215
+ --previous-training-identity previous_training_identity.json \
216
+ --out update_plan.json
217
+ ```
218
+
219
+ `manifest validate` prints bounded warning output by default. It reports
220
+ warning totals grouped by type, category, and project/category, then shows a
221
+ small set of examples. Use `--max-warnings`, `--all-warnings`, or
222
+ `--warnings-output warnings.jsonl` when full warning details are needed.
223
+
224
+ Downloaded clips are stored under
225
+ `projects/<project-name>/clips/<clip-id>/`. Manifest file paths are relative to
226
+ `--data-root`, so the dataset can be moved as a directory.
227
+
228
+ The platform is expected to assign globally unique `clip_id` values. The devkit
229
+ validator checks duplicate clip identities and split leakage, but it does not
230
+ perform expensive semantic near-duplicate detection across projects.
231
+
232
+ ```python
233
+ from x4d_devkit import X4DClient
234
+
235
+ client = X4DClient.from_config()
236
+ manifest = client.create_training_manifest(
237
+ projects=["hg1.0", "hg1.5"],
238
+ data_root="/data/x4d/cache/hg_mix",
239
+ output="/data/x4d/cache/hg_mix/manifest.json",
240
+ download_missing=True,
241
+ split_policy="by_clip",
242
+ val_ratio=0.2,
243
+ seed=42,
244
+ )
245
+ ```
246
+
247
+ The detection manifest keeps boxes in their native X-4D annotation frame
248
+ (`meta.sensors[meta.annotation_source_channel].frame_id`). It does not align
249
+ poses across clips; each clip's `clip_world` remains local to that clip.
250
+
251
+ ### Consume the project label schema
252
+
253
+ X-4D is the source of truth for label schemas. External training and inference
254
+ projects should read the effective project schema from the platform instead of
255
+ maintaining their own category list.
256
+
257
+ ```bash
258
+ x4d-devkit label-schemas project-get --project-id 1 --format json
259
+ x4d-devkit label-schemas get \
260
+ --schema-id object_3d_low_speed_roadside \
261
+ --version 1.0.0 \
262
+ --format json
263
+ ```
264
+
265
+ ```python
266
+ from x4d_devkit import X4DClient
267
+ from x4d_devkit.label_schema import compare_project_label_schema_snapshot
268
+
269
+ client = X4DClient.from_config()
270
+ project = client.projects.resolve(project_name="nuscenes-mini")
271
+ project_schema = client.label_schemas.resolve_project(project_name="nuscenes-mini")
272
+ label_schema = project_schema.label_schema
273
+
274
+ print(project["id"], project["name"])
275
+ print(project_schema.binding.to_dict())
276
+ print(sorted(label_schema.class_ids))
277
+
278
+ label_schema.validate_category("tanker_truck")
279
+
280
+ stored_snapshot = project_schema.to_dict()
281
+ current = client.label_schemas.resolve_project(project_id=project["id"])
282
+ comparison = compare_project_label_schema_snapshot(stored_snapshot, current)
283
+ assert comparison.status == "same"
284
+ ```
285
+
286
+ The registry schema is the fine-grained annotation contract. Training code may
287
+ still define experiment-specific class mappings, but those mappings should be
288
+ validated against the project schema before a job starts.
289
+
290
+ Training manifests keep the immutable project contract separate from the
291
+ observed/configured class catalog:
292
+
293
+ ```text
294
+ label_schema.projects.<project>.contract # binding + full registry record
295
+ label_schema.projects.<project>.class_catalog # training/observed class catalog
296
+ ```
297
+
298
+ Consumers must parse `contract` with
299
+ `parse_project_label_schema_snapshot()`. `class_catalog` is
300
+ non-authoritative observation metadata; there are no per-project
301
+ `classes`, `source`, or `schema_version` aliases.
302
+
303
+ This strict parser/view contract is introduced in x4d-devkit `0.17.0`.
304
+ Consumers tracking `main` should still record `get_build_info()["source"]` in
305
+ training identity so an untagged development run remains reproducible by exact
306
+ commit. Deployments that copy a source tree without its Git directory must set
307
+ `X4D_DEVKIT_COMMIT` to the copied 40- or 64-hex commit; `get_build_info()` records
308
+ that value with `commit_source=environment`.
309
+
310
+ Project resolution by name/key is exact and handles platform pagination
311
+ internally; ambiguous or missing project references raise structured resolver
312
+ errors instead of requiring callers to scan project lists themselves.
313
+
314
+ ### Load a clip
315
+
316
+ ```python
317
+ from x4d_devkit import ClipLoader
318
+
319
+ loader = ClipLoader("/path/to/clip")
320
+ print(loader.meta)
321
+
322
+ for sample in loader.samples:
323
+ for sd in loader.sample_data_for_sample(sample.token):
324
+ print(sd.channel, sd.file_path)
325
+ ```
326
+
327
+ ### Open a platform work session
328
+
329
+ Internal tools such as X-Points can open the platform's current clip state
330
+ without waiting for a prebuilt archive:
331
+
332
+ ```python
333
+ from x4d_devkit import ClipLoader, X4DClient, materialize_work_session_clip
334
+ from x4d_devkit.client import WorkSessionConflictError
335
+ from x4d_devkit.core.loader import CLIP_WORLD_FRAME_ID
336
+
337
+ client = X4DClient.from_config()
338
+ manifest = client.clip_work_sessions.open(project_id=1812, clip_id="clip-id")
339
+ materialize_work_session_clip(
340
+ manifest,
341
+ output_dir="/data/x4d_cache/clips/clip-id",
342
+ client=client,
343
+ asset_policy="keyframes_only",
344
+ )
345
+
346
+ loader = ClipLoader.from_work_session(manifest, api_url=client.api_url)
347
+ anns_world = loader.annotations_for_sample("sample-token", frame=CLIP_WORLD_FRAME_ID)
348
+ lidar_asset = loader.point_cloud_asset("sample-token", manifest.annotation_source_channel)
349
+
350
+ try:
351
+ response = client.clip_work_sessions.submit_annotations(
352
+ manifest=manifest,
353
+ annotations=manifest.annotations,
354
+ instances=manifest.instances,
355
+ )
356
+ print(response["annotation_revision"])
357
+ except WorkSessionConflictError as exc:
358
+ print(exc.current_identities)
359
+ raise
360
+ ```
361
+
362
+ Materialization preserves both schema facts atomically: the clip's recorded
363
+ identity remains in `meta.label_schema`, while the effective full registry
364
+ record is written to `label_schema.json`. Full and annotation-only refreshes
365
+ write the same schema snapshot, so local validation does not require a live
366
+ registry lookup.
367
+
368
+ A ready work session is accepted only when `label_schema_fingerprint`, the full
369
+ registry record, `clip_label_schema`, and `meta.label_schema` declare the same
370
+ identity. A not-ready session may expose a stale clip identity alongside the
371
+ current project schema so migration tooling can report the mismatch, but it
372
+ cannot be materialized.
373
+
374
+ Submit is conditional on the manifest's `source_revision`,
375
+ `annotation_revision`, and `label_schema_fingerprint`. If the platform state
376
+ changed after the session was opened, the client raises
377
+ `WorkSessionConflictError` with machine-readable `conflicts`,
378
+ `base_identities`, and `current_identities`.
379
+
380
+ ### Coordinate frame transforms
381
+
382
+ Frames are real frame_id strings: any node in the calibration tree (e.g.
383
+ `"LIDAR_TOP"`, `"base_link"`, `"cam_front"`) plus the constant
384
+ `"clip_world"` (the SLAM-anchored clip-local world). The legacy aliases
385
+ `"sensor"`, `"ego"`, `"world"` are not accepted.
386
+
387
+ Training converters should read the clip's self-described frame contract rather
388
+ than assume fixed channel names. The native annotation frame is derived from
389
+ `meta.sensors[meta.annotation_source_channel].frame_id`; `clip_world` is local
390
+ to one clip and is not a global frame across clips.
391
+
392
+ The installed package exposes the training coordinate contract for logs and
393
+ debugging:
394
+
395
+ ```bash
396
+ x4d-devkit dataset coordinate-contract
397
+ x4d-devkit dataset coordinate-contract --format json
398
+ ```
399
+
400
+ ```python
401
+ from x4d_devkit import COORDINATE_CONTRACT_VERSION, get_training_coordinate_contract
402
+ ```
403
+
404
+ ```python
405
+ from x4d_devkit import ClipLoader
406
+ from x4d_devkit.core.loader import CLIP_WORLD_FRAME_ID
407
+
408
+ loader = ClipLoader("/path/to/clip")
409
+ sd = loader.sample_data_for_channel("LIDAR_TOP")[0]
410
+
411
+ # Load point cloud in different frames
412
+ pts_sensor = loader.load_point_cloud(sd) # raw sensor (default)
413
+ pts_ego = loader.load_point_cloud(sd, frame=loader.ego_pose_frame_id) # sensor → ego
414
+ pts_world = loader.load_point_cloud(sd, frame=CLIP_WORLD_FRAME_ID) # sensor → clip_world
415
+
416
+ # Get annotations transformed to clip-local world
417
+ anns_world = loader.annotations_for_sample(sample.token, frame=CLIP_WORLD_FRAME_ID)
418
+
419
+ # Or to a specific sensor's frame
420
+ anns_lidar = loader.annotations_for_sample(sample.token, frame="LIDAR_TOP")
421
+
422
+ # Get the transform matrix directly (sd is required when clip_world is involved)
423
+ T = loader.get_transform(loader.sensor_frame_id(sd), CLIP_WORLD_FRAME_ID, sd=sd)
424
+ pts_world = T.apply(pts_sensor[:, :3]) # or use T.as_matrix for 4x4
425
+ ```
426
+
427
+ ### Validate a clip
428
+
429
+ ```bash
430
+ x4d-devkit validate /path/to/clip
431
+ ```
432
+
433
+ ```python
434
+ from x4d_devkit import validate_clip
435
+
436
+ report = validate_clip("/path/to/clip")
437
+ print(report)
438
+ ```
439
+
440
+ ### Serve an external inference backend
441
+
442
+ Third-party model projects should use `x4d-devkit[inference]` instead of
443
+ hand-writing the X-4D inference HTTP protocol.
444
+
445
+ ```python
446
+ from x4d_devkit.inference import DetectionModel, InferenceService, detection_3d_item
447
+
448
+
449
+ class MyDetector(DetectionModel):
450
+ model_id = "centerpoint-v1"
451
+ display_name = "CenterPoint v1"
452
+ raw_classes = ["car", "truck", "pedestrian"]
453
+
454
+ def predict_clip(self, clip, config):
455
+ return [
456
+ detection_3d_item(
457
+ sample_token=clip.samples[0].token,
458
+ raw_class="car",
459
+ score=0.91,
460
+ translation=(1.0, 2.0, 0.5),
461
+ size=(4.5, 1.8, 1.6),
462
+ yaw=0.2,
463
+ )
464
+ ]
465
+
466
+
467
+ InferenceService(service_name="my-detector", models=[MyDetector()]).run(port=9000)
468
+ ```
469
+
470
+ See [docs/external-inference.md](docs/external-inference.md) and
471
+ `examples/external_detection_service.py` for the standard integration flow.
472
+
473
+ ### Detection evaluation
474
+
475
+ ```python
476
+ from x4d_devkit.eval import DetectionEval, DetectionConfig
477
+
478
+ config = DetectionConfig(
479
+ class_names=["car", "pedestrian", "bicycle"],
480
+ dist_thresholds=[0.5, 1.0, 2.0, 4.0],
481
+ )
482
+ evaluator = DetectionEval(config, gt_clips=[...], pred_clips=[...])
483
+ result = evaluator.evaluate()
484
+ print(f"mAP: {result.mAP:.3f}, NDS: {result.NDS:.3f}")
485
+ ```
486
+
487
+ For detectors that intentionally do not predict velocity, construct 7D boxes
488
+ and evaluate with `with_velocity=False` so velocity error is excluded from the
489
+ primary detection score:
490
+
491
+ ```python
492
+ from x4d_devkit.eval import Box, DetectionConfig, evaluate
493
+
494
+ pred = {
495
+ "sample_1": [
496
+ Box.from_xyzlwhyaw([0, 0, 0, 4, 2, 1.5, 0.0], category="car", score=0.9)
497
+ ]
498
+ }
499
+ config = DetectionConfig(
500
+ class_names=["car"],
501
+ dist_thresholds=[0.5, 1.0, 2.0, 4.0],
502
+ dist_th_tp=2.0,
503
+ min_recall=0.1,
504
+ min_precision=0.1,
505
+ max_boxes_per_sample=500,
506
+ class_range={"car": 50.0},
507
+ with_velocity=False,
508
+ )
509
+ result = evaluate(gt, pred, config)
510
+ assert result.with_velocity is False
511
+ ```
512
+
513
+ ### Convert from NuScenes
514
+
515
+ ```python
516
+ from x4d_devkit.converters import NuScenesConverter
517
+
518
+ converter = NuScenesConverter("/path/to/nuscenes")
519
+ converter.convert_scene("scene-0001", output_dir="/path/to/output")
520
+ ```
521
+
522
+ ## Modules
523
+
524
+ | Module | Description |
525
+ |--------|-------------|
526
+ | `core` | Data models, token generation, coordinate transforms, clip loader |
527
+ | `eval` | Detection evaluation (mAP, TP metrics, NDS) |
528
+ | `converters` | Format converters (NuScenes → X4D) |
529
+ | `manifest` | Training manifest creation, validation, and clip archive download |
530
+ | `validation` | Clip structure and data validation |
531
+ | `client` | X-4D platform API client |
532
+
533
+ ## License
534
+
535
+ Apache License 2.0