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