feat(perception): integrate calibrated operator pipeline

Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 00:23:28 +03:00
parent ada2a55ee6
commit b53d6d5a45
221 changed files with 55923 additions and 1357 deletions
@@ -0,0 +1,111 @@
# External perception stack review — 2026-07-20
## Objective
Select mature, reusable components for Mission Core's K1 camera/LiDAR pipeline
instead of growing the LAB implementation into a bespoke stack by trial and
error. The target outputs are reviewed 2D/dense semantic labels, classified 3D
boxes in the point cloud, distance to objects, and a path to recorded and live
execution.
## Decision
Keep Rerun as the synchronized viewer and experiment evidence surface. Do not
treat it as the perception engine. Use CVAT as the human-review and
ground-truth control plane. Reuse NVIDIA's production inference patterns for
LiDAR and multimodal execution, adapting only K1 data loading, timestamps, and
factory calibration. Use open model frameworks as benchmark sources, not as a
second production runtime.
The shortest credible route is:
1. Review LAB E2 drafts in CVAT and freeze a versioned ground-truth subset.
2. Benchmark corrected/masked K1 images on that same subset.
3. Replace heuristic 3D cube construction with a LiDAR-native PointPillars
baseline.
4. Serve the selected 2D and 3D models through the existing Triton worker and
log outputs to Rerun.
5. Gate camera/LiDAR BEVFusion on proven synchronization, calibration, and
baseline metrics.
## Evidence from mature implementations
| Reference | Reusable practice | Mission Core use | Do not copy blindly |
| --- | --- | --- | --- |
| [Rerun](https://rerun.io/docs/overview/what-is-rerun) and its [examples](https://rerun.io/examples) | Time-aligned logging of images, point clouds, masks, 2D/3D boxes, trajectories, and model outputs | Viewer, debugging, playback, run evidence | It does not infer segmentation, depth, or boxes |
| [OctoSense](https://github.com/anthonytec2/OctoSense) | Camera rectification before inference; RGB/LiDAR timestamp alignment; semantic labeling; dynamic-object masks; calibrated LiDAR projection; dataset packaging | Copy the pipeline shape and QA gates | It is primarily a dataset-generation recipe, not a drop-in real-time Mission Core runtime |
| [DeepStream LiDAR 3D inference](https://docs.nvidia.com/metropolis/deepstream/7.1/text/DS_3D_Lidar_Inference.html) | PointPillars preprocessing, inference, postprocessing, and 3D box visualization in a production pipeline | First classified 3D-box baseline | Its sample data loader and calibration assumptions must be replaced with K1 adapters |
| [DeepStream multimodal fusion](https://docs.nvidia.com/metropolis/deepstream/8.0/text/DS_3D_MultiModal_Lidar_Sensor_Fusion.html) | Camera/LiDAR preprocessing and BEVFusion deployment | Later accuracy/robustness stage after LiDAR-only baseline | Too expensive as the first 3D fix; requires trustworthy synchronization and extrinsics |
| [MMDetection3D](https://github.com/open-mmlab/mmdetection3d) and [OpenPCDet](https://github.com/open-mmlab/OpenPCDet) | Reproducible 3D model/config baselines and pretrained checkpoints | Offline comparison and checkpoint selection | Do not introduce a parallel permanent serving stack beside Triton |
| [TAO auto-label](https://docs.nvidia.com/tao/tao-toolkit/text/data_services/auto-label.html) | Assisted labeling and model-driven annotation | Candidate accelerator after the first reviewed GT set exists | Auto-label output is still draft data, not accuracy evidence |
| [FiftyOne annotation integration](https://docs.voxel51.com/integrations/annotation.html) | Dataset curation, slice analysis, and CVAT round trips | Add when error analysis across many sessions becomes the bottleneck | Not needed to complete the first GT gate |
| [Isaac ROS nvblox](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox) | GPU mapping, reconstruction, and cost-map production | Later navigation/mapping consumer | It does not replace object detection or classified 3D boxes |
| [Isaac Sim camera sensors](https://docs.isaacsim.omniverse.nvidia.com/latest/sensors/isaacsim_sensors_camera.html) | Fisheye/OpenCV camera models and calibration-aware simulation | Synthetic regression cases after real-data metrics exist | Simulation is not a substitute for validating K1 factory calibration on recorded data |
## Selected stack boundaries
| Layer | Selected role | Current state | Gate before expansion |
| --- | --- | --- | --- |
| K1 ingestion | Preserve native timestamps, camera identity, point cloud, and vendor calibration | Recording works | Validate camera identity and projection on representative frames |
| Image preprocessing | Stable optical-circle mask plus calibration-derived correction profile | LAB E2 mask/draft path exists | Freeze profile ID and compare corrected versus raw on identical frames |
| 2D/dense perception | Generate prelabels, then evaluate against reviewed CVAT exports | LAB E2 drafts imported | Human two-pass review and class-level metrics |
| 3D detection | LiDAR-native PointPillars baseline | Next model integration | K1 point format adapter, coordinate convention test, reviewed 3D subset |
| Multimodal fusion | BEVFusion/DeepStream candidate | Deferred | Baselines, sync tolerance, extrinsic validation, GPU budget |
| Serving | Existing Triton worker | Running | Pin model/config artifacts and record per-stage latency/resource metrics |
| Visualization | Rerun | Running | Log reviewed labels and model outputs separately |
| Annotation/GT | CVAT `v2.70.0` | Deployed on D; LAB E2 tasks imported | Two-pass review; export with immutable manifest and hashes |
## Configuration practices to adopt
- Rectify or calibration-map the fisheye image before model inference when the
model expects a pinhole-like domain. Keep the raw frame alongside it.
- Apply a cached optical-circle mask/profile by camera identity; do not
recompute the mask for every frame.
- Synchronize camera frames to LiDAR timestamps explicitly and record the
chosen tolerance and dropped/unmatched frames.
- Keep 2D detections, dense semantics, LiDAR-native 3D detections, and fused
results as separate evidence streams. Fusion must not erase provenance.
- Project 3D boxes into the camera for validation, but do not construct final
3D boxes by simply extruding every 2D detection. That heuristic produces the
overlapping-cube failure observed in LAB E1.
- Record model identity, checkpoint hash, preprocessing profile, precision,
batch size, GPU memory, stage latency, wall time, and output hashes for every
material experiment.
- Treat every model-produced label as `ground_truth=false` until reviewed and
exported through the ground-truth gate.
## Next experiment gates
### LAB E2 review gate
- Review both imported tasks across the same 64 frames.
- First pass: correct labels, masks, and missed/false objects.
- Second pass: independent consistency review.
- Export reviewed instance and semantic annotations with manifest hashes.
### LAB E3 image baseline
- Compare raw-circle-mask, rectified, and correction-profile inputs on the
frozen reviewed subset.
- Select by class metrics and temporal stability, not by visual preference.
- Measure preprocessing, inference, postprocessing, and end-to-end time
separately.
### LAB E4 3D baseline
- Integrate a PointPillars-compatible K1 point loader.
- Lock coordinate axes, units, timestamps, and calibration version in the run
manifest.
- Evaluate classified 3D boxes on a small reviewed subset before any BEVFusion
work.
## Rejected shortcuts
- Replacing Rerun with OctoSense: wrong boundary; OctoSense supplies useful
pipeline patterns while Rerun remains the viewer.
- Moving the full stack to Omniverse: useful for simulation and synthetic data,
not the shortest path to recorded K1 accuracy.
- Treating current semantic/instance drafts as GT: invalidates all subsequent
model comparisons.
- Starting with BEVFusion: adds synchronization, calibration, conversion, and
runtime risks before the simpler LiDAR-native baseline is measured.
@@ -0,0 +1,223 @@
# LAB E10-N1 — Semantic-worker loss negative control
Date: 2026-07-22
State: completed; negative control accepted
Ground truth: no
Navigation/safety acceptance: no
Publication scope: laboratory negative control only
## Purpose and answer
E10-N1 tests a failure property required by the unmanned-system runtime:
> If the lower-rate semantic worker stops, does the 10 Hz detector/world-state
> path continue, does semantic freshness become stale explicitly, and does 3D
> fusion refuse to use an expired mask?
The answer is **yes for the accepted 60-second recorded source-paced control**:
- semantics was intentionally stopped after exactly 20 completed results;
- detector/tracker processed 601/601 frames at 10.010952 FPS with zero drops;
- world-state age p95 remained 90.268570 ms;
- semantic binding transitioned to 498 `stale` detector states;
- 3D fusion stopped after 96 fresh-mask frames and never resumed;
- freshness violations: zero;
- every negative-control acceptance check passed.
This proves fail-closed behavior for a controlled semantic-worker stop. It does
not yet prove recovery/restart, direct K1 transport or a process/container crash.
## Input and configuration identity
The camera, calibration, LiDAR pack, models and 60-second source interval are
identical to accepted LAB E10:
- session: `RAVNOVES00` / `20260720T065719Z_viewer_live`;
- source: `sensor.camera.right`, 800x600;
- source frames: 1,000–1,600 inclusive;
- session time: 135.365857292–195.334857292 seconds;
- source span: 59.969 seconds;
- calibration slot: `camera_1`;
- calibration SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`;
- LiDAR pack:
`e10-lidar-pack-5da0396d32a27f9d1ca537cc2e8a371d386078d6f0dc71737b78620992af9625`;
- detector: YOLOX-S/Triton every frame;
- semantics: EoMT/FP16 scheduled every fifth frame;
- semantic TTL: 750 ms;
- detector queue capacity: 2;
- semantic queue capacity: 1.
The only intentional change is:
```text
mode = semantic-loss-negative-control
stop_after_completed_results = 20
```
Profile SHA-256:
`eb2b114048336c273e77f3046d1eeac5187d27ec01e830de89ff201fb326c8c8`.
## Failure injection and expected behavior
The semantic worker exits cleanly immediately after publishing its twentieth
real mask. The source producer and detector consumer remain unchanged. The last
semantic result may be reused only until its 750 ms TTL expires.
After TTL expiration:
- semantic status must become `stale`;
- world-state delivery health becomes degraded where LiDAR exists;
- LiDAR association and cuboid generation must not execute with that mask;
- raw source scheduling and detector/tracker must continue to the final frame;
- the bounded semantic queue may discard scheduled work because its consumer is
intentionally absent, but this must not block the detector queue.
The result uses publication scope
`recorded-integrated-semantic-loss-negative-control-only`. Mission Core's E10
overlay store explicitly excludes that scope, so the negative control cannot
replace the accepted visual E10 result in `RAVNOVES00`.
## Result
Result ID:
`e10-integrated-perception-cf3b763731515120dc2714b6c3d7030496ed75057ce41ca88a1df62c21c4149c`
### Detector and world-state continuity
| Measurement | Result |
|---|---:|
| Detector scheduled / processed | 601 / 601 |
| Detector drops | 0 |
| Detector effective rate | 10.010952 FPS |
| Detector queue maximum / capacity | 1 / 2 |
| Replay wall / source span | 60.034251 / 59.969 s |
| World-state age mean | 63.686918 ms |
| World-state age p95 | 90.268570 ms |
| World-state age max | 140.979884 ms |
The main path remained source-paced after semantic loss. Its p95 stayed well
inside the frozen 175 ms laboratory budget.
### Semantic loss and freshness
| Measurement | Result |
|---|---:|
| Scheduled semantic frames | 121 |
| Completed before injected stop | 20 |
| Queue-overwritten after stop | 100 |
| Final bounded queue depth | 1 |
| Unavailable detector states | 5 |
| Fresh detector states | 98 |
| Stale detector states | 498 |
| Fresh coverage | 16.3062% |
Queue accounting is exact: 20 consumed + 100 overwritten + 1 final bounded
entry = 121 scheduled frames. These 100 semantic discards are the expected
effect of intentionally removing the consumer; detector drops remained zero.
### Fail-closed fusion
| Fusion state | Frames |
|---|---:|
| `fused` | 96 |
| `semantic-stale` | 428 |
| `semantic-unavailable` | 2 |
| `depth-unavailable-sync-gate` | 75 |
- accepted cuboid observations before loss became stale: 279;
- fused frames after the mask became stale: zero;
- fusion freshness violations: zero.
The 428 `semantic-stale` fusion states are fewer than the 498 stale detector
states because the depth synchronization gate takes precedence on 70 of those
frames. Neither path performs fusion with an expired semantic mask.
## Acceptance contract
| Check | Threshold | Result |
|---|---:|---:|
| Detector accounting | exact | pass |
| Detector rate | >= 9.5 FPS | 10.010952 |
| Detector drop fraction | <= 0.5% | 0% |
| World-state age p95 | <= 175 ms | 90.268570 ms |
| Semantic stop | exactly 20 results | 20 |
| Stale detector states | >= 450 | 498 |
| Semantic queue accounting | exact | pass |
| Fusion with non-fresh mask | zero | zero |
| Worker/producer errors | zero | zero |
All checks passed. `navigation_or_safety_accepted` remains false.
## GPU, memory and disk
Across 61 telemetry samples:
- GPU utilization: 58.72% mean, 90% p95, 93% max;
- board memory used: 13,151.64 MiB mean, 13,161 MiB max;
- power: 160.05 W mean, 211.02 W p95, 219.41 W max;
- temperature: 41.28 C mean, 42 C max;
- CUDA peak allocated: 2,107.925 MiB;
- CUDA peak reserved: 2,840.0 MiB;
- process peak RSS: 2,023.730 MiB.
The worker orchestrator used only `D:\NDC_MISSIONCORE`, enforced a 360 GiB
floor, and completed with approximately 376.88 GiB free after cleanup. Windows
C was not used. YOLOX was returned to its previous load state.
## Producer and artifact identity
- runner SHA-256:
`894e679c91d398b0084fa7894346c827e6a54b01e1841cf505a21be76a6c7188`;
- fusion runtime SHA-256:
`042136fffbc6e06ef62c6d223d5272ce48e0136651f430ab1d59729b32c4bd90`;
- orchestrator SHA-256:
`ac0ee42495839773a2de60090ebcb49eee979dc7ee3b8587662a3c59e19a4d03`;
- profile SHA-256:
`eb2b114048336c273e77f3046d1eeac5187d27ec01e830de89ff201fb326c8c8`;
- independent validator/renderer SHA-256:
`deb9e9f581b0868c9cf3ecae73b17b3253152c969d46089f11ad364efa6f6804`.
| Artifact | SHA-256 |
|---|---|
| `semantic-frames.jsonl` | `ad187a84335cefed40b2d2db7261c16ff053057d49554dc9097e1258a63fa81e` |
| `fusion-frames.jsonl` | `dbd8f203c41ed3f9ba0325b9a53227c7d25c66a34733caf0f2d687a18c26b5a6` |
| `world-state.jsonl` | `2a4e1e0e1bade8f07b12e880602c24fb4b4f8814b683ab01b407174592bd4230` |
| `transient-perception.npz` | `b653d8be9bc2474804268e9aa0a8c7dfdb1b2cacc779b515ef6b112aa9231e89` |
| `gpu-telemetry.jsonl` | `07708357da7a5c464db2015f897137deb730762c68e247ed5eeb40cee19ac108` |
| `run-report.json` | `06a8170ccdf561c51616050887a0ddd489894e36d22d35f0dd02ebbc700cf9e1` |
| `result.json` | `e23821353a3afdd3789b9fee57d58915ca56c002828453662ec30824a878d3f5` |
Independent validation accepted the result and confirmed its negative-control
publication scope. A store-selection regression proved that the UI continues to
select the accepted qualification result
`e10-integrated-perception-c6e0263...`, not E10-N1. The targeted regression
suite now passes 48 tests.
## Accepted scope
- controlled semantic-worker stop after real inference results;
- detector/tracker continuity at source pace;
- explicit unavailable/fresh/stale state transition;
- fail-closed LiDAR fusion after TTL expiration;
- bounded semantic queue behavior without detector backpressure;
- negative-control isolation from the user-facing Rerun result;
- D-only storage and disk-floor enforcement.
## Not accepted
- automatic semantic-worker restart or state recovery;
- hard process/container/GPU failure;
- direct K1 live transport;
- long-duration operation;
- target onboard hardware;
- navigation or safety use.
## Next gate
The next gate is the full `RAVNOVES00` stability run with the normal accepted E10
configuration. It must cover the complete approximately nine-minute camera
track, build a matching full-session LiDAR replay pack, preserve bounded queues
and memory, and keep the D free-space floor. After that, the recorded source
adapter can be replaced with direct K1 transport under the same contracts.
@@ -0,0 +1,443 @@
# LAB E10 — Integrated source-paced perception and world state
Date: 2026-07-22 (Europe/Moscow)
Run timestamp: 2026-07-21T22:30:03.785Z
State: completed; 60-second qualification accepted
Ground truth: no
Navigation/safety acceptance: no
## Purpose and answer
LAB E10 is the first Mission Core experiment that executes the current
perception branches as one source-paced pipeline rather than composing separate
offline outputs:
```text
recorded K1 camera at original timestamps
-> valid fisheye FOV
-> YOLOX/Triton detections at ~10 Hz
-> ByteTrack-style temporal IDs
+ concurrent EoMT semantics at ~2 Hz
+ factory KB4 camera/LiDAR projection
+ strict recorded LiDAR synchronization gate
-> point-supported distance and oriented 3D cuboids
-> clearance sectors and transient world state
-> Mission Core/Rerun recorded-session overlay
```
The answer is **yes for the accepted recorded 60-second interval on the RTX
4090 worker**:
- detector/tracker: 601/601 frames, 0 drops, 9.996120 FPS;
- semantics: 121/121 scheduled frames, 0 drops, 2.012530 FPS;
- fresh semantic coverage: 597/601 states, 99.3344%;
- LiDAR-fused states: 525;
- accepted point-supported cuboids: 760 observations;
- world-state age p95: 76.514918 ms;
- measured replay wall time: 60.123327 seconds for 59.969 source seconds;
- every frozen acceptance check passed.
This closes the integration gap left by E9. Semantic masks used by LiDAR
association were the actual newest in-memory EoMT results from the concurrent
worker. E6 fusion and E7-style world-state calculation were executed inside the
same critical replay loop. Per-frame masks were not synchronously encoded as
PNG during the measurement.
This result does **not** prove direct live K1 transport, long-duration stability,
forest-domain accuracy, target onboard compute performance or safety fitness.
## Immutable input identity
- Compute job: `recorded-camera-602ac89026ed12978619801d`
- Input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`
- Session title: `RAVNOVES00`
- Session ID: `20260720T065719Z_viewer_live`
- Camera source: `sensor.camera.right`
- Resolution: 800x600
- Factory calibration slot: `camera_1`
- Calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Source frame selection: 1,000–1,600 inclusive, 601 frames
- Session timeline: 135.365857292–195.334857292 seconds
- Timeline SHA-256:
`06e3514228b488137b92c303cbca171154ede00368783e32107ed2c39c41baa6`
- Source span: 59.969 seconds
Valid-FOV identity:
- generation:
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`;
- mask SHA-256:
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`;
- valid pixels: 270,606 of 480,000.
The FOV mask is generated once and content-addressed. Pixels outside the usable
fisheye circle are excluded before model processing; no repeated per-frame mask
discovery occurs.
## LiDAR replay pack
The 60-second LiDAR input is a private immutable replay pack derived from the
accepted E6 synchronization evidence:
- pack ID:
`e10-lidar-pack-5da0396d32a27f9d1ca537cc2e8a371d386078d6f0dc71737b78620992af9625`;
- 601 camera timeline rows;
- 526 LiDAR rows passing the accepted strict synchronization gate;
- 1,182,292 total points;
- source coordinate system: K1 map frame;
- target camera: `sensor.camera.right` / factory `camera_1`;
- projection model: KB4, 800x600;
- temporal contract: accepted E6 nearest-host-arrival best effort;
- `lidar-pack.npz`: 9,629,449 bytes;
- pack SHA-256:
`72aa73340b20fcfaa21b330ef5b93b975c14a70e2cd16a57752d9952ff05ad9a`;
- manifest SHA-256:
`189a56660b69b05d39308b8dfa6c5ad8ed8812e870a218188181787cc6ea00ba`.
The pack is a replay optimization only. It does not change camera/LiDAR
calibration, points, timestamps or the E6 synchronization decision.
## Models and worker runtime
Detector/tracker branch:
- official Megvii YOLOX-S ONNX release `0.1.1rc0`;
- COCO-80 classes;
- model SHA-256:
`c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- ONNX Runtime GPU through the existing Triton container;
- ByteTrack-style two-stage IoU tracking inherited from E5/E8/E9.
Semantic branch:
- `tue-mps/cityscapes_semantic_eomt_large_1024`;
- revision: `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`;
- weights: 1,276,175,488 bytes;
- weights SHA-256:
`c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`;
- batch size one, FP16 autocast;
- Cityscapes output mapped to the existing Mission Core 0–15 taxonomy.
Worker runtime:
- GPU: NVIDIA GeForce RTX 4090;
- container: `nvcr.io/nvidia/tritonserver:26.06-py3`;
- Python 3.12.3;
- PyTorch 2.13.0+cu130;
- Transformers 4.57.6;
- NumPy 1.26.4;
- SciPy 1.16.3.
## Frozen scheduling and fusion configuration
Detector path:
- every source frame, nominal 10 Hz;
- bounded latest-wins queue capacity: 2;
- observed maximum queue depth: 1;
- overflow drops: 0.
Semantic path:
- every fifth source frame, nominal 2 Hz;
- bounded latest-wins queue capacity: 1;
- TTL: 750 ms;
- overflow drops: 0;
- states before the first completed mask are explicitly `unavailable`.
LiDAR association:
- nearest-depth KB4 projection buffer;
- horizontal box inset 5%; top inset 4%; bottom inset 2%;
- semantic support required for person, bicycle, motorcycle and vehicle groups;
- class-dependent spatial clustering radius: 0.9 m for person/two-wheelers,
1.5 m for vehicles;
- minimum support points: 3 for person/two-wheelers, 5 for vehicles;
- depth-cluster split: max(0.5 m, 6% of depth);
- same-group NMS IoU: 0.55;
- distance history: 5 frames;
- distance innovation gate: max(1.5 m, 25%);
- oriented cuboid dimensions use robust p05–p95 surface extents and explicit
class-dependent plausibility limits.
World state:
- velocity history: 1.0 second;
- clearance: 72 azimuth sectors;
- considered range: 0.5–30 m;
- obstacle height above estimated ground: 0.2–3.0 m;
- forward clearance half-angle: 15 degrees.
## Frozen acceptance contract
| Check | Threshold | Result |
|---|---:|---:|
| Detector effective rate | >= 9.5 FPS | 9.996120 FPS |
| Detector drop fraction | <= 0.5% | 0% |
| Semantic effective rate | >= 1.8 FPS | 2.012530 FPS |
| Semantic drop fraction | <= 5% | 0% |
| Semantic completion age p95 | <= 400 ms | 179.682194 ms |
| Fresh semantic coverage | >= 90% | 99.3344% |
| World-state age p95 | <= 175 ms | 76.514918 ms |
| LiDAR-fused frames | >= 450 | 525 |
| Accepted cuboid observations | >= 100 | 760 |
| Failures | zero | zero |
All checks were frozen before the qualification output and all passed. The
result remains explicitly `navigation_or_safety_accepted=false`.
## Timing result
### Critical detector/fusion/world-state path
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| World-state age from source deadline | 56.074 ms | 52.017 ms | 76.515 ms | 118.755 ms |
| Camera decode | 28.826 ms | 28.332 ms | 33.181 ms | 51.831 ms |
| Detector inference path | 23.442 ms | 20.298 ms | 36.127 ms | 48.794 ms |
| Detector queue wait | 0.087 ms | 0.066 ms | 0.155 ms | 5.694 ms |
| LiDAR projection | 0.411 ms | 0.358 ms | 0.723 ms | 4.258 ms |
| Semantic/LiDAR association | 1.649 ms | 1.391 ms | 3.858 ms | 12.513 ms |
| Clearance | 0.187 ms | 0.175 ms | 0.394 ms | 0.667 ms |
| World-state serialization/calculation | 0.088 ms | 0.076 ms | 0.226 ms | 0.586 ms |
The critical path stayed below its 175 ms laboratory ceiling without frame
drops. Decode plus detection dominates the 10 Hz path; LiDAR projection,
association, cuboid generation, clearance and world-state computation consume
only a few milliseconds at p95 on this input.
### Semantic branch
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| Completion age from source deadline | 155.067 ms | 150.439 ms | 179.682 ms | 384.761 ms |
| Processing after dequeue | 124.748 ms | 122.595 ms | 138.430 ms | 353.011 ms |
| EoMT forward | 98.849 ms | 96.101 ms | 112.343 ms | 309.115 ms |
| Processor | 9.647 ms | 9.347 ms | 11.958 ms | 31.297 ms |
| Host to device | 5.680 ms | 6.063 ms | 8.184 ms | 11.627 ms |
| Model postprocess | 7.451 ms | 6.703 ms | 11.166 ms | 12.413 ms |
| Valid-FOV fill | 1.927 ms | 1.882 ms | 2.249 ms | 3.075 ms |
| Queue wait | 0.145 ms | 0.155 ms | 0.194 ms | 0.308 ms |
The semantic branch is deliberately asynchronous. A semantic outlier therefore
does not block detector admission or raw recording. The newest completed mask
is bound only to current/future detector states and only while its TTL remains
valid.
## Fusion and world-state output
Fusion states across 601 detector rows:
- `fused`: 525;
- `depth-unavailable-sync-gate`: 75;
- `semantic-unavailable`: 1.
The LiDAR pack exposes 526 strict-sync frames. One of those occurred before the
first semantic result completed, explaining 525 actual fused states without
inventing a mask.
Cuboid association counts:
- accepted point-supported cuboid observations: 760;
- rejected, fewer than five clustered points: 996;
- rejected, no semantic LiDAR support: 574;
- rejected, distance innovation: 172;
- rejected, fewer than three clustered points: 45;
- rejected, implausible cuboid: 14.
The 760 value is a count of accepted per-frame cuboid observations, not 760
unique physical objects. Cuboids bound the visible LiDAR-supported surface. They
must not be interpreted as complete object volume or ground truth.
## GPU, memory and disk
Across 61 one-second telemetry samples:
- GPU utilization: 45.57% mean, 61% p95, 65% max;
- board memory used: 13,119.30 MiB mean, 13,128 MiB max;
- board memory utilization: 19.51% mean, 26% max;
- power: 154.70 W mean, 166.75 W p95, 178.45 W max;
- temperature: 43.87 C mean, 46 C max;
- EoMT process CUDA peak allocated: 2,107.925 MiB;
- EoMT process CUDA peak reserved: 2,840.0 MiB;
- process peak RSS: 2,022.535 MiB.
This shows usable compute headroom on the laboratory RTX 4090 for this frozen
configuration. It says nothing yet about the final onboard GPU/accelerator.
That hardware must receive its own experiment and budgets.
Task-controlled worker storage remained under `D:\NDC_MISSIONCORE`:
| Disk point | Free bytes | Approx. free GiB |
|---|---:|---:|
| Before measured replay | 404,946,915,328 | 377.136 |
| After measured replay | 404,933,578,752 | 377.124 |
| Enforced floor | 386,547,056,640 | 360.000 |
Temporary decoded frames were cleaned from the exact run-owned D path. The
YOLOX model state was restored after the run. Triton, Frigate and Ollama were
left running. Windows C was not used or modified.
## Result artifacts and integrity
Published result:
`e10-integrated-perception-c6e0263b9e18be7e4f447597065b5ff93b8511463e8f453800f98a61f25c37df`
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `semantic-frames.jsonl` | 55,025 | `16cb53c7df8491b6620d0483f5004953d0aa54e83198bb101ae357c06e34be89` |
| `fusion-frames.jsonl` | 1,418,427 | `1b28baf9dfc6c8a793f60233f5ffd03a3e40f63a4dfac7a97bff2813f5bd967d` |
| `world-state.jsonl` | 1,524,066 | `9bdab9c1749fca98797ab428bd94f3e1b5a80b7977f013f323283ce9559040ee` |
| `transient-perception.npz` | 1,106,484 | `7e0d0761584f4700e55caf31965d53de1c13b4f8a17c7cfbd2279af4c5f4cabd` |
| `gpu-telemetry.jsonl` | 13,704 | `e036cdd35addc96152c3c534e75e249ac25b18b588cecabb30a8729d36d80f3c` |
| `run-report.json` | 13,563 | `a053d629ea1b48e4a039d7277e0753108926e6efa0000971bc89008eac66ec26` |
| `result.json` | immutable manifest | `48ac8865ff629b00e56ca90cb4a3a68b161f06275465a86ae7973808f8472c2b` |
Accepted producer identities:
- runner SHA-256:
`3f667e291f18bcccd58d0ced391e23e0cca05ba3cdeb0da50b0f2fb814c4ff70`;
- fusion runtime SHA-256:
`4d4d9d7fcf8f3b86ac84228902df1948aafc2f48643c53f77250e77bf9854d5d`;
- profile SHA-256:
`42e2a49945e65daf86e7891794e30ef2c5182b71d073c76ae764c03ea2501589`;
- accepted run orchestrator SHA-256:
`8bc939e9a5e94707cd30bf7d948fed893087c03d3345e31c20c853b237ecde76`.
After acceptance, only the orchestrator's human-readable free-space line was
changed from pipeline output to host output so future variable assignment does
not capture the label. Current orchestrator SHA-256 is
`ac0ee42495839773a2de60090ebcb49eee979dc7ee3b8587662a3c59e19a4d03`.
This post-run correction does not alter or relabel the accepted E10 result.
The local E10 Python sources were subsequently normalized by the repository's
Ruff formatter and passed the same 47-test regression suite. The accepted
worker staging directory remains the immutable source of the producer hashes
above. Current local source hashes for the next run are:
- fusion runtime:
`042136fffbc6e06ef62c6d223d5272ce48e0136651f430ab1d59729b32c4bd90`;
- LiDAR-pack builder:
`e75875342f11a0573eb5eef106f10ff9545debf6c012e717c1df281d479b746b`;
- worker runner:
`9e434a82d370d1ecf079d76cec5c7a4cf955ca631651862eacb8572b94e33c8b`;
- Mission Core validator/renderer:
`260a03c623bcab15c6dafd63caa36514e9e35a40e0e6bd4d6551eb3db832c058`.
Durable local evidence root:
```text
.runtime/compute-experiments/e10/worker-results/
```
Worker result root:
```text
D:\NDC_MISSIONCORE\runtime\derived\
e10-integrated-perception-c6e0263b9e18be7e4f447597065b5ff93b8511463e8f453800f98a61f25c37df
```
## Independent validation and regression checks
The independent E10 validator confirmed:
- content-addressed result identity and artifact hashes;
- exact job/session/source/calibration/model/profile/runner binding;
- detector and semantic queue accounting;
- ordered source timestamps and semantic stride;
- semantic binding never comes from a future source frame;
- unavailable states contain no invented perception payload;
- LiDAR pack identity, size and hash;
- every frozen acceptance check.
The standalone E10 fusion runtime was also run against the accepted E6
precomputed E4/E5 inputs. It reproduced E6 exactly: 526 fused frames and 918
accepted cuboid observations. This is the control that the E10 integration did
not silently change the established E6 geometry algorithm.
Targeted automated tests passed: 47 tests across E10, compute-result discovery,
session API, E9, and E6. Direct Rerun rendering produced a valid 58,342,680-byte
`RRF2` stream. The Mission Core perception endpoint returned HTTP 200 with a
58,342,936-byte `RRF2` response.
## Mission Core / Rerun publication
Mission Core now discovers the newest accepted E10 result for the active
recording before falling back to E6 or legacy 2D results. The generated Rerun
stream contains:
- camera frames;
- semantic masks;
- 2D tracked detections;
- raw map-frame point cloud;
- LiDAR support points;
- oriented point-supported 3D cuboids;
- range and world-state evidence.
Visual acceptance was completed in the local Control Station:
1. `Наблюдение` -> `Пространственная сцена`;
2. saved session `RAVNOVES00`;
3. three active sources loaded;
4. original camera and `Сегментация · камера right` played together on the
common session timeline;
5. real point cloud and trajectory remained visible behind the camera panels;
6. playback time advanced normally, proving the result is not a single image.
The E10 perception overlay covers only its qualified 60-second source interval.
The underlying saved session remains the full 8:55.718 recording.
## Accepted scope
- actual concurrent YOLOX/Triton and EoMT/PyTorch execution;
- actual current in-memory semantic mask consumed by LiDAR association;
- source-paced detector/tracker near 10 Hz and semantics near 2 Hz;
- bounded latest-wins queues with zero measured drops;
- factory KB4 projection using XGRIDS calibration;
- point-supported distance and oriented 3D cuboids;
- clearance and transient world-state publication in the critical loop;
- 60-second latency, GPU, memory and disk evidence;
- playback in Mission Core/Rerun for the recorded session;
- D-only worker storage and restored service/model state.
## Not accepted
- direct K1 MQTT/camera transport into the perception scheduler;
- the full 8:55.718 session or multi-hour continuous operation;
- hardware-clock camera/LiDAR synchronization proof;
- failure behavior after semantic-worker loss;
- forest/off-road model quality or class coverage;
- final onboard compute rate, thermals or power;
- complete-object 3D geometry;
- navigation or safety use.
## Next gates
The next work is aimed at the unmanned-system operating contour, not cosmetic
offline quality:
1. **Semantic-loss negative control.** Kill or stall the semantic branch during
source-paced replay and prove detector/raw recording continue, freshness
becomes stale/unavailable, and no false fresh mask is published.
2. **Full RAVNOVES00 stability run.** Build a full-session immutable LiDAR pack,
run the same frozen pipeline for approximately nine minutes, verify bounded
memory/queues, zero recording interference and D free-space floor.
3. **Direct K1 transport.** Replace the recorded camera/LiDAR source adapter
with the existing live K1 ingestion while preserving the accepted scheduler,
freshness and world-state contracts.
4. **Target onboard worker qualification.** Repeat the same gates on the actual
deployable compute hardware; optimize models only against explicit rate,
latency, thermal and power budgets.
5. **Domain quality branch.** In parallel with runtime work, create forest and
off-road evaluation data for classes, traversability and geometry. This must
not block proving the live transport and scheduling contour.
Item 1 was subsequently completed and accepted as
`LAB_E10_N1_REPORT_2026-07-22.md`: detector continuity remained 10.010952 FPS
with zero drops, 498 states became explicitly stale, and fusion recorded zero
freshness violations after the controlled semantic stop. The immediate gate is
therefore the full-session run. No model upgrade or UI polish is justified
before long-duration boundedness is measured.
@@ -0,0 +1,369 @@
# LAB E11 — Full-session integrated perception stability qualification
Date: 2026-07-22
Accepted result creation time: 2026-07-22T06:27:51.941Z
Laboratory status: **accepted**
Navigation or safety status: **not accepted**
## Objective
LAB E11 tested whether the integrated Mission Core perception contour proven on
the 60-second LAB E10 interval remains bounded and source-paced across the
complete recorded right-camera epoch from `RAVNOVES00`.
The qualification retained the intended onboard architecture:
```text
recorded camera at original source timestamps
-> bounded latest-wins detector queue
-> YOLOX-S + ByteTrack-compatible tracking at camera rate
-> bounded sampled semantic queue
-> EoMT Cityscapes semantics at 1/5 camera rate
-> factory camera_1 KB4 camera/LiDAR projection
-> visible LiDAR-supported 3D cuboids and distance
-> clearance sectors and timestamped world state
```
This was not an offline maximum-quality render. The replay speed was exactly
`1.0`, queues were bounded, and acceptance required the worker to keep up with
the recorded source without detector loss.
## Result summary
Accepted immutable result:
`e10-integrated-perception-7613b09021455ddec941144224142f7879e3c6ecac5b2370ea8a7d4cb01918ba`
Worker result root:
`D:\NDC_MISSIONCORE\runtime\derived\e10-integrated-perception-7613b09021455ddec941144224142f7879e3c6ecac5b2370ea8a7d4cb01918ba`
Local verified mirror:
`.runtime/compute-experiments/e10/worker-results/e10-integrated-perception-7613b09021455ddec941144224142f7879e3c6ecac5b2370ea8a7d4cb01918ba`
| Measure | Result | Frozen gate | State |
|---|---:|---:|---|
| Source frames | 4,489/4,489 | exactly 4,489 | pass |
| Detector drops | 0 | 0 | pass |
| Detector effective rate | 10.004402 FPS | >=9.5 FPS | pass |
| Semantic results | 898/898 scheduled | accounting exact | pass |
| Semantic drops | 0 | <=5% | pass |
| Semantic effective rate | 2.001326 FPS | >=1.8 FPS | pass |
| Fresh semantic coverage | 99.754956% | >=90% | pass |
| Semantic completion age p95 | 267.211864 ms | <=400 ms | pass |
| World-state age p95 | 102.714712 ms | <=175 ms | pass |
| LiDAR-fused frames | 3,917 | >=3,500 | pass |
| Accepted cuboid observations | 6,157 | >=500 | pass |
| Inference/producer failures | 0 | 0 | pass |
| Fusion with non-fresh semantics | 0 | 0 | pass |
The complete 448.623-second camera interval was replayed in 448.702 seconds.
This is source-paced operation, not an accelerated batch claim.
## Frozen inputs
### Recorded source
- UI session alias: `RAVNOVES00`;
- session ID: `20260720T065719Z_viewer_live`;
- compute job: `recorded-camera-602ac89026ed12978619801d`;
- job input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`;
- source: `sensor.camera.right`;
- codec epoch: `1`;
- resolution: 800x600;
- source frame indices: 0–4,488 inclusive;
- camera timeline: 35.421857292–484.044857292 seconds;
- timeline SHA-256:
`f91dad0a3cc998f4250be794b48f437943b3c60edccbe7e5ceed59030854a807`;
- job payload: 363,235,615 bytes;
- raw MQTT evidence: 133,572,722 bytes.
### Factory calibration
- slot: `camera_1`;
- content identity SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`;
- projection: factory KB4 intrinsics plus camera/LiDAR extrinsic;
- LiDAR coordinate source: K1 map frame.
No calibration was fitted or modified during LAB E11.
### Full LiDAR replay pack
Immutable pack:
`e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b`
- frames: 4,489;
- frames with accepted LiDAR and pose: 3,928;
- strict-sync coverage: 87.502785%;
- point observations: 9,207,270;
- compressed payload: 72,996,000 bytes;
- payload SHA-256:
`0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944`;
- builder SHA-256:
`bc81a1aa8759a750480745a52af34f4c700261e8bfbf5d615e8c6e5fa96bb69c`;
- accepted E6 temporal-profile SHA-256:
`b8d3a6f043fde54ffd32ea05bfe580e2be7aace1a23cdbf7e266eb3325d147b2`;
- accepted E6 result:
`e6-fusion-b4e4226674a66f6196c033785eb307c255a7bdd8493ef9809dfb1d6e5bd68eaa`.
The pack uses the unchanged E6 temporal policy:
- clock source: recorded host monotonic arrival;
- binding: nearest host-arrival best effort;
- maximum camera/LiDAR delta: 100 ms;
- maximum point/pose delta: 100 ms.
The pack is a replay optimization and immutable input. It does not improve or
reinterpret synchronization accuracy.
## Frozen software configuration
### Integrated scheduling profile
Profile: `e10_full_session_profile.json`
SHA-256: `73dd8472b59c9ad48b781600c89eadb0aab12cb6ef59c4572ce5a8e17dcd4472`
- mode: `full-session-qualification`;
- replay speed: 1.0;
- detector queue: latest-wins, capacity 2;
- semantic queue: latest-wins, capacity 1;
- semantic scheduling: every fifth camera frame;
- semantic TTL: 750 ms;
- detector loss gate: exactly zero;
- required source selection: frames 0–4,488 and at least 448 seconds.
### Producer identities
| Component | SHA-256 |
|---|---|
| Integrated runner | `5548cc607f245cd1c01c85d6b821fdf5b91e512ee9b2f8ad6862175c93b7628c` |
| Fusion runtime | `844a3a3ecc56313be2f96a6b861abd8f55100ec7427a72379e52a55fbd4c5749` |
| PowerShell orchestrator | `ac0ee42495839773a2de60090ebcb49eee979dc7ee3b8587662a3c59e19a4d03` |
| Detector profile | `819bdfb0da521d187ec9ce3d3039a0ff75daa0ab473fb3d8e5fa92db9c1ddca1` |
| Semantic profile | `ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875` |
Worker runner generation:
`D:\NDC_MISSIONCORE\runtime\derived\e10-runner-5548cc607f24-ac0ee4249583`
### Models
Detector:
- YOLOX-S, COCO-80;
- ONNX SHA-256:
`c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- Triton config SHA-256:
`5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604`.
Semantic model:
- `tue-mps/cityscapes_semantic_eomt_large_1024`;
- revision `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`;
- `model.safetensors` SHA-256:
`c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`;
- precision: FP16 autocast;
- batch size: 1.
Valid-FOV mask:
- generation:
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`;
- mask SHA-256:
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`;
- admitted pixels: 270,606/480,000.
## Worker and resource envelope
- GPU: NVIDIA GeForce RTX 4090;
- container: `nvcr.io/nvidia/tritonserver:26.06-py3`;
- Python: 3.12.3;
- PyTorch: 2.13.0+cu130;
- Transformers: 4.57.6;
- NumPy: 1.26.4;
- SciPy: 1.16.3.
GPU telemetry, 449 one-second samples:
| Measure | Mean | p95 | Max |
|---|---:|---:|---:|
| GPU utilization | 67.70% | 92% | 96% |
| Board GPU memory used | 13,151.48 MiB | 13,161 MiB | 13,161 MiB |
| GPU-memory controller utilization | 16.40% | 33% | 34% |
| Power | 207.34 W | 215.51 W | 229.74 W |
| Temperature | 48.46 C | 53 C | 54 C |
Process and CUDA measurements:
- process peak RSS: 2,944.93 MiB;
- CUDA peak allocated by the runner: 2,107.93 MiB;
- CUDA peak reserved by the runner: 2,840 MiB.
The board-memory figure includes the existing Triton/model context and is not
equivalent to runner-only allocation.
## Latency detail
| Stage | Mean | p95 | Max |
|---|---:|---:|---:|
| Frame decode | 34.198 ms | 47.488 ms | 84.767 ms |
| Detector | 32.099 ms | 47.759 ms | 70.328 ms |
| Detector queue wait | 0.230 ms | 0.202 ms | 33.917 ms |
| LiDAR projection | 0.491 ms | 0.940 ms | 7.396 ms |
| Object association | 2.176 ms | 5.795 ms | 20.093 ms |
| Clearance | 0.236 ms | 0.577 ms | 4.740 ms |
| World-state construction | 0.117 ms | 0.333 ms | 4.562 ms |
| End-to-end world-state age | 71.634 ms | 102.715 ms | 189.403 ms |
Semantic inference:
| Stage | Mean | p95 | Max |
|---|---:|---:|---:|
| Processing | 191.693 ms | 209.808 ms | 448.629 ms |
| Forward | 156.680 ms | 170.785 ms | 414.754 ms |
| Completion age | 228.427 ms | 267.212 ms | 477.220 ms |
| Semantic queue wait | 0.186 ms | 0.279 ms | 4.548 ms |
The acceptance contract uses p95 rather than the isolated maximum. Five startup
frames had no semantic state and six later frames observed a stale state; no 3D
fusion was asserted on any non-fresh semantic state.
## Fusion detail
- LiDAR available: 3,928 frames;
- fused with fresh semantics: 3,917 frames;
- fused share of LiDAR-available frames: 99.719959%;
- depth unavailable under the frozen sync gate: 561 frames;
- semantic unavailable: 5 frames;
- semantic stale: 6 frames;
- accepted cuboid observations: 6,157;
- fusion freshness violations: 0.
Rejections are expected diagnostic outcomes:
| Reason | Count |
|---|---:|
| Insufficient 5-point support | 8,268 |
| No semantic LiDAR support | 4,892 |
| Distance innovation gate | 1,045 |
| Insufficient 3-point support | 142 |
| Implausible cuboid extent | 24 |
These counts describe per-frame object observations, not unique physical
objects.
## Disk safety and storage behavior
The worker orchestrator was confined to `D:\NDC_MISSIONCORE`. Drive C was not
read or written by this task.
- configured D free-space floor: 360 GiB;
- preflight free space: 376.506 GiB;
- calculated additional reserve requirement: 11.834 GiB;
- after full stream reconstruction: 376.127 GiB;
- after 4,489 PNG extraction: 373.578 GiB;
- after publication, before temporary cleanup: 373.347 GiB;
- after cleanup and model-state restoration: 376.223 GiB.
Temporary frames and the reconstructed camera stream were deleted by the
orchestrator. No raw recording, prior result, model, cache, or accepted runner
generation was deleted.
Known orchestrator timings:
- frame preparation: 31.425 seconds;
- source-paced replay: 448.702 seconds;
- total orchestrator wall time, including model validation/loading and cleanup:
600.623 seconds.
## Published artifacts
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `semantic-frames.jsonl` | 404,380 | `49b1a859924b26ab5f550ca3c6d6ec907c32c0ef01870c270c33ef3eb103db53` |
| `fusion-frames.jsonl` | 11,322,702 | `67ee78bc1385e5733ff33106fb7d257825ce297f69356af763c2598a531e8f3c` |
| `world-state.jsonl` | 11,703,966 | `d4772484dd0740ba009a947114d4c6bde7b5fc2ffbe55ddf7a341cf9aa11e5a5` |
| `transient-perception.npz` | 8,538,476 | `e53f0aea9b94704cd95c898b4b6f9c21da4fdad864373889213a9c1d66080ccf` |
| `gpu-telemetry.jsonl` | 100,714 | `2fd0d8f9497f5cff06a3035b9f251c780a0f4fcccf14d3a89f49a18584f43e92` |
| `run-report.json` | 14,076 | `3f0577b0dc8b55b38b8528c8f081be71af856327485f088f0ab0305e5a731ab1` |
Publication scope is
`recorded-integrated-realtime-qualification-only`. The accepted result is
eligible to become the newest recorded integrated overlay for this session.
## Verification
The copied result was independently validated on the Mission Core host using
`validate_integrated_perception_result`. Validation rechecked:
- compute-job identity;
- result and configuration identities;
- full contiguous source selection;
- LiDAR-pack identity and calibration binding;
- artifact sizes and SHA-256 values;
- 4,489 fusion rows and 4,489 world-state rows;
- semantic timeline ordering;
- NPZ array shapes and offsets;
- report/result acceptance consistency.
The focused automated suite completed with 24 passing tests, including the E10,
E8, E9, live-perception and qualification validators.
## Controlled preflight rejection
An initial invocation passed the base E5 detector profile where the integrated
runner requires the E8 real-time tracking wrapper. The schema guard rejected it
during preflight with `LAB E8 profile schema changed`.
That invocation did not extract frames, start replay, publish a result, or alter
model state. The accepted invocation changed only the detector-profile argument
to the already frozen E8 profile. This rejected preflight is retained here so
the laboratory history is complete.
## What LAB E11 proves
LAB E11 proves that the current RTX 4090 worker and bounded scheduling contour
can replay the complete recorded `RAVNOVES00` right-camera epoch at source rate
while producing:
- 10 Hz 2D detection/tracking;
- approximately 2 Hz semantic state;
- factory-calibrated LiDAR-supported distance and visible-surface cuboids;
- timestamped world state and clearance;
- bounded queues with no detector or semantic loss in this run.
It also shows that the one-minute LAB E10 result was not a short-interval
artifact: source-paced behavior remained stable for the full 448.623 seconds.
## What LAB E11 does not prove
- direct live K1 camera/MQTT ingestion into this scheduler;
- hardware-clock camera/LiDAR synchronization;
- forest-domain accuracy or robustness;
- safety or navigation fitness;
- complete amodal 3D object dimensions;
- multi-camera fusion;
- performance on weaker onboard compute hardware.
YOLOX COCO and EoMT Cityscapes are generic models. Cuboids describe the visible
LiDAR-supported surface selected by the current association policy, not a
ground-truth full object volume.
## Next gate
The next priority is not another offline quality-only pass. It is a transport
integration gate:
1. feed the same bounded scheduler from direct K1 camera and LiDAR/MQTT sources;
2. keep the latest-wins queues and freshness rules unchanged;
3. expose live world-state age, drop counters and semantic freshness;
4. qualify a controlled near-real-time live interval on this same worker;
5. only then compare lower-power onboard targets or more domain-specific models.
The full-session recorded overlay may be rendered on demand for visual QA, but
its generation is presentation work and is not required to accept the compute
stability result.
@@ -0,0 +1,237 @@
# LAB E12 — Bounded shadow transport to the external perception worker
Date: 2026-07-22
State: near-live transport qualification accepted; physical K1 live run pending
Ground truth: no
Navigation/safety acceptance: no
Publication scope: diagnostic shadow transport only
## Purpose and answer
LAB E12 asks whether Mission Core can carry the already proven camera,
LiDAR and pose inputs to the external RTX worker at source rate without giving
that worker a second connection to K1, without allowing it to back-pressure the
raw recording, and without granting it any command authority.
The answer is **yes for a 15-second source-paced RAVNOVES00 near-live run**:
- Mission Core admitted and the Windows worker verified 446/446 ordered events;
- the stream contained 151 camera frames, one camera init, 142 LiDAR frames,
150 poses and two lifecycle records;
- ingress sequence gaps, payload-integrity failures, queue overflow and oversize
rejection were all zero in the nominal run;
- the worker completed in 15.073 seconds for a 15-second source interval;
- the worker connected only to an authenticated Mission Core endpoint through
an SSH reverse tunnel and had no K1 address or K1 transport;
- both sides declared `commands_enabled=false` and
`navigation_or_safety_accepted=false`.
The physical K1 run was not attempted because the unit was not present in the
BLE scan and the only unclassified LAN neighbour rejected both MQTT and RTSP.
No subnet scan or guessed device address was used. The exact same ingress is
already attached to the physical acquisition path and will receive live events
when K1 is next awake.
## Architecture under test
```text
K1 camera RTSP ──> Mission Core FFmpeg ──> durable fMP4 segment commit
│
└─> camera latest-wins queue (2)
K1 MQTT ──> durable raw K1MQTT commit ──> reviewed live callback
├─> LiDAR latest-wins queue (2)
└─> pose bounded queue (16)
separate modality queues ──> one exclusive localhost WebSocket consumer
──> bearer authentication ──> SSH reverse tunnel
──> read-only Docker probe on RTX worker D:
```
The camera observer is invoked only after `CameraArchiveWriter.append()` has
atomically published and fsynced the fragment, its index entry and its crash
checkpoint. The MQTT observer is invoked only by `capture_mqtt`'s
`on_message_recorded` callback after the raw frame has been committed. Derived
perception therefore remains expendable: it cannot become the source of record.
The ingress is process-local and bounded by modality:
| Queue | Capacity | Maximum payload |
|---|---:|---:|
| control | 4 | 16 KiB |
| camera init | 1 | 1 MiB |
| camera frame | 2 | 1 MiB |
| LiDAR | 2 | 2 MiB |
| pose | 16 | 2 MiB |
Camera bursts cannot evict pose or LiDAR observations. A full queue drops its
oldest derived item and retains the latest work; it never waits on the worker.
Only one authenticated worker consumer may hold the stream lease.
## Nominal qualification input
- canonical session: `20260720T065719Z_viewer_live` / `RAVNOVES00`;
- camera source: `sensor.camera.right`, codec epoch 1, 800x600 H.264 fMP4;
- source interval: the first 15 seconds of the canonical camera epoch;
- MQTT input: the matching canonical raw K1MQTT arrival interval;
- source pacing: 1.0x recorded host-arrival time;
- camera index SHA-256:
`e029815a60ad9fbfedb6169142c7449df2b119a51d1ce001f08806e04eb0be14`;
- raw MQTT SHA-256:
`70da0edad5cbf0e89b6e2355c0aac294f8c36a67249cd33b8918353fd88c83af`.
This replay is a transport qualification, not a synthetic message generator.
Every camera segment was rechecked against its durable length and SHA-256 index
record. K1MQTT framing and aligned timing metadata were read through the
existing reviewed replay parser.
## Nominal result
### Event accounting
| Modality | Selected | Edge admitted | Worker verified | Payload bytes |
|---|---:|---:|---:|---:|
| camera init | 1 | 1 | 1 | 763 |
| camera frame | 151 | 151 | 151 | 11,583,362 |
| LiDAR | 142 | 142 | 142 | 3,674,244 |
| pose | 150 | 150 | 150 | 25,346 |
| control | 2 | 2 | 2 | 48 |
| **Total** | **446** | **446** | **446** | **15,283,763** |
- worker first/last ingress sequence: 1/446;
- sequence gaps: 0;
- per-event payload digest mismatches: 0;
- session-end observed: yes;
- worker wall time: 15.072754 s;
- edge publication wall time including orderly disconnect: 15.698036 s.
### Queue behaviour
| Queue | Maximum depth / capacity | Consumed | Dropped | Oversize rejected | Final depth |
|---|---:|---:|---:|---:|---:|
| control | 1 / 4 | 2 | 0 | 0 | 0 |
| camera init | 1 / 1 | 1 | 0 | 0 | 0 |
| camera frame | 2 / 2 | 151 | 0 | 0 | 0 |
| LiDAR | 2 / 2 | 142 | 0 | 0 | 0 |
| pose | 2 / 16 | 150 | 0 | 0 | 0 |
The camera and LiDAR queues reached their configured maximum capacity but did
not overflow: the tunnel/worker consumer drained them within source rate.
## Worker-disconnect negative control
A separate four-second source-paced run intentionally removed the consumer
after approximately one second. The source still admitted its complete selected
interval:
- camera frames: 41/41 admitted;
- LiDAR: 38/38 admitted;
- pose: 40/40 admitted;
- camera init: 1/1 admitted;
- source run completed in 4.147395 seconds;
- the worker saw 23 ordered events and no session-end, as intended.
After disconnect, the bounded derived queues behaved as designed:
| Queue | Consumed | Overflow drops | Retained final depth / capacity |
|---|---:|---:|---:|
| camera frame | 10 | 29 | 2 / 2 |
| LiDAR | 6 | 30 | 2 / 2 |
| pose | 6 | 18 | 16 / 16 |
| control | 1 | 0 | 1 / 4 |
Exact accounting holds for every modality: consumed + dropped + retained equals
published. The consumer failure did not stop, delay or throw into the source
publisher. This is the required fail-open property for raw capture and the
required fail-closed property for derived perception availability.
## Transport and security boundary
- Mission Core remains bound to `127.0.0.1`.
- A private 0600 bearer token is stored under
`.runtime/live-perception/shadow-worker.token`; it is not returned in plugin
state or passed on a remote command line.
- The Mac opens an SSH reverse tunnel using the pinned `mission-gpu` host key.
- The worker receives the token on stdin and connects through
`host.docker.internal` to that tunnel.
- The server permits one consumer lease and immediately releases it on client
disconnect.
- Every binary event contains an explicit schema, bounded header, exact payload
length and SHA-256 digest.
- The worker container was read-only, capability-dropped, `no-new-privileges`,
PID-limited and given only read-only runner plus D-backed report mounts.
- No GPU access was required for this transport-only probe.
- The worker never received a K1 address and never opened MQTT or RTSP.
## Storage and runtime
All task-controlled Windows files remained under `D:\NDC_MISSIONCORE`:
- runner:
`D:\NDC_MISSIONCORE\workspace\mission-core-compute\run_e12_shadow_transport_probe.py`;
- report root:
`D:\NDC_MISSIONCORE\runtime\derived\e12-shadow-transport`.
Windows drive C was not used by this task. D had approximately 375.75 GiB free
before the nominal run and 376.06 GiB at the final read; the report is only a
small JSON document and raw payloads were not retained on the worker.
## Artifact and producer identity
- nominal edge report:
`.runtime/compute-experiments/e12/20260722T065957Z-e12-source.json`,
SHA-256 `36018374bc82b720d6ee50dace9ca1621431a5f90f49bfaf824054ecf5e6dd15`;
- nominal D-worker report:
`D:\NDC_MISSIONCORE\runtime\derived\e12-shadow-transport\20260722T065955Z-e12-shadow-transport.json`,
SHA-256 `4983b6bcb489892170d7dbc8014dc04e4576202a8590394658259dd8d1b42de6`;
- negative edge report:
`.runtime/compute-experiments/e12/20260722T070212Z-e12-source.json`,
SHA-256 `2cc4f45959b4ce58fa2a58e004a6572a26a483b33e458f46a471012bf5abda74`;
- negative worker report:
`.runtime/compute-experiments/e12/local-negative/20260722T070209Z-e12-shadow-transport.json`,
SHA-256 `311e34f3e514f77caf0a36355902b15a2fc22cd861ecbf00a91d00f08a695a63`;
- ingress implementation SHA-256:
`73e9359d926f7004ebe9519e0de4a05e4230922b714058ded9d27eff55ca06c0`;
- authenticated router SHA-256:
`4c409299d99304a0330256113f188c0bca97aadab6f742a9a626c059a91c0ce6`;
- camera raw-first hook SHA-256:
`086adf61d8143587025b85f46a59c9af746eee5656213cc1aa1c80974592d0a9`;
- physical acquisition integration SHA-256:
`a9aaf129f083cfb219767e13765b8575fc62df8e0ed05e6d50cff00c45ef8186`;
- near-live source runner SHA-256:
`9492804d8ecb629cb4a43b7b2d3220484c7b545b2e53dbad09eb8069f680d17d`;
- D-worker transport probe SHA-256 on both hosts:
`0ad67a1eb6935dd111c490128645b0d4345dc86d171dd7c4045ba990287da44d`.
## Accepted scope
- raw-first post-commit camera/MQTT fan-out;
- separate bounded modality queues;
- exclusive authenticated worker lease;
- source-rate camera/LiDAR/pose transport through the real Windows/Docker path;
- per-event integrity and ordered lifecycle;
- worker-disconnect isolation from the source;
- D-only worker persistence;
- zero K1 command authority.
## Not accepted
- a physical live K1 run in this experiment;
- live camera decode on the worker;
- live detector, semantic model or LiDAR fusion execution over this transport;
- cross-host latency based on unsynchronised wall clocks;
- long-duration soak, reconnect/recovery or worker-process restart;
- forest/off-road domain accuracy;
- vehicle-body extrinsics, navigation or safety use.
## Next gate
1. Attach the worker's persistent camera decoder to the camera-init/frame branch.
2. Feed decoded frames to the already accepted capacity-2 10 Hz detector queue.
3. Decode LiDAR/pose into their verified K1 schemas and carry the latest matched
depth epoch into the existing E10 fusion runtime.
4. Run YOLOX/Triton at 10 Hz and EoMT semantics at 2 Hz without writing per-frame
images; publish only transient diagnostic world state and telemetry.
5. Repeat the disconnect negative control with inference loaded.
6. When K1 is awake, run the same profile first for 15 seconds and then for 60
seconds against the physical acquisition path before any longer soak.
@@ -0,0 +1,238 @@
# LAB E13 — class-aware amodal 3D cuboids
Дата прогона: 2026-07-22
Статус: accepted for recorded near-live qualification; not accepted for navigation or safety
Основной результат: `e10-integrated-perception-eaf7427637326e74dbc5fe66aaffcab83d0a6c18c0c45b93fdc2ecff062b94ee`
## 1. Цель
LAB E13 заменяет диагностические p05–p95 оболочки LAB E10/E11 на полные 3D cuboids, пригодные для дальнейшей разработки «глаз» беспилотника. Старый fitter описывал только видимую LiDAR-поверхность: например, у автомобиля получалась длинная, но очень тонкая пластина. E13 должен:
- сохранить реальное LiDAR-свидетельство отдельно от достроенной геометрии;
- получить полный class-aware объём car/truck/bus/person/bicycle/motorcycle;
- привязать нижнюю грань к локальной оценке земли;
- стабилизировать центр, размеры и yaw по `track_id`;
- не публиковать старую оболочку как полноценный 3D object box, если достройка не прошла проверки;
- остаться в ранее принятом near-live бюджете: detector около 10 FPS, semantic около 2 FPS, p95 возраста world state не более 175 ms.
E13 не утверждает, что невидимая часть объекта измерена. Полный cuboid явно маркируется как `class-prior-completed-from-visible-lidar-support`, а исходная измеренная оболочка сохраняется в `observed_cuboid_*`.
## 2. Входные данные
- Сессия: `20260720T065719Z_viewer_live` (`RAVNOVES00`).
- Камера: `sensor.camera.right`, XGRIDS calibration slot `camera_1`.
- Калибровка SHA-256: `05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
- Camera compute job: `recorded-camera-602ac89026ed12978619801d`.
- LiDAR pack: `e10-lidar-pack-5da0396d32a27f9d1ca537cc2e8a371d386078d6f0dc71737b78620992af9625`.
- Кадры: 1000–1600 включительно, 601 кадр.
- Временной интервал общей шкалы: 135.365857292–195.334857292 s.
- Source span: 59.969 s.
- Timeline SHA-256: `06e3514228b488137b92c303cbca171154ede00368783e32107ed2c39c41baa6`.
- Valid-FOV mask: 270,606 из 480,000 пикселей.
- Valid-FOV mask SHA-256: `a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
## 3. Вычислительный контур
- Worker: `<worker-host>`, выполнение только на диске `D:`.
- GPU: NVIDIA GeForce RTX 4090.
- Контейнер: `nvcr.io/nvidia/tritonserver:26.06-py3`.
- Python 3.12.3; NumPy 1.26.4; SciPy 1.16.3; PyTorch 2.13.0+cu130; Transformers 4.57.6.
- Detector: YOLOX-S, COCO-80, Triton, 640×640.
- Detector model SHA-256: `c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`.
- Semantic model: `tue-mps/cityscapes_semantic_eomt_large_1024`, FP16 autocast.
- Semantic model SHA-256: `c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`.
- Source pace: 1.0×.
- Detector queue: latest-wins capacity 2.
- Semantic queue: latest-wins capacity 1; sampling every fifth camera frame.
- Profile SHA-256: `1890d5039d93abea29bd75cf5609264d64b2c0b0fa5b032233fa5e45872776cd`.
- Runner SHA-256: `bf04efa64c3614688742fb28d2da7c428ff70b4ca61a513318cd2440e79ff776`.
- Fusion runtime SHA-256: `9724a4b5af719ea902f02a8a1efdb720e64b5af68b5759367bdc756bdd31b5c9`.
- PowerShell orchestrator SHA-256: `ac0ee42495839773a2de60090ebcb49eee979dc7ee3b8587662a3c59e19a4d03`.
Перед финальным прогоном на `D:` было 375.612 GiB свободно при жёстком floor 360 GiB. После трёх сохранённых A/B-прогонов и публикации текущий объём свободного места составлял 375.317 GiB. Диск `C:` не использовался и не проверялся.
## 4. Алгоритм E13
### 4.1 Измеренная часть
Сохраняется прежний доказанный контур:
1. calibrated KB4 projection LiDAR → camera;
2. nearest-depth-per-rounded-pixel occlusion gate;
3. пересечение 2D track, semantic class mask и LiDAR projection;
4. depth-contiguous cluster;
5. spatial connected cluster;
6. robust oriented p05–p95 envelope как `observed_cuboid`.
### 4.2 Достройка полного объёма
- Для каждого detector label задан nominal/min/max размер и support padding.
- Видимая грань достраивается от сенсора, то есть невидимый объём продолжается за измеренной поверхностью, а не симметрично в обе стороны.
- Для вытянутых классов fitter различает длинную боковую поверхность и короткую фронтальную/заднюю грань. При короткой грани yaw поворачивается на 90° по нормали поверхности.
- При слабой анизотропии ориентация берётся из track history; при отсутствии истории используется sensor-bearing fallback.
- Локальная земля оценивается по нижнему процентилю полного облака в радиусе 2.5 m вокруг объекта. Нереалистичная оценка заменяется ограниченным fallback относительно нижней границы support points.
- Размеры ограничиваются физическими class-bounds. Выход за bound означает rejection, а не расширение коробки до заведомо ложного размера.
- Центр, размер и yaw фильтруются по `track_id`.
- Cuboid должен содержать не менее 75% текущих support points.
- Если temporal smoothing уводит box от текущих точек, фильтр сбрасывается на текущий несглаженный candidate.
- `failure_policy=reject`: если полный cuboid нельзя построить честно, 2D detection и observed support остаются в журнале, но 3D box не публикуется.
### 4.3 Классовые nominal sizes
| Detector label | Nominal L×W×H, m | Допустимый диапазон, m |
|---|---:|---:|
| person | 0.55×0.55×1.72 | 0.35–1.20 × 0.35–1.20 × 1.30–2.30 |
| bicycle | 1.80×0.65×1.50 | 1.20–2.50 × 0.40–1.20 × 1.00–2.20 |
| motorcycle | 2.10×0.80×1.45 | 1.40–3.00 × 0.50–1.40 × 1.00–2.20 |
| car | 4.50×1.85×1.55 | 3.20–5.80 × 1.45–2.40 × 1.20–2.30 |
| truck | 7.00×2.50×3.00 | 4.80–12.50 × 1.80–3.20 × 1.80–4.20 |
| bus | 10.50×2.55×3.20 | 7.00–13.50 × 2.10–3.20 × 2.50–4.20 |
## 5. Итерации A/B
### E13.1 — completion с визуальным fallback
- Result: `e10-integrated-perception-54e660dc90333f5d38b9ad7054e05e32a808e268080220db20d2d2a1141cc2c3`.
- 494 опубликованных 3D boxes: 382 class-prior-completed и 112 fallback visible envelopes.
- World-state p95 age: 97.876 ms.
- Решение отклонено как финальное: интерфейс смешивал полные cuboids и старые короткие оболочки.
### E13.2 — fail-closed и yaw axis hysteresis
- Result: `e10-integrated-perception-b35c3e94e8c8614ad4cf51a5b9a41ebe5a7be856aa1b3cc837b2d9bd6f05e8fe`.
- Только 334 class-prior-completed boxes опубликованы.
- 81 rejection по support coverage и 79 rejection по class-size bounds.
- 90°/innovation resets снижены с 76 до 16.
- World-state p95 age: 100.072 ms.
- Найден дефект: при coverage reset смешивались новый центр/yaw и ранее сглаженный размер.
### E13.3 — полный current-candidate reset
- Result: `e10-integrated-perception-eaf7427637326e74dbc5fe66aaffcab83d0a6c18c0c45b93fdc2ecff062b94ee`.
- 375 class-prior-completed boxes опубликованы.
- Coverage rejections снижены с 81 до 39.
- Class-bound rejections: 81; они сохранены как корректный fail-closed outcome.
- Innovation resets: 15.
- Финальная итерация принята.
## 6. Финальные результаты
### 6.1 Throughput и latency
| Метрика | LAB E10 baseline, тот же clip | LAB E13.3 | Gate |
|---|---:|---:|---:|
| Detector effective FPS | 9.996 | 9.986 | ≥9.5 |
| Detector dropped frames | 0 | 0 | 0 |
| Semantic effective FPS | 2.013 | 2.011 | ≥1.8 |
| Fresh semantic coverage | 99.33% | 99.00% | ≥90% |
| Fused frames | 525 | 523 | ≥450 |
| Published 3D boxes | 760 visible envelopes | 375 completed cuboids | ≥100 |
| Association p50 | 1.391 ms | 1.751 ms | diagnostic |
| Association p95 | 3.858 ms | 5.184 ms | diagnostic |
| World-state age p50 | 52.017 ms | 68.547 ms | diagnostic |
| World-state age p95 | 76.515 ms | 101.829 ms | ≤175 ms |
| Replay wall time | 60.123 s | 60.184 s | about 1.0× |
Добавленная геометрия увеличила p95 association примерно на 1.33 ms. Разница полного world-state age между отдельными запусками также содержит вариативность decode, detector и semantic runtimes; её нельзя целиком приписывать cuboid fitter.
### 6.2 Ресурсы
- Process peak RSS: 2022.719 MiB.
- CUDA peak allocated: 2107.925 MiB.
- CUDA peak reserved: 2840 MiB.
- GPU telemetry mean/max utilization: 63.77% / 79%.
- GPU telemetry mean/max memory used: 13,381 / 13,391 MiB.
- GPU power mean/max: 215.40 / 228.72 W.
- GPU temperature mean/max: 46 / 49 °C.
### 6.3 Геометрия и качество фильтра
- Accepted cuboids: 375.
- По классам: car 267; truck 100; person 6; bicycle 2; motorcycle/bus 0 в accepted set.
- Mean support coverage: 85.82%.
- Mean completion fraction: 92.67%; p50 95.06%; p95 99.46%.
- Средний accepted car box: 4.536×1.891×1.569 m.
- Средний accepted truck box: 7.080×2.500×3.001 m.
- Средний accepted person box: 0.572×0.618×1.720 m.
- Temporal statuses: confirmed 185; tentative 115; reset-support-coverage 60; reset-innovation 15.
- Orientation sources: support-PCA 196; support-face-normal 129; track-history axis disambiguation 48; track-history 1; sensor-bearing fallback 1.
На 256 соседних accepted observations одного track с разрывом не более 0.25 s:
| Temporal metric | Measured visible envelope | Completed E13 cuboid |
|---|---:|---:|
| Median size step | 0.548 m | 0.000 m |
| p95 size step | 2.167 m | 0.152 m |
| Median yaw step | 4.67° | 1.31° |
| p95 yaw step | 58.61° | 22.10° |
Это не является сравнением с ground truth, но подтверждает, что размер и yaw перестали повторять шум каждой отдельной видимой поверхности.
### 6.4 Fail-closed accounting
- `rejected-fewer-than-8-clustered-points`: 1288 vehicle observations.
- `rejected-fewer-than-4-clustered-points`: 66 non-vehicle observations.
- `rejected-no-semantic-lidar-support`: 590.
- `rejected-distance-innovation`: 98.
- `rejected-amodal-completion-required-size-exceeds-class-bound`: 81.
- `rejected-amodal-completion-support-coverage-below-threshold`: 39.
- `rejected-implausible-cuboid`: 12.
Высокое число rejected candidates ожидаемо: E13 сознательно уменьшает визуальный мусор и публикует только полный cuboid, прошедший semantic, clustering, distance, class-size и support-coverage gates.
## 7. Визуальная проверка и публикация
- Результат прошёл `validate_integrated_perception_result` локально.
- Rerun perception overlay: 58,326,068 bytes; API build time 9.778 s; HTTP 200.
- E13 опубликован как newest accepted overlay для `RAVNOVES00`.
- Проверено в Mission Core на общей шкале около 02:27: cloud, trajectory, recorded camera, semantic overlay и полные полупрозрачные 3D cuboids отображаются совместно.
- Принятый вид оставлен открытым в локальном интерфейсе.
## 8. Артефакты
Worker root:
`D:\NDC_MISSIONCORE\runtime\derived\e10-integrated-perception-eaf7427637326e74dbc5fe66aaffcab83d0a6c18c0c45b93fdc2ecff062b94ee`
Local laboratory copy:
`.runtime/compute-experiments/e13/worker-results/e10-integrated-perception-eaf7427637326e74dbc5fe66aaffcab83d0a6c18c0c45b93fdc2ecff062b94ee`
Published overlay source:
`.runtime/compute-experiments/e10/worker-results/e10-integrated-perception-eaf7427637326e74dbc5fe66aaffcab83d0a6c18c0c45b93fdc2ecff062b94ee`
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `result.json` | — | `512f5fcb72d3c10f4d3d6beb081b28dbb65addcd166e7d4a4bdc3f55a63be4c6` |
| `semantic-frames.jsonl` | 55,023 | `43e72dc9f3ec9fc74f4e1c69dc1ba2fd900ac445aa1744f8d5ea2cc2165cd52b` |
| `fusion-frames.jsonl` | 2,096,208 | `ed8256b41c8a56b8d1dd561e6c47479932a913c80e64cf73c4dc761722637acf` |
| `world-state.jsonl` | 1,336,724 | `a418d1b90fddd810b3ec3fa20c6e5bfa14ecaf2f348500f4a9b56a761a2063d0` |
| `transient-perception.npz` | 1,065,071 | `a68e5bd06eee4f2372eacaf0d94326515b94848189e6527d063002398d0c0f56` |
| `gpu-telemetry.jsonl` | 13,704 | `a80cced041a87446cfb9e074a0867ab5f980108f826b347ca5175664d2b9c907` |
| `run-report.json` | 17,870 | `b6e67b710e6a2f9d3132a0293ac1fae54ca688b6a1ebbc42174893388c0ab88d` |
## 9. Ограничения
1. Это recorded source-paced qualification, а не физический live K1 → worker прогон.
2. Host-arrival camera/LiDAR binding не доказывает hardware-clock synchronization.
3. COCO YOLOX и Cityscapes EoMT не являются forest-domain или safety-validated моделями.
4. Средняя доля достроенного объёма 92.67% показывает, что текущие «нормальные» cuboids в основном опираются на class priors. Это инженерный amodal estimate, не измеренная форма объекта.
5. 3D ground truth отсутствует. Нельзя утверждать 3D IoU, heading accuracy, center error или recall относительно истины.
6. Truck detections наследуют ограниченность COCO label и могут включать крупные автомобили, которые в конкретном кадре классифицированы неточно.
7. Current local-ground percentile не заменяет полноценную road/terrain plane model для уклонов, бордюров и лесной поверхности.
8. E13 не имеет navigation/control authority и не принят для safety.
## 10. Решение и следующий gate
E13.3 принят как новый визуальный и world-state baseline вместо visible-surface boxes. Он доказывает, что class-aware full cuboids и temporal stabilization укладываются в near-live бюджет на существующем worker.
Следующий обязательный этап — LAB E14:
1. добавить 3D ground-truth/prelabel contour хотя бы для репрезентативного набора кадров;
2. провести внешний benchmark learned LiDAR/BEV 3D detectors (CenterPoint/PointPillars и camera-LiDAR fusion вариант) на том же K1 point pattern;
3. сравнить learned box с E13 prior-completed baseline по latency, 3D IoU/center/yaw и стабильности;
4. подключить принятый fitter к bounded E12 shadow ingress и проверить disconnect/overflow вместе с inference;
5. после этого выполнить физический live K1 gate без control/navigation authority.
До появления 3D labels E13 следует использовать как честно маркированный near-live amodal baseline, а не как финальное решение 3D detection.
@@ -0,0 +1,276 @@
# LAB E14 — full-session recorded near-live qualification
Дата прогона: 2026-07-22
Статус вычислительного прогона: accepted for recorded near-live qualification
Статус physical live K1: not tested
Navigation/safety acceptance: false
Основной результат: `e10-integrated-perception-9c10dc36640b409393c161f6cbd59b000581b39851ef7a8931898cf8f46d7e8b`
## 1. Цель
LAB E14 переносит принятый в E13 class-aware amodal 3D cuboid contour с минутной выборки на всю доступную запись `RAVNOVES00`. Эксперимент отвечает на практический вопрос: может ли текущий RTX 4090 worker непрерывно обрабатывать полный recorded stream в темпе источника, одновременно выполняя:
- декодирование fisheye-видео;
- YOLOX-S 2D detection на каждом camera frame;
- EoMT semantic segmentation на каждом пятом frame;
- calibrated KB4 camera–LiDAR projection;
- semantic/LiDAR association;
- class-prior amodal 3D cuboid completion и temporal stabilization;
- clearance и world-state publication;
- GPU/runtime telemetry;
- fail-closed accounting без скрытых потерь очередей.
E14 является capacity qualification на записанном источнике. Он не заменяет физический live K1 gate, не подтверждает hardware-clock synchronization и не выдаёт контуру право управлять беспилотником.
## 2. Входные данные
- Сессия: `20260720T065719Z_viewer_live`, операторское имя `RAVNOVES00`.
- Камера: `sensor.camera.right`.
- XGRIDS calibration slot: `camera_1`.
- Калибровка SHA-256: `05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
- Camera compute job: `recorded-camera-602ac89026ed12978619801d`.
- Camera input SHA-256: `602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`.
- LiDAR pack: `e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b`.
- Кадры: 0–4488 включительно, 4489 camera frames.
- Timeline: 35.421857292–484.044857292 s.
- Source span: 448.623 s.
- Timeline SHA-256: `f91dad0a3cc998f4250be794b48f437943b3c60edccbe7e5ceed59030854a807`.
- Разрешение: 800×600.
- Valid-FOV mask: 270,606 из 480,000 пикселей, 56.38% кадра.
- Valid-FOV mask SHA-256: `a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
Valid-FOV mask вычислена заранее из стабильной геометрии объектива и переиспользуется. За пределами полезного круга detector/semantic contour не получает содержательного изображения; маска не пересчитывается для каждого кадра.
## 3. Воспроизводимая конфигурация
### 3.1 Worker и модели
- Worker host: `<worker-host>`.
- GPU: NVIDIA GeForce RTX 4090.
- Worker storage: только `D:\NDC_MISSIONCORE`.
- Диск `C:` не использовался и не проверялся.
- Container image: `nvcr.io/nvidia/tritonserver:26.06-py3`.
- Python 3.12.3; NumPy 1.26.4; SciPy 1.16.3; PyTorch 2.13.0+cu130; Transformers 4.57.6.
- Detector: YOLOX-S, COCO-80, Triton, 640×640.
- Detector model SHA-256: `c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`.
- Semantic: `tue-mps/cityscapes_semantic_eomt_large_1024`, FP16 autocast.
- Semantic model revision: `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`.
- Semantic model SHA-256: `c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`.
### 3.2 Scheduler и профили
- Source pace: 1.0×.
- Detector: каждый frame, latest-wins queue capacity 2.
- Semantic: каждый пятый frame, latest-wins queue capacity 1.
- Semantic TTL: 750 ms.
- Profile: `experiments/perception/worker/e14_full_session_amodal_profile.json`.
- Profile SHA-256: `a010e32070459b123423f3d4f57aa1d839f73ed05f3df8580d12693406a1b218`.
- Detector profile SHA-256: `819bdfb0da521d187ec9ce3d3039a0ff75daa0ab473fb3d8e5fa92db9c1ddca1`.
- Semantic profile SHA-256: `ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875`.
- Runner SHA-256: `bf04efa64c3614688742fb28d2da7c428ff70b4ca61a513318cd2440e79ff776`.
- Fusion runtime SHA-256: `9724a4b5af719ea902f02a8a1efdb720e64b5af68b5759367bdc756bdd31b5c9`.
- PowerShell orchestrator SHA-256: `ac0ee42495839773a2de60090ebcb49eee979dc7ee3b8587662a3c59e19a4d03`.
- Runner generation: `D:\NDC_MISSIONCORE\runtime\derived\e14-runner-bf04efa64c36-ac0ee4249583-a010e3207045`.
### 3.3 Acceptance gates
| Gate | Порог |
|---|---:|
| Detector effective FPS | ≥9.5 |
| Detector drop fraction | ≤0.5% |
| Semantic effective FPS | ≥1.8 |
| Semantic drop fraction | ≤5% |
| Semantic completion age p95 | ≤400 ms |
| Fresh semantic coverage | ≥90% |
| World-state age p95 | ≤175 ms |
| Fused frames | ≥3500 |
| Accepted completed cuboids | ≥1500 |
| Detector/semantic failures | 0 |
## 4. Выполнение и время
- Подготовка worker input/runtime: 31.249 s.
- Source-paced replay внутри runner: 448.699 s.
- Source duration: 448.623 s.
- Replay/source ratio: 1.00017×, то есть вычислительный цикл удержал темп записи.
- Полный PowerShell orchestration wall time: 597.752 s.
Разница между replay и orchestration включает подготовку, запуск/проверку контейнерного контура, сбор и валидацию артефактов, восстановление model state и финальную уборку. Она не является inference latency кадра.
Контур выполнялся параллельно: detector и semantic workers имели отдельные bounded queues; fusion/world-state потреблял detector result на camera cadence и последнюю допустимую semantic result. Медленная semantic модель не блокировала detector loop и не накапливала безразмерную очередь.
## 5. Итоговые near-live метрики
| Метрика | Результат | Gate | Статус |
|---|---:|---:|---|
| Detector frames | 4489/4489 | полное accounting | pass |
| Detector effective FPS | 10.0045 | ≥9.5 | pass |
| Detector drops | 0 | ≤0.5% | pass |
| Detector queue max depth | 1 из 2 | bounded | pass |
| Semantic frames | 898 | every fifth frame | pass |
| Semantic effective FPS | 2.0013 | ≥1.8 | pass |
| Semantic drops | 0 | ≤5% | pass |
| Fresh semantic coverage | 99.7104% | ≥90% | pass |
| Semantic completion age p95 | 261.259 ms | ≤400 ms | pass |
| Fused frames | 3915 | ≥3500 | pass |
| Accepted 3D cuboids | 3656 | ≥1500 | pass |
| World-state age p50 / p95 | 67.439 / 102.883 ms | p95 ≤175 ms | pass |
| Failures | 0 | 0 | pass |
Fusion state accounting:
- `fused`: 3915;
- `depth-unavailable-sync-gate`: 561;
- `semantic-stale`: 8;
- `semantic-unavailable`: 5;
- semantic freshness violations: 0.
## 6. Latency decomposition
| Stage | Mean, ms | p50, ms | p95, ms | Max, ms |
|---|---:|---:|---:|---:|
| Decode | 33.438 | 31.333 | 48.551 | 77.638 |
| Detector | 31.502 | 28.524 | 47.158 | 65.501 |
| Detector queue wait | 0.202 | 0.074 | 0.200 | 23.505 |
| Projection | 0.476 | 0.397 | 0.884 | 12.028 |
| Association + cuboid | 2.575 | 1.964 | 6.834 | 26.413 |
| Clearance | 0.222 | 0.187 | 0.517 | 5.949 |
| World-state serialization | 0.075 | 0.046 | 0.233 | 0.964 |
| End-to-end world-state age | 70.425 | 67.439 | 102.883 | 182.733 |
Semantic processing:
| Stage | Mean, ms | p95, ms | Max, ms |
|---|---:|---:|---:|
| Host → device | 6.869 | 10.309 | 18.386 |
| Forward | 154.711 | 167.953 | 407.725 |
| Model postprocess | 9.825 | 14.110 | 20.840 |
| Full semantic processing | 188.479 | 206.391 | 434.399 |
| Completion age | 224.099 | 261.259 | 467.008 |
## 7. 3D geometry
Accepted class-prior-completed cuboids: 3656.
| Class | Count | Mean L×W×H, m |
|---|---:|---:|
| car | 3118 | 4.514×1.881×1.575 |
| truck | 493 | 7.016×2.514×3.125 |
| person | 31 | 0.622×0.588×1.720 |
| bus | 10 | 10.500×2.631×3.200 |
| bicycle | 2 | 1.800×1.016×1.500 |
| motorcycle | 2 | 2.100×0.874×1.450 |
- Mean support coverage: 86.19%.
- Mean inferred completion fraction: 91.56%.
- Temporal statuses: confirmed 2070; tentative 920; support-coverage reset 574; innovation reset 92.
- Orientation: support-PCA 1890; support-face-normal 1034; track-history axis disambiguation 702; track-history 23; sensor-bearing fallback 7.
Высокая completion fraction означает, что значительная часть полного объёма достроена по class priors из видимой LiDAR-поверхности. Это диагностическая геометрия с измеренным support audit, а не 3D ground truth.
Fail-closed rejections:
- fewer than 8 clustered vehicle points: 10,563;
- no semantic/LiDAR support: 4,884;
- distance innovation: 505;
- required size exceeds class bound: 395;
- support coverage below threshold: 306;
- fewer than 4 clustered non-vehicle points: 184;
- implausible cuboid: 20.
Rejection сохраняет 2D/audit evidence, но не публикует ложный полноценный 3D box.
## 8. Ресурсы и диски
- Process peak RSS: 2964.414 MiB.
- CUDA peak allocated: 2107.925 MiB.
- CUDA peak reserved: 2840 MiB.
- GPU utilization mean/p50/p95/max: 74.59% / 75% / 91% / 98%.
- GPU memory used mean/p50/p95/max: 13,274.69 / 13,273 / 13,282 / 13,284 MiB.
- GPU memory utilization mean/p95/max: 22.67% / 32% / 36%.
- GPU power mean/p95/max: 200.44 / 208.43 / 225.21 W.
- GPU temperature mean/p95/max: 49.09 / 54 / 55 °C.
- Telemetry samples: 449 at 1 s interval.
На `D:` до staging/preparation было приблизительно 389.65 GiB свободно. Runner стартовал с 415,364,739,072 bytes и закончил с 415,598,956,544 bytes при hard floor 386,547,056,640 bytes (360 GiB). После orchestration и уборки оставалось около 387.05 GiB. Временные camera frames удалены оркестратором; model state восстановлен. Диск `C:` не затрагивался.
На локальном Mac опубликованный Rerun overlay занимает 443,421,386 bytes. На момент отчёта локально оставалось около 32 GiB; лишние копии видео не создавались.
## 9. Публикация и интерфейсная проверка
- Результат дважды прошёл строгую локальную `validate_integrated_perception_result`: до и после immutable publication в canonical provider root.
- Full-session perception RRD: 443,421,386 bytes.
- RRD SHA-256: `1dc29900ddeb09897af70c7334755823b02aeaa95f203d979be4bd75fae5080b`.
- Mission Core API вернул HTTP 200 и сохранил immutable result-scoped cache.
- Сервис не перезапускался и оставался healthy во время построения и браузерной проверки.
- В Mission Core подтверждена полная шкала `00:00 / 08:55.718`.
- В режиме `Распознавание` исходная камера и `Сегментация · камера right` синхронно менялись при воспроизведении; это не статичный кадр.
Во время проверки обнаружен presentation defect режима `Кубы 3D`: blueprint имел origin `/world/perception`, поэтому исключал native cloud `/world/points` и показывал cuboids в отдельной пустой сцене. Исправление выполнено в source:
- cuboid view перенесён на origin `/world`;
- native cloud и `/world/perception` объединены в одном latest-at view;
- 12-second accumulation к cuboid view не применяется, поэтому коробки разных кадров не наслаиваются;
- дублирующий серый `/world/perception/lidar` скрыт;
- добавлены regression assertions для initial и dynamic blueprints.
Backend не перезапускался из-за операторского требования никогда не оставлять Mission Core недоступным. Поэтому вычислительный 3D result принят, source fix протестирован, но визуальная runtime-проверка исправленного blueprint остаётся отдельным коротким gate после следующего штатного перезапуска сервиса.
## 10. Артефакты
Worker result root:
`D:\NDC_MISSIONCORE\runtime\derived\e10-integrated-perception-9c10dc36640b409393c161f6cbd59b000581b39851ef7a8931898cf8f46d7e8b`
Local laboratory copy:
`.runtime/compute-experiments/e14/worker-results/e10-integrated-perception-9c10dc36640b409393c161f6cbd59b000581b39851ef7a8931898cf8f46d7e8b`
Published provider root:
`.runtime/compute-experiments/e10/worker-results/e10-integrated-perception-9c10dc36640b409393c161f6cbd59b000581b39851ef7a8931898cf8f46d7e8b`
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `result.json` | 13,799 | `ff129ee1e51e19b514ffb790a145cf17292bd49e9256de2a77a2ca2557e50e68` |
| `run-report.json` | 18,144 | `a85a714215358715120efb057991087f8b214fe6ef86ce2f68124f19287f2a4d` |
| `semantic-frames.jsonl` | 404,398 | `0156bd7450cf6e89fcc3e66831377e87089c6f322a0db382d7135246241cf6b3` |
| `fusion-frames.jsonl` | 17,008,643 | `04c6589c0d5258b47937114c80a1562b6a427456b22e8aedb8cfa86741fe183b` |
| `world-state.jsonl` | 10,699,803 | `5b030270c4ae9029690666cbde7e6191df4129f393a8530dc561b76a68b61f89` |
| `transient-perception.npz` | 8,291,832 | `36677d1b2f4260f821dcf29929dc0dea90b28a4e74ff5c777d320dda7213f659` |
| `gpu-telemetry.jsonl` | 100,917 | `d4fb026b46d25b97b7d35397d598fec23647276af6339c98898cbfea97345601` |
| full-session `perception.rrd` | 443,421,386 | `1dc29900ddeb09897af70c7334755823b02aeaa95f203d979be4bd75fae5080b` |
## 11. Что доказано и что не доказано
Доказано:
1. На RTX 4090 текущий YOLOX + EoMT + calibrated LiDAR fusion + E13 cuboid/world-state contour удерживает полный 448.623-second recorded stream в 1.0×.
2. Detector и semantic queues bounded; за 4489 frames нет ни overflow drops, ни model failures.
3. p95 world-state age 102.883 ms оставляет запас до лабораторного 175 ms gate.
4. Full-session 2D/semantic playback опубликован и работает на общей временной шкале Mission Core.
5. Все 3656 опубликованных 3D boxes прошли class bounds и support gates; rejected candidates не маскируются.
Не доказано:
1. Физический K1 live ingress → decode → inference → fusion на реальном сетевом потоке.
2. Hardware-clock camera/LiDAR synchronization.
3. Устойчивость к реальному disconnect/reconnect при работающем inference.
4. Forest-domain качество COCO/Cityscapes моделей.
5. 3D IoU, center/yaw error и recall относительно ground truth.
6. Navigation/safety authority.
## 12. Следующий gate
Следующий этап не должен улучшать recorded-видео в вакууме. Он переносит доказанный E14 compute contour на уже принятый E12 ingress:
1. подключить persistent fMP4 camera decoder к bounded shadow transport;
2. выполнять detector на 10 Hz и semantic на 2 Hz через latest-wins queues;
3. декодировать LiDAR/pose и публиковать тот же calibrated fusion/world-state contract;
4. вывести live telemetry: source age, decode/inference/fusion age, queue depth, drops, semantic freshness, reconnect generation;
5. выполнить физический K1 shadow gate 15 s;
6. при нулевых gaps/failures выполнить 60 s gate;
7. только после этого увеличивать длительность и проверять disconnect/reconnect;
8. control/navigation output оставить физически отключённым.
Отдельная короткая presentation-проверка после следующего штатного перезапуска Mission Core должна подтвердить, что исправленный `Кубы 3D` показывает latest-at cuboids непосредственно поверх native point cloud.
@@ -0,0 +1,351 @@
# LAB E15 — source-paced live shadow inference qualification
Дата: 22 июля 2026 года
Финальный accepted run: 20:23–20:26 MSK / `2026-07-22T17:26:37Z` source completion
Статус: **accepted для recorded-source-paced shadow diagnostic**
Safety/navigation authority: **не выдана**
## 1. Что проверялось
LAB E15 впервые соединяет принятые ранее части Mission Core в один потоковый контур, близкий к будущему onboard perception loop:
1. bounded Mission Core shadow ingress;
2. инкрементальный decode сохранённого fMP4 без сборки видео целиком и без PNG на диске;
3. YOLOX-S object detection на каждом camera frame;
4. EoMT semantic segmentation на каждом пятом camera frame;
5. nearest-time camera → LiDAR → pose binding;
6. KB4 calibrated camera–LiDAR projection;
7. semantic/LiDAR association;
8. class-prior amodal 3D cuboids;
9. clearance и timestamped world-state;
10. bounded queues, telemetry и fail-closed acceptance.
Это не offline-просчёт «как можно качественнее». Источник отдавал 15 секунд реальной записи в темпе 1.0×, а worker обязан был удерживать camera cadence, bounded memory и ограничение на возраст результата.
## 2. Граница доказательства
Доказано:
- один RTX 4090 worker принимает camera/LiDAR/pose через live shadow wire contract;
- 151 fMP4 fragment декодируется инкрементально без потерь и без временных frame-файлов;
- detector работает практически на camera cadence 10 Hz;
- semantic contour работает примерно на 2 Hz и не блокирует detector;
- LiDAR/pose fusion и 3D cuboid/world-state укладываются в заданный p95 age budget;
- transport, model queues и media buffer остаются bounded;
- disconnect/replay transport не вмешивается в raw recorder;
- весь persistent worker contour и результаты размещены только на `D:`.
Не доказано:
- работа от физически подключённого K1 в этом конкретном E15 run;
- hardware-clock synchronization камеры и LiDAR;
- forest-domain accuracy, recall или safety quality;
- пригодность COCO/Cityscapes labels для автономной навигации;
- vehicle-body transform;
- управление беспилотником или safety authority;
- публикация текущего live world-state в операторский Rerun UI.
Итоговый scope результата: `live-shadow-diagnostic-only`.
## 3. Входные данные и provenance
### 3.1 Источник
- Session: `20260720T065719Z_viewer_live` (`RAVNOVES00`).
- Camera source: `sensor.camera.right`.
- Camera calibration slot: `camera_1`.
- Camera resolution: 800×600.
- Source interval: 14.989973 s.
- Replay pace: 1.0× recorded host arrival time.
- Camera frames: 151.
- LiDAR messages: 142.
- Pose messages: 150.
- Control messages: 2.
- Total wire events: 446.
- Camera index SHA-256: `e029815a60ad9fbfedb6169142c7449df2b119a51d1ce001f08806e04eb0be14`.
- MQTT raw SHA-256: `70da0edad5cbf0e89b6e2355c0aac294f8c36a67249cd33b8918353fd88c83af`.
- Accepted source report: `.runtime/compute-experiments/e12/20260722T172637Z-e12-source.json`.
- Accepted source report SHA-256: `8d36a9715b19fcd820e585629e0774d97ea21dfd5b763fd48e9672e098814e48`.
### 3.2 Калибровка
- Calibration SHA-256: `05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
- Projection model: KB4.
- Projection pack: `e15-live-projection-713fa7344805b7a592d12e949a6db38116e5370f768bde1cf9144fab5f93f0ae`.
- Projection arrays SHA-256: `d37bff4299b3fd6232926ba6e04d3cbd2a5aba0e66bf5ed02e0873340597566c`.
- Pack classification: `calibration-only-no-recorded-sensor-frames`.
- В projection pack нет записанных camera/LiDAR frames: только intrinsics, KB4 distortion и `T_camera_from_lidar`.
### 3.3 Valid-FOV
- Generation: `valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`.
- Mask SHA-256: `a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
- Valid pixels: 270,606 из 480,000.
- Маска вычислена заранее и переиспользуется; область за пределами полезного круга не подаётся моделям как содержательное изображение.
## 4. Software и model configuration
### 4.1 Runtime
- Host: `<worker-host>`.
- GPU: NVIDIA GeForce RTX 4090.
- Container image: `nvcr.io/nvidia/tritonserver:26.06-py3@sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794`.
- Python: 3.12.3.
- NumPy: 1.26.4.
- SciPy: 1.16.3.
- PyTorch: 2.13.0+cu130.
- Transformers: 4.57.6.
- PyAV: 18.0.0.
- lz4: 4.4.5.
- Media runtime payload SHA-256: `67487da59bba27676e5a5b4e43566e956fb350f1730589d93d55f22bcddc4c6b`.
### 4.2 Models
Detector:
- YOLOX-S, COCO-80, Triton inference;
- input 1×3×640×640;
- model SHA-256: `c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- config SHA-256: `5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604`.
Semantic:
- `tue-mps/cityscapes_semantic_eomt_large_1024`;
- revision `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`;
- FP16 autocast;
- model SHA-256: `c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`.
### 4.3 Immutable code/config identities
- Accepted live profile SHA-256: `8b7f267ee947932bdd52d1ef4b241adc8c9b4c4d5a8a3cd6c1e7f6e066983699`.
- E14 profile SHA-256: `a010e32070459b123423f3d4f57aa1d839f73ed05f3df8580d12693406a1b218`.
- Detector profile SHA-256: `819bdfb0da521d187ec9ce3d3039a0ff75daa0ab473fb3d8e5fa92db9c1ddca1`.
- Semantic profile SHA-256: `ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875`.
- Runner SHA-256: `8e487108404d912739b99d0043b8cbdd605ae148d8ad1d32ab30819ecfbb7dbc`.
- fMP4 runtime SHA-256: `6785763350f6e2c00796f3580ea6707e0c44339f18fa8eb56687178c6f0403fc`.
- PowerShell orchestrator SHA-256: `129bb6874730191cece10667b580fe5b8aa4e0878d442779679977079aa6069e`.
- Minimal worker package: `e15-worker-package-dd67306b6116dd6fa091557371eb80b27afb48dae4568fb0173f253a1fb4613a`.
- Minimal worker package identity SHA-256: `dd67306b6116dd6fa091557371eb80b27afb48dae4568fb0173f253a1fb4613a`.
- Package содержит 11 runtime modules и не тянет MQTT/BLE/web/device lifecycle в GPU-контейнер.
## 5. Scheduler и bounded-state contract
- Detector queue: latest-wins, capacity 2.
- Semantic queue: latest-wins, capacity 1.
- Semantic sample: каждый пятый camera frame.
- Semantic TTL: 750 ms.
- Sensor buffers: 32 LiDAR + 32 pose records максимум.
- Sensor retention: 3 s.
- Camera metadata queue: 16 максимум.
- Incremental media buffer: 8 MiB максимум.
- Camera → LiDAR maximum absolute delta: 100 ms.
- LiDAR → pose maximum absolute delta: 100 ms.
- Accepted sensor wait budget: 90 ms.
- Команды: disabled.
- Navigation/safety acceptance: false.
Камера не блокируется semantic model. Decoder публикует frame в две bounded очереди. Detector идёт на camera cadence; semantic worker независимо обновляет latest semantic mask. Detector result связывается с latest fresh semantic result и ближайшей допустимой LiDAR/pose парой.
## 6. Экспериментальные попытки
### 6.1 Attempt A — 20 ms sensor wait, rejected
Result ID: `e15-shadow-inference-396152240d878551ffe4913e03aee95be0b0ec572b69d76313edd73a7ee0cf7d`.
Все transport, decoder, FPS, semantic, latency, queue и authority checks прошли. Единственный failed check:
- fusion coverage 127/151 = 84.106%; требование ≥85%.
Причина:
- 19 camera frames получили `lidar-camera-delta-exceeded`;
- для девяти из этих frames следующий LiDAR находился всего в 49–88 ms после camera timestamp;
- wait budget 20 ms завершал binding раньше, чем этот LiDAR был декодирован и опубликован;
- расширять temporal gate более 100 ms не потребовалось.
### 6.2 Infrastructure interruption — не считается lab attempt
Один повтор был остановлен idle SSH timeout во время model preflight. Consumer к source не подключился, события не публиковались, immutable result не создавался. После проверки оставались только штатные `mission-core-triton`, `sentinel-frigate`, `sentinel-ollama`. Повтор выполнен с временными SSH `ServerAlive` options без изменения сетевой/VPN конфигурации.
### 6.3 Attempt B — 90 ms sensor wait, accepted
Result ID: `e15-shadow-inference-f3f06e7c6b338add445f7ee4e4a81f831cd763a899ac2fbe29ba3476c4bdf0c8`.
Изменён только `sensor_wait_ms`: 20 → 90. Temporal gate остался 100 ms, models и все acceptance thresholds не менялись.
Результат:
- fused frames: 136/151 = 90.066%;
- `lidar-camera-delta-exceeded`: 19 → 10;
- accepted cuboid states: 329 → 355;
- p95 world-state age: 79.28 → 149.43 ms;
- лимит p95 world-state age: 200 ms;
- итог: accepted.
Это осознанный обмен дополнительного bounded ожидания на более полное depth coverage без допуска LiDAR старше 100 ms.
## 7. Acceptance matrix финального run
| Gate | Требование | Результат | State |
|---|---:|---:|---|
| Camera frames | ≥140 | 151 | pass |
| Camera decode accounting | 1:1 | 151/151 | pass |
| Detector effective FPS | ≥9.5 | 9.881 | pass |
| Detector drops | ≤1% | 0/151 | pass |
| Semantic effective FPS | ≥1.8 | 2.029 | pass |
| Semantic drops | ≤5% | 0/31 | pass |
| Semantic completion age p95 | ≤400 ms | 162.162 ms | pass |
| Fresh semantic coverage | ≥90% | 96.689% | pass |
| Fused fraction | ≥85% | 90.066% | pass |
| Decode age p95 | ≤80 ms | 15.150 ms | pass |
| World-state age p95 | ≤200 ms | 149.427 ms | pass |
| Ingress sequence gaps | 0 | 0 | pass |
| Camera sequence gaps | 0 | 0 | pass |
| Failures | 0 | 0 | pass |
| Session-end | required | seen | pass |
| Commands/navigation authority | false | false | pass |
Все 18 acceptance checks прошли.
## 8. Производительность финального run
### 8.1 End-to-end
- Source span: 14.989973 s.
- Measured run wall: 15.281886 s.
- Detector: 151 frames, 9.881 FPS.
- Semantic: 31 frames, 2.029 FPS.
- Fused: 136 frames.
- Source-paced factor: около 1.019× wall/source, то есть контур удержал темп источника.
- Full orchestrator wall including isolated preflight, second model load, warm-up, run and publication: 113.669 s.
Большой orchestrator wall — cold-start overhead лабораторного запуска, а не latency активного perception loop. Для onboard-контура модели должны жить в постоянном worker process и не загружаться дважды перед каждой сессией.
### 8.2 Latency, ms
| Stage | mean | p50 | p95 | max |
|---|---:|---:|---:|---:|
| fMP4 decode age | 8.003 | 6.428 | 15.150 | 41.297 |
| Detector queue wait | 8.577 | 0.086 | 47.431 | 165.393 |
| Detector | 25.607 | 23.899 | 36.631 | 43.492 |
| Sensor wait | 10.075 | 0.033 | 90.136 | 110.995 |
| KB4 projection | 0.538 | 0.372 | 0.838 | 12.403 |
| Association/cuboid | 5.297 | 4.100 | 14.197 | 55.061 |
| Clearance | 0.204 | 0.177 | 0.413 | 0.877 |
| World-state projection | 0.154 | 0.131 | 0.358 | 0.748 |
| End-to-end world-state age | 58.154 | 43.815 | 149.427 | 282.994 |
World-state health:
- healthy: 136 frames;
- degraded: 13 frames;
- stale: 2 frames;
- unavailable: 0 frames.
Два max-latency spikes выше 200 ms не нарушили заданный p95 gate, но подтверждают, что результат пока не имеет safety authority.
### 8.3 Semantic timing, ms
| Stage | mean | p50 | p95 | max |
|---|---:|---:|---:|---:|
| Total processing | 135.524 | 126.185 | 147.835 | 396.316 |
| Forward | 107.197 | 99.099 | 109.701 | 369.566 |
| Completion age | 146.979 | 137.378 | 162.162 | 437.821 |
Semantic fresh status был доступен для 146/151 detector frames. Первые пять frames корректно помечены `semantic-unavailable`, а не замаскированы фиктивным результатом.
### 8.4 Sensor decode
- LiDAR decode mean/p95/max: 12.373 / 12.913 / 97.968 ms.
- Pose decode mean/p95/max: 0.077 / 0.103 / 0.186 ms.
- Synchronizer maximum stored depth: 32 point frames + 32 pose frames.
- Media buffer maximum actual depth: 178,687 bytes из 8,388,608 bytes.
- Camera metadata maximum depth: 2 из 16.
## 9. Functional output
- 1,188 detector/fusion object records рассмотрено за 151 frames.
- 355 accepted amodal cuboid states опубликовано.
- Frames с хотя бы одним accepted 3D object: 130/151.
- Maximum simultaneous accepted objects: 7.
- Accepted class states: 350 vehicle, 5 person.
- Cuboids являются temporal object states, а не 355 уникальными физическими объектами.
- Ground-truth quality в E15 не измерялась.
Основные rejection classes:
- fewer than 8 clustered points: 572;
- no semantic/LiDAR support: 173;
- amodal support coverage below threshold: 72;
- amodal required size exceeds class bound: 7;
- fewer than 4 clustered points: 6;
- distance innovation: 3.
Эти отказы fail-closed: сомнительная геометрия не превращается в принятый 3D cuboid.
## 10. Hardware и storage
Accepted run:
- process peak RSS: 2,023.5 MiB;
- process CUDA peak allocated: 2,107.9 MiB;
- process CUDA peak reserved: 2,840 MiB;
- total GPU memory used telemetry: около 13,298 MiB, включая соседний Triton contour;
- GPU utilization mean/p95/max: 27.4% / 45% / 58%;
- GPU memory-utilization mean/p95/max: 11.8% / 20% / 25%;
- GPU power mean/p95/max: 140.5 / 144.65 / 145.41 W;
- GPU temperature mean/p95/max: 44.6 / 47 / 48 °C.
Storage:
- hard free-space floor: 360 GiB;
- observed final free space: около 389.34 GiB;
- accepted result payload: около 1.56 MiB;
- no per-frame PNG/JPEG output;
- no recorded video copy in result;
- disk `C:` не проверялся и не использовался;
- persistent inputs, runtime и results находятся на `D:`.
## 11. Accepted artifacts
Local audit mirror:
`.runtime/compute-experiments/e15/results/e15-shadow-inference-f3f06e7c6b338add445f7ee4e4a81f831cd763a899ac2fbe29ba3476c4bdf0c8`
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `fusion-frames.jsonl` | 1,016,064 | `ae510afe881d7d6c5bc94520c5f87c385fdeb984bd30bd0cff70bd103f718963` |
| `world-state.jsonl` | 568,384 | `579266c99ae1e65b0240e8c5c224beba4073d05c531c5a725f096e9bbf9a3c05` |
| `semantic-frames.jsonl` | 13,955 | `21a8a659cc19e2acc9b971b93b746a4e26f8bb51829d1e1d13c4f42c1f79f1a5` |
| `gpu-telemetry.jsonl` | 3,591 | `51024d0774dd7e648ca4ae0cba69cca4de70baf8e47c425fa78c6805c2824712` |
| `run-report.json` | 13,230 | `f04fce80d9246d3c19f7368d6174787eb2fd73cdc7c51fa8d18555a0bfa0bf65` |
| `result.json` | 6,384 | `5b933109eedba02483ad35c65221d344da19f7e2584bd700d9be7c6b2ecef995` |
Rejected Attempt A также сохранён неизменяемо:
`.runtime/compute-experiments/e15/results/e15-shadow-inference-396152240d878551ffe4913e03aee95be0b0ec572b69d76313edd73a7ee0cf7d`
## 12. Вывод
На текущем RTX 4090 доказан не просто offline pipeline, а bounded source-paced perception loop на 15-секундном recorded replay:
- camera 10 Hz;
- detector около 10 Hz;
- semantic около 2 Hz;
- calibrated LiDAR/pose fusion;
- diagnostic 3D cuboids и world-state;
- p95 результата 149.4 ms;
- нулевые transport/model queue drops;
- 90.1% fused coverage;
- никакой command authority.
Следующий правильный gate — не дальнейшее улучшение recorded-video качества, а тот же профиль на физическом K1 shadow stream. Параллельно нужно убрать лабораторный cold start, превратив model-loaded contour в постоянный worker service. Только после повторяемого physical live gate имеет смысл подключать live world-state/3D cuboids к Rerun UI и отдельно начинать forest-domain evaluation и safety policy.
## 13. Следующие действия
1. Поднять persistent model-loaded E15 worker на `D:` без двойной загрузки моделей на каждую сессию.
2. Подключить физический K1 через уже существующий shadow ingress без command path.
3. Повторить ≥15 s physical shadow gate с теми же thresholds и отдельным LAB E16 report.
4. Добавить reconnect/worker-loss negative control: raw recorder обязан продолжить запись.
5. Передавать accepted `world-state` и cuboids в live Rerun entities с source/result timestamps и health.
6. После live transport qualification собирать forest-domain ground truth и считать 2D segmentation/object, distance и 3D cuboid quality отдельно от throughput.
@@ -0,0 +1,148 @@
# LAB E16 — Persistent AI worker and unified live perception publication
Date: 2026-07-22
Acceptance time: 2026-07-22 21:23 MSK / 2026-07-22T18:23:49Z
## Purpose
LAB E16 validates the infrastructure between the previously accepted E15 perception
pipeline and one unified Mission Core Rerun scene. This is an infrastructure
acceptance, not a physical K1 perception acceptance. No device, acquisition or camera
session was active during this lab.
The target operator contract is one original-video timeline with independently
switchable derived layers:
- tracked 2D detections with confidence and metric distance when fusion provides it;
- semantic segmentation on the original camera image;
- accepted, calibrated 3D cuboids in the LiDAR/world scene.
## Safety and authority
- The production Mission Core process at `127.0.0.1:8000` was not stopped or restarted.
- UI and backend acceptance used an isolated shadow process at `127.0.0.1:8001`.
- The AI worker ran only on the Windows worker's `D:` drive.
- VPN, Hidemyname, Wi-Fi and host network configuration were not modified.
- Worker authority remained `commands_enabled=false` and
`navigation_or_safety_accepted=false`.
- The result direction is diagnostic-only and contains no command endpoint.
## Immutable worker inputs
- Runner SHA-256:
`f2f2889e07c609e3ba53b4504c3f2417f151fbb9920828f4680ea375a5d3010e`.
- Persistent-service orchestrator SHA-256:
`ed2dc35de423d0d9f8ac0501db283a10081e78b6535ebeb8d521a58ca6988b11`.
- Persistent-run orchestrator SHA-256:
`1c6af15736396ee0124d4aabe309b7bc24bb1425d88e24295c5675c686fc27bc`.
- Worker package:
`e15-worker-package-34fb22ce506aab6e3b6a0fd8058c868e24076a8cfc739b4299b2474f2cb92b99`.
- Runner root on the worker:
`D:\NDC_MISSIONCORE\runtime\derived\e16-runner-f2f2889e07c6-ed2dc35de423-1c6af1573639-34fb22ce506a`.
- Worker package root:
`D:\NDC_MISSIONCORE\runtime\inputs\e16\worker-packages\e15-worker-package-34fb22ce506aab6e3b6a0fd8058c868e24076a8cfc739b4299b2474f2cb92b99`.
- Container: `mission-core-perception-worker`.
- Pinned base image: `nvcr.io/nvidia/tritonserver:26.06-py3` with the E15 digest.
## Persistent model-loaded worker result
- GPU: NVIDIA GeForce RTX 4090.
- Model load and warm-up: 38.296654 s.
- Health: `ready`.
- Models loaded: true.
- Completed physical runs: 0.
- Failed physical runs: 0.
- D: free after startup: 418,065,694,720 bytes, approximately 389.354 GiB.
- Guarded D: floor: 360 GiB plus a 512 MiB startup reserve.
The service binds its control HTTP endpoint only to `127.0.0.1` inside the worker
container's shared Triton network namespace. It has no exposed LAN control port. A run
request is single-flight, authenticated by the existing shadow token, limited to one
direct child of the guarded D:-backed publication root and reuses already loaded models.
## Result transport contract
The authenticated K1 shadow WebSocket remains the sensor ingress and now has one
bounded worker-to-Mission-Core diagnostic result direction.
- Result schema: `missioncore.live-perception-result-wire/v1`.
- Result queue: latest-wins, capacity 2.
- Maximum result payload: 2 MiB.
- Maximum JSON header: 256 KiB.
- Maximum original JPEG: 1 MiB.
- Segmentation: fixed 800x600 uint8 mask compressed with zlib.
- Maximum objects per frame: 128.
- Payload SHA-256, lengths, geometry, timestamps and authority are validated before
the frame is admitted to the visualization runtime.
- Partial cuboids, non-finite geometry, invalid JPEG/mask payloads and any authority
claim are rejected.
## Unified Rerun publication
Accepted live results use the same timeline and stable entities:
- `/perception/camera/image` — original worker-decoded camera frame;
- `/perception/camera/detections` — 2D boxes, track id, class, confidence and distance;
- `/perception/camera/segmentation` — semantic mask;
- `/world/perception/boxes3d` — calibrated accepted cuboids.
The live blueprint switches between the normal 3D-only scene and a single horizontal
original-video plus 3D-world composition. The three AI controls are independent; they
are not mutually exclusive views. When dynamic perception is active, Rerun uses
latest-at presentation so historical cuboids do not stack into a cloud of boxes.
## Validation
- Complete Python test suite: passed.
- Focused live result, WebSocket return, visualization runtime and Rerun bridge tests:
passed.
- Ruff on the changed Python surface: passed.
- Frontend TypeScript build: passed.
- Frontend unit suite: 144/144 passed.
- Production frontend build: passed.
- Windows PowerShell parser accepted both service and run orchestrators.
- In-container encode/decode probe returned a 600x800 segmentation mask successfully.
- Shadow backend health at `127.0.0.1:8001`: healthy.
- Production backend health at `127.0.0.1:8000`: healthy and uninterrupted.
- Browser QA opened RAVNOVES00, reached Rerun ready state, enabled 2D,
segmentation and 3D cuboids simultaneously, and reported zero browser errors.
## Disk handling
The earlier 418 MiB temporary recorded-perception RRD used for payload inspection was
removed after validation. It was a regenerable temporary duplicate, not source evidence.
No source recording or user data was deleted.
## Accepted conclusions
1. The models can remain loaded between runs; the previous double-load startup cost is
removed from subsequent run requests.
2. Mission Core has a bounded, authenticated, non-authoritative return path for live AI
presentation.
3. Original video, 2D detections, segmentation and 3D cuboids now share one Rerun
timeline and one operator composition.
4. Layer visibility is independently controlled and no longer modeled as separate
perception streams.
## Explicitly not accepted yet
- Physical K1 shadow inference: no device session was active.
- End-to-end detector, segmentation and fusion FPS through the new persistent service:
requires the next physical 20-second K1 gate.
- The new backend code is intentionally not active on production port 8000 yet because
that process was not restarted. Port 8001 is the accepted shadow build.
- 3D semantic point coloring is not yet returned by the live result frame; E16 covers
camera segmentation and 3D cuboids.
- The recorded full-session perception RRD remains a large monolith and is not the
target real-time transport.
- None of the outputs are ground truth or safety/navigation accepted.
## Next gate
1. Connect K1 and establish a normal Mission Core device/acquisition/camera session.
2. Run one 20-second persistent physical shadow request without command authority.
3. Record detector FPS, semantic FPS, fusion fraction, result age, result payload rate,
GPU telemetry, queue drops and D: free space.
4. Verify the same four entities visually in the live Rerun scene.
5. Add bounded semantic LiDAR point coloring, then repeat as LAB E17.
@@ -0,0 +1,297 @@
# LAB E3 — K1 rectified segmentation baseline
Date: 2026-07-20
Completed at: 2026-07-20T18:57:23.215Z
State: completed, model draft, not ground truth
Ops card: MISSIONCOR-17
## Objective
Compare an adult Cityscapes semantic model on the same immutable XGRIDS K1
evaluation pack under three input profiles:
1. valid-FOV fisheye image;
2. valid-FOV fisheye image with CLAHE;
3. factory-calibrated KB4 five-view perspective rectification with CLAHE and
fusion back into the original fisheye coordinate system.
The experiment answers whether calibration-derived rectification is technically
valid, how much it costs, and whether it is a credible next baseline. It does not
measure accuracy because no reviewed ground truth exists yet.
## Immutable inputs and provenance
- Evaluation pack:
`evaluation-pack-7a983bba75d46c7c260252cb2d461e1384dcb92cda9e164397e841e6ebb37789`
- Session: `20260720T065719Z_viewer_live`
- Frames: 64, image IDs 1–64, resolution 800×600
- Source: `sensor.camera.right`
- Factory calibration slot: `camera_1`
- Factory calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Valid-FOV generation:
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`
- Valid pixels per frame: 270,606
- LAB E2 comparison draft:
`evaluation-prelabels-4ba26bbf6eb8a49631f5caf984267e0445958540aeda2b5b0d82ca6440835cf1`
The LAB E2 draft has a legacy provenance inconsistency. Its declared identity
SHA-256 is `4ba26b…`, while a fresh canonical hash of the stored identity document
is `33292c85eb8281d68c1b444baaf0d7931c7aa84938ac1d329b89e93f3b525099`.
LAB E3 did not alter LAB E2. It independently verified the evaluation-pack
binding, the declared result/directory relationship, and the size and SHA-256 of
every one of the 64 consumed semantic artifacts. Both hashes and
`e2_prelabel_identity_consistent=false` are preserved in the LAB E3 identity.
## Calibration and preprocessing configuration
Camera-1 intrinsics (`fx, fy, cx, cy`):
```text
194.59817287616025
194.57531427932872
396.31861150187996
301.49644357408005
```
Kannala–Brandt KB4 coefficients:
```text
-0.023164451386679667
-0.0014974198594105452
-0.001039213149441563
-0.000035237331915978814
```
Rectification profile:
- projection: five perspective gnomonic views;
- views: front, left, right, up, down;
- yaw/pitch: `0/0`, `-90/0`, `90/0`, `0/90`, `0/-90` degrees;
- tile size: 768×768;
- horizontal and vertical FOV: 100 degrees;
- RGB interpolation: OpenCV linear remap;
- fused-label sampling: nearest pixel;
- overlap winner: maximum optical-axis cosine;
- contrast: CLAHE on LAB luminance, clip limit 2.0, grid 8×8.
Measured geometry coverage:
- valid pixels covered: 270,606 of 270,606;
- coverage: 100%;
- views covering one valid pixel: minimum 1, maximum 3, mean 1.190099259;
- uncovered valid pixels: 0.
## Model and software identity
- Model: `tue-mps/cityscapes_semantic_eomt_large_1024`
- Architecture: `EomtForUniversalSegmentation`
- Revision: `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`
- Precision: FP16 autocast
- Batch size: 1
- Model weights: 1,276,175,488 bytes, SHA-256
`c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`
- Config SHA-256:
`7f4aa94fa4e43c0dbd79a5420edb511120aef62bd82bfbcbcece79948286a650`
- Preprocessor SHA-256:
`97e2fbf7f0bdba2cfc90251c5133bae9c27ddc9c4410509f40670be2332854e7`
- OpenCV distribution: `opencv-python-headless==4.13.0.92`
- OpenCV runtime module: `4.13.0`
- Transformers: `4.57.6`
- PyTorch: `2.13.0+cu130`
- CUDA runtime: `13.0`
- Container: existing `nvcr.io/nvidia/tritonserver:26.06-py3`; no image pull
- Profile SHA-256:
`ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875`
- Dependency identity SHA-256:
`4eb1f8d33236806e74f9e5bb96b7dce2ac37623dc39b2184be2aa8d7d00e983e`
- Final runner SHA-256:
`01881862d4eaa218955f776a948124bf19c34be2b5ec282115daeacb15c53ae6`
The Cityscapes labels were mapped into the existing Mission Core 0–15
perception taxonomy. Outputs outside the admitted K1 valid FOV were forced to
category 0.
## Execution topology
The comparison was deliberately sequential so each profile has isolated timing:
```text
frame
-> EoMT on fisheye mask
-> CLAHE -> EoMT on fisheye mask
-> KB4 remap of five views -> CLAHE -> five sequential EoMT calls
-> nearest-label fusion into the raw fisheye coordinates
-> masks, preview, metrics, and telemetry
```
There were seven model evaluations per frame and 448 evaluations for the full
pack. No software GPU-utilization or power cap was applied. The experiment did
not batch or parallelize the five rectified views; that is a future optimization,
not part of the present baseline.
## Full-run results
Immutable result:
`e3-segmentation-01bd497c44c2b940add145ec784d3418010327bce0baddd4420b0925317e8a16`
| Variant | Mean end-to-end/frame | Mean forward | Approx. standalone throughput | Mean change vs E2 draft |
|---|---:|---:|---:|---:|
| EoMT fisheye mask | 121.02 ms | 98.64 ms | 8.26 FPS | 16.44% pixels |
| EoMT fisheye + CLAHE | 127.41 ms | 99.10 ms | 7.85 FPS | 16.51% pixels |
| EoMT KB4 cubemap5 + CLAHE | 341.97 ms | 267.39 ms across 5 views | 2.92 FPS | 18.13% pixels |
Additional timing facts:
- full comparison wall time: 46.170785 seconds;
- average full comparison time: 721.42 ms/frame including all three variants,
preview/mask encoding, metrics, and telemetry;
- five-view rectification preprocessing: 22.60 ms/frame mean;
- five-view label fusion: 3.03 ms/frame mean;
- one rectified tile forward: 53.48 ms mean, 65.85 ms p95;
- raw-vs-cubemap pixel disagreement: 8.24% mean, 5.80% median, 24.66% p95,
36.76% maximum.
The largest raw/cubemap difference occurs at image 59 / frame 4224. The
near-structure temporal clip (images 60–63, frames 4433–4436) contributes four
of the next five largest differences. This is the correct area to prioritize in
human review: extreme distortion and nearby planar structures are precisely
where projection choice changes the model most.
## GPU, CPU, memory, and disk
Worker GPU: NVIDIA GeForce RTX 4090, compute capability 8.9.
- process CUDA peak allocated: 2,099.8 MiB;
- process CUDA peak reserved: 2,840.0 MiB;
- total board memory used, including pre-existing services: 13,406 MiB max,
13,396.9 MiB mean;
- GPU utilization: 72.34% mean, 91% max;
- GPU memory-controller utilization: 27.38% mean, 41% max;
- GPU power: 288.88 W mean, 334.09 W max;
- GPU temperature: 55.94°C mean, 61°C max;
- process peak RSS: 2,097.14 MiB;
- GPU telemetry samples: 47 at one-second intervals.
All task-controlled persistent artifacts are on `D:\NDC_MISSIONCORE`:
| Artifact group | Bytes | MiB |
|---|---:|---:|
| Pinned E3 model cache | 1,276,294,838 | 1,217.17 |
| Pinned OpenCV environment | 211,776,082 | 201.97 |
| Four-frame pilot | 2,040,795 | 1.95 |
| Full 64-frame result | 7,924,484 | 7.56 |
| Total | 1,498,036,199 | 1,428.64 |
Final D: free space was 414,903,472,128 bytes (386.409 GiB), 26.409 GiB above
the enforced 360 GiB floor. No task-controlled C: path was created or mounted.
The report does not claim to measure incidental internal writes by Windows or an
already configured Docker Desktop engine.
## Visual findings
1. The factory KB4 geometry is usable now. The five views are upright, have the
expected content, and fuse back without missing valid pixels.
2. EoMT produces visibly cleaner road, grass, building, person, and vehicle
regions than the LAB E2 BEiT draft on the inspected samples.
3. CLAHE changes the global result very little while adding about 6.4 ms/frame.
It is not justified as the default from this unreviewed proxy alone.
4. Cubemap5 often improves separation of people, cars, and rectified planar
surfaces, especially where raw fisheye distortion is severe.
5. Hard winner-take-all fusion creates local seam/checker artifacts, most visible
near the lower rim, the sun/sky, and close structures. Cubemap5 should not be
promoted to the default without seam-aware fusion and reviewed accuracy.
6. Cityscapes has no explicit dirt, animal, or general other-background output in
this mapping. That limits this model as a complete forest/off-road taxonomy.
## Integrity and acceptance status
- 211 of 211 listed output artifacts were rehashed successfully after transfer.
- 192 of 192 semantic masks were checked.
- Every semantic value was within categories 0–15.
- Every pixel outside the valid-FOV mask was category 0.
- Four-frame pilot and full-run outputs are immutable and separately identified.
- Outputs remain `ground_truth=false`.
- No mIoU, AP, distance accuracy, 3D geometry, tracking, or safety claim is made.
## Engineering gates encountered
The following preflight failures happened before a result was accepted and are
preserved as part of the laboratory history:
- Git revision and SHA-256 were initially treated as the same hash shape;
- the OpenCV package version `4.13.0.92` differs from runtime `cv2` version
`4.13.0`;
- Hugging Face snapshot files are symlinks into a content-addressed blob store;
- the initially recorded weight SHA was replaced by the independently measured
blob SHA-256 `c265…`;
- EoMT processor output includes a non-tensor `task_inputs` field;
- LAB E2 contains the identity inconsistency documented above;
- a PowerShell full-run expected-frame lookup initially read the wrong JSON
level. That technical run was stopped, its exact transient container was
removed, its staging was cleaned, and no result was published.
These were fail-closed contract checks, not model failures.
## Decision and next gate
The current evidence supports this decision:
- keep plain EoMT fisheye-mask as the speed/quality control baseline;
- keep cubemap5 as the calibration-derived challenger;
- do not make CLAHE the default yet;
- do not select a production winner from proxy disagreement or visual inspection;
- review the high-disagreement and temporal frames in CVAT, export reviewed masks,
and calculate class-wise IoU, boundary IoU, temporal stability, and object-class
recall before changing the default pipeline;
- if cubemap5 survives reviewed accuracy, replace hard view selection with
overlap-aware/logit fusion and then test batched five-view inference.
3D cuboids, LiDAR association, distance accuracy, and point-cloud segmentation
remain outside LAB E3 and should use the reviewed 2D baseline as an input to the
next dedicated experiments rather than being mixed into this result.
## CVAT review handoff
The control and challenger were converted into deterministic CVAT Segmentation
Mask 1.1 packages and imported into the existing D-hosted CVAT v2.70.0 instance.
The original 64-frame image archive is reused and was not duplicated.
- Workspace:
`e3-cvat-review-ca599521e345446ca8e9af5a9013062099278e7317f83ff89740c6b092ddc52f`
- Workspace manifest SHA-256:
`5e1b318d31431c2081c169235ec877693591ef2b05bbebab61c72061bc1621d9`
- CVAT import report SHA-256:
`fe0933efc3a5bb1bcb7b995bb1241b308e7a860113cd7a97351aedfbd43c9b64`
- [Task 3 — EoMT fisheye control](http://localhost:18080/tasks/3): 64 frames,
5,284 imported shapes.
- [Task 4 — EoMT KB4 cubemap5 challenger](http://localhost:18080/tasks/4): 64
frames, 5,762 imported shapes.
- Both task pages returned HTTP 200 through the existing SSH tunnel.
- Both tasks remain model drafts and `ground_truth=false`.
- Review priority by measured raw/cubemap disagreement: image IDs
`59, 63, 62, 61, 60, 64, 50, 30, 17, 29`.
- Review bundle size on D: 1,390,051 bytes.
- Post-import D: free space: 414,762,061,824 bytes (386.277 GiB), 26.277 GiB
above the enforced floor.
The required human sequence is still two passes: correction/completeness first,
then independent consistency review. Importing the drafts does not complete that
gate and does not convert either task into ground truth.
## Durable paths
Worker full result:
```text
D:\NDC_MISSIONCORE\runtime\derived\e3-segmentation\e3-segmentation-01bd497c44c2b940add145ec784d3418010327bce0baddd4420b0925317e8a16
```
Local evidence copy:
```text
.runtime/compute-experiments/e3/full/e3-segmentation-01bd497c44c2b940add145ec784d3418010327bce0baddd4420b0925317e8a16
```
The `result.json`, `run-report.json`, 16 comparison previews, rectification
atlas, 192 semantic masks, and GPU telemetry are contained in that result.
@@ -0,0 +1,360 @@
# LAB E4 — Full-session EoMT segmentation playback
Date: 2026-07-21
Completed at: 2026-07-21T07:21:16.791Z
State: completed and published, model output, not ground truth
Ops card: MISSIONCOR-18
## Objective
Turn the plain EoMT valid-FOV configuration selected in LAB E3 into the first
complete, watchable semantic-segmentation result for a saved Mission Core
session. The acceptance target was deliberately narrow and observable:
1. process every recorded camera frame, without sampling;
2. preserve the exact saved-session timeline;
3. publish an H.264 semantic-overlay video and per-frame semantic masks through
the existing recorded-perception contract;
4. make that video selectable and playable in the RAVNOVES00 saved-session UI;
5. measure full inference, publication, GPU, memory, power, temperature and disk
behavior on the connected AI worker;
6. keep all task-controlled worker data on `D:\NDC_MISSIONCORE` and enforce a
360 GiB free-space floor.
LAB E4 is semantic-only. It does not claim instance detection, tracking, 3D
cuboids, LiDAR association, metric distance, point-cloud segmentation or
safety-grade perception.
## Immutable input and provenance
- UI session alias: `RAVNOVES00`
- Session ID: `20260720T065719Z_viewer_live`
- Compute job: `recorded-camera-602ac89026ed12978619801d`
- Source: `sensor.camera.right`
- Factory calibration slot: `camera_1`
- Input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`
- Factory calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Codec epoch: 1
- Resolution: 800x600
- Segments/frames admitted: 4,489/4,489
- Timeline: 35.421857292–484.144857292 session seconds
- Media duration: 448.723 seconds
- Approximate source rate: 10.0037 FPS
- Frame timeline SHA-256:
`16098d7606e5462f1d6ccbc1b6e99158f68e2b2ca85ea11794799ef709b61c61`
The valid image circle is the immutable LAB E1 artifact
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`.
Its mask SHA-256 is
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
It admits 270,606 of 480,000 pixels. The mask is loaded once and reused; it is
not recomputed from every frame.
## Accepted configuration
LAB E4 deliberately promotes the LAB E3 speed/quality control, not the more
expensive five-view challenger:
- pipeline: `recorded-semantic-eomt-fisheye-mask/v1`;
- model: `tue-mps/cityscapes_semantic_eomt_large_1024`;
- architecture: `EomtForUniversalSegmentation`;
- pinned revision:
`8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`;
- model weights: 1,276,175,488 bytes, SHA-256
`c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`;
- config SHA-256:
`7f4aa94fa4e43c0dbd79a5420edb511120aef62bd82bfbcbcece79948286a650`;
- preprocessor SHA-256:
`97e2fbf7f0bdba2cfc90251c5133bae9c27ddc9c4410509f40670be2332854e7`;
- precision: FP16 autocast;
- batch size: 1;
- frame policy: all frames, no sampling;
- semantic-overlay alpha: 0.48;
- CLAHE: disabled;
- KB4 cubemap rectification: disabled;
- instance branch: disabled;
- container: existing `nvcr.io/nvidia/tritonserver:26.06-py3`;
- PyTorch: `2.13.0+cu130`;
- CUDA runtime: `13.0`;
- Transformers: `4.57.6`;
- NumPy: `1.26.4`;
- Pillow: `12.3.0`.
Producer identities:
- runner SHA-256:
`651e8e06c3912dffb036b7fd08f2c0623f7563d8306cc7aee05db562798518f4`;
- orchestrator SHA-256:
`c1c16fc351e7c8c4806fe2fd17a56e659495d843db5ff3e9ac3699a8d9722640`;
- profile SHA-256:
`ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875`;
- dependency identity SHA-256:
`4eb1f8d33236806e74f9e5bb96b7dce2ac37623dc39b2184be2aa8d7d00e983e`.
The Cityscapes outputs are mapped into the existing Mission Core 0–15
robotics taxonomy. Pixels outside the admitted FOV are category 0. Cityscapes
does not provide the required forest-specific `ground_dirt`, `animal` and
general `other_background` classes, so those mapped totals are zero. This is a
known taxonomy limitation, not evidence that those contents are absent.
## Execution topology
The work was sequential at frame level:
```text
recorded fMP4 epoch
-> exact RGB frame reconstruction
-> fixed calibration-bound valid-FOV fill
-> EoMT semantic forward pass on RTX 4090
-> resize/map semantic labels into Mission Core taxonomy
-> force excluded FOV pixels to category 0
-> write indexed semantic PNG and 48%-alpha overlay PNG
-> append timestamped frame metadata
-> encode overlays as H.264 MP4
-> archive semantic masks
-> hash, validate and publish result
```
The input stream and model loop were not split across parallel jobs. Batch size
was one. GPU work is internally asynchronous where PyTorch/CUDA permits, but
the next frame was not admitted until the current frame's semantic result and
artifacts had been produced. The final video was encoded on NVENC after the
overlay sequence was complete.
No software power, utilization or memory cap was applied. The main deliberate
limits were batch size one, one model, a 360 GiB D: free-space floor and a
conservative preflight working-set reserve.
## Pilot gate
An eight-frame non-publishable pilot ran first under
`pilot-8-4038b699764048579f9fb7c6886a0a10`.
- frames: 8/8;
- inference wall: 2.381609 seconds;
- throughput including frame artifacts: 3.359074 FPS;
- visual check: valid-FOV alignment, road, vegetation, vehicles, person and
building overlays were usable;
- publication: prohibited by design.
The pilot exposed a report-only PowerShell parsing defect in a free-space
measurement: status text and the numeric value reached the same output stream.
The full-run orchestrator separated them before execution. The pilot data was
retained as evidence; no pilot result entered the saved-session result store.
## Full-run result
Published result:
`result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30`.
| Measurement | Result |
|---|---:|
| Frames expected / processed | 4,489 / 4,489 |
| Frames failed / skipped | 0 / 0 |
| Inference wall | 1,447.565362 s (24:07.565) |
| Inference throughput | 3.101069 FPS |
| Extract/reconstruction phase | 31.837595 s |
| H.264 encoding | 3.374570 s |
| Mask archive | 1.332036 s |
| Full worker wall | 1,668.259063 s (27:48.259) |
| End-to-end throughput | 2.690829 FPS |
The source plays at approximately 10.0037 FPS. This offline E4 path is therefore
about 3.72 times slower than source real time. It is a complete playback
baseline, not yet a live-stream configuration.
The earlier LAB E0 full path achieved 1.678305 inference FPS and 1.593908
end-to-end FPS while executing both Mask R-CNN and BEiT. E4 is approximately
1.85 times faster on the measured inference loop and 1.69 times faster overall,
but the comparison is not like-for-like: E4 is semantic-only and intentionally
disables the instance branch.
### Per-frame latency
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| End to end | 322.458 ms | 320.654 ms | 348.467 ms | 441.481 ms |
| Model forward | 141.142 ms | 140.483 ms | 153.122 ms | 185.848 ms |
| Artifact write | 99.477 ms | 98.231 ms | 113.678 ms | 155.797 ms |
| Image decode | 30.677 ms | 28.915 ms | 42.952 ms | 82.429 ms |
| Processor | 10.656 ms | 9.597 ms | 15.529 ms | 85.791 ms |
| Host to device | 7.737 ms | 7.515 ms | 10.726 ms | 21.986 ms |
| Model postprocess | 10.214 ms | 10.421 ms | 14.078 ms | 25.200 ms |
| Overlay | 18.712 ms | 17.595 ms | 25.332 ms | 42.439 ms |
| Valid-FOV fill | 1.952 ms | 1.834 ms | 2.582 ms | 7.701 ms |
The measured stage means account for the frame loop. Approximately 184.15
seconds of the full worker wall remain outside extract, inference, encode and
archive. This includes full input/model hashing, model load, container/preflight
work, exact stream setup, result finalization, output hashing and validation.
## GPU, process and neighboring services
Worker GPU: NVIDIA GeForce RTX 4090, compute capability 8.9.
- one-second GPU samples: 1,448;
- GPU utilization: 73.05% mean, 73% p50, 89% p95, 95% max;
- board memory used, including pre-existing services: 13,397.65 MiB mean,
13,409 MiB max;
- GPU memory-controller utilization: 21.41% mean, 32% p95, 35% max;
- power: 234.24 W mean, 232.91 W p50, 247.15 W p95, 253.23 W max;
- temperature: 52.94 C mean, 53 C p50, 57 C p95, 58 C max;
- process CUDA peak allocated: 2,099.8 MiB;
- process CUDA peak reserved: 2,840.0 MiB;
- process peak RSS: 2,009.332 MiB.
The board-memory figure is not the E4 process allocation. It is the total
device usage observed by `nvidia-smi`, including already running services. The
per-process CUDA peaks above are the relevant E4 allocation measurements.
`mission-core-triton`, `sentinel-frigate` and `sentinel-ollama` remained up.
## Disk policy and measured usage
The orchestrator admitted only explicit paths rooted at
`D:\NDC_MISSIONCORE`. It performed a conservative space preflight, sampled free
space every 100 frames and failed closed if the floor would be crossed.
| Disk point | Free bytes | Free GiB |
|---|---:|---:|
| Before run | 410,711,453,696 | 382.505 |
| After input extraction | 407,553,695,744 | 379.564 |
| After inference | 404,899,336,192 | 377.092 |
| After final artifacts | 404,796,903,424 | 376.996 |
| After task temporary cleanup | 407,372,025,856 | 379.395 |
| Enforced floor | 386,547,056,640 | 360.000 |
The conservative working-set reserve was 18,462,883,615 bytes (17.195 GiB).
The floor was never crossed. Final headroom above the floor was approximately
19.395 GiB. No task-controlled C: path was used, created or mounted. This does
not claim to observe incidental internal writes by Windows or the pre-existing
Docker Desktop engine.
Only exact E4 temporary extraction, mask and overlay work directories were
removed after successful publication. Immutable result artifacts, reports,
telemetry, the pilot and runner identities remain recoverable on D:.
## Published artifacts and integrity
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `perception.mp4` | 75,353,780 | `6e58cf72d9571ebbb003a814132b44907b7c0e31d18b5449cce330afcad29829` |
| `masks.tar.gz` | 39,812,627 | `ef291034e51c5c719dcc1c0902bae1e3daedf919d7b55a21fcb0a31af298071f` |
| `frames.jsonl` | 3,927,616 | `4780339f5ef94e1f58922dfe2df67ecdf983c4b4e030761f4a990f65b511d5cf` |
| `gpu-telemetry.jsonl` | 325,407 | `c6a4db1a8ea01d1ca66aded4bcd990ed91fc015930113869b20f024632a2aacf` |
| `run-report.json` | 7,894 | `8ee04bf2ed8e563641dc1dacd320aee6fc8cd4077a43fab564033a9c29cbaa07` |
The published local result occupies approximately 123 MiB. Independent checks
confirmed:
- all five artifact size/digest pairs match `result.json`;
- `frames.jsonl` contains exactly 4,489 ordered timestamp rows;
- the mask archive contains exactly 4,489 indexed masks;
- FFprobe reports H.264, 800x600, 448.723 seconds and exactly 4,489 frames;
- the complete recorded-perception v2 result validates against its job, input,
session, source, timestamp bounds and `camera_1` calibration binding;
- the result store selects this result as the latest admitted generation for
`recorded.perception.right`.
Durable local result root:
```text
.runtime/compute-results/recorded-camera-602ac89026ed12978619801d/
result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30/
```
Worker evidence root:
```text
D:\NDC_MISSIONCORE\runtime\derived\
result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30
```
## Mission Core publication and UI proof
The saved-session replay API returned schema
`missioncore.observation-session-replay/v2` with source
`recorded.perception.right`, label `Сегментация · камера right`, the exact result
ID and the MP4 generation SHA above. The media manifest validated as
`missioncore.observation-recorded-media/v3`. A byte-range request returned HTTP
206 with `Content-Range: bytes 0-1023/75353780`.
The final acceptance was performed in the real Mission Core browser UI:
1. open **Сохранённые сессии**;
2. select **RAVNOVES00**;
3. open the scene source picker;
4. select **Сегментация · камера right**;
5. press **Воспроизвести**.
The visible video element loaded the exact new result, reported 800x600,
duration 448.723 seconds, `readyState=4` and no media error. Its current time
advanced from 128.522 to 130.282 seconds during a timed browser check, proving
that the result is not merely discoverable but actually plays.
## Visual findings
Start, middle and end frames were inspected from the final encoded video.
1. The fixed valid-FOV circle remains aligned across the complete recording;
excluded lens pixels do not generate semantic clutter.
2. Road, sidewalk, sky, buildings and vegetation form spatially coherent
regions at the inspected start/middle/end points.
3. Cars and people are visibly segmented when present, but this is semantic
class color, not a persistent object identity.
4. The output is substantially more usable than the original E0 visual result
for playback, while still showing expected Cityscapes/domain artifacts on
close structures, thin boundaries and forest/off-road content.
5. No visual inspection can substitute for the pending human-reviewed E2
accuracy set. The result remains `ground_truth=false`.
Final contact sheet:
`.runtime/compute-experiments/e4/full-preview/start-mid-end.png`, SHA-256
`a16f78732346db4b476b7d66dd140857d92eb1f11add5826c49e4dddf0ac1d5f`.
## Acceptance status and limitations
Accepted:
- complete no-sampling processing of the chosen saved session;
- zero failed/skipped frames;
- immutable semantic masks and frame timestamps;
- exact H.264 playback on the original session timeline;
- source discovery, manifest and HTTP range transport;
- real UI selection and playback;
- measured worker resource and disk behavior;
- D-only task-controlled worker storage with enforced floor.
Not accepted or not attempted:
- live real-time throughput;
- instance masks, object IDs or tracking;
- 2D detector boxes from an E4 model branch;
- 3D cuboids or point-cloud semantic labels;
- LiDAR-camera association and distance-to-object accuracy;
- forest/off-road model fitness;
- mIoU, boundary IoU, AP or safety metrics;
- production or safety qualification.
## Decision and next gate
E4 closes the first full-session semantic playback milestone. Plain EoMT with
the immutable valid-FOV mask is now the operational reference for recorded
video: reproducible, watchable and measured.
The next work should not be another blind full-epoch run. It should proceed in
two bounded tracks:
1. finish human review of the E2 evaluation pack and measure semantic accuracy,
especially people, vehicles, road/free-space boundaries and near distorted
structures; use those metrics to decide whether to keep the raw fisheye
baseline, improve the model/taxonomy or revive seam-aware rectification;
2. add an instance/detection and tracking branch, then project accepted 2D
observations through the factory camera/LiDAR calibration into the point
cloud for distance and 3D-cuboid experiments.
Live processing should be admitted only after the chosen branches are profiled
with bounded queues and an explicit drop/sampling policy. At the present 3.10
inference FPS, this exact configuration cannot consume the recorded ~10 FPS
camera stream without backlog.
@@ -0,0 +1,405 @@
# LAB E5 — Instance detection and temporal tracking qualification
Date: 2026-07-21
Completed at: 2026-07-21T08:45:46.307Z
State: completed qualification baseline, model output, not ground truth
Ops card: MISSIONCOR-19
## Objective
Establish the first measured temporal object-identity baseline on recorded K1
camera data before attempting LiDAR association and 3D cuboids. The acceptance
target was deliberately bounded:
1. run an upstream, reproducibly pinned detector through the existing Triton
service on the AI worker;
2. reuse the immutable calibration-bound valid-FOV mask instead of processing
the black fisheye border;
3. attach persistent IDs with a ByteTrack-style two-stage association loop;
4. produce a watchable 2D tracking video and exact timestamped frame metadata;
5. measure detector, tracking, artifact, GPU, memory and disk behavior;
6. retain all task-controlled worker data under `D:\NDC_MISSIONCORE`, enforce
the 360 GiB free-space floor and restore the prior Triton model state;
7. record visible failure modes honestly before using these identities for 3D.
LAB E5 is a qualification clip, not a full-session or saved-session production
source. It does not claim tracking ground truth, ID-switch accuracy, instance
masks, 3D cuboids, LiDAR distance, point-cloud labels, real-time operation or
safety fitness.
## Immutable input and clip selection
- UI session alias: `RAVNOVES00`
- Session ID: `20260720T065719Z_viewer_live`
- Compute job: `recorded-camera-602ac89026ed12978619801d`
- Source: `sensor.camera.right`
- Factory calibration slot: `camera_1`
- Input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`
- Factory calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Codec epoch: 1
- Resolution: 800x600
- Full source: 4,489 frames, 35.421857292–484.144857292 session seconds
- Qualification selection: source frames 1,000–1,600 inclusive
- Frames admitted: 601/601
- Qualification timeline: 135.365857292–195.334857292 session seconds
- Qualification media duration: 60.068948 seconds
- Qualification timeline SHA-256:
`06e3514228b488137b92c303cbca171154ede00368783e32107ed2c39c41baa6`
Frames 1,000–1,600 were selected before the full run. The contiguous interval
contains stationary and changing views, multiple parked vehicles, distant
pedestrians, and a woman with a child/stroller passing close to the camera.
That close pass deliberately stresses scale change, fisheye distortion,
occlusion and identity continuity. It was not selected from the final output to
make the result look favorable.
The valid image circle is the immutable LAB E1 artifact
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`.
Its mask SHA-256 is
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
It admits 270,606 of 480,000 pixels. The mask is loaded once and reused; it is
not recomputed per frame.
## Accepted software configuration
Detector:
- official Megvii YOLOX-S ONNX release `0.1.1rc0`;
- license: Apache-2.0;
- classes: COCO-80, filtered to person, bicycle, car, motorcycle, bus and
truck;
- model SHA-256:
`c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- Triton config SHA-256:
`5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604`;
- input: FP32 `[1,3,640,640]`, BGR, bilinear top-left letterbox, fill 114;
- output: FP32 `[1,8400,85]`, official grid/stride decode;
- container: `nvcr.io/nvidia/tritonserver:26.06-py3`;
- Triton backend: ONNX Runtime GPU.
Detection and FOV filtering:
- detector minimum score: 0.10;
- high/new-track threshold: 0.25;
- same-class NMS IoU: 0.45;
- same-class containment suppression: 0.80;
- minimum box area: 64 pixels;
- maximum box area: 50% of image;
- minimum valid-FOV box fraction: 0.50;
- box center must lie inside the valid FOV.
Tracking:
- algorithm: `bytetrack-style-two-stage-iou/v1`;
- assignment: SciPy linear-sum assignment;
- primary high-score match IoU: 0.20;
- secondary low-score match IoU: 0.10;
- lost-track buffer: 15 frames;
- confirmation: 2 hits;
- association is class-consistent;
- no appearance ReID and no camera-motion compensation.
Producer identities:
- pipeline: `recorded-yolox-bytetrack-style-qualification/v1`;
- runner SHA-256:
`adc7c924cb8d01278ccf63c9eeb3c9f810c09d2d1d7fe169e136102ee32c247a`;
- orchestrator SHA-256:
`8fff9c87e6213883ebde257a4226765d96b16cd4b8a2a5bb492aef728e3e2242`;
- profile SHA-256:
`1de8a83ef0fe630ca317b70bbe3a8ec497ae7cb5267fabda2ea234ee2cf2e562`;
- Python 3.12.3, NumPy 2.5.1 and SciPy 1.16.3 inside the bounded run
container.
The profile was frozen after the second visual pilot. No thresholds were tuned
against the 601-frame result.
## Execution topology
The pipeline was sequential at frame level:
```text
recorded fMP4 epoch
-> exact RGB frame reconstruction
-> fixed calibration-bound valid-FOV fill
-> YOLOX-S Triton request
-> YOLOX decode and class/FOV filtering
-> same-class NMS plus nested-box suppression
-> high-score association
-> low-score association to unmatched tracks
-> confirm/age/retire tracks
-> write ID boxes and trails
-> append exact timestamped detections/tracks
-> encode overlays as H.264
-> hash, validate and publish qualification result
```
Batch size was one. Frames were not parallelized. Triton/CUDA may execute
internal work asynchronously, but each source frame completed decode,
inference, association and overlay writing before the next frame was admitted.
NVENC encoded the completed overlay sequence after inference.
The model was absent from Triton's ready set before the run. The orchestrator
loaded it only for LAB E5 and restored the original unloaded state in `finally`.
The pre-existing `mission-core-triton`, `sentinel-frigate` and
`sentinel-ollama` services remained running.
## Pilot history and configuration freeze
The first launch failed closed during preflight because the base Triton image
did not contain Pillow. It performed no inference, loaded no model and
published no result. The correction mounted the already existing D-only
perception environment read-only at `/opt/env`; no new environment was placed
on C:.
The first successful 32-frame pilot used frames 1,230–1,261 and published the
immutable diagnostic result
`e5-tracking-005090ae34dc2c09d75f0dcfe68b4f9eed9e011d480e0c4b0d650be9369ed841`.
It exposed two implementation effects:
1. nested same-class body/torso boxes survived conventional IoU NMS;
2. the first SciPy assignment import contaminated the first tracking latency
with a 422 ms one-time cost.
The accepted runner added 80% containment suppression and warmed the assignment
solver before timed frames. A second 32-frame pilot then published
`e5-tracking-8035c0013e36a7cfb68aa3652b05ab79fe36baf136462d5fbf91b348ee85ec08`.
It processed 32/32 frames in 4.843450 seconds at 6.606861 FPS, reduced
detections from 235 to 227, retained zero same-class pairs at IoU >= 0.8 and
reduced tracking p95 to 0.401 ms. Visual inspection confirmed that the remaining
nearby person boxes represented the woman and child, not nested duplicates.
Both successful pilots remain immutable evidence. Only the second profile was
admitted to the main qualification run.
## Qualification result
Published result:
`e5-tracking-88aace13ef9963f8dc07f85228e530f9d28c2b49aca9192409f7975512b058f6`.
| Measurement | Result |
|---|---:|
| Frames expected / processed | 601 / 601 |
| Frames failed / skipped | 0 / 0 |
| Frames with confirmed tracks | 600 |
| Inference/tracking/artifact loop | 88.092835 s |
| Loop throughput | 6.822348 FPS |
| Stream extraction | 10.243353 s |
| H.264 encoding | 0.686277 s |
| Full worker wall | 166.335664 s |
| End-to-end throughput | 3.613176 FPS |
| Process peak RSS | 126.199 MiB |
The source rate is approximately 10.004 FPS. The measured frame loop is about
1.47 times slower than source real time, and the complete worker run is about
2.77 times slower. This exact artifact-heavy path is therefore offline. It is
not admitted as a live pipeline.
### Per-frame latency
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| End to end | 146.279 ms | 144.622 ms | 163.340 ms | 215.759 ms |
| Overlay write | 87.363 ms | 86.227 ms | 99.219 ms | 139.534 ms |
| Image decode | 30.132 ms | 28.455 ms | 40.723 ms | 63.155 ms |
| Triton request | 12.359 ms | 12.148 ms | 17.948 ms | 31.662 ms |
| Detector postprocess | 7.307 ms | 7.016 ms | 10.734 ms | 21.003 ms |
| Preprocess | 6.202 ms | 5.803 ms | 7.991 ms | 18.645 ms |
| Tracking | 0.247 ms | 0.234 ms | 0.390 ms | 0.894 ms |
The tracker is not the speed problem. The Triton request itself is also below
the approximately 100 ms/frame source budget on average. Synchronous PNG
overlay writing and image decoding dominate this qualification loop. A live
design must decouple visualization/artifact production from the inference
critical path and define bounded queues and a drop policy; it must not simply
run this script against an unbounded stream.
## Detection and identity observations
| Metric | Result |
|---|---:|
| Detections admitted | 4,049 |
| Confirmed track observations | 3,320 |
| Confirmed track IDs | 167 |
| Tracker tracks created / retired | 177 / 166 |
| Confirmed length mean / p50 / p95 / max | 19.88 / 6 / 94 / 212 frames |
| Same-class duplicate pairs at IoU >= 0.8 | 0 |
| Rejected oversized boxes | 7 |
Admitted detections by detector label:
| Label | Detections | Confirmed observations | Confirmed IDs |
|---|---:|---:|---:|
| car | 2,972 | 2,556 | 123 |
| truck | 646 | 483 | 15 |
| person | 364 | 249 | 24 |
| bicycle | 45 | 29 | 3 |
| motorcycle | 16 | 2 | 1 |
| bus | 6 | 1 | 1 |
Track persistence without ground-truth interpretation:
- 150/167 IDs had at least 2 confirmed observations;
- 97/167 had at least 5 observations;
- 63/167 had at least 10 observations;
- 30/167 had at least 30 observations;
- 15/167 had at least 60 observations;
- 7/167 had at least 100 observations.
These counts prove that the temporal state machine is operating; they do not
measure accuracy. The high number of short vehicle IDs is a visible baseline
limitation, driven by changing viewpoint, intermittent low-confidence
detections and class changes between `car` and `truck` while association is
class-consistent.
### Visual findings
1. Boxes remain inside the calibrated valid image circle; the black lens border
no longer produces visual clutter or tracking candidates.
2. Parked vehicles and normally moving people retain IDs over useful clear-view
intervals. Several vehicle tracks persist for 100–212 frames.
3. The close woman/child sequence demonstrates both success and failure. IDs
54 and 60 persist through the unobstructed approach. When the woman/stroller
fills the lower-left fisheye region and becomes heavily occluded, ID 60 is
lost and a new person ID 76 begins. This is a real identity fragmentation,
not hidden by the report.
4. YOLOX-S sometimes classifies the stroller as `motorcycle`, and the same
parked vehicle can alternate between `car` and `truck`. COCO has no stroller
class, and the generic road detector is not calibrated for the K1 fisheye.
5. Conventional NMS plus containment suppression removed the visible nested
body/torso duplicates from the first pilot without suppressing the distinct
woman and child.
6. There is no human-reviewed identity ground truth. IDF1, HOTA, MOTA and true
ID-switch count therefore remain unknown and must not be inferred from the
track-length statistics.
## GPU and neighboring services
Worker GPU: NVIDIA GeForce RTX 4090.
- one-second GPU samples: 88;
- GPU utilization: 51.65% mean, 52% p50, 55% p95, 56% max;
- board memory used, including pre-existing services: 10,239.0 MiB mean,
10,249 MiB max;
- memory-controller utilization: 6.11% mean, 7% max;
- power: 144.78 W mean, 146.71 W p95, 148.40 W max;
- temperature: 42.60 C mean, 43 C max;
- process peak RSS: 126.199 MiB.
The board-memory value is total device use observed by `nvidia-smi`, not an E5
process allocation. This run did not expose a trustworthy per-process CUDA
peak, so none is claimed.
## Disk policy and measured usage
The orchestrator admitted only explicit worker paths rooted at
`D:\NDC_MISSIONCORE`. It reserved a conservative 4,726,173,047-byte working set,
checked D: at each major phase and every 100 frames, and failed closed if the
360 GiB floor would be crossed.
| Disk point | Free bytes | Free GiB |
|---|---:|---:|
| Before run | 409,176,440,832 | 381.075 |
| After frame extraction | 408,682,668,032 | 380.615 |
| After inference | 408,303,550,464 | 380.262 |
| After final artifacts | 408,300,429,312 | 380.259 |
| After exact temporary cleanup | 408,660,688,896 | 380.595 |
| Enforced floor | 386,547,056,640 | 360.000 |
The floor was never approached closer than 20.259 GiB. Only exact E5 temporary
stream, frame and overlay roots were removed. The immutable pilots, accepted
result, telemetry, runner and profiles remain on D:. No task-controlled C: path
was created, mounted or used. This does not claim visibility into incidental
internal writes by Windows or the pre-existing Docker Desktop engine.
## Artifacts and independent validation
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `tracking.mp4` | 12,270,533 | `d911efc46c5c3df38ca961700f82c407c44222ba465af752d2a7de75911740ba` |
| `frames.jsonl` | 1,116,700 | `e5f19e5dd8bf76040cb7859ffada3f1bb68a0f5cd93fac243947276899cce014` |
| `gpu-telemetry.jsonl` | 19,694 | `84cbaa4d5a7d503129f2fa329ab2b231cff54a6972cd31ba95fe91f48f01adad` |
| `contact-sheet.png` | 926,575 | `d9a23cc76e1a9a4aba1156609f85b781bc2502cd80f3b17ac83ca4228f2121a8` |
| `run-report.json` | 9,183 | `96d1cb059b35a0aa3fcc7c3458a0f47f57a4bd52a64c34f3c7607302e49206d9` |
Independent local validation confirmed:
- the result directory name equals its canonical identity SHA-256;
- all five artifact size/digest pairs match `result.json`;
- the result is bound to the exact job, input, session, source, clip bounds and
timestamp basis;
- `frames.jsonl` contains exactly 601 ordered source indices and strictly
increasing session timestamps;
- every reported detection and track box is finite and inside 800x600;
- no frame failed or was skipped;
- FFprobe reports H.264, yuv420p, 800x600, 60.068948 seconds, and exactly
601 declared/read frames;
- the custom result validator accepts the immutable result;
- the E5 source and test files pass Ruff, and the focused E5 test suite covers
profile pinning, FOV math, YOLOX decode, containment suppression, two-stage
low-score identity retention, class isolation and nonzero source timelines.
Durable local result root:
```text
.runtime/compute-experiments/e5/results/
e5-tracking-88aace13ef9963f8dc07f85228e530f9d28c2b49aca9192409f7975512b058f6/
```
Worker evidence root:
```text
D:\NDC_MISSIONCORE\runtime\derived\
e5-tracking-88aace13ef9963f8dc07f85228e530f9d28c2b49aca9192409f7975512b058f6
```
## Acceptance status
Accepted:
- reproducible official YOLOX-S inference through the existing Triton worker;
- fixed calibration-bound valid-FOV preprocessing;
- ByteTrack-style high/low-score temporal association;
- 601/601 exact recorded frames with no failures or skips;
- watchable 2D ID overlay, trails and complete frame metadata;
- honest timing, GPU, process and disk telemetry;
- content-addressed immutable qualification artifacts;
- D-only task-controlled worker storage and restoration of Triton model state.
Not accepted:
- production-quality object classes or identity continuity;
- IDF1, HOTA, MOTA or measured ID-switch rate;
- handling of heavy close occlusion, re-entry or long disappearance;
- appearance ReID or camera-motion compensation;
- instance masks, 3D cuboids, LiDAR association or metric distance;
- a live unbounded stream or safety use;
- promotion of the partial clip into the saved-session production source list.
## Decision and next gate
LAB E5 closes the first temporal identity baseline. The result is useful enough
to become input to a bounded 3D fusion experiment, but not strong enough to be
called production tracking.
The next gate should be LAB E6 on the same frozen 601-frame interval:
1. use factory `camera_1` calibration to project synchronized LiDAR points into
each accepted 2D observation;
2. reject points outside the valid FOV and use robust depth/cluster gates rather
than a single nearest pixel;
3. compute per-object range and coarse point-supported 3D cuboids with explicit
support counts and confidence;
4. smooth distance over the existing track ID where continuity is available;
5. treat COCO `car`/`truck` as one vehicle association group in the fusion
layer so detector class flicker does not automatically break geometry;
6. publish a bounded Rerun qualification recording only after visual and
numeric checks pass.
In parallel with, but not as a prerequisite for, this first 3D proof, the 2D
branch should later compare camera-motion compensation and appearance ReID on a
small human-reviewed identity set. Full-session and live runs should wait until
the artifact-writing path is removed from the inference critical section and a
bounded queue/drop policy has been tested.
@@ -0,0 +1,292 @@
# LAB E6 — Factory-calibrated tracked LiDAR fusion
Date: 2026-07-21
Completed at: 2026-07-21T09:18:15.532Z
State: completed qualification baseline, diagnostic geometry, not ground truth
Ops card: pending direct `nodedc-ops-agent` availability
## Objective
Connect the already qualified E5 temporal identities to metric observations from
the K1 LiDAR without inventing depth from image boxes. The bounded acceptance
target was:
1. reuse the exact 601-frame E5 interval and E4 semantic masks;
2. bind the right camera to factory calibration slot `camera_1`;
3. project raw K1 points with the factory KB4 intrinsics and camera/LiDAR
extrinsics;
4. reject camera/point/pose combinations outside a strict 100 ms host-arrival
gate;
5. associate only semantically compatible, nearest-depth, spatially connected
LiDAR support with each tracked image object;
6. estimate a robust metric range and a point-supported oriented 3D envelope;
7. publish a watchable overlay and a standalone Rerun recording containing the
camera view, map-frame point cloud, support points and translucent `Boxes3D`;
8. measure the geometry path and preserve every pilot and accepted artifact.
LAB E6 proves that the existing XGRIDS factory calibration can drive useful
recorded camera/LiDAR fusion. It does not claim amodal vehicle dimensions,
object-complete 3D boxes, ground truth, live-rate admission or safety fitness.
## Immutable inputs
- UI session alias: `RAVNOVES00`
- Session ID: `20260720T065719Z_viewer_live`
- Camera job: `recorded-camera-602ac89026ed12978619801d`
- Camera source: `sensor.camera.right`
- Factory calibration slot: `camera_1`
- Camera input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`
- Factory calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Raw MQTT capture SHA-256:
`70da0edad5cbf0e89b6e2355c0aac294f8c36a67249cd33b8918353fd88c83af`
- E4 semantic result:
`result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30`
- E5 tracking result:
`e5-tracking-88aace13ef9963f8dc07f85228e530f9d28c2b49aca9192409f7975512b058f6`
- Source frames: 1,000–1,600 inclusive
- Frames admitted: 601/601
- Session interval: 135.365857292–195.334857292 seconds
- Media duration: 60.068948 seconds
- Resolution: 800x600
The 127 MiB MQTT capture contains the raw point and pose observations used by
the run. The calibration snapshot files are preserved separately from Git:
| File | SHA-256 |
|---|---|
| `camera.yaml` | `d123ebd2207e18a800cf5d07d055bf38231d28390bf3ba8c4e718e3cb3a7775e` |
| `extrinsic_camera_lidar.yaml` | `d09b2eda51e1deb7e3a31ff8441cba6006e8da60afc3e33de44a857437dfcfb8` |
| `manifest.json` | `75e1d582f661dabafb48fc6e8c7aabe0f217e0d95ed934b7d9dac18bdfc1216c` |
## Accepted configuration
Producer identities:
- pipeline: `factory-kb4-tracked-box-semantic-lidar/v1`;
- profile schema: `missioncore.e6-tracking-lidar-profile/v1`;
- profile SHA-256:
`b8d3a6f043fde54ffd32ea05bfe580e2be7aace1a23cdbf7e266eb3325d147b2`;
- runner SHA-256:
`9765be5e0338b9978db7a58a739313b228bc7ceee1352a7a76af0ff2a528a953`.
Temporal binding:
- nearest host-arrival observation, best effort;
- maximum LiDAR/camera delta: 100 ms;
- maximum pose/point delta: 100 ms;
- frames outside the gate fail closed for depth; image tracking remains visible.
Projection and occlusion:
- factory KB4 projection for `camera_1`;
- source points transformed from the K1 map frame through the recorded pose;
- only camera-front, in-frame points are retained;
- one nearest-depth point is retained per rounded image pixel;
- no fabricated depth and no 2D-box extrusion.
Association:
- image boxes are inset 5% horizontally, 4% at the top and 2% at the bottom;
- `car`, `truck` and `bus` share the `vehicle` association group;
- same-frame vehicle overlap suppression uses IoU 0.55;
- E4 semantic IDs gate LiDAR support: person 1, bicycle 2, motorcycle 3,
vehicle 4/5;
- depth splitting uses `max(0.5 m, 6% of range)`;
- the selected depth component is split again by 3D connected components;
- spatial radii are 0.9 m for person/bicycle/motorcycle and 1.5 m for vehicle;
- minimum support is 3 points for person/bicycle/motorcycle and 5 for vehicle;
- distance uses cluster median, with p10 retained for diagnostics;
- a five-frame track history smooths distance;
- an observation is rejected when its distance innovation exceeds
`max(1.5 m, 25% of track history)`.
Cuboid construction:
- yaw comes from PCA over supported map-frame XY points;
- dimensions are the oriented p05–p95 support extent;
- every axis has a 0.15 m diagnostic minimum for visibility;
- maximum accepted extents are 1.5x1.5x2.8 m for person,
3.5x2.0x2.5 m for bicycle/motorcycle and 6.5x3.5x4.0 m for vehicle;
- rejected or unsupported detections never produce a 3D cuboid.
## Execution topology and resource boundary
The E6 geometry stage ran as a single local CPU process on the Mission Core
host, an Apple M3 Pro with 18 GiB RAM. It used Python 3.12.13, NumPy 2.5.1,
Pillow 12.3.0, Rerun 0.34.1 and FFmpeg 7.1.1. The external AI worker and its
Triton model state were not changed. No path on worker disk C: or D: was read,
written or cleaned by this stage.
```text
exact E5 frame + track IDs
+ exact E4 semantic mask
+ nearest gated K1 point/pose sample
-> factory KB4 camera projection
-> per-pixel nearest-depth buffer
-> box inset + semantic support gate
-> 1D depth split + 3D connected component
-> range innovation gate
-> robust range + PCA-yaw p05/p95 envelope
-> overlay / NPZ / JSONL / Rerun recording
-> content hash, qualification validation and publish
```
Frames were processed sequentially. Artifact writing was performed inside the
same measured process. This is a recorded qualification path, not the live
queue/worker architecture.
## Pilot history and profile freeze
Three immutable 32-frame pilots were retained:
1. `e6-fusion-a72468...` exposed 7–8 m person envelopes when a depth-only
cluster admitted background structure. It was rejected.
2. `e6-fusion-693246...` added 3D connectivity and plausible size gates. Box
dimensions became plausible, but range could still jump between foreground
and background support on the same track. It was rejected.
3. `e6-fusion-70fa670ed9005957266ae7a46ac2bfd2d526f1ea170cd052f4494a2397fff76f`
added the distance-innovation gate and was accepted. It fused 32/32 frames,
produced 47 cuboids across five track IDs, rejected two range jumps, ran in
3.681 seconds and peaked at 159.828 MiB RSS.
The accepted configuration was frozen after the third pilot. The 601-frame
result was not used to tune its thresholds.
## Qualification result
Published result:
`e6-fusion-b4e4226674a66f6196c033785eb307c255a7bdd8493ef9809dfb1d6e5bd68eaa`.
| Measurement | Result |
|---|---:|
| Frames expected / processed | 601 / 601 |
| Frames fused | 526 |
| Frames outside strict sync gate | 75 |
| Frames with at least one accepted cuboid | 438 |
| Accepted cuboids | 918 |
| Unique track IDs with depth | 56 |
| Source cloud points consumed | 1,182,292 |
| Accepted support points | 14,025 |
| Geometry/artifact wall time | 14.774254 s |
| Effective throughput | 40.678 FPS |
| User / system CPU | 13.571966 / 0.772126 s |
| Process peak RSS | 240.469 MiB |
This recorded geometry/artifact stage is about 4.07 times faster than the
approximately 10.004 FPS camera source. That does not establish a live
end-to-end rate: E4 segmentation and E5 detection/tracking are precomputed
inputs and were not included in the 14.774-second measurement.
### Synchronization
| Delta | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| LiDAR to camera | 29.389 ms | 23.905 ms | 70.794 ms | 96.107 ms |
| Pose to point cloud | 7.940 ms | 6.370 ms | 19.113 ms | 46.139 ms |
All fused frames satisfy the configured 100 ms gates. The 75 nonconforming
camera frames retain their 2D presentation but carry no depth or 3D claim.
Host-arrival time is still only a best-effort clock; hardware timestamps have
not been ground-truthed.
### Accepted geometry distribution
| Group | Cuboids | Unique track IDs |
|---|---:|---:|
| Vehicle | 809 | 43 |
| Person | 96 | 12 |
| Bicycle | 13 | 1 |
| Total | 918 | 56 |
- accepted range: 1.239–37.749 m, p50 10.189 m, p95 24.126 m;
- support per cuboid: minimum 3, p50 10, p95 45, maximum 89 points;
- p95 oriented extent: 5.383x1.197x2.222 m;
- maximum admitted extent: 6.490x2.948x2.783 m.
The extent is an envelope of the visible supported surface. It is intentionally
not an amodal estimate of the object's complete physical volume. A parked car
seen from one side can therefore receive a thin cuboid that does not wrap the
whole vehicle silhouette.
### Fail-closed decisions
| Status | Observations |
|---|---:|
| Accepted point-supported cuboid | 918 |
| Fewer than 5 vehicle support points | 1,023 |
| No semantically compatible LiDAR support | 365 |
| Rejected distance innovation | 209 |
| Fewer than 3 non-vehicle support points | 28 |
| Implausible cuboid extent | 24 |
These rejection counts are part of the result, not discarded errors. In
particular, the high low-support count prevents distant image detections from
being turned into false metric objects.
## Rerun and visual QA
The Rerun recording logs seven entities across the `session_time` timeline:
- encoded camera fusion overlay;
- raw map-frame LiDAR points;
- selected object support points;
- translucent oriented `Boxes3D` with track/class/range labels;
- a visible qualification contract stating the timing and safety limits.
The web viewer was loaded from the local gRPC proxy and inspected at multiple
timestamps. Camera overlay, point cloud, support and `Boxes3D` layers load
together. Selecting a cuboid exposes its real center, half-sizes, quaternion,
fill mode and label in Rerun. Browser logs contained no load/render errors;
only Rerun web-backend deprecation/default warnings were present.
## Artifacts
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `fusion.npz` | 9,745,489 | `3e0e63797c56505eb9755d253f262459d46ec841e80cc5b785a6b495ed2007f9` |
| `fusion-frames.jsonl` | 1,607,124 | `ea1621bb337c6c268f91f6e2c7cef997d78672da4025975328c9e2583a0718e0` |
| `box-labels.json` | 30,328 | `0c423024a086344fcdbac8665635a45e88fef81270be73d9e1af3e6bc4eb860c` |
| `fusion-overlay.mp4` | 24,156,459 | `a863d1910eb973cef1f4cca28432cb16b26030149e197ff7f9ad3d168b69954c` |
| `fusion.rrd` | 63,092,299 | `77b0b91171176dd7ea6d2ef726cc946ab1fc24367750101d3f10617360cb4d63` |
| `contact-sheet.png` | 3,066,509 | `adcad4db4fd3089175ed947b312129fc86f04abc065314175eceb62368adac49` |
| `run-report.json` | 5,405 | `ca01cb5fa0325d80504199b623f2972141f45b680b4b16ad6a5271f9dd1deb2c` |
FFprobe independently confirmed H.264/yuv420p, 800x600, 601 declared/read
frames and 60.068948 seconds. The custom E6 validator rehashed every artifact
and verified the exact session, job, camera, calibration, clip, upstream result
and configuration identities. The complete E6 workspace including all pilots
uses 124 MiB on the Mission Core host.
## What is proven and what remains open
Proven:
- the right-camera/`camera_1` factory calibration is operational for recorded
K1 camera/LiDAR projection;
- E5 track identities can receive robust metric LiDAR ranges;
- unsupported 2D boxes do not become fake 3D geometry;
- Rerun can display the camera, map cloud, support and true oriented 3D
primitives on one timeline;
- the geometry stage itself is comfortably faster than source rate on this
host.
Not proven:
- object-complete vehicle/person volume or metric box accuracy;
- calibration error in pixels or centimetres against a measured target;
- hardware-clock synchronization accuracy;
- tracking accuracy, instance-mask accuracy or semantic ground truth;
- live end-to-end throughput, bounded queues, drop policy or recovery;
- safety or navigation fitness.
## Next gate — LAB E7
Use this exact E6 result as the control and improve 3D geometry without hiding
rejections. The bounded E7 experiment should add ground removal, object-aware
support completion and a reviewed upstream 3D detection baseline; compare box
coverage, range stability, support/rejection rates and runtime on the same
601-frame interval. Only after that comparison should the winning geometry
path be connected to a bounded live queue. Multi-camera fusion and product UI
work remain outside this gate.
@@ -0,0 +1,348 @@
# LAB E7 — Bounded replay-real-time perception loop
Date: 2026-07-21
Completed at: 2026-07-21T10:12:16.828Z
State: accepted replay scheduler and world-state qualification
Navigation/safety state: not accepted
Ops card: pending direct `nodedc-ops-agent` availability
## Objective
Stop treating recorded perception as a video-enhancement exercise and establish
the first measured runtime contract for the K1 as the eyes of an autonomous
platform. LAB E7 replays the immutable E6 interval at its original timing and
requires the derived perception path to:
1. publish a machine-readable world state, not only a rendered overlay;
2. keep derived work in a small bounded queue;
3. absorb short source bursts without discarding processable frames;
4. discard old derived frames under real overload instead of building latency;
5. expose result age, queue depth, drops and explicit health states;
6. preserve object identity, metric range and 3D geometry in both map and
LiDAR-relative coordinates;
7. reject untrustworthy velocity estimates instead of publishing extreme
motion caused by cuboid jitter;
8. emit a diagnostic polar clearance observation from the LiDAR;
9. prove the scheduler at the approximately 10 Hz source rate before connecting
the real inference worker or live K1 stream.
This experiment qualifies the downstream scheduler, world-state contract and
diagnostic publication path. E4 segmentation, E5 detection/tracking and E6
fusion are immutable precomputed inputs. LAB E7 therefore does not claim live
inference, live sensor transport, collision avoidance or navigation fitness.
## Immutable input binding
- Session: `20260720T065719Z_viewer_live`
- Source: `sensor.camera.right`
- Camera slot: `camera_1`
- Camera job: `recorded-camera-602ac89026ed12978619801d`
- Input SHA-256:
`602ac89026ed12978619801d4edea0cae24b5cc3afabd9f7af2858de6505a20e`
- Factory calibration SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- E6 result:
`e6-fusion-b4e4226674a66f6196c033785eb307c255a7bdd8493ef9809dfb1d6e5bd68eaa`
- E7 selection: E6 frames 0–600 inclusive
- Source frames: 1,000–1,600 inclusive
- Frames attempted: 601
- Replay timing span: 59.969 seconds
- Replay speed: 1.0x
The adapter spent 9.490253 seconds before the timed replay revalidating inputs,
reading the raw point/pose capture and reconstructing the exact E6 pose binding.
That preparation is measured separately and is not counted as live latency. A
real source will receive frames incrementally and must not preload a multi-hour
session into memory.
## Accepted configuration
Producer identities:
- pipeline: `bounded-latest-wins-e6-world-state-replay/v1`;
- profile schema: `missioncore.e7-replay-realtime-profile/v1`;
- profile SHA-256:
`7222c65ddef20798a242d83edc53973cdfd6f5dec8fb9ffb6fcb5dbcef57f1c5`;
- runner SHA-256:
`ce36b919fe5860bbb81f04b29c58a8a333915b1ba9bd7f233f256ce77163b15d`;
- runtime contract SHA-256:
`4c65140550e47c08c528b10d51922d5f73b7d19a1c34ef07896c052f492309f5`;
- result validator SHA-256:
`719b966ff263f196a97b4d72e946b00fd10705a0158b7df6ecf452c6c9bc4e5b`;
- E6 replay adapter SHA-256:
`9765be5e0338b9978db7a58a739313b228bc7ceee1352a7a76af0ff2a528a953`.
Scheduler:
- one source-paced producer and one derived-state consumer;
- replay speed 1.0x using the original per-frame session timestamps;
- queue capacity: two derived frames;
- overflow policy: evict the oldest queued derived frame;
- normal consumer delay injection: zero;
- stale threshold: 300 ms;
- unavailable threshold: 1,000 ms;
- raw evidence is outside this queue and is never dropped by it.
Acceptance thresholds:
- delivery rate at least 9.5 Hz;
- p95 result age at most 300 ms;
- drop fraction at most 1%;
- maximum queue depth two;
- process RSS at most 512 MiB.
## World-state contract
Each published state uses
`missioncore.live-perception-world-state/v1` and contains:
- exact E6/source frame identity and session timestamp;
- `healthy`, `degraded`, `stale` or `unavailable` delivery health;
- the reason for every non-healthy state;
- accepted obstacle track ID, class, detector label and confidence;
- map-frame and K1-LiDAR-frame 3D positions;
- map-frame orientation and visible-surface cuboid dimensions;
- robust LiDAR range and support-point count;
- qualified diagnostic map-frame velocity or an explicit rejection reason;
- 72-sector LiDAR-relative obstacle-clearance observation.
The vehicle-body frame is explicitly
`unavailable-no-rig-to-vehicle-transform`. K1 LiDAR coordinates must not be
silently presented as the final autopilot body frame. A measured rigid
sensor-to-vehicle transform is required before behavior integration.
### Velocity policy
Naive frame-to-frame cuboid differencing produced visibly false speeds because
the supported surface moves within an object as viewpoint and point density
change. The accepted implementation uses a one-second bounded history,
pairwise slopes with at least 200 ms baselines, median velocity, a 400 ms
minimum history, a 20 m/s diagnostic bound and a 0.75 m median position-residual
gate.
The field is published only as `diagnostic-robust-history`. Observations with
insufficient history, excessive residual or an implausible speed retain the
object and range but publish no velocity.
### Clearance policy
The first clearance observation is deliberately simple and diagnostic:
- 72 horizontal sectors over 360 degrees;
- 0.5–30 m range;
- local ground estimate at the fifth LiDAR Z percentile;
- obstacle slice 0.2–3.0 m above that estimate;
- `front_m` is the nearest retained point within ±15 degrees;
- unobserved sectors remain `null`, never free by assumption.
This is obstacle clearance evidence, not a traversable free-space polygon. It
does not yet model vehicle footprint, slope, negative obstacles or a certified
ground plane.
## Pilot history and architecture correction
### Pilot 1 — rejected queue consumption policy
Result:
`e7-live-replay-a578764161fc007c8fc8f8e8b5ffc339e2e861668f13c1fee7dc017ac3c4b972`.
It processed 61/64 frames at 9.820 Hz with 10.787 ms p95 latency, but discarded
three frames during short timestamp bursts even though the consumer was fast.
The first implementation always selected the newest queued frame. This was too
aggressive for a capacity-two jitter buffer and was rejected.
The correction preserves FIFO order while capacity remains available and
evicts the oldest frame only when a new publication would overflow the queue.
This retains short processable bursts while remaining latest-wins under actual
backpressure.
### Pilot 2 — scheduler accepted, velocity rejected by review
Result:
`e7-live-replay-810e2628c756d3e3694603c4da5429a8a333e684208cd137c6a076a7e9ae8b73`.
It processed 64/64 frames with zero drops at 10.303 Hz and 5.943 ms p95
latency. Scheduler acceptance passed, but inspection of naive frame-difference
velocity found a 1,028.5 m/s maximum caused by a cuboid-center jump. The run is
retained as immutable evidence and rejected as a world-motion baseline.
### Overload negative control — accepted
Result:
`e7-live-replay-1c5ce5718d8be7c4dfcbe540c12fe96339ca04a598e8f5f2fb85e84bc0748796`.
An intentional 250 ms consumer delay was applied to 80 source-paced frames:
| Measurement | Result |
|---|---:|
| Frames attempted / processed / dropped | 80 / 33 / 47 |
| Drop fraction | 58.75% |
| Maximum / final queue depth | 2 / 0 |
| Healthy / stale states | 1 / 32 |
| End-to-end p95 | 468.525 ms |
| Peak RSS | 373.656 MiB |
The negative control proves the intended failure mode: derived output quality
degrades and old derived frames are discarded, but queue depth remains bounded
and the runtime reports staleness. It does not block or delete raw evidence.
### Pilot 3 — accepted profile freeze
Result:
`e7-live-replay-2fb63b4177900348fc876a1dc580a19015753742c3665c4a044e1f251b44a430`.
It processed 64/64 frames with zero drops at 10.303 Hz and 5.865 ms p95
latency. Robust-history velocity replaced naive differencing. In this interval
72 velocities were admitted with 0.439 m/s median, 2.046 m/s p95 and 4.811 m/s
maximum; eight noisy histories were rejected by the position-residual gate.
The configuration was then frozen for the full run.
### Preliminary full run — metrics valid, provenance incomplete
Result:
`e7-live-replay-24222ede524e2e7824e4c7e6e9d17e87aa75bf3b8768f9e88c4b97eb410b633b`.
It processed 601/601 frames with zero drops and passed every runtime threshold.
Final review found that its identity pinned the E7 runner but did not separately
pin the imported runtime contract, validator and E6 adapter. The immutable run
is retained as evidence, but it is not the reference result. Those dependencies
were added to the identity and the complete 1.0x run was repeated.
## Full qualification result
Published result:
`e7-live-replay-e339aaed75fff05ed893ae48770bd127cc0c764f3124fc4b6be94b5fd9f8389c`.
| Measurement | Result |
|---|---:|
| Frames attempted / processed / dropped | 601 / 601 / 0 |
| Delivery rate | 10.021845 Hz |
| Replay timing span | 59.969000 s |
| Actual replay wall | 59.981432 s |
| Queue capacity / maximum / final depth | 2 / 2 / 0 |
| Healthy / degraded / stale / unavailable | 526 / 75 / 0 / 0 |
| Process peak RSS | 398.047 MiB |
The 75 degraded states exactly match E6 frames that failed the strict
camera/LiDAR timing gate. They remain timestamped states with no fabricated
depth. Every fused E6 frame is healthy in this downstream replay. No frame was
lost by the accepted runtime.
### Runtime latency
| Stage | Mean | p95 | Max |
|---|---:|---:|---:|
| Scheduled tick to world-state publication | 4.880 ms | 5.927 ms | 8.895 ms |
| Producer scheduling lag | 4.124 ms | 5.019 ms | 5.644 ms |
| Queue wait | 0.071 ms | 0.144 ms | 1.154 ms |
| World projection and clearance | 0.372 ms | 0.538 ms | 1.647 ms |
| Rerun publication | 0.257 ms | 0.355 ms | 1.662 ms |
The p95 result is 50 times below the 300 ms initial laboratory threshold. This
headroom belongs only to the downstream replay/world-state stage; live decode,
network transport, E4/E5 inference and fresh E6 geometry must still be added to
the latency budget.
### Object state
| Group | Observations | Unique track IDs |
|---|---:|---:|
| Vehicle | 809 | 43 |
| Person | 96 | 12 |
| Bicycle | 13 | 1 |
| Total | 918 | 56 |
- range p50 / p95 / maximum: 10.189 / 24.126 / 37.749 m;
- 572 object observations received a diagnostic robust velocity;
- 263 retained the object but reported insufficient velocity history;
- 83 retained the object but rejected velocity due to position residual;
- admitted speed p50 / p95 / maximum: 0.600 / 2.640 / 6.431 m/s.
These velocities are behavior-facing schema candidates, not validated motion
ground truth. A 3D motion filter and annotated moving/static evaluation set are
still required.
### Clearance observation
- valid front clearance on 526 fused frames;
- front minimum / p50 / p95 / maximum:
0.801 / 5.356 / 11.092 / 25.954 m;
- observed-sector fraction p50 / p95: 81.94% / 87.50%;
- all 75 frames without admitted LiDAR remain clearance-unavailable.
## Rerun visual QA
The standalone Rerun recording contains:
- the qualification contract;
- map-frame LiDAR point cloud and oriented obstacle `Boxes3D`;
- end-to-end latency chart;
- queue-depth chart;
- health-state chart;
- the exact 59.969-second replay timeline.
The web viewer loaded all 601 ticks and exposed real cuboid centers, half-sizes,
quaternions, labels and ranges. Latency remains approximately 0–8.9 ms, queue
depth remains 0–1 at consumption with a measured maximum of two, and health
transitions correspond to depth-unavailable E6 frames. No browser load/render
errors were observed; only Rerun web-backend warnings were present.
## Artifacts
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `world-state.jsonl` | 1,730,546 | `6078d19c6414c2bf2f7ca9d95c5164b46f388496690d5fee67c662724fb7f956` |
| `telemetry.jsonl` | 317,486 | `c25a01dc2d7a067672b8ae667e08450f2010ead3e3ccbaea7fde47ec3638119c` |
| `world-state.rrd` | 16,234,276 | `540c7d91a6c4cf8c19276994f6bbafcb267686ddb1fdbae46d7fd2aba7d2c4b9` |
| `run-report.json` | 4,163 | `04fd00324c57f6fa06cd6360256c14f70ab5c3fe51d5e662e427780bd228345d` |
The E7 validator rehashes every artifact, revalidates the complete E6 upstream
chain, checks frame/drop/queue/health accounting, validates ordered world-state
and telemetry rows, and verifies the RRF2 recording. All pilots, the preliminary
full result and the reference result occupy 44 MiB on the Mission Core host.
The AI worker and worker disks C: and D: were not accessed or modified.
## What is proven
- the accepted E6 observations can be delivered as machine world state at the
source rate with a capacity-two derived queue and zero loss;
- source timestamp bursts are absorbed without unnecessary drops;
- real overload fails boundedly and explicitly rather than growing latency;
- result freshness and sensor/fusion degradation are separate states;
- accepted obstacles have map and K1-LiDAR positions, range, geometry and
guarded velocity fields;
- a first diagnostic LiDAR clearance signal is available;
- Rerun remains a subscriber/diagnostic surface, not the perception authority.
## What remains open
- actual live camera/LiDAR ingestion through this scheduler;
- network and inference latency on the RTX worker;
- 10 Hz detector/tracker execution rather than precomputed E5 observations;
- asynchronous lower-rate semantic inference rather than precomputed E4 masks;
- current-frame E6 fusion rather than precomputed geometry;
- calibrated K1-to-vehicle-body transform;
- ground-truthed velocity, clearance, object and free-space accuracy;
- bounded worker disconnect/recovery and authenticated non-SSH transport;
- navigation or safety acceptance.
## Next gate — LAB E8
Move the same contract onto the real executor path without changing K1 command
ownership:
1. replay encoded camera frames to the RTX worker at 10 Hz;
2. run YOLOX detection/tracking without synchronous video-artifact writes;
3. run semantic segmentation asynchronously at a lower measured rate and
expire old masks explicitly;
4. feed current detections, latest valid semantics and current LiDAR/pose into
the E6 fusion adapter;
5. publish the same E7 world-state schema through the capacity-two queue;
6. measure transport, decode, inference, fusion and publication latency
separately;
7. repeat the 250 ms overload and worker-disconnect negative controls;
8. connect the live K1 only after source-paced worker replay passes.
The initial E8 gate remains 5–10 Hz derived perception with p95 world-state age
below 300 ms and uninterrupted raw recording. A visually better model is
accepted only if it improves obstacle evidence inside that operational budget.
@@ -0,0 +1,369 @@
# LAB E8 — Source-paced RTX detector/tracker qualification
Date: 2026-07-21
State: completed; qualification and overload-control results accepted
Ground truth: no
Navigation/safety acceptance: no
## Purpose
LAB E8 answers one narrow but essential autonomous-perception question:
> Can the existing RTX worker consume the K1 right-camera stream at its recorded
> approximately 10 Hz rate, produce YOLOX detections and temporal tracks before
> the next source deadlines, and remain bounded if processing becomes slower than
> the source?
The lab deliberately removes synchronous overlay rendering and per-frame output
images from the inference critical path. It does not improve playback video in
isolation. It qualifies the detector/tracker branch that will feed the
near-real-time fused world state.
The answer is **yes for this recorded 60-second qualification interval**. The
worker processed 601/601 source frames with zero drops at 10.009945 effective
FPS. Result-age p95 was 93.158479 ms. A separate forced-overload run proved that
the queue remains bounded and discards superseded derived frames instead of
building an unbounded latency tail.
## Input identity
- Compute job: `recorded-camera-602ac89026ed12978619801d`
- Session: `RAVNOVES00`
- Source: `sensor.camera.right`
- Calibration slot: `camera_1`
- Calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Codec epoch: 1
- Resolution: 800x600
- Source selection: frames 1,000–1,600 inclusive
- Frames scheduled: 601
- Session timeline: 135.365857292–195.334857292 seconds
- Source span: 59.969 seconds
- Approximate source rate: 10.004 FPS
The interval is the same predeclared E5/E6/E7 qualification slice. It contains
parked and moving vehicles, people at several scales, a close woman/child/stroller
pass, fisheye distortion, occlusion and viewpoint change.
The valid-FOV mask is the immutable E1 result
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`.
Its mask SHA-256 is
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
It admits 270,606 of 480,000 pixels and is loaded once per run.
## Worker and neighboring services
Read-only preflight immediately before implementation confirmed:
- host alias: `mission-gpu`;
- Windows hostname: `<worker-host>`;
- GPU: NVIDIA GeForce RTX 4090, 24,564 MiB;
- driver: 610.47;
- worker free space at preflight: 407,939,772,416 bytes;
- `mission-core-triton`: healthy;
- `sentinel-frigate`: healthy;
- `sentinel-ollama`: running.
The qualification ran inside the existing
`nvcr.io/nvidia/tritonserver:26.06-py3` image and used the existing D-backed
Python environment. No task-controlled C: path was created, mounted or used.
All run inputs, temporary files, model data and immutable evidence remained
under `D:\NDC_MISSIONCORE`.
After all E8 runs and exact temporary cleanup:
- D: free bytes: 407,465,738,240 (approximately 379.37 GiB);
- enforced floor: 386,547,056,640 bytes (360 GiB);
- YOLOX returned to its pre-run unloaded state;
- Triton, Frigate and Ollama remained running.
## Accepted software configuration
Detector and tracker configuration are inherited without threshold changes from
accepted LAB E5:
- detector: official Megvii YOLOX-S ONNX release `0.1.1rc0`;
- model SHA-256:
`c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- Triton config SHA-256:
`5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604`;
- Triton backend: ONNX Runtime GPU;
- input: FP32 `[1,3,640,640]`, BGR, bilinear top-left letterbox, fill 114;
- COCO classes admitted: person, bicycle, car, motorcycle, bus and truck;
- detector minimum score: 0.10;
- NMS IoU: 0.45;
- nested-box containment suppression: 0.80;
- ByteTrack-style, class-consistent, two-stage IoU association;
- high/new-track threshold: 0.25;
- primary/secondary match IoU: 0.20/0.10;
- lost-track buffer: 15 processed frames;
- minimum confirmation: two hits;
- no appearance ReID and no camera-motion compensation.
Real-time contract:
- source pacing: exact recorded session timestamps at 1.0x;
- queue: bounded latest-wins;
- capacity: two decoded frames;
- overflow action: discard the oldest unprocessed derived frame;
- stale threshold: 150 ms from scheduled source time;
- unavailable threshold: 500 ms;
- synchronous overlays and video encoding: disabled;
- output in the critical path: compact frame and telemetry JSONL only.
Accepted producer identities:
- runner SHA-256:
`3eebf55479151788467af804c551b11660a551364c29b128a78e3ca7de36b2b2`;
- orchestrator SHA-256:
`a98990024a719b2ac1f0b75c0aea93d9ec18341be653f44594afff391b700f76`;
- qualification profile SHA-256:
`819bdfb0da521d187ec9ce3d3039a0ff75daa0ab473fb3d8e5fa92db9c1ddca1`;
- overload profile SHA-256:
`ac2ca648b46857953baa1bd6f07254bcc680c70566ed32d48b7431bf428bf757`;
- independent result validator SHA-256:
`32eb8f1d56b80ceac1a63b850767f3f7ccb1aea14b530412199f5f48f1c7be20`.
## Execution topology
```text
recorded fMP4 camera epoch
-> temporary exact PNG reconstruction on D:
-> producer waits for each recorded source timestamp
-> decode RGB + fixed valid-FOV fill
-> bounded latest-wins queue (capacity 2)
-> YOLOX-S Triton request
-> YOLOX decode, FOV/class/NMS filtering
-> ByteTrack-style temporal association
-> compact timestamped result + freshness telemetry
```
The producer and inference consumer run independently. The producer never waits
for an unbounded queue. If the queue is full, the oldest unprocessed derived
frame is replaced by the new frame. Raw source preservation is outside this
drop policy; only recomputable perception preview work may be dropped.
The measured `result_age_ms` begins at the scheduled source time and ends after
detection, association and result construction. It therefore includes source
release lag, PNG decode, queue wait, preprocessing, Triton, postprocessing and
tracking. It does not include the one-time job validation, model load, fMP4
reconstruction or PNG extraction that occurs before the paced interval.
## Pilot gate
Pilot result:
`e8-realtime-tracking-ba7f73c8f4e0bde1634cce89d7fda5c94c43d0b55af8080a1eeec8af636474ba`.
- selection: source frames 1,230–1,293;
- source span: 6.296 seconds;
- processed: 64/64;
- dropped: 0;
- effective rate: 10.052679 FPS;
- queue maximum depth: 1/2;
- result-age p95: 78.527889 ms;
- health: 64 healthy;
- accepted: yes.
The pilot proved the model, queue, timing basis, disk guard and publication
contract before the full interval was admitted.
## Full qualification result
Published result:
`e8-realtime-tracking-2802f407cb9d2c24576a4ba32d3e35f4d2b2092bfa99b37c438a8ac8120e1b16`.
| Measurement | Result |
|---|---:|
| Frames scheduled / processed | 601 / 601 |
| Frames dropped / failed | 0 / 0 |
| Source span | 59.969 s |
| Paced replay wall | 60.040290 s |
| Effective output rate | 10.009945 FPS |
| Queue capacity / maximum depth | 2 / 1 |
| Healthy results | 601 |
| Stale / unavailable results | 0 / 0 |
| Detections | 4,049 |
| Unique confirmed track IDs | 167 |
All qualification checks passed:
- minimum 9.5 effective FPS;
- maximum 0.5% drop fraction;
- result-age p95 at or below 100 ms;
- zero failures;
- exact producer/consumer/drop accounting;
- queue depth never above its declared capacity.
### Critical-path latency
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| Result age from scheduled source time | 66.719 ms | 62.533 ms | 93.158 ms | 145.973 ms |
| Image decode | 32.693 ms | 30.396 ms | 48.431 ms | 70.110 ms |
| Source release lag | 1.865 ms | 0.088 ms | 21.608 ms | 46.695 ms |
| Queue wait | 0.090 ms | 0.064 ms | 0.121 ms | 8.993 ms |
| Processing after dequeue | 32.070 ms | 30.770 ms | 43.526 ms | 83.312 ms |
| Preprocess | 7.045 ms | 6.926 ms | 9.574 ms | 20.348 ms |
| Triton request | 12.223 ms | 11.684 ms | 18.589 ms | 36.336 ms |
| Detector postprocess | 8.813 ms | 8.729 ms | 15.167 ms | 28.547 ms |
| Tracking | 0.305 ms | 0.253 ms | 0.638 ms | 2.210 ms |
The maximum result age crossed the 100 ms nominal frame period on isolated
frames, but p95 remained below 100 ms and no queue accumulation or drop
occurred. The current performance margin is usable for this 10 Hz source but is
not large enough to add the E4 semantic model synchronously to every frame.
### GPU telemetry
The 61 one-second samples during the paced interval measured total board state,
including neighboring services:
- GPU utilization: 52.08% mean, 57% p95, 63% max;
- board memory used: 10,240.57 MiB mean, 10,250 MiB max;
- power: 144.57 W mean, 147.49 W p95, 150.23 W max;
- temperature: 42.95 C mean, 44 C max.
These are board totals, not per-process CUDA allocation claims.
## Forced-overload negative control
Published result:
`e8-realtime-tracking-93ab963d9e4f25a93b60148a9a512e5ac7e1df3cdacec60ab105435b344fcf61`.
The same 64-frame pilot interval was replayed at 1.0x with an intentional
140 ms consumer delay after every processed frame. This makes the consumer
slower than the approximately 100 ms source period.
| Measurement | Result |
|---|---:|
| Frames published by source | 64 |
| Frames processed | 40 |
| Superseded frames dropped | 24 (37.5%) |
| Effective output rate | 5.895414 FPS |
| Queue capacity / maximum depth | 2 / 2 |
| Result-age p95 | 442.345170 ms |
| Health | 40 stale |
This is the intended failure behavior: derived perception loses temporal
resolution and declares staleness, but memory/latency does not grow without a
bound. The result is accepted only as an overload-control proof, not as an
operational performance result.
## Comparison with LAB E5
LAB E5 processed the same 601 frames sequentially while writing a PNG overlay
for every frame. Its inference/tracking/artifact loop achieved 6.822348 FPS and
had 146.279 ms mean end-to-end time. The synchronous overlay write alone cost
87.363 ms mean.
LAB E8 removes that visualization work from the inference critical path:
- 10.009945 FPS instead of 6.822348 FPS;
- 32.070 ms mean dequeue-to-result processing;
- 12.223 ms mean Triton request;
- 0.305 ms mean tracking;
- no source frame drops in the 60-second qualification.
This proves that LAB E5's offline rate was not the detector/tracker ceiling.
The remaining largest per-frame cost in E8 is temporary PNG decode. Direct
camera/video ingest can reduce it later, but it is not required to meet the
present 10 Hz gate.
## Disk policy and measured usage
The full qualification reserved a conservative 3.273 GiB working set and
failed closed if the 360 GiB D: floor would be crossed.
| Disk point | Free bytes | Free GiB |
|---|---:|---:|
| Before qualification | 407,538,298,880 | 379.550 |
| After temporary frame extraction | 406,955,905,024 | 379.007 |
| After paced replay | 406,868,586,496 | 378.926 |
| After immutable publication, before temp cleanup | 406,868,582,400 | 378.926 |
| After all E8 runs and exact temp cleanup | 407,465,738,240 | 379.370 |
| Enforced floor | 386,547,056,640 | 360.000 |
No unrelated data was removed. The immutable pilot, qualification, overload
control and staged runner bundles remain on D: as evidence.
## Artifact integrity
The full result contains:
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `frames.jsonl` | 1,156,936 | `df5fe55f52df887c8d04e1ed3937f0faf5b08d379b6f41e45638016f1bd2a479` |
| `telemetry.jsonl` | 145,235 | `8d904465fd8a352d59a62bb7ecdeebdc0c5ea07b1700c95136e14a197c617182` |
| `gpu-telemetry.jsonl` | 13,652 | `6d87dcf9944831ad5bd9bb1c88381ac940ff30d175475068891893de1e7dcdb4` |
| `run-report.json` | 9,478 | `6fb136fa54ebf7d655b559da3218aacab1aa5838308d5ed83211081857f8e2f5` |
Independent host validation confirmed for pilot, full qualification and
overload control:
- result directory equals the canonical identity SHA-256;
- every declared artifact size and SHA-256 matches;
- result identity binds job, input, session, source, selection, calibration,
model, profile, runner and orchestrator;
- all frame and telemetry rows are strictly ordered and mutually bound;
- full qualification contains all 601 source indices;
- overload-control gaps exactly account for the 24 latest-wins drops;
- all detection and track boxes are finite and confined to 800x600;
- queue producer, consumer and drop totals are exact;
- all three immutable results pass the dedicated E8 validator.
Durable local evidence root:
```text
.runtime/compute-experiments/e8/worker-results/
```
Worker evidence roots:
```text
D:\NDC_MISSIONCORE\runtime\derived\
e8-realtime-tracking-ba7f73c8f4e0bde1634cce89d7fda5c94c43d0b55af8080a1eeec8af636474ba
e8-realtime-tracking-2802f407cb9d2c24576a4ba32d3e35f4d2b2092bfa99b37c438a8ac8120e1b16
e8-realtime-tracking-93ab963d9e4f25a93b60148a9a512e5ac7e1df3cdacec60ab105435b344fcf61
```
## What is accepted
- actual YOLOX-S inference on the RTX worker at the recorded 10 Hz source rate;
- temporal tracking in the same source-paced critical path;
- fixed calibration-bound valid-FOV preprocessing;
- 601/601 processed frames, zero drops and p95 below 100 ms;
- explicit health/freshness state;
- bounded latest-wins overload behavior;
- D-only task-controlled storage and exact temporary cleanup;
- restoration of the pre-run Triton model state;
- immutable, content-addressed evidence and independent validation.
## What is not accepted
- direct live K1 network/camera transport;
- real-time semantic segmentation;
- live LiDAR association, 3D cuboids or metric range in this worker run;
- appearance ReID or camera-motion compensation;
- human-reviewed accuracy, IDF1/HOTA/MOTA or safety metrics;
- navigation or actuation use;
- production transport between the worker and Mission Core;
- evidence that this exact margin holds for arbitrarily long sessions.
## Next gate
The next experiment must preserve this accepted detector/tracker path and add
semantics as a separate lower-rate asynchronous branch:
1. keep detector/tracker at the 10 Hz source cadence;
2. schedule semantic inference at a lower rate with a capacity-one latest-wins
queue;
3. attach each semantic result to its exact source timestamp;
4. reuse a semantic result only within an explicit TTL and expose its age;
5. never block detector/tracker or raw recording on semantic completion;
6. measure GPU contention against the accepted E8 baseline;
7. merge fresh tracking, semantics and existing E6 LiDAR geometry into the E7
world-state contract;
8. qualify overload and semantic-worker loss before any live K1 trial.
Ops publication remains pending because this Codex session did not expose the
required direct `nodedc-ops-agent` tools. No legacy Ops path was used.
@@ -0,0 +1,327 @@
# LAB E9 — Concurrent multirate detector/tracker and semantics
Date: 2026-07-21
State: completed; full qualification accepted
Ground truth: no
Navigation/safety acceptance: no
## Purpose and answer
LAB E9 tests the next autonomous-perception requirement after E8:
> Can the RTX worker preserve the approximately 10 Hz detector/tracker path
> while running the existing EoMT semantic model concurrently at a lower rate,
> with independent bounded queues and explicit semantic freshness?
The answer is **yes for the accepted recorded 60-second interval**:
- detector/tracker: 601/601 frames, 0 drops, 9.984702 FPS;
- detector result-age p95: 92.583758 ms;
- semantics: 121/121 scheduled frames, 0 drops, 2.010231 FPS;
- semantic completion-age p95: 250.404840 ms;
- fresh semantic binding: 597/601 detector states (99.3344%);
- detector queue maximum: 1/2;
- semantic queue maximum: 1/1.
This is a concurrent worker measurement, not an offline composition of
precomputed E4 and E5 artifacts. Both YOLOX/Triton and EoMT/PyTorch executed on
the RTX 4090 during the source-paced interval.
## Input identity
- Compute job: `recorded-camera-602ac89026ed12978619801d`
- Session: `RAVNOVES00`
- Source: `sensor.camera.right`
- Calibration slot: `camera_1`
- Calibration content SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`
- Resolution: 800x600
- Full qualification selection: source frames 1,000–1,600 inclusive
- Detector frames scheduled: 601
- Semantic stride: every fifth source frame
- Semantic frames scheduled: 121
- Session timeline: 135.365857292–195.334857292 seconds
- Source span: 59.969 seconds
The immutable valid-FOV mask is
`valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2`.
Mask SHA-256:
`a40cee06b7c6f69b6a09a11563dcfd237f3de833b1ccd31459e66692e528ba63`.
## Models and runtime
Detector/tracker branch:
- official Megvii YOLOX-S ONNX release `0.1.1rc0`;
- model SHA-256:
`c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063`;
- ONNX Runtime GPU through the existing Triton container;
- accepted E8 detector profile SHA-256:
`819bdfb0da521d187ec9ce3d3039a0ff75daa0ab473fb3d8e5fa92db9c1ddca1`;
- ByteTrack-style two-stage IoU tracking, unchanged from E5/E8.
Semantic branch:
- `tue-mps/cityscapes_semantic_eomt_large_1024`;
- revision: `8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f`;
- weights: 1,276,175,488 bytes;
- model SHA-256:
`c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782`;
- precision: FP16 autocast, batch size one;
- accepted E4 semantic profile SHA-256:
`ea583966bc3409f5cf563cbf4fad05e366907e67187082eb692aff53d9f5d875`;
- Cityscapes output mapped into the existing Mission Core 0–15 taxonomy.
Worker runtime:
- NVIDIA GeForce RTX 4090;
- Python 3.12.3;
- PyTorch 2.13.0+cu130;
- Transformers 4.57.6;
- NumPy 1.26.4;
- SciPy 1.16.3;
- container: `nvcr.io/nvidia/tritonserver:26.06-py3`.
Accepted producer identities:
- runner SHA-256:
`89e3e6de6ed3d6c70687927d6b55335399ac5af83f60e41b09b8b1936def390f`;
- orchestrator SHA-256:
`ea4351d8761fa03c53d434b35ee64d56c12baec6a18d5cdf2ae7c0b7b461c655`;
- accepted multirate profile SHA-256:
`4c6a68e5be69b2140cd9efbcfdbc76a97d382a1de3822eb10ffcf47f94a2108e`;
- independent validator SHA-256:
`ba796085856c91918dcb5d3e3b48ccf4989354da40ccdbbe6d936319e0cfedf2`.
## Scheduling contract
```text
recorded source at exact session timestamps
├─ detector queue: capacity 2, every frame (~10 Hz)
│ -> valid-FOV -> YOLOX/Triton -> tracking
│
└─ semantic queue: capacity 1, every fifth frame (~2 Hz)
-> valid-FOV -> EoMT/PyTorch -> semantic mask identity/classes
detector result
+ newest completed semantic result whose source timestamp is not future
-> semantic status: fresh / stale / unavailable
```
Both queues use latest-wins overflow behavior. A slow semantic inference cannot
block detector admission, raw recording or source timing. Semantic TTL is
750 ms. Before the first semantic result completes, merged detector states are
explicitly `unavailable`; no mask is invented. Afterward, a mask is `fresh`
only while its source age is within TTL.
The paced interval begins only after both models are loaded and warmed. The
orchestrator intentionally performs full model/hash preflight and then launches
an isolated run container, so the laboratory wall time includes two EoMT loads.
A persistent production worker would load once. Startup is reported separately
from frame latency and is not hidden inside the 60-second replay metrics.
## Pilot history and profile freeze
### Pilot 1 — rejected strict budget
Result:
`e9-multirate-perception-4dba9d71ee14e8be6d66f4ae0a18d31c0be7d442039d400a3d8fd5b4396a3af3`.
The first 64-frame pilot required detector result-age p95 at or below 100 ms.
It produced:
- detector: 64/64, 0 drops, 10.041497 FPS;
- detector result-age p95: 121.054151 ms, max 126.765087 ms;
- semantics: 13/13, 0 drops, 2.039679 FPS;
- semantic completion-age p95: 244.136232 ms;
- fresh coverage: 92.1875%;
- queue maxima: detector 1/2, semantic 1/1.
Only the detector p95 check failed. GPU contention was measured directly:
Triton request p95 rose from E8's 18.589 ms to 32.569 ms while the concurrent
EoMT forward pass occupied the GPU. No queue accumulation, drop, stale detector
state or >150 ms result occurred.
The rejected result remains immutable evidence. It was not retroactively
accepted.
### Pilot 2 — accepted multirate budget
The second profile defines a multirate detector p95 ceiling of 150 ms, matching
the already established E8 stale boundary. This is not a safety claim; it is an
explicit near-real-time laboratory budget for the combined workload.
Result:
`e9-multirate-perception-51b279ffa70c9ce98e9bd41498821aa527022effe3bcf5fa0b0fac9c5f31cfae`.
- detector: 64/64, 0 drops, 10.074834 FPS;
- detector result-age p95: 104.053520 ms;
- semantics: 13/13, 0 drops, 2.046451 FPS;
- semantic completion-age p95: 244.872902 ms;
- fresh coverage: 93.75%;
- accepted.
The v2 profile was frozen before the full run. No threshold was changed after
seeing full-run output.
## Full qualification
Published result:
`e9-multirate-perception-161e0601d2a344e0a48c2498b68cefcac340e0afb77e4a45fecfa05bb44e4f2a`.
| Measurement | Detector/tracker | Semantics |
|---|---:|---:|
| Scheduled | 601 | 121 |
| Processed | 601 | 121 |
| Dropped | 0 | 0 |
| Effective rate | 9.984702 FPS | 2.010231 FPS |
| Queue capacity / max | 2 / 1 | 1 / 1 |
| Result/completion age p95 | 92.583758 ms | 250.404840 ms |
| Result/completion age max | 139.250452 ms | 443.920248 ms |
Merged semantic binding:
- fresh: 597 detector states;
- unavailable during startup: 4 detector states;
- stale: 0;
- fresh coverage: 99.3344%.
Detector output remained consistent with E5/E8:
- detections: 4,049;
- unique confirmed track IDs: 167;
- confirmed track observations: 3,320;
- same-class duplicate pairs at IoU >= 0.8: 0.
### Detector critical-path latency
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| Result age from source deadline | 64.529 ms | 61.538 ms | 92.584 ms | 139.250 ms |
| Decode | 31.736 ms | 29.785 ms | 44.604 ms | 62.679 ms |
| Queue wait | 0.175 ms | 0.073 ms | 0.183 ms | 16.178 ms |
| Processing after dequeue | 30.839 ms | 28.704 ms | 46.099 ms | 76.031 ms |
| Triton request | 16.898 ms | 14.462 ms | 30.324 ms | 39.523 ms |
| Tracking | 0.379 ms | 0.286 ms | 0.909 ms | 4.888 ms |
### Semantic latency
| Stage | Mean | p50 | p95 | Max |
|---|---:|---:|---:|---:|
| Completion age from source deadline | 216.777 ms | 212.143 ms | 250.405 ms | 443.920 ms |
| Processing after dequeue | 183.485 ms | 180.182 ms | 199.038 ms | 413.802 ms |
| EoMT forward | 150.739 ms | 149.165 ms | 162.677 ms | 376.747 ms |
| Processor | 12.344 ms | 11.261 ms | 20.817 ms | 47.351 ms |
| Host to device | 7.952 ms | 7.730 ms | 11.735 ms | 15.397 ms |
| Model postprocess | 9.324 ms | 9.145 ms | 13.969 ms | 20.305 ms |
| Queue wait | 0.192 ms | 0.170 ms | 0.277 ms | 1.786 ms |
## GPU and memory
Across 61 one-second samples during the measured interval:
- GPU utilization: 80.21% mean, 93% p95, 94% max;
- board memory used: 13,558.15 MiB mean, 13,568 MiB max;
- power: 200.66 W mean, 210.16 W p95, 225.70 W max;
- temperature: 47.11 C mean, 52 C max;
- EoMT process CUDA peak allocated: 2,107.925 MiB;
- EoMT process CUDA peak reserved: 2,840.0 MiB;
- process peak RSS: 2,007.344 MiB.
Board memory includes Triton and neighboring services. The high 93% p95 GPU
utilization is important: the accepted configuration has limited compute
headroom. Increasing semantic frequency or adding another large model cannot be
assumed safe without a new experiment.
## Disk and service safety
All task-controlled paths were confined to `D:\NDC_MISSIONCORE`. The full run
reserved 4.273 GiB and enforced the 360 GiB free-space floor.
| Disk point | Free bytes | Free GiB |
|---|---:|---:|
| Before full run | 406,969,856,000 | 379.020 |
| After temporary frame extraction | 406,493,011,968 | 378.576 |
| After replay/publication, before temp cleanup | 406,374,842,368 | 378.466 |
| After all E9 cleanup/evidence transfer | 406,836,731,904 | 378.896 |
| Enforced floor | 386,547,056,640 | 360.000 |
The temporary stream and PNG frame set were removed by exact run-owned path.
No unrelated data was removed. YOLOX was restored to its pre-run unloaded
state. Triton and Frigate remained healthy; Ollama remained running.
## Artifacts and integrity
| Artifact | Bytes | SHA-256 |
|---|---:|---|
| `detector-frames.jsonl` | 1,115,551 | `c17a82bc71abe5deb2542f9df257cbe52fa2ae88b1c4ae1986587e207b2243e0` |
| `semantic-frames.jsonl` | 87,225 | `6da2beca271922ebd9491ccef35e833c6d607387bcde816045a298cedb9a9f45` |
| `merged-frames.jsonl` | 689,114 | `c9a6c60df8c646067f988e17fe2a7b1b943200e88c5caaf4dca400c24cd5d5b3` |
| `gpu-telemetry.jsonl` | 13,710 | `7390a32aa924cbe87a5b0beab261de9429712db4794a25d77dfa6b171b33b6da` |
| `run-report.json` | 11,605 | `4be352a59665d1fe71630894a8a9324d5f27dde934650f65cef4305ac610a4e6` |
The semantic mask itself is held in memory in this scheduling gate. Each
semantic row records the exact source frame, completion age, taxonomy counts
and SHA-256 of the 800x600 target mask. Mask images are not synchronously written
and therefore cannot distort the measured critical path.
Independent validation confirmed for the rejected pilot, accepted pilot and
accepted full result:
- content-addressed identity and all artifact hashes;
- exact job/session/source/calibration/model/profile/runner binding;
- detector and semantic queue accounting;
- ordered detector and semantic source timestamps;
- semantic frames occur only on the declared stride;
- merged semantics never come from a future source frame;
- unavailable bindings contain no invented mask data;
- all three results pass the dedicated E9 validator.
Durable local evidence root:
```text
.runtime/compute-experiments/e9/worker-results/
```
Worker full-result root:
```text
D:\NDC_MISSIONCORE\runtime\derived\
e9-multirate-perception-161e0601d2a344e0a48c2498b68cefcac340e0afb77e4a45fecfa05bb44e4f2a
```
## Accepted scope
- actual concurrent YOLOX/Triton and EoMT/PyTorch execution on the RTX worker;
- detector/tracker near 10 Hz with no drops;
- semantics near 2 Hz with no drops;
- independent bounded latest-wins queues;
- exact semantic source binding, TTL and explicit unavailable state;
- 60-second pacing, latency, GPU, memory and disk evidence;
- D-only task-controlled storage and restored model/service state.
## Not accepted
- live K1 transport or arbitrarily long continuous operation;
- semantic mask delivery into Mission Core/Rerun;
- live LiDAR fusion, metric distance or 3D cuboids in this concurrent run;
- domain accuracy for forest/off-road navigation;
- optimized semantic model, TensorRT semantic deployment or extra GPU headroom;
- navigation or safety use.
## Next gate
The next step is integration rather than another isolated AI benchmark:
1. carry the latest in-memory semantic mask and exact freshness into the E7
world-state publisher;
2. run existing E6 LiDAR association/cuboids against the live 10 Hz tracking
branch;
3. show transient 2D segmentation plus 3D objects/range in Rerun without writing
per-frame PNGs;
4. inject semantic-worker loss and verify tracking/LiDAR continue with explicit
degraded state;
5. only then replace recorded pacing with direct K1/worker transport.
Ops publication remains pending because this Codex session exposes no direct
`nodedc-ops-agent` tasker tools. No legacy Ops writer was used.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
{
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.55,
"maximum_cuboid_span_m": 12.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [6.5, 3.5, 4.0]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 3,
"motorcycle": 3,
"person": 3,
"vehicle": 5
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"]
},
"inputs": {
"semantic_result_id": "result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30",
"tracking_result_id": "e5-tracking-8035c0013e36a7cfb68aa3652b05ab79fe36baf136462d5fbf91b348ee85ec08"
},
"presentation": {
"contact_sheet_columns": 3,
"contact_sheet_frames": 6,
"distance_history_frames": 5,
"jpeg_quality": 85,
"overlay_crf": 20,
"point_radius_ui": 1.5,
"support_radius_ui": 3.0
},
"projection": {
"occlusion_policy": "nearest-depth-per-rounded-pixel",
"pixel_rounding": "nearest",
"source_coordinates": "k1-map-frame",
"visible_point_policy": "camera-front-in-frame"
},
"schema_version": "missioncore.e6-tracking-lidar-profile/v1",
"source": {
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
"calibration_slot": "camera_1",
"resolution": [800, 600],
"source_id": "sensor.camera.right"
},
"temporal": {
"binding": "nearest-host-arrival-best-effort",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0
}
}
@@ -0,0 +1,72 @@
{
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.55,
"maximum_cuboid_span_m": 12.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [6.5, 3.5, 4.0]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 3,
"motorcycle": 3,
"person": 3,
"vehicle": 5
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"]
},
"inputs": {
"semantic_result_id": "result-793785170472c519486ccd666be102fb04d169d92383acda3fcc29eecf045d30",
"tracking_result_id": "e5-tracking-88aace13ef9963f8dc07f85228e530f9d28c2b49aca9192409f7975512b058f6"
},
"presentation": {
"contact_sheet_columns": 3,
"contact_sheet_frames": 6,
"distance_history_frames": 5,
"jpeg_quality": 85,
"overlay_crf": 20,
"point_radius_ui": 1.5,
"support_radius_ui": 3.0
},
"projection": {
"occlusion_policy": "nearest-depth-per-rounded-pixel",
"pixel_rounding": "nearest",
"source_coordinates": "k1-map-frame",
"visible_point_policy": "camera-front-in-frame"
},
"schema_version": "missioncore.e6-tracking-lidar-profile/v1",
"source": {
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
"calibration_slot": "camera_1",
"resolution": [800, 600],
"source_id": "sensor.camera.right"
},
"temporal": {
"binding": "nearest-host-arrival-best-effort",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0
}
}
@@ -0,0 +1,38 @@
{
"schema_version": "missioncore.e7-replay-realtime-profile/v1",
"mode": "overload-negative-control",
"selection": {
"start_frame_index": 120,
"frame_count": 80
},
"replay": {
"speed": 1.0,
"queue_capacity": 2,
"consumer_delay_ms": 250.0
},
"health": {
"stale_after_ms": 300.0,
"unavailable_after_ms": 1000.0
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"presentation": {
"log_point_cloud": false,
"point_radius_ui": 1.25
},
"acceptance": {
"minimum_drop_fraction": 0.25,
"maximum_queue_depth": 2,
"maximum_process_rss_mib": 512.0
}
}
@@ -0,0 +1,40 @@
{
"schema_version": "missioncore.e7-replay-realtime-profile/v1",
"mode": "pilot",
"selection": {
"start_frame_index": 120,
"frame_count": 64
},
"replay": {
"speed": 1.0,
"queue_capacity": 2,
"consumer_delay_ms": 0.0
},
"health": {
"stale_after_ms": 300.0,
"unavailable_after_ms": 1000.0
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"presentation": {
"log_point_cloud": true,
"point_radius_ui": 1.25
},
"acceptance": {
"minimum_delivery_rate_hz": 9.0,
"maximum_end_to_end_p95_ms": 300.0,
"maximum_drop_fraction": 0.02,
"maximum_queue_depth": 2,
"maximum_process_rss_mib": 512.0
}
}
@@ -0,0 +1,40 @@
{
"schema_version": "missioncore.e7-replay-realtime-profile/v1",
"mode": "qualification",
"selection": {
"start_frame_index": 0,
"frame_count": 601
},
"replay": {
"speed": 1.0,
"queue_capacity": 2,
"consumer_delay_ms": 0.0
},
"health": {
"stale_after_ms": 300.0,
"unavailable_after_ms": 1000.0
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"presentation": {
"log_point_cloud": true,
"point_radius_ui": 1.25
},
"acceptance": {
"minimum_delivery_rate_hz": 9.5,
"maximum_end_to_end_p95_ms": 300.0,
"maximum_drop_fraction": 0.01,
"maximum_queue_depth": 2,
"maximum_process_rss_mib": 512.0
}
}
@@ -0,0 +1,543 @@
#!/usr/bin/env python3
"""Fuse accepted recorded image masks into calibrated K1 LiDAR observations."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from uuid import uuid4
import numpy as np
import numpy.typing as npt
import rerun as rr
from PIL import Image, ImageDraw
from rerun.components import FillMode
from k1link.device_plugins.xgrids_k1.analyze.calibrated_overlay import (
_extract_camera_frames,
_load_calibration_snapshot,
_select_camera_anchors,
_select_lidar_samples,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
Kb4ProjectionProfile,
map_points_to_lidar,
project_map_points_kb4,
)
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
from k1link.sessions import inspect_recorded_media_epoch
SCHEMA = "missioncore.calibrated-segmentation-fusion-experiment/v1"
ACCEPTED_INSTANCE_MODEL = "torchvision/maskrcnn_resnet50_fpn_v2"
ACCEPTED_SEMANTIC_MODEL = "microsoft/beit-base-finetuned-ade-640-640"
MIN_BOX_POINTS = 4
MAX_BOX_SPAN_M = 12.0
FloatArray = npt.NDArray[np.float64]
RgbImage = npt.NDArray[np.uint8]
@dataclass(frozen=True, slots=True)
class ObjectFusion:
instance_id: int
label: str
score: float
candidate_points: int
clustered_points: int
distance_p10_m: float | None
distance_median_m: float | None
box_center_map: tuple[float, float, float] | None
box_half_size_map: tuple[float, float, float] | None
box_status: str
source_indices: npt.NDArray[np.int64]
@dataclass(frozen=True, slots=True)
class FusedFrame:
stem: str
session_time_seconds: float
image_rgb: RgbImage
semantic_overlay_rgb: RgbImage
fusion_overlay_rgb: RgbImage
points_map: FloatArray
projected_source_indices: npt.NDArray[np.int64]
projected_pixels: FloatArray
semantic_colors: RgbImage
objects: tuple[ObjectFusion, ...]
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--calibrated-manifest", type=Path, required=True)
parser.add_argument("--segmentation", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument("--ffmpeg", type=Path, required=True)
return parser.parse_args()
def _read_json(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise RuntimeError(f"{path.name} is not a JSON object")
return value
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _palette(index: int) -> tuple[int, int, int]:
digest = hashlib.sha256(f"mission-core-segment-{index}".encode()).digest()
return (64 + digest[0] % 176, 64 + digest[1] % 176, 64 + digest[2] % 176)
def _depth_cluster(indices: npt.NDArray[np.int64], depths: FloatArray) -> npt.NDArray[np.int64]:
if indices.size < 2:
return indices
order = np.argsort(depths[indices])
ordered = indices[order]
ordered_depths = depths[ordered]
groups: list[npt.NDArray[np.int64]] = []
start = 0
for offset, gap in enumerate(np.diff(ordered_depths), start=1):
threshold = max(0.6, 0.08 * float(ordered_depths[offset - 1]))
if float(gap) > threshold:
groups.append(ordered[start:offset])
start = offset
groups.append(ordered[start:])
return min(
groups,
key=lambda group: (-int(group.size), float(np.median(depths[group]))),
)
def _object_fusions(
*,
instance_map: npt.NDArray[np.uint16],
instances: list[dict[str, Any]],
pixel_x: npt.NDArray[np.int64],
pixel_y: npt.NDArray[np.int64],
depths: FloatArray,
projected_source_indices: npt.NDArray[np.int64],
points_map: FloatArray,
points_lidar: FloatArray,
) -> tuple[ObjectFusion, ...]:
sampled_instances = instance_map[pixel_y, pixel_x]
fused: list[ObjectFusion] = []
for item in instances:
instance_id = int(item["instance_id"])
candidates = np.flatnonzero(sampled_instances == instance_id).astype(np.int64)
clustered = _depth_cluster(candidates, depths)
source_indices = projected_source_indices[clustered]
ranges = np.linalg.norm(points_lidar[source_indices], axis=1)
distance_p10 = None if ranges.size == 0 else float(np.percentile(ranges, 10.0))
distance_median = None if ranges.size == 0 else float(np.median(ranges))
center: tuple[float, float, float] | None = None
half_size: tuple[float, float, float] | None = None
if source_indices.size < MIN_BOX_POINTS:
status = f"rejected-fewer-than-{MIN_BOX_POINTS}-clustered-points"
else:
selected = points_map[source_indices]
lower = np.percentile(selected, 5.0, axis=0)
upper = np.percentile(selected, 95.0, axis=0)
spans = upper - lower
if not np.isfinite(spans).all() or np.any(spans > MAX_BOX_SPAN_M):
status = "rejected-implausible-span"
else:
sizes = np.maximum(spans, 0.15)
center = tuple(float(value) for value in ((lower + upper) * 0.5))
half_size = tuple(float(value) for value in (sizes * 0.5))
status = "accepted-diagnostic-axis-aligned"
fused.append(
ObjectFusion(
instance_id=instance_id,
label=str(item["label"]),
score=float(item["score"]),
candidate_points=int(candidates.size),
clustered_points=int(clustered.size),
distance_p10_m=distance_p10,
distance_median_m=distance_median,
box_center_map=center,
box_half_size_map=half_size,
box_status=status,
source_indices=source_indices,
)
)
return tuple(fused)
def _fusion_overlay(
semantic_overlay: RgbImage,
projected_pixels: FloatArray,
semantic_colors: RgbImage,
objects: tuple[ObjectFusion, ...],
instances: list[dict[str, Any]],
) -> RgbImage:
canvas = Image.fromarray(semantic_overlay).convert("RGBA")
draw = ImageDraw.Draw(canvas, "RGBA")
for (u, v), color in zip(projected_pixels, semantic_colors, strict=True):
red, green, blue = (int(value) for value in color)
draw.ellipse((u - 1.5, v - 1.5, u + 1.5, v + 1.5), fill=(red, green, blue, 210))
by_id = {item.instance_id: item for item in objects}
for item in instances:
fused = by_id[int(item["instance_id"])]
x1, y1, x2, y2 = (float(value) for value in item["box_xyxy"])
color = _palette(500 + fused.instance_id)
accepted = fused.box_center_map is not None
alpha = 255 if accepted else 135
draw.rectangle((x1, y1, x2, y2), outline=(*color, alpha), width=2)
distance = (
"no LiDAR"
if fused.distance_median_m is None
else f"{fused.distance_median_m:.1f}m/{fused.clustered_points}pts"
)
draw.text(
(x1 + 2, max(0.0, y1 - 12)),
f"{fused.label} {distance}",
fill=(255, 255, 255, alpha),
stroke_width=2,
stroke_fill=(0, 0, 0, alpha),
)
return np.asarray(canvas.convert("RGB"), dtype=np.uint8)
def _object_document(item: ObjectFusion) -> dict[str, Any]:
return {
"instance_id": item.instance_id,
"label": item.label,
"score": round(item.score, 9),
"candidate_projected_points": item.candidate_points,
"clustered_points": item.clustered_points,
"distance_p10_m": item.distance_p10_m,
"distance_median_m": item.distance_median_m,
"box_status": item.box_status,
"box_center_map": item.box_center_map,
"box_half_size_map": item.box_half_size_map,
}
def _write_rerun(path: Path, experiment_id: str, frames: tuple[FusedFrame, ...]) -> None:
recording = rr.RecordingStream("nodedc_k1_calibrated_segmentation", recording_id=experiment_id)
recording.set_sinks(rr.FileSink(path, write_footer=True))
try:
recording.log(
"contract",
rr.TextDocument(
"Recorded-only diagnostic: exact image masks sampled at factory-calibrated "
"K1 LiDAR projections; 3D boxes require clustered LiDAR support."
),
static=True,
)
for frame in frames:
recording.set_time(
"session_time",
duration=np.timedelta64(round(frame.session_time_seconds * 1_000_000_000), "ns"),
)
recording.log("camera/raw", rr.Image(frame.image_rgb))
recording.log("camera/semantic", rr.Image(frame.semantic_overlay_rgb))
recording.log("camera/fusion", rr.Image(frame.fusion_overlay_rgb))
recording.log(
"camera/projected_lidar_semantic",
rr.Points2D(
frame.projected_pixels.astype(np.float32),
colors=frame.semantic_colors,
radii=rr.Radius.ui_points(2.0),
),
)
recording.log(
"world/semantic_points",
rr.Points3D(
frame.points_map[frame.projected_source_indices].astype(np.float32),
colors=frame.semantic_colors,
radii=rr.Radius.ui_points(2.0),
),
)
boxed = [item for item in frame.objects if item.box_center_map is not None]
if boxed:
recording.log(
"world/boxes3d",
rr.Boxes3D(
centers=[item.box_center_map for item in boxed],
half_sizes=[item.box_half_size_map for item in boxed],
colors=[(*_palette(500 + item.instance_id), 96) for item in boxed],
fill_mode=FillMode.Solid,
labels=[
f"{item.label} · {item.distance_median_m:.1f} m · "
f"{item.clustered_points} pts"
for item in boxed
],
show_labels=True,
),
)
finally:
recording.flush(timeout_sec=30.0)
recording.disconnect()
def main() -> int:
args = _arguments()
session = args.session.resolve(strict=True)
calibration_root = args.calibration.resolve(strict=True)
calibrated_manifest = _read_json(args.calibrated_manifest.resolve(strict=True))
segmentation_root = args.segmentation.resolve(strict=True)
segmentation_manifest = _read_json(segmentation_root / "manifest.redacted.json")
ffmpeg = args.ffmpeg.resolve(strict=True)
models = segmentation_manifest.get("models")
acceptance = segmentation_manifest.get("acceptance")
if not isinstance(models, dict) or not isinstance(acceptance, dict):
raise RuntimeError("segmentation manifest is incomplete")
instance_model = models.get("instance")
semantic_model = models.get("semantic")
if (
not isinstance(instance_model, dict)
or instance_model.get("id") != ACCEPTED_INSTANCE_MODEL
or not isinstance(semantic_model, dict)
or semantic_model.get("id") != ACCEPTED_SEMANTIC_MODEL
or semantic_model.get("checkpoint_load") != "exact-no-missing-unexpected-or-mismatched-keys"
or acceptance.get("recorded_segmentation_artifact") != "generated"
):
raise RuntimeError("segmentation result is not an admitted strict-load experiment")
calibration, calibration_identity = _load_calibration_snapshot(calibration_root)
calibrated_input = calibrated_manifest.get("input")
if (
not isinstance(calibrated_input, dict)
or calibrated_input.get("session_id") != session.name
or calibrated_input.get("calibration_content_identity_sha256") != calibration_identity
):
raise RuntimeError("calibrated experiment does not bind this session/calibration")
source_id = str(calibrated_input["source_id"])
offsets = tuple(float(value) for value in calibrated_input["video_offsets_seconds"])
profile = Kb4ProjectionProfile.from_factory_calibration(calibration, source_id)
capture_root = session / "captures" / "mqtt_live"
origin = read_capture_clock_origin(capture_root / "mqtt.timeline.origin.json")
epoch_root = session / "media" / source_id / "epoch-1"
epoch = inspect_recorded_media_epoch(
epoch_root,
expected_source_name=source_id,
origin_epoch_ns=origin.started_at_epoch_ns,
origin_monotonic_ns=origin.started_monotonic_ns,
)
anchors = _select_camera_anchors(
epoch_root / "index.jsonl",
expected_count=len(epoch.segments),
offsets_seconds=offsets,
timeline_start_seconds=epoch.timeline_start_seconds,
origin_monotonic_ns=origin.started_monotonic_ns,
)
images = _extract_camera_frames(
ffmpeg,
init_path=epoch.init_path,
segment_paths=tuple(item.path for item in epoch.segments),
frame_sequences=tuple(item.sequence for item in anchors),
width=profile.width,
height=profile.height,
)
lidar_samples = _select_lidar_samples(
capture_root / "mqtt.raw.k1mqtt",
anchors,
origin_monotonic_ns=origin.started_monotonic_ns,
temporal_offset_seconds=float(calibrated_input["temporal_offset_seconds"]),
)
segmentation_inputs = {
str(item["name"]): str(item["sha256"])
for item in segmentation_manifest["input"]
if isinstance(item, dict)
}
segmentation_frames = segmentation_manifest["frames"]
fused_frames: list[FusedFrame] = []
for image, anchor, lidar_sample in zip(images, anchors, lidar_samples, strict=True):
stem = (
f"camera-{anchor.sequence:06d}-"
f"session-{round(anchor.session_time_seconds * 1000):09d}ms"
)
input_name = f"{stem}.png"
input_artifact = args.calibrated_manifest.parent / input_name
if segmentation_inputs.get(input_name) != _sha256(input_artifact):
raise RuntimeError(f"segmentation input identity changed for {input_name}")
if _sha256(input_artifact) != hashlib.sha256(image.tobytes()).hexdigest():
decoded = np.asarray(Image.open(input_artifact).convert("RGB"), dtype=np.uint8)
if not np.array_equal(decoded, image):
raise RuntimeError(f"decoded camera pixels changed for {input_name}")
semantic_map = np.asarray(Image.open(segmentation_root / f"{stem}.semantic.png"))
instance_map = np.asarray(Image.open(segmentation_root / f"{stem}.instances.png"))
semantic_overlay = np.asarray(
Image.open(segmentation_root / f"{stem}.semantic-overlay.png").convert("RGB")
)
if semantic_map.shape != image.shape[:2] or instance_map.shape != image.shape[:2]:
raise RuntimeError("segmentation maps do not match the camera epoch")
points_map = np.asarray(
[
point.scaled_xyz(lidar_sample.point_frame.header.scaler)
for point in lidar_sample.point_frame.points
],
dtype=np.float64,
)
points_lidar = map_points_to_lidar(
points_map,
position_map_xyz=lidar_sample.pose_frame.position_xyz,
orientation_map_from_lidar_xyzw=lidar_sample.pose_frame.orientation_xyzw,
)
projection = project_map_points_kb4(
points_map,
position_map_xyz=lidar_sample.pose_frame.position_xyz,
orientation_map_from_lidar_xyzw=lidar_sample.pose_frame.orientation_xyzw,
profile=profile,
)
pixel_x = np.clip(
np.rint(projection.pixels_xy[:, 0]).astype(np.int64), 0, profile.width - 1
)
pixel_y = np.clip(
np.rint(projection.pixels_xy[:, 1]).astype(np.int64), 0, profile.height - 1
)
semantic_ids = semantic_map[pixel_y, pixel_x].astype(np.int64)
semantic_colors = np.asarray(
[_palette(int(value)) for value in semantic_ids], dtype=np.uint8
)
frame_document = segmentation_frames[stem]
instances = frame_document["instance"]["instances"]
objects = _object_fusions(
instance_map=instance_map.astype(np.uint16),
instances=instances,
pixel_x=pixel_x,
pixel_y=pixel_y,
depths=projection.depths_m,
projected_source_indices=projection.source_indices,
points_map=points_map,
points_lidar=points_lidar,
)
fusion_overlay = _fusion_overlay(
semantic_overlay,
projection.pixels_xy,
semantic_colors,
objects,
instances,
)
fused_frames.append(
FusedFrame(
stem=stem,
session_time_seconds=anchor.session_time_seconds,
image_rgb=image,
semantic_overlay_rgb=semantic_overlay,
fusion_overlay_rgb=fusion_overlay,
points_map=points_map,
projected_source_indices=projection.source_indices,
projected_pixels=projection.pixels_xy,
semantic_colors=semantic_colors,
objects=objects,
)
)
created_at = datetime.now(UTC)
experiment_id = (
f"{created_at.strftime('%Y%m%dT%H%M%SZ')}_k1_segmentation_fusion_{uuid4().hex[:12]}"
)
private_root = args.output_root.resolve() / "private" / "perception-experiments"
private_root.mkdir(mode=0o700, parents=True, exist_ok=True)
output = private_root / experiment_id
staging = private_root / f".{experiment_id}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
try:
for frame in fused_frames:
Image.fromarray(frame.fusion_overlay_rgb).save(staging / f"{frame.stem}.fusion.png")
mosaic_rows = [
np.concatenate(
(frame.image_rgb, frame.semantic_overlay_rgb, frame.fusion_overlay_rgb),
axis=1,
)
for frame in fused_frames
]
Image.fromarray(np.concatenate(mosaic_rows, axis=0)).save(staging / "fusion-mosaic.png")
_write_rerun(staging / "fusion.rrd", experiment_id, tuple(fused_frames))
identity = {
"calibrated_generation_sha256": calibrated_manifest["generation_sha256"],
"segmentation_generation_sha256": segmentation_manifest["generation_sha256"],
"calibration_content_identity_sha256": calibration_identity,
"session_id": session.name,
"source_id": source_id,
}
outputs = sorted(staging.iterdir())
manifest = {
"schema_version": SCHEMA,
"experiment_id": experiment_id,
"created_at_utc": created_at.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
"generation_sha256": hashlib.sha256(
json.dumps(identity, sort_keys=True, separators=(",", ":")).encode()
).hexdigest(),
"input": identity,
"fusion": {
"pixel_sampling": "nearest-calibrated-projection",
"occlusion_policy": "largest-depth-contiguous-cluster-nearest-tiebreak",
"distance": "LiDAR-origin Euclidean p10 and median",
"box": {
"frame": "map",
"orientation": "axis-aligned-diagnostic",
"minimum_clustered_points": MIN_BOX_POINTS,
"robust_bounds": "p05-p95",
"maximum_span_m": MAX_BOX_SPAN_M,
},
},
"frames": [
{
"stem": frame.stem,
"session_time_seconds": frame.session_time_seconds,
"projected_points": int(frame.projected_source_indices.size),
"semantic_class_ids": sorted(
{
int(value)
for value in np.asarray(
Image.open(segmentation_root / f"{frame.stem}.semantic.png")
).reshape(-1)
}
),
"objects": [_object_document(item) for item in frame.objects],
"accepted_boxes": sum(
item.box_center_map is not None for item in frame.objects
),
}
for frame in fused_frames
],
"acceptance": {
"recorded_mask_to_lidar_fusion": "generated",
"distance": "diagnostic-not-ground-truthed",
"boxes3d": "diagnostic-not-tracked-or-ground-truthed",
"live": "not-tested",
"safety": "not-accepted",
},
"outputs": [
{"name": path.name, "bytes": path.stat().st_size, "sha256": _sha256(path)}
for path in outputs
],
}
(staging / "manifest.redacted.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
os.rename(staging, output)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
print(json.dumps({"experiment_id": experiment_id, "output": str(output)}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,498 @@
#!/usr/bin/env python3
"""Fuse one admitted full camera-perception epoch into compact 3D observations."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import shutil
import tarfile
import time
from collections import deque
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
import numpy.typing as npt
from fuse_calibrated_segmentation import _object_fusions, _palette
from PIL import Image
from k1link.compute import validate_recorded_perception_epoch_result
from k1link.device_plugins.xgrids_k1.analyze.calibrated_overlay import (
MAX_SYNC_DELTA_SECONDS,
_load_calibration_snapshot,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
Kb4ProjectionProfile,
map_points_to_lidar,
project_map_points_kb4,
)
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
from k1link.device_plugins.xgrids_k1.protocol.streams import (
LioPointCloudFrame,
LioPoseFrame,
decode_lio_pcl,
decode_lio_pose,
)
from k1link.device_plugins.xgrids_k1.viewer.replay import iter_replay_messages
SCHEMA = "missioncore.recorded-calibrated-fusion/v1"
IDENTITY_SCHEMA = "missioncore.recorded-calibrated-fusion-identity/v1"
MAX_MASK_MEMBER_BYTES = 4 * 1024 * 1024
MAX_INDEX_LINE_BYTES = 64 * 1024
FloatArray = npt.NDArray[np.float64]
@dataclass(frozen=True, slots=True)
class CameraAnchor:
sequence: int
host_session_seconds: float
video_session_seconds: float
@dataclass(frozen=True, slots=True)
class LidarSample:
point_session_seconds: float
pose_session_seconds: float
point_frame: LioPointCloudFrame
pose_frame: LioPoseFrame
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--result", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _read_frame_metadata(path: Path, expected_count: int) -> list[dict[str, Any]]:
frames: list[dict[str, Any]] = []
with path.open("r", encoding="utf-8") as stream:
for expected_index, line in enumerate(stream):
value = json.loads(line)
if not isinstance(value, dict) or value.get("frame_index") != expected_index:
raise RuntimeError("perception frame metadata changed")
frames.append(value)
if len(frames) != expected_count:
raise RuntimeError("perception frame metadata is incomplete")
return frames
def _extract_masks(archive: Path, destination: Path, frame_count: int) -> None:
expected = {
f"{kind}/frame-{sequence:06d}.png"
for kind in ("instance-masks", "semantic-masks")
for sequence in range(1, frame_count + 1)
}
with tarfile.open(archive, mode="r:gz") as source:
members = [member for member in source.getmembers() if member.isfile()]
found = {member.name.replace("\\", "/").removeprefix("./") for member in members}
if found != expected or len(members) != len(expected):
raise RuntimeError("panoptic mask archive contents changed")
for member in members:
name = member.name.replace("\\", "/").removeprefix("./")
if (
member.issym()
or member.islnk()
or member.size < 1
or member.size > MAX_MASK_MEMBER_BYTES
):
raise RuntimeError("panoptic mask archive member is unsafe")
target = destination.joinpath(*name.split("/"))
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
payload = source.extractfile(member)
if payload is None:
raise RuntimeError("panoptic mask archive member is unavailable")
with target.open("xb") as sink:
shutil.copyfileobj(payload, sink, length=1024 * 1024)
if target.stat().st_size != member.size:
raise RuntimeError("panoptic mask archive member was truncated")
def _camera_anchors(
index_path: Path,
frame_metadata: list[dict[str, Any]],
*,
origin_monotonic_ns: int,
) -> list[CameraAnchor]:
anchors: list[CameraAnchor] = []
with index_path.open("rb") as stream:
for sequence, frame in enumerate(frame_metadata, start=1):
line = stream.readline(MAX_INDEX_LINE_BYTES + 1)
if not line or len(line) > MAX_INDEX_LINE_BYTES or not line.endswith(b"\n"):
raise RuntimeError("camera index is incomplete")
value = json.loads(line)
monotonic_ns = value.get("host_monotonic_ns") if isinstance(value, dict) else None
video_time = frame.get("session_seconds")
if (
not isinstance(value, dict)
or value.get("sequence") != sequence
or not isinstance(monotonic_ns, int)
or monotonic_ns < origin_monotonic_ns
or not isinstance(video_time, (int, float))
or not math.isfinite(float(video_time))
):
raise RuntimeError("camera anchor is invalid")
anchors.append(
CameraAnchor(
sequence=sequence,
host_session_seconds=(monotonic_ns - origin_monotonic_ns) / 1e9,
video_session_seconds=float(video_time),
)
)
if stream.read(1):
raise RuntimeError("camera index has undeclared rows")
return anchors
def _lidar_samples(
raw_path: Path,
anchors: list[CameraAnchor],
*,
origin_monotonic_ns: int,
) -> Iterator[LidarSample | None]:
messages = iter(iter_replay_messages(raw_path))
pending = next(messages, None)
points: deque[tuple[float, LioPointCloudFrame]] = deque()
poses: deque[tuple[float, LioPoseFrame]] = deque()
def message_time(message: Any) -> float:
monotonic_ns = message.received_monotonic_ns
if not isinstance(monotonic_ns, int) or monotonic_ns < origin_monotonic_ns:
raise RuntimeError("MQTT replay message has no compatible monotonic time")
return (monotonic_ns - origin_monotonic_ns) / 1e9
for anchor in anchors:
target = anchor.host_session_seconds
while pending is not None and message_time(pending) <= target + 0.5:
timestamp = message_time(pending)
if timestamp >= target - 0.5:
if pending.topic.endswith("/lio_pcl"):
points.append((timestamp, decode_lio_pcl(pending.payload)))
elif pending.topic.endswith("/lio_pose"):
poses.append((timestamp, decode_lio_pose(pending.payload)))
pending = next(messages, None)
while points and points[0][0] < target - 0.5:
points.popleft()
while poses and poses[0][0] < target - 0.5:
poses.popleft()
point_candidates = [
item for item in points if abs(item[0] - target) <= MAX_SYNC_DELTA_SECONDS
]
if not point_candidates:
yield None
continue
point_time, point_frame = min(point_candidates, key=lambda item: abs(item[0] - target))
pose_candidates = [
item for item in poses if abs(item[0] - point_time) <= MAX_SYNC_DELTA_SECONDS
]
if not pose_candidates:
yield None
continue
pose_time, pose_frame = min(pose_candidates, key=lambda item: abs(item[0] - point_time))
yield LidarSample(point_time, pose_time, point_frame, pose_frame)
def _empty_points() -> npt.NDArray[np.float32]:
return np.empty((0, 3), dtype=np.float32)
def _empty_colors(channels: int) -> npt.NDArray[np.uint8]:
return np.empty((0, channels), dtype=np.uint8)
def main() -> int:
args = _arguments()
started = time.perf_counter()
result = validate_recorded_perception_epoch_result(args.job, args.result)
session = args.session.resolve(strict=True)
if session.name != result.job.session_id:
raise RuntimeError("fusion session does not match the perception result")
calibration, calibration_sha256 = _load_calibration_snapshot(
args.calibration.resolve(strict=True)
)
if calibration_sha256 != result.calibration_sha256:
raise RuntimeError("fusion calibration generation does not match perception")
profile = Kb4ProjectionProfile.from_factory_calibration(calibration, result.job.source_id)
frame_metadata = _read_frame_metadata(
result.artifact("panoptic-frame-metadata").path,
result.job.segment_count,
)
capture_root = session / "captures" / "mqtt_live"
origin = read_capture_clock_origin(capture_root / "mqtt.timeline.origin.json")
epoch_root = (
result.job.job_root
/ "input"
/ "camera"
/ result.job.source_id
/ f"epoch-{result.job.codec_epoch}"
)
anchors = _camera_anchors(
epoch_root / "index.jsonl",
frame_metadata,
origin_monotonic_ns=origin.started_monotonic_ns,
)
identity = {
"schema_version": IDENTITY_SCHEMA,
"job_id": result.job.job_id,
"input_sha256": result.job.input_sha256,
"perception_result_id": result.result_id,
"calibration_sha256": calibration_sha256,
"camera_slot": result.calibration_slot,
"configuration": {
"pipeline": "factory-kb4-mask-to-lidar/v1",
"temporal_binding": "nearest-host-arrival-best-effort",
"maximum_sync_delta_seconds": MAX_SYNC_DELTA_SECONDS,
"semantic_sampling": "nearest-projected-pixel",
"box_policy": "depth-clustered-map-axis-aligned-p05-p95-diagnostic",
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
fusion_id = f"fusion-{identity_sha256}"
parent = args.output_root.resolve() / result.job.job_id
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
output = parent / fusion_id
if output.exists():
print(json.dumps({"fusion_id": fusion_id, "output": str(output), "reused": True}))
return 0
staging = parent / f".{fusion_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
masks_root = staging / "masks"
masks_root.mkdir(mode=0o700)
try:
_extract_masks(
result.artifact("panoptic-mask-archive").path,
masks_root,
result.job.segment_count,
)
frame_times_ns: list[int] = []
point_offsets = [0]
point_arrays: list[npt.NDArray[np.float32]] = []
point_color_arrays: list[npt.NDArray[np.uint8]] = []
box_offsets = [0]
box_centers: list[tuple[float, float, float]] = []
box_half_sizes: list[tuple[float, float, float]] = []
box_colors: list[tuple[int, int, int, int]] = []
box_labels: list[str] = []
frame_reports: list[dict[str, Any]] = []
compatible_frames = 0
accepted_boxes = 0
for index, (anchor, sample) in enumerate(
zip(
anchors,
_lidar_samples(
capture_root / "mqtt.raw.k1mqtt",
anchors,
origin_monotonic_ns=origin.started_monotonic_ns,
),
strict=True,
)
):
frame_times_ns.append(round(anchor.video_session_seconds * 1e9))
if sample is None:
point_arrays.append(_empty_points())
point_color_arrays.append(_empty_colors(3))
point_offsets.append(point_offsets[-1])
box_offsets.append(box_offsets[-1])
frame_reports.append(
{"frame_index": index, "state": "depth-unavailable", "points": 0, "boxes": 0}
)
continue
compatible_frames += 1
with Image.open(
masks_root / "semantic-masks" / f"frame-{index + 1:06d}.png"
) as semantic_image:
semantic_map = np.array(semantic_image, dtype=np.uint8, copy=True)
with Image.open(
masks_root / "instance-masks" / f"frame-{index + 1:06d}.png"
) as instance_image:
instance_map = np.array(instance_image, dtype=np.uint16, copy=True)
if semantic_map.shape != (profile.height, profile.width) or instance_map.shape != (
profile.height,
profile.width,
):
raise RuntimeError("panoptic mask dimensions changed")
points_map = np.asarray(
[
point.scaled_xyz(sample.point_frame.header.scaler)
for point in sample.point_frame.points
],
dtype=np.float64,
)
points_lidar = map_points_to_lidar(
points_map,
position_map_xyz=sample.pose_frame.position_xyz,
orientation_map_from_lidar_xyzw=sample.pose_frame.orientation_xyzw,
)
projection = project_map_points_kb4(
points_map,
position_map_xyz=sample.pose_frame.position_xyz,
orientation_map_from_lidar_xyzw=sample.pose_frame.orientation_xyzw,
profile=profile,
)
pixel_x = np.clip(
np.rint(projection.pixels_xy[:, 0]).astype(np.int64),
0,
profile.width - 1,
)
pixel_y = np.clip(
np.rint(projection.pixels_xy[:, 1]).astype(np.int64),
0,
profile.height - 1,
)
semantic_ids = semantic_map[pixel_y, pixel_x].astype(np.int64)
colors = np.asarray([_palette(int(value)) for value in semantic_ids], dtype=np.uint8)
projected = points_map[projection.source_indices].astype(np.float32)
point_arrays.append(projected)
point_color_arrays.append(colors)
point_offsets.append(point_offsets[-1] + projected.shape[0])
instances = frame_metadata[index]["instances"]
objects = _object_fusions(
instance_map=instance_map,
instances=instances,
pixel_x=pixel_x,
pixel_y=pixel_y,
depths=projection.depths_m,
projected_source_indices=projection.source_indices,
points_map=points_map,
points_lidar=points_lidar,
)
frame_box_count = 0
for item in objects:
if item.box_center_map is None or item.box_half_size_map is None:
continue
frame_box_count += 1
accepted_boxes += 1
box_centers.append(item.box_center_map)
box_half_sizes.append(item.box_half_size_map)
box_colors.append((*_palette(500 + item.instance_id), 96))
box_labels.append(
f"{item.label} · {item.distance_median_m:.1f} m · {item.clustered_points} pts"
)
box_offsets.append(box_offsets[-1] + frame_box_count)
frame_reports.append(
{
"frame_index": index,
"state": "fused",
"points": int(projected.shape[0]),
"boxes": frame_box_count,
"lidar_camera_delta_ms": round(
(sample.point_session_seconds - anchor.host_session_seconds) * 1000,
6,
),
"pose_point_delta_ms": round(
(sample.pose_session_seconds - sample.point_session_seconds) * 1000,
6,
),
}
)
if (index + 1) % 250 == 0 or index + 1 == result.job.segment_count:
print(
json.dumps(
{"phase": "fusion", "frames": index + 1, "total": result.job.segment_count}
),
flush=True,
)
arrays_path = staging / "fusion.npz"
np.savez_compressed(
arrays_path,
frame_times_ns=np.asarray(frame_times_ns, dtype=np.int64),
point_offsets=np.asarray(point_offsets, dtype=np.int64),
points=(np.concatenate(point_arrays) if point_arrays else _empty_points()),
point_colors=(
np.concatenate(point_color_arrays) if point_color_arrays else _empty_colors(3)
),
box_offsets=np.asarray(box_offsets, dtype=np.int64),
box_centers=np.asarray(box_centers, dtype=np.float32).reshape((-1, 3)),
box_half_sizes=np.asarray(box_half_sizes, dtype=np.float32).reshape((-1, 3)),
box_colors=np.asarray(box_colors, dtype=np.uint8).reshape((-1, 4)),
)
labels_path = staging / "box-labels.json"
labels_path.write_text(
json.dumps(box_labels, ensure_ascii=False, separators=(",", ":")),
encoding="utf-8",
)
frames_path = staging / "fusion-frames.jsonl"
frames_path.write_text(
"".join(json.dumps(item, sort_keys=True) + "\n" for item in frame_reports),
encoding="utf-8",
)
shutil.rmtree(masks_root)
created_at = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
artifacts = [
{
"name": path.name,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
for path in (arrays_path, labels_path, frames_path)
]
manifest = {
"schema_version": SCHEMA,
"fusion_id": fusion_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": created_at,
"session_id": result.job.session_id,
"source_id": result.job.source_id,
"frame_count": result.job.segment_count,
"timeline_start_seconds": result.job.timeline_start_seconds,
"timeline_end_seconds": result.job.timeline_end_seconds,
"metrics": {
"frames_fused": compatible_frames,
"frames_depth_unavailable": result.job.segment_count - compatible_frames,
"semantic_points": point_offsets[-1],
"accepted_diagnostic_boxes": accepted_boxes,
"wall_seconds": round(time.perf_counter() - started, 6),
},
"acceptance": {
"geometry": "factory-calibrated-kb4",
"timing": "host-arrival-best-effort-not-ground-truthed",
"distance": "diagnostic-not-ground-truthed",
"boxes3d": "support-gated-diagnostic-not-tracked",
"safety": "not-accepted",
},
"artifacts": artifacts,
}
(staging / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
os.replace(staging, output)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
print(json.dumps({"fusion_id": fusion_id, "output": str(output), "reused": False}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""Build a full-camera-epoch LAB E10 LiDAR/pose replay pack from raw evidence."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from e10_fusion_runtime import PACK_SCHEMA, LidarReplayPack, canonical_json, sha256
from fuse_e6_tracking_lidar import _camera_anchors, _lidar_samples, _read_profile
from k1link.compute import validate_tracked_fusion_qualification_result
from k1link.device_plugins.xgrids_k1.analyze.calibrated_overlay import (
_load_calibration_snapshot,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
Kb4ProjectionProfile,
)
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
def arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--tracking", type=Path, required=True)
parser.add_argument("--semantic", type=Path, required=True)
parser.add_argument("--e6-result", type=Path, required=True)
parser.add_argument("--e6-profile", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _read_e6_identity(result_root: Path) -> dict[str, Any]:
document = json.loads(
(result_root.resolve(strict=True) / "result.json").read_text(encoding="utf-8")
)
identity = document.get("identity") if isinstance(document, dict) else None
if not isinstance(identity, dict):
raise RuntimeError("accepted LAB E6 identity is unavailable")
return identity
def _read_full_timeline(path: Path, expected_count: int) -> list[dict[str, Any]]:
frames: list[dict[str, Any]] = []
previous_seconds = -1.0
with path.resolve(strict=True).open(encoding="utf-8") as stream:
for frame_index, line in enumerate(stream):
value = json.loads(line)
session_seconds = value.get("session_seconds") if isinstance(value, dict) else None
if (
not isinstance(value, dict)
or value.get("frame_index") != frame_index
or value.get("sequence") != frame_index + 1
or not isinstance(session_seconds, (int, float))
or isinstance(session_seconds, bool)
or not float(session_seconds) > previous_seconds
):
raise RuntimeError("full camera timeline is invalid")
frames.append(
{
"frame_index": frame_index,
"source_frame_index": frame_index,
"session_seconds": float(session_seconds),
"tracks": [],
}
)
previous_seconds = float(session_seconds)
if len(frames) != expected_count:
raise RuntimeError("full camera timeline count changed")
return frames
def main() -> int:
args = arguments()
upstream = validate_tracked_fusion_qualification_result(
args.job,
args.tracking,
args.semantic,
args.e6_result,
)
session = args.session.resolve(strict=True)
if session.name != upstream.job.session_id:
raise RuntimeError("LAB E10 full session differs from accepted E6")
e6_profile, e6_profile_sha256 = _read_profile(args.e6_profile)
e6_identity = _read_e6_identity(upstream.result_root)
if (
e6_identity.get("profile_sha256") != e6_profile_sha256
or e6_identity.get("configuration") != e6_profile
):
raise RuntimeError("LAB E10 full pack temporal policy differs from accepted E6")
calibration, calibration_sha256 = _load_calibration_snapshot(
args.calibration.resolve(strict=True)
)
if calibration_sha256 != upstream.semantic.calibration_sha256:
raise RuntimeError("LAB E10 full pack calibration differs from accepted E6")
projection = Kb4ProjectionProfile.from_factory_calibration(
calibration,
upstream.job.source_id,
)
timeline_frames = _read_full_timeline(
upstream.semantic.artifact("panoptic-frame-metadata").path,
upstream.job.segment_count,
)
capture_root = session / "captures" / "mqtt_live"
origin = read_capture_clock_origin(capture_root / "mqtt.timeline.origin.json")
epoch_root = (
upstream.job.job_root
/ "input"
/ "camera"
/ upstream.job.source_id
/ f"epoch-{upstream.job.codec_epoch}"
)
anchors = _camera_anchors(
epoch_root / "index.jsonl",
timeline_frames,
origin_monotonic_ns=origin.started_monotonic_ns,
)
maximum_lidar_delta_s = float(e6_profile["temporal"]["maximum_lidar_camera_delta_ms"]) / 1000.0
maximum_pose_delta_s = float(e6_profile["temporal"]["maximum_pose_point_delta_ms"]) / 1000.0
samples = _lidar_samples(
capture_root / "mqtt.raw.k1mqtt",
anchors,
origin_monotonic_ns=origin.started_monotonic_ns,
maximum_lidar_camera_delta_s=maximum_lidar_delta_s,
maximum_pose_point_delta_s=maximum_pose_delta_s,
)
count = len(anchors)
available = np.zeros((count,), dtype=np.bool_)
offsets = [0]
clouds: list[np.ndarray] = []
positions = np.full((count, 3), np.nan, dtype=np.float64)
quaternions = np.full((count, 4), np.nan, dtype=np.float64)
lidar_delta = np.full((count,), np.nan, dtype=np.float64)
pose_delta = np.full((count,), np.nan, dtype=np.float64)
for index, (anchor, sample) in enumerate(zip(anchors, samples, strict=True)):
if sample is None:
offsets.append(offsets[-1])
else:
cloud = np.asarray(
[
point.scaled_xyz(sample.point_frame.header.scaler)
for point in sample.point_frame.points
],
dtype=np.float32,
).reshape((-1, 3))
available[index] = True
clouds.append(cloud)
offsets.append(offsets[-1] + cloud.shape[0])
positions[index] = sample.pose_frame.position_xyz
quaternions[index] = sample.pose_frame.orientation_xyzw
lidar_delta[index] = (
sample.point_session_seconds - anchor.host_session_seconds
) * 1000.0
pose_delta[index] = (
sample.pose_session_seconds - sample.point_session_seconds
) * 1000.0
if (index + 1) % 500 == 0 or index + 1 == count:
print(
json.dumps(
{
"phase": "full-lidar-pack",
"frames": index + 1,
"total": count,
"lidar_frames": int(available[: index + 1].sum()),
"points": offsets[-1],
},
sort_keys=True,
),
flush=True,
)
source_indices = np.arange(count, dtype=np.int64)
session_seconds = np.asarray(
[anchor.video_session_seconds for anchor in anchors], dtype=np.float64
)
temporal = {
"binding": e6_profile["temporal"]["binding"],
"maximum_lidar_camera_delta_ms": float(
e6_profile["temporal"]["maximum_lidar_camera_delta_ms"]
),
"maximum_pose_point_delta_ms": float(e6_profile["temporal"]["maximum_pose_point_delta_ms"]),
"clock_source": "recorded-host-monotonic-arrival",
}
identity = {
"schema_version": PACK_SCHEMA,
"job_id": upstream.job.job_id,
"input_sha256": upstream.job.input_sha256,
"session_id": upstream.job.session_id,
"source_id": upstream.job.source_id,
"camera_slot": upstream.semantic.calibration_slot,
"calibration_sha256": calibration_sha256,
"e6_result_id": upstream.result_id,
"e6_profile_sha256": e6_profile_sha256,
"semantic_timeline_result_id": upstream.semantic.result_id,
"frame_count": count,
"source_start_frame_index": 0,
"source_end_frame_index": count - 1,
"timeline_start_seconds": float(session_seconds[0]),
"timeline_end_seconds": float(session_seconds[-1]),
"available_lidar_frames": int(available.sum()),
"point_count": int(offsets[-1]),
"temporal_binding": "accepted-e6-nearest-host-arrival-best-effort",
"temporal_policy": temporal,
"projection": {
"model": "kb4",
"width": projection.width,
"height": projection.height,
"source_coordinates": "k1-map",
"target_camera": upstream.job.source_id,
},
"producer_sha256": sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
pack_id = f"e10-lidar-pack-{identity_sha256}"
parent = args.output_root.resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
output = parent / pack_id
if output.exists():
print(json.dumps({"pack_id": pack_id, "output": str(output), "reused": True}))
return 0
staging = parent / f".{pack_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
try:
arrays_path = staging / "lidar-pack.npz"
np.savez_compressed(
arrays_path,
frame_indices=np.arange(count, dtype=np.int64),
source_frame_indices=source_indices,
session_seconds=session_seconds,
sample_available=available,
cloud_offsets=np.asarray(offsets, dtype=np.int64),
cloud_points_map=(
np.concatenate(clouds) if clouds else np.empty((0, 3), dtype=np.float32)
),
pose_positions_map=positions,
pose_quaternions_map_from_lidar=quaternions,
lidar_camera_delta_ms=lidar_delta,
pose_point_delta_ms=pose_delta,
intrinsic_fx_fy_cx_cy=np.asarray(projection.intrinsic_fx_fy_cx_cy, dtype=np.float64),
distortion_kb4=np.asarray(projection.distortion_kb4, dtype=np.float64),
t_camera_from_lidar=np.asarray(projection.t_camera_from_lidar, dtype=np.float64),
)
manifest = {
"schema_version": PACK_SCHEMA,
"pack_id": pack_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"classification": "private-recorded-sensor-replay-input",
"ground_truth": False,
"artifact": {
"path": arrays_path.name,
"media_type": "application/x-npz",
"byte_length": arrays_path.stat().st_size,
"sha256": sha256(arrays_path),
},
}
(staging / "manifest.json").write_bytes(
json.dumps(
manifest,
ensure_ascii=False,
sort_keys=True,
indent=2,
allow_nan=False,
).encode()
+ b"\n"
)
os.replace(staging, output)
try:
validation = LidarReplayPack(output, expected_job_id=upstream.job.job_id)
validation.close()
except BaseException:
shutil.rmtree(output, ignore_errors=True)
raise
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
print(
json.dumps(
{
"pack_id": pack_id,
"output": str(output),
"frames": count,
"lidar_frames": int(available.sum()),
"points": int(offsets[-1]),
"byte_length": (output / "lidar-pack.npz").stat().st_size,
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""Build a content-addressed LAB E10 LiDAR/pose replay pack from accepted E6."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import os
import shutil
import sys
from datetime import UTC, datetime
from pathlib import Path
from types import ModuleType
import numpy as np
from e10_fusion_runtime import PACK_SCHEMA, canonical_json, sha256
from k1link.compute import validate_tracked_fusion_qualification_result
from k1link.device_plugins.xgrids_k1.analyze.calibrated_overlay import (
_load_calibration_snapshot,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
Kb4ProjectionProfile,
)
def arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--tracking", type=Path, required=True)
parser.add_argument("--semantic", type=Path, required=True)
parser.add_argument("--e6-result", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def load_e7_runner() -> ModuleType:
path = Path(__file__).with_name("run_e7_replay_realtime.py").resolve(strict=True)
spec = importlib.util.spec_from_file_location("missioncore_e10_e7_adapter", path)
if spec is None or spec.loader is None:
raise RuntimeError("LAB E7 adapter cannot be loaded")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def main() -> int:
args = arguments()
upstream = validate_tracked_fusion_qualification_result(
args.job,
args.tracking,
args.semantic,
args.e6_result,
)
session = args.session.resolve(strict=True)
if session.name != upstream.job.session_id:
raise RuntimeError("LAB E10 session differs from the accepted E6 input")
calibration, calibration_sha256 = _load_calibration_snapshot(
args.calibration.resolve(strict=True)
)
if calibration_sha256 != upstream.semantic.calibration_sha256:
raise RuntimeError("LAB E10 calibration differs from the accepted E6 input")
projection = Kb4ProjectionProfile.from_factory_calibration(
calibration,
upstream.job.source_id,
)
adapter = load_e7_runner()
frames = adapter._prepare_frames(
e6=upstream,
session=session,
selection_start=0,
selection_count=upstream.frame_count,
)
if len(frames) != upstream.frame_count:
raise RuntimeError("LAB E10 E6 replay frame count changed")
frame_indices = np.arange(len(frames), dtype=np.int64)
source_indices = np.asarray(
[int(frame.frame["source_frame_index"]) for frame in frames], dtype=np.int64
)
session_seconds = np.asarray(
[float(frame.frame["session_seconds"]) for frame in frames], dtype=np.float64
)
available = np.asarray([frame.position_map_xyz is not None for frame in frames], dtype=np.bool_)
offsets = [0]
clouds = []
positions = np.full((len(frames), 3), np.nan, dtype=np.float64)
quaternions = np.full((len(frames), 4), np.nan, dtype=np.float64)
lidar_delta = np.full((len(frames),), np.nan, dtype=np.float64)
pose_delta = np.full((len(frames),), np.nan, dtype=np.float64)
for index, frame in enumerate(frames):
cloud = np.asarray(frame.cloud_map, dtype=np.float32)
clouds.append(cloud)
offsets.append(offsets[-1] + cloud.shape[0])
if frame.position_map_xyz is not None and frame.orientation_map_from_lidar_xyzw is not None:
positions[index] = frame.position_map_xyz
quaternions[index] = frame.orientation_map_from_lidar_xyzw
lidar_delta[index] = float(frame.frame["lidar_camera_delta_ms"])
pose_delta[index] = float(frame.frame["pose_point_delta_ms"])
identity = {
"schema_version": PACK_SCHEMA,
"job_id": upstream.job.job_id,
"input_sha256": upstream.job.input_sha256,
"session_id": upstream.job.session_id,
"source_id": upstream.job.source_id,
"camera_slot": upstream.semantic.calibration_slot,
"calibration_sha256": calibration_sha256,
"e6_result_id": upstream.result_id,
"frame_count": len(frames),
"source_start_frame_index": int(source_indices[0]),
"source_end_frame_index": int(source_indices[-1]),
"timeline_start_seconds": float(session_seconds[0]),
"timeline_end_seconds": float(session_seconds[-1]),
"available_lidar_frames": int(available.sum()),
"point_count": int(offsets[-1]),
"temporal_binding": "accepted-e6-nearest-host-arrival-best-effort",
"projection": {
"model": "kb4",
"width": projection.width,
"height": projection.height,
"source_coordinates": "k1-map",
"target_camera": upstream.job.source_id,
},
"producer_sha256": sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
pack_id = f"e10-lidar-pack-{identity_sha256}"
parent = args.output_root.resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
output = parent / pack_id
if output.exists():
print(json.dumps({"pack_id": pack_id, "output": str(output), "reused": True}))
return 0
staging = parent / f".{pack_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
try:
arrays_path = staging / "lidar-pack.npz"
np.savez_compressed(
arrays_path,
frame_indices=frame_indices,
source_frame_indices=source_indices,
session_seconds=session_seconds,
sample_available=available,
cloud_offsets=np.asarray(offsets, dtype=np.int64),
cloud_points_map=(
np.concatenate(clouds) if clouds else np.empty((0, 3), dtype=np.float32)
),
pose_positions_map=positions,
pose_quaternions_map_from_lidar=quaternions,
lidar_camera_delta_ms=lidar_delta,
pose_point_delta_ms=pose_delta,
intrinsic_fx_fy_cx_cy=np.asarray(projection.intrinsic_fx_fy_cx_cy, dtype=np.float64),
distortion_kb4=np.asarray(projection.distortion_kb4, dtype=np.float64),
t_camera_from_lidar=np.asarray(projection.t_camera_from_lidar, dtype=np.float64),
)
manifest = {
"schema_version": PACK_SCHEMA,
"pack_id": pack_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"classification": "private-recorded-sensor-replay-input",
"ground_truth": False,
"artifact": {
"path": arrays_path.name,
"media_type": "application/x-npz",
"byte_length": arrays_path.stat().st_size,
"sha256": sha256(arrays_path),
},
}
manifest_path = staging / "manifest.json"
manifest_path.write_bytes(
json.dumps(
manifest,
ensure_ascii=False,
sort_keys=True,
indent=2,
allow_nan=False,
).encode()
+ b"\n"
)
os.replace(staging, output)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
print(
json.dumps(
{
"pack_id": pack_id,
"output": str(output),
"frames": len(frames),
"lidar_frames": int(available.sum()),
"points": int(offsets[-1]),
"byte_length": (output / "lidar-pack.npz").stat().st_size,
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""Extract a minimal content-addressed live projection pack from accepted E14 input."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
SCHEMA = "missioncore.e15-live-projection-pack/v1"
PACK_ARRAYS = {
"intrinsic_fx_fy_cx_cy",
"distortion_kb4",
"t_camera_from_lidar",
}
def _canonical(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while block := stream.read(1024 * 1024):
digest.update(block)
return digest.hexdigest()
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise RuntimeError("projection source manifest is invalid")
return value
def build_projection_pack(
source_root: Path,
output_root: Path,
*,
expected_calibration_sha256: str,
) -> Path:
source = source_root.expanduser().resolve(strict=True)
manifest_path = source / "manifest.json"
arrays_path = source / "lidar-pack.npz"
manifest = _read_object(manifest_path)
identity = manifest.get("identity")
if (
not isinstance(identity, dict)
or identity.get("calibration_sha256") != expected_calibration_sha256
or identity.get("source_id") != "sensor.camera.right"
or identity.get("camera_slot") != "camera_1"
or manifest.get("artifact", {}).get("sha256") != _sha256(arrays_path)
):
raise RuntimeError("accepted LiDAR pack calibration binding changed")
with np.load(arrays_path, allow_pickle=False) as source_arrays:
if not PACK_ARRAYS.issubset(source_arrays.files):
raise RuntimeError("accepted LiDAR pack projection arrays changed")
intrinsic = np.asarray(source_arrays["intrinsic_fx_fy_cx_cy"], dtype=np.float64)
distortion = np.asarray(source_arrays["distortion_kb4"], dtype=np.float64)
transform = np.asarray(source_arrays["t_camera_from_lidar"], dtype=np.float64)
if (
intrinsic.shape != (4,)
or distortion.shape != (4,)
or transform.shape != (4, 4)
or not np.isfinite(intrinsic).all()
or not np.isfinite(distortion).all()
or not np.isfinite(transform).all()
):
raise RuntimeError("live projection arrays are invalid")
pack_identity = {
"schema_version": SCHEMA,
"source_id": "sensor.camera.right",
"calibration_slot": "camera_1",
"calibration_sha256": expected_calibration_sha256,
"resolution": [800, 600],
"projection_model": "kb4",
"coordinate_contract": "k1-map-to-camera_1-via-inverse-map-pose",
"source_lidar_pack_id": manifest.get("pack_id"),
"source_lidar_pack_identity_sha256": manifest.get("identity_sha256"),
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(_canonical(pack_identity)).hexdigest()
pack_id = f"e15-live-projection-{identity_sha256}"
parent = output_root.expanduser().resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
destination = parent / pack_id
if destination.exists():
validate_projection_pack(destination, expected_calibration_sha256)
return destination
staging = parent / f".{pack_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
try:
output_arrays = staging / "projection.npz"
np.savez(
output_arrays,
intrinsic_fx_fy_cx_cy=intrinsic,
distortion_kb4=distortion,
t_camera_from_lidar=transform,
)
document = {
"schema_version": SCHEMA,
"pack_id": pack_id,
"identity_sha256": identity_sha256,
"identity": pack_identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"classification": "calibration-only-no-recorded-sensor-frames",
"artifact": {
"path": output_arrays.name,
"byte_length": output_arrays.stat().st_size,
"sha256": _sha256(output_arrays),
},
}
(staging / "manifest.json").write_bytes(
json.dumps(document, indent=2, sort_keys=True).encode() + b"\n"
)
os.replace(staging, destination)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
validate_projection_pack(destination, expected_calibration_sha256)
return destination
def validate_projection_pack(root: Path, expected_calibration_sha256: str) -> dict[str, Any]:
resolved = root.expanduser().resolve(strict=True)
manifest = _read_object(resolved / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
artifact = manifest.get("artifact")
arrays_path = resolved / "projection.npz"
if (
manifest.get("schema_version") != SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != SCHEMA
or identity.get("calibration_sha256") != expected_calibration_sha256
or not isinstance(identity_sha256, str)
or hashlib.sha256(_canonical(identity)).hexdigest() != identity_sha256
or resolved.name != f"e15-live-projection-{identity_sha256}"
or manifest.get("pack_id") != resolved.name
or not isinstance(artifact, dict)
or artifact.get("path") != arrays_path.name
or artifact.get("byte_length") != arrays_path.stat().st_size
or artifact.get("sha256") != _sha256(arrays_path)
):
raise RuntimeError("live projection pack identity is invalid")
with np.load(arrays_path, allow_pickle=False) as arrays:
if set(arrays.files) != PACK_ARRAYS:
raise RuntimeError("live projection pack arrays changed")
if (
arrays["intrinsic_fx_fy_cx_cy"].shape != (4,)
or arrays["distortion_kb4"].shape != (4,)
or arrays["t_camera_from_lidar"].shape != (4, 4)
):
raise RuntimeError("live projection pack shapes changed")
return manifest
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument("--calibration-sha256", required=True)
return parser.parse_args()
def main() -> int:
args = _arguments()
output = build_projection_pack(
args.source_root,
args.output_root,
expected_calibration_sha256=args.calibration_sha256,
)
print(json.dumps({"projection_pack": str(output)}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,199 @@
#!/usr/bin/env python3
"""Build the minimal hash-addressed K1 runtime imported by the E15 worker."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import uuid
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
SCHEMA = "missioncore.e15-worker-package/v1"
COPIED_FILES = (
"k1link/__init__.py",
"k1link/compute/live_perception.py",
"k1link/data_plane/__init__.py",
"k1link/data_plane/views.py",
"k1link/device_plugins/__init__.py",
"k1link/device_plugins/xgrids_k1/protocol/__init__.py",
"k1link/device_plugins/xgrids_k1/protocol/normalizer.py",
"k1link/device_plugins/xgrids_k1/protocol/protobuf_wire.py",
"k1link/device_plugins/xgrids_k1/protocol/streams.py",
)
GENERATED_FILES = {
"k1link/compute/__init__.py": (
'"""Minimal E15 worker projection; import live_perception explicitly."""\n'
),
"k1link/device_plugins/xgrids_k1/__init__.py": (
'"""Minimal E15 K1 decoder projection; no device-runtime side effects."""\n'
),
}
def _canonical(value: object) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _identity(source_root: Path) -> dict[str, Any]:
source_files = []
for relative in COPIED_FILES:
source = source_root / relative
if not source.is_file() or source.is_symlink():
raise RuntimeError(f"E15 worker source is not a regular file: {relative}")
source_files.append({"path": relative, "sha256": _sha256(source)})
return {
"schema_version": SCHEMA,
"classification": "minimal-live-worker-import-projection",
"source_files": source_files,
"generated_initializers": {
path: hashlib.sha256(content.encode()).hexdigest()
for path, content in sorted(GENERATED_FILES.items())
},
"imports": [
"k1link.compute.live_perception.LiveSensorSynchronizer",
"k1link.data_plane.DecodedPointCloudView",
"k1link.data_plane.DecodedPoseView",
"k1link.device_plugins.xgrids_k1.protocol.normalizer.normalize_k1_message",
],
}
def validate_worker_package(root: Path, *, allow_staging: bool = False) -> dict[str, Any]:
resolved = root.resolve(strict=True)
manifest_path = resolved / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
artifacts = manifest.get("artifacts")
expected_paths = {*COPIED_FILES, *GENERATED_FILES, "manifest.json"}
actual_paths = {
path.relative_to(resolved).as_posix()
for path in resolved.rglob("*")
if path.is_file()
}
if (
manifest.get("schema_version") != SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != SCHEMA
or not isinstance(identity_sha256, str)
or hashlib.sha256(_canonical(identity)).hexdigest() != identity_sha256
or manifest.get("package_id") != f"e15-worker-package-{identity_sha256}"
or (
resolved.name != manifest.get("package_id")
and not (
allow_staging
and resolved.name.startswith(f".{manifest.get('package_id')}.")
and resolved.name.endswith(".tmp")
)
)
or not isinstance(artifacts, list)
or actual_paths != expected_paths
):
raise RuntimeError("E15 worker package identity is invalid")
artifact_paths = set()
for artifact in artifacts:
if not isinstance(artifact, dict):
raise RuntimeError("E15 worker package artifact is invalid")
relative = str(artifact.get("path", ""))
path = resolved / relative
if (
relative not in expected_paths - {"manifest.json"}
or relative in artifact_paths
or not path.is_file()
or path.is_symlink()
or artifact.get("byte_length") != path.stat().st_size
or artifact.get("sha256") != _sha256(path)
):
raise RuntimeError("E15 worker package artifact changed")
artifact_paths.add(relative)
if artifact_paths != expected_paths - {"manifest.json"}:
raise RuntimeError("E15 worker package artifact set changed")
return manifest
def build_worker_package(source_root: Path, output_root: Path) -> Path:
source = source_root.resolve(strict=True)
output = output_root.resolve()
output.mkdir(mode=0o700, parents=True, exist_ok=True)
identity = _identity(source)
identity_sha256 = hashlib.sha256(_canonical(identity)).hexdigest()
package_id = f"e15-worker-package-{identity_sha256}"
destination = output / package_id
if destination.exists():
validate_worker_package(destination)
return destination
staging = output / f".{package_id}.{uuid.uuid4().hex}.tmp"
staging.mkdir(mode=0o700)
try:
for relative in COPIED_FILES:
target = staging / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source / relative, target)
for relative, content in GENERATED_FILES.items():
target = staging / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
artifacts = []
for relative in sorted((*COPIED_FILES, *GENERATED_FILES)):
path = staging / relative
artifacts.append(
{
"path": relative,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
)
manifest = {
"schema_version": SCHEMA,
"package_id": package_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"artifacts": artifacts,
}
with (staging / "manifest.json").open("x", encoding="utf-8", newline="\n") as stream:
json.dump(manifest, stream, indent=2, sort_keys=True)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
validate_worker_package(staging, allow_staging=True)
staging.replace(destination)
finally:
if staging.exists():
shutil.rmtree(staging)
validate_worker_package(destination)
return destination
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
destination = build_worker_package(args.source_root, args.output_root)
print(destination)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""Prepare immutable E1 preprocessing and frame-selection inputs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from k1link.compute.qualification import (
DEFAULT_QUALIFICATION_FRAME_COUNT,
prepare_recorded_qualification_slice,
)
from k1link.device_plugins.xgrids_k1.analyze.valid_fov import (
DEFAULT_EDGE_MARGIN_PIXELS,
prepare_k1_valid_fov_mask,
)
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", type=Path, required=True)
parser.add_argument("--calibration-root", type=Path, required=True)
parser.add_argument("--source-id", required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument(
"--sample-count",
type=int,
default=DEFAULT_QUALIFICATION_FRAME_COUNT,
)
parser.add_argument(
"--edge-margin-pixels",
type=float,
default=DEFAULT_EDGE_MARGIN_PIXELS,
)
return parser.parse_args()
def main() -> int:
args = _arguments()
mask = prepare_k1_valid_fov_mask(
calibration_snapshot_root=args.calibration_root,
source_id=args.source_id,
output_root=args.output_root / "valid-fov",
edge_margin_pixels=args.edge_margin_pixels,
)
qualification = prepare_recorded_qualification_slice(
job_root=args.job_root,
output_root=args.output_root / "qualification-slices",
sample_count=args.sample_count,
)
print(
json.dumps(
{
"schema_version": "missioncore.e1-qualification-preparation/v1",
"valid_fov": {
"generation_id": mask.generation_id,
"manifest": str(mask.manifest_path),
"mask": str(mask.mask_path),
"calibration_sha256": mask.calibration_sha256,
"source_id": mask.source_id,
"calibration_slot": mask.calibration_slot,
"center_xy": list(mask.center_xy),
"radius_pixels": mask.radius_pixels,
"crop_xyxy_exclusive": list(mask.crop_xyxy),
"valid_pixel_count": mask.valid_pixel_count,
"valid_fraction": mask.valid_fraction,
},
"qualification_slice": {
"generation_id": qualification.generation_id,
"manifest": str(qualification.manifest_path),
"job_id": qualification.job_id,
"input_sha256": qualification.input_sha256,
"policy": qualification.policy,
"source_frame_count": qualification.source_frame_count,
"selected_frame_count": len(qualification.frames),
"first_frame_index": qualification.frames[0].frame_index,
"last_frame_index": qualification.frames[-1].frame_index,
},
},
ensure_ascii=False,
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Build or validate the immutable LAB E2 CVAT annotation handoff."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import k1link.compute.annotation_workspace as workspace_module
from k1link.compute import prepare_annotation_workspace, validate_annotation_workspace
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
prepare = commands.add_parser("prepare")
prepare.add_argument("--evaluation-pack", type=Path, required=True)
prepare.add_argument("--prelabels", type=Path, required=True)
prepare.add_argument("--valid-fov-root", type=Path, required=True)
prepare.add_argument("--output-root", type=Path, required=True)
validate = commands.add_parser("validate")
validate.add_argument("--workspace", type=Path, required=True)
validate.add_argument("--evaluation-pack", type=Path, required=True)
validate.add_argument("--prelabels", type=Path, required=True)
validate.add_argument("--valid-fov-root", type=Path, required=True)
return parser.parse_args()
def main() -> int:
args = _arguments()
if args.command == "prepare":
result = prepare_annotation_workspace(
evaluation_pack_root=args.evaluation_pack,
prelabels_root=args.prelabels,
valid_fov_root=args.valid_fov_root,
output_root=args.output_root,
producer_files=(
(
"annotation_workspace.py",
_sha256(Path(str(workspace_module.__file__)).resolve(strict=True)),
),
(
"prepare_e2_annotation_workspace.py",
_sha256(Path(__file__).resolve(strict=True)),
),
),
)
else:
result = validate_annotation_workspace(
args.workspace,
evaluation_pack_root=args.evaluation_pack,
prelabels_root=args.prelabels,
valid_fov_root=args.valid_fov_root,
)
print(
json.dumps(
{
"generation_id": result.generation_id,
"root": str(result.root),
"frame_count": result.frame_count,
"draft_instance_count": result.draft_instance_count,
"ground_truth": False,
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,364 @@
#!/usr/bin/env python3
"""Review and seal the LAB E2 recorded-perception evaluation pack."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
from PIL import Image, ImageDraw, ImageFont
import k1link.compute.evaluation_pack as evaluation_pack_module
from k1link.artifacts import utc_now_iso, write_json_atomic
from k1link.compute import (
EvaluationFrameRequest,
prepare_recorded_evaluation_pack,
validate_camera_compute_job,
validate_recorded_qualification_slice,
)
CANDIDATE_SCHEMA = "missioncore.e2-evaluation-candidates/v1"
SELECTION_SCHEMA = "missioncore.e2-evaluation-selection/v1"
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
candidates = commands.add_parser("candidates")
candidates.add_argument("--job-root", type=Path, required=True)
candidates.add_argument("--qualification-root", type=Path, required=True)
candidates.add_argument("--output-root", type=Path, required=True)
seal = commands.add_parser("seal")
seal.add_argument("--job-root", type=Path, required=True)
seal.add_argument("--qualification-root", type=Path, required=True)
seal.add_argument("--valid-fov-root", type=Path, required=True)
seal.add_argument("--selection", type=Path, required=True)
seal.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _run(command: list[str]) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, check=True, text=True, capture_output=True)
def _ffmpeg_version() -> str:
completed = _run(["ffmpeg", "-version"])
return completed.stdout.splitlines()[0].strip()
def _reconstruct_stream(job_root: Path, destination: Path) -> None:
job = validate_camera_compute_job(job_root)
epoch = (
job.job_root
/ "input"
/ "camera"
/ job.source_id
/ f"epoch-{job.codec_epoch}"
)
with destination.open("xb") as output:
with (epoch / "init.mp4").open("rb") as source:
shutil.copyfileobj(source, output, 1024 * 1024)
for sequence in range(1, job.segment_count + 1):
with (epoch / "segments" / f"{sequence}.m4s").open("rb") as source:
shutil.copyfileobj(source, output, 1024 * 1024)
output.flush()
os.fsync(output.fileno())
def _timeline(stream_path: Path, start_seconds: float, expected_count: int) -> list[float]:
completed = _run(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"frame=best_effort_timestamp_time",
"-of",
"json",
str(stream_path),
]
)
document = json.loads(completed.stdout)
frames = document.get("frames")
if not isinstance(frames, list) or len(frames) != expected_count:
raise RuntimeError("decoded camera timestamp count differs from the compute job")
epoch_values = [float(frame["best_effort_timestamp_time"]) for frame in frames]
first = epoch_values[0]
values = [start_seconds + value - first for value in epoch_values]
if any(right <= left for left, right in zip(values, values[1:], strict=False)):
raise RuntimeError("decoded camera timestamps are not strictly monotonic")
return values
def _extract_frames(stream_path: Path, indices: list[int], output_root: Path) -> None:
if not indices or indices != sorted(set(indices)):
raise RuntimeError("frame extraction indices must be sorted and unique")
output_root.mkdir(mode=0o700, parents=True, exist_ok=False)
expression = "+".join(f"eq(n\\,{index})" for index in indices)
pattern = output_root / "selected-%06d.png"
_run(
[
"ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-i",
str(stream_path),
"-map",
"0:v:0",
"-vf",
f"select={expression}",
"-fps_mode",
"passthrough",
str(pattern),
]
)
extracted = sorted(output_root.glob("selected-*.png"))
if len(extracted) != len(indices):
raise RuntimeError("ffmpeg did not extract the requested frame set")
for source, frame_index in zip(extracted, indices, strict=True):
destination = output_root / f"frame-{frame_index:06d}.png"
source.replace(destination)
os.chmod(destination, 0o600)
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _contact_sheets(
frames_root: Path,
indices: list[int],
timestamps: list[float],
output_root: Path,
) -> list[dict[str, Any]]:
output_root.mkdir(mode=0o700, parents=True, exist_ok=False)
columns = 8
rows = 4
thumb_size = (200, 150)
label_height = 25
font = ImageFont.load_default(size=15)
records: list[dict[str, Any]] = []
page_size = columns * rows
for page, offset in enumerate(range(0, len(indices), page_size), start=1):
page_indices = indices[offset : offset + page_size]
canvas = Image.new(
"RGB",
(columns * thumb_size[0], rows * (thumb_size[1] + label_height)),
(18, 18, 18),
)
draw = ImageDraw.Draw(canvas)
for position, frame_index in enumerate(page_indices):
row, column = divmod(position, columns)
x = column * thumb_size[0]
y = row * (thumb_size[1] + label_height)
with Image.open(frames_root / f"frame-{frame_index:06d}.png") as opened:
image = opened.convert("RGB")
image.thumbnail(thumb_size, Image.Resampling.LANCZOS)
canvas.paste(image, (x, y))
draw.text(
(x + 4, y + thumb_size[1] + 3),
f"f={frame_index} t={timestamps[frame_index]:.3f}s",
fill=(240, 240, 240),
font=font,
)
path = output_root / f"contact-sheet-{page:02d}.png"
canvas.save(path, format="PNG", optimize=False)
os.chmod(path, 0o600)
records.append(
{
"path": path.name,
"first_frame_index": page_indices[0],
"last_frame_index": page_indices[-1],
"frame_count": len(page_indices),
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
)
return records
def _candidates(args: argparse.Namespace) -> int:
job = validate_camera_compute_job(args.job_root)
qualification = validate_recorded_qualification_slice(
args.qualification_root,
job_root=job.job_root,
)
output = args.output_root.expanduser()
output.mkdir(mode=0o700, parents=True, exist_ok=False)
published = False
try:
with tempfile.TemporaryDirectory(prefix="missioncore-e2-") as temporary:
stream_path = Path(temporary) / "camera.mp4"
_reconstruct_stream(job.job_root, stream_path)
timestamps = _timeline(stream_path, job.timeline_start_seconds, job.segment_count)
indices = [frame.frame_index for frame in qualification.frames]
frames_root = output / "frames"
_extract_frames(stream_path, indices, frames_root)
sheets = _contact_sheets(frames_root, indices, timestamps, output / "contact-sheets")
manifest = {
"schema_version": CANDIDATE_SCHEMA,
"created_at_utc": utc_now_iso(),
"state": "review-candidates",
"job_id": job.job_id,
"input_sha256": job.input_sha256,
"qualification_generation_id": qualification.generation_id,
"ffmpeg": _ffmpeg_version(),
"frames": [
{
"qualification_order": order,
"frame_index": frame.frame_index,
"sequence": frame.sequence,
"segment_sha256": frame.segment_sha256,
"session_seconds": timestamps[frame.frame_index],
"path": f"frames/frame-{frame.frame_index:06d}.png",
"byte_length": (
frames_root / f"frame-{frame.frame_index:06d}.png"
).stat().st_size,
"sha256": _sha256(frames_root / f"frame-{frame.frame_index:06d}.png"),
}
for order, frame in enumerate(qualification.frames, start=1)
],
"contact_sheets": sheets,
"selection_guidance": {
"anchor_target": 48,
"temporal_clip_target": {"clips": 4, "frames_per_clip": 4},
"total_target": 64,
"required_coverage": [
"person",
"car_or_heavy_vehicle",
"building_structure",
"paved_or_unpaved_ground",
"grass_and_woody_vegetation",
"lens_boundary_hard_negative",
"motion_or_occlusion",
],
},
}
write_json_atomic(output / "manifest.json", manifest)
os.chmod(output / "manifest.json", 0o600)
published = True
finally:
if not published and output.exists():
shutil.rmtree(output)
print(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True))
return 0
def _load_selection(path: Path) -> tuple[EvaluationFrameRequest, ...]:
document = json.loads(path.expanduser().read_text(encoding="utf-8"))
rows = document.get("frames") if isinstance(document, dict) else None
if document.get("schema_version") != SELECTION_SCHEMA or not isinstance(rows, list):
raise RuntimeError("E2 selection document is incompatible")
selection: list[EvaluationFrameRequest] = []
for row in rows:
if not isinstance(row, dict):
raise RuntimeError("E2 selection frame is invalid")
selection.append(
EvaluationFrameRequest(
frame_index=int(row["frame_index"]),
role=str(row["role"]),
group_id=str(row["group_id"]),
)
)
return tuple(selection)
def _write_timeline(path: Path, timestamps: list[float]) -> None:
with path.open("x", encoding="utf-8", newline="\n") as stream:
for frame_index, session_seconds in enumerate(timestamps):
stream.write(
json.dumps(
{"frame_index": frame_index, "session_seconds": session_seconds},
separators=(",", ":"),
)
+ "\n"
)
stream.flush()
os.fsync(stream.fileno())
def _seal(args: argparse.Namespace) -> int:
job = validate_camera_compute_job(args.job_root)
selection = _load_selection(args.selection)
indices = [frame.frame_index for frame in selection]
with tempfile.TemporaryDirectory(prefix="missioncore-e2-") as temporary:
temporary_root = Path(temporary)
stream_path = temporary_root / "camera.mp4"
frames_root = temporary_root / "frames"
timeline_path = temporary_root / "timeline.jsonl"
_reconstruct_stream(job.job_root, stream_path)
timestamps = _timeline(stream_path, job.timeline_start_seconds, job.segment_count)
_extract_frames(stream_path, indices, frames_root)
_write_timeline(timeline_path, timestamps)
pack = prepare_recorded_evaluation_pack(
job_root=job.job_root,
qualification_root=args.qualification_root,
valid_fov_root=args.valid_fov_root,
decoded_frames_root=frames_root,
timeline_path=timeline_path,
output_root=args.output_root,
selection=selection,
decoder_version=_ffmpeg_version(),
selection_document_sha256=_sha256(args.selection.expanduser().resolve(strict=True)),
producer_files=(
(
"evaluation_pack.py",
_sha256(Path(str(evaluation_pack_module.__file__)).resolve(strict=True)),
),
(
"prepare_e2_evaluation_pack.py",
_sha256(Path(__file__).resolve(strict=True)),
),
),
)
print(
json.dumps(
{
"schema_version": "missioncore.e2-evaluation-preparation/v1",
"generation_id": pack.generation_id,
"root": str(pack.root),
"job_id": pack.job_id,
"input_sha256": pack.input_sha256,
"qualification_generation_id": pack.qualification_generation_id,
"valid_fov_generation_id": pack.valid_fov_generation_id,
"calibration_sha256": pack.calibration_sha256,
"frame_count": len(pack.frames),
"anchor_count": sum(frame.role == "anchor" for frame in pack.frames),
"temporal_frame_count": sum(frame.role == "temporal" for frame in pack.frames),
"annotation_state": "unannotated",
},
ensure_ascii=False,
indent=2,
sort_keys=True,
)
)
return 0
def main() -> int:
args = _arguments()
if args.command == "candidates":
return _candidates(args)
if args.command == "seal":
return _seal(args)
raise RuntimeError("unknown E2 command")
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,336 @@
#!/usr/bin/env python3
"""Build an immutable CVAT review handoff for the LAB E3 control and challenger."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import zipfile
from pathlib import Path, PurePosixPath
from typing import Any
import numpy as np
from PIL import Image
RESULT_SCHEMA = "missioncore.k1-e3-rectified-segmentation-result/v1"
PACK_SCHEMA = "missioncore.perception-evaluation-pack/v1"
LABEL_SCHEMA = "missioncore.perception-cvat-labels/v1"
MANIFEST_SCHEMA = "missioncore.lab-e3-cvat-review-workspace/v1"
IDENTITY_SCHEMA = "missioncore.lab-e3-cvat-review-identity/v1"
CONTROL = "eomt-fisheye-mask"
CHALLENGER = "eomt-kb4-cubemap5-clahe"
EXPECTED_FRAMES = 64
PRIORITY_IMAGE_IDS = (59, 63, 62, 61, 60, 64, 50, 30, 17, 29)
ZIP_TIMESTAMP = (1980, 1, 1, 0, 0, 0)
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--evaluation-pack", type=Path, required=True)
parser.add_argument("--e3-result", type=Path, required=True)
parser.add_argument("--labels", type=Path, required=True)
parser.add_argument("--images-zip", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def _valid_sha256(value: object) -> bool:
return (
isinstance(value, str)
and len(value) == 64
and all(character in "0123456789abcdef" for character in value)
)
def _safe_artifact(root: Path, encoded: object) -> Path:
if not isinstance(encoded, str):
raise RuntimeError("artifact path is not a string")
relative = PurePosixPath(encoded)
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
raise RuntimeError("artifact path is unsafe")
path = root.joinpath(*relative.parts).resolve(strict=True)
if not path.is_file() or not path.is_relative_to(root):
raise RuntimeError("artifact path escaped its root")
return path
def _artifact(path: Path, root: Path) -> dict[str, Any]:
return {
"path": path.relative_to(root).as_posix(),
"bytes": path.stat().st_size,
"sha256": _sha256(path),
}
def _validate_pack(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
manifest = _read_object(root / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
if (
manifest.get("schema_version") != PACK_SCHEMA
or not isinstance(identity, dict)
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"evaluation-pack-{identity_sha256}"
or identity.get("source_id") != "sensor.camera.right"
or identity.get("calibration_slot") != "camera_1"
):
raise RuntimeError("evaluation pack is incompatible")
frames = identity.get("frames")
if (
not isinstance(frames, list)
or len(frames) != EXPECTED_FRAMES
or [frame.get("image_id") for frame in frames] != list(range(1, 65))
):
raise RuntimeError("evaluation pack frame contract changed")
return manifest, frames
def _validate_result(root: Path, pack: dict[str, Any]) -> dict[str, Any]:
result = _read_object(root / "result.json")
identity = result.get("identity")
identity_sha256 = result.get("identity_sha256")
if (
result.get("schema_version") != RESULT_SCHEMA
or result.get("ground_truth") is not False
or not isinstance(identity, dict)
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or result.get("result_id") != f"e3-segmentation-{identity_sha256}"
or root.name != result.get("result_id")
or identity.get("evaluation_pack_id") != pack["generation_id"]
or identity.get("evaluation_identity_sha256") != pack["identity_sha256"]
or identity.get("frame_count") != EXPECTED_FRAMES
or CONTROL not in identity.get("variants", ())
or CHALLENGER not in identity.get("variants", ())
):
raise RuntimeError("LAB E3 result is incompatible")
artifacts = result.get("artifacts")
if not isinstance(artifacts, list):
raise RuntimeError("LAB E3 artifact list is invalid")
descriptors = {
item.get("path"): item
for item in artifacts
if isinstance(item, dict) and isinstance(item.get("path"), str)
}
for variant in (CONTROL, CHALLENGER):
for image_id in range(1, 65):
encoded = f"semantic-masks/{variant}/image-{image_id:03d}.png"
descriptor = descriptors.get(encoded)
path = _safe_artifact(root, encoded)
if (
not isinstance(descriptor, dict)
or path.stat().st_size != descriptor.get("bytes")
or not _valid_sha256(descriptor.get("sha256"))
or _sha256(path) != descriptor["sha256"]
):
raise RuntimeError(f"LAB E3 semantic artifact changed: {encoded}")
return result
def _semantic_labels(document: dict[str, Any]) -> list[dict[str, Any]]:
if document.get("schema_version") != LABEL_SCHEMA:
raise RuntimeError("CVAT label document is incompatible")
raw = document.get("semantic_task")
if not isinstance(raw, list):
raise RuntimeError("CVAT semantic labels are absent")
labels = [
{"id": 0, "name": "background", "color": "#000000"},
*[
{"id": int(item["id"]), "name": str(item["name"]), "color": str(item["color"])}
for item in raw
],
]
if [label["id"] for label in labels] != list(range(16)) + [255]:
raise RuntimeError("CVAT semantic taxonomy changed")
return labels
def _rgb(value: str) -> tuple[int, int, int]:
if len(value) != 7 or not value.startswith("#"):
raise RuntimeError("CVAT label color is invalid")
return tuple(int(value[index : index + 2], 16) for index in (1, 3, 5))
def _zip_info(name: str) -> zipfile.ZipInfo:
info = zipfile.ZipInfo(name, date_time=ZIP_TIMESTAMP)
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o100644 << 16
return info
def _write_segmentation_zip(
destination: Path,
*,
result_root: Path,
variant: str,
frames: list[dict[str, Any]],
labels: list[dict[str, Any]],
) -> None:
palette = np.zeros((256, 3), dtype=np.uint8)
for label in labels:
palette[label["id"]] = _rgb(label["color"])
labelmap = "".join(
f"{label['name']}:{','.join(str(value) for value in _rgb(label['color']))}::\n"
for label in labels
).encode()
stems = [
f"image-{frame['image_id']:03d}-frame-{frame['frame_index']:06d}"
for frame in frames
]
with zipfile.ZipFile(destination, mode="x", compression=zipfile.ZIP_DEFLATED) as archive:
archive.writestr(_zip_info("labelmap.txt"), labelmap)
archive.writestr(
_zip_info("ImageSets/Segmentation/default.txt"),
("\n".join(stems) + "\n").encode(),
)
for image_id, stem in enumerate(stems, start=1):
source = result_root / "semantic-masks" / variant / f"image-{image_id:03d}.png"
with Image.open(source) as opened:
indexed = np.asarray(opened.convert("L"), dtype=np.uint8)
if indexed.shape != (600, 800) or np.any(~np.isin(indexed, list(range(16)))):
raise RuntimeError(f"semantic mask taxonomy changed: {source}")
rgb = palette[indexed]
from io import BytesIO
stream = BytesIO()
Image.fromarray(rgb, mode="RGB").save(stream, format="PNG", optimize=True)
archive.writestr(
_zip_info(f"SegmentationClass/{stem}.png"),
stream.getvalue(),
)
def _validate_existing(root: Path, identity_sha256: str) -> dict[str, Any]:
manifest = _read_object(root / "manifest.json")
if (
manifest.get("schema_version") != MANIFEST_SCHEMA
or manifest.get("workspace_id") != f"e3-cvat-review-{identity_sha256}"
or manifest.get("identity_sha256") != identity_sha256
or manifest.get("ground_truth") is not False
):
raise RuntimeError("existing LAB E3 CVAT workspace is incompatible")
for descriptor in manifest.get("artifacts", ()):
path = _safe_artifact(root, descriptor.get("path"))
if path.stat().st_size != descriptor.get("bytes") or _sha256(path) != descriptor.get(
"sha256"
):
raise RuntimeError("existing LAB E3 CVAT artifact changed")
return manifest
def main() -> int:
args = _arguments()
pack_root = args.evaluation_pack.resolve(strict=True)
result_root = args.e3_result.resolve(strict=True)
labels_path = args.labels.resolve(strict=True)
images_zip = args.images_zip.resolve(strict=True)
output_root = args.output_root.resolve()
pack, frames = _validate_pack(pack_root)
result = _validate_result(result_root, pack)
labels_document = _read_object(labels_path)
labels = _semantic_labels(labels_document)
with zipfile.ZipFile(images_zip) as archive:
if len(archive.namelist()) != EXPECTED_FRAMES:
raise RuntimeError("CVAT image archive frame count changed")
identity = {
"schema_version": IDENTITY_SCHEMA,
"evaluation_pack_id": pack["generation_id"],
"evaluation_identity_sha256": pack["identity_sha256"],
"e3_result_id": result["result_id"],
"e3_identity_sha256": result["identity_sha256"],
"control_variant": CONTROL,
"challenger_variant": CHALLENGER,
"frame_count": EXPECTED_FRAMES,
"labels_sha256": _sha256(labels_path),
"images_zip_sha256": _sha256(images_zip),
"priority_image_ids": list(PRIORITY_IMAGE_IDS),
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
workspace_id = f"e3-cvat-review-{identity_sha256}"
final_root = output_root / workspace_id
if final_root.exists():
manifest = _validate_existing(final_root.resolve(strict=True), identity_sha256)
print(json.dumps({"state": "reused", **manifest}, sort_keys=True))
return 0
output_root.mkdir(parents=True, exist_ok=True)
staging = output_root / f".{workspace_id}.{os.getpid()}.publish"
staging.mkdir(mode=0o700)
try:
labels_output = staging / "labels.json"
labels_output.write_text(json.dumps(labels_document, indent=2) + "\n", encoding="utf-8")
for variant, filename in (
(CONTROL, "control-fisheye-mask.zip"),
(CHALLENGER, "challenger-kb4-cubemap5.zip"),
):
_write_segmentation_zip(
staging / filename,
result_root=result_root,
variant=variant,
frames=frames,
labels=labels,
)
artifacts = [
_artifact(path, staging)
for path in sorted(staging.iterdir())
if path.is_file()
]
manifest = {
"schema_version": MANIFEST_SCHEMA,
"workspace_id": workspace_id,
"identity_sha256": identity_sha256,
"identity": identity,
"ground_truth": False,
"artifacts": artifacts,
"next_gate": "two-pass CVAT review and reviewed export",
}
(staging / "manifest.json").write_text(
json.dumps(manifest, indent=2) + "\n",
encoding="utf-8",
)
staging.replace(final_root)
finally:
if staging.exists():
shutil.rmtree(staging)
print(
json.dumps(
{"state": "created", "workspace_id": workspace_id, "root": str(final_root)},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,285 @@
#!/usr/bin/env python3
"""Source-paced RAVNOVES00 qualification of the live shadow transport."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import threading
import time
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Literal
import uvicorn
from fastapi import FastAPI
from k1link.compute.live_perception import LiveIngressModality, LivePerceptionIngress
from k1link.device_plugins.xgrids_k1.live_perception_shadow import (
build_live_perception_shadow_router,
ensure_live_shadow_token,
)
from k1link.device_plugins.xgrids_k1.viewer.replay import iter_replay_messages
REPORT_SCHEMA = "missioncore.e12-shadow-transport-source-report/v1"
PLUGIN_ID = "nodedc.device.xgrids-lixelkity-k1"
@dataclass(frozen=True, slots=True)
class ReplayEvent:
modality: LiveIngressModality
source_id: str
source_sequence: int
epoch_ns: int
monotonic_ns: int
payload: bytes
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def _camera_events(epoch_root: Path, duration_seconds: float) -> list[ReplayEvent]:
index_path = epoch_root / "index.jsonl"
if not index_path.is_file():
raise RuntimeError("camera index is missing")
events: list[ReplayEvent] = []
start_epoch_ns: int | None = None
end_epoch_ns: int | None = None
with index_path.open("r", encoding="utf-8") as stream:
for line in stream:
record = json.loads(line)
epoch_ns = int(record["host_epoch_ns"])
if start_epoch_ns is None:
start_epoch_ns = epoch_ns
end_epoch_ns = epoch_ns + int(duration_seconds * 1_000_000_000)
assert end_epoch_ns is not None
if epoch_ns > end_epoch_ns:
break
payload_path = epoch_root / str(record["path"])
payload = payload_path.read_bytes()
if len(payload) != int(record["length"]):
raise RuntimeError("camera replay segment length differs from its index")
if hashlib.sha256(payload).hexdigest() != record["sha256"]:
raise RuntimeError("camera replay segment digest differs from its index")
events.append(
ReplayEvent(
modality="camera-frame",
source_id="sensor.camera.right",
source_sequence=int(record["sequence"]),
epoch_ns=epoch_ns,
monotonic_ns=int(record["host_monotonic_ns"]),
payload=payload,
)
)
if not events or start_epoch_ns is None:
raise RuntimeError("camera replay interval is empty")
init_payload = (epoch_root / "init.mp4").read_bytes()
events.insert(
0,
ReplayEvent(
modality="camera-init",
source_id="sensor.camera.right",
source_sequence=0,
epoch_ns=start_epoch_ns - 1,
monotonic_ns=events[0].monotonic_ns - 1,
payload=init_payload,
),
)
return events
def _mqtt_events(
capture_path: Path,
*,
start_epoch_ns: int,
duration_seconds: float,
) -> list[ReplayEvent]:
end_epoch_ns = start_epoch_ns + int(duration_seconds * 1_000_000_000)
events: list[ReplayEvent] = []
for message in iter_replay_messages(capture_path):
if message.received_at_epoch_ns < start_epoch_ns:
continue
if message.received_at_epoch_ns > end_epoch_ns:
break
if message.topic.endswith("/lio_pcl") or message.topic == "RealtimePointcloud":
modality: Literal["lidar", "pose"] = "lidar"
elif message.topic.endswith("/lio_pose") or message.topic == "RealtimePath":
modality = "pose"
else:
continue
events.append(
ReplayEvent(
modality=modality,
source_id=message.topic,
source_sequence=message.sequence,
epoch_ns=message.received_at_epoch_ns,
monotonic_ns=message.received_monotonic_ns or 0,
payload=message.payload,
)
)
return events
def _wait_for(predicate: object, *, timeout_seconds: float, label: str) -> None:
if not callable(predicate):
raise TypeError("wait predicate must be callable")
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
if predicate():
return
time.sleep(0.05)
raise RuntimeError(f"timed out waiting for {label}")
def run(args: argparse.Namespace) -> dict[str, object]:
repository_root = Path(args.repository_root).expanduser().resolve()
session_root = Path(args.session_root).expanduser().resolve()
epoch_root = session_root / "media" / "sensor.camera.right" / "epoch-1"
capture_path = session_root / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt"
camera = _camera_events(epoch_root, args.duration_seconds)
mqtt = _mqtt_events(
capture_path,
start_epoch_ns=camera[1].epoch_ns,
duration_seconds=args.duration_seconds,
)
events = sorted((*camera, *mqtt), key=lambda event: (event.epoch_ns, event.modality))
if not mqtt:
raise RuntimeError("selected replay interval contains no LiDAR or pose events")
ingress = LivePerceptionIngress()
_, token = ensure_live_shadow_token(repository_root)
app = FastAPI()
app.include_router(
build_live_perception_shadow_router(
ingress,
PLUGIN_ID,
bearer_token=token,
)
)
server = uvicorn.Server(
uvicorn.Config(
app,
host="127.0.0.1",
port=args.port,
log_level="warning",
access_log=False,
)
)
server_thread = threading.Thread(target=server.run, name="e12-shadow-source", daemon=True)
server_thread.start()
_wait_for(lambda: server.started, timeout_seconds=10.0, label="E12 source server")
session_id = f"e12-ravnoves00-{int(time.time())}"
ingress.begin_session(session_id)
_wait_for(
lambda: ingress.snapshot()["consumer_connected"],
timeout_seconds=args.consumer_timeout_seconds,
label="exclusive shadow worker",
)
started_monotonic = time.monotonic()
source_start_ns = events[0].epoch_ns
published: dict[str, int] = {}
try:
for event in events:
target = started_monotonic + (event.epoch_ns - source_start_ns) / 1_000_000_000
remaining = target - time.monotonic()
if remaining > 0:
time.sleep(remaining)
accepted = ingress.publish(
modality=event.modality,
source_id=event.source_id,
source_sequence=event.source_sequence,
captured_at_epoch_ns=event.epoch_ns,
received_monotonic_ns=event.monotonic_ns,
payload=event.payload,
)
if accepted:
published[event.modality] = published.get(event.modality, 0) + 1
ingress.end_session(session_id)
_wait_for(
lambda: not ingress.snapshot()["consumer_connected"],
timeout_seconds=10.0,
label="shadow worker report completion",
)
finally:
ingress.end_session(session_id)
ingress.close()
server.should_exit = True
server_thread.join(timeout=10.0)
completed_monotonic = time.monotonic()
source_snapshot = ingress.snapshot()
report: dict[str, object] = {
"schema_version": REPORT_SCHEMA,
"state": "completed",
"session_id": session_id,
"source": {
"kind": "recorded-source-paced-near-live",
"session": session_root.name,
"camera_source": "sensor.camera.right",
"camera_epoch": 1,
"duration_seconds": args.duration_seconds,
"mqtt_raw_sha256": _sha256(capture_path),
"camera_index_sha256": _sha256(epoch_root / "index.jsonl"),
},
"events_selected": {
"camera-init": sum(event.modality == "camera-init" for event in events),
"camera-frame": sum(event.modality == "camera-frame" for event in events),
"lidar": sum(event.modality == "lidar" for event in events),
"pose": sum(event.modality == "pose" for event in events),
},
"events_admitted": published,
"wall_seconds": completed_monotonic - started_monotonic,
"ingress": source_snapshot,
"authority": {
"mode": "shadow-diagnostic-only",
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
output_root = repository_root / ".runtime" / "compute-experiments" / "e12"
output_root.mkdir(parents=True, exist_ok=True)
stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
temporary = output_root / f".{stamp}-e12-source.json.tmp"
destination = output_root / f"{stamp}-e12-source.json"
encoded = json.dumps(report, indent=2, sort_keys=True).encode("utf-8") + b"\n"
with temporary.open("xb") as stream:
stream.write(encoded)
stream.flush()
os.fsync(stream.fileno())
temporary.replace(destination)
report["report_path"] = str(destination)
return report
def _arguments() -> argparse.Namespace:
root = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser()
parser.add_argument("--repository-root", default=str(root))
parser.add_argument(
"--session-root",
default=str(
root
/ ".runtime"
/ "mission-core"
/ "evidence"
/ "sessions"
/ "20260720T065719Z_viewer_live"
),
)
parser.add_argument("--port", type=int, default=8012)
parser.add_argument("--duration-seconds", type=float, default=15.0)
parser.add_argument("--consumer-timeout-seconds", type=float, default=30.0)
return parser.parse_args()
if __name__ == "__main__":
print(json.dumps(run(_arguments()), indent=2, sort_keys=True))
@@ -0,0 +1,809 @@
#!/usr/bin/env python3
"""Qualify a bounded replay-as-live world-state loop on immutable LAB E6 data."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import math
import os
import re
import resource
import shutil
import sys
import threading
import time
from collections import Counter
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from types import ModuleType
from typing import Any
import numpy as np
import numpy.typing as npt
import rerun as rr
from rerun.components import FillMode
import k1link.compute.live_perception as live_perception_module
import k1link.compute.live_replay_qualification as live_validator_module
from k1link.compute import (
TELEMETRY_SCHEMA,
LatestWinsQueue,
WorldStateProjector,
classify_health,
validate_live_replay_qualification_result,
validate_tracked_fusion_qualification_result,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
map_points_to_lidar,
)
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
PROFILE_SCHEMA = "missioncore.e7-replay-realtime-profile/v1"
RESULT_SCHEMA = "missioncore.e7-live-replay-result/v1"
IDENTITY_SCHEMA = "missioncore.e7-live-replay-identity/v1"
REPORT_SCHEMA = "missioncore.e7-live-replay-run-report/v1"
PIPELINE_ID = "bounded-latest-wins-e6-world-state-replay/v1"
_EXECUTION_ID = re.compile(r"^[a-z0-9][a-z0-9._-]{2,95}$")
@dataclass(frozen=True, slots=True)
class ReplayFrame:
ordinal: int
frame: dict[str, Any]
cloud_map: npt.NDArray[np.float32]
position_map_xyz: tuple[float, float, float] | None
orientation_map_from_lidar_xyzw: tuple[float, float, float, float] | None
@dataclass(frozen=True, slots=True)
class QueueEnvelope:
replay_frame: ReplayFrame
scheduled_monotonic: float
enqueued_monotonic: float
producer_lag_ms: float
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--tracking", type=Path, required=True)
parser.add_argument("--semantic", type=Path, required=True)
parser.add_argument("--e6-result", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--profile", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument("--execution-id", required=True)
return parser.parse_args()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _read_profile(path: Path) -> tuple[dict[str, Any], str]:
resolved = path.resolve(strict=True)
profile = json.loads(resolved.read_text(encoding="utf-8"))
if not isinstance(profile, dict) or profile.get("schema_version") != PROFILE_SCHEMA:
raise RuntimeError("LAB E7 profile schema changed")
selection = profile.get("selection")
replay = profile.get("replay")
health = profile.get("health")
world = profile.get("world_state")
presentation = profile.get("presentation")
acceptance = profile.get("acceptance")
if not all(
isinstance(value, dict)
for value in (selection, replay, health, world, presentation, acceptance)
):
raise RuntimeError("LAB E7 profile sections are invalid")
mode = profile.get("mode")
if mode not in {"pilot", "qualification", "overload-negative-control"}:
raise RuntimeError("LAB E7 mode is invalid")
start = selection.get("start_frame_index")
count = selection.get("frame_count")
capacity = replay.get("queue_capacity")
speed = replay.get("speed")
delay = replay.get("consumer_delay_ms")
stale = health.get("stale_after_ms")
unavailable = health.get("unavailable_after_ms")
if (
not isinstance(start, int)
or start < 0
or not isinstance(count, int)
or count < 2
or not isinstance(capacity, int)
or not 1 <= capacity <= 8
or not isinstance(speed, int | float)
or not 0.1 <= float(speed) <= 10.0
or not isinstance(delay, int | float)
or not 0 <= float(delay) <= 5000
or not isinstance(stale, int | float)
or not isinstance(unavailable, int | float)
or not 0 < float(stale) < float(unavailable)
):
raise RuntimeError("LAB E7 scheduling contract is invalid")
clearance = world.get("clearance")
if not isinstance(clearance, dict):
raise RuntimeError("LAB E7 clearance contract is invalid")
sectors = clearance.get("sector_count")
if not isinstance(sectors, int) or not 12 <= sectors <= 360:
raise RuntimeError("LAB E7 clearance sector count is invalid")
return profile, _sha256(resolved)
def _load_e6_runner() -> ModuleType:
path = Path(__file__).with_name("fuse_e6_tracking_lidar.py").resolve(strict=True)
spec = importlib.util.spec_from_file_location("missioncore_e6_replay_adapter", path)
if spec is None or spec.loader is None:
raise RuntimeError("LAB E6 replay adapter cannot be loaded")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
with path.open(encoding="utf-8") as stream:
for line in stream:
value = json.loads(line)
if not isinstance(value, dict):
raise RuntimeError("LAB E7 input JSONL row is invalid")
rows.append(value)
return rows
def _artifact_path(artifacts: Sequence[Mapping[str, Any]], kind: str, root: Path) -> Path:
matches = [item for item in artifacts if item.get("kind") == kind]
if len(matches) != 1 or not isinstance(matches[0].get("path"), str):
raise RuntimeError(f"LAB E7 upstream artifact is missing: {kind}")
return root / str(matches[0]["path"])
def _prepare_frames(
*,
e6: Any,
session: Path,
selection_start: int,
selection_count: int,
) -> list[ReplayFrame]:
if selection_start + selection_count > e6.frame_count:
raise RuntimeError("LAB E7 selection exceeds the E6 frame range")
runner = _load_e6_runner()
frames_path = _artifact_path(
e6.artifacts,
"tracked-lidar-frame-metadata",
e6.result_root,
)
frame_rows = _read_jsonl(frames_path)
arrays_path = _artifact_path(e6.artifacts, "tracked-lidar-arrays", e6.result_root)
tracking_frames = runner._read_tracking_frames(
e6.tracking.artifact("tracking-frame-metadata").path,
e6.tracking.frame_count,
e6.tracking.source_start_frame_index,
)
origin = read_capture_clock_origin(
session / "captures" / "mqtt_live" / "mqtt.timeline.origin.json"
)
epoch_root = (
e6.job.job_root
/ "input"
/ "camera"
/ e6.job.source_id
/ f"epoch-{e6.job.codec_epoch}"
)
anchors = runner._camera_anchors(
epoch_root / "index.jsonl",
tracking_frames,
origin_monotonic_ns=origin.started_monotonic_ns,
)
temporal = json.loads(
(e6.result_root / "run-report.json").read_text(encoding="utf-8")
)["identity"]["configuration"]["temporal"]
samples = list(
runner._lidar_samples(
session / "captures" / "mqtt_live" / "mqtt.raw.k1mqtt",
anchors,
origin_monotonic_ns=origin.started_monotonic_ns,
maximum_lidar_camera_delta_s=float(
temporal["maximum_lidar_camera_delta_ms"]
)
/ 1000.0,
maximum_pose_point_delta_s=float(temporal["maximum_pose_point_delta_ms"])
/ 1000.0,
)
)
prepared: list[ReplayFrame] = []
with np.load(arrays_path, allow_pickle=False) as arrays:
offsets = arrays["cloud_offsets"]
clouds = arrays["cloud_points"]
for ordinal in range(selection_start, selection_start + selection_count):
frame = frame_rows[ordinal]
sample = samples[ordinal]
if (frame["state"] == "fused") != (sample is not None):
raise RuntimeError("LAB E7 pose binding changed from the accepted E6 result")
start = int(offsets[ordinal])
end = int(offsets[ordinal + 1])
prepared.append(
ReplayFrame(
ordinal=ordinal,
frame=frame,
cloud_map=np.asarray(clouds[start:end], dtype=np.float32).copy(),
position_map_xyz=(
None
if sample is None
else tuple(float(value) for value in sample.pose_frame.position_xyz)
),
orientation_map_from_lidar_xyzw=(
None
if sample is None
else tuple(
float(value)
for value in sample.pose_frame.orientation_xyzw
)
),
)
)
return prepared
def _transform_to_lidar(
points_map: npt.NDArray[np.float32] | npt.NDArray[np.float64],
frame: ReplayFrame,
) -> npt.NDArray[np.float64]:
if frame.position_map_xyz is None or frame.orientation_map_from_lidar_xyzw is None:
return np.empty((0, 3), dtype=np.float64)
return map_points_to_lidar(
np.asarray(points_map, dtype=np.float64),
position_map_xyz=frame.position_map_xyz,
orientation_map_from_lidar_xyzw=frame.orientation_map_from_lidar_xyzw,
)
def _clearance(
points_lidar: npt.NDArray[np.float64],
profile: Mapping[str, Any],
) -> dict[str, Any]:
sector_count = int(profile["sector_count"])
empty = {
"schema": "diagnostic-polar-obstacle-clearance/v1",
"state": "unavailable",
"frame": "k1-lidar",
"front_m": None,
"observed_sector_fraction": 0.0,
"sector_ranges_m": [None] * sector_count,
}
if points_lidar.size == 0:
return empty
finite = np.all(np.isfinite(points_lidar), axis=1)
points = points_lidar[finite]
if points.size == 0:
return empty
ground_z = float(np.percentile(points[:, 2], float(profile["ground_percentile"])))
ranges = np.linalg.norm(points[:, :2], axis=1)
keep = (
(ranges >= float(profile["minimum_range_m"]))
& (ranges <= float(profile["maximum_range_m"]))
& (points[:, 2] >= ground_z + float(profile["minimum_height_above_ground_m"]))
& (points[:, 2] <= ground_z + float(profile["maximum_height_above_ground_m"]))
)
points = points[keep]
ranges = ranges[keep]
if points.size == 0:
return {**empty, "ground_z_estimate_m": ground_z}
angles = np.arctan2(points[:, 1], points[:, 0])
indices = np.floor((angles + math.pi) / (2.0 * math.pi) * sector_count).astype(int)
indices = np.clip(indices, 0, sector_count - 1)
sector_ranges = np.full(sector_count, np.inf, dtype=np.float64)
np.minimum.at(sector_ranges, indices, ranges)
observed = np.isfinite(sector_ranges)
front_half = math.radians(float(profile["front_half_angle_degrees"]))
front = ranges[np.abs(angles) <= front_half]
return {
"schema": "diagnostic-polar-obstacle-clearance/v1",
"state": "observed" if np.any(observed) else "unavailable",
"frame": "k1-lidar",
"ground_z_estimate_m": ground_z,
"front_m": None if front.size == 0 else float(np.min(front)),
"observed_sector_fraction": float(np.mean(observed)),
"sector_ranges_m": [
None if not math.isfinite(value) else float(value) for value in sector_ranges
],
"safety_accepted": False,
}
def _percentile(values: Sequence[float], quantile: float) -> float:
return float(np.percentile(np.asarray(values, dtype=np.float64), quantile))
def _peak_rss_mib() -> float:
value = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return float(value / (1024 * 1024) if sys.platform == "darwin" else value / 1024)
def _queue_document(queue: LatestWinsQueue[Any]) -> dict[str, Any]:
snapshot = queue.snapshot()
return {
"capacity": snapshot.capacity,
"depth": snapshot.depth,
"maximum_depth": snapshot.maximum_depth,
"published": snapshot.published,
"consumed": snapshot.consumed,
"dropped_overflow": snapshot.dropped_overflow,
"dropped_superseded": snapshot.dropped_superseded,
"closed": snapshot.closed,
}
def _artifact(path: Path, kind: str, media_type: str, schema: str | None = None) -> dict[str, Any]:
value: dict[str, Any] = {
"kind": kind,
"path": path.name,
"media_type": media_type,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
if schema is not None:
value["schema_version"] = schema
return value
def _accepted(profile: Mapping[str, Any], metrics: Mapping[str, Any]) -> tuple[bool, list[str]]:
acceptance = profile["acceptance"]
reasons: list[str] = []
if metrics["queue"]["maximum_depth"] > int(acceptance["maximum_queue_depth"]):
reasons.append("queue-depth")
if metrics["process_peak_rss_mib"] > float(acceptance["maximum_process_rss_mib"]):
reasons.append("process-rss")
if profile["mode"] == "overload-negative-control":
if metrics["drop_fraction"] < float(acceptance["minimum_drop_fraction"]):
reasons.append("negative-control-did-not-overload")
else:
if metrics["delivery_rate_hz"] < float(acceptance["minimum_delivery_rate_hz"]):
reasons.append("delivery-rate")
if metrics["end_to_end_latency_ms"]["p95"] > float(
acceptance["maximum_end_to_end_p95_ms"]
):
reasons.append("latency-p95")
if metrics["drop_fraction"] > float(acceptance["maximum_drop_fraction"]):
reasons.append("drop-fraction")
return not reasons, reasons
def main() -> int:
args = _arguments()
if _EXECUTION_ID.fullmatch(args.execution_id) is None:
raise RuntimeError("LAB E7 execution ID is invalid")
profile, profile_sha256 = _read_profile(args.profile)
session = args.session.resolve(strict=True)
preparation_started = time.perf_counter()
e6 = validate_tracked_fusion_qualification_result(
args.job,
args.tracking,
args.semantic,
args.e6_result,
)
if session.name != e6.job.session_id:
raise RuntimeError("LAB E7 session does not match the E6 result")
selection = profile["selection"]
replay_frames = _prepare_frames(
e6=e6,
session=session,
selection_start=int(selection["start_frame_index"]),
selection_count=int(selection["frame_count"]),
)
preparation_wall = time.perf_counter() - preparation_started
identity = {
"schema_version": IDENTITY_SCHEMA,
"execution_id": args.execution_id,
"pipeline": PIPELINE_ID,
"e6_result_id": e6.result_id,
"job_id": e6.job.job_id,
"input_sha256": e6.job.input_sha256,
"session_id": e6.job.session_id,
"source_id": e6.job.source_id,
"calibration_sha256": e6.semantic.calibration_sha256,
"camera_slot": e6.semantic.calibration_slot,
"selection": selection,
"configuration": profile,
"profile_sha256": profile_sha256,
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
"runtime_contract_sha256": _sha256(
Path(live_perception_module.__file__).resolve(strict=True)
),
"result_validator_sha256": _sha256(
Path(live_validator_module.__file__).resolve(strict=True)
),
"e6_adapter_sha256": _sha256(
Path(__file__).with_name("fuse_e6_tracking_lidar.py").resolve(strict=True)
),
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"e7-live-replay-{identity_sha256}"
parent = args.output_root.resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
output = parent / result_id
if output.exists():
print(json.dumps({"result_id": result_id, "output": str(output), "reused": True}))
return 0
staging = parent / f".{result_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
world_path = staging / "world-state.jsonl"
telemetry_path = staging / "telemetry.jsonl"
rrd_path = staging / "world-state.rrd"
report_path = staging / "run-report.json"
queue = LatestWinsQueue[QueueEnvelope](int(profile["replay"]["queue_capacity"]))
producer_error: list[BaseException] = []
recording: rr.RecordingStream | None = None
try:
recording = rr.RecordingStream("nodedc_mission_core_e7", recording_id=result_id)
recording.set_sinks(rr.FileSink(rrd_path, write_footer=True))
recording.log("/world", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True)
recording.log(
"/contract",
rr.TextDocument(
"LAB E7 replay-as-live: bounded latest-wins derived queue, explicit age and "
"degradation, E6 diagnostic objects, no vehicle-control authority."
),
static=True,
)
replay_speed = float(profile["replay"]["speed"])
first_session = float(replay_frames[0].frame["session_seconds"])
replay_started = time.perf_counter() + 0.25
def produce() -> None:
try:
for replay_frame in replay_frames:
offset = (
float(replay_frame.frame["session_seconds"]) - first_session
) / replay_speed
deadline = replay_started + offset
remaining = deadline - time.perf_counter()
if remaining > 0:
time.sleep(remaining)
enqueued = time.perf_counter()
queue.publish(
QueueEnvelope(
replay_frame=replay_frame,
scheduled_monotonic=deadline,
enqueued_monotonic=enqueued,
producer_lag_ms=max(0.0, (enqueued - deadline) * 1000.0),
)
)
except BaseException as exc: # noqa: BLE001 - forwarded to main thread
producer_error.append(exc)
finally:
queue.close()
producer = threading.Thread(target=produce, name="e7-replay-producer", daemon=True)
producer.start()
projector = WorldStateProjector(
velocity_history_limit_s=float(
profile["world_state"]["velocity_history_limit_s"]
)
)
health_counts: Counter[str] = Counter()
end_to_end: list[float] = []
queue_wait: list[float] = []
processing: list[float] = []
rerun_latency: list[float] = []
producer_lag: list[float] = []
published_indices: list[int] = []
consumer_delay = float(profile["replay"]["consumer_delay_ms"]) / 1000.0
with world_path.open("w", encoding="utf-8") as world_stream, telemetry_path.open(
"w", encoding="utf-8"
) as telemetry_stream:
while True:
envelope = queue.take_next(timeout=0.5)
if envelope is None:
if queue.snapshot().closed:
break
continue
dequeued = time.perf_counter()
if consumer_delay > 0:
time.sleep(consumer_delay)
stage_started = time.perf_counter()
replay_frame = envelope.replay_frame
cloud_lidar = _transform_to_lidar(replay_frame.cloud_map, replay_frame)
accepted = [
item
for item in replay_frame.frame.get("objects", [])
if str(item.get("cuboid_status", "")).startswith("accepted-")
]
centers_map = np.asarray(
[item["cuboid_center_map"] for item in accepted], dtype=np.float64
).reshape((-1, 3))
centers_lidar = _transform_to_lidar(centers_map, replay_frame)
lidar_positions = {
int(item["track_id"]): centers_lidar[index]
for index, item in enumerate(accepted)
}
world = projector.project(
frame=replay_frame.frame,
lidar_positions=lidar_positions,
clearance=_clearance(
cloud_lidar,
profile["world_state"]["clearance"],
),
)
projection_finished = time.perf_counter()
relative_seconds = (
float(replay_frame.frame["session_seconds"]) - first_session
)
recording.set_time(
"replay_time",
duration=np.timedelta64(round(relative_seconds * 1e9), "ns"),
)
if bool(profile["presentation"]["log_point_cloud"]) and replay_frame.cloud_map.size:
recording.log(
"/world/lidar",
rr.Points3D(
replay_frame.cloud_map,
colors=np.tile(
np.asarray([[145, 145, 145]], dtype=np.uint8),
(replay_frame.cloud_map.shape[0], 1),
),
radii=rr.Radius.ui_points(
float(profile["presentation"]["point_radius_ui"])
),
),
)
elif not replay_frame.cloud_map.size:
recording.log("/world/lidar", rr.Clear(recursive=False))
if accepted:
colors = []
labels = []
for item in accepted:
group = str(item["association_group"])
colors.append(
{
"person": [255, 70, 170, 88],
"vehicle": [130, 90, 255, 88],
"bicycle": [80, 220, 255, 88],
"motorcycle": [255, 180, 65, 88],
}.get(group, [220, 220, 220, 88])
)
labels.append(
f"{group} #{item['track_id']} · "
f"{float(item['distance_smoothed_m']):.1f} m"
)
recording.log(
"/world/perception/boxes3d",
rr.Boxes3D(
centers=centers_map.astype(np.float32),
half_sizes=np.asarray(
[item["cuboid_half_size"] for item in accepted],
dtype=np.float32,
),
quaternions=np.asarray(
[item["cuboid_quaternion_xyzw"] for item in accepted],
dtype=np.float32,
),
colors=np.asarray(colors, dtype=np.uint8),
labels=labels,
fill_mode=FillMode.Solid,
show_labels=True,
),
)
else:
recording.log("/world/perception/boxes3d", rr.Clear(recursive=False))
rerun_finished = time.perf_counter()
age_ms = (rerun_finished - envelope.scheduled_monotonic) * 1000.0
health, reasons = classify_health(
source_available=True,
fusion_state=str(replay_frame.frame["state"]),
result_age_ms=age_ms,
stale_after_ms=float(profile["health"]["stale_after_ms"]),
unavailable_after_ms=float(profile["health"]["unavailable_after_ms"]),
)
health_counts[health] += 1
world["delivery"] = {
"health": health,
"reasons": list(reasons),
"result_age_ms": age_ms,
"queue_policy": "bounded-latest-wins",
}
world_stream.write(json.dumps(world, ensure_ascii=False, allow_nan=False) + "\n")
published = time.perf_counter()
latency_ms = (published - envelope.scheduled_monotonic) * 1000.0
recording.log("/telemetry/end_to_end_latency_ms", rr.Scalars([latency_ms]))
recording.log("/telemetry/queue_depth", rr.Scalars([queue.snapshot().depth]))
recording.log(
"/telemetry/health_code",
rr.Scalars(
[{"healthy": 0, "degraded": 1, "stale": 2, "unavailable": 3}[health]]
),
)
telemetry = {
"schema_version": TELEMETRY_SCHEMA,
"frame_index": int(replay_frame.frame["frame_index"]),
"source_frame_index": int(replay_frame.frame["source_frame_index"]),
"session_seconds": float(replay_frame.frame["session_seconds"]),
"producer_lag_ms": envelope.producer_lag_ms,
"queue_wait_ms": (dequeued - envelope.enqueued_monotonic) * 1000.0,
"world_projection_ms": (projection_finished - stage_started) * 1000.0,
"rerun_publish_ms": (rerun_finished - projection_finished) * 1000.0,
"end_to_end_latency_ms": latency_ms,
"health": health,
"queue": _queue_document(queue),
}
telemetry_stream.write(
json.dumps(telemetry, ensure_ascii=False, allow_nan=False) + "\n"
)
end_to_end.append(latency_ms)
queue_wait.append(telemetry["queue_wait_ms"])
processing.append(telemetry["world_projection_ms"])
rerun_latency.append(telemetry["rerun_publish_ms"])
producer_lag.append(envelope.producer_lag_ms)
published_indices.append(int(replay_frame.frame["frame_index"]))
producer.join(timeout=5)
if producer.is_alive():
raise RuntimeError("LAB E7 replay producer did not stop")
if producer_error:
raise RuntimeError("LAB E7 replay producer failed") from producer_error[0]
if recording is not None:
recording.flush()
recording.disconnect()
recording = None
finished = time.perf_counter()
queue_metrics = queue.snapshot()
replay_span = (
float(replay_frames[-1].frame["session_seconds"])
- float(replay_frames[0].frame["session_seconds"])
) / replay_speed
replay_wall = finished - replay_started
attempted = len(replay_frames)
processed = len(published_indices)
metrics = {
"frames_attempted": attempted,
"frames_processed": processed,
"frames_dropped": queue_metrics.dropped_total,
"drop_fraction": queue_metrics.dropped_total / attempted,
"delivery_rate_hz": processed / replay_span,
"replay_span_seconds": replay_span,
"replay_wall_seconds": replay_wall,
"preparation_wall_seconds": preparation_wall,
"process_peak_rss_mib": _peak_rss_mib(),
"health_counts": dict(health_counts),
"queue": {
"capacity": queue_metrics.capacity,
"maximum_depth": queue_metrics.maximum_depth,
"final_depth": queue_metrics.depth,
"published": queue_metrics.published,
"consumed": queue_metrics.consumed,
"dropped_overflow": queue_metrics.dropped_overflow,
"dropped_superseded": queue_metrics.dropped_superseded,
},
"end_to_end_latency_ms": {
"mean": float(np.mean(end_to_end)),
"p50": _percentile(end_to_end, 50),
"p95": _percentile(end_to_end, 95),
"max": max(end_to_end),
},
"queue_wait_ms": {
"mean": float(np.mean(queue_wait)),
"p95": _percentile(queue_wait, 95),
"max": max(queue_wait),
},
"world_projection_ms": {
"mean": float(np.mean(processing)),
"p95": _percentile(processing, 95),
"max": max(processing),
},
"rerun_publish_ms": {
"mean": float(np.mean(rerun_latency)),
"p95": _percentile(rerun_latency, 95),
"max": max(rerun_latency),
},
"producer_lag_ms": {
"mean": float(np.mean(producer_lag)),
"p95": _percentile(producer_lag, 95),
"max": max(producer_lag),
},
"published_frame_index_start": min(published_indices),
"published_frame_index_end": max(published_indices),
}
accepted, rejection_reasons = _accepted(profile, metrics)
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC).isoformat(timespec="milliseconds").replace(
"+00:00", "Z"
),
"identity": identity,
"acceptance": {
"accepted": accepted,
"mode": profile["mode"],
"rejection_reasons": rejection_reasons,
"navigation_or_safety_accepted": False,
"scope": "replay-scheduler-and-world-state-not-live-inference",
},
"metrics": metrics,
}
report_path.write_text(
json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
artifacts = [
_artifact(
world_path,
"live-world-state",
"application/x-ndjson",
"missioncore.live-perception-world-state/v1",
),
_artifact(
telemetry_path,
"live-telemetry",
"application/x-ndjson",
TELEMETRY_SCHEMA,
),
_artifact(rrd_path, "live-rerun", "application/vnd.rerun.rrd"),
_artifact(report_path, "live-run-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"ground_truth": False,
"publication_scope": "replay-qualification-only",
"acceptance_state": "accepted" if accepted else "rejected",
"metrics": metrics,
"artifacts": artifacts,
}
(staging / "result.json").write_text(
json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
staging.rename(output)
validated = validate_live_replay_qualification_result(
args.job,
args.tracking,
args.semantic,
args.e6_result,
output,
)
if validated.result_id != result_id or validated.accepted != accepted:
raise RuntimeError("LAB E7 published result failed identity validation")
print(
json.dumps(
{
"result_id": result_id,
"output": str(output),
"accepted": accepted,
"metrics": metrics,
},
ensure_ascii=False,
)
)
return 0 if accepted else 2
except BaseException:
if recording is not None:
recording.disconnect()
queue.close()
shutil.rmtree(staging, ignore_errors=True)
raise
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,153 @@
[CmdletBinding()]
param(
[ValidateRange(1, 1000)] [int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3@sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
[string]$RuntimeName = "perception-e15-media-pyav180-lz445-v1"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a real D: directory"
}
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return $Path.Replace("\", "/") }
function Assert-FreeSpace([string]$Phase) {
$free = [int64](Get-PSDrive -Name D).Free
$floor = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3}" -f
$Phase, $free, [math]::Round($free / 1GB, 3), $FreeGiBFloor
)
if ($free -lt ($floor + 2GB)) {
throw "D: lacks the guarded LAB E15 media-runtime reserve during $Phase"
}
return $free
}
function Get-PayloadDigest([string]$Root) {
$resolved = Resolve-DDirectory $Root "E15 media runtime"
$lines = @(
Get-ChildItem -LiteralPath $resolved -Recurse -File -Force |
Where-Object { $_.Name -ne "manifest.json" } |
Sort-Object FullName |
ForEach-Object {
$relative = $_.FullName.Substring($resolved.Length).TrimStart("\").Replace("\", "/")
$hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
"{0}`t{1}`t{2}" -f $relative, $_.Length, $hash
}
)
if ($lines.Count -lt 4) { throw "E15 media runtime payload is incomplete" }
$bytes = [Text.Encoding]::UTF8.GetBytes(($lines -join "`n") + "`n")
$hasher = [Security.Cryptography.SHA256]::Create()
try {
return ([BitConverter]::ToString($hasher.ComputeHash($bytes))).Replace("-", "").ToLowerInvariant()
}
finally { $hasher.Dispose() }
}
function Assert-Runtime([string]$Path) {
$root = Resolve-DDirectory $Path "E15 media runtime"
$manifestPath = Join-Path $root "manifest.json"
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
throw "E15 media runtime manifest is missing"
}
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
if (
$manifest.schema_version -ne "missioncore.e15-media-runtime/v1" -or
$manifest.runtime_name -ne $RuntimeName -or
$manifest.container_image -ne $ContainerImage -or
$manifest.packages.av -ne "18.0.0" -or
$manifest.packages.lz4 -ne "4.4.5" -or
$manifest.payload_sha256 -ne (Get-PayloadDigest $root)
) { throw "E15 media runtime identity changed" }
$dockerRoot = Convert-ToDockerPath $root
$verifyCode = "import av,lz4.version;print(av.__version__);print(lz4.version.version)"
$verifyOutput = @(& docker run --rm --network none --read-only `
--security-opt "no-new-privileges:true" --cap-drop ALL --pids-limit 64 `
--tmpfs "/tmp:rw,noexec,nosuid,size=64m" `
-e "PYTHONPATH=/opt/media" -e "PYTHONDONTWRITEBYTECODE=1" `
-v ("{0}:/opt/media:ro" -f $dockerRoot) `
--entrypoint python3 $ContainerImage -c $verifyCode)
Assert-LastExitCode "E15 media runtime verification"
if ($verifyOutput.Count -ne 2 -or $verifyOutput[0] -ne "18.0.0" -or $verifyOutput[1] -ne "4.4.5") {
throw "E15 media runtime package versions changed"
}
Write-Host "MEDIA_RUNTIME_OK pyav=18.0.0 lz4=4.4.5"
return $root
}
$runtime = Resolve-DDirectory $RuntimeRoot "Runtime root"
$derived = Resolve-DDirectory (Join-Path $runtime "derived") "Runtime derived root"
$destination = Join-Path $derived $RuntimeName
$freeBefore = Assert-FreeSpace "media-runtime-preflight"
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Pinned container image inspection"
if (Test-Path -LiteralPath $destination) {
$resolved = Assert-Runtime $destination
Write-Output "STATE=existing-verified"
Write-Output ("MEDIA_RUNTIME_ROOT={0}" -f $resolved)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBefore)
Write-Output ("DISK_FREE_BYTES_AFTER={0}" -f ([int64](Get-PSDrive -Name D).Free))
return
}
$token = [Guid]::NewGuid().ToString("N")
$staging = Join-Path $derived (".{0}-{1}.tmp" -f $RuntimeName, $token)
$null = New-Item -ItemType Directory -Path $staging
$completed = $false
try {
$dockerStaging = Convert-ToDockerPath $staging
Write-Output "PHASE=media-runtime-install-start"
& docker run --rm --network bridge --read-only `
--security-opt "no-new-privileges:true" --cap-drop ALL --pids-limit 128 `
--tmpfs "/tmp:rw,nosuid,size=1g" `
-e "PIP_DISABLE_PIP_VERSION_CHECK=1" -e "PYTHONDONTWRITEBYTECODE=1" `
-v ("{0}:/target:rw" -f $dockerStaging) `
--entrypoint python3 $ContainerImage -m pip install `
--no-cache-dir --only-binary ":all:" --target /target `
"av==18.0.0" "lz4==4.4.5"
Assert-LastExitCode "E15 media runtime installation"
$payloadSha256 = Get-PayloadDigest $staging
$manifest = [ordered]@{
schema_version = "missioncore.e15-media-runtime/v1"
runtime_name = $RuntimeName
created_at_utc = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
container_image = $ContainerImage
packages = [ordered]@{ av = "18.0.0"; lz4 = "4.4.5" }
payload_sha256 = $payloadSha256
storage_scope = "D-only-immutable-runtime"
}
$manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $staging "manifest.json") -Encoding utf8
$null = Assert-Runtime $staging
Move-Item -LiteralPath $staging -Destination $destination
$completed = $true
$resolved = Assert-Runtime $destination
$freeAfter = Assert-FreeSpace "media-runtime-published"
Write-Output "STATE=created-verified"
Write-Output ("MEDIA_RUNTIME_ROOT={0}" -f $resolved)
Write-Output ("PAYLOAD_SHA256={0}" -f $payloadSha256)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBefore)
Write-Output ("DISK_FREE_BYTES_AFTER={0}" -f $freeAfter)
}
finally {
if (-not $completed -and (Test-Path -LiteralPath $staging)) {
Remove-Item -LiteralPath $staging -Recurse -Force
}
}
@@ -0,0 +1,313 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)] [string]$JobRoot,
[Parameter(Mandatory = $true)] [string]$RunnerPath,
[Parameter(Mandatory = $true)] [string]$ProfilePath,
[Parameter(Mandatory = $true)] [string]$DetectorProfilePath,
[Parameter(Mandatory = $true)] [string]$SemanticProfilePath,
[Parameter(Mandatory = $true)] [string]$ValidFovRoot,
[Parameter(Mandatory = $true)] [string]$LidarPackRoot,
[ValidateRange(0, 1000000)] [int]$StartFrame = 1000,
[ValidateRange(0, 1000000)] [int]$EndFrame = 1600,
[ValidateRange(1, 1000)] [int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ModelRoot = "D:\NDC_MISSIONCORE\runtime\models\yolox_s",
[string]$TritonContainer = "mission-core-triton",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a real D: directory"
}
return $item.FullName
}
function Resolve-DFile([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a regular D: file"
}
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return $Path.Replace("\", "/") }
function Get-DFreeBytes { return [int64](Get-PSDrive -Name D).Free }
function Assert-FreeSpace([string]$Phase, [int64]$RequiredAdditionalBytes = 0) {
$free = Get-DFreeBytes
$floor = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase, $free, [math]::Round($free / 1GB, 3), $FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($free -lt ($floor + $RequiredAdditionalBytes)) {
throw "D: lacks the guarded LAB E10 reserve during $Phase"
}
return $free
}
function Test-TritonModelReady {
try {
$response = Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 `
-Uri "http://127.0.0.1:8000/v2/models/yolox_s/ready"
return $response.StatusCode -eq 200
}
catch { return $false }
}
$jobDirectory = Resolve-DDirectory $JobRoot "Job root"
$runner = Resolve-DFile $RunnerPath "LAB E10 runner"
$profile = Resolve-DFile $ProfilePath "LAB E10 profile"
$detectorProfile = Resolve-DFile $DetectorProfilePath "Detector profile"
$semanticProfile = Resolve-DFile $SemanticProfilePath "Semantic profile"
$validFov = Resolve-DDirectory $ValidFovRoot "Valid-FOV root"
$lidarPack = Resolve-DDirectory $LidarPackRoot "LiDAR replay pack"
$runtime = Resolve-DDirectory $RuntimeRoot "Runtime root"
$model = Resolve-DDirectory $ModelRoot "YOLOX model root"
$runnerRoot = Split-Path $runner -Parent
foreach ($path in @($profile, $detectorProfile, $semanticProfile)) {
if ((Split-Path $path -Parent) -ne $runnerRoot) { throw "Runner and profiles must share one mount" }
}
foreach ($dependency in @(
"e10_fusion_runtime.py",
"run_e9_multirate_perception.py",
"run_e8_realtime_tracking.py",
"run_e5_instance_tracking.py",
"run_e4_full_session_segmentation.py",
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Resolve-DFile (Join-Path $runnerRoot $dependency) "LAB E10 dependency"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$fullFrameCount = [int]$job.input.segment_count
$clipFrameCount = $EndFrame - $StartFrame + 1
$timelineStart = [double]$job.input.timeline.start_seconds
if ($sourceId -ne "sensor.camera.right" -or $StartFrame -lt 0 -or $EndFrame -lt $StartFrame -or $EndFrame -ge $fullFrameCount -or $clipFrameCount -lt 2) {
throw "LAB E10 clip escapes the camera job"
}
$lidarManifest = Get-Content -LiteralPath (Join-Path $lidarPack "manifest.json") -Raw | ConvertFrom-Json
if (
$lidarManifest.schema_version -ne "missioncore.e10-lidar-replay-pack/v1" -or
$lidarManifest.identity.job_id -ne $job.job_id -or
[int]$lidarManifest.identity.source_start_frame_index -ne $StartFrame -or
[int]$lidarManifest.identity.source_end_frame_index -ne $EndFrame -or
[int]$lidarManifest.identity.frame_count -ne $clipFrameCount
) { throw "LAB E10 LiDAR pack differs from the selected camera clip" }
Write-Output ("PHASE=inputs-validated JOB={0} CLIP={1}-{2} FRAMES={3} LIDAR_PACK={4}" -f $job.job_id, $StartFrame, $EndFrame, $clipFrameCount, $lidarManifest.pack_id)
$epochRoot = Resolve-DDirectory (Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)) "Camera epoch"
$initPath = Resolve-DFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Resolve-DDirectory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$cacheRoot = Resolve-DDirectory (Join-Path $runtime "cache\perception-e3-models-v1") "EoMT cache"
$e3Environment = Resolve-DDirectory (Join-Path $derivedRoot "perception-e3-opencv413092-v1") "E3 environment"
$torchEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-transformers4576-v1") "Transformers environment"
$pixelWorkingSet = [int64]$clipFrameCount * 800 * 600 * 3
$partialStreamReserve = [int64][math]::Ceiling(([int64]$job.input.byte_length * ($EndFrame + 1) / $fullFrameCount) * 1.2)
$workingSetReserve = [int64][math]::Ceiling($pixelWorkingSet * 1.4) + $partialStreamReserve + 3GB
$freeBytesBefore = Assert-FreeSpace "preflight" $workingSetReserve
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
$tritonState = docker inspect --format "{{.State.Running}}" $TritonContainer
Assert-LastExitCode "Triton container inspection"
if ($tritonState.Trim().ToLowerInvariant() -ne "true") { throw "LAB E10 requires the existing Triton container" }
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$detectorProfileName = Split-Path $detectorProfile -Leaf
$semanticProfileName = Split-Path $semanticProfile -Leaf
$lidarMount = "/" + (Split-Path $lidarPack -Leaf)
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$commonMounts = @(
"-e", "PYTHONPATH=/runner:/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $lidarPack) + (":{0}:ro" -f $lidarMount)),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $e3Environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro")
)
$commonRunnerArgs = @(
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--detector-profile", ("/runner/{0}" -f $detectorProfileName),
"--semantic-profile", ("/runner/{0}" -f $semanticProfileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model",
"--cache", "/cache",
"--environment", "/environment",
"--lidar-pack", $lidarMount
)
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "preflight"
) + $commonRunnerArgs
Write-Output "PHASE=e10-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E10 preflight"
Write-Output "PHASE=e10-preflight-complete"
$modelWasReady = Test-TritonModelReady
$loadedByRun = $false
if (-not $modelWasReady) {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 -Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" -Method Post -ContentType "application/json" -Body "{}" *> $null
$loadedByRun = $true
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady)) {
if ([DateTime]::UtcNow -ge $deadline) { throw "YOLOX-S did not become ready" }
Start-Sleep -Milliseconds 500
}
}
Write-Output ("PHASE=e10-model-ready LOADED_BY_RUN={0}" -f $loadedByRun)
$token = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e10-{1}" -f $job.job_id, $token)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e10-{1}.publish" -f $job.job_id, $token)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$completed = $false
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
try {
$stream = [IO.File]::Open($streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
$input = [IO.File]::OpenRead($initPath)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
for ($sequence = 1; $sequence -le ($EndFrame + 1); $sequence++) {
$path = Resolve-DFile (Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)) "Camera segment"
$input = [IO.File]::OpenRead($path)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
}
$stream.Flush($true)
}
finally { $stream.Dispose() }
$null = Assert-FreeSpace "post-stream-reconstruction"
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
$selectFilter = "select='between(n,{0},{1})'" -f $StartFrame, $EndFrame
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 -vf $selectFilter -fps_mode passthrough (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "LAB E10 camera extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time -of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E10 timestamp probe"
$decoded = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$pts = @((Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json).frames)
if ($decoded.Count -ne $clipFrameCount -or $pts.Count -ne ($EndFrame + 1)) { throw "LAB E10 decoded frame count changed" }
$firstEpochSeconds = [double]::Parse(([string]$pts[0].best_effort_timestamp_time).Trim(), [Globalization.CultureInfo]::InvariantCulture)
$writer = [IO.StreamWriter]::new($timelinePath, $false, [Text.UTF8Encoding]::new($false))
try {
for ($local = 0; $local -lt $clipFrameCount; $local++) {
$source = $StartFrame + $local
$epochSeconds = [double]::Parse(([string]$pts[$source].best_effort_timestamp_time).Trim(), [Globalization.CultureInfo]::InvariantCulture) - $firstEpochSeconds
$row = [ordered]@{
frame_index = $local
sequence = $local + 1
source_frame_index = $source
source_sequence = $source + 1
epoch_seconds = $epochSeconds
session_seconds = $timelineStart + $epochSeconds
}
$writer.WriteLine(($row | ConvertTo-Json -Compress))
}
$writer.Flush()
}
finally { $writer.Dispose() }
$extractWatch.Stop()
$freePostExtract = Assert-FreeSpace "post-frame-extraction"
$runArgs = @(
"run", "--rm", "--gpus", "all", "--network", ("container:{0}" -f $TritonContainer),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "run"
) + $commonRunnerArgs + @(
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--output", "/publish/output",
"--triton-url", "http://127.0.0.1:8000",
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage
)
Write-Output ("PHASE=e10-integrated-replay-start FRAMES={0}" -f $clipFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E10 integrated replay"
$freePostReplay = Assert-FreeSpace "post-replay"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if ($result.schema_version -ne "missioncore.e10-integrated-perception-result/v1" -or $result.result_id -notmatch "^e10-integrated-perception-[a-f0-9]{64}$") {
throw "LAB E10 result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) { throw "Immutable LAB E10 result already exists" }
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
$totalWatch.Stop()
$freeFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("ACCEPTANCE_STATE={0}" -f $result.acceptance_state)
Write-Output ("PREPARATION_SECONDS={0}" -f $extractWatch.Elapsed.TotalSeconds)
Write-Output ("ORCHESTRATOR_WALL_SECONDS={0}" -f $totalWatch.Elapsed.TotalSeconds)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freePostExtract)
Write-Output ("DISK_FREE_BYTES_POST_REPLAY={0}" -f $freePostReplay)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeFinal)
}
finally {
if (Test-Path -LiteralPath $workRoot) { Remove-Item -LiteralPath $workRoot -Recurse -Force }
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) { Remove-Item -LiteralPath $publishRoot -Recurse -Force }
if ($loadedByRun) {
try {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 -Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/unload" -Method Post -ContentType "application/json" -Body "{}" *> $null
Write-Output "PHASE=e10-model-state-restored"
}
catch { Write-Warning "LAB E10 could not restore the prior YOLOX-S state" }
}
}
@@ -0,0 +1,100 @@
[CmdletBinding()]
param(
[switch]$TokenStdin,
[ValidatePattern("^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")]
[string]$RequestId = ("physical-k1-shadow-{0}" -f [Guid]::NewGuid().ToString("N")),
[string]$PersistentContainer = "mission-core-perception-worker",
[string]$PersistentOutputRoot = "D:\NDC_MISSIONCORE\runtime\derived\.perception-persistent-publish",
[ValidateRange(1024, 65535)] [int]$PersistentPort = 18020,
[ValidateRange(1, 1000)] [int]$FreeGiBFloor = 360
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a real D: directory"
}
return $item.FullName
}
function Get-DFreeBytes { return [int64](Get-PSDrive -Name D).Free }
if (-not $TokenStdin) { throw "Persistent shadow run requires the token through stdin" }
$token = [Console]::In.ReadLine()
if (-not $token -or $token.Length -lt 40 -or $token.Length -gt 512) {
throw "Persistent shadow token is missing or malformed"
}
$outputRoot = Resolve-DDirectory $PersistentOutputRoot "Persistent output root"
$freeBefore = Get-DFreeBytes
if ($freeBefore -lt ([int64]$FreeGiBFloor * 1GB + 512MB)) {
throw "D: lacks the guarded persistent-run reserve"
}
$containerRunning = docker inspect --format "{{.State.Running}}" $PersistentContainer
Assert-LastExitCode "Persistent worker inspection"
if ($containerRunning.Trim().ToLowerInvariant() -ne "true") {
throw "Persistent worker is not running"
}
$outputName = $RequestId
$request = @{
request_id = $RequestId
output_name = $outputName
token = $token
} | ConvertTo-Json -Compress
$token = $null
$client = (
"import sys,urllib.request,urllib.error;data=sys.stdin.buffer.read();" +
"request=urllib.request.Request('http://127.0.0.1:{0}/run',data=data," -f $PersistentPort
) + "headers={'Content-Type':'application/json'},method='POST');" +
"`ntry:`n response=urllib.request.urlopen(request,timeout=3600); body=response.read(); code=response.status" +
"`nexcept urllib.error.HTTPError as exc:`n body=exc.read(); code=exc.code" +
"`nsys.stdout.buffer.write(body);raise SystemExit(0 if code==200 else 22)"
try {
$responseJson = $request | & docker exec -i $PersistentContainer python3 -c $client
$request = $null
Assert-LastExitCode "Persistent worker request"
}
finally {
$token = $null
$request = $null
}
$response = $responseJson | ConvertFrom-Json
if ($response.request_id -ne $RequestId -or $response.models_reused -ne $true) {
throw "Persistent worker response contract changed"
}
$staging = Join-Path $outputRoot $outputName
$resultPath = Join-Path $staging "result.json"
if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) {
throw "Persistent worker result manifest is missing"
}
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.e15-shadow-inference-result/v1" -or
$result.result_id -notmatch "^e15-shadow-inference-[a-f0-9]{64}$" -or
$result.publication_scope -ne "live-shadow-diagnostic-only"
) { throw "Persistent worker result manifest is incompatible" }
$derivedRoot = Resolve-DDirectory (Split-Path $outputRoot -Parent) "Runtime derived root"
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "Immutable persistent-worker result already exists"
}
Move-Item -LiteralPath $staging -Destination $finalRoot
$freeFinal = Get-DFreeBytes
if ($freeFinal -lt ([int64]$FreeGiBFloor * 1GB)) {
throw "D: crossed the guarded floor after persistent run"
}
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("ACCEPTANCE_STATE={0}" -f $result.acceptance_state)
Write-Output ("RUNNER_EXIT_CODE={0}" -f $response.exit_code)
Write-Output "MODELS_REUSED=true"
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBefore)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeFinal)
@@ -0,0 +1,432 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)] [string]$JobRoot,
[Parameter(Mandatory = $true)] [string]$RunnerPath,
[Parameter(Mandatory = $true)] [string]$LiveProfilePath,
[Parameter(Mandatory = $true)] [string]$E14ProfilePath,
[Parameter(Mandatory = $true)] [string]$DetectorProfilePath,
[Parameter(Mandatory = $true)] [string]$SemanticProfilePath,
[Parameter(Mandatory = $true)] [string]$ValidFovRoot,
[Parameter(Mandatory = $true)] [string]$ProjectionPackRoot,
[Parameter(Mandatory = $true)] [string]$PackageRoot,
[switch]$PreflightOnly,
[switch]$PersistentService,
[switch]$TokenStdin,
[ValidateRange(5, 3600)] [int]$MaximumDurationSeconds = 20,
[ValidateRange(1, 1000)] [int]$FreeGiBFloor = 360,
[ValidateRange(1024, 65535)] [int]$SourcePort = 18012,
[string]$SourceHost = "host.docker.internal",
[string]$SourcePath = "/api/v1/device-plugins/nodedc.device.xgrids-lixelkity-k1/live-perception-shadow",
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ModelRoot = "D:\NDC_MISSIONCORE\runtime\models\yolox_s",
[string]$MediaRuntimeRoot = "D:\NDC_MISSIONCORE\runtime\derived\perception-e15-media-pyav180-lz445-v1",
[string]$TritonContainer = "mission-core-triton",
[string]$PersistentContainer = "mission-core-perception-worker",
[string]$PersistentOutputRoot = "D:\NDC_MISSIONCORE\runtime\derived\.perception-persistent-publish",
[ValidateRange(1024, 65535)] [int]$PersistentPort = 18020,
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3@sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a real D: directory"
}
return $item.FullName
}
function Resolve-DFile([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a regular D: file"
}
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return $Path.Replace("\", "/") }
function Get-DFreeBytes { return [int64](Get-PSDrive -Name D).Free }
function Assert-FreeSpace([string]$Phase, [int64]$RequiredAdditionalBytes = 0) {
$free = Get-DFreeBytes
$floor = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase, $free, [math]::Round($free / 1GB, 3), $FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($free -lt ($floor + $RequiredAdditionalBytes)) {
throw "D: lacks the guarded LAB E15 reserve during $Phase"
}
return $free
}
function Test-TritonModelReady {
try {
$response = Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 `
-Uri "http://127.0.0.1:8000/v2/models/yolox_s/ready"
return $response.StatusCode -eq 200
}
catch { return $false }
}
$jobDirectory = Resolve-DDirectory $JobRoot "Job root"
$runner = Resolve-DFile $RunnerPath "LAB E15 runner"
$liveProfile = Resolve-DFile $LiveProfilePath "LAB E15 live profile"
$e14Profile = Resolve-DFile $E14ProfilePath "Accepted E14 profile"
$detectorProfile = Resolve-DFile $DetectorProfilePath "Detector profile"
$semanticProfile = Resolve-DFile $SemanticProfilePath "Semantic profile"
$validFov = Resolve-DDirectory $ValidFovRoot "Valid-FOV root"
$projectionPack = Resolve-DDirectory $ProjectionPackRoot "E15 projection pack"
$package = Resolve-DDirectory $PackageRoot "Mission Core package root"
$runtime = Resolve-DDirectory $RuntimeRoot "Runtime root"
$model = Resolve-DDirectory $ModelRoot "YOLOX model root"
$mediaRuntime = Resolve-DDirectory $MediaRuntimeRoot "E15 media runtime"
$runnerRoot = Split-Path $runner -Parent
foreach ($path in @($liveProfile, $e14Profile, $detectorProfile, $semanticProfile)) {
if ((Split-Path $path -Parent) -ne $runnerRoot) {
throw "Runner and profiles must share one immutable mount"
}
}
foreach ($dependency in @(
"e10_fusion_runtime.py",
"e15_shadow_runtime.py",
"run_e10_integrated_perception.py",
"run_e12_shadow_transport_probe.py",
"run_e9_multirate_perception.py",
"run_e8_realtime_tracking.py",
"run_e5_instance_tracking.py",
"run_e4_full_session_segmentation.py",
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Resolve-DFile (Join-Path $runnerRoot $dependency) "LAB E15 dependency"
}
if (-not (Test-Path -LiteralPath (Join-Path $package "k1link\compute\live_perception.py") -PathType Leaf)) {
throw "Mission Core package mount lacks live perception synchronization"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if (
$job.schema_version -ne "missioncore.compute-job/v1" -or
$job.job_id -ne (Split-Path $jobDirectory -Leaf) -or
$job.input.source_id -ne "sensor.camera.right"
) { throw "Compute bootstrap job manifest is incompatible" }
$live = Get-Content -LiteralPath $liveProfile -Raw | ConvertFrom-Json
if (
$live.schema_version -ne "missioncore.e15-shadow-inference-profile/v1" -or
$live.mode -ne "replay-shadow-gate" -or
[bool]$live.authority.commands_enabled -or
[bool]$live.authority.navigation_or_safety_accepted -or
$live.transport.pyav_version -ne "18.0.0"
) { throw "LAB E15 replay-shadow authority contract changed" }
$projection = Get-Content -LiteralPath (Join-Path $projectionPack "manifest.json") -Raw | ConvertFrom-Json
if (
$projection.schema_version -ne "missioncore.e15-live-projection-pack/v1" -or
$projection.identity.source_id -ne "sensor.camera.right" -or
$projection.identity.calibration_slot -ne "camera_1" -or
$projection.identity.calibration_sha256 -ne $live.source.calibration_sha256
) { throw "LAB E15 projection pack binding changed" }
$mediaManifest = Get-Content -LiteralPath (Join-Path $mediaRuntime "manifest.json") -Raw | ConvertFrom-Json
if (
$mediaManifest.schema_version -ne "missioncore.e15-media-runtime/v1" -or
$mediaManifest.container_image -ne $ContainerImage -or
$mediaManifest.packages.av -ne "18.0.0" -or
$mediaManifest.packages.lz4 -ne "4.4.5"
) { throw "LAB E15 media runtime identity changed" }
$derivedRoot = Resolve-DDirectory (Join-Path $runtime "derived") "Runtime derived root"
$cacheRoot = Resolve-DDirectory (Join-Path $runtime "cache\perception-e3-models-v1") "EoMT cache"
$e3Environment = Resolve-DDirectory (Join-Path $derivedRoot "perception-e3-opencv413092-v1") "E3 environment"
$torchEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-transformers4576-v1") "Transformers environment"
$freeBytesBefore = Assert-FreeSpace "preflight" 512MB
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Pinned container image inspection"
$tritonState = docker inspect --format "{{.State.Running}}" $TritonContainer
Assert-LastExitCode "Triton container inspection"
if ($tritonState.Trim().ToLowerInvariant() -ne "true") {
throw "LAB E15 requires the existing Triton container"
}
$runnerName = Split-Path $runner -Leaf
$liveProfileName = Split-Path $liveProfile -Leaf
$e14ProfileName = Split-Path $e14Profile -Leaf
$detectorProfileName = Split-Path $detectorProfile -Leaf
$semanticProfileName = Split-Path $semanticProfile -Leaf
$projectionMount = "/" + (Split-Path $projectionPack -Leaf)
$packageMount = "/" + (Split-Path $package -Leaf)
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$commonMounts = @(
"-e", ("PYTHONPATH=/runner:{0}:/opt/media:/opt/transformers:/opt/env" -f $packageMount),
"-e", "PYTHONDONTWRITEBYTECODE=1",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $projectionPack) + (":{0}:ro" -f $projectionMount)),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $e3Environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $mediaRuntime) + ":/opt/media:ro"),
"-v", ((Convert-ToDockerPath $package) + (":{0}:ro" -f $packageMount)),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro")
)
$commonRunnerArgs = @(
"--job", "/job",
"--live-profile", ("/runner/{0}" -f $liveProfileName),
"--e14-profile", ("/runner/{0}" -f $e14ProfileName),
"--detector-profile", ("/runner/{0}" -f $detectorProfileName),
"--semantic-profile", ("/runner/{0}" -f $semanticProfileName),
"--valid-fov-root", "/valid-fov",
"--projection-pack", $projectionMount,
"--model-root", "/model",
"--cache", "/cache",
"--environment", "/environment",
"--worker-package", $packageMount
)
if ($PersistentService) {
if ($PreflightOnly -or $TokenStdin) {
throw "Persistent service cannot be combined with one-shot switches"
}
$persistentParent = Resolve-DDirectory (Split-Path $PersistentOutputRoot -Parent) "Persistent output parent"
if (-not (Test-Path -LiteralPath $PersistentOutputRoot)) {
$null = New-Item -ItemType Directory -Path $PersistentOutputRoot
}
$persistentOutput = Resolve-DDirectory $PersistentOutputRoot "Persistent output root"
if ((Split-Path $persistentOutput -Parent) -ne $persistentParent) {
throw "Persistent output root must be a direct child of its guarded D: parent"
}
$runnerSha256 = (Get-FileHash -LiteralPath $runner -Algorithm SHA256).Hash.ToLowerInvariant()
$existingContainerId = docker ps -a --filter ("name=^{0}$" -f $PersistentContainer) --format "{{.ID}}"
Assert-LastExitCode "Persistent worker container lookup"
if ($existingContainerId) {
$labels = (docker inspect --format "{{json .Config.Labels}}" $PersistentContainer) | ConvertFrom-Json
Assert-LastExitCode "Persistent worker label inspection"
if (
$labels.'missioncore.role' -ne "perception-persistent-worker" -or
$labels.'missioncore.runner.sha256' -ne $runnerSha256
) { throw "Existing persistent worker has a different immutable identity" }
$running = docker inspect --format "{{.State.Running}}" $PersistentContainer
Assert-LastExitCode "Persistent worker state inspection"
if ($running.Trim().ToLowerInvariant() -ne "true") {
throw "Matching persistent worker exists but is not running"
}
$healthProbe = (
"import json,urllib.request;d=json.load(urllib.request.urlopen(" +
"'http://127.0.0.1:{0}/health',timeout=5));" -f $PersistentPort
) + "raise SystemExit(0 if d.get('ok') and d.get('models_loaded') else 2)"
$previousErrorPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
& docker exec $PersistentContainer python3 -c $healthProbe 2>$null
$healthExitCode = $LASTEXITCODE
$ErrorActionPreference = $previousErrorPreference
if ($healthExitCode -ne 0) { throw "Existing persistent worker health probe failed" }
Write-Output "STATE=persistent-worker-reused"
Write-Output ("CONTAINER={0}" -f $PersistentContainer)
return
}
if (-not (Test-TritonModelReady)) {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" `
-Method Post -ContentType "application/json" -Body "{}" *> $null
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady)) {
if ([DateTime]::UtcNow -ge $deadline) { throw "YOLOX-S did not become ready" }
Start-Sleep -Milliseconds 500
}
}
$serveArgs = @(
"run", "--detach", "--name", $PersistentContainer,
"--label", "missioncore.role=perception-persistent-worker",
"--label", ("missioncore.runner.sha256={0}" -f $runnerSha256),
"--gpus", "all", "--network", ("container:{0}" -f $TritonContainer),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"-v", ((Convert-ToDockerPath $persistentOutput) + ":/publish:rw"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "serve"
) + $commonRunnerArgs + @(
"--host", $SourceHost,
"--port", [string]$SourcePort,
"--path", $SourcePath,
"--triton-url", "http://127.0.0.1:8000",
"--output-root", "/publish",
"--listen-host", "127.0.0.1",
"--listen-port", [string]$PersistentPort,
"--max-duration-seconds", [string]$MaximumDurationSeconds,
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage
)
Write-Output "PHASE=persistent-worker-model-load-start"
& docker @serveArgs
Assert-LastExitCode "Persistent worker container start"
$healthProbe = (
"import json,urllib.request;d=json.load(urllib.request.urlopen(" +
"'http://127.0.0.1:{0}/health',timeout=5));" -f $PersistentPort
) + "print(json.dumps(d,sort_keys=True));raise SystemExit(0 if d.get('ok') and d.get('models_loaded') else 2)"
$deadline = [DateTime]::UtcNow.AddSeconds(240)
do {
Start-Sleep -Seconds 2
$running = docker inspect --format "{{.State.Running}}" $PersistentContainer
if ($LASTEXITCODE -ne 0 -or $running.Trim().ToLowerInvariant() -ne "true") {
& docker logs --tail 80 $PersistentContainer
throw "Persistent worker exited during model load"
}
$previousErrorPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$health = & docker exec $PersistentContainer python3 -c $healthProbe 2>$null
$healthExitCode = $LASTEXITCODE
$ErrorActionPreference = $previousErrorPreference
$healthy = $healthExitCode -eq 0
} while (-not $healthy -and [DateTime]::UtcNow -lt $deadline)
if (-not $healthy) {
& docker logs --tail 80 $PersistentContainer
throw "Persistent worker model load did not become ready"
}
Write-Output "STATE=persistent-worker-ready"
Write-Output ("CONTAINER={0}" -f $PersistentContainer)
Write-Output ("HEALTH={0}" -f $health)
Write-Output ("DISK_FREE_BYTES={0}" -f (Get-DFreeBytes))
return
}
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "preflight"
) + $commonRunnerArgs
Write-Output "PHASE=e15-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E15 preflight"
Write-Output "PHASE=e15-preflight-complete"
if ($PreflightOnly) {
Write-Output "STATE=preflight-ready"
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_AFTER={0}" -f (Get-DFreeBytes))
return
}
if (-not $TokenStdin) { throw "LAB E15 requires the shadow token through stdin" }
$shadowToken = [Console]::In.ReadLine()
if (-not $shadowToken -or $shadowToken.Length -lt 40 -or $shadowToken.Length -gt 512) {
throw "LAB E15 shadow token is missing or malformed"
}
$modelWasReady = Test-TritonModelReady
$loadedByRun = $false
if (-not $modelWasReady) {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" `
-Method Post -ContentType "application/json" -Body "{}" *> $null
$loadedByRun = $true
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady)) {
if ([DateTime]::UtcNow -ge $deadline) { throw "YOLOX-S did not become ready" }
Start-Sleep -Milliseconds 500
}
}
Write-Output ("PHASE=e15-model-ready LOADED_BY_RUN={0}" -f $loadedByRun)
$token = [Guid]::NewGuid().ToString("N")
$publishRoot = Join-Path $derivedRoot (".{0}-e15-{1}.publish" -f $job.job_id, $token)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $publishRoot
$completed = $false
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
try {
$runArgs = @(
"run", "--rm", "--interactive", "--gpus", "all",
"--network", ("container:{0}" -f $TritonContainer),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "run"
) + $commonRunnerArgs + @(
"--host", $SourceHost,
"--port", [string]$SourcePort,
"--path", $SourcePath,
"--token-stdin",
"--triton-url", "http://127.0.0.1:8000",
"--output", "/publish/output",
"--max-duration-seconds", [string]$MaximumDurationSeconds,
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage
)
Write-Output ("PHASE=e15-shadow-inference-start MAX_DURATION_SECONDS={0}" -f $MaximumDurationSeconds)
$shadowToken | & docker @runArgs
$runnerExitCode = $LASTEXITCODE
$shadowToken = $null
if ($runnerExitCode -ne 0 -and $runnerExitCode -ne 2) {
throw "LAB E15 shadow inference failed with exit code $runnerExitCode"
}
$null = Assert-FreeSpace "post-shadow-inference"
$resultPath = Join-Path $stagingRoot "result.json"
if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) {
throw "LAB E15 result manifest is missing"
}
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.e15-shadow-inference-result/v1" -or
$result.result_id -notmatch "^e15-shadow-inference-[a-f0-9]{64}$" -or
$result.publication_scope -ne "live-shadow-diagnostic-only"
) { throw "LAB E15 result manifest is incompatible" }
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) { throw "Immutable LAB E15 result already exists" }
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
$totalWatch.Stop()
$freeFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("ACCEPTANCE_STATE={0}" -f $result.acceptance_state)
Write-Output ("RUNNER_EXIT_CODE={0}" -f $runnerExitCode)
Write-Output ("ORCHESTRATOR_WALL_SECONDS={0}" -f $totalWatch.Elapsed.TotalSeconds)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeFinal)
}
finally {
$shadowToken = $null
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
if ($loadedByRun) {
try {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/unload" `
-Method Post -ContentType "application/json" -Body "{}" *> $null
Write-Output "PHASE=e15-model-state-restored"
}
catch { Write-Warning "LAB E15 could not restore the prior YOLOX-S state" }
}
}
@@ -0,0 +1,251 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$EvaluationPack,
[Parameter(Mandatory = $true)]
[string]$E2Prelabels,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$BaselineRunnerPath,
[Parameter(Mandatory = $true)]
[string]$ProfilePath,
[ValidateRange(0, 64)]
[int]$MaxFrames = 0,
[ValidateRange(1, 1000)]
[int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Assert-DDrivePath {
param([string]$Path, [string]$Label)
$fullPath = [IO.Path]::GetFullPath($Path)
if ([IO.Path]::GetPathRoot($fullPath).TrimEnd("\") -ine "D:") {
throw "$Label must be stored on D:"
}
return $fullPath
}
function Assert-FreeSpace {
param([string]$Phase)
$freeBytes = [int64](Get-PSDrive -Name D).Free
$floorBytes = [int64]$FreeGiBFloor * 1GB
$freeGiB = [math]::Round($freeBytes / 1GB, 3)
Write-Output ("DISK_GUARD PHASE={0} DRIVE=D FREE_GIB={1} FLOOR_GIB={2}" -f $Phase, $freeGiB, $FreeGiBFloor)
if ($freeBytes -lt $floorBytes) {
throw "D: free-space floor was crossed during $Phase"
}
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
$packRoot = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $EvaluationPack).Path "Evaluation pack") "Evaluation pack"
$prelabelsRoot = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $E2Prelabels).Path "E2 prelabels") "E2 prelabels"
$validFov = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root") "Valid-FOV root"
$runner = Assert-DDrivePath (Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "LAB E3 runner") "LAB E3 runner"
$baselineRunner = Assert-DDrivePath (Assert-RegularFile (Resolve-Path -LiteralPath $BaselineRunnerPath).Path "Baseline runner") "Baseline runner"
$profile = Assert-DDrivePath (Assert-RegularFile (Resolve-Path -LiteralPath $ProfilePath).Path "LAB E3 profile") "LAB E3 profile"
$runtime = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root") "Runtime root"
if (
(Split-Path $runner -Parent) -ne (Split-Path $baselineRunner -Parent) -or
(Split-Path $runner -Parent) -ne (Split-Path $profile -Parent)
) {
throw "LAB E3 runner, baseline runner, and profile must share one read-only mount"
}
$pack = Get-Content -LiteralPath (Join-Path $packRoot "manifest.json") -Raw | ConvertFrom-Json
$prelabels = Get-Content -LiteralPath (Join-Path $prelabelsRoot "result.json") -Raw | ConvertFrom-Json
$profileDocument = Get-Content -LiteralPath $profile -Raw | ConvertFrom-Json
$packFrameCount = @($pack.identity.frames).Count
if (
$pack.schema_version -ne "missioncore.perception-evaluation-pack/v1" -or
$pack.generation_id -ne (Split-Path $packRoot -Leaf) -or
$pack.identity.source_id -ne "sensor.camera.right" -or
$pack.identity.calibration_slot -ne "camera_1" -or
$packFrameCount -ne 64 -or
$prelabels.schema_version -ne "missioncore.perception-evaluation-prelabels/v1" -or
$prelabels.identity.evaluation_pack_id -ne $pack.generation_id -or
$profileDocument.schema_version -ne "missioncore.k1-e3-rectified-segmentation-profile/v1" -or
$profileDocument.source.source_id -ne $pack.identity.source_id -or
$profileDocument.source.calibration_slot -ne $pack.identity.calibration_slot -or
$profileDocument.source.calibration_sha256 -ne $pack.identity.calibration_sha256
) {
throw "LAB E3 inputs are incompatible"
}
$null = Assert-RegularFile (Join-Path $validFov "manifest.json") "Valid-FOV manifest"
$null = Assert-RegularFile (Join-Path $validFov "mask.png") "Valid-FOV mask"
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
Assert-FreeSpace "preflight"
$cacheRoot = Join-Path $runtime "cache\perception-e3-models-v1"
$derivedRoot = Join-Path $runtime "derived\e3-segmentation"
$environmentRoot = Join-Path $runtime "derived\perception-e3-opencv413092-v1"
$torchEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-transformers4576-v1") "Transformers environment"
$runnerRoot = Split-Path $runner -Parent
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$null = New-Item -ItemType Directory -Path $cacheRoot -Force
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
if (-not (Test-Path -LiteralPath $environmentRoot)) {
$environmentToken = [Guid]::NewGuid().ToString("N")
$environmentStaging = Join-Path (Split-Path $environmentRoot -Parent) (".e3-environment-{0}.publish" -f $environmentToken)
$null = New-Item -ItemType Directory -Path $environmentStaging
$null = New-Item -ItemType Directory -Path (Join-Path $environmentStaging "tmp")
try {
$prepareArgs = @(
"run", "--rm", "--network", "bridge", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--tmpfs", "/tmp:rw,noexec,nosuid,size=1g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "HF_HOME=/cache/huggingface",
"-e", "PIP_NO_CACHE_DIR=1",
"-e", "HOME=/environment/tmp",
"-e", "TMPDIR=/environment/tmp",
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $environmentStaging) + ":/environment:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "prepare",
"--profile", ("/runner/{0}" -f $profileName),
"--cache", "/cache",
"--environment", "/environment"
)
Write-Output "PHASE=e3-dependency-prepare-start"
& docker @prepareArgs
Assert-LastExitCode "LAB E3 dependency preparation"
$null = Assert-RegularFile (Join-Path $environmentStaging "manifest.json") "LAB E3 dependency manifest"
Remove-Item -LiteralPath (Join-Path $environmentStaging "tmp") -Recurse -Force
Move-Item -LiteralPath $environmentStaging -Destination $environmentRoot
Write-Output "PHASE=e3-dependency-prepare-complete"
}
finally {
if (Test-Path -LiteralPath $environmentStaging) {
Remove-Item -LiteralPath $environmentStaging -Recurse -Force
}
}
}
$environment = Assert-Directory $environmentRoot "LAB E3 dependency environment"
$null = Assert-RegularFile (Join-Path $environment "manifest.json") "LAB E3 dependency manifest"
Assert-FreeSpace "post-dependency-prepare"
$runToken = [Guid]::NewGuid().ToString("N")
$publishRoot = Join-Path $derivedRoot (".{0}-{1}.publish" -f $pack.generation_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $publishRoot
try {
$runArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-e", "HOME=/tmp",
"-v", ((Convert-ToDockerPath $packRoot) + ":/evaluation-pack:ro"),
"-v", ((Convert-ToDockerPath $prelabelsRoot) + ":/e2-prelabels:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "run",
"--profile", ("/runner/{0}" -f $profileName),
"--evaluation-pack", "/evaluation-pack",
"--e2-prelabels", "/e2-prelabels",
"--valid-fov-root", "/valid-fov",
"--cache", "/cache",
"--environment", "/environment",
"--output", "/publish/output",
"--telemetry-interval-seconds", "1"
)
if ($MaxFrames -gt 0) {
$runArgs += @("--max-frames", [string]$MaxFrames)
}
$expectedFrames = if ($MaxFrames -gt 0) { $MaxFrames } else { $packFrameCount }
Write-Output ("PHASE=e3-run-start PACK={0} FRAMES={1}" -f $pack.generation_id, $expectedFrames)
& docker @runArgs
Assert-LastExitCode "LAB E3 segmentation"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.k1-e3-rectified-segmentation-result/v1" -or
$result.result_id -notmatch "^e3-segmentation-[a-f0-9]{64}$" -or
$result.identity.evaluation_pack_id -ne $pack.generation_id -or
[int]$result.identity.frame_count -ne $expectedFrames -or
$result.ground_truth -ne $false
) {
throw "LAB E3 result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable LAB E3 result with the same identity already exists: $finalRoot"
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
Assert-FreeSpace "post-run"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
}
finally {
if (Test-Path -LiteralPath $publishRoot) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
}
@@ -0,0 +1,421 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$JobRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$ProfilePath,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[ValidateRange(0, 256)]
[int]$PilotFrames = 0,
[ValidateRange(1, 1000)]
[int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Assert-DDrivePath {
param([string]$Path, [string]$Label)
$fullPath = [IO.Path]::GetFullPath($Path)
if ([IO.Path]::GetPathRoot($fullPath).TrimEnd("\") -ine "D:") {
throw "$Label must be stored on D:"
}
return $fullPath
}
function Get-DFreeBytes {
return [int64](Get-PSDrive -Name D).Free
}
function Assert-FreeSpace {
param(
[string]$Phase,
[int64]$RequiredAdditionalBytes = 0
)
$freeBytes = Get-DFreeBytes
$floorBytes = [int64]$FreeGiBFloor * 1GB
$requiredBytes = $floorBytes + $RequiredAdditionalBytes
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase,
$freeBytes,
[math]::Round($freeBytes / 1GB, 3),
$FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($freeBytes -lt $requiredBytes) {
throw "D: does not have the guarded LAB E4 working-set reserve during $Phase"
}
return $freeBytes
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
$jobDirectory = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $JobRoot).Path "Job root") "Job root"
$runner = Assert-DDrivePath (Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "LAB E4 runner") "LAB E4 runner"
$profile = Assert-DDrivePath (Assert-RegularFile (Resolve-Path -LiteralPath $ProfilePath).Path "LAB E4 profile") "LAB E4 profile"
$validFov = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root") "Valid-FOV root"
$runtime = Assert-DDrivePath (Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root") "Runtime root"
$runnerRoot = Split-Path $runner -Parent
if ((Split-Path $profile -Parent) -ne $runnerRoot) {
throw "LAB E4 runner and profile must share one read-only mount"
}
foreach ($dependency in @(
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Assert-RegularFile (Join-Path $runnerRoot $dependency) "LAB E4 runner dependency"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$fullFrameCount = [int]$job.input.segment_count
$activeFrameCount = if ($PilotFrames -gt 0) { $PilotFrames } else { $fullFrameCount }
$timelineStart = [double]$job.input.timeline.start_seconds
$timelineEnd = [double]$job.input.timeline.end_seconds
$timelineDuration = $timelineEnd - $timelineStart
if (
$sourceId -ne "sensor.camera.right" -or
$fullFrameCount -lt 1 -or
$activeFrameCount -gt $fullFrameCount -or
$timelineDuration -le 0
) {
throw "LAB E4 camera job contract is invalid"
}
Write-Output ("PHASE=job-manifest-validated JOB={0} FRAMES={1}/{2}" -f $job.job_id, $activeFrameCount, $fullFrameCount)
$epochRoot = Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
$epochRoot = Assert-DDrivePath (Assert-Directory $epochRoot "Camera epoch") "Camera epoch"
$initPath = Assert-RegularFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Assert-Directory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$cacheRoot = Assert-DDrivePath (Assert-Directory (Join-Path $runtime "cache\perception-e3-models-v1") "EoMT cache") "EoMT cache"
$e3Environment = Assert-DDrivePath (Assert-Directory (Join-Path $derivedRoot "perception-e3-opencv413092-v1") "E3 dependency environment") "E3 dependency environment"
$torchEnvironment = Assert-DDrivePath (Assert-Directory (Join-Path $derivedRoot "perception-p0-env-v1") "Torch environment") "Torch environment"
$transformersEnvironment = Assert-DDrivePath (Assert-Directory (Join-Path $derivedRoot "perception-p0-transformers4576-v1") "Transformers environment") "Transformers environment"
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
# Conservative upper bound: decoded RGB + RGB overlay + one-byte semantic mask,
# plus 20% filesystem/encoding overhead and the reconstructed source stream.
$pixelWorkingSet = [int64]$activeFrameCount * 800 * 600 * 7
$workingSetReserve = [int64][math]::Ceiling(($pixelWorkingSet * 1.2) + [int64]$job.input.byte_length)
$freeBytesBefore = Assert-FreeSpace "preflight" $workingSetReserve
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $e3Environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "preflight",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--cache", "/cache",
"--environment", "/environment"
)
Write-Output "PHASE=e4-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E4 preflight"
Write-Output "PHASE=e4-preflight-complete"
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e4-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e4-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
$completed = $false
Write-Output "PHASE=e4-private-staging-created"
try {
$stream = [IO.File]::Open($streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
$input = [IO.File]::OpenRead($initPath)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
for ($sequence = 1; $sequence -le $activeFrameCount; $sequence++) {
$path = Assert-RegularFile (Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)) "Camera segment"
$input = [IO.File]::OpenRead($path)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
if ($sequence % 500 -eq 0 -or $sequence -eq $activeFrameCount) {
Write-Output ("PHASE=e4-stream-reconstruction SEGMENTS={0}/{1}" -f $sequence, $activeFrameCount)
}
}
$stream.Flush($true)
}
finally {
$stream.Dispose()
}
$null = Assert-FreeSpace "post-stream-reconstruction"
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=e4-frame-extraction-start"
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 -fps_mode passthrough -frames:v $activeFrameCount (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "LAB E4 camera extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time -of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E4 camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $activeFrameCount -or $pts.Count -lt $activeFrameCount) {
throw "Decoded LAB E4 frame count differs from the requested camera epoch"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousEpochSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new($timelinePath, $false, [Text.UTF8Encoding]::new($false))
try {
for ($index = 0; $index -lt $activeFrameCount; $index++) {
$epochSeconds = [double]::Parse(
([string]$pts[$index].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
if ($epochSeconds -le $previousEpochSeconds -or $epochSeconds -gt ($timelineDuration + 0.001)) {
throw "Decoded LAB E4 timestamps are not strictly monotonic inside the camera timeline"
}
$row = [ordered]@{
frame_index = $index
epoch_seconds = $epochSeconds
session_seconds = $timelineStart + $epochSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousEpochSeconds = $epochSeconds
}
$timelineWriter.Flush()
}
finally {
$timelineWriter.Dispose()
}
$extractWatch.Stop()
$freeBytesPostExtract = Assert-FreeSpace "post-frame-extraction"
Write-Output ("PHASE=e4-frame-extraction-complete FRAMES={0}" -f $activeFrameCount)
$runArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $e3Environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "run",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--cache", "/cache",
"--environment", "/environment",
"--output", "/publish/output",
"--frame-limit", [string]$activeFrameCount,
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage,
"--telemetry-interval-seconds", "1"
)
Write-Output ("PHASE=e4-inference-start FRAMES={0}" -f $activeFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E4 semantic inference"
$freeBytesPostInference = Assert-FreeSpace "post-inference"
Write-Output "PHASE=e4-inference-complete"
if ($PilotFrames -gt 0) {
$pilotParent = Join-Path $derivedRoot "e4-pilots"
$null = New-Item -ItemType Directory -Path $pilotParent -Force
$pilotRoot = Join-Path $pilotParent ("pilot-{0}-{1}" -f $activeFrameCount, $runToken)
Move-Item -LiteralPath $stagingRoot -Destination $pilotRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
Write-Output ("PILOT_ROOT={0}" -f $pilotRoot)
Write-Output ("PILOT_FRAMES={0}" -f $activeFrameCount)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freeBytesPostExtract)
Write-Output ("DISK_FREE_BYTES_POST_INFERENCE={0}" -f $freeBytesPostInference)
return
}
$videoPath = Join-Path $stagingRoot "perception.mp4"
$encodeWatch = [Diagnostics.Stopwatch]::StartNew()
$averageFps = $fullFrameCount / $timelineDuration
$fpsText = $averageFps.ToString("0.#########", [Globalization.CultureInfo]::InvariantCulture)
$durationText = $timelineDuration.ToString("0.#########", [Globalization.CultureInfo]::InvariantCulture)
& ffmpeg -hide_banner -loglevel error -framerate $fpsText -i (Join-Path $stagingRoot "overlay-frames\frame-%06d.png") -t $durationText -c:v h264_nvenc -preset p4 -tune hq -rc vbr -cq 21 -b:v 0 -pix_fmt yuv420p -movflags +faststart $videoPath
Assert-LastExitCode "LAB E4 video encoding"
$encodeWatch.Stop()
$null = Assert-FreeSpace "post-video-encoding"
Write-Output "PHASE=e4-video-encoding-complete"
$masksPath = Join-Path $stagingRoot "masks.tar.gz"
$archiveWatch = [Diagnostics.Stopwatch]::StartNew()
& tar.exe -czf $masksPath -C $stagingRoot semantic-masks
Assert-LastExitCode "LAB E4 mask archive publication"
$archiveWatch.Stop()
$freeBytesPostArtifacts = Assert-FreeSpace "post-mask-archive"
Write-Output "PHASE=e4-mask-archive-complete"
$totalWatch.Stop()
$finalizeArgs = @(
"run", "--rm", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "128", "--tmpfs", "/tmp:rw,noexec,nosuid,size=256m",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-v", ((Convert-ToDockerPath $stagingRoot) + ":/output:rw"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "finalize",
"--output", "/output",
"--video", "/output/perception.mp4",
"--masks", "/output/masks.tar.gz",
"--extract-seconds", $extractWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encode-seconds", $encodeWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--archive-seconds", $archiveWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--wall-seconds", $totalWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encoder", "ffmpeg-h264_nvenc-p4-cq21-yuv420p-faststart",
"--disk-free-before-bytes", [string]$freeBytesBefore,
"--disk-free-post-extract-bytes", [string]$freeBytesPostExtract,
"--disk-free-post-inference-bytes", [string]$freeBytesPostInference,
"--disk-free-post-artifacts-bytes", [string]$freeBytesPostArtifacts,
"--disk-floor-bytes", [string]([int64]$FreeGiBFloor * 1GB),
"--working-set-reserve-bytes", [string]$workingSetReserve
)
& docker @finalizeArgs
Assert-LastExitCode "LAB E4 result finalization"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.recorded-perception-result/v2" -or
$result.result_id -notmatch "^result-[a-f0-9]{64}$" -or
[int]$result.frames_processed -ne $fullFrameCount -or
$result.ground_truth -ne $false
) {
throw "Final LAB E4 result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable result with the same LAB E4 identity already exists: $finalRoot"
}
foreach ($temporaryChild in @("overlay-frames", "semantic-masks")) {
$temporaryPath = Join-Path $stagingRoot $temporaryChild
if (Test-Path -LiteralPath $temporaryPath) {
Remove-Item -LiteralPath $temporaryPath -Recurse -Force
}
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
$freeBytesFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freeBytesPostExtract)
Write-Output ("DISK_FREE_BYTES_POST_INFERENCE={0}" -f $freeBytesPostInference)
Write-Output ("DISK_FREE_BYTES_POST_ARTIFACTS={0}" -f $freeBytesPostArtifacts)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeBytesFinal)
}
finally {
if (Test-Path -LiteralPath $workRoot) {
Remove-Item -LiteralPath $workRoot -Recurse -Force
}
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
}
@@ -0,0 +1,544 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$JobRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$ProfilePath,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[ValidateRange(0, 1000000)]
[int]$StartFrame = 1000,
[ValidateRange(0, 1000000)]
[int]$EndFrame = 1600,
[ValidateRange(1, 1000)]
[int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ModelRoot = "D:\NDC_MISSIONCORE\runtime\models\yolox_s",
[string]$TritonContainer = "mission-core-triton",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Assert-DDrivePath {
param([string]$Path, [string]$Label)
$fullPath = [IO.Path]::GetFullPath($Path)
if ([IO.Path]::GetPathRoot($fullPath).TrimEnd("\") -ine "D:") {
throw "$Label must be stored on D:"
}
return $fullPath
}
function Get-DFreeBytes {
return [int64](Get-PSDrive -Name D).Free
}
function Assert-FreeSpace {
param(
[string]$Phase,
[int64]$RequiredAdditionalBytes = 0
)
$freeBytes = Get-DFreeBytes
$floorBytes = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase,
$freeBytes,
[math]::Round($freeBytes / 1GB, 3),
$FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($freeBytes -lt ($floorBytes + $RequiredAdditionalBytes)) {
throw "D: does not have the guarded LAB E5 working-set reserve during $Phase"
}
return $freeBytes
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
function Test-TritonModelReady {
param([string]$ModelName)
try {
$response = Invoke-WebRequest `
-Uri ("http://127.0.0.1:8000/v2/models/{0}/ready" -f $ModelName) `
-Method Get `
-UseBasicParsing `
-TimeoutSec 10
return $response.StatusCode -eq 200
}
catch {
return $false
}
}
$jobDirectory = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $JobRoot).Path "Job root"
) "Job root"
$runner = Assert-DDrivePath (
Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "LAB E5 runner"
) "LAB E5 runner"
$profile = Assert-DDrivePath (
Assert-RegularFile (Resolve-Path -LiteralPath $ProfilePath).Path "LAB E5 profile"
) "LAB E5 profile"
$validFov = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root"
) "Valid-FOV root"
$runtime = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root"
) "Runtime root"
$model = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $ModelRoot).Path "YOLOX model root"
) "YOLOX model root"
$runnerRoot = Split-Path $runner -Parent
if ((Split-Path $profile -Parent) -ne $runnerRoot) {
throw "LAB E5 runner and profile must share one read-only mount"
}
foreach ($dependency in @(
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Assert-RegularFile (Join-Path $runnerRoot $dependency) "LAB E5 runner dependency"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$fullFrameCount = [int]$job.input.segment_count
$clipFrameCount = $EndFrame - $StartFrame + 1
$timelineStart = [double]$job.input.timeline.start_seconds
if (
$sourceId -ne "sensor.camera.right" -or
$StartFrame -lt 0 -or
$EndFrame -lt $StartFrame -or
$EndFrame -ge $fullFrameCount -or
$clipFrameCount -lt 2
) {
throw "LAB E5 clip escapes the camera job"
}
Write-Output (
"PHASE=job-manifest-validated JOB={0} CLIP={1}-{2} FRAMES={3}" -f
$job.job_id, $StartFrame, $EndFrame, $clipFrameCount
)
$epochRoot = Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
$epochRoot = Assert-DDrivePath (Assert-Directory $epochRoot "Camera epoch") "Camera epoch"
$initPath = Assert-RegularFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Assert-Directory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$pythonEnvironment = Assert-DDrivePath (
Assert-Directory (Join-Path $derivedRoot "perception-p0-env-v1") "Python environment"
) "Python environment"
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
# Conservative bound: partial reconstructed stream, decoded RGB PNG payload,
# RGB overlays, encoded result and 2 GiB of filesystem/codec overhead.
$pixelWorkingSet = [int64]$clipFrameCount * 800 * 600 * 6
$partialStreamReserve = [int64][math]::Ceiling(
([int64]$job.input.byte_length * ($EndFrame + 1) / $fullFrameCount) * 1.2
)
$workingSetReserve = [int64][math]::Ceiling($pixelWorkingSet * 1.4) + $partialStreamReserve + 2GB
$freeBytesBefore = Assert-FreeSpace "preflight" $workingSetReserve
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
$tritonState = docker inspect --format "{{.State.Running}}" $TritonContainer
Assert-LastExitCode "Triton container inspection"
if ($tritonState.Trim().ToLowerInvariant() -ne "true") {
throw "LAB E5 requires the existing Triton container"
}
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$preflightArgs = @(
"run", "--rm", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "128", "--tmpfs", "/tmp:rw,noexec,nosuid,size=256m",
"-e", "PYTHONPATH=/runner:/opt/env",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $pythonEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "preflight",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model"
)
Write-Output "PHASE=e5-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E5 preflight"
Write-Output "PHASE=e5-preflight-complete"
$modelWasReady = Test-TritonModelReady "yolox_s"
$loadedByRun = $false
if (-not $modelWasReady) {
Invoke-WebRequest `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" `
-Method Post `
-ContentType "application/json" `
-Body "{}" `
-UseBasicParsing `
-TimeoutSec 60 *> $null
$loadedByRun = $true
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady "yolox_s")) {
if ([DateTime]::UtcNow -ge $deadline) {
throw "YOLOX-S did not become ready in Triton"
}
Start-Sleep -Milliseconds 500
}
}
Write-Output ("PHASE=e5-model-ready LOADED_BY_RUN={0}" -f $loadedByRun)
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e5-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e5-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
$completed = $false
Write-Output "PHASE=e5-private-staging-created"
try {
$stream = [IO.File]::Open(
$streamPath,
[IO.FileMode]::CreateNew,
[IO.FileAccess]::Write,
[IO.FileShare]::None
)
try {
$input = [IO.File]::OpenRead($initPath)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
for ($sequence = 1; $sequence -le ($EndFrame + 1); $sequence++) {
$path = Assert-RegularFile (
Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)
) "Camera segment"
$input = [IO.File]::OpenRead($path)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
if ($sequence % 500 -eq 0 -or $sequence -eq ($EndFrame + 1)) {
Write-Output (
"PHASE=e5-stream-reconstruction SEGMENTS={0}/{1}" -f
$sequence, ($EndFrame + 1)
)
}
}
$stream.Flush($true)
}
finally {
$stream.Dispose()
}
$null = Assert-FreeSpace "post-stream-reconstruction"
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=e5-frame-extraction-start"
$selectFilter = "select='between(n,{0},{1})'" -f $StartFrame, $EndFrame
& ffmpeg `
-hide_banner -loglevel fatal `
-i $streamPath `
-map 0:v:0 `
-vf $selectFilter `
-fps_mode passthrough `
(Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "LAB E5 camera clip extraction"
& ffprobe `
-v error `
-select_streams v:0 `
-show_entries frame=best_effort_timestamp_time `
-of json `
$streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E5 camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $clipFrameCount -or $pts.Count -ne ($EndFrame + 1)) {
throw "Decoded LAB E5 frame/timestamp count differs from the selected clip"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousSessionSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new(
$timelinePath,
$false,
[Text.UTF8Encoding]::new($false)
)
try {
for ($localIndex = 0; $localIndex -lt $clipFrameCount; $localIndex++) {
$sourceIndex = $StartFrame + $localIndex
$epochSeconds = [double]::Parse(
([string]$pts[$sourceIndex].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
$sessionSeconds = $timelineStart + $epochSeconds
if ($sessionSeconds -le $previousSessionSeconds) {
throw "Decoded LAB E5 clip timestamps are not strictly monotonic"
}
$row = [ordered]@{
frame_index = $localIndex
sequence = $localIndex + 1
source_frame_index = $sourceIndex
source_sequence = $sourceIndex + 1
epoch_seconds = $epochSeconds
session_seconds = $sessionSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousSessionSeconds = $sessionSeconds
}
$timelineWriter.Flush()
}
finally {
$timelineWriter.Dispose()
}
$extractWatch.Stop()
$freeBytesPostExtract = Assert-FreeSpace "post-frame-extraction"
Write-Output ("PHASE=e5-frame-extraction-complete FRAMES={0}" -f $clipFrameCount)
$runArgs = @(
"run", "--rm", "--gpus", "all",
"--network", ("container:{0}" -f $TritonContainer),
"--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "256", "--shm-size", "1g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=512m",
"-e", "PYTHONPATH=/runner:/opt/env",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $pythonEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "run",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model",
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--output", "/publish/output",
"--triton-url", "http://127.0.0.1:8000",
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage,
"--telemetry-interval-seconds", "1"
)
Write-Output ("PHASE=e5-inference-start FRAMES={0}" -f $clipFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E5 detector/tracker inference"
$freeBytesPostInference = Assert-FreeSpace "post-inference"
Write-Output "PHASE=e5-inference-complete"
$timelineRows = @(Get-Content -LiteralPath $timelinePath | ForEach-Object {
$_ | ConvertFrom-Json
})
$clipSpanSeconds = [double]$timelineRows[-1].session_seconds - [double]$timelineRows[0].session_seconds
if ($clipSpanSeconds -le 0) {
throw "LAB E5 clip duration is invalid"
}
$averageFps = ($clipFrameCount - 1) / $clipSpanSeconds
$fpsText = $averageFps.ToString("0.#########", [Globalization.CultureInfo]::InvariantCulture)
$videoPath = Join-Path $stagingRoot "tracking.mp4"
$encodeWatch = [Diagnostics.Stopwatch]::StartNew()
& ffmpeg `
-hide_banner -loglevel error `
-framerate $fpsText `
-i (Join-Path $stagingRoot "overlay-frames\frame-%06d.png") `
-c:v h264_nvenc `
-preset p4 `
-tune hq `
-rc vbr `
-cq 21 `
-b:v 0 `
-pix_fmt yuv420p `
-movflags +faststart `
$videoPath
Assert-LastExitCode "LAB E5 video encoding"
$encodeWatch.Stop()
Write-Output "PHASE=e5-video-encoding-complete"
$contactSheetPath = Join-Path $stagingRoot "contact-sheet.png"
$tileColumns = if ($clipFrameCount -le 60) { 4 } else { 3 }
$tileRows = 2
$sampleInterval = [math]::Max(0.25, $clipSpanSeconds / ($tileColumns * $tileRows))
$sampleText = $sampleInterval.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture)
$contactFilter = "fps=1/{0},scale=400:-1,tile={1}x{2}:padding=8:margin=8:color=black" -f `
$sampleText, $tileColumns, $tileRows
& ffmpeg `
-hide_banner -loglevel error `
-i $videoPath `
-vf $contactFilter `
-frames:v 1 `
$contactSheetPath
Assert-LastExitCode "LAB E5 contact-sheet generation"
$probe = & ffprobe `
-v error `
-select_streams v:0 `
-count_frames `
-show_entries stream=codec_name,width,height,nb_read_frames `
-of json `
$videoPath | ConvertFrom-Json
Assert-LastExitCode "LAB E5 result video probe"
$videoStream = @($probe.streams)[0]
if (
$videoStream.codec_name -ne "h264" -or
[int]$videoStream.width -ne 800 -or
[int]$videoStream.height -ne 600 -or
[int]$videoStream.nb_read_frames -ne $clipFrameCount
) {
throw "LAB E5 result video contract changed"
}
$freeBytesPostArtifacts = Assert-FreeSpace "post-artifacts"
$totalWatch.Stop()
$finalizeArgs = @(
"run", "--rm", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "128", "--tmpfs", "/tmp:rw,noexec,nosuid,size=256m",
"-e", "PYTHONPATH=/runner",
"-v", ((Convert-ToDockerPath $stagingRoot) + ":/output:rw"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
$ContainerImage,
("/runner/{0}" -f $runnerName), "finalize",
"--output", "/output",
"--video", "/output/tracking.mp4",
"--contact-sheet", "/output/contact-sheet.png",
"--extract-seconds", $extractWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encode-seconds", $encodeWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--wall-seconds", $totalWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encoder", "ffmpeg-h264_nvenc-p4-cq21-yuv420p-faststart",
"--disk-free-before-bytes", [string]$freeBytesBefore,
"--disk-free-post-extract-bytes", [string]$freeBytesPostExtract,
"--disk-free-post-inference-bytes", [string]$freeBytesPostInference,
"--disk-free-post-artifacts-bytes", [string]$freeBytesPostArtifacts,
"--disk-floor-bytes", [string]([int64]$FreeGiBFloor * 1GB),
"--working-set-reserve-bytes", [string]$workingSetReserve
)
& docker @finalizeArgs
Assert-LastExitCode "LAB E5 result finalization"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.e5-tracking-result/v1" -or
$result.result_id -notmatch "^e5-tracking-[a-f0-9]{64}$" -or
[int]$result.frames_processed -ne $clipFrameCount -or
$result.ground_truth -ne $false -or
$result.publication_scope -ne "qualification-clip-only"
) {
throw "Final LAB E5 result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable result with the same LAB E5 identity already exists: $finalRoot"
}
$overlayFrames = Join-Path $stagingRoot "overlay-frames"
if (Test-Path -LiteralPath $overlayFrames) {
Remove-Item -LiteralPath $overlayFrames -Recurse -Force
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
$freeBytesFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freeBytesPostExtract)
Write-Output ("DISK_FREE_BYTES_POST_INFERENCE={0}" -f $freeBytesPostInference)
Write-Output ("DISK_FREE_BYTES_POST_ARTIFACTS={0}" -f $freeBytesPostArtifacts)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeBytesFinal)
}
finally {
if (Test-Path -LiteralPath $workRoot) {
Remove-Item -LiteralPath $workRoot -Recurse -Force
}
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
if ($loadedByRun) {
try {
Invoke-WebRequest `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/unload" `
-Method Post `
-ContentType "application/json" `
-Body "{}" `
-UseBasicParsing `
-TimeoutSec 60 *> $null
Write-Output "PHASE=e5-model-state-restored"
}
catch {
Write-Warning "LAB E5 could not restore the prior unloaded YOLOX-S state"
}
}
}
@@ -0,0 +1,411 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$JobRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$ProfilePath,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[ValidateRange(0, 1000000)]
[int]$StartFrame = 1000,
[ValidateRange(0, 1000000)]
[int]$EndFrame = 1600,
[ValidateRange(1, 1000)]
[int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ModelRoot = "D:\NDC_MISSIONCORE\runtime\models\yolox_s",
[string]$TritonContainer = "mission-core-triton",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Assert-DDrivePath {
param([string]$Path, [string]$Label)
$fullPath = [IO.Path]::GetFullPath($Path)
if ([IO.Path]::GetPathRoot($fullPath).TrimEnd("\") -ine "D:") {
throw "$Label must be stored on D:"
}
return $fullPath
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
function Get-DFreeBytes {
return [int64](Get-PSDrive -Name D).Free
}
function Assert-FreeSpace {
param([string]$Phase, [int64]$RequiredAdditionalBytes = 0)
$freeBytes = Get-DFreeBytes
$floorBytes = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase,
$freeBytes,
[math]::Round($freeBytes / 1GB, 3),
$FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($freeBytes -lt ($floorBytes + $RequiredAdditionalBytes)) {
throw "D: does not have the guarded LAB E8 working-set reserve during $Phase"
}
return $freeBytes
}
function Test-TritonModelReady {
try {
$response = Invoke-WebRequest `
-Uri "http://127.0.0.1:8000/v2/models/yolox_s/ready" `
-Method Get -UseBasicParsing -TimeoutSec 10
return $response.StatusCode -eq 200
}
catch {
return $false
}
}
$jobDirectory = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $JobRoot).Path "Job root"
) "Job root"
$runner = Assert-DDrivePath (
Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "LAB E8 runner"
) "LAB E8 runner"
$profile = Assert-DDrivePath (
Assert-RegularFile (Resolve-Path -LiteralPath $ProfilePath).Path "LAB E8 profile"
) "LAB E8 profile"
$validFov = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root"
) "Valid-FOV root"
$runtime = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root"
) "Runtime root"
$model = Assert-DDrivePath (
Assert-Directory (Resolve-Path -LiteralPath $ModelRoot).Path "YOLOX model root"
) "YOLOX model root"
$runnerRoot = Split-Path $runner -Parent
if ((Split-Path $profile -Parent) -ne $runnerRoot) {
throw "LAB E8 runner and profile must share one read-only mount"
}
foreach ($dependency in @(
"run_e5_instance_tracking.py",
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Assert-RegularFile (Join-Path $runnerRoot $dependency) "LAB E8 runner dependency"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$fullFrameCount = [int]$job.input.segment_count
$clipFrameCount = $EndFrame - $StartFrame + 1
$timelineStart = [double]$job.input.timeline.start_seconds
if (
$sourceId -ne "sensor.camera.right" -or
$StartFrame -lt 0 -or
$EndFrame -lt $StartFrame -or
$EndFrame -ge $fullFrameCount -or
$clipFrameCount -lt 2
) {
throw "LAB E8 clip escapes the camera job"
}
Write-Output (
"PHASE=job-manifest-validated JOB={0} CLIP={1}-{2} FRAMES={3}" -f
$job.job_id, $StartFrame, $EndFrame, $clipFrameCount
)
$epochRoot = Assert-DDrivePath (
Assert-Directory (
Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
) "Camera epoch"
) "Camera epoch"
$initPath = Assert-RegularFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Assert-Directory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$pythonEnvironment = Assert-DDrivePath (
Assert-Directory (Join-Path $derivedRoot "perception-p0-env-v1") "Python environment"
) "Python environment"
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
# LAB E8 has temporary decoded input but no per-frame output images. Reserve
# decoded RGB payload, partial stream and two GiB for filesystem/codec overhead.
$pixelWorkingSet = [int64]$clipFrameCount * 800 * 600 * 3
$partialStreamReserve = [int64][math]::Ceiling(
([int64]$job.input.byte_length * ($EndFrame + 1) / $fullFrameCount) * 1.2
)
$workingSetReserve = [int64][math]::Ceiling($pixelWorkingSet * 1.4) + $partialStreamReserve + 2GB
$freeBytesBefore = Assert-FreeSpace "preflight" $workingSetReserve
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
$tritonState = docker inspect --format "{{.State.Running}}" $TritonContainer
Assert-LastExitCode "Triton container inspection"
if ($tritonState.Trim().ToLowerInvariant() -ne "true") {
throw "LAB E8 requires the existing Triton container"
}
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$preflightArgs = @(
"run", "--rm", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "128", "--tmpfs", "/tmp:rw,noexec,nosuid,size=256m",
"-e", "PYTHONPATH=/runner:/opt/env",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $pythonEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "preflight",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model"
)
Write-Output "PHASE=e8-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E8 preflight"
Write-Output "PHASE=e8-preflight-complete"
$modelWasReady = Test-TritonModelReady
$loadedByRun = $false
if (-not $modelWasReady) {
Invoke-WebRequest `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" `
-Method Post -ContentType "application/json" -Body "{}" `
-UseBasicParsing -TimeoutSec 60 *> $null
$loadedByRun = $true
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady)) {
if ([DateTime]::UtcNow -ge $deadline) {
throw "YOLOX-S did not become ready in Triton"
}
Start-Sleep -Milliseconds 500
}
}
Write-Output ("PHASE=e8-model-ready LOADED_BY_RUN={0}" -f $loadedByRun)
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e8-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e8-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
$completed = $false
Write-Output "PHASE=e8-private-staging-created"
try {
$stream = [IO.File]::Open(
$streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None
)
try {
$input = [IO.File]::OpenRead($initPath)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
for ($sequence = 1; $sequence -le ($EndFrame + 1); $sequence++) {
$path = Assert-RegularFile (
Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)
) "Camera segment"
$input = [IO.File]::OpenRead($path)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
}
$stream.Flush($true)
}
finally {
$stream.Dispose()
}
$null = Assert-FreeSpace "post-stream-reconstruction"
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=e8-frame-extraction-start"
$selectFilter = "select='between(n,{0},{1})'" -f $StartFrame, $EndFrame
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 `
-vf $selectFilter -fps_mode passthrough (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "LAB E8 camera clip extraction"
& ffprobe -v error -select_streams v:0 `
-show_entries frame=best_effort_timestamp_time -of json $streamPath |
Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E8 camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $clipFrameCount -or $pts.Count -ne ($EndFrame + 1)) {
throw "Decoded LAB E8 frame/timestamp count differs from the selected clip"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousSessionSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new(
$timelinePath, $false, [Text.UTF8Encoding]::new($false)
)
try {
for ($localIndex = 0; $localIndex -lt $clipFrameCount; $localIndex++) {
$sourceIndex = $StartFrame + $localIndex
$epochSeconds = [double]::Parse(
([string]$pts[$sourceIndex].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
$sessionSeconds = $timelineStart + $epochSeconds
if ($sessionSeconds -le $previousSessionSeconds) {
throw "Decoded LAB E8 clip timestamps are not strictly monotonic"
}
$row = [ordered]@{
frame_index = $localIndex
sequence = $localIndex + 1
source_frame_index = $sourceIndex
source_sequence = $sourceIndex + 1
epoch_seconds = $epochSeconds
session_seconds = $sessionSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousSessionSeconds = $sessionSeconds
}
$timelineWriter.Flush()
}
finally {
$timelineWriter.Dispose()
}
$extractWatch.Stop()
$freeBytesPostExtract = Assert-FreeSpace "post-frame-extraction"
Write-Output ("PHASE=e8-frame-extraction-complete FRAMES={0}" -f $clipFrameCount)
$runArgs = @(
"run", "--rm", "--gpus", "all",
"--network", ("container:{0}" -f $TritonContainer),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "256", "--shm-size", "1g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=512m",
"-e", "PYTHONPATH=/runner:/opt/env",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $pythonEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "run",
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model",
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--output", "/publish/output",
"--triton-url", "http://127.0.0.1:8000",
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage,
"--telemetry-interval-seconds", "1"
)
Write-Output ("PHASE=e8-source-paced-replay-start FRAMES={0}" -f $clipFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E8 source-paced detector/tracker"
$freeBytesPostReplay = Assert-FreeSpace "post-replay"
Write-Output "PHASE=e8-source-paced-replay-complete"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.e8-realtime-tracking-result/v1" -or
$result.result_id -notmatch "^e8-realtime-tracking-[a-f0-9]{64}$" -or
$result.acceptance_state -ne "accepted" -or
$result.ground_truth -ne $false
) {
throw "Final LAB E8 result manifest is incompatible or rejected"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable result with the same LAB E8 identity already exists: $finalRoot"
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$totalWatch.Stop()
$completed = $true
$freeBytesFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("PREPARATION_SECONDS={0}" -f $extractWatch.Elapsed.TotalSeconds)
Write-Output ("ORCHESTRATOR_WALL_SECONDS={0}" -f $totalWatch.Elapsed.TotalSeconds)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freeBytesPostExtract)
Write-Output ("DISK_FREE_BYTES_POST_REPLAY={0}" -f $freeBytesPostReplay)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeBytesFinal)
}
finally {
if (Test-Path -LiteralPath $workRoot) {
Remove-Item -LiteralPath $workRoot -Recurse -Force
}
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
if ($loadedByRun) {
try {
Invoke-WebRequest `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/unload" `
-Method Post -ContentType "application/json" -Body "{}" `
-UseBasicParsing -TimeoutSec 60 *> $null
Write-Output "PHASE=e8-model-state-restored"
}
catch {
Write-Warning "LAB E8 could not restore the prior unloaded YOLOX-S state"
}
}
}
@@ -0,0 +1,349 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)] [string]$JobRoot,
[Parameter(Mandatory = $true)] [string]$RunnerPath,
[Parameter(Mandatory = $true)] [string]$ProfilePath,
[Parameter(Mandatory = $true)] [string]$DetectorProfilePath,
[Parameter(Mandatory = $true)] [string]$SemanticProfilePath,
[Parameter(Mandatory = $true)] [string]$ValidFovRoot,
[ValidateRange(0, 1000000)] [int]$StartFrame = 1000,
[ValidateRange(0, 1000000)] [int]$EndFrame = 1600,
[ValidateRange(1, 1000)] [int]$FreeGiBFloor = 360,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$ModelRoot = "D:\NDC_MISSIONCORE\runtime\models\yolox_s",
[string]$TritonContainer = "mission-core-triton",
[string]$ContainerImage = "nvcr.io/nvidia/tritonserver:26.06-py3"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a real D: directory"
}
return $item.FullName
}
function Resolve-DFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
$root = [IO.Path]::GetPathRoot($item.FullName).TrimEnd("\")
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or $root -ine "D:") {
throw "$Label must be a regular D: file"
}
return $item.FullName
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
function Get-DFreeBytes { return [int64](Get-PSDrive -Name D).Free }
function Assert-FreeSpace {
param([string]$Phase, [int64]$RequiredAdditionalBytes = 0)
$freeBytes = Get-DFreeBytes
$floorBytes = [int64]$FreeGiBFloor * 1GB
Write-Host (
"DISK_GUARD PHASE={0} DRIVE=D FREE_BYTES={1} FREE_GIB={2} FLOOR_GIB={3} REQUIRED_ADDITIONAL_GIB={4}" -f
$Phase, $freeBytes, [math]::Round($freeBytes / 1GB, 3), $FreeGiBFloor,
[math]::Round($RequiredAdditionalBytes / 1GB, 3)
)
if ($freeBytes -lt ($floorBytes + $RequiredAdditionalBytes)) {
throw "D: does not have the guarded LAB E9 working-set reserve during $Phase"
}
return $freeBytes
}
function Test-TritonModelReady {
try {
$response = Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 `
-Uri "http://127.0.0.1:8000/v2/models/yolox_s/ready"
return $response.StatusCode -eq 200
}
catch { return $false }
}
$jobDirectory = Resolve-DDirectory $JobRoot "Job root"
$runner = Resolve-DFile $RunnerPath "LAB E9 runner"
$profile = Resolve-DFile $ProfilePath "LAB E9 profile"
$detectorProfile = Resolve-DFile $DetectorProfilePath "LAB E9 detector profile"
$semanticProfile = Resolve-DFile $SemanticProfilePath "LAB E9 semantic profile"
$validFov = Resolve-DDirectory $ValidFovRoot "Valid-FOV root"
$runtime = Resolve-DDirectory $RuntimeRoot "Runtime root"
$model = Resolve-DDirectory $ModelRoot "YOLOX model root"
$runnerRoot = Split-Path $runner -Parent
foreach ($path in @($profile, $detectorProfile, $semanticProfile)) {
if ((Split-Path $path -Parent) -ne $runnerRoot) {
throw "LAB E9 runner and profiles must share one read-only mount"
}
}
foreach ($dependency in @(
"run_e8_realtime_tracking.py",
"run_e5_instance_tracking.py",
"run_e4_full_session_segmentation.py",
"run_recorded_perception_epoch.py",
"run_e3_rectified_segmentation.py",
"run_evaluation_prelabels.py"
)) {
$null = Resolve-DFile (Join-Path $runnerRoot $dependency) "LAB E9 runner dependency"
}
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$fullFrameCount = [int]$job.input.segment_count
$clipFrameCount = $EndFrame - $StartFrame + 1
$timelineStart = [double]$job.input.timeline.start_seconds
if (
$sourceId -ne "sensor.camera.right" -or $StartFrame -lt 0 -or
$EndFrame -lt $StartFrame -or $EndFrame -ge $fullFrameCount -or $clipFrameCount -lt 2
) { throw "LAB E9 clip escapes the camera job" }
Write-Output (
"PHASE=job-manifest-validated JOB={0} CLIP={1}-{2} FRAMES={3}" -f
$job.job_id, $StartFrame, $EndFrame, $clipFrameCount
)
$epochRoot = Resolve-DDirectory (
Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
) "Camera epoch"
$initPath = Resolve-DFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Resolve-DDirectory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$cacheRoot = Resolve-DDirectory (Join-Path $runtime "cache\perception-e3-models-v1") "EoMT cache"
$e3Environment = Resolve-DDirectory (Join-Path $derivedRoot "perception-e3-opencv413092-v1") "E3 environment"
$torchEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Resolve-DDirectory (Join-Path $derivedRoot "perception-p0-transformers4576-v1") "Transformers environment"
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
$pixelWorkingSet = [int64]$clipFrameCount * 800 * 600 * 3
$partialStreamReserve = [int64][math]::Ceiling(
([int64]$job.input.byte_length * ($EndFrame + 1) / $fullFrameCount) * 1.2
)
$workingSetReserve = [int64][math]::Ceiling($pixelWorkingSet * 1.4) + $partialStreamReserve + 3GB
$freeBytesBefore = Assert-FreeSpace "preflight" $workingSetReserve
& docker image inspect $ContainerImage *> $null
Assert-LastExitCode "Existing container image inspection"
$tritonState = docker inspect --format "{{.State.Running}}" $TritonContainer
Assert-LastExitCode "Triton container inspection"
if ($tritonState.Trim().ToLowerInvariant() -ne "true") {
throw "LAB E9 requires the existing Triton container"
}
$runnerName = Split-Path $runner -Leaf
$profileName = Split-Path $profile -Leaf
$detectorProfileName = Split-Path $detectorProfile -Leaf
$semanticProfileName = Split-Path $semanticProfile -Leaf
$orchestratorSha256 = (Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant()
$commonMounts = @(
"-e", "PYTHONPATH=/runner:/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $model) + ":/model:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:ro"),
"-v", ((Convert-ToDockerPath $e3Environment) + ":/environment:ro"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro")
)
$commonRunnerArgs = @(
"--job", "/job",
"--profile", ("/runner/{0}" -f $profileName),
"--detector-profile", ("/runner/{0}" -f $detectorProfileName),
"--semantic-profile", ("/runner/{0}" -f $semanticProfileName),
"--valid-fov-root", "/valid-fov",
"--model-root", "/model",
"--cache", "/cache",
"--environment", "/environment"
)
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none", "--read-only",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "preflight"
) + $commonRunnerArgs
Write-Output "PHASE=e9-preflight-start"
& docker @preflightArgs
Assert-LastExitCode "LAB E9 preflight"
Write-Output "PHASE=e9-preflight-complete"
$modelWasReady = Test-TritonModelReady
$loadedByRun = $false
if (-not $modelWasReady) {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/load" `
-Method Post -ContentType "application/json" -Body "{}" *> $null
$loadedByRun = $true
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while (-not (Test-TritonModelReady)) {
if ([DateTime]::UtcNow -ge $deadline) { throw "YOLOX-S did not become ready" }
Start-Sleep -Milliseconds 500
}
}
Write-Output ("PHASE=e9-model-ready LOADED_BY_RUN={0}" -f $loadedByRun)
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e9-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e9-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$completed = $false
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
try {
$stream = [IO.File]::Open($streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
$input = [IO.File]::OpenRead($initPath)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
for ($sequence = 1; $sequence -le ($EndFrame + 1); $sequence++) {
$path = Resolve-DFile (Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)) "Camera segment"
$input = [IO.File]::OpenRead($path)
try { $input.CopyTo($stream) } finally { $input.Dispose() }
}
$stream.Flush($true)
}
finally { $stream.Dispose() }
$null = Assert-FreeSpace "post-stream-reconstruction"
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
$selectFilter = "select='between(n,{0},{1})'" -f $StartFrame, $EndFrame
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 `
-vf $selectFilter -fps_mode passthrough (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "LAB E9 camera clip extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time `
-of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E9 camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$pts = @((Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json).frames)
if ($decodedFrames.Count -ne $clipFrameCount -or $pts.Count -ne ($EndFrame + 1)) {
throw "Decoded LAB E9 frame/timestamp count changed"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousSessionSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new(
$timelinePath, $false, [Text.UTF8Encoding]::new($false)
)
try {
for ($localIndex = 0; $localIndex -lt $clipFrameCount; $localIndex++) {
$sourceIndex = $StartFrame + $localIndex
$epochSeconds = [double]::Parse(
([string]$pts[$sourceIndex].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
$sessionSeconds = $timelineStart + $epochSeconds
if ($sessionSeconds -le $previousSessionSeconds) { throw "LAB E9 timeline is not monotonic" }
$row = [ordered]@{
frame_index = $localIndex
sequence = $localIndex + 1
source_frame_index = $sourceIndex
source_sequence = $sourceIndex + 1
epoch_seconds = $epochSeconds
session_seconds = $sessionSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousSessionSeconds = $sessionSeconds
}
$timelineWriter.Flush()
}
finally { $timelineWriter.Dispose() }
$extractWatch.Stop()
$freeBytesPostExtract = Assert-FreeSpace "post-frame-extraction"
Write-Output ("PHASE=e9-frame-extraction-complete FRAMES={0}" -f $clipFrameCount)
$runArgs = @(
"run", "--rm", "--gpus", "all", "--network", ("container:{0}" -f $TritonContainer),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "512", "--shm-size", "4g",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g"
) + $commonMounts + @(
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"--entrypoint", "python3", $ContainerImage,
("/runner/{0}" -f $runnerName), "run"
) + $commonRunnerArgs + @(
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--output", "/publish/output",
"--triton-url", "http://127.0.0.1:8000",
"--free-bytes-floor", [string]([int64]$FreeGiBFloor * 1GB),
"--orchestrator-sha256", $orchestratorSha256,
"--container-image", $ContainerImage,
"--telemetry-interval-seconds", "1"
)
Write-Output ("PHASE=e9-multirate-replay-start FRAMES={0}" -f $clipFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E9 multirate replay"
$freeBytesPostReplay = Assert-FreeSpace "post-replay"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.e9-multirate-perception-result/v1" -or
$result.result_id -notmatch "^e9-multirate-perception-[a-f0-9]{64}$" -or
$result.acceptance_state -notin @("accepted", "rejected") -or
$result.ground_truth -ne $false
) { throw "LAB E9 result manifest is incompatible" }
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) { throw "Immutable LAB E9 result already exists" }
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
$completed = $true
$totalWatch.Stop()
$freeBytesFinal = Assert-FreeSpace "post-publication"
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
Write-Output ("ACCEPTANCE_STATE={0}" -f $result.acceptance_state)
Write-Output ("PREPARATION_SECONDS={0}" -f $extractWatch.Elapsed.TotalSeconds)
Write-Output ("ORCHESTRATOR_WALL_SECONDS={0}" -f $totalWatch.Elapsed.TotalSeconds)
Write-Output ("DISK_FREE_BYTES_BEFORE={0}" -f $freeBytesBefore)
Write-Output ("DISK_FREE_BYTES_POST_EXTRACT={0}" -f $freeBytesPostExtract)
Write-Output ("DISK_FREE_BYTES_POST_REPLAY={0}" -f $freeBytesPostReplay)
Write-Output ("DISK_FREE_BYTES_FINAL={0}" -f $freeBytesFinal)
}
finally {
if (Test-Path -LiteralPath $workRoot) { Remove-Item -LiteralPath $workRoot -Recurse -Force }
if (-not $completed -and (Test-Path -LiteralPath $publishRoot)) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
if ($loadedByRun) {
try {
Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 `
-Uri "http://127.0.0.1:8000/v2/repository/models/yolox_s/unload" `
-Method Post -ContentType "application/json" -Body "{}" *> $null
Write-Output "PHASE=e9-model-state-restored"
}
catch { Write-Warning "LAB E9 could not restore the prior unloaded YOLOX-S state" }
}
}
@@ -0,0 +1,133 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$EvaluationPack,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$BaselineRunnerPath,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
$packRoot = Assert-Directory (Resolve-Path -LiteralPath $EvaluationPack).Path "Evaluation pack"
$validFov = Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root"
$runner = Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "Prelabel runner"
$baselineRunner = Assert-RegularFile (Resolve-Path -LiteralPath $BaselineRunnerPath).Path "Baseline runner"
if ((Split-Path $runner -Parent) -ne (Split-Path $baselineRunner -Parent)) {
throw "Prelabel and baseline runners must share one read-only mount"
}
$runtime = Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root"
$pack = Get-Content -LiteralPath (Join-Path $packRoot "manifest.json") -Raw | ConvertFrom-Json
if (
$pack.schema_version -ne "missioncore.perception-evaluation-pack/v1" -or
$pack.generation_id -ne (Split-Path $packRoot -Leaf) -or
$pack.identity.preprocessing_profile -ne "fixed-valid-fov-fill/v1"
) {
throw "Evaluation pack manifest is incompatible"
}
$null = Assert-RegularFile (Join-Path $validFov "manifest.json") "Valid-FOV manifest"
$null = Assert-RegularFile (Join-Path $validFov "mask.png") "Valid-FOV mask"
$cacheRoot = Assert-Directory (Join-Path $runtime "cache\perception-p0-models-v1") "Model cache"
$torchEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-transformers4576-v1") "Transformers environment"
$runnerRoot = Split-Path $runner -Parent
$runnerName = Split-Path $runner -Leaf
$derivedRoot = Join-Path $runtime "derived\evaluation-prelabels"
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$runToken = [Guid]::NewGuid().ToString("N")
$publishRoot = Join-Path $derivedRoot (".{0}-{1}.publish" -f $pack.generation_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $publishRoot
try {
$dockerArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--shm-size", "2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $packRoot) + ":/evaluation-pack:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $runnerName),
"--evaluation-pack", "/evaluation-pack",
"--valid-fov-root", "/valid-fov",
"--output", "/publish/output",
"--cache", "/cache",
"--telemetry-interval-seconds", "1"
)
Write-Output ("PHASE=e2-prelabels-start PACK={0}" -f $pack.generation_id)
& docker @dockerArgs
Assert-LastExitCode "E2 prelabel generation"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.perception-evaluation-prelabels/v1" -or
$result.result_id -notmatch "^evaluation-prelabels-[a-f0-9]{64}$" -or
$result.identity.evaluation_pack_id -ne $pack.generation_id
) {
throw "E2 prelabel result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable E2 prelabel result with the same identity already exists: $finalRoot"
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
}
finally {
if (Test-Path -LiteralPath $publishRoot) {
Remove-Item -LiteralPath $publishRoot -Recurse -Force
}
}
@@ -0,0 +1,276 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$JobRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[Parameter(Mandatory = $true)]
[string]$BaselineRunnerPath,
[Parameter(Mandatory = $true)]
[string]$QualificationManifest,
[Parameter(Mandatory = $true)]
[string]$ValidFovRoot,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$CalibrationSha256 = "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
[string]$CalibrationSlot = "camera_1"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
$jobDirectory = Assert-Directory (Resolve-Path -LiteralPath $JobRoot).Path "Job root"
$runner = Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "Qualification runner"
$baselineRunner = Assert-RegularFile (Resolve-Path -LiteralPath $BaselineRunnerPath).Path "Baseline runner"
$qualification = Assert-RegularFile (Resolve-Path -LiteralPath $QualificationManifest).Path "Qualification manifest"
$validFov = Assert-Directory (Resolve-Path -LiteralPath $ValidFovRoot).Path "Valid-FOV root"
$null = Assert-RegularFile (Join-Path $validFov "manifest.json") "Valid-FOV manifest"
$null = Assert-RegularFile (Join-Path $validFov "mask.png") "Valid-FOV mask"
if ((Split-Path $runner -Parent) -ne (Split-Path $baselineRunner -Parent)) {
throw "Qualification and baseline runners must share one read-only mount"
}
$runtime = Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root"
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
if ($CalibrationSha256 -notmatch "^[a-f0-9]{64}$" -or $CalibrationSlot -notmatch "^[A-Za-z0-9._-]+$") {
throw "Calibration binding is invalid"
}
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$frameCount = [int]$job.input.segment_count
$timelineStart = [double]$job.input.timeline.start_seconds
$timelineEnd = [double]$job.input.timeline.end_seconds
$timelineDuration = $timelineEnd - $timelineStart
if ($frameCount -lt 1 -or $timelineDuration -le 0) {
throw "Compute job frame/timeline contract is invalid"
}
$qualificationDocument = Get-Content -LiteralPath $qualification -Raw | ConvertFrom-Json
if (
$qualificationDocument.schema_version -ne "missioncore.recorded-qualification-slice/v1" -or
$qualificationDocument.identity.job_id -ne $job.job_id -or
$qualificationDocument.identity.input_sha256 -ne $job.input_sha256
) {
throw "Qualification manifest is not bound to this job"
}
Write-Output ("PHASE=inputs-validated QUALIFICATION_FRAMES={0}" -f @($qualificationDocument.frames).Count)
$epochRoot = Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
$epochRoot = Assert-Directory $epochRoot "Camera epoch"
$initPath = Assert-RegularFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Assert-Directory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived\qualification"
$tmpRoot = Join-Path $runtime "tmp"
$cacheRoot = Assert-Directory (Join-Path $runtime "cache\perception-p0-models-v1") "Model cache"
$torchEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Assert-Directory (Join-Path $runtime "derived\perception-p0-transformers4576-v1") "Transformers environment"
$runnerRoot = Split-Path $runner -Parent
$runnerName = Split-Path $runner -Leaf
$baselineRunnerName = Split-Path $baselineRunner -Leaf
$qualificationRoot = Split-Path $qualification -Parent
$qualificationName = Split-Path $qualification -Leaf
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $baselineRunnerName), "preflight",
"--job", "/job",
"--cache", "/cache"
)
Write-Output "PHASE=worker-preflight-started"
& docker @preflightArgs
Assert-LastExitCode "Worker preflight"
Write-Output "PHASE=worker-preflight-complete"
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e1-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e1-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
Write-Output "PHASE=private-staging-created"
try {
$stream = [IO.File]::Open($streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
$input = [IO.File]::OpenRead($initPath)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
for ($sequence = 1; $sequence -le $frameCount; $sequence++) {
$path = Assert-RegularFile (Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)) "Camera segment"
$input = [IO.File]::OpenRead($path)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
if ($sequence % 500 -eq 0 -or $sequence -eq $frameCount) {
Write-Output ("PHASE=stream-reconstruction SEGMENTS={0}/{1}" -f $sequence, $frameCount)
}
}
$stream.Flush($true)
}
finally {
$stream.Dispose()
}
Write-Output "PHASE=frame-extraction-started"
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 -fps_mode passthrough (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "Camera extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time -of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "Camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $frameCount -or $pts.Count -ne $frameCount) {
throw "Decoded frame count differs from the compute job"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousEpochSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new($timelinePath, $false, [Text.UTF8Encoding]::new($false))
try {
for ($index = 0; $index -lt $frameCount; $index++) {
$epochSeconds = [double]::Parse(
([string]$pts[$index].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
if ($epochSeconds -le $previousEpochSeconds -or $epochSeconds -gt ($timelineDuration + 0.001)) {
throw "Decoded camera timestamps are not strictly monotonic inside the compute job timeline"
}
$row = [ordered]@{
frame_index = $index
epoch_seconds = $epochSeconds
session_seconds = $timelineStart + $epochSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousEpochSeconds = $epochSeconds
}
$timelineWriter.Flush()
}
finally {
$timelineWriter.Dispose()
}
Write-Output ("PHASE=frame-extraction-complete FRAMES={0}" -f $frameCount)
$dockerArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--shm-size", "2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"-v", ((Convert-ToDockerPath $qualificationRoot) + ":/qualification:ro"),
"-v", ((Convert-ToDockerPath $validFov) + ":/valid-fov:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $runnerName),
"--job", "/job",
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--qualification", ("/qualification/{0}" -f $qualificationName),
"--valid-fov-root", "/valid-fov",
"--output", "/publish/output",
"--cache", "/cache",
"--calibration-sha256", $CalibrationSha256,
"--calibration-slot", $CalibrationSlot,
"--telemetry-interval-seconds", "1"
)
& docker @dockerArgs
Assert-LastExitCode "Preprocessing qualification"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if (
$result.schema_version -ne "missioncore.perception-preprocessing-qualification-result/v1" -or
$result.result_id -notmatch "^qualification-result-[a-f0-9]{64}$"
) {
throw "Qualification result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable qualification result with the same identity already exists: $finalRoot"
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
}
finally {
if (Test-Path -LiteralPath $workRoot) {
Remove-Item -LiteralPath $workRoot -Recurse -Force
}
}
@@ -0,0 +1,292 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$JobRoot,
[Parameter(Mandatory = $true)]
[string]$RunnerPath,
[string]$RuntimeRoot = "D:\NDC_MISSIONCORE\runtime",
[string]$CalibrationSha256 = "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
[string]$CalibrationSlot = "camera_1"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
function Assert-LastExitCode {
param([string]$Operation)
if ($LASTEXITCODE -ne 0) {
throw "$Operation failed with exit code $LASTEXITCODE"
}
}
function Assert-RegularFile {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if (-not $item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a regular file"
}
function Assert-Directory {
param([string]$Path, [string]$Label)
$item = Get-Item -LiteralPath $Path -Force
if ($item.PSIsContainer -and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
return $item.FullName
}
throw "$Label is not a real directory"
}
function Convert-ToDockerPath {
param([string]$Path)
return $Path.Replace("\", "/")
}
$jobDirectory = Assert-Directory (Resolve-Path -LiteralPath $JobRoot).Path "Job root"
$runner = Assert-RegularFile (Resolve-Path -LiteralPath $RunnerPath).Path "Runner"
$runtime = Assert-Directory (Resolve-Path -LiteralPath $RuntimeRoot).Path "Runtime root"
$job = Get-Content -LiteralPath (Join-Path $jobDirectory "job.json") -Raw | ConvertFrom-Json
if ($job.schema_version -ne "missioncore.compute-job/v1" -or $job.job_id -ne (Split-Path $jobDirectory -Leaf)) {
throw "Compute job manifest is incompatible"
}
if ($CalibrationSha256 -notmatch "^[a-f0-9]{64}$" -or $CalibrationSlot -notmatch "^[A-Za-z0-9._-]+$") {
throw "Calibration binding is invalid"
}
Write-Output "PHASE=job-manifest-validated"
$sourceId = [string]$job.input.source_id
$epoch = [int]$job.input.codec_epoch
$frameCount = [int]$job.input.segment_count
$timelineStart = [double]$job.input.timeline.start_seconds
$timelineEnd = [double]$job.input.timeline.end_seconds
$timelineDuration = $timelineEnd - $timelineStart
if ($frameCount -lt 1 -or $timelineDuration -le 0) {
throw "Compute job frame/timeline contract is invalid"
}
$epochRoot = Join-Path $jobDirectory ("input\camera\{0}\epoch-{1}" -f $sourceId, $epoch)
$epochRoot = Assert-Directory $epochRoot "Camera epoch"
$initPath = Assert-RegularFile (Join-Path $epochRoot "init.mp4") "Camera init"
$segmentsRoot = Assert-Directory (Join-Path $epochRoot "segments") "Camera segments"
$derivedRoot = Join-Path $runtime "derived"
$tmpRoot = Join-Path $runtime "tmp"
$cacheRoot = Assert-Directory (Join-Path $runtime "cache\perception-p0-models-v1") "Model cache"
$torchEnvironment = Assert-Directory (Join-Path $derivedRoot "perception-p0-env-v1") "Torch environment"
$transformersEnvironment = Assert-Directory (Join-Path $derivedRoot "perception-p0-transformers4576-v1") "Transformers environment"
$runnerRoot = Split-Path $runner -Parent
$runnerName = Split-Path $runner -Leaf
$null = New-Item -ItemType Directory -Path $derivedRoot -Force
$null = New-Item -ItemType Directory -Path $tmpRoot -Force
$preflightArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $runnerName), "preflight",
"--job", "/job",
"--cache", "/cache"
)
Write-Output "PHASE=worker-preflight-started"
& docker @preflightArgs
Assert-LastExitCode "Worker preflight"
Write-Output "PHASE=worker-preflight-complete"
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-panoptic-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-panoptic-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=private-staging-created"
try {
$stream = [IO.File]::Open($streamPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
try {
foreach ($path in @($initPath)) {
$input = [IO.File]::OpenRead($path)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
}
for ($sequence = 1; $sequence -le $frameCount; $sequence++) {
$path = Assert-RegularFile (Join-Path $segmentsRoot ("{0}.m4s" -f $sequence)) "Camera segment"
$input = [IO.File]::OpenRead($path)
try {
$input.CopyTo($stream)
}
finally {
$input.Dispose()
}
if ($sequence % 500 -eq 0 -or $sequence -eq $frameCount) {
Write-Output ("PHASE=stream-reconstruction SEGMENTS={0}/{1}" -f $sequence, $frameCount)
}
}
$stream.Flush($true)
}
finally {
$stream.Dispose()
}
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=frame-extraction-started"
# This archive contains valid strictly-increasing PTS but image2 rounds DTS
# to its own time base and reports harmless duplicate-DTS diagnostics. Exact
# frame-count and JSON timestamp checks below are the admission boundary.
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 -fps_mode passthrough (Join-Path $framesRoot "frame-%06d.png")
Assert-LastExitCode "Camera extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time -of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "Camera timestamp probe"
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $frameCount -or $pts.Count -ne $frameCount) {
throw "Decoded frame count differs from the compute job"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$previousEpochSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new($timelinePath, $false, [Text.UTF8Encoding]::new($false))
try {
for ($index = 0; $index -lt $frameCount; $index++) {
$epochSeconds = [double]::Parse(
([string]$pts[$index].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
if ($epochSeconds -le $previousEpochSeconds -or $epochSeconds -gt ($timelineDuration + 0.001)) {
throw "Decoded camera timestamps are not strictly monotonic inside the compute job timeline"
}
$sessionSeconds = $timelineStart + $epochSeconds
$row = [ordered]@{
frame_index = $index
epoch_seconds = $epochSeconds
session_seconds = $sessionSeconds
}
$timelineWriter.WriteLine(($row | ConvertTo-Json -Compress))
$previousEpochSeconds = $epochSeconds
}
$timelineWriter.Flush()
}
finally {
$timelineWriter.Dispose()
}
$extractWatch.Stop()
Write-Output ("PHASE=frame-extraction-complete FRAMES={0}" -f $frameCount)
$dockerArgs = @(
"run", "--rm", "--gpus", "all", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--shm-size", "2g",
"-e", "PYTHONPATH=/opt/transformers:/opt/env",
"-e", "TORCH_HOME=/cache/torch",
"-e", "HF_HOME=/cache/huggingface",
"-e", "HF_HUB_OFFLINE=1",
"-e", "TRANSFORMERS_OFFLINE=1",
"-v", ((Convert-ToDockerPath $jobDirectory) + ":/job:ro"),
"-v", ((Convert-ToDockerPath $framesRoot) + ":/frames:ro"),
"-v", ((Convert-ToDockerPath $workRoot) + ":/work:ro"),
"-v", ((Convert-ToDockerPath $publishRoot) + ":/publish:rw"),
"-v", ((Convert-ToDockerPath $cacheRoot) + ":/cache:rw"),
"-v", ((Convert-ToDockerPath $torchEnvironment) + ":/opt/env:ro"),
"-v", ((Convert-ToDockerPath $transformersEnvironment) + ":/opt/transformers:ro"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $runnerName), "run",
"--job", "/job",
"--frames", "/frames",
"--timeline", "/work/timeline.jsonl",
"--output", "/publish/output",
"--cache", "/cache",
"--calibration-sha256", $CalibrationSha256,
"--calibration-slot", $CalibrationSlot,
"--telemetry-interval-seconds", "1"
)
& docker @dockerArgs
Assert-LastExitCode "Panoptic inference"
Write-Output "PHASE=panoptic-inference-complete"
$videoPath = Join-Path $stagingRoot "perception.mp4"
$encodeWatch = [Diagnostics.Stopwatch]::StartNew()
$averageFps = $frameCount / $timelineDuration
$fpsText = $averageFps.ToString("0.#########", [Globalization.CultureInfo]::InvariantCulture)
$durationText = $timelineDuration.ToString("0.#########", [Globalization.CultureInfo]::InvariantCulture)
& ffmpeg -hide_banner -loglevel error -framerate $fpsText -i (Join-Path $stagingRoot "overlay-frames\frame-%06d.png") -t $durationText -c:v h264_nvenc -preset p4 -tune hq -rc vbr -cq 21 -b:v 0 -pix_fmt yuv420p -movflags +faststart $videoPath
Assert-LastExitCode "Panoptic video encoding"
$encodeWatch.Stop()
Write-Output "PHASE=video-encoding-complete"
$masksPath = Join-Path $stagingRoot "masks.tar.gz"
& tar.exe -czf $masksPath -C $stagingRoot instance-masks semantic-masks
Assert-LastExitCode "Mask archive publication"
$totalWatch.Stop()
$finalizeArgs = @(
"run", "--rm", "--network", "none",
"--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"-v", ((Convert-ToDockerPath $stagingRoot) + ":/output:rw"),
"-v", ((Convert-ToDockerPath $runnerRoot) + ":/runner:ro"),
"--entrypoint", "python3",
"nvcr.io/nvidia/tritonserver:26.06-py3",
("/runner/{0}" -f $runnerName), "finalize",
"--output", "/output",
"--video", "/output/perception.mp4",
"--masks", "/output/masks.tar.gz",
"--extract-seconds", $extractWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encode-seconds", $encodeWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--wall-seconds", $totalWatch.Elapsed.TotalSeconds.ToString("0.######", [Globalization.CultureInfo]::InvariantCulture),
"--encoder", "ffmpeg-h264_nvenc-p4-cq21-yuv420p-faststart"
)
& docker @finalizeArgs
Assert-LastExitCode "Result finalization"
$result = Get-Content -LiteralPath (Join-Path $stagingRoot "result.json") -Raw | ConvertFrom-Json
if ($result.schema_version -ne "missioncore.recorded-perception-result/v2" -or $result.result_id -notmatch "^result-[a-f0-9]{64}$") {
throw "Final result manifest is incompatible"
}
$finalRoot = Join-Path $derivedRoot ([string]$result.result_id)
if (Test-Path -LiteralPath $finalRoot) {
throw "An immutable result with the same identity already exists: $finalRoot"
}
foreach ($temporaryChild in @("overlay-frames", "instance-masks", "semantic-masks")) {
$temporaryPath = Join-Path $stagingRoot $temporaryChild
if (Test-Path -LiteralPath $temporaryPath) {
Remove-Item -LiteralPath $temporaryPath -Recurse -Force
}
}
Move-Item -LiteralPath $stagingRoot -Destination $finalRoot
Remove-Item -LiteralPath $publishRoot -Force
Write-Output ("RESULT_ROOT={0}" -f $finalRoot)
Write-Output ("RESULT_ID={0}" -f $result.result_id)
}
finally {
if (Test-Path -LiteralPath $workRoot) {
Remove-Item -LiteralPath $workRoot -Recurse -Force
}
}
@@ -0,0 +1,62 @@
# Mission Core CVAT D-only profile
This profile installs the annotation control plane in a dedicated WSL2
distribution named `MissionCore-CVAT`. Its VHDX, Docker image store, CVAT
source, persistent volumes, reports, and imported datasets live below
`D:\NDC_MISSIONCORE`. It does not use the Docker Desktop image store that backs
the Triton, Frigate, and Ollama containers.
Pinned inputs:
- Ubuntu 24.04.4 WSL AMD64 image, SHA-256
`9b2f7730dc68227dd04a9f3e5eab86ad85caf556b8606ad94f1f29ff5c4fd3f5`
- CVAT `v2.70.0`
- D free-space floor: `360 GiB`
- WSL VHD logical ceiling: `32 GB` (sparse allocation)
The official CVAT Compose topology is retained. The override only replaces its
named volumes with explicit bind-backed directories inside the D-hosted WSL
VHD. Images are pulled serially and the D free-space floor is checked before and
after every image.
Run from the Windows host:
```powershell
wsl -d MissionCore-CVAT -u root -- bash /mnt/d/NDC_MISSIONCORE/workspace/mission-core-compute/cvat/provision_cvat_wsl.sh
wsl -d MissionCore-CVAT -u root -- bash /mnt/d/NDC_MISSIONCORE/workspace/mission-core-compute/cvat/ensure_cvat_admin.sh
wsl -d MissionCore-CVAT -u root -- bash /mnt/d/NDC_MISSIONCORE/workspace/mission-core-compute/cvat/import_e2_workspace.sh
```
After a Windows reboot, start the existing deployment without pulling images or
re-provisioning it:
```powershell
& D:\NDC_MISSIONCORE\workspace\mission-core-compute\cvat\Start-Cvat.ps1
```
The launcher checks the `360 GiB` D-drive floor, starts the pinned Compose
topology in the D-hosted WSL distribution, waits for the API, and keeps the WSL
runtime alive. It writes startup logs only below
`D:\NDC_MISSIONCORE\runtime\annotation\cvat\logs`.
From the Mission Core repository on the Mac, start CVAT if needed, discover the
current WSL address, and open the SSH tunnel without a hard-coded IP:
```bash
bash experiments/perception/worker/cvat/open_cvat_tunnel.sh
```
Keep that terminal open and use `http://localhost:18080`. The same SSH session
keeps the WSL runtime alive. Pass `--background` when detached runtime and
tunnel sessions are preferred; rerun the helper after a Mac or Windows reboot.
The generated administrator password is stored only in
`D:\NDC_MISSIONCORE\secrets\cvat\admin.env`; it is not printed by the script or
committed to Git.
Traefik binds inside the dedicated WSL environment to `127.0.0.1:8080` and
`127.0.0.1:8090`. It also publishes container port `8080` as WSL-internal port
`18080` so the existing Windows SSH service can forward it without relying on
Windows-to-WSL localhost forwarding. Remote review still uses the exact SSH
host alias and a local forward; this profile does not add routes, Windows port
proxies, DNS changes, or firewall rules.
@@ -0,0 +1,58 @@
[CmdletBinding()]
param(
[int]$TimeoutSeconds = 180
)
$ErrorActionPreference = 'Stop'
$Distro = 'MissionCore-CVAT'
$Root = 'D:\NDC_MISSIONCORE'
$RuntimeRoot = Join-Path $Root 'runtime\annotation\cvat'
$LogRoot = Join-Path $RuntimeRoot 'logs'
$LinuxStartScript = '/mnt/d/NDC_MISSIONCORE/workspace/mission-core-compute/cvat/start_cvat_runtime.sh'
$FreeGiBFloor = 360
$drive = Get-PSDrive -Name D
$freeGiB = [math]::Floor($drive.Free / 1GB)
if ($freeGiB -lt $FreeGiBFloor) {
throw "D: free-space floor crossed ($freeGiB GiB free; $FreeGiBFloor GiB required)"
}
try {
$about = Invoke-RestMethod -Uri 'http://localhost:8080/api/server/about' -TimeoutSec 3
if ($about.version -eq '2.70.0') {
Write-Output "CVAT already ready version=$($about.version) free_gib=$freeGiB"
exit 0
}
} catch {
# A stopped WSL distribution is the normal condition after a Windows reboot.
}
New-Item -ItemType Directory -Path $LogRoot -Force | Out-Null
$timestamp = (Get-Date).ToUniversalTime().ToString('yyyyMMddTHHmmssZ')
$stdoutPath = Join-Path $LogRoot "start-$timestamp.stdout.log"
$stderrPath = Join-Path $LogRoot "start-$timestamp.stderr.log"
$arguments = @(
'-d', $Distro,
'-u', 'root',
'--', 'bash', $LinuxStartScript, '--keepalive'
)
Start-Process -FilePath 'wsl.exe' -ArgumentList $arguments -WindowStyle Hidden `
-RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
do {
Start-Sleep -Seconds 2
try {
$about = Invoke-RestMethod -Uri 'http://localhost:8080/api/server/about' -TimeoutSec 3
if ($about.version -eq '2.70.0') {
Write-Output "CVAT ready version=$($about.version) free_gib=$freeGiB"
Write-Output "startup_log=$stdoutPath"
exit 0
}
} catch {
# Continue until the complete CVAT Compose topology is ready.
}
} while ((Get-Date) -lt $deadline)
throw "CVAT did not become ready within $TimeoutSeconds seconds; inspect $stderrPath"
@@ -0,0 +1,50 @@
services:
traefik:
ports: !override
- 127.0.0.1:8080:8080
- 127.0.0.1:8090:8090
- 0.0.0.0:18080:8080
volumes:
cvat_db:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_db
cvat_data:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_data
cvat_keys:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_keys
cvat_logs:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_logs
cvat_inmem_db:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_inmem_db
cvat_events_db:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_events_db
cvat_cache_db:
driver: local
driver_opts:
type: none
o: bind
device: /srv/mission-core-cvat/volumes/cvat_cache_db
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -Eeuo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
readonly SECRET_ROOT="/mnt/d/NDC_MISSIONCORE/secrets/cvat"
readonly SECRET_FILE="${SECRET_ROOT}/admin.env"
if [[ "${EUID}" -ne 0 ]]; then
echo "ensure_cvat_admin.sh must run as root" >&2
exit 2
fi
install -d -m 0700 "${SECRET_ROOT}"
if [[ ! -f "${SECRET_FILE}" ]]; then
umask 077
password="$(openssl rand -hex 24)"
{
printf 'CVAT_ADMIN_USERNAME=missioncore\n'
printf 'CVAT_ADMIN_EMAIL=missioncore@local.invalid\n'
printf 'CVAT_ADMIN_PASSWORD=%s\n' "${password}"
} >"${SECRET_FILE}"
fi
set -a
# shellcheck disable=SC1090
. "${SECRET_FILE}"
set +a
docker exec \
-e "CVAT_ADMIN_USERNAME=${CVAT_ADMIN_USERNAME}" \
-e "CVAT_ADMIN_EMAIL=${CVAT_ADMIN_EMAIL}" \
-e "CVAT_ADMIN_PASSWORD=${CVAT_ADMIN_PASSWORD}" \
cvat_server \
python3 /home/django/manage.py shell -c \
'import os; from django.contrib.auth import get_user_model; User = get_user_model(); user, _ = User.objects.get_or_create(username=os.environ["CVAT_ADMIN_USERNAME"]); user.email = os.environ["CVAT_ADMIN_EMAIL"]; user.is_staff = True; user.is_superuser = True; user.set_password(os.environ["CVAT_ADMIN_PASSWORD"]); user.save()'
printf 'admin_ready=true\nusername=%s\nsecret_file=%s\n' \
"${CVAT_ADMIN_USERNAME}" "${SECRET_FILE}"
@@ -0,0 +1,284 @@
from __future__ import annotations
import argparse
import hashlib
import json
import os
import time
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from cvat_sdk import make_client, models
from cvat_sdk.api_client.exceptions import ServiceException
from cvat_sdk.core.proxies.tasks import ResourceType
WORKSPACE_ID = (
"annotation-workspace-"
"9a950d1c37d56dc12cc285b13c5addd7795285879cbcb1fbb2d5811c3c69821a"
)
EVALUATION_PACK_ID = (
"evaluation-pack-"
"7a983bba75d46c7c260252cb2d461e1384dcb92cda9e164397e841e6ebb37789"
)
EXPECTED_FRAME_COUNT = 64
EXPECTED_INSTANCE_COUNT = 775
BACKGROUND_LABEL = {
"name": "background",
"color": "#000000",
"attributes": [],
}
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _label_specs(raw_labels: list[dict[str, Any]]) -> list[dict[str, Any]]:
labels = [
{
"name": item["name"],
"color": item["color"],
"attributes": [],
}
for item in raw_labels
]
if not any(label["name"] == BACKGROUND_LABEL["name"] for label in labels):
labels.insert(0, dict(BACKGROUND_LABEL))
return labels
def _annotation_counts(task: Any) -> dict[str, int]:
annotations = task.get_annotations()
return {
"shape_count": len(annotations.shapes),
"tag_count": len(annotations.tags),
"track_count": len(annotations.tracks),
}
def _list_tasks_when_ready(client: Any, *, attempts: int = 60) -> list[Any]:
for attempt in range(1, attempts + 1):
try:
return list(client.tasks.list())
except ServiceException as error:
if error.status not in {500, 502, 503} or attempt == attempts:
raise
time.sleep(2)
raise AssertionError("unreachable")
def _task_record(task: Any, *, disposition: str, expected_labels: list[str]) -> dict[str, Any]:
task.fetch()
task_labels = list(task.get_labels())
actual_labels = sorted(label.name for label in task_labels)
if task.size != EXPECTED_FRAME_COUNT:
raise RuntimeError(
f"Task {task.id} has {task.size} frames, expected {EXPECTED_FRAME_COUNT}"
)
if actual_labels != sorted(expected_labels):
raise RuntimeError(
f"Task {task.id} labels differ: actual={actual_labels!r} "
f"expected={sorted(expected_labels)!r}"
)
annotation_counts = _annotation_counts(task)
label_names_by_id = {label.id: label.name for label in task_labels}
annotations = task.get_annotations()
shape_counts_by_label = dict(
sorted(
Counter(
label_names_by_id.get(shape.label_id, f"unknown:{shape.label_id}")
for shape in annotations.shapes
).items()
)
)
return {
"id": task.id,
"name": task.name,
"disposition": disposition,
"frame_count": task.size,
"label_names": actual_labels,
**annotation_counts,
"shape_counts_by_label": shape_counts_by_label,
"url_path": f"/tasks/{task.id}",
}
def _ensure_task(
client: Any,
*,
name: str,
labels: list[dict[str, Any]],
images_path: Path,
annotation_path: Path,
annotation_format: str,
expected_shape_count: int | None,
) -> dict[str, Any]:
matches = [task for task in _list_tasks_when_ready(client) if task.name == name]
if len(matches) > 1:
raise RuntimeError(f"Multiple CVAT tasks have the reserved name {name!r}")
expected_labels = [label["name"] for label in labels]
if matches:
task = matches[0]
task.fetch()
actual_labels = sorted(label.name for label in task.get_labels())
expected_without_background = sorted(
label for label in expected_labels if label != BACKGROUND_LABEL["name"]
)
disposition = "reused"
if (
actual_labels == expected_without_background
and BACKGROUND_LABEL["name"] in expected_labels
):
task.update(
models.PatchedTaskWriteRequest(
labels=[models.PatchedLabelRequest(**BACKGROUND_LABEL)]
)
)
disposition = "reused-and-background-label-added"
record = _task_record(
task,
disposition=disposition,
expected_labels=expected_labels,
)
if record["shape_count"] == 0:
task.import_annotations(annotation_format, annotation_path)
record = _task_record(
task,
disposition=f"{disposition}-and-annotations-imported",
expected_labels=expected_labels,
)
if expected_shape_count is not None and record["shape_count"] != expected_shape_count:
raise RuntimeError(
f"Task {task.id} has {record['shape_count']} shapes, "
f"expected {expected_shape_count}"
)
return record
task = client.tasks.create_from_data(
spec=models.TaskWriteRequest(
name=name,
labels=labels,
segment_size=EXPECTED_FRAME_COUNT,
overlap=0,
),
resources=[images_path],
resource_type=ResourceType.LOCAL,
data_params={
"image_quality": 100,
"sorting_method": "lexicographical",
"use_cache": False,
},
annotation_path=annotation_path,
annotation_format=annotation_format,
status_check_period=2,
)
record = _task_record(task, disposition="created", expected_labels=expected_labels)
if expected_shape_count is not None and record["shape_count"] != expected_shape_count:
raise RuntimeError(
f"Task {task.id} has {record['shape_count']} shapes, "
f"expected {expected_shape_count}"
)
return record
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--server", required=True)
parser.add_argument("--username", required=True)
parser.add_argument("--password-env", required=True)
parser.add_argument("--workspace", type=Path, required=True)
parser.add_argument("--report", type=Path, required=True)
args = parser.parse_args()
password = os.environ.get(args.password_env)
if not password:
raise RuntimeError(f"Password environment variable {args.password_env!r} is empty")
workspace = args.workspace.resolve()
if workspace.name != WORKSPACE_ID:
raise RuntimeError(f"Unexpected annotation workspace: {workspace}")
manifest_path = workspace / "manifest.json"
labels_path = workspace / "cvat" / "labels.json"
images_path = workspace / "cvat" / "images.zip"
instance_path = workspace / "cvat" / "instance-coco.zip"
semantic_path = workspace / "cvat" / "semantic-mask.zip"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
labels = json.loads(labels_path.read_text(encoding="utf-8"))
if manifest["ground_truth"] is not False:
raise RuntimeError("LAB E2 import must remain an unreviewed model draft")
if manifest["identity"]["frame_count"] != EXPECTED_FRAME_COUNT:
raise RuntimeError("Unexpected LAB E2 frame count")
if manifest["identity"]["draft_instance_count"] != EXPECTED_INSTANCE_COUNT:
raise RuntimeError("Unexpected LAB E2 instance count")
task_specs = [
{
"name": "LAB E2 | K1 | instance prelabels | pack 7a983bba",
"labels": _label_specs(labels["instance_task"]),
"annotation_path": instance_path,
"annotation_format": "COCO 1.0",
"expected_shape_count": EXPECTED_INSTANCE_COUNT,
},
{
"name": "LAB E2 | K1 | dense semantic prelabels | pack 7a983bba",
"labels": _label_specs(labels["semantic_task"]),
"annotation_path": semantic_path,
"annotation_format": "Segmentation mask 1.1",
"expected_shape_count": None,
},
]
with make_client(args.server, credentials=(args.username, password)) as client:
client.check_server_version(fail_if_unsupported=True)
task_records = [
_ensure_task(
client,
name=task_spec["name"],
labels=task_spec["labels"],
images_path=images_path,
annotation_path=task_spec["annotation_path"],
annotation_format=task_spec["annotation_format"],
expected_shape_count=task_spec["expected_shape_count"],
)
for task_spec in task_specs
]
report = {
"schema_version": "missioncore.lab-e2-cvat-import/v1",
"created_at_utc": datetime.now(UTC).isoformat(timespec="milliseconds").replace(
"+00:00", "Z"
),
"server": args.server,
"cvat_version": "v2.70.0",
"workspace_id": WORKSPACE_ID,
"evaluation_pack_id": EVALUATION_PACK_ID,
"workspace_manifest_sha256": _sha256(manifest_path),
"ground_truth": False,
"inputs": {
"images_zip_sha256": _sha256(images_path),
"instance_coco_zip_sha256": _sha256(instance_path),
"semantic_mask_zip_sha256": _sha256(semantic_path),
},
"tasks": task_records,
"next_gate": (
"two-pass human review and reviewed export; "
"do not treat drafts as accuracy evidence"
),
}
args.report.parent.mkdir(parents=True, exist_ok=True)
args.report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -Eeuo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
readonly FREE_GIB_FLOOR="${FREE_GIB_FLOOR:-360}"
readonly WINDOWS_ROOT="/mnt/d/NDC_MISSIONCORE"
readonly COMPUTE_ROOT="${WINDOWS_ROOT}/workspace/mission-core-compute"
readonly TOOL_ROOT="/srv/mission-core-cvat/tools/e2-import"
readonly SECRET_FILE="${WINDOWS_ROOT}/secrets/cvat/admin.env"
readonly WORKSPACE_ROOT="${WINDOWS_ROOT}/runtime/annotation/imports/LAB-E2/annotation-workspace-9a950d1c37d56dc12cc285b13c5addd7795285879cbcb1fbb2d5811c3c69821a"
readonly REPORT_ROOT="${WINDOWS_ROOT}/runtime/annotation/cvat/reports"
if [[ "${EUID}" -ne 0 ]]; then
echo "import_e2_workspace.sh must run as root" >&2
exit 2
fi
free_gib() {
df -BG --output=avail "${WINDOWS_ROOT}" | tail -n 1 | tr -dc '0-9'
}
guard_disk() {
local stage="$1"
local free
free="$(free_gib)"
printf 'disk_guard stage=%s free_gib=%s floor_gib=%s\n' \
"${stage}" "${free}" "${FREE_GIB_FLOOR}"
if (( free < FREE_GIB_FLOOR )); then
echo "D: free-space floor crossed; refusing further writes" >&2
exit 3
fi
}
wait_for_cvat() {
local attempt http_code
for attempt in $(seq 1 60); do
http_code="$(
curl --silent --output /dev/null --write-out '%{http_code}' \
"http://localhost:8080/api/server/about" || true
)"
if [[ "${http_code}" == "200" ]]; then
http_code="$(
curl --silent --output /dev/null --write-out '%{http_code}' \
"http://localhost:8080/api/tasks" || true
)"
if [[ "${http_code}" == "200" || "${http_code}" == "401" || "${http_code}" == "403" ]]; then
printf 'cvat_ready attempt=%s tasks_http=%s\n' "${attempt}" "${http_code}"
return 0
fi
fi
sleep 2
done
echo "CVAT API did not become ready within 120 seconds" >&2
return 1
}
test -f "${SECRET_FILE}"
test -f "${WORKSPACE_ROOT}/manifest.json"
test -f "${COMPUTE_ROOT}/cvat/import_e2_workspace.py"
guard_disk preflight
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y python3-venv
install -d -m 0750 "${TOOL_ROOT}" "${REPORT_ROOT}"
if [[ ! -x "${TOOL_ROOT}/venv/bin/python" ]]; then
python3 -m venv "${TOOL_ROOT}/venv"
"${TOOL_ROOT}/venv/bin/pip" install --disable-pip-version-check \
"cvat-sdk==2.70.0"
fi
set -a
# shellcheck disable=SC1090
. "${SECRET_FILE}"
set +a
wait_for_cvat
report="${REPORT_ROOT}/lab-e2-cvat-import-$(date -u +%Y%m%dT%H%M%SZ).json"
"${TOOL_ROOT}/venv/bin/python" "${COMPUTE_ROOT}/cvat/import_e2_workspace.py" \
--server "http://localhost:8080" \
--username "${CVAT_ADMIN_USERNAME}" \
--password-env CVAT_ADMIN_PASSWORD \
--workspace "${WORKSPACE_ROOT}" \
--report "${report}"
guard_disk imported
printf 'import_report=%s\n' "${report}"
@@ -0,0 +1,179 @@
from __future__ import annotations
import argparse
import hashlib
import json
import os
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from typing import Any
from cvat_sdk import make_client
from import_e2_workspace import _ensure_task, _label_specs, _sha256
WORKSPACE_SCHEMA = "missioncore.lab-e3-cvat-review-workspace/v1"
IDENTITY_SCHEMA = "missioncore.lab-e3-cvat-review-identity/v1"
EXPECTED_PACK_ID = (
"evaluation-pack-"
"7a983bba75d46c7c260252cb2d461e1384dcb92cda9e164397e841e6ebb37789"
)
EXPECTED_RESULT_ID = (
"e3-segmentation-"
"01bd497c44c2b940add145ec784d3418010327bce0baddd4420b0925317e8a16"
)
EXPECTED_FRAMES = 64
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _valid_sha256(value: object) -> bool:
return (
isinstance(value, str)
and len(value) == 64
and all(character in "0123456789abcdef" for character in value)
)
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def _safe_artifact(root: Path, encoded: object) -> Path:
if not isinstance(encoded, str):
raise RuntimeError("artifact path is not a string")
relative = PurePosixPath(encoded)
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
raise RuntimeError("artifact path is unsafe")
path = root.joinpath(*relative.parts).resolve(strict=True)
if not path.is_file() or not path.is_relative_to(root):
raise RuntimeError("artifact path escaped its root")
return path
def _workspace(root: Path, images_zip: Path) -> tuple[dict[str, Any], dict[str, Path]]:
manifest_path = root / "manifest.json"
manifest = _read_object(manifest_path)
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
if (
manifest.get("schema_version") != WORKSPACE_SCHEMA
or manifest.get("ground_truth") is not False
or not isinstance(identity, dict)
or identity.get("schema_version") != IDENTITY_SCHEMA
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("workspace_id") != f"e3-cvat-review-{identity_sha256}"
or root.name != manifest.get("workspace_id")
or identity.get("evaluation_pack_id") != EXPECTED_PACK_ID
or identity.get("e3_result_id") != EXPECTED_RESULT_ID
or identity.get("frame_count") != EXPECTED_FRAMES
or _sha256(images_zip) != identity.get("images_zip_sha256")
):
raise RuntimeError("LAB E3 CVAT review workspace is incompatible")
artifacts: dict[str, Path] = {}
descriptors = manifest.get("artifacts")
if not isinstance(descriptors, list):
raise RuntimeError("LAB E3 CVAT artifact list is invalid")
for descriptor in descriptors:
if not isinstance(descriptor, dict):
raise RuntimeError("LAB E3 CVAT artifact descriptor is invalid")
path = _safe_artifact(root, descriptor.get("path"))
if (
path.stat().st_size != descriptor.get("bytes")
or not _valid_sha256(descriptor.get("sha256"))
or _sha256(path) != descriptor["sha256"]
):
raise RuntimeError(f"LAB E3 CVAT artifact changed: {path.name}")
artifacts[path.name] = path
if set(artifacts) != {
"labels.json",
"control-fisheye-mask.zip",
"challenger-kb4-cubemap5.zip",
}:
raise RuntimeError("LAB E3 CVAT artifact set changed")
return manifest, artifacts
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--server", required=True)
parser.add_argument("--username", required=True)
parser.add_argument("--password-env", required=True)
parser.add_argument("--workspace", type=Path, required=True)
parser.add_argument("--images-zip", type=Path, required=True)
parser.add_argument("--report", type=Path, required=True)
args = parser.parse_args()
password = os.environ.get(args.password_env)
if not password:
raise RuntimeError(f"Password environment variable {args.password_env!r} is empty")
workspace = args.workspace.resolve(strict=True)
images_zip = args.images_zip.resolve(strict=True)
manifest, artifacts = _workspace(workspace, images_zip)
labels_document = _read_object(artifacts["labels.json"])
semantic_labels = labels_document.get("semantic_task")
if not isinstance(semantic_labels, list):
raise RuntimeError("semantic labels are absent")
labels = _label_specs(semantic_labels)
task_specs = (
{
"name": "LAB E3 | K1 | EoMT fisheye control | pack 7a983bba",
"annotation": artifacts["control-fisheye-mask.zip"],
"role": "control",
},
{
"name": "LAB E3 | K1 | EoMT KB4 cubemap5 challenger | pack 7a983bba",
"annotation": artifacts["challenger-kb4-cubemap5.zip"],
"role": "challenger",
},
)
with make_client(args.server, credentials=(args.username, password)) as client:
client.check_server_version(fail_if_unsupported=True)
tasks = []
for spec in task_specs:
record = _ensure_task(
client,
name=spec["name"],
labels=labels,
images_path=images_zip,
annotation_path=spec["annotation"],
annotation_format="Segmentation mask 1.1",
expected_shape_count=None,
)
record["role"] = spec["role"]
tasks.append(record)
report = {
"schema_version": "missioncore.lab-e3-cvat-import/v1",
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"server": args.server,
"cvat_version": "v2.70.0",
"workspace_id": manifest["workspace_id"],
"workspace_manifest_sha256": _sha256(workspace / "manifest.json"),
"evaluation_pack_id": EXPECTED_PACK_ID,
"e3_result_id": EXPECTED_RESULT_ID,
"ground_truth": False,
"priority_image_ids": manifest["identity"]["priority_image_ids"],
"tasks": tasks,
"next_gate": "two-pass human review and reviewed export",
}
args.report.parent.mkdir(parents=True, exist_ok=True)
args.report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
set -Eeuo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
readonly FREE_GIB_FLOOR="${FREE_GIB_FLOOR:-360}"
readonly WINDOWS_ROOT="/mnt/d/NDC_MISSIONCORE"
readonly COMPUTE_ROOT="${WINDOWS_ROOT}/workspace/mission-core-compute"
readonly TOOL_ROOT="/srv/mission-core-cvat/tools/e2-import"
readonly SECRET_FILE="${WINDOWS_ROOT}/secrets/cvat/admin.env"
readonly WORKSPACE_ID="e3-cvat-review-ca599521e345446ca8e9af5a9013062099278e7317f83ff89740c6b092ddc52f"
readonly WORKSPACE_ROOT="${WINDOWS_ROOT}/runtime/annotation/imports/LAB-E3/${WORKSPACE_ID}"
readonly E2_WORKSPACE_ID="annotation-workspace-9a950d1c37d56dc12cc285b13c5addd7795285879cbcb1fbb2d5811c3c69821a"
readonly IMAGES_ZIP="${WINDOWS_ROOT}/runtime/annotation/imports/LAB-E2/${E2_WORKSPACE_ID}/cvat/images.zip"
readonly REPORT_ROOT="${WINDOWS_ROOT}/runtime/annotation/cvat/reports"
if [[ "${EUID}" -ne 0 ]]; then
echo "import_e3_review.sh must run as root" >&2
exit 2
fi
free_gib() {
df -BG --output=avail "${WINDOWS_ROOT}" | tail -n 1 | tr -dc '0-9'
}
guard_disk() {
local stage="$1"
local free
free="$(free_gib)"
printf 'disk_guard stage=%s free_gib=%s floor_gib=%s\n' \
"${stage}" "${free}" "${FREE_GIB_FLOOR}"
if (( free < FREE_GIB_FLOOR )); then
echo "D: free-space floor crossed; refusing further writes" >&2
exit 3
fi
}
wait_for_cvat() {
local attempt http_code
for attempt in $(seq 1 60); do
http_code="$(
curl --silent --output /dev/null --write-out '%{http_code}' \
"http://localhost:8080/api/server/about" || true
)"
if [[ "${http_code}" == "200" ]]; then
printf 'cvat_ready attempt=%s\n' "${attempt}"
return 0
fi
sleep 2
done
echo "CVAT API did not become ready within 120 seconds" >&2
return 1
}
test -x "${TOOL_ROOT}/venv/bin/python"
test -f "${SECRET_FILE}"
test -f "${WORKSPACE_ROOT}/manifest.json"
test -f "${IMAGES_ZIP}"
test -f "${COMPUTE_ROOT}/cvat/import_e2_workspace.py"
test -f "${COMPUTE_ROOT}/cvat/import_e3_review.py"
guard_disk preflight
set -a
# shellcheck disable=SC1090
. "${SECRET_FILE}"
set +a
wait_for_cvat
report="${REPORT_ROOT}/lab-e3-cvat-import-$(date -u +%Y%m%dT%H%M%SZ).json"
"${TOOL_ROOT}/venv/bin/python" "${COMPUTE_ROOT}/cvat/import_e3_review.py" \
--server "http://localhost:8080" \
--username "${CVAT_ADMIN_USERNAME}" \
--password-env CVAT_ADMIN_PASSWORD \
--workspace "${WORKSPACE_ROOT}" \
--images-zip "${IMAGES_ZIP}" \
--report "${report}"
guard_disk imported
printf 'import_report=%s\n' "${report}"
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -Eeuo pipefail
readonly SSH_ALIAS="mission-gpu"
readonly LOCAL_PORT="${CVAT_LOCAL_PORT:-18080}"
readonly REMOTE_WSL_PORT="18080"
readonly LINUX_START_SCRIPT='/mnt/d/NDC_MISSIONCORE/workspace/mission-core-compute/cvat/start_cvat_runtime.sh'
if curl --max-time 2 --fail --silent \
"http://localhost:${LOCAL_PORT}/api/server/about" >/dev/null 2>&1; then
printf 'cvat_tunnel_ready url=http://localhost:%s\n' "${LOCAL_PORT}"
exit 0
fi
if command -v lsof >/dev/null \
&& lsof -nP -iTCP:"${LOCAL_PORT}" -sTCP:LISTEN >/dev/null 2>&1; then
echo "Local port ${LOCAL_PORT} is already occupied by a non-responsive process" >&2
exit 2
fi
runtime_command="wsl.exe -d MissionCore-CVAT -u root -- bash ${LINUX_START_SCRIPT} --keepalive"
background=false
if [[ "${1:-}" == "--background" ]]; then
background=true
ssh -f "${SSH_ALIAS}" "${runtime_command}"
else
ssh "${SSH_ALIAS}" "${runtime_command}" &
runtime_ssh_pid=$!
cleanup() {
if [[ -n "${tunnel_ssh_pid:-}" ]]; then
kill "${tunnel_ssh_pid}" 2>/dev/null || true
fi
kill "${runtime_ssh_pid}" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
fi
for attempt in $(seq 1 30); do
cvat_ip="$(
ssh "${SSH_ALIAS}" \
"wsl.exe -d MissionCore-CVAT -u root -- hostname -I" \
| tr -d '\r' | awk '{print $1}'
)"
if [[ "${cvat_ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
break
fi
sleep 1
done
if [[ ! "${cvat_ip:-}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "MissionCore-CVAT did not return a valid WSL address" >&2
if [[ "${background}" == false ]]; then
kill "${runtime_ssh_pid}" 2>/dev/null || true
fi
exit 3
fi
forward=(
-N
-L "${LOCAL_PORT}:${cvat_ip}:${REMOTE_WSL_PORT}"
-o ExitOnForwardFailure=yes
"${SSH_ALIAS}"
)
if [[ "${background}" == true ]]; then
ssh -f "${forward[@]}"
else
ssh "${forward[@]}" &
tunnel_ssh_pid=$!
fi
for attempt in $(seq 1 60); do
if curl --max-time 2 --fail --silent \
"http://localhost:${LOCAL_PORT}/api/server/about" >/dev/null 2>&1; then
printf 'cvat_tunnel_ready url=http://localhost:%s wsl_ip=%s\n' \
"${LOCAL_PORT}" "${cvat_ip}"
if [[ "${background}" == true ]]; then
exit 0
fi
wait "${tunnel_ssh_pid}"
tunnel_status=$?
kill "${runtime_ssh_pid}" 2>/dev/null || true
exit "${tunnel_status}"
fi
sleep 2
done
echo "CVAT tunnel did not become ready within 120 seconds" >&2
if [[ "${background}" == false ]]; then
kill "${tunnel_ssh_pid}" "${runtime_ssh_pid}" 2>/dev/null || true
fi
exit 4
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Do not inherit Windows executables into this D-only runtime. In particular,
# Docker Desktop's docker.exe belongs to a different image store.
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
readonly CVAT_TAG="${CVAT_TAG:-v2.70.0}"
readonly FREE_GIB_FLOOR="${FREE_GIB_FLOOR:-360}"
readonly WINDOWS_ROOT="/mnt/d/NDC_MISSIONCORE"
readonly COMPUTE_ROOT="${WINDOWS_ROOT}/workspace/mission-core-compute"
readonly RUNTIME_ROOT="${WINDOWS_ROOT}/runtime/annotation/cvat"
readonly CVAT_ROOT="${RUNTIME_ROOT}/source/cvat-${CVAT_TAG}"
readonly STATE_ROOT="/srv/mission-core-cvat"
readonly OVERRIDE_FILE="${COMPUTE_ROOT}/cvat/docker-compose.override.yml"
if [[ "${EUID}" -ne 0 ]]; then
echo "provision_cvat_wsl.sh must run as root" >&2
exit 2
fi
free_gib() {
df -BG --output=avail "${WINDOWS_ROOT}" | tail -n 1 | tr -dc '0-9'
}
guard_disk() {
local stage="$1"
local free
free="$(free_gib)"
printf 'disk_guard stage=%s free_gib=%s floor_gib=%s\n' \
"${stage}" "${free}" "${FREE_GIB_FLOOR}"
if (( free < FREE_GIB_FLOOR )); then
echo "D: free-space floor crossed; refusing further writes" >&2
exit 3
fi
}
guard_disk preflight
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y ca-certificates curl git
if ! dpkg-query -W -f='${Status}' docker-ce 2>/dev/null \
| grep -qx 'install ok installed'; then
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# shellcheck disable=SC1091
. /etc/os-release
arch="$(dpkg --print-architecture)"
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu %s stable\n' \
"${arch}" "${VERSION_CODENAME}" >/etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y \
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
fi
systemctl enable --now docker
docker version
docker compose version
guard_disk docker-engine
install -d -m 0750 \
"${STATE_ROOT}/volumes/cvat_db" \
"${STATE_ROOT}/volumes/cvat_data" \
"${STATE_ROOT}/volumes/cvat_keys" \
"${STATE_ROOT}/volumes/cvat_logs" \
"${STATE_ROOT}/volumes/cvat_inmem_db" \
"${STATE_ROOT}/volumes/cvat_events_db" \
"${STATE_ROOT}/volumes/cvat_cache_db" \
"${RUNTIME_ROOT}/source" \
"${RUNTIME_ROOT}/reports"
if [[ ! -d "${CVAT_ROOT}/.git" ]]; then
git clone --depth 1 --branch "${CVAT_TAG}" \
https://github.com/cvat-ai/cvat.git "${CVAT_ROOT}"
fi
test "$(git -C "${CVAT_ROOT}" describe --tags --exact-match)" = "${CVAT_TAG}"
test -f "${OVERRIDE_FILE}"
guard_disk cvat-source
export CVAT_VERSION="${CVAT_TAG}"
export CVAT_HOST="localhost"
export CVAT_HTTP_PORT="8080"
export COMPOSE_PROJECT_NAME="missioncore-cvat"
compose=(
docker compose
--project-directory "${CVAT_ROOT}"
-f "${CVAT_ROOT}/docker-compose.yml"
-f "${OVERRIDE_FILE}"
)
"${compose[@]}" config --quiet
mapfile -t images < <("${compose[@]}" config --images | sort -u)
for image in "${images[@]}"; do
guard_disk "before-pull:${image}"
docker pull "${image}"
guard_disk "after-pull:${image}"
done
"${compose[@]}" up -d --no-build
guard_disk cvat-started
deadline=$((SECONDS + 600))
until curl --fail --silent --show-error http://localhost:8080/api/server/about \
>/dev/null; do
if (( SECONDS >= deadline )); then
"${compose[@]}" ps
echo "CVAT did not become ready within 600 seconds" >&2
exit 4
fi
sleep 5
done
report="${RUNTIME_ROOT}/reports/deployment-$(date -u +%Y%m%dT%H%M%SZ).txt"
{
printf 'cvat_tag=%s\n' "${CVAT_TAG}"
printf 'cvat_commit=%s\n' "$(git -C "${CVAT_ROOT}" rev-parse HEAD)"
printf 'docker_version=%s\n' "$(docker version --format '{{.Server.Version}}')"
printf 'compose_version=%s\n' "$(docker compose version --short)"
printf 'free_gib=%s\n' "$(free_gib)"
printf 'generated_at_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
docker image inspect "${images[@]}" \
--format 'image={{index .RepoTags 0}} id={{.Id}} size={{.Size}}'
"${compose[@]}" ps
} | tee "${report}"
printf 'CVAT ready at http://localhost:8080\nreport=%s\n' "${report}"
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -Eeuo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
readonly FREE_GIB_FLOOR="${FREE_GIB_FLOOR:-360}"
readonly WINDOWS_ROOT="/mnt/d/NDC_MISSIONCORE"
readonly COMPUTE_ROOT="${WINDOWS_ROOT}/workspace/mission-core-compute"
readonly CVAT_ROOT="${WINDOWS_ROOT}/runtime/annotation/cvat/source/cvat-v2.70.0"
readonly OVERRIDE_FILE="${COMPUTE_ROOT}/cvat/docker-compose.override.yml"
if [[ "${EUID}" -ne 0 ]]; then
echo "start_cvat_runtime.sh must run as root" >&2
exit 2
fi
free_gib="$(df -BG --output=avail "${WINDOWS_ROOT}" | tail -n 1 | tr -dc '0-9')"
printf 'disk_guard stage=start free_gib=%s floor_gib=%s\n' \
"${free_gib}" "${FREE_GIB_FLOOR}"
if (( free_gib < FREE_GIB_FLOOR )); then
echo "D: free-space floor crossed; refusing to start CVAT" >&2
exit 3
fi
test -f "${CVAT_ROOT}/docker-compose.yml"
test -f "${OVERRIDE_FILE}"
systemctl start docker
export CVAT_VERSION="v2.70.0"
export CVAT_HOST="localhost"
export CVAT_HTTP_PORT="8080"
export COMPOSE_PROJECT_NAME="missioncore-cvat"
compose=(
docker compose
--project-directory "${CVAT_ROOT}"
-f "${CVAT_ROOT}/docker-compose.yml"
-f "${OVERRIDE_FILE}"
)
"${compose[@]}" up -d --no-build
deadline=$((SECONDS + 180))
while true; do
about_http="$(
curl --silent --output /dev/null --write-out '%{http_code}' \
http://localhost:8080/api/server/about || true
)"
tasks_http="$(
curl --silent --output /dev/null --write-out '%{http_code}' \
http://localhost:8080/api/tasks || true
)"
if [[ "${about_http}" == "200" ]] \
&& [[ "${tasks_http}" == "200" || "${tasks_http}" == "401" || "${tasks_http}" == "403" ]]; then
break
fi
if (( SECONDS >= deadline )); then
"${compose[@]}" ps
echo "CVAT did not become ready within 180 seconds" >&2
exit 4
fi
sleep 2
done
printf 'cvat_ready about_http=%s tasks_http=%s free_gib=%s\n' \
"${about_http}" "${tasks_http}" "${free_gib}"
if [[ "${1:-}" == "--keepalive" ]]; then
exec sleep infinity
fi
@@ -0,0 +1,87 @@
{
"schema_version": "missioncore.e10-integrated-perception-profile/v1",
"mode": "full-session-qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"selection": {
"required_frame_count": 4489,
"required_source_start_frame_index": 0,
"required_source_end_frame_index": 4488,
"minimum_source_span_seconds": 448.0
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.55,
"maximum_cuboid_span_m": 12.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [6.5, 3.5, 4.0]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 3,
"motorcycle": 3,
"person": 3,
"vehicle": 5
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"],
"distance_history_frames": 5
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.0,
"maximum_p95_world_state_age_ms": 175.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 400.0,
"minimum_fresh_semantic_coverage": 0.9,
"minimum_lidar_fused_frames": 3500,
"minimum_accepted_cuboids": 500,
"require_zero_failures": true
}
}
@@ -0,0 +1,81 @@
{
"schema_version": "missioncore.e10-integrated-perception-profile/v1",
"mode": "qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.55,
"maximum_cuboid_span_m": 12.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [6.5, 3.5, 4.0]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 3,
"motorcycle": 3,
"person": 3,
"vehicle": 5
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"],
"distance_history_frames": 5
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"maximum_p95_world_state_age_ms": 175.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 400.0,
"minimum_fresh_semantic_coverage": 0.9,
"minimum_lidar_fused_frames": 450,
"minimum_accepted_cuboids": 100,
"require_zero_failures": true
}
}
@@ -0,0 +1,78 @@
{
"schema_version": "missioncore.e10-integrated-perception-profile/v1",
"mode": "semantic-loss-negative-control",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"semantic_loss": {
"stop_after_completed_results": 20
},
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.55,
"maximum_cuboid_span_m": 12.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [6.5, 3.5, 4.0]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 3,
"motorcycle": 3,
"person": 3,
"vehicle": 5
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"],
"distance_history_frames": 5
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"maximum_p95_world_state_age_ms": 175.0,
"minimum_stale_detector_frames": 450
}
}
@@ -0,0 +1,144 @@
{
"schema_version": "missioncore.e10-integrated-perception-profile/v1",
"mode": "pilot",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.5,
"maximum_cuboid_span_m": 15.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [12.5, 4.0, 4.5]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 4,
"motorcycle": 4,
"person": 4,
"vehicle": 8
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"],
"distance_history_frames": 5
},
"cuboid_completion": {
"mode": "class-prior-amodal-v1",
"failure_policy": "reject",
"classes": {
"person": {
"nominal_size_m": [0.55, 0.55, 1.72],
"minimum_size_m": [0.35, 0.35, 1.3],
"maximum_size_m": [1.2, 1.2, 2.3],
"support_padding_m": [0.12, 0.12, 0.12]
},
"bicycle": {
"nominal_size_m": [1.8, 0.65, 1.5],
"minimum_size_m": [1.2, 0.4, 1.0],
"maximum_size_m": [2.5, 1.2, 2.2],
"support_padding_m": [0.18, 0.12, 0.12]
},
"motorcycle": {
"nominal_size_m": [2.1, 0.8, 1.45],
"minimum_size_m": [1.4, 0.5, 1.0],
"maximum_size_m": [3.0, 1.4, 2.2],
"support_padding_m": [0.2, 0.14, 0.14]
},
"car": {
"nominal_size_m": [4.5, 1.85, 1.55],
"minimum_size_m": [3.2, 1.45, 1.2],
"maximum_size_m": [5.8, 2.4, 2.3],
"support_padding_m": [0.25, 0.18, 0.15]
},
"truck": {
"nominal_size_m": [7.0, 2.5, 3.0],
"minimum_size_m": [4.8, 1.8, 1.8],
"maximum_size_m": [12.5, 3.2, 4.2],
"support_padding_m": [0.35, 0.22, 0.2]
},
"bus": {
"nominal_size_m": [10.5, 2.55, 3.2],
"minimum_size_m": [7.0, 2.1, 2.5],
"maximum_size_m": [13.5, 3.2, 4.2],
"support_padding_m": [0.4, 0.24, 0.2]
}
},
"ground": {
"local_radius_m": 2.5,
"lower_percentile": 8.0,
"maximum_below_support_m": 1.2,
"maximum_above_support_m": 0.15,
"fallback_below_support_m": 0.25
},
"orientation": {
"minimum_anisotropy_ratio": 1.35,
"face_width_switch_fraction": 1.05
},
"temporal": {
"center_alpha": 0.4,
"size_alpha": 0.2,
"yaw_alpha": 0.25,
"maximum_center_innovation_m": 2.0,
"maximum_yaw_innovation_degrees": 55.0,
"maximum_idle_s": 1.0,
"confirmation_hits": 3
},
"minimum_support_coverage_fraction": 0.75
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"maximum_p95_world_state_age_ms": 175.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 400.0,
"minimum_fresh_semantic_coverage": 0.9,
"minimum_lidar_fused_frames": 450,
"minimum_accepted_cuboids": 100,
"require_zero_failures": true
}
}
@@ -0,0 +1,150 @@
{
"schema_version": "missioncore.e10-integrated-perception-profile/v1",
"mode": "full-session-qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"selection": {
"required_frame_count": 4489,
"required_source_start_frame_index": 0,
"required_source_end_frame_index": 4488,
"minimum_source_span_seconds": 448.0
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"association": {
"box_inset": {
"bottom_fraction": 0.02,
"horizontal_fraction": 0.05,
"top_fraction": 0.04
},
"depth_cluster_gap_fraction": 0.06,
"depth_cluster_minimum_gap_m": 0.5,
"group_nms_iou_threshold": 0.5,
"maximum_cuboid_span_m": 15.0,
"maximum_distance_innovation_fraction": 0.25,
"maximum_distance_innovation_m": 1.5,
"maximum_oriented_extent_m": {
"bicycle": [3.5, 2.0, 2.5],
"motorcycle": [3.5, 2.0, 2.5],
"person": [1.5, 1.5, 2.8],
"vehicle": [12.5, 4.0, 4.5]
},
"minimum_cuboid_extent_m": 0.15,
"minimum_support_points": {
"bicycle": 4,
"motorcycle": 4,
"person": 4,
"vehicle": 8
},
"semantic_ids": {
"bicycle": [2],
"motorcycle": [3],
"person": [1],
"vehicle": [4, 5]
},
"spatial_cluster_radius_m": {
"bicycle": 0.9,
"motorcycle": 0.9,
"person": 0.9,
"vehicle": 1.5
},
"vehicle_labels": ["car", "truck", "bus"],
"distance_history_frames": 5
},
"cuboid_completion": {
"mode": "class-prior-amodal-v1",
"failure_policy": "reject",
"classes": {
"person": {
"nominal_size_m": [0.55, 0.55, 1.72],
"minimum_size_m": [0.35, 0.35, 1.3],
"maximum_size_m": [1.2, 1.2, 2.3],
"support_padding_m": [0.12, 0.12, 0.12]
},
"bicycle": {
"nominal_size_m": [1.8, 0.65, 1.5],
"minimum_size_m": [1.2, 0.4, 1.0],
"maximum_size_m": [2.5, 1.2, 2.2],
"support_padding_m": [0.18, 0.12, 0.12]
},
"motorcycle": {
"nominal_size_m": [2.1, 0.8, 1.45],
"minimum_size_m": [1.4, 0.5, 1.0],
"maximum_size_m": [3.0, 1.4, 2.2],
"support_padding_m": [0.2, 0.14, 0.14]
},
"car": {
"nominal_size_m": [4.5, 1.85, 1.55],
"minimum_size_m": [3.2, 1.45, 1.2],
"maximum_size_m": [5.8, 2.4, 2.3],
"support_padding_m": [0.25, 0.18, 0.15]
},
"truck": {
"nominal_size_m": [7.0, 2.5, 3.0],
"minimum_size_m": [4.8, 1.8, 1.8],
"maximum_size_m": [12.5, 3.2, 4.2],
"support_padding_m": [0.35, 0.22, 0.2]
},
"bus": {
"nominal_size_m": [10.5, 2.55, 3.2],
"minimum_size_m": [7.0, 2.1, 2.5],
"maximum_size_m": [13.5, 3.2, 4.2],
"support_padding_m": [0.4, 0.24, 0.2]
}
},
"ground": {
"local_radius_m": 2.5,
"lower_percentile": 8.0,
"maximum_below_support_m": 1.2,
"maximum_above_support_m": 0.15,
"fallback_below_support_m": 0.25
},
"orientation": {
"minimum_anisotropy_ratio": 1.35,
"face_width_switch_fraction": 1.05
},
"temporal": {
"center_alpha": 0.4,
"size_alpha": 0.2,
"yaw_alpha": 0.25,
"maximum_center_innovation_m": 2.0,
"maximum_yaw_innovation_degrees": 55.0,
"maximum_idle_s": 1.0,
"confirmation_hits": 3
},
"minimum_support_coverage_fraction": 0.75
},
"world_state": {
"velocity_history_limit_s": 1.0,
"clearance": {
"sector_count": 72,
"minimum_range_m": 0.5,
"maximum_range_m": 30.0,
"ground_percentile": 5.0,
"minimum_height_above_ground_m": 0.2,
"maximum_height_above_ground_m": 3.0,
"front_half_angle_degrees": 15.0
}
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"maximum_p95_world_state_age_ms": 175.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 400.0,
"minimum_fresh_semantic_coverage": 0.9,
"minimum_lidar_fused_frames": 3500,
"minimum_accepted_cuboids": 1500,
"require_zero_failures": true
}
}
@@ -0,0 +1,51 @@
{
"schema_version": "missioncore.e15-shadow-inference-profile/v1",
"mode": "replay-shadow-gate",
"authority": {
"commands_enabled": false,
"navigation_or_safety_accepted": false
},
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"transport": {
"wire_schema": "missioncore.live-perception-wire/v1",
"camera_media": "persistent-fmp4-pyav",
"pyav_version": "18.0.0",
"maximum_media_buffer_bytes": 8388608,
"camera_metadata_capacity": 16
},
"scheduling": {
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0,
"sensor_wait_ms": 90.0
},
"temporal": {
"binding": "nearest-recorded-host-arrival-best-effort",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0,
"buffer_capacity_per_modality": 32,
"retention_seconds": 3.0,
"clock_qualification": "not-hardware-synchronized"
},
"acceptance": {
"minimum_camera_frames": 140,
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.01,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 400.0,
"minimum_fresh_semantic_coverage": 0.9,
"minimum_fused_fraction": 0.85,
"maximum_p95_decode_age_ms": 80.0,
"maximum_p95_world_state_age_ms": 200.0,
"require_zero_transport_gaps": true,
"require_zero_camera_sequence_gaps": true,
"require_zero_failures": true
}
}
@@ -0,0 +1,283 @@
"""Bounded media/runtime primitives for the LAB E15 shadow inference worker."""
from __future__ import annotations
import threading
import time
from collections import deque
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
import numpy as np
class ShadowRuntimeError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class CameraFragmentMetadata:
ingress_sequence: int
source_sequence: int
captured_at_epoch_ns: int
worker_received_monotonic: float
@dataclass(frozen=True, slots=True)
class DecodedCameraFrame:
frame_index: int
metadata: CameraFragmentMetadata
image: np.ndarray
decoded_monotonic: float
decode_age_ms: float
class IncrementalMediaBuffer:
"""A bounded non-seekable blocking byte source accepted by PyAV."""
def __init__(self, maximum_buffer_bytes: int) -> None:
if maximum_buffer_bytes < 1024:
raise ValueError("media buffer bound is too small")
self._maximum_buffer_bytes = maximum_buffer_bytes
self._buffer = bytearray()
self._condition = threading.Condition()
self._finished = False
self._failure: BaseException | None = None
self._bytes_published = 0
self._bytes_read = 0
self._maximum_depth = 0
def readable(self) -> bool:
return True
def read(self, size: int = -1) -> bytes:
with self._condition:
self._condition.wait_for(
lambda: bool(self._buffer) or self._finished or self._failure is not None
)
if self._failure is not None:
raise ShadowRuntimeError("incremental media source failed") from self._failure
if not self._buffer:
return b""
count = len(self._buffer) if size < 0 else min(size, len(self._buffer))
result = bytes(self._buffer[:count])
del self._buffer[:count]
self._bytes_read += count
self._condition.notify_all()
return result
def append(self, payload: bytes) -> None:
if not payload:
raise ShadowRuntimeError("empty fMP4 payload is not admitted")
with self._condition:
if self._finished or self._failure is not None:
raise ShadowRuntimeError("incremental media source is closed")
if len(self._buffer) + len(payload) > self._maximum_buffer_bytes:
failure = ShadowRuntimeError("incremental media buffer exceeded its hard bound")
self._failure = failure
self._condition.notify_all()
raise failure
self._buffer.extend(payload)
self._bytes_published += len(payload)
self._maximum_depth = max(self._maximum_depth, len(self._buffer))
self._condition.notify_all()
def finish(self) -> None:
with self._condition:
self._finished = True
self._condition.notify_all()
def fail(self, failure: BaseException) -> None:
with self._condition:
if self._failure is None:
self._failure = failure
self._condition.notify_all()
def snapshot(self) -> dict[str, int | bool]:
with self._condition:
return {
"maximum_buffer_bytes": self._maximum_buffer_bytes,
"depth_bytes": len(self._buffer),
"maximum_depth_bytes": self._maximum_depth,
"bytes_published": self._bytes_published,
"bytes_read": self._bytes_read,
"finished": self._finished,
"failed": self._failure is not None,
}
class CameraMetadataQueue:
"""Fail-closed segment metadata queue; dropping a fragment would corrupt H.264."""
def __init__(self, capacity: int) -> None:
if capacity < 2:
raise ValueError("camera metadata capacity is too small")
self._capacity = capacity
self._items: deque[CameraFragmentMetadata] = deque()
self._condition = threading.Condition()
self._finished = False
self._published = 0
self._consumed = 0
self._maximum_depth = 0
def publish(self, value: CameraFragmentMetadata) -> None:
with self._condition:
if self._finished:
raise ShadowRuntimeError("camera metadata queue is closed")
if len(self._items) >= self._capacity:
raise ShadowRuntimeError("camera metadata queue exceeded its hard bound")
self._items.append(value)
self._published += 1
self._maximum_depth = max(self._maximum_depth, len(self._items))
self._condition.notify_all()
def take(self) -> CameraFragmentMetadata:
with self._condition:
self._condition.wait_for(lambda: bool(self._items) or self._finished)
if not self._items:
raise ShadowRuntimeError("decoder emitted a frame without segment metadata")
self._consumed += 1
return self._items.popleft()
def finish(self) -> None:
with self._condition:
self._finished = True
self._condition.notify_all()
def snapshot(self) -> dict[str, int | bool]:
with self._condition:
return {
"capacity": self._capacity,
"depth": len(self._items),
"maximum_depth": self._maximum_depth,
"published": self._published,
"consumed": self._consumed,
"finished": self._finished,
}
class PersistentFmp4Decoder:
"""Decode one committed fMP4 epoch incrementally without writing frames."""
def __init__(
self,
*,
on_frame: Callable[[DecodedCameraFrame], None],
width: int = 800,
height: int = 600,
maximum_buffer_bytes: int = 8 * 1024 * 1024,
metadata_capacity: int = 16,
) -> None:
if width < 1 or height < 1:
raise ValueError("decoder resolution is invalid")
self._on_frame = on_frame
self._width = width
self._height = height
self._media = IncrementalMediaBuffer(maximum_buffer_bytes)
self._metadata = CameraMetadataQueue(metadata_capacity)
self._thread = threading.Thread(
target=self._decode,
name="lab-e15-fmp4-decoder",
daemon=True,
)
self._started = False
self._init_seen = False
self._last_source_sequence: int | None = None
self._decoded_frames = 0
self._failure: BaseException | None = None
def start(self) -> None:
if self._started:
raise ShadowRuntimeError("fMP4 decoder was already started")
self._started = True
self._thread.start()
def feed_init(self, payload: bytes) -> None:
if not self._started or self._init_seen or self._last_source_sequence is not None:
raise ShadowRuntimeError("camera init ordering is invalid")
self._init_seen = True
self._media.append(payload)
def feed_segment(self, metadata: CameraFragmentMetadata, payload: bytes) -> None:
if not self._init_seen:
raise ShadowRuntimeError("camera media arrived before init")
expected = 1 if self._last_source_sequence is None else self._last_source_sequence + 1
if metadata.source_sequence != expected:
raise ShadowRuntimeError(
f"camera source sequence gap: expected {expected}, got {metadata.source_sequence}"
)
self._metadata.publish(metadata)
try:
self._media.append(payload)
except BaseException as exc:
self._media.fail(exc)
self._metadata.finish()
raise
self._last_source_sequence = metadata.source_sequence
def finish_input(self) -> None:
self._media.finish()
self._metadata.finish()
def join(self, timeout_seconds: float = 30.0) -> None:
self._thread.join(timeout=timeout_seconds)
if self._thread.is_alive():
raise ShadowRuntimeError("persistent fMP4 decoder did not stop")
if self._failure is not None:
raise ShadowRuntimeError("persistent fMP4 decoder failed") from self._failure
metadata = self._metadata.snapshot()
if metadata["published"] != metadata["consumed"] or metadata["depth"] != 0:
raise ShadowRuntimeError("camera segment/frame accounting differs")
def snapshot(self) -> dict[str, Any]:
return {
"init_seen": self._init_seen,
"last_source_sequence": self._last_source_sequence,
"decoded_frames": self._decoded_frames,
"failed": self._failure is not None,
"media": self._media.snapshot(),
"metadata": self._metadata.snapshot(),
}
def _decode(self) -> None:
try:
import av
container = av.open(
self._media,
mode="r",
format="mp4",
options={"probesize": "32", "analyzeduration": "0"},
)
try:
for frame in container.decode(video=0):
metadata = self._metadata.take()
image = frame.to_ndarray(format="rgb24")
if image.shape != (self._height, self._width, 3):
raise ShadowRuntimeError("decoded camera resolution changed")
image.setflags(write=False)
decoded_monotonic = time.perf_counter()
self._on_frame(
DecodedCameraFrame(
frame_index=self._decoded_frames,
metadata=metadata,
image=image,
decoded_monotonic=decoded_monotonic,
decode_age_ms=max(
0.0,
(
decoded_monotonic
- metadata.worker_received_monotonic
)
* 1000,
),
)
)
self._decoded_frames += 1
finally:
container.close()
except BaseException as exc:
self._failure = exc
self._media.fail(exc)
self._metadata.finish()
@@ -0,0 +1,145 @@
{
"schema_version": "missioncore.k1-e3-rectified-segmentation-profile/v1",
"profile_id": "k1-camera1-kb4-cubemap5-eomt-cityscapes/v1",
"source": {
"source_id": "sensor.camera.right",
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9",
"resolution": [
800,
600
],
"intrinsic_fx_fy_cx_cy": [
194.59817287616025,
194.57531427932872,
396.31861150187996,
301.49644357408005
],
"distortion_kb4": [
-0.023164451386679667,
-0.0014974198594105452,
-0.001039213149441563,
-0.000035237331915978814
]
},
"variants": [
"eomt-fisheye-mask",
"eomt-fisheye-mask-clahe",
"eomt-kb4-cubemap5-clahe"
],
"rectification": {
"projection": "five-perspective-gnomonic/v1",
"tile_size": 768,
"horizontal_fov_degrees": 100.0,
"vertical_fov_degrees": 100.0,
"tile_selection": "maximum-optical-axis-cosine/v1",
"rgb_sampling": "opencv-remap-linear",
"label_sampling": "nearest",
"tiles": [
{
"name": "front",
"yaw_degrees": 0.0,
"pitch_degrees": 0.0
},
{
"name": "left",
"yaw_degrees": -90.0,
"pitch_degrees": 0.0
},
{
"name": "right",
"yaw_degrees": 90.0,
"pitch_degrees": 0.0
},
{
"name": "up",
"yaw_degrees": 0.0,
"pitch_degrees": 90.0
},
{
"name": "down",
"yaw_degrees": 0.0,
"pitch_degrees": -90.0
}
]
},
"contrast": {
"method": "clahe-lab-luminance",
"clip_limit": 2.0,
"tile_grid_size": [
8,
8
]
},
"model": {
"id": "tue-mps/cityscapes_semantic_eomt_large_1024",
"revision": "8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f",
"architecture": "EomtForUniversalSegmentation",
"precision": "fp16-autocast",
"batch_size": 1,
"files": {
"config.json": {
"bytes": 1575,
"sha256": "7f4aa94fa4e43c0dbd79a5420edb511120aef62bd82bfbcbcece79948286a650"
},
"preprocessor_config.json": {
"bytes": 666,
"sha256": "97e2fbf7f0bdba2cfc90251c5133bae9c27ddc9c4410509f40670be2332854e7"
},
"model.safetensors": {
"bytes": 1276175488,
"sha256": "c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782"
}
}
},
"opencv": {
"distribution": "opencv-python-headless",
"version": "4.13.0.92",
"runtime_version": "4.13.0",
"wheel": {
"filename": "opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
"bytes": 56016764,
"sha256": "0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22",
"url": "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
}
},
"target_taxonomy": {
"0": "outside_valid_fov",
"1": "person",
"2": "bicycle",
"3": "motorcycle",
"4": "car",
"5": "heavy_vehicle",
"6": "building_structure",
"7": "paved_road",
"8": "sidewalk_curb",
"9": "ground_dirt",
"10": "grass_low_vegetation",
"11": "tree_woody_vegetation",
"12": "sky",
"13": "static_obstacle",
"14": "animal",
"15": "other_background"
},
"cityscapes_to_target": {
"road": 7,
"sidewalk": 8,
"building": 6,
"wall": 6,
"fence": 13,
"pole": 13,
"traffic light": 13,
"traffic sign": 13,
"vegetation": 11,
"terrain": 10,
"sky": 12,
"person": 1,
"rider": 1,
"car": 4,
"truck": 5,
"bus": 5,
"train": 5,
"motorcycle": 3,
"bicycle": 2
}
}
@@ -0,0 +1,53 @@
{
"schema_version": "missioncore.e5-tracking-profile/v1",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"model": {
"id": "yolox_s",
"version": 1,
"architecture": "YOLOX-S",
"source": "Megvii-BaseDetection/YOLOX release 0.1.1rc0",
"license": "Apache-2.0",
"model_sha256": "c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063",
"config_sha256": "5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604",
"input_name": "images",
"output_name": "output",
"input_shape": [1, 3, 640, 640],
"classes": "COCO-80"
},
"preprocessing": {
"color_order": "BGR",
"resize": "bilinear-letterbox-top-left",
"pad_value": 114,
"valid_fov_fill_value": 114
},
"detection": {
"minimum_score": 0.1,
"nms_iou_threshold": 0.45,
"nms_containment_threshold": 0.8,
"target_class_ids": [0, 1, 2, 3, 5, 7],
"minimum_box_area_pixels": 64.0,
"maximum_box_area_fraction": 0.5,
"minimum_valid_fov_fraction": 0.5,
"require_center_inside_valid_fov": true
},
"tracking": {
"algorithm": "bytetrack-style-two-stage-iou/v1",
"high_score_threshold": 0.25,
"new_track_threshold": 0.25,
"primary_match_iou": 0.2,
"secondary_match_iou": 0.1,
"lost_track_buffer_frames": 15,
"minimum_confirmed_hits": 2,
"assignment_solver": "scipy-linear-sum-assignment"
},
"overlay": {
"line_width": 3,
"trail_length": 24,
"show_unconfirmed": false
}
}
@@ -0,0 +1,64 @@
{
"schema_version": "missioncore.e8-realtime-tracking-profile/v1",
"mode": "overload-negative-control",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"model": {
"id": "yolox_s",
"version": 1,
"architecture": "YOLOX-S",
"source": "Megvii-BaseDetection/YOLOX release 0.1.1rc0",
"license": "Apache-2.0",
"model_sha256": "c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063",
"config_sha256": "5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604",
"input_name": "images",
"output_name": "output",
"input_shape": [1, 3, 640, 640],
"classes": "COCO-80"
},
"preprocessing": {
"color_order": "BGR",
"resize": "bilinear-letterbox-top-left",
"pad_value": 114,
"valid_fov_fill_value": 114
},
"detection": {
"minimum_score": 0.1,
"nms_iou_threshold": 0.45,
"nms_containment_threshold": 0.8,
"target_class_ids": [0, 1, 2, 3, 5, 7],
"minimum_box_area_pixels": 64.0,
"maximum_box_area_fraction": 0.5,
"minimum_valid_fov_fraction": 0.5,
"require_center_inside_valid_fov": true
},
"tracking": {
"algorithm": "bytetrack-style-two-stage-iou/v1",
"high_score_threshold": 0.25,
"new_track_threshold": 0.25,
"primary_match_iou": 0.2,
"secondary_match_iou": 0.1,
"lost_track_buffer_frames": 15,
"minimum_confirmed_hits": 2,
"assignment_solver": "scipy-linear-sum-assignment"
},
"realtime": {
"queue_policy": "bounded-latest-wins",
"queue_capacity": 2,
"speed": 1.0,
"consumer_delay_ms": 140.0,
"stale_after_ms": 150.0,
"unavailable_after_ms": 500.0
},
"acceptance": {
"minimum_effective_fps": 0.0,
"maximum_drop_fraction": 1.0,
"maximum_p95_result_age_ms": 5000.0,
"require_zero_failures": true,
"expect_overload": true
}
}
@@ -0,0 +1,64 @@
{
"schema_version": "missioncore.e8-realtime-tracking-profile/v1",
"mode": "qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"model": {
"id": "yolox_s",
"version": 1,
"architecture": "YOLOX-S",
"source": "Megvii-BaseDetection/YOLOX release 0.1.1rc0",
"license": "Apache-2.0",
"model_sha256": "c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063",
"config_sha256": "5795c737a7935a655961b069e8404d336d891f9762fb6dffb93956a076479604",
"input_name": "images",
"output_name": "output",
"input_shape": [1, 3, 640, 640],
"classes": "COCO-80"
},
"preprocessing": {
"color_order": "BGR",
"resize": "bilinear-letterbox-top-left",
"pad_value": 114,
"valid_fov_fill_value": 114
},
"detection": {
"minimum_score": 0.1,
"nms_iou_threshold": 0.45,
"nms_containment_threshold": 0.8,
"target_class_ids": [0, 1, 2, 3, 5, 7],
"minimum_box_area_pixels": 64.0,
"maximum_box_area_fraction": 0.5,
"minimum_valid_fov_fraction": 0.5,
"require_center_inside_valid_fov": true
},
"tracking": {
"algorithm": "bytetrack-style-two-stage-iou/v1",
"high_score_threshold": 0.25,
"new_track_threshold": 0.25,
"primary_match_iou": 0.2,
"secondary_match_iou": 0.1,
"lost_track_buffer_frames": 15,
"minimum_confirmed_hits": 2,
"assignment_solver": "scipy-linear-sum-assignment"
},
"realtime": {
"queue_policy": "bounded-latest-wins",
"queue_capacity": 2,
"speed": 1.0,
"consumer_delay_ms": 0.0,
"stale_after_ms": 150.0,
"unavailable_after_ms": 500.0
},
"acceptance": {
"minimum_effective_fps": 9.5,
"maximum_drop_fraction": 0.005,
"maximum_p95_result_age_ms": 100.0,
"require_zero_failures": true,
"expect_overload": false
}
}
@@ -0,0 +1,27 @@
{
"schema_version": "missioncore.e9-multirate-perception-profile/v1",
"mode": "qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"detector_maximum_p95_result_age_ms": 100.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 350.0,
"minimum_fresh_semantic_coverage": 0.9,
"require_zero_failures": true
}
}
@@ -0,0 +1,27 @@
{
"schema_version": "missioncore.e9-multirate-perception-profile/v1",
"mode": "qualification",
"source": {
"source_id": "sensor.camera.right",
"resolution": [800, 600],
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"replay": {
"speed": 1.0,
"detector_queue_capacity": 2,
"semantic_queue_capacity": 1,
"semantic_sample_every_frames": 5,
"semantic_ttl_ms": 750.0
},
"acceptance": {
"detector_minimum_effective_fps": 9.5,
"detector_maximum_drop_fraction": 0.005,
"detector_maximum_p95_result_age_ms": 150.0,
"semantic_minimum_effective_fps": 1.8,
"semantic_maximum_drop_fraction": 0.05,
"semantic_maximum_p95_completion_age_ms": 350.0,
"minimum_fresh_semantic_coverage": 0.9,
"require_zero_failures": true
}
}
@@ -0,0 +1,976 @@
#!/usr/bin/env python3
"""Run camera inference, LiDAR fusion and world-state publication in one paced loop."""
from __future__ import annotations
import argparse
import hashlib
import importlib.metadata
import json
import os
import platform
import resource
import shutil
import threading
import time
from collections import Counter
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
import run_e9_multirate_perception as e9
from e10_fusion_runtime import (
CuboidCompletionTracker,
LidarReplayPack,
WorldStateProjector,
_completion_profile,
canonical_json,
clearance,
distance_history,
fuse_tracks,
fusion_document,
project_points,
sha256,
)
from run_e4_full_session_segmentation import TARGET_CLASS_COUNT, _dependency_manifest, _load_model
from run_e4_full_session_segmentation import _profile as read_semantic_profile
from run_e4_full_session_segmentation import _validate_source as validate_semantic_source
from run_e5_instance_tracking import (
TwoStageTracker,
_detections,
_infer,
_load_valid_fov,
_preprocess,
_read_timeline,
_track_document,
_validate_source,
_verify_model,
)
from run_e8_realtime_tracking import LatestWinsQueue
from run_e8_realtime_tracking import _read_profile as read_detector_profile
from run_recorded_perception_epoch import (
_artifact,
_GpuTelemetry,
_percentiles,
_valid_sha256,
_validate_job,
_write_json,
)
PROFILE_SCHEMA = "missioncore.e10-integrated-perception-profile/v1"
RESULT_SCHEMA = "missioncore.e10-integrated-perception-result/v1"
REPORT_SCHEMA = "missioncore.e10-integrated-perception-report/v1"
IDENTITY_SCHEMA = "missioncore.e10-integrated-perception-identity/v1"
SEMANTIC_SCHEMA = "missioncore.e10-semantic-frame/v1"
FUSION_SCHEMA = "missioncore.e10-fusion-frame/v1"
WORLD_SCHEMA = "missioncore.live-perception-world-state/v1"
PIPELINE_ID = "source-paced-yolox-eomt-kb4-lidar-world-state/v1"
def arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
for name in ("preflight", "run"):
command = commands.add_parser(name)
command.add_argument("--job", type=Path, required=True)
command.add_argument("--profile", type=Path, required=True)
command.add_argument("--detector-profile", type=Path, required=True)
command.add_argument("--semantic-profile", type=Path, required=True)
command.add_argument("--valid-fov-root", type=Path, required=True)
command.add_argument("--model-root", type=Path, required=True)
command.add_argument("--cache", type=Path, required=True)
command.add_argument("--environment", type=Path, required=True)
command.add_argument("--lidar-pack", type=Path, required=True)
if name == "run":
command.add_argument("--frames", type=Path, required=True)
command.add_argument("--timeline", type=Path, required=True)
command.add_argument("--output", type=Path, required=True)
command.add_argument("--triton-url", required=True)
command.add_argument("--free-bytes-floor", type=int, default=0)
command.add_argument("--orchestrator-sha256", required=True)
command.add_argument("--container-image", required=True)
return parser.parse_args()
def read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def read_profile(path: Path) -> tuple[dict[str, Any], str]:
resolved = path.resolve(strict=True)
profile = read_object(resolved)
replay = profile.get("replay")
source = profile.get("source")
association = profile.get("association")
world = profile.get("world_state")
acceptance = profile.get("acceptance")
if (
profile.get("schema_version") != PROFILE_SCHEMA
or profile.get("mode")
not in {
"pilot",
"qualification",
"full-session-qualification",
"semantic-loss-negative-control",
}
or not all(
isinstance(value, dict) for value in (replay, source, association, world, acceptance)
)
or source.get("source_id") != "sensor.camera.right"
or source.get("resolution") != [800, 600]
or set(association.get("semantic_ids", {}))
!= {"person", "bicycle", "motorcycle", "vehicle"}
or not isinstance(world.get("clearance"), dict)
):
raise RuntimeError("LAB E10 profile contract is invalid")
if (
not 0.1 <= float(replay.get("speed", 0)) <= 10
or not 1 <= int(replay.get("detector_queue_capacity", 0)) <= 8
or not 1 <= int(replay.get("semantic_queue_capacity", 0)) <= 4
or not 2 <= int(replay.get("semantic_sample_every_frames", 0)) <= 30
or not 100 <= float(replay.get("semantic_ttl_ms", 0)) <= 5000
):
raise RuntimeError("LAB E10 scheduling contract is invalid")
if profile["mode"] == "semantic-loss-negative-control":
semantic_loss = profile.get("semantic_loss")
if (
not isinstance(semantic_loss, dict)
or not 1 <= int(semantic_loss.get("stop_after_completed_results", 0)) <= 100
or not 1 <= int(acceptance.get("minimum_stale_detector_frames", 0)) <= 1_000_000
):
raise RuntimeError("LAB E10 semantic-loss contract is invalid")
if profile["mode"] == "full-session-qualification":
selection = profile.get("selection")
if (
not isinstance(selection, dict)
or int(selection.get("required_frame_count", 0)) < 2
or int(selection.get("required_source_start_frame_index", -1)) != 0
or int(selection.get("required_source_end_frame_index", -1))
!= int(selection["required_frame_count"]) - 1
or float(selection.get("minimum_source_span_seconds", 0)) <= 0
):
raise RuntimeError("LAB E10 full-session selection contract is invalid")
completion = profile.get("cuboid_completion")
if completion is not None:
if not isinstance(completion, dict):
raise RuntimeError("LAB E13 cuboid completion contract is invalid")
_completion_profile(completion)
return profile, sha256(resolved)
@dataclass(frozen=True, slots=True)
class SemanticResult:
frame_index: int
source_frame_index: int
session_seconds: float
completion_age_ms: float
completed_monotonic: float
mask: np.ndarray
mask_sha256: str
class_pixels: dict[str, int]
class LatestSemantic:
def __init__(self) -> None:
self.lock = threading.Lock()
self.value: SemanticResult | None = None
def publish(self, value: SemanticResult) -> None:
with self.lock:
if self.value is not None and value.frame_index <= self.value.frame_index:
raise RuntimeError("LAB E10 semantic results are not monotonic")
self.value = value
def snapshot(self) -> SemanticResult | None:
with self.lock:
return self.value
def semantic_worker(
*,
queue: LatestWinsQueue,
latest: LatestSemantic,
valid_mask: np.ndarray,
target_lut: np.ndarray,
target_names: dict[int, str],
infer: Any,
latency: dict[str, list[float]],
completed: list[SemanticResult],
failures: list[BaseException],
stop_after_results: int | None,
) -> None:
try:
while (envelope := queue.take()) is not None:
started = time.perf_counter()
latency["queue_wait_ms"].append(max(0.0, (started - envelope.decoded_monotonic) * 1000))
fill_started = time.perf_counter()
model_input = np.where(valid_mask[..., None], envelope.image, 0).astype(np.uint8)
latency["valid_fov_fill_ms"].append((time.perf_counter() - fill_started) * 1000)
semantic, measured = infer(model_input)
if int(semantic.max()) >= len(target_lut):
raise RuntimeError("LAB E10 EoMT emitted an unknown category")
target = target_lut[semantic].copy()
target[~valid_mask] = 0
for name, value in measured.items():
latency[name].append(float(value))
finished = time.perf_counter()
age = max(0.0, (finished - envelope.scheduled_monotonic) * 1000)
latency["completion_age_ms"].append(age)
latency["processing_ms"].append((finished - started) * 1000)
counts = np.bincount(target[valid_mask], minlength=TARGET_CLASS_COUNT)
result = SemanticResult(
frame_index=envelope.frame_index,
source_frame_index=int(envelope.timeline["source_frame_index"]),
session_seconds=float(envelope.timeline["session_seconds"]),
completion_age_ms=age,
completed_monotonic=finished,
mask=target,
mask_sha256=hashlib.sha256(target.tobytes()).hexdigest(),
class_pixels={
target_names[index]: int(counts[index])
for index in range(1, TARGET_CLASS_COUNT)
if int(counts[index]) > 0
},
)
completed.append(result)
latest.publish(result)
if stop_after_results is not None and len(completed) >= stop_after_results:
return
except BaseException as exc:
failures.append(exc)
def semantic_binding(
semantic: SemanticResult | None, frame_seconds: float, ttl_ms: float
) -> tuple[str, float | None]:
if semantic is None:
return "unavailable", None
age = max(0.0, (frame_seconds - semantic.session_seconds) * 1000)
return ("fresh" if age <= ttl_ms else "stale"), age
def preflight(args: argparse.Namespace) -> int:
import torch
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = read_profile(args.profile)
detector, detector_sha256 = read_detector_profile(args.detector_profile)
semantic, semantic_sha256 = read_semantic_profile(args.semantic_profile)
_validate_source(job, detector)
validate_semantic_source(job, semantic)
if profile["source"] != detector["source"]:
raise RuntimeError("LAB E10 detector source binding changed")
if profile["source"] != {
key: semantic["source"][key]
for key in ("source_id", "resolution", "calibration_slot", "calibration_sha256")
}:
raise RuntimeError("LAB E10 semantic source binding changed")
lidar = LidarReplayPack(args.lidar_pack, expected_job_id=job["job_id"])
try:
if lidar.identity["calibration_sha256"] != profile["source"]["calibration_sha256"]:
raise RuntimeError("LAB E10 LiDAR calibration binding changed")
if profile["mode"] == "full-session-qualification":
selection = profile["selection"]
if (
lidar.frame_count != int(selection["required_frame_count"])
or int(lidar.source_frame_indices[0])
!= int(selection["required_source_start_frame_index"])
or int(lidar.source_frame_indices[-1])
!= int(selection["required_source_end_frame_index"])
or float(lidar.session_seconds[-1] - lidar.session_seconds[0])
< float(selection["minimum_source_span_seconds"])
):
raise RuntimeError("LAB E10 full-session LiDAR selection changed")
_load_valid_fov(args.valid_fov_root, job, detector)
detector_files = _verify_model(detector, args.model_root)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != semantic_sha256:
raise RuntimeError("LAB E10 semantic dependency identity changed")
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for LAB E10")
device = torch.device("cuda:0")
processor, model, _labels, _lut, semantic_files = _load_model(
semantic, args.cache.resolve(strict=True), device
)
del processor, model, _labels, _lut
torch.cuda.empty_cache()
print(
json.dumps(
{
"state": "preflight-ready",
"job_id": job["job_id"],
"profile_sha256": profile_sha256,
"detector_profile_sha256": detector_sha256,
"semantic_profile_sha256": semantic_sha256,
"lidar_pack_id": lidar.pack_id,
"detector_files": detector_files,
"semantic_files": semantic_files,
"cuda_device": torch.cuda.get_device_name(),
},
sort_keys=True,
),
flush=True,
)
finally:
lidar.close()
return 0
def run(args: argparse.Namespace) -> int:
import torch
import transformers
from PIL import Image
from scipy.optimize import linear_sum_assignment
if not _valid_sha256(args.orchestrator_sha256):
raise RuntimeError("LAB E10 orchestrator SHA-256 is invalid")
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = read_profile(args.profile)
detector, detector_sha256 = read_detector_profile(args.detector_profile)
semantic, semantic_sha256 = read_semantic_profile(args.semantic_profile)
_validate_source(job, detector)
validate_semantic_source(job, semantic)
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, detector)
detector_files = _verify_model(detector, args.model_root)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != semantic_sha256:
raise RuntimeError("LAB E10 semantic dependency identity changed")
lidar = LidarReplayPack(args.lidar_pack, expected_job_id=job["job_id"])
frames = sorted(args.frames.resolve(strict=True).glob("frame-*.png"))
timeline_path = args.timeline.resolve(strict=True)
timeline = _read_timeline(timeline_path, len(frames))
if (
len(frames) != lidar.frame_count
or [int(row["source_frame_index"]) for row in timeline]
!= lidar.source_frame_indices.tolist()
or not np.allclose(
np.asarray([float(row["session_seconds"]) for row in timeline]),
lidar.session_seconds,
rtol=0,
atol=1e-6,
)
):
lidar.close()
raise RuntimeError("LAB E10 camera and LiDAR replay selections differ")
if profile["mode"] == "full-session-qualification":
selection = profile["selection"]
source_span_seconds = float(timeline[-1]["session_seconds"]) - float(
timeline[0]["session_seconds"]
)
if (
len(frames) != int(selection["required_frame_count"])
or int(timeline[0]["source_frame_index"])
!= int(selection["required_source_start_frame_index"])
or int(timeline[-1]["source_frame_index"])
!= int(selection["required_source_end_frame_index"])
or source_span_seconds < float(selection["minimum_source_span_seconds"])
):
lidar.close()
raise RuntimeError("LAB E10 full-session camera selection changed")
output = args.output.resolve()
if output.exists():
raise RuntimeError("LAB E10 output must be absent")
output.mkdir(mode=0o700, parents=True, exist_ok=False)
assert_disk(output, args.free_bytes_floor, 0)
device = torch.device("cuda:0")
processor, semantic_model, _semantic_labels, target_lut, semantic_files = _load_model(
semantic, args.cache.resolve(strict=True), device
)
target_names = {int(key): str(value) for key, value in semantic["target_taxonomy"].items()}
infer_semantic = e9._semantic_infer_factory(processor, semantic_model, device)
tracker = TwoStageTracker(detector["tracking"])
linear_sum_assignment(np.zeros((1, 1), dtype=np.float64))
with Image.open(frames[0]) as opened:
warm = np.asarray(opened.convert("RGB"), dtype=np.uint8)
_infer(args.triton_url, detector["model"], _preprocess(warm, valid_mask, detector))
infer_semantic(np.where(valid_mask[..., None], warm, 0).astype(np.uint8))
del warm
replay = profile["replay"]
detector_queue = LatestWinsQueue(int(replay["detector_queue_capacity"]))
semantic_queue = LatestWinsQueue(int(replay["semantic_queue_capacity"]))
latest = LatestSemantic()
completed_semantics: list[SemanticResult] = []
producer_errors: list[BaseException] = []
semantic_errors: list[BaseException] = []
semantic_latency = {
name: []
for name in (
"queue_wait_ms",
"valid_fov_fill_ms",
"processor_ms",
"host_to_device_ms",
"forward_ms",
"model_postprocess_ms",
"processing_ms",
"completion_age_ms",
)
}
latency = {
name: []
for name in (
"decode_ms",
"queue_wait_ms",
"detector_ms",
"projection_ms",
"association_ms",
"clearance_ms",
"world_state_ms",
"world_state_age_ms",
)
}
status_counts: Counter[str] = Counter()
fusion_state_counts: Counter[str] = Counter()
rejection_counts: Counter[str] = Counter()
accepted_cuboids = 0
fused_frames = 0
detector_failures = 0
fusion_freshness_violations = 0
history = distance_history(int(profile["association"]["distance_history_frames"]))
projector = WorldStateProjector(float(profile["world_state"]["velocity_history_limit_s"]))
completion_tracker = (
CuboidCompletionTracker(profile["cuboid_completion"])
if "cuboid_completion" in profile
else None
)
semantic_path = output / "semantic-frames.jsonl"
fusion_path = output / "fusion-frames.jsonl"
world_path = output / "world-state.jsonl"
gpu_path = output / "gpu-telemetry.jsonl"
support_offsets = [0]
support_points: list[np.ndarray] = []
support_colors: list[np.ndarray] = []
box_offsets = [0]
box_centers: list[tuple[float, float, float]] = []
box_half_sizes: list[tuple[float, float, float]] = []
box_quaternions: list[tuple[float, float, float, float]] = []
box_colors: list[tuple[int, int, int, int]] = []
frame_times_ns: list[int] = []
disk_before = shutil.disk_usage(output).free
with (
semantic_path.open("x", encoding="utf-8", newline="\n") as semantic_stream,
fusion_path.open("x", encoding="utf-8", newline="\n") as fusion_stream,
world_path.open("x", encoding="utf-8", newline="\n") as world_stream,
gpu_path.open("x", encoding="utf-8", newline="\n") as gpu_stream,
_GpuTelemetry(gpu_stream, 1.0) as gpu,
):
semantic_thread = threading.Thread(
target=semantic_worker,
kwargs={
"queue": semantic_queue,
"latest": latest,
"valid_mask": valid_mask,
"target_lut": target_lut,
"target_names": target_names,
"infer": infer_semantic,
"latency": semantic_latency,
"completed": completed_semantics,
"failures": semantic_errors,
"stop_after_results": (
int(profile["semantic_loss"]["stop_after_completed_results"])
if profile["mode"] == "semantic-loss-negative-control"
else None
),
},
name="lab-e10-semantic",
daemon=True,
)
semantic_thread.start()
replay_started = time.perf_counter() + 0.25
producer = threading.Thread(
target=e9._producer,
kwargs={
"detector_queue": detector_queue,
"semantic_queue": semantic_queue,
"semantic_stride": int(replay["semantic_sample_every_frames"]),
"frame_paths": frames,
"timeline_rows": timeline,
"replay_started": replay_started,
"speed": float(replay["speed"]),
"error": producer_errors,
},
name="lab-e10-source",
daemon=True,
)
producer.start()
while (envelope := detector_queue.take()) is not None:
started = time.perf_counter()
latency["decode_ms"].append(envelope.decode_ms)
latency["queue_wait_ms"].append(max(0.0, (started - envelope.decoded_monotonic) * 1000))
try:
detector_started = time.perf_counter()
tensor = _preprocess(envelope.image, valid_mask, detector)
output_tensor, _request_ms = _infer(args.triton_url, detector["model"], tensor)
detections, _rejected = _detections(output_tensor, detector, valid_mask)
tracks = tracker.update(detections, envelope.frame_index)
latency["detector_ms"].append((time.perf_counter() - detector_started) * 1000)
frame_seconds = float(envelope.timeline["session_seconds"])
current_semantic = latest.snapshot()
semantic_status, semantic_age = semantic_binding(
current_semantic,
frame_seconds,
float(replay["semantic_ttl_ms"]),
)
status_counts[semantic_status] += 1
lidar_frame = lidar.frame(envelope.frame_index)
fusions = ()
points_lidar = np.empty((0, 3), dtype=np.float64)
if lidar_frame is None:
fusion_state = "depth-unavailable-sync-gate"
elif semantic_status != "fresh" or current_semantic is None:
fusion_state = f"semantic-{semantic_status}"
else:
points_map, position, quaternion = lidar_frame
projection_started = time.perf_counter()
pixels, depths, source_indices, points_lidar = project_points(
points_map, position, quaternion, lidar.profile
)
latency["projection_ms"].append(
(time.perf_counter() - projection_started) * 1000
)
association_started = time.perf_counter()
fusions = fuse_tracks(
tracks=[_track_document(track) for track in tracks],
semantic_map=current_semantic.mask,
pixels=pixels,
depths=depths,
source_indices=source_indices,
points_map=points_map,
points_lidar=points_lidar,
association=profile["association"],
distance_history=history,
completion_tracker=completion_tracker,
sensor_position_map=position,
session_seconds=frame_seconds,
)
latency["association_ms"].append(
(time.perf_counter() - association_started) * 1000
)
fusion_state = "fused"
fused_frames += 1
if fusion_state == "fused" and semantic_status != "fresh":
fusion_freshness_violations += 1
fusion_state_counts[fusion_state] += 1
accepted = [item for item in fusions if item.cuboid is not None]
accepted_cuboids += len(accepted)
for item in fusions:
rejection_counts[item.status] += 1
frame_support = []
frame_support_colors = []
for item in accepted:
color_seed = hashlib.sha256(
f"e10:{item.association_group}:{item.track_id}".encode()
).digest()
color = tuple(64 + value % 176 for value in color_seed[:3])
if lidar_frame is None:
continue
points_map = lidar_frame[0]
values = points_map[item.source_indices].astype(np.float32)
frame_support.append(values)
frame_support_colors.append(
np.tile(np.asarray([color], dtype=np.uint8), (values.shape[0], 1))
)
box_centers.append(item.cuboid.center_map)
box_half_sizes.append(item.cuboid.half_size)
box_quaternions.append(item.cuboid.quaternion_xyzw)
box_colors.append((*color, 88))
if frame_support:
support = np.concatenate(frame_support)
colors = np.concatenate(frame_support_colors)
support_points.append(support)
support_colors.append(colors)
support_offsets.append(support_offsets[-1] + support.shape[0])
else:
support_offsets.append(support_offsets[-1])
box_offsets.append(box_offsets[-1] + len(accepted))
clearance_started = time.perf_counter()
clearance_state = clearance(points_lidar, profile["world_state"]["clearance"])
latency["clearance_ms"].append((time.perf_counter() - clearance_started) * 1000)
world_started = time.perf_counter()
result_age = max(0.0, (world_started - envelope.scheduled_monotonic) * 1000)
if result_age >= 1000:
health = "unavailable"
elif result_age >= float(profile["acceptance"]["maximum_p95_world_state_age_ms"]):
health = "stale"
elif fusion_state != "fused":
health = "degraded"
else:
health = "healthy"
delivery = {
"health": health,
"result_age_ms": result_age,
"semantic_status": semantic_status,
"semantic_source_age_ms": semantic_age,
}
world = projector.project(
frame_index=envelope.frame_index,
source_frame_index=int(envelope.timeline["source_frame_index"]),
session_seconds=frame_seconds,
fusion_state=fusion_state,
fusions=fusions,
points_lidar=points_lidar,
clearance_state=clearance_state,
delivery=delivery,
)
latency["world_state_ms"].append((time.perf_counter() - world_started) * 1000)
result_age = max(0.0, (time.perf_counter() - envelope.scheduled_monotonic) * 1000)
world["delivery"]["result_age_ms"] = result_age
latency["world_state_age_ms"].append(result_age)
frame_times_ns.append(round(frame_seconds * 1e9))
fusion_stream.write(
json.dumps(
{
"schema_version": FUSION_SCHEMA,
"frame_index": envelope.frame_index,
"source_frame_index": int(envelope.timeline["source_frame_index"]),
"session_seconds": frame_seconds,
"fusion_state": fusion_state,
"semantic_status": semantic_status,
"semantic_source_frame_index": None
if current_semantic is None
else current_semantic.source_frame_index,
"objects": [fusion_document(item) for item in fusions],
},
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
world_stream.write(
json.dumps(world, sort_keys=True, separators=(",", ":"), allow_nan=False) + "\n"
)
except Exception:
detector_failures += 1
raise
consumed = int(detector_queue.snapshot()["consumed"])
if consumed % 100 == 0:
fusion_stream.flush()
world_stream.flush()
assert_disk(output, args.free_bytes_floor, consumed)
print(
json.dumps(
{
"phase": "e10-integrated",
"processed": consumed,
"detector_dropped": detector_queue.snapshot()["dropped_overflow"],
"semantic_processed": semantic_queue.snapshot()["consumed"],
"fused_frames": fused_frames,
"accepted_cuboids": accepted_cuboids,
},
sort_keys=True,
),
flush=True,
)
producer.join(timeout=5)
semantic_thread.join(timeout=30)
if producer.is_alive() or producer_errors:
raise RuntimeError("LAB E10 producer failed")
if semantic_thread.is_alive() or semantic_errors:
raise RuntimeError("LAB E10 semantic worker failed")
for result in completed_semantics:
semantic_stream.write(
json.dumps(
{
"schema_version": SEMANTIC_SCHEMA,
"frame_index": result.frame_index,
"source_frame_index": result.source_frame_index,
"session_seconds": result.session_seconds,
"completion_age_ms": result.completion_age_ms,
"mask_sha256": result.mask_sha256,
"class_pixels": result.class_pixels,
},
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
for stream in (semantic_stream, fusion_stream, world_stream):
stream.flush()
os.fsync(stream.fileno())
replay_finished = time.perf_counter()
detector_state = detector_queue.snapshot()
semantic_state = semantic_queue.snapshot()
source_span = (
float(timeline[-1]["session_seconds"]) - float(timeline[0]["session_seconds"])
) / float(replay["speed"])
replay_wall = replay_finished - replay_started
detector_fps = int(detector_state["consumed"]) / max(source_span, replay_wall, 1e-9)
semantic_fps = int(semantic_state["consumed"]) / max(source_span, replay_wall, 1e-9)
scheduled_semantic = ((len(frames) - 1) // int(replay["semantic_sample_every_frames"])) + 1
fresh_coverage = status_counts["fresh"] / max(1, int(detector_state["consumed"]))
latency_summary = {name: _percentiles(values) for name, values in latency.items()}
semantic_summary = {name: _percentiles(values) for name, values in semantic_latency.items()}
arrays_path = output / "transient-perception.npz"
np.savez_compressed(
arrays_path,
frame_times_ns=np.asarray(frame_times_ns, dtype=np.int64),
semantic_frame_indices=np.asarray(
[value.frame_index for value in completed_semantics], dtype=np.int64
),
semantic_masks=np.stack([value.mask for value in completed_semantics]).astype(np.uint8),
support_offsets=np.asarray(support_offsets, dtype=np.int64),
support_points=np.concatenate(support_points)
if support_points
else np.empty((0, 3), dtype=np.float32),
support_colors=np.concatenate(support_colors)
if support_colors
else np.empty((0, 3), dtype=np.uint8),
box_offsets=np.asarray(box_offsets, dtype=np.int64),
box_centers=np.asarray(box_centers, dtype=np.float32).reshape((-1, 3)),
box_half_sizes=np.asarray(box_half_sizes, dtype=np.float32).reshape((-1, 3)),
box_quaternions=np.asarray(box_quaternions, dtype=np.float32).reshape((-1, 4)),
box_colors=np.asarray(box_colors, dtype=np.uint8).reshape((-1, 4)),
)
acceptance = profile["acceptance"]
detector_checks = {
"detector_accounting": int(detector_state["consumed"])
+ int(detector_state["dropped_overflow"])
== len(frames),
"detector_minimum_effective_fps": detector_fps
>= float(acceptance["detector_minimum_effective_fps"]),
"detector_maximum_drop_fraction": int(detector_state["dropped_overflow"]) / len(frames)
<= float(acceptance["detector_maximum_drop_fraction"]),
"maximum_p95_world_state_age_ms": float(latency_summary["world_state_age_ms"]["p95"])
<= float(acceptance["maximum_p95_world_state_age_ms"]),
"zero_detector_failures": detector_failures == 0 and not producer_errors,
}
if profile["mode"] == "semantic-loss-negative-control":
stop_after = int(profile["semantic_loss"]["stop_after_completed_results"])
checks = {
**detector_checks,
"semantic_loss_triggered": len(completed_semantics) == stop_after,
"semantic_queue_accounting": int(semantic_state["consumed"])
+ int(semantic_state["dropped_overflow"])
+ int(semantic_state["final_depth"])
== scheduled_semantic,
"minimum_stale_detector_frames": status_counts["stale"]
>= int(acceptance["minimum_stale_detector_frames"]),
"no_fusion_with_nonfresh_semantics": fusion_freshness_violations == 0,
"semantic_worker_stopped_without_error": not semantic_errors,
}
else:
checks = {
**detector_checks,
"semantic_accounting": int(semantic_state["consumed"])
+ int(semantic_state["dropped_overflow"])
== scheduled_semantic,
"semantic_minimum_effective_fps": semantic_fps
>= float(acceptance["semantic_minimum_effective_fps"]),
"semantic_maximum_drop_fraction": int(semantic_state["dropped_overflow"])
/ scheduled_semantic
<= float(acceptance["semantic_maximum_drop_fraction"]),
"semantic_maximum_p95_completion_age_ms": float(
semantic_summary["completion_age_ms"]["p95"]
)
<= float(acceptance["semantic_maximum_p95_completion_age_ms"]),
"minimum_fresh_semantic_coverage": fresh_coverage
>= float(acceptance["minimum_fresh_semantic_coverage"]),
"minimum_lidar_fused_frames": fused_frames
>= int(acceptance["minimum_lidar_fused_frames"]),
"minimum_accepted_cuboids": accepted_cuboids
>= int(acceptance["minimum_accepted_cuboids"]),
"zero_semantic_failures": not semantic_errors,
}
accepted = all(checks.values())
identity = {
"schema_version": IDENTITY_SCHEMA,
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": job["input"]["session_id"],
"source_id": job["input"]["source_id"],
"lidar_pack_id": lidar.pack_id,
"selection": {
"frame_count": len(frames),
"source_start_frame_index": timeline[0]["source_frame_index"],
"source_end_frame_index": timeline[-1]["source_frame_index"],
"timeline_start_seconds": timeline[0]["session_seconds"],
"timeline_end_seconds": timeline[-1]["session_seconds"],
"timeline_sha256": sha256(timeline_path),
},
"configuration": {
"pipeline": PIPELINE_ID,
"profile": profile,
"profile_sha256": profile_sha256,
"detector_profile_sha256": detector_sha256,
"semantic_profile_sha256": semantic_sha256,
"runner_sha256": sha256(Path(__file__).resolve(strict=True)),
"fusion_runtime_sha256": sha256(
Path(__file__).with_name("e10_fusion_runtime.py").resolve(strict=True)
),
"orchestrator_sha256": args.orchestrator_sha256,
"container_image": args.container_image,
"valid_fov": valid_fov,
},
"models": {
"detector": detector["model"],
"detector_files": detector_files,
"semantic": semantic["model"],
"semantic_files": semantic_files,
},
}
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
result_id = f"e10-integrated-perception-{identity_sha256}"
metrics = {
"source_span_seconds": source_span,
"replay_wall_seconds": replay_wall,
"detector": {
"frames_processed": detector_state["consumed"],
"frames_dropped": detector_state["dropped_overflow"],
"effective_fps": detector_fps,
"queue": detector_state,
},
"semantic": {
"frames_processed": semantic_state["consumed"],
"frames_dropped": semantic_state["dropped_overflow"],
"effective_fps": semantic_fps,
"fresh_coverage": fresh_coverage,
"status_counts": dict(status_counts),
"latency_ms": semantic_summary,
},
"fusion": {
"fused_frames": fused_frames,
"fusion_state_counts": dict(fusion_state_counts),
"accepted_cuboids": accepted_cuboids,
"rejection_counts": dict(rejection_counts),
"freshness_violations": fusion_freshness_violations,
},
"semantic_loss": {
"configured": profile["mode"] == "semantic-loss-negative-control",
"stop_after_completed_results": (
int(profile["semantic_loss"]["stop_after_completed_results"])
if profile["mode"] == "semantic-loss-negative-control"
else None
),
"triggered": (
len(completed_semantics)
== int(profile["semantic_loss"]["stop_after_completed_results"])
if profile["mode"] == "semantic-loss-negative-control"
else False
),
},
"latency_ms": latency_summary,
"gpu_telemetry": gpu.summary(),
"process_peak_rss_mib": resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024,
"cuda_peak_memory_allocated_mib": torch.cuda.max_memory_allocated() / 2**20,
"cuda_peak_memory_reserved_mib": torch.cuda.max_memory_reserved() / 2**20,
"disk": {
"free_bytes_before": disk_before,
"free_bytes_after": shutil.disk_usage(output).free,
"free_bytes_floor": args.free_bytes_floor,
},
}
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "accepted" if accepted else "rejected",
"ground_truth": False,
"identity": identity,
"runtime": {
"hostname": platform.node(),
"python": platform.python_version(),
"numpy": np.__version__,
"torch": torch.__version__,
"transformers": transformers.__version__,
"scipy": importlib.metadata.version("scipy"),
"gpu": torch.cuda.get_device_name(),
},
"metrics": metrics,
"acceptance": {
"accepted": accepted,
"checks": checks,
"navigation_or_safety_accepted": False,
},
"limitations": [
"Recorded source-paced replay, not direct live K1 transport.",
"Host-arrival camera/LiDAR synchronization is not a hardware-clock proof.",
"COCO and Cityscapes models are not forest-domain or safety validated.",
(
"Completed 3D cuboids mix visible LiDAR support with class-size priors; "
"unobserved volume is inferred, not measured or ground truth."
if "cuboid_completion" in profile
else (
"3D cuboids describe visible LiDAR-supported surfaces, "
"not complete object volume."
)
),
*(
["Semantic inference was intentionally stopped for a negative-control run."]
if profile["mode"] == "semantic-loss-negative-control"
else []
),
],
}
report_path = output / "run-report.json"
_write_json(report_path, report)
artifacts = [
_artifact(semantic_path, "e10-semantic-frames", "application/x-ndjson", SEMANTIC_SCHEMA),
_artifact(fusion_path, "e10-fusion-frames", "application/x-ndjson", FUSION_SCHEMA),
_artifact(world_path, "e10-world-state", "application/x-ndjson", WORLD_SCHEMA),
_artifact(arrays_path, "e10-transient-perception", "application/x-npz"),
_artifact(gpu_path, "worker-gpu-telemetry", "application/x-ndjson"),
_artifact(report_path, "e10-run-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": report["created_at_utc"],
"acceptance_state": report["state"],
"ground_truth": False,
"publication_scope": (
"recorded-integrated-semantic-loss-negative-control-only"
if profile["mode"] == "semantic-loss-negative-control"
else "recorded-integrated-realtime-qualification-only"
),
"frames_processed": detector_state["consumed"],
"artifacts": artifacts,
}
_write_json(output / "result.json", result)
lidar.close()
print(
json.dumps(
{
"result_id": result_id,
"accepted": accepted,
"detector_fps": detector_fps,
"semantic_fps": semantic_fps,
"world_state_age_p95_ms": latency_summary["world_state_age_ms"]["p95"],
"fused_frames": fused_frames,
"accepted_cuboids": accepted_cuboids,
},
sort_keys=True,
),
flush=True,
)
return 0
def assert_disk(path: Path, floor: int, frame: int) -> None:
if floor < 0 or (floor and shutil.disk_usage(path).free < floor):
raise RuntimeError(f"LAB E10 crossed the D-backed free-space floor at frame {frame}")
def main() -> int:
args = arguments()
return preflight(args) if args.command == "preflight" else run(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,304 @@
#!/usr/bin/env python3
"""Receive and qualify the Mission Core live shadow stream without K1 access."""
from __future__ import annotations
import argparse
import base64
import hashlib
import json
import os
import select
import socket
import struct
import sys
import time
from collections import Counter
from contextlib import suppress
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, BinaryIO
WIRE_SCHEMA = "missioncore.live-perception-wire/v1"
REPORT_SCHEMA = "missioncore.e12-shadow-transport-report/v1"
WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
MAX_HEADER_BYTES = 64 * 1024
MAX_FRAME_BYTES = 3 * 1024 * 1024
class ProbeError(RuntimeError):
pass
def _read_exact(stream: BinaryIO, size: int) -> bytes:
parts: list[bytes] = []
remaining = size
while remaining:
chunk = stream.read(remaining)
if not chunk:
raise ProbeError("shadow transport ended unexpectedly")
parts.append(chunk)
remaining -= len(chunk)
return b"".join(parts)
def _read_http_headers(stream: BinaryIO) -> tuple[str, dict[str, str]]:
encoded = bytearray()
while not encoded.endswith(b"\r\n\r\n"):
encoded.extend(_read_exact(stream, 1))
if len(encoded) > MAX_HEADER_BYTES:
raise ProbeError("websocket response headers exceed the bound")
try:
lines = encoded.decode("ascii").split("\r\n")
except UnicodeDecodeError as exc:
raise ProbeError("websocket response headers are not ASCII") from exc
headers: dict[str, str] = {}
for line in lines[1:]:
if not line:
continue
name, separator, value = line.partition(":")
if not separator:
raise ProbeError("malformed websocket response header")
headers[name.strip().lower()] = value.strip()
return lines[0], headers
def _send_client_frame(stream: BinaryIO, opcode: int, payload: bytes) -> None:
if len(payload) > 125:
raise ProbeError("client control frame exceeds websocket bound")
mask = os.urandom(4)
masked = bytes(value ^ mask[index % 4] for index, value in enumerate(payload))
stream.write(bytes((0x80 | opcode, 0x80 | len(payload))) + mask + masked)
stream.flush()
def _read_server_frame(stream: BinaryIO) -> tuple[int, bytes]:
first, second = _read_exact(stream, 2)
if not first & 0x80:
raise ProbeError("fragmented websocket frames are not admitted")
opcode = first & 0x0F
masked = bool(second & 0x80)
length = second & 0x7F
if length == 126:
length = struct.unpack("!H", _read_exact(stream, 2))[0]
elif length == 127:
length = struct.unpack("!Q", _read_exact(stream, 8))[0]
if length > MAX_FRAME_BYTES:
raise ProbeError("websocket frame exceeds the shadow transport bound")
mask = _read_exact(stream, 4) if masked else None
payload = _read_exact(stream, length)
if mask is not None:
payload = bytes(value ^ mask[index % 4] for index, value in enumerate(payload))
return opcode, payload
def _connect(
host: str,
port: int,
path: str,
token: str,
timeout_seconds: float,
) -> tuple[socket.socket, BinaryIO]:
connection = socket.create_connection((host, port), timeout=timeout_seconds)
connection.settimeout(timeout_seconds)
stream = connection.makefile("rwb", buffering=0)
key = base64.b64encode(os.urandom(16)).decode("ascii")
request = (
f"GET {path} HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
"Sec-WebSocket-Version: 13\r\n"
f"Authorization: Bearer {token}\r\n"
"\r\n"
).encode("ascii")
stream.write(request)
stream.flush()
status, headers = _read_http_headers(stream)
expected_accept = base64.b64encode(
hashlib.sha1(f"{key}{WEBSOCKET_GUID}".encode("ascii")).digest()
).decode("ascii")
if not status.startswith("HTTP/1.1 101 "):
raise ProbeError(f"websocket upgrade failed: {status}")
if headers.get("sec-websocket-accept") != expected_accept:
raise ProbeError("websocket accept identity mismatch")
connection.settimeout(None)
return connection, stream
def _decode_event(frame: bytes) -> tuple[dict[str, Any], bytes]:
if len(frame) < 4:
raise ProbeError("shadow event is truncated")
header_bytes = struct.unpack("!I", frame[:4])[0]
if header_bytes < 2 or header_bytes > MAX_HEADER_BYTES:
raise ProbeError("shadow event header length is invalid")
boundary = 4 + header_bytes
if boundary > len(frame):
raise ProbeError("shadow event header is truncated")
try:
header = json.loads(frame[4:boundary])
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ProbeError("shadow event header is invalid") from exc
if not isinstance(header, dict) or header.get("schema_version") != WIRE_SCHEMA:
raise ProbeError("shadow event schema is incompatible")
payload = frame[boundary:]
if int(header.get("payload_bytes", -1)) != len(payload):
raise ProbeError("shadow event payload length mismatch")
if hashlib.sha256(payload).hexdigest() != header.get("payload_sha256"):
raise ProbeError("shadow event payload digest mismatch")
if header.get("commands_enabled") is not False:
raise ProbeError("shadow transport unexpectedly enables commands")
if header.get("navigation_or_safety_accepted") is not False:
raise ProbeError("shadow transport unexpectedly claims safety acceptance")
return header, payload
def _write_report(output_root: Path, report: dict[str, Any]) -> Path:
root = output_root.expanduser().resolve()
if os.name == "nt" and root.drive.upper() != "D:":
raise ProbeError("Windows probe output must stay on drive D")
root.mkdir(parents=True, exist_ok=True)
stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
temporary = root / f".{stamp}-e12-shadow-transport.json.tmp"
destination = root / f"{stamp}-e12-shadow-transport.json"
encoded = json.dumps(report, indent=2, sort_keys=True).encode("utf-8") + b"\n"
with temporary.open("xb") as stream:
stream.write(encoded)
stream.flush()
os.fsync(stream.fileno())
temporary.replace(destination)
return destination
def run(args: argparse.Namespace) -> dict[str, Any]:
token = sys.stdin.readline().strip() if args.token_stdin else args.token
if not token or len(token) < 40:
raise ProbeError("shadow bearer token is missing or invalid")
started_epoch_ns = time.time_ns()
started_monotonic_ns = time.monotonic_ns()
counts: Counter[str] = Counter()
payload_bytes: Counter[str] = Counter()
first_ingress_sequence: int | None = None
last_ingress_sequence: int | None = None
ingress_gaps = 0
session_ids: set[str] = set()
source_ids: set[str] = set()
session_end_seen = False
connection, stream = _connect(
args.host,
args.port,
args.path,
token,
args.socket_timeout_seconds,
)
try:
deadline = time.monotonic() + args.max_duration_seconds
while time.monotonic() < deadline:
readable, _, _ = select.select(
[connection],
[],
[],
min(0.5, max(0.0, deadline - time.monotonic())),
)
if not readable:
continue
opcode, frame = _read_server_frame(stream)
if opcode == 0x8:
break
if opcode == 0x9:
_send_client_frame(stream, 0xA, frame)
continue
if opcode != 0x2:
raise ProbeError(f"unexpected websocket opcode: {opcode}")
header, payload = _decode_event(frame)
sequence = int(header["ingress_sequence"])
if last_ingress_sequence is not None and sequence > last_ingress_sequence + 1:
ingress_gaps += sequence - last_ingress_sequence - 1
if last_ingress_sequence is not None and sequence <= last_ingress_sequence:
raise ProbeError("shadow ingress sequence is not strictly increasing")
first_ingress_sequence = first_ingress_sequence or sequence
last_ingress_sequence = sequence
modality = str(header["modality"])
counts[modality] += 1
payload_bytes[modality] += len(payload)
session_ids.add(str(header["session_id"]))
source_ids.add(str(header["source_id"]))
if modality == "control":
try:
control = json.loads(payload)
except json.JSONDecodeError as exc:
raise ProbeError("control event payload is invalid") from exc
if control.get("event") == "session-end":
session_end_seen = True
break
finally:
with suppress(Exception):
_send_client_frame(stream, 0x8, b"")
stream.close()
connection.close()
completed_epoch_ns = time.time_ns()
report = {
"schema_version": REPORT_SCHEMA,
"state": "completed" if session_end_seen else "timed-out",
"started_at_epoch_ns": started_epoch_ns,
"completed_at_epoch_ns": completed_epoch_ns,
"wall_seconds": (time.monotonic_ns() - started_monotonic_ns) / 1_000_000_000,
"transport": {
"kind": "ssh-reverse-tunnel-websocket",
"endpoint": f"{args.host}:{args.port}",
"worker_has_k1_connection": False,
"payload_integrity": "sha256-verified-per-event",
"clock_qualification": "not-qualified-cross-host",
},
"authority": {
"mode": "shadow-diagnostic-only",
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
"events": {
"counts": dict(sorted(counts.items())),
"payload_bytes": dict(sorted(payload_bytes.items())),
"first_ingress_sequence": first_ingress_sequence,
"last_ingress_sequence": last_ingress_sequence,
"ingress_sequence_gaps": ingress_gaps,
"session_end_seen": session_end_seen,
"session_ids": sorted(session_ids),
"source_ids": sorted(source_ids),
},
}
destination = _write_report(Path(args.output_root), report)
report["report_path"] = str(destination)
return report
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="host.docker.internal")
parser.add_argument("--port", type=int, default=18012)
parser.add_argument(
"--path",
default=(
"/api/v1/device-plugins/"
"nodedc.device.xgrids-lixelkity-k1/live-perception-shadow"
),
)
parser.add_argument("--token", default="")
parser.add_argument("--token-stdin", action="store_true")
parser.add_argument("--max-duration-seconds", type=float, default=900.0)
parser.add_argument("--socket-timeout-seconds", type=float, default=30.0)
parser.add_argument(
"--output-root",
default="D:/NDC_MISSIONCORE/runtime/derived/e12-shadow-transport",
)
return parser.parse_args()
if __name__ == "__main__":
try:
print(json.dumps(run(_arguments()), indent=2, sort_keys=True))
except Exception as exc:
print(f"E12_SHADOW_TRANSPORT_ERROR={type(exc).__name__}:{exc}", file=sys.stderr)
raise SystemExit(1) from exc
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,768 @@
#!/usr/bin/env python3
"""Run and seal LAB E4 full-session EoMT semantic segmentation."""
from __future__ import annotations
import argparse
import hashlib
import importlib.metadata
import json
import os
import platform
import resource
import shutil
import time
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from run_e3_rectified_segmentation import (
_dependency_manifest,
_model_snapshot,
_profile,
_resource_delta,
_resource_snapshot,
_verify_model_files,
)
from run_recorded_perception_epoch import (
FRAME_SCHEMA,
REPORT_SCHEMA,
RESULT_SCHEMA,
_artifact,
_canonical_json,
_GpuTelemetry,
_palette,
_percentiles,
_read_timeline,
_sha256,
_valid_sha256,
_validate_job,
_write_json,
)
IDENTITY_SCHEMA = "missioncore.recorded-perception-identity/v2"
VALID_FOV_SCHEMA = "missioncore.k1-valid-fov-mask/v1"
PIPELINE_ID = "recorded-semantic-eomt-fisheye-mask/v1"
SEMANTIC_ALPHA = 0.48
TARGET_CLASS_COUNT = 16
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
preflight = commands.add_parser("preflight")
preflight.add_argument("--job", type=Path, required=True)
preflight.add_argument("--profile", type=Path, required=True)
preflight.add_argument("--valid-fov-root", type=Path, required=True)
preflight.add_argument("--cache", type=Path, required=True)
preflight.add_argument("--environment", type=Path, required=True)
run = commands.add_parser("run")
run.add_argument("--job", type=Path, required=True)
run.add_argument("--profile", type=Path, required=True)
run.add_argument("--valid-fov-root", type=Path, required=True)
run.add_argument("--frames", type=Path, required=True)
run.add_argument("--timeline", type=Path, required=True)
run.add_argument("--cache", type=Path, required=True)
run.add_argument("--environment", type=Path, required=True)
run.add_argument("--output", type=Path, required=True)
run.add_argument("--frame-limit", type=int, default=0)
run.add_argument("--free-bytes-floor", type=int, default=0)
run.add_argument("--orchestrator-sha256", required=True)
run.add_argument("--container-image", required=True)
run.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
finalize = commands.add_parser("finalize")
finalize.add_argument("--output", type=Path, required=True)
finalize.add_argument("--video", type=Path, required=True)
finalize.add_argument("--masks", type=Path, required=True)
finalize.add_argument("--extract-seconds", type=float, required=True)
finalize.add_argument("--encode-seconds", type=float, required=True)
finalize.add_argument("--archive-seconds", type=float, required=True)
finalize.add_argument("--wall-seconds", type=float, required=True)
finalize.add_argument("--encoder", required=True)
finalize.add_argument("--disk-free-before-bytes", type=int, required=True)
finalize.add_argument("--disk-free-post-extract-bytes", type=int, required=True)
finalize.add_argument("--disk-free-post-inference-bytes", type=int, required=True)
finalize.add_argument("--disk-free-post-artifacts-bytes", type=int, required=True)
finalize.add_argument("--disk-floor-bytes", type=int, required=True)
finalize.add_argument("--working-set-reserve-bytes", type=int, required=True)
return parser.parse_args()
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def _validate_source(job: dict[str, Any], profile: dict[str, Any]) -> None:
source = profile["source"]
input_document = job["input"]
if (
input_document.get("kind") != "canonical-camera-epoch"
or input_document.get("source_id") != source["source_id"]
or source.get("resolution") != [800, 600]
):
raise RuntimeError("LAB E4 profile does not match the camera job")
def _load_valid_fov(
root: Path,
job: dict[str, Any],
profile: dict[str, Any],
) -> tuple[Any, dict[str, Any]]:
import numpy as np
from PIL import Image
resolved = root.resolve(strict=True)
manifest = _read_object(resolved / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
artifact = manifest.get("artifact")
source = profile["source"]
if (
manifest.get("schema_version") != VALID_FOV_SCHEMA
or not isinstance(identity, dict)
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"valid-fov-mask-{identity_sha256}"
or identity.get("calibration_sha256") != source["calibration_sha256"]
or identity.get("calibration_slot") != source["calibration_slot"]
or identity.get("source_id") != job["input"]["source_id"]
or identity.get("admitted_resolution") != source["resolution"]
or not isinstance(artifact, dict)
or artifact.get("path") != "mask.png"
or artifact.get("mode") != "L"
or artifact.get("inside_value") != 255
or artifact.get("outside_value") != 0
or not _valid_sha256(artifact.get("sha256"))
):
raise RuntimeError("LAB E4 valid-FOV binding is invalid")
mask_path = (resolved / "mask.png").resolve(strict=True)
if (
mask_path.parent != resolved
or mask_path.stat().st_size != artifact.get("byte_length")
or _sha256(mask_path) != artifact["sha256"]
):
raise RuntimeError("LAB E4 valid-FOV artifact changed")
with Image.open(mask_path) as opened:
mask = np.asarray(opened, dtype=np.uint8)
if mask.shape != (600, 800) or not np.isin(mask, (0, 255)).all():
raise RuntimeError("LAB E4 valid-FOV mask pixels are invalid")
valid = mask > 0
geometry = manifest.get("geometry")
if (
not isinstance(geometry, dict)
or geometry.get("valid_pixel_count") != int(valid.sum())
or int(valid.sum()) < 1
):
raise RuntimeError("LAB E4 valid-FOV geometry changed")
return valid, {
"generation_id": manifest["generation_id"],
"identity_sha256": identity_sha256,
"mask_sha256": artifact["sha256"],
"valid_pixel_count": int(valid.sum()),
"total_pixel_count": int(valid.size),
}
def _load_model(
profile: dict[str, Any],
cache: Path,
device: Any,
) -> tuple[Any, Any, dict[int, str], Any, list[dict[str, Any]]]:
import numpy as np
import torch
from transformers import AutoImageProcessor, EomtForUniversalSegmentation
snapshot = _model_snapshot(profile, cache, offline=True)
model_files = _verify_model_files(profile, snapshot)
processor = AutoImageProcessor.from_pretrained(
snapshot,
local_files_only=True,
use_fast=True,
)
model, loading = EomtForUniversalSegmentation.from_pretrained(
snapshot,
local_files_only=True,
output_loading_info=True,
)
problems = {
name: loading.get(name, [])
for name in ("missing_keys", "unexpected_keys", "mismatched_keys", "error_msgs")
if loading.get(name)
}
if problems:
raise RuntimeError("EoMT checkpoint did not load exactly: " + json.dumps(problems))
model = model.to(device=device, dtype=torch.float16).eval()
labels = {int(key): str(value) for key, value in model.config.id2label.items()}
mapping = profile["cityscapes_to_target"]
if set(labels.values()) != set(mapping):
raise RuntimeError("EoMT runtime taxonomy changed")
target_lut = np.asarray([mapping[labels[index]] for index in range(19)], dtype=np.uint8)
return processor, model, labels, target_lut, model_files
def _preflight(args: argparse.Namespace) -> int:
import torch
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = _profile(args.profile)
_validate_source(job, profile)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != profile_sha256:
raise RuntimeError("LAB E4 dependencies belong to another profile")
_load_valid_fov(args.valid_fov_root, job, profile)
if not torch.cuda.is_available() or torch.cuda.device_count() < 1:
raise RuntimeError("CUDA device 0 is unavailable")
device = torch.device("cuda:0")
processor, model, _labels, _target_lut, model_files = _load_model(
profile,
args.cache.resolve(strict=True),
device,
)
del processor, model, _labels, _target_lut
torch.cuda.empty_cache()
print(
json.dumps(
{
"state": "preflight-ready",
"job_id": job["job_id"],
"source_id": job["input"]["source_id"],
"frames": job["input"]["segment_count"],
"pipeline": PIPELINE_ID,
"model_files": model_files,
"cuda_device": torch.cuda.get_device_name(),
},
sort_keys=True,
),
flush=True,
)
return 0
def _class_document(
semantic: Any,
valid_mask: Any,
target_names: dict[int, str],
) -> list[dict[str, Any]]:
import numpy as np
counts = np.bincount(semantic[valid_mask].reshape(-1), minlength=TARGET_CLASS_COUNT)
total = int(valid_mask.sum())
return [
{
"id": index,
"label": target_names[index],
"pixels": int(counts[index]),
"fraction_of_valid_fov": round(float(counts[index]) / total, 9),
}
for index in range(1, TARGET_CLASS_COUNT)
if counts[index] > 0
]
def _overlay(image: Any, semantic: Any) -> Any:
import numpy as np
colors = np.zeros_like(image)
for category_id in range(1, TARGET_CLASS_COUNT):
colors[semantic == category_id] = _palette(category_id)
blended = np.rint(
image.astype(np.float32) * (1.0 - SEMANTIC_ALPHA)
+ colors.astype(np.float32) * SEMANTIC_ALPHA
)
return (
np.where(
(semantic > 0)[..., None],
blended,
image,
)
.clip(0, 255)
.astype(np.uint8)
)
def _write_png(path: Path, array: Any) -> None:
from PIL import Image
Image.fromarray(array).save(path, format="PNG", optimize=False)
def _assert_disk_floor(path: Path, floor: int, frame: int) -> None:
if floor < 0:
raise RuntimeError("disk free-space floor is invalid")
free = shutil.disk_usage(path).free
if floor and free < floor:
raise RuntimeError(f"D-backed output crossed its free-space floor at frame {frame}")
def _run(args: argparse.Namespace) -> int:
import numpy as np
import torch
import transformers
from PIL import Image
job_root = args.job.resolve(strict=True)
if not _valid_sha256(args.orchestrator_sha256):
raise RuntimeError("LAB E4 orchestrator SHA-256 is invalid")
if not 1 <= len(args.container_image) <= 256:
raise RuntimeError("LAB E4 container image identity is invalid")
job = _validate_job(job_root)
profile, profile_sha256 = _profile(args.profile)
_validate_source(job, profile)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != profile_sha256:
raise RuntimeError("LAB E4 dependencies belong to another profile")
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, profile)
input_document = job["input"]
full_frame_count = int(input_document["segment_count"])
if args.frame_limit < 0 or args.frame_limit > full_frame_count:
raise RuntimeError("LAB E4 frame limit escapes the camera job")
frame_count = args.frame_limit or full_frame_count
frames_root = args.frames.resolve(strict=True)
frame_paths = [frames_root / f"frame-{index:06d}.png" for index in range(1, frame_count + 1)]
if not all(path.is_file() for path in frame_paths):
raise RuntimeError("decoded LAB E4 frame set is incomplete")
if len(list(frames_root.glob("frame-*.png"))) != frame_count:
raise RuntimeError("decoded LAB E4 frame set contains unexpected files")
timeline = input_document["timeline"]
timestamps = _read_timeline(
args.timeline.resolve(strict=True),
frame_count,
float(timeline["start_seconds"]),
float(timeline["end_seconds"]),
)
output = args.output.resolve()
if output.exists():
raise RuntimeError("LAB E4 output must be absent")
output.mkdir(mode=0o700, parents=True, exist_ok=False)
masks_root = output / "semantic-masks"
overlays_root = output / "overlay-frames"
masks_root.mkdir(mode=0o700)
overlays_root.mkdir(mode=0o700)
_assert_disk_floor(output, args.free_bytes_floor, 0)
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for LAB E4")
device = torch.device("cuda:0")
processor, model, model_labels, target_lut, model_files = _load_model(
profile,
args.cache.resolve(strict=True),
device,
)
target_names = {int(key): str(value) for key, value in profile["target_taxonomy"].items()}
if set(target_names) != set(range(TARGET_CLASS_COUNT)):
raise RuntimeError("LAB E4 target taxonomy changed")
def infer(image: Any) -> tuple[Any, dict[str, float]]:
processor_started = time.perf_counter()
inputs = processor(images=Image.fromarray(image), return_tensors="pt")
processor_ms = (time.perf_counter() - processor_started) * 1000.0
torch.cuda.synchronize()
transfer_started = time.perf_counter()
inputs = {
name: value.to(device) if isinstance(value, torch.Tensor) else value
for name, value in inputs.items()
}
torch.cuda.synchronize()
transfer_ms = (time.perf_counter() - transfer_started) * 1000.0
forward_started = time.perf_counter()
with torch.inference_mode(), torch.amp.autocast("cuda", dtype=torch.float16):
outputs = model(**inputs)
torch.cuda.synchronize()
forward_ms = (time.perf_counter() - forward_started) * 1000.0
post_started = time.perf_counter()
semantic = processor.post_process_semantic_segmentation(
outputs,
target_sizes=[image.shape[:2]],
)[0]
semantic = semantic.detach().cpu().numpy().astype(np.uint8)
post_ms = (time.perf_counter() - post_started) * 1000.0
del inputs, outputs
return semantic, {
"processor_ms": processor_ms,
"host_to_device_ms": transfer_ms,
"forward_ms": forward_ms,
"model_postprocess_ms": post_ms,
}
with Image.open(frame_paths[0]) as opened:
warm_image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
if warm_image.shape != (600, 800, 3):
raise RuntimeError("LAB E4 input resolution changed")
warm_image = np.where(valid_mask[..., None], warm_image, 0).astype(np.uint8)
warm_semantic, _warm_latency = infer(warm_image)
del warm_semantic, _warm_latency
latency = {
name: []
for name in (
"image_decode_ms",
"valid_fov_fill_ms",
"processor_ms",
"host_to_device_ms",
"forward_ms",
"model_postprocess_ms",
"overlay_ms",
"artifact_write_ms",
"end_to_end_ms",
)
}
total_pixels = Counter()
metadata_path = output / "frames.jsonl"
telemetry_path = output / "gpu-telemetry.jsonl"
resource_before = _resource_snapshot()
disk_before = shutil.disk_usage(output).free
started = time.perf_counter()
with (
telemetry_path.open("x", encoding="utf-8", newline="\n") as telemetry_stream,
metadata_path.open("x", encoding="utf-8", newline="\n") as metadata_stream,
_GpuTelemetry(telemetry_stream, args.telemetry_interval_seconds) as telemetry,
):
for frame_index, (path, session_seconds) in enumerate(
zip(frame_paths, timestamps, strict=True)
):
frame_started = time.perf_counter()
decode_started = time.perf_counter()
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
latency["image_decode_ms"].append((time.perf_counter() - decode_started) * 1000.0)
if image.shape != (600, 800, 3):
raise RuntimeError("LAB E4 input resolution changed")
fill_started = time.perf_counter()
model_input = np.where(valid_mask[..., None], image, 0).astype(np.uint8)
latency["valid_fov_fill_ms"].append((time.perf_counter() - fill_started) * 1000.0)
semantic, measured = infer(model_input)
if semantic.max() >= len(target_lut):
raise RuntimeError("EoMT emitted an unknown category")
target = target_lut[semantic]
target = target.copy()
target[~valid_mask] = 0
if target.shape != valid_mask.shape or np.any(target[~valid_mask] != 0):
raise RuntimeError("LAB E4 semantic mask escaped valid FOV")
for name, value in measured.items():
latency[name].append(value)
overlay_started = time.perf_counter()
overlay = _overlay(image, target)
classes = _class_document(target, valid_mask, target_names)
latency["overlay_ms"].append((time.perf_counter() - overlay_started) * 1000.0)
counts = np.bincount(target[valid_mask], minlength=TARGET_CLASS_COUNT)
for category_id in range(1, TARGET_CLASS_COUNT):
total_pixels[target_names[category_id]] += int(counts[category_id])
write_started = time.perf_counter()
_write_png(masks_root / f"frame-{frame_index + 1:06d}.png", target)
_write_png(overlays_root / f"frame-{frame_index + 1:06d}.png", overlay)
metadata_stream.write(
json.dumps(
{
"schema_version": FRAME_SCHEMA,
"frame_index": frame_index,
"sequence": frame_index + 1,
"session_seconds": round(session_seconds, 9),
"instances": [],
"semantic_classes": classes,
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
latency["artifact_write_ms"].append((time.perf_counter() - write_started) * 1000.0)
latency["end_to_end_ms"].append((time.perf_counter() - frame_started) * 1000.0)
completed = frame_index + 1
if completed % 100 == 0 or completed == frame_count:
metadata_stream.flush()
telemetry_stream.flush()
_assert_disk_floor(output, args.free_bytes_floor, completed)
print(
json.dumps(
{
"phase": "semantic",
"frames_processed": completed,
"frames_total": frame_count,
"free_bytes": shutil.disk_usage(output).free,
},
sort_keys=True,
),
flush=True,
)
metadata_stream.flush()
os.fsync(metadata_stream.fileno())
inference_elapsed = time.perf_counter() - started
resource_after = _resource_snapshot()
disk_after = shutil.disk_usage(output).free
report = {
"schema_version": REPORT_SCHEMA,
"run_id": f"{job['job_id']}-e4-eomt-full-session-v1",
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": (
"inference-complete-awaiting-publication"
if frame_count == full_frame_count
else "technical-pilot-complete-not-publishable"
),
"ground_truth": False,
"input": {
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": input_document["session_id"],
"source_id": input_document["source_id"],
"codec_epoch": input_document["codec_epoch"],
"segment_count": full_frame_count,
"frames_admitted": frame_count,
"byte_length": input_document["byte_length"],
"timeline_start_seconds": timeline["start_seconds"],
"timeline_end_seconds": timeline["end_seconds"],
"frame_timeline_sha256": _sha256(args.timeline.resolve(strict=True)),
},
"calibration": {
"content_identity_sha256": profile["source"]["calibration_sha256"],
"camera_slot": profile["source"]["calibration_slot"],
},
"configuration": {
"pipeline": PIPELINE_ID,
"semantic_alpha": SEMANTIC_ALPHA,
"batch_size": 1,
"precision": "fp16-autocast",
"frame_policy": (
"all-frames-no-sampling"
if frame_count == full_frame_count
else f"technical-pilot-first-{frame_count}-frames"
),
"instance_branch": "disabled",
"profile_sha256": profile_sha256,
"dependency_identity_sha256": dependency["identity_sha256"],
"valid_fov": valid_fov,
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
"orchestrator_sha256": args.orchestrator_sha256,
"container_image": args.container_image,
},
"models": {
"semantic": {
"id": profile["model"]["id"],
"revision": profile["model"]["revision"],
"architecture": profile["model"]["architecture"],
"checkpoint_load": "exact-no-missing-unexpected-or-mismatched-keys",
"runtime_labels": {
str(index): label for index, label in sorted(model_labels.items())
},
},
"files": model_files,
},
"runtime": {
"hostname": platform.node(),
"platform": platform.platform(),
"python": platform.python_version(),
"torch": torch.__version__,
"transformers": transformers.__version__,
"cuda_runtime": torch.version.cuda,
"gpu": torch.cuda.get_device_name(),
"gpu_compute_capability": list(torch.cuda.get_device_capability()),
},
"metrics": {
"frames_expected": frame_count,
"frames_processed": frame_count,
"frames_failed": 0,
"frames_skipped": 0,
"instances": 0,
"inference_wall_seconds": round(inference_elapsed, 6),
"inference_frames_per_second": round(frame_count / inference_elapsed, 6),
"latency_ms": {name: _percentiles(values) for name, values in latency.items()},
"semantic_pixel_totals_inside_valid_fov": dict(sorted(total_pixels.items())),
"cuda_peak_memory_allocated_mib": round(
torch.cuda.max_memory_allocated() / 2**20,
3,
),
"cuda_peak_memory_reserved_mib": round(
torch.cuda.max_memory_reserved() / 2**20,
3,
),
"process_peak_rss_mib": round(
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024,
3,
),
"resource_delta": _resource_delta(resource_before, resource_after),
"gpu_telemetry": telemetry.summary(),
"disk": {
"free_bytes_before_inference": disk_before,
"free_bytes_after_inference": disk_after,
"free_bytes_floor": args.free_bytes_floor,
},
},
"versions": {
name: importlib.metadata.version(name)
for name in ("numpy", "pillow", "torch", "transformers")
},
}
_write_json(output / "run-report.partial.json", report)
print(
json.dumps(
{
"state": report["state"],
"frames": frame_count,
"inference_seconds": round(inference_elapsed, 6),
},
sort_keys=True,
),
flush=True,
)
return 0
def _finalize(args: argparse.Namespace) -> int:
output = args.output.resolve(strict=True)
video = args.video.resolve(strict=True)
masks = args.masks.resolve(strict=True)
if video.parent != output or masks.parent != output:
raise RuntimeError("published LAB E4 artifacts must be direct result children")
partial_path = output / "run-report.partial.json"
frames_path = output / "frames.jsonl"
telemetry_path = output / "gpu-telemetry.jsonl"
partial = _read_object(partial_path)
input_document = partial.get("input")
metrics = partial.get("metrics")
if (
partial.get("schema_version") != REPORT_SCHEMA
or partial.get("state") != "inference-complete-awaiting-publication"
or not isinstance(input_document, dict)
or not isinstance(metrics, dict)
or input_document.get("frames_admitted") != input_document.get("segment_count")
or metrics.get("frames_expected") != input_document.get("segment_count")
or metrics.get("frames_processed") != input_document.get("segment_count")
):
raise RuntimeError("only a complete LAB E4 camera epoch can be finalized")
for value in (
args.extract_seconds,
args.encode_seconds,
args.archive_seconds,
args.wall_seconds,
):
if value <= 0:
raise RuntimeError("LAB E4 publication timing is invalid")
disk_values = (
args.disk_free_before_bytes,
args.disk_free_post_extract_bytes,
args.disk_free_post_inference_bytes,
args.disk_free_post_artifacts_bytes,
args.disk_floor_bytes,
args.working_set_reserve_bytes,
)
if any(value < 0 for value in disk_values):
raise RuntimeError("LAB E4 publication disk telemetry is invalid")
if any(
value < args.disk_floor_bytes
for value in (
args.disk_free_before_bytes,
args.disk_free_post_extract_bytes,
args.disk_free_post_inference_bytes,
args.disk_free_post_artifacts_bytes,
)
):
raise RuntimeError("LAB E4 publication crossed its D: free-space floor")
identity = {
"schema_version": IDENTITY_SCHEMA,
"job_id": input_document["job_id"],
"input_sha256": input_document["input_sha256"],
"calibration": partial["calibration"],
"configuration": partial["configuration"],
"models": partial["models"],
"publication": {
"video_encoder": args.encoder,
"video_media_type": "video/mp4",
"mask_archive_media_type": "application/gzip",
"content": "semantic-overlay-and-semantic-masks",
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"result-{identity_sha256}"
final_report = {
**partial,
"state": "published",
"result_id": result_id,
"publication": {
"extract_seconds": round(args.extract_seconds, 6),
"encode_seconds": round(args.encode_seconds, 6),
"archive_seconds": round(args.archive_seconds, 6),
"wall_seconds": round(args.wall_seconds, 6),
"end_to_end_frames_per_second": round(
int(metrics["frames_processed"]) / args.wall_seconds,
6,
),
"encoder": args.encoder,
"disk": {
"free_bytes_before": args.disk_free_before_bytes,
"free_bytes_post_extract": args.disk_free_post_extract_bytes,
"free_bytes_post_inference": args.disk_free_post_inference_bytes,
"free_bytes_post_artifacts": args.disk_free_post_artifacts_bytes,
"free_bytes_floor": args.disk_floor_bytes,
"working_set_reserve_bytes": args.working_set_reserve_bytes,
},
},
}
report_path = output / "run-report.json"
_write_json(report_path, final_report)
artifacts = [
_artifact(video, "panoptic-overlay-video", "video/mp4"),
_artifact(masks, "panoptic-mask-archive", "application/gzip"),
_artifact(frames_path, "panoptic-frame-metadata", "application/x-ndjson", FRAME_SCHEMA),
_artifact(telemetry_path, "worker-gpu-telemetry", "application/x-ndjson"),
_artifact(report_path, "perception-run-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": final_report["created_at_utc"],
"job_id": input_document["job_id"],
"input_sha256": input_document["input_sha256"],
"session_id": input_document["session_id"],
"source_id": input_document["source_id"],
"codec_epoch": input_document["codec_epoch"],
"timestamp_basis": "session-time-seconds",
"timeline_start_seconds": input_document["timeline_start_seconds"],
"timeline_end_seconds": input_document["timeline_end_seconds"],
"frames_processed": metrics["frames_processed"],
"ground_truth": False,
"artifacts": artifacts,
}
_write_json(output / "result.json", result)
partial_path.unlink()
print(
json.dumps(
{"result_id": result_id, "identity_sha256": identity_sha256},
sort_keys=True,
),
flush=True,
)
return 0
def main() -> int:
args = _arguments()
if args.command == "preflight":
return _preflight(args)
if args.command == "run":
return _run(args)
return _finalize(args)
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,687 @@
#!/usr/bin/env python3
"""Run source-paced LAB E8 YOLOX plus tracking without synchronous overlays."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import platform
import resource
import shutil
import tempfile
import threading
import time
from collections import Counter, deque
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from run_e5_instance_tracking import (
TwoStageTracker,
_detections,
_duplicate_pairs,
_infer,
_load_valid_fov,
_preprocess,
_read_timeline,
_track_document,
_validate_source,
_verify_model,
)
from run_e5_instance_tracking import (
_read_profile as _read_e5_profile,
)
from run_recorded_perception_epoch import (
_artifact,
_canonical_json,
_GpuTelemetry,
_percentiles,
_sha256,
_valid_sha256,
_validate_job,
_write_json,
)
PROFILE_SCHEMA = "missioncore.e8-realtime-tracking-profile/v1"
FRAME_SCHEMA = "missioncore.e8-realtime-tracking-frame/v1"
TELEMETRY_SCHEMA = "missioncore.e8-realtime-tracking-telemetry/v1"
REPORT_SCHEMA = "missioncore.e8-realtime-tracking-report/v1"
RESULT_SCHEMA = "missioncore.e8-realtime-tracking-result/v1"
IDENTITY_SCHEMA = "missioncore.e8-realtime-tracking-identity/v1"
PIPELINE_ID = "source-paced-yolox-bytetrack-latest-wins/v1"
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
for name in ("preflight", "run"):
command = commands.add_parser(name)
command.add_argument("--job", type=Path, required=True)
command.add_argument("--profile", type=Path, required=True)
command.add_argument("--valid-fov-root", type=Path, required=True)
command.add_argument("--model-root", type=Path, required=True)
if name == "run":
command.add_argument("--frames", type=Path, required=True)
command.add_argument("--timeline", type=Path, required=True)
command.add_argument("--output", type=Path, required=True)
command.add_argument("--triton-url", required=True)
command.add_argument("--free-bytes-floor", type=int, default=0)
command.add_argument("--orchestrator-sha256", required=True)
command.add_argument("--container-image", required=True)
command.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
return parser.parse_args()
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def _read_profile(path: Path) -> tuple[dict[str, Any], str]:
resolved = path.resolve(strict=True)
profile = _read_object(resolved)
if profile.get("schema_version") != PROFILE_SCHEMA:
raise RuntimeError("LAB E8 profile schema changed")
# Reuse the exhaustively tested E5 detector/tracker validation by changing
# only the schema in memory. No E5 defaults are inferred.
e5_shape = dict(profile)
e5_shape["schema_version"] = "missioncore.e5-tracking-profile/v1"
temporary: Path | None = None
try:
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
prefix="lab-e8-e5-validation-",
suffix=".json",
delete=False,
) as stream:
json.dump(e5_shape, stream, sort_keys=True, separators=(",", ":"))
temporary = Path(stream.name)
_read_e5_profile(temporary)
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)
realtime = profile.get("realtime")
acceptance = profile.get("acceptance")
if not isinstance(realtime, dict) or not isinstance(acceptance, dict):
raise RuntimeError("LAB E8 realtime/acceptance configuration is missing")
mode = profile.get("mode")
capacity = realtime.get("queue_capacity")
speed = realtime.get("speed")
delay = realtime.get("consumer_delay_ms")
stale = realtime.get("stale_after_ms")
unavailable = realtime.get("unavailable_after_ms")
if (
mode not in {"pilot", "qualification", "overload-negative-control"}
or realtime.get("queue_policy") != "bounded-latest-wins"
or not isinstance(capacity, int)
or isinstance(capacity, bool)
or not 1 <= capacity <= 8
or not isinstance(speed, int | float)
or isinstance(speed, bool)
or not 0.1 <= float(speed) <= 10.0
or not isinstance(delay, int | float)
or isinstance(delay, bool)
or not 0 <= float(delay) <= 5000
or not isinstance(stale, int | float)
or not isinstance(unavailable, int | float)
or not 0 < float(stale) < float(unavailable)
):
raise RuntimeError("LAB E8 scheduling configuration is invalid")
numeric_acceptance = (
acceptance.get("minimum_effective_fps"),
acceptance.get("maximum_drop_fraction"),
acceptance.get("maximum_p95_result_age_ms"),
)
if (
any(
not isinstance(value, int | float)
or isinstance(value, bool)
or not math.isfinite(float(value))
or float(value) < 0
for value in numeric_acceptance
)
or float(acceptance["maximum_drop_fraction"]) > 1
or not isinstance(acceptance.get("require_zero_failures"), bool)
or not isinstance(acceptance.get("expect_overload"), bool)
):
raise RuntimeError("LAB E8 acceptance configuration is invalid")
return profile, _sha256(resolved)
@dataclass(frozen=True, slots=True)
class FrameEnvelope:
frame_index: int
path: Path
timeline: dict[str, Any]
scheduled_monotonic: float
decoded_monotonic: float
image: np.ndarray
decode_ms: float
source_release_lag_ms: float
class LatestWinsQueue:
def __init__(self, capacity: int) -> None:
self.capacity = capacity
self.items: deque[FrameEnvelope] = deque()
self.condition = threading.Condition()
self.maximum_depth = 0
self.published = 0
self.consumed = 0
self.dropped_overflow = 0
self.closed = False
def publish(self, item: FrameEnvelope) -> None:
with self.condition:
if self.closed:
raise RuntimeError("cannot publish to a closed LAB E8 queue")
if len(self.items) == self.capacity:
self.items.popleft()
self.dropped_overflow += 1
self.items.append(item)
self.published += 1
self.maximum_depth = max(self.maximum_depth, len(self.items))
self.condition.notify()
def take(self) -> FrameEnvelope | None:
with self.condition:
self.condition.wait_for(lambda: bool(self.items) or self.closed)
if not self.items:
return None
value = self.items.popleft()
self.consumed += 1
return value
def close(self) -> None:
with self.condition:
self.closed = True
self.condition.notify_all()
def snapshot(self) -> dict[str, int | bool]:
with self.condition:
return {
"capacity": self.capacity,
"final_depth": len(self.items),
"maximum_depth": self.maximum_depth,
"published": self.published,
"consumed": self.consumed,
"dropped_overflow": self.dropped_overflow,
"closed": self.closed,
}
def _wait_until(deadline: float) -> None:
while True:
remaining = deadline - time.perf_counter()
if remaining <= 0:
return
time.sleep(min(remaining, 0.02))
def _health(age_ms: float, realtime: dict[str, Any]) -> str:
if age_ms >= float(realtime["unavailable_after_ms"]):
return "unavailable"
if age_ms >= float(realtime["stale_after_ms"]):
return "stale"
return "healthy"
def _producer(
*,
queue: LatestWinsQueue,
frame_paths: list[Path],
timeline_rows: list[dict[str, Any]],
replay_started: float,
speed: float,
error: list[BaseException],
) -> None:
from PIL import Image
first_session = float(timeline_rows[0]["session_seconds"])
try:
for frame_index, (path, row) in enumerate(zip(frame_paths, timeline_rows, strict=True)):
scheduled = replay_started + (float(row["session_seconds"]) - first_session) / speed
_wait_until(scheduled)
decode_started = time.perf_counter()
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
decoded = time.perf_counter()
if image.shape != (600, 800, 3):
raise RuntimeError("LAB E8 input resolution changed")
queue.publish(
FrameEnvelope(
frame_index=frame_index,
path=path,
timeline=row,
scheduled_monotonic=scheduled,
decoded_monotonic=decoded,
image=image,
decode_ms=(decoded - decode_started) * 1000.0,
source_release_lag_ms=max(0.0, (decode_started - scheduled) * 1000.0),
)
)
except BaseException as exc:
error.append(exc)
finally:
queue.close()
def _assert_disk_floor(path: Path, floor: int, frame: int) -> None:
if floor < 0:
raise RuntimeError("LAB E8 disk floor is invalid")
free = shutil.disk_usage(path).free
if floor and free < floor:
raise RuntimeError(f"LAB E8 crossed the D-backed free-space floor at frame {frame}")
def _preflight(args: argparse.Namespace) -> int:
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = _read_profile(args.profile)
_validate_source(job, profile)
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, profile)
model_files = _verify_model(profile, args.model_root)
from scipy.optimize import linear_sum_assignment # noqa: F401
print(
json.dumps(
{
"state": "preflight-ready",
"job_id": job["job_id"],
"profile_sha256": profile_sha256,
"model_files": model_files,
"valid_fov": valid_fov,
"valid_pixels": int(valid_mask.sum()),
},
sort_keys=True,
),
flush=True,
)
return 0
def _run(args: argparse.Namespace) -> int:
from PIL import Image
from scipy.optimize import linear_sum_assignment
if not _valid_sha256(args.orchestrator_sha256):
raise RuntimeError("LAB E8 orchestrator SHA-256 is invalid")
if not args.triton_url.startswith("http://") or len(args.triton_url) > 256:
raise RuntimeError("LAB E8 Triton URL is invalid")
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = _read_profile(args.profile)
_validate_source(job, profile)
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, profile)
model_files = _verify_model(profile, args.model_root)
frames_root = args.frames.resolve(strict=True)
frame_paths = sorted(frames_root.glob("frame-*.png"))
if not frame_paths or [path.name for path in frame_paths] != [
f"frame-{index:06d}.png" for index in range(1, len(frame_paths) + 1)
]:
raise RuntimeError("LAB E8 decoded frame sequence changed")
timeline_path = args.timeline.resolve(strict=True)
timeline_rows = _read_timeline(timeline_path, len(frame_paths))
input_timeline = job["input"]["timeline"]
if float(timeline_rows[0]["session_seconds"]) < float(input_timeline["start_seconds"]) or float(
timeline_rows[-1]["session_seconds"]
) > float(input_timeline["end_seconds"]):
raise RuntimeError("LAB E8 clip escaped the source timeline")
output = args.output.resolve()
if output.exists():
raise RuntimeError("LAB E8 output must be absent")
output.mkdir(mode=0o700, parents=True, exist_ok=False)
_assert_disk_floor(output, args.free_bytes_floor, 0)
tracker = TwoStageTracker(profile["tracking"])
linear_sum_assignment(np.zeros((1, 1), dtype=np.float64))
with Image.open(frame_paths[0]) as opened:
warm_image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
warm_tensor = _preprocess(warm_image, valid_mask, profile)
_infer(args.triton_url, profile["model"], warm_tensor)
del warm_tensor, warm_image
realtime = profile["realtime"]
queue = LatestWinsQueue(int(realtime["queue_capacity"]))
producer_errors: list[BaseException] = []
latency = {
name: []
for name in (
"image_decode_ms",
"source_release_lag_ms",
"queue_wait_ms",
"preprocess_ms",
"triton_request_ms",
"postprocess_ms",
"tracking_ms",
"processing_ms",
"result_age_ms",
)
}
detection_labels: Counter[str] = Counter()
track_labels: Counter[str] = Counter()
rejection_reasons: Counter[str] = Counter()
health_counts: Counter[str] = Counter()
unique_track_ids: set[int] = set()
duplicate_pairs = 0
failures = 0
metadata_path = output / "frames.jsonl"
telemetry_path = output / "telemetry.jsonl"
gpu_path = output / "gpu-telemetry.jsonl"
disk_before = shutil.disk_usage(output).free
replay_started = time.perf_counter() + 0.25
producer = threading.Thread(
target=_producer,
kwargs={
"queue": queue,
"frame_paths": frame_paths,
"timeline_rows": timeline_rows,
"replay_started": replay_started,
"speed": float(realtime["speed"]),
"error": producer_errors,
},
name="lab-e8-source-producer",
daemon=True,
)
producer.start()
with (
metadata_path.open("x", encoding="utf-8", newline="\n") as metadata_stream,
telemetry_path.open("x", encoding="utf-8", newline="\n") as telemetry_stream,
gpu_path.open("x", encoding="utf-8", newline="\n") as gpu_stream,
_GpuTelemetry(gpu_stream, args.telemetry_interval_seconds) as gpu_telemetry,
):
while (envelope := queue.take()) is not None:
processing_started = time.perf_counter()
latency["image_decode_ms"].append(envelope.decode_ms)
latency["source_release_lag_ms"].append(envelope.source_release_lag_ms)
latency["queue_wait_ms"].append(
max(0.0, (processing_started - envelope.decoded_monotonic) * 1000.0)
)
try:
preprocess_started = time.perf_counter()
tensor = _preprocess(envelope.image, valid_mask, profile)
latency["preprocess_ms"].append((time.perf_counter() - preprocess_started) * 1000.0)
output_tensor, request_ms = _infer(args.triton_url, profile["model"], tensor)
latency["triton_request_ms"].append(request_ms)
postprocess_started = time.perf_counter()
detections, rejected = _detections(output_tensor, profile, valid_mask)
latency["postprocess_ms"].append(
(time.perf_counter() - postprocess_started) * 1000.0
)
rejection_reasons.update(rejected)
detection_labels.update(str(item["label"]) for item in detections)
tracking_started = time.perf_counter()
tracks = tracker.update(detections, envelope.frame_index)
latency["tracking_ms"].append((time.perf_counter() - tracking_started) * 1000.0)
duplicate_pairs += _duplicate_pairs(tracks)
for track in tracks:
unique_track_ids.add(track.track_id)
track_labels[track.label] += 1
delay_seconds = float(realtime["consumer_delay_ms"]) / 1000.0
if delay_seconds:
time.sleep(delay_seconds)
except Exception:
failures += 1
raise
published = time.perf_counter()
processing_ms = (published - processing_started) * 1000.0
result_age_ms = max(0.0, (published - envelope.scheduled_monotonic) * 1000.0)
latency["processing_ms"].append(processing_ms)
latency["result_age_ms"].append(result_age_ms)
health = _health(result_age_ms, realtime)
health_counts[health] += 1
frame_document = {
"schema_version": FRAME_SCHEMA,
"frame_index": envelope.frame_index,
"sequence": envelope.frame_index + 1,
"source_frame_index": envelope.timeline["source_frame_index"],
"source_sequence": envelope.timeline["source_sequence"],
"session_seconds": round(float(envelope.timeline["session_seconds"]), 9),
"detections": detections,
"tracks": [_track_document(track) for track in tracks],
"delivery": {"health": health, "result_age_ms": round(result_age_ms, 6)},
}
metadata_stream.write(
json.dumps(
frame_document,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
queue_state = queue.snapshot()
telemetry_stream.write(
json.dumps(
{
"schema_version": TELEMETRY_SCHEMA,
"frame_index": envelope.frame_index,
"session_seconds": frame_document["session_seconds"],
"health": health,
"result_age_ms": round(result_age_ms, 6),
"processing_ms": round(processing_ms, 6),
"queue_depth_after_take": queue_state["final_depth"],
"queue_dropped_overflow": queue_state["dropped_overflow"],
},
sort_keys=True,
separators=(",", ":"),
)
+ "\n"
)
completed = int(queue_state["consumed"])
if completed % 100 == 0:
metadata_stream.flush()
telemetry_stream.flush()
gpu_stream.flush()
_assert_disk_floor(output, args.free_bytes_floor, completed)
print(
json.dumps(
{
"phase": "realtime-tracking",
"frames_consumed": completed,
"frames_published": queue_state["published"],
"frames_dropped": queue_state["dropped_overflow"],
"maximum_queue_depth": queue_state["maximum_depth"],
},
sort_keys=True,
),
flush=True,
)
producer.join(timeout=5)
if producer.is_alive():
raise RuntimeError("LAB E8 producer did not terminate")
if producer_errors:
raise RuntimeError("LAB E8 producer failed") from producer_errors[0]
metadata_stream.flush()
telemetry_stream.flush()
os.fsync(metadata_stream.fileno())
os.fsync(telemetry_stream.fileno())
finished = time.perf_counter()
queue_state = queue.snapshot()
source_span = (
float(timeline_rows[-1]["session_seconds"]) - float(timeline_rows[0]["session_seconds"])
) / float(realtime["speed"])
wall_seconds = finished - replay_started
effective_fps = int(queue_state["consumed"]) / max(source_span, wall_seconds, 1e-9)
drop_fraction = int(queue_state["dropped_overflow"]) / len(frame_paths)
result_age = _percentiles(latency["result_age_ms"])
acceptance = profile["acceptance"]
expect_overload = bool(acceptance["expect_overload"])
checks = {
"queue_bounded": int(queue_state["maximum_depth"]) <= int(queue_state["capacity"]),
"producer_accounting": int(queue_state["published"]) == len(frame_paths),
"consumer_accounting": (
int(queue_state["consumed"]) + int(queue_state["dropped_overflow"]) == len(frame_paths)
),
"zero_failures": failures == 0,
}
if expect_overload:
checks["overload_drop_observed"] = int(queue_state["dropped_overflow"]) > 0
checks["overload_degraded_or_stale_observed"] = (
health_counts["stale"] + health_counts["unavailable"] > 0
)
else:
checks.update(
{
"minimum_effective_fps": effective_fps
>= float(acceptance["minimum_effective_fps"]),
"maximum_drop_fraction": drop_fraction
<= float(acceptance["maximum_drop_fraction"]),
"maximum_p95_result_age_ms": float(result_age["p95"])
<= float(acceptance["maximum_p95_result_age_ms"]),
}
)
accepted = all(checks.values())
identity = {
"schema_version": IDENTITY_SCHEMA,
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": job["input"]["session_id"],
"source_id": job["input"]["source_id"],
"selection": {
"frame_count": len(frame_paths),
"source_start_frame_index": timeline_rows[0]["source_frame_index"],
"source_end_frame_index": timeline_rows[-1]["source_frame_index"],
"timeline_start_seconds": timeline_rows[0]["session_seconds"],
"timeline_end_seconds": timeline_rows[-1]["session_seconds"],
"timeline_sha256": _sha256(timeline_path),
},
"configuration": {
"pipeline": PIPELINE_ID,
"profile_sha256": profile_sha256,
"profile": profile,
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
"orchestrator_sha256": args.orchestrator_sha256,
"container_image": args.container_image,
"valid_fov": valid_fov,
},
"models": {"detector": profile["model"], "files": model_files},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"e8-realtime-tracking-{identity_sha256}"
metrics = {
"frames_expected": len(frame_paths),
"frames_published": queue_state["published"],
"frames_processed": queue_state["consumed"],
"frames_dropped": queue_state["dropped_overflow"],
"frames_failed": failures,
"drop_fraction": round(drop_fraction, 9),
"source_span_seconds": round(source_span, 6),
"replay_wall_seconds": round(wall_seconds, 6),
"effective_frames_per_second": round(effective_fps, 6),
"queue": queue_state,
"latency_ms": {name: _percentiles(values) for name, values in latency.items()},
"health_counts": dict(sorted(health_counts.items())),
"detections": int(sum(detection_labels.values())),
"detections_by_label": dict(sorted(detection_labels.items())),
"detection_rejections": dict(sorted(rejection_reasons.items())),
"unique_confirmed_tracks": len(unique_track_ids),
"track_observations": int(sum(track_labels.values())),
"track_observations_by_label": dict(sorted(track_labels.items())),
"same_class_duplicate_pairs_iou_ge_0_8": duplicate_pairs,
"tracker_tracks_created": tracker.created,
"tracker_tracks_retired": tracker.retired,
"process_peak_rss_mib": round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024, 3),
"gpu_telemetry": gpu_telemetry.summary(),
"disk": {
"free_bytes_before_replay": disk_before,
"free_bytes_after_replay": shutil.disk_usage(output).free,
"free_bytes_floor": args.free_bytes_floor,
},
}
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "accepted" if accepted else "rejected",
"ground_truth": False,
"identity": identity,
"runtime": {
"hostname": platform.node(),
"platform": platform.platform(),
"python": platform.python_version(),
"numpy": np.__version__,
},
"metrics": metrics,
"acceptance": {
"accepted": accepted,
"checks": checks,
"navigation_or_safety_accepted": False,
},
"limitations": [
"Recorded source-paced replay, not a live K1 transport.",
"Generic COCO detector and IoU-only tracking are not safety validated.",
"No semantic model, LiDAR fusion or navigation actuation is executed in this gate.",
(
"Decoded PNG input is temporary lab transport; camera ingest decode "
"remains a separate gate."
),
],
}
report_path = output / "run-report.json"
_write_json(report_path, report)
artifacts = [
_artifact(metadata_path, "realtime-tracking-frames", "application/x-ndjson", FRAME_SCHEMA),
_artifact(
telemetry_path, "realtime-tracking-telemetry", "application/x-ndjson", TELEMETRY_SCHEMA
),
_artifact(gpu_path, "worker-gpu-telemetry", "application/x-ndjson"),
_artifact(report_path, "realtime-tracking-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": report["created_at_utc"],
"acceptance_state": report["state"],
"ground_truth": False,
"publication_scope": "recorded-realtime-qualification-only",
"frames_processed": queue_state["consumed"],
"artifacts": artifacts,
}
_write_json(output / "result.json", result)
print(
json.dumps(
{
"result_id": result_id,
"accepted": accepted,
"frames_processed": queue_state["consumed"],
"frames_dropped": queue_state["dropped_overflow"],
"effective_fps": round(effective_fps, 6),
"result_age_p95_ms": result_age["p95"],
},
sort_keys=True,
),
flush=True,
)
return 0 if accepted else 2
def main() -> int:
args = _arguments()
if args.command == "preflight":
return _preflight(args)
return _run(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,933 @@
#!/usr/bin/env python3
"""Qualify concurrent 10 Hz tracking and lower-rate semantic perception."""
from __future__ import annotations
import argparse
import hashlib
import importlib.metadata
import json
import math
import os
import platform
import resource
import shutil
import threading
import time
from collections import Counter
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from run_e4_full_session_segmentation import (
TARGET_CLASS_COUNT,
_dependency_manifest,
_load_model,
)
from run_e4_full_session_segmentation import (
_profile as _read_semantic_profile,
)
from run_e4_full_session_segmentation import (
_validate_source as _validate_semantic_source,
)
from run_e5_instance_tracking import (
TwoStageTracker,
_detections,
_duplicate_pairs,
_infer,
_load_valid_fov,
_preprocess,
_read_timeline,
_track_document,
_validate_source,
_verify_model,
)
from run_e8_realtime_tracking import (
FrameEnvelope,
LatestWinsQueue,
_wait_until,
)
from run_e8_realtime_tracking import (
_read_profile as _read_detector_profile,
)
from run_recorded_perception_epoch import (
_artifact,
_canonical_json,
_GpuTelemetry,
_percentiles,
_sha256,
_valid_sha256,
_validate_job,
_write_json,
)
PROFILE_SCHEMA = "missioncore.e9-multirate-perception-profile/v1"
DETECTOR_FRAME_SCHEMA = "missioncore.e9-multirate-detector-frame/v1"
SEMANTIC_FRAME_SCHEMA = "missioncore.e9-multirate-semantic-frame/v1"
MERGED_FRAME_SCHEMA = "missioncore.e9-multirate-merged-frame/v1"
REPORT_SCHEMA = "missioncore.e9-multirate-perception-report/v1"
RESULT_SCHEMA = "missioncore.e9-multirate-perception-result/v1"
IDENTITY_SCHEMA = "missioncore.e9-multirate-perception-identity/v1"
PIPELINE_ID = "concurrent-yolox-tracking-eomt-semantic-latest-wins/v1"
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
for name in ("preflight", "run"):
command = commands.add_parser(name)
command.add_argument("--job", type=Path, required=True)
command.add_argument("--profile", type=Path, required=True)
command.add_argument("--detector-profile", type=Path, required=True)
command.add_argument("--semantic-profile", type=Path, required=True)
command.add_argument("--valid-fov-root", type=Path, required=True)
command.add_argument("--model-root", type=Path, required=True)
command.add_argument("--cache", type=Path, required=True)
command.add_argument("--environment", type=Path, required=True)
if name == "run":
command.add_argument("--frames", type=Path, required=True)
command.add_argument("--timeline", type=Path, required=True)
command.add_argument("--output", type=Path, required=True)
command.add_argument("--triton-url", required=True)
command.add_argument("--free-bytes-floor", type=int, default=0)
command.add_argument("--orchestrator-sha256", required=True)
command.add_argument("--container-image", required=True)
command.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
return parser.parse_args()
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise RuntimeError(f"JSON root is not an object: {path}")
return value
def _read_profile(path: Path) -> tuple[dict[str, Any], str]:
resolved = path.resolve(strict=True)
profile = _read_object(resolved)
replay = profile.get("replay")
acceptance = profile.get("acceptance")
source = profile.get("source")
if (
profile.get("schema_version") != PROFILE_SCHEMA
or profile.get("mode") not in {"pilot", "qualification"}
or not isinstance(replay, dict)
or not isinstance(acceptance, dict)
or not isinstance(source, dict)
or source.get("source_id") != "sensor.camera.right"
or source.get("resolution") != [800, 600]
):
raise RuntimeError("LAB E9 profile contract changed")
speed = replay.get("speed")
detector_capacity = replay.get("detector_queue_capacity")
semantic_capacity = replay.get("semantic_queue_capacity")
semantic_stride = replay.get("semantic_sample_every_frames")
semantic_ttl = replay.get("semantic_ttl_ms")
if (
not _positive_number(speed)
or not 0.1 <= float(speed) <= 10
or not isinstance(detector_capacity, int)
or isinstance(detector_capacity, bool)
or not 1 <= detector_capacity <= 8
or not isinstance(semantic_capacity, int)
or isinstance(semantic_capacity, bool)
or not 1 <= semantic_capacity <= 4
or not isinstance(semantic_stride, int)
or isinstance(semantic_stride, bool)
or not 2 <= semantic_stride <= 30
or not _positive_number(semantic_ttl)
or not 100 <= float(semantic_ttl) <= 5000
):
raise RuntimeError("LAB E9 replay configuration is invalid")
required = (
"detector_minimum_effective_fps",
"detector_maximum_drop_fraction",
"detector_maximum_p95_result_age_ms",
"semantic_minimum_effective_fps",
"semantic_maximum_drop_fraction",
"semantic_maximum_p95_completion_age_ms",
"minimum_fresh_semantic_coverage",
)
if (
any(not _nonnegative_number(acceptance.get(name)) for name in required)
or float(acceptance["detector_maximum_drop_fraction"]) > 1
or float(acceptance["semantic_maximum_drop_fraction"]) > 1
or float(acceptance["minimum_fresh_semantic_coverage"]) > 1
or not isinstance(acceptance.get("require_zero_failures"), bool)
):
raise RuntimeError("LAB E9 acceptance configuration is invalid")
return profile, _sha256(resolved)
def _positive_number(value: object) -> bool:
return _nonnegative_number(value) and float(value) > 0
def _nonnegative_number(value: object) -> bool:
return (
isinstance(value, int | float)
and not isinstance(value, bool)
and math.isfinite(float(value))
and float(value) >= 0
)
@dataclass(frozen=True, slots=True)
class SemanticResult:
frame_index: int
source_frame_index: int
session_seconds: float
completed_monotonic: float
completion_age_ms: float
mask_sha256: str
class_pixels: dict[str, int]
class_fractions: dict[str, float]
class LatestSemantic:
def __init__(self) -> None:
self._lock = threading.Lock()
self._value: SemanticResult | None = None
def publish(self, value: SemanticResult) -> None:
with self._lock:
if self._value is not None and value.frame_index <= self._value.frame_index:
raise RuntimeError("LAB E9 semantic results are not monotonic")
self._value = value
def snapshot(self) -> SemanticResult | None:
with self._lock:
return self._value
def _producer(
*,
detector_queue: LatestWinsQueue,
semantic_queue: LatestWinsQueue,
semantic_stride: int,
frame_paths: list[Path],
timeline_rows: list[dict[str, Any]],
replay_started: float,
speed: float,
error: list[BaseException],
) -> None:
from PIL import Image
first_session = float(timeline_rows[0]["session_seconds"])
try:
for frame_index, (path, row) in enumerate(zip(frame_paths, timeline_rows, strict=True)):
scheduled = replay_started + (float(row["session_seconds"]) - first_session) / speed
_wait_until(scheduled)
decode_started = time.perf_counter()
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
decoded = time.perf_counter()
if image.shape != (600, 800, 3):
raise RuntimeError("LAB E9 input resolution changed")
envelope = FrameEnvelope(
frame_index=frame_index,
path=path,
timeline=row,
scheduled_monotonic=scheduled,
decoded_monotonic=decoded,
image=image,
decode_ms=(decoded - decode_started) * 1000.0,
source_release_lag_ms=max(0.0, (decode_started - scheduled) * 1000.0),
)
detector_queue.publish(envelope)
if frame_index % semantic_stride == 0:
semantic_queue.publish(envelope)
except BaseException as exc:
error.append(exc)
finally:
detector_queue.close()
semantic_queue.close()
def _semantic_worker(
*,
queue: LatestWinsQueue,
latest: LatestSemantic,
stream: Any,
valid_mask: np.ndarray,
target_lut: np.ndarray,
target_names: dict[int, str],
infer: Any,
latency: dict[str, list[float]],
failures: list[BaseException],
) -> None:
try:
while (envelope := queue.take()) is not None:
started = time.perf_counter()
latency["image_decode_ms"].append(envelope.decode_ms)
latency["queue_wait_ms"].append(
max(0.0, (started - envelope.decoded_monotonic) * 1000.0)
)
fill_started = time.perf_counter()
model_input = np.where(valid_mask[..., None], envelope.image, 0).astype(np.uint8)
latency["valid_fov_fill_ms"].append((time.perf_counter() - fill_started) * 1000.0)
semantic, measured = infer(model_input)
if semantic.max() >= len(target_lut):
raise RuntimeError("LAB E9 EoMT emitted an unknown category")
target = target_lut[semantic]
target = target.copy()
target[~valid_mask] = 0
for name, value in measured.items():
latency[name].append(float(value))
completed = time.perf_counter()
completion_age_ms = max(0.0, (completed - envelope.scheduled_monotonic) * 1000.0)
latency["completion_age_ms"].append(completion_age_ms)
latency["processing_ms"].append((completed - started) * 1000.0)
counts = np.bincount(target[valid_mask], minlength=TARGET_CLASS_COUNT)
class_pixels = {
target_names[index]: int(counts[index])
for index in range(1, TARGET_CLASS_COUNT)
if int(counts[index]) > 0
}
valid_pixels = int(valid_mask.sum())
result = SemanticResult(
frame_index=envelope.frame_index,
source_frame_index=int(envelope.timeline["source_frame_index"]),
session_seconds=float(envelope.timeline["session_seconds"]),
completed_monotonic=completed,
completion_age_ms=completion_age_ms,
mask_sha256=hashlib.sha256(target.tobytes()).hexdigest(),
class_pixels=class_pixels,
class_fractions={
name: round(count / valid_pixels, 9) for name, count in class_pixels.items()
},
)
latest.publish(result)
stream.write(
json.dumps(
{
"schema_version": SEMANTIC_FRAME_SCHEMA,
"frame_index": result.frame_index,
"source_frame_index": result.source_frame_index,
"session_seconds": round(result.session_seconds, 9),
"completion_age_ms": round(result.completion_age_ms, 6),
"mask_sha256": result.mask_sha256,
"class_pixels": result.class_pixels,
"class_fractions": result.class_fractions,
},
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
except BaseException as exc:
failures.append(exc)
def _semantic_binding(
semantic: SemanticResult | None,
*,
frame_session_seconds: float,
ttl_ms: float,
) -> dict[str, Any]:
if semantic is None:
return {
"status": "unavailable",
"source_frame_index": None,
"source_age_ms": None,
"completion_age_ms": None,
"mask_sha256": None,
}
source_age_ms = max(0.0, (frame_session_seconds - semantic.session_seconds) * 1000.0)
return {
"status": "fresh" if source_age_ms <= ttl_ms else "stale",
"source_frame_index": semantic.source_frame_index,
"source_age_ms": round(source_age_ms, 6),
"completion_age_ms": round(semantic.completion_age_ms, 6),
"mask_sha256": semantic.mask_sha256,
}
def _semantic_infer_factory(
processor: Any,
model: Any,
device: Any,
) -> Any:
import torch
from PIL import Image
def infer(image: np.ndarray) -> tuple[np.ndarray, dict[str, float]]:
processor_started = time.perf_counter()
inputs = processor(images=Image.fromarray(image), return_tensors="pt")
processor_ms = (time.perf_counter() - processor_started) * 1000.0
torch.cuda.synchronize()
transfer_started = time.perf_counter()
inputs = {
name: value.to(device) if isinstance(value, torch.Tensor) else value
for name, value in inputs.items()
}
torch.cuda.synchronize()
transfer_ms = (time.perf_counter() - transfer_started) * 1000.0
forward_started = time.perf_counter()
with torch.inference_mode(), torch.amp.autocast("cuda", dtype=torch.float16):
outputs = model(**inputs)
torch.cuda.synchronize()
forward_ms = (time.perf_counter() - forward_started) * 1000.0
post_started = time.perf_counter()
semantic = processor.post_process_semantic_segmentation(
outputs,
target_sizes=[image.shape[:2]],
)[0]
semantic = semantic.detach().cpu().numpy().astype(np.uint8)
post_ms = (time.perf_counter() - post_started) * 1000.0
del inputs, outputs
return semantic, {
"processor_ms": processor_ms,
"host_to_device_ms": transfer_ms,
"forward_ms": forward_ms,
"model_postprocess_ms": post_ms,
}
return infer
def _preflight(args: argparse.Namespace) -> int:
import torch
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = _read_profile(args.profile)
detector_profile, detector_profile_sha256 = _read_detector_profile(args.detector_profile)
semantic_profile, semantic_profile_sha256 = _read_semantic_profile(args.semantic_profile)
_validate_source(job, detector_profile)
_validate_semantic_source(job, semantic_profile)
if profile["source"] != detector_profile["source"]:
raise RuntimeError("LAB E9 source and detector bindings differ")
if profile["source"] != {
key: semantic_profile["source"][key]
for key in ("source_id", "resolution", "calibration_slot", "calibration_sha256")
}:
raise RuntimeError("LAB E9 source and semantic bindings differ")
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, detector_profile)
detector_files = _verify_model(detector_profile, args.model_root)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != semantic_profile_sha256:
raise RuntimeError("LAB E9 semantic dependencies belong to another profile")
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for LAB E9")
device = torch.device("cuda:0")
processor, model, _labels, _lut, semantic_files = _load_model(
semantic_profile,
args.cache.resolve(strict=True),
device,
)
del processor, model, _labels, _lut
torch.cuda.empty_cache()
print(
json.dumps(
{
"state": "preflight-ready",
"job_id": job["job_id"],
"profile_sha256": profile_sha256,
"detector_profile_sha256": detector_profile_sha256,
"semantic_profile_sha256": semantic_profile_sha256,
"detector_files": detector_files,
"semantic_files": semantic_files,
"valid_fov": valid_fov,
"cuda_device": torch.cuda.get_device_name(),
},
sort_keys=True,
),
flush=True,
)
return 0
def _run(args: argparse.Namespace) -> int:
import torch
import transformers
from PIL import Image
from scipy.optimize import linear_sum_assignment
if not _valid_sha256(args.orchestrator_sha256):
raise RuntimeError("LAB E9 orchestrator SHA-256 is invalid")
job = _validate_job(args.job.resolve(strict=True))
profile, profile_sha256 = _read_profile(args.profile)
detector_profile, detector_profile_sha256 = _read_detector_profile(args.detector_profile)
semantic_profile, semantic_profile_sha256 = _read_semantic_profile(args.semantic_profile)
_validate_source(job, detector_profile)
_validate_semantic_source(job, semantic_profile)
valid_mask, valid_fov = _load_valid_fov(args.valid_fov_root, job, detector_profile)
detector_files = _verify_model(detector_profile, args.model_root)
dependency = _dependency_manifest(args.environment.resolve(strict=True))
if dependency["identity"]["profile_sha256"] != semantic_profile_sha256:
raise RuntimeError("LAB E9 semantic dependency identity changed")
frames_root = args.frames.resolve(strict=True)
frame_paths = sorted(frames_root.glob("frame-*.png"))
if not frame_paths or [path.name for path in frame_paths] != [
f"frame-{index:06d}.png" for index in range(1, len(frame_paths) + 1)
]:
raise RuntimeError("LAB E9 decoded frame sequence changed")
timeline_path = args.timeline.resolve(strict=True)
timeline_rows = _read_timeline(timeline_path, len(frame_paths))
output = args.output.resolve()
if output.exists():
raise RuntimeError("LAB E9 output must be absent")
output.mkdir(mode=0o700, parents=True, exist_ok=False)
_assert_disk_floor(output, args.free_bytes_floor, 0)
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for LAB E9")
device = torch.device("cuda:0")
processor, semantic_model, semantic_labels, target_lut, semantic_files = _load_model(
semantic_profile,
args.cache.resolve(strict=True),
device,
)
target_names = {
int(key): str(value) for key, value in semantic_profile["target_taxonomy"].items()
}
if set(target_names) != set(range(TARGET_CLASS_COUNT)):
raise RuntimeError("LAB E9 target taxonomy changed")
infer_semantic = _semantic_infer_factory(processor, semantic_model, device)
tracker = TwoStageTracker(detector_profile["tracking"])
linear_sum_assignment(np.zeros((1, 1), dtype=np.float64))
with Image.open(frame_paths[0]) as opened:
warm_image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
detector_tensor = _preprocess(warm_image, valid_mask, detector_profile)
_infer(args.triton_url, detector_profile["model"], detector_tensor)
semantic_input = np.where(valid_mask[..., None], warm_image, 0).astype(np.uint8)
warm_semantic, _warm_latency = infer_semantic(semantic_input)
del detector_tensor, semantic_input, warm_semantic, _warm_latency, warm_image
replay = profile["replay"]
detector_queue = LatestWinsQueue(int(replay["detector_queue_capacity"]))
semantic_queue = LatestWinsQueue(int(replay["semantic_queue_capacity"]))
latest_semantic = LatestSemantic()
producer_errors: list[BaseException] = []
semantic_errors: list[BaseException] = []
detector_failures = 0
detector_latency = {
name: []
for name in (
"image_decode_ms",
"source_release_lag_ms",
"queue_wait_ms",
"preprocess_ms",
"triton_request_ms",
"postprocess_ms",
"tracking_ms",
"processing_ms",
"result_age_ms",
)
}
semantic_latency = {
name: []
for name in (
"image_decode_ms",
"queue_wait_ms",
"valid_fov_fill_ms",
"processor_ms",
"host_to_device_ms",
"forward_ms",
"model_postprocess_ms",
"processing_ms",
"completion_age_ms",
)
}
detector_labels: Counter[str] = Counter()
track_labels: Counter[str] = Counter()
semantic_status: Counter[str] = Counter()
unique_tracks: set[int] = set()
duplicate_pairs = 0
detector_path = output / "detector-frames.jsonl"
semantic_path = output / "semantic-frames.jsonl"
merged_path = output / "merged-frames.jsonl"
gpu_path = output / "gpu-telemetry.jsonl"
disk_before = shutil.disk_usage(output).free
with (
detector_path.open("x", encoding="utf-8", newline="\n") as detector_stream,
semantic_path.open("x", encoding="utf-8", newline="\n") as semantic_stream,
merged_path.open("x", encoding="utf-8", newline="\n") as merged_stream,
gpu_path.open("x", encoding="utf-8", newline="\n") as gpu_stream,
_GpuTelemetry(gpu_stream, args.telemetry_interval_seconds) as gpu_telemetry,
):
semantic_thread = threading.Thread(
target=_semantic_worker,
kwargs={
"queue": semantic_queue,
"latest": latest_semantic,
"stream": semantic_stream,
"valid_mask": valid_mask,
"target_lut": target_lut,
"target_names": target_names,
"infer": infer_semantic,
"latency": semantic_latency,
"failures": semantic_errors,
},
name="lab-e9-semantic-consumer",
daemon=True,
)
semantic_thread.start()
replay_started = time.perf_counter() + 0.25
producer = threading.Thread(
target=_producer,
kwargs={
"detector_queue": detector_queue,
"semantic_queue": semantic_queue,
"semantic_stride": int(replay["semantic_sample_every_frames"]),
"frame_paths": frame_paths,
"timeline_rows": timeline_rows,
"replay_started": replay_started,
"speed": float(replay["speed"]),
"error": producer_errors,
},
name="lab-e9-source-producer",
daemon=True,
)
producer.start()
while (envelope := detector_queue.take()) is not None:
started = time.perf_counter()
detector_latency["image_decode_ms"].append(envelope.decode_ms)
detector_latency["source_release_lag_ms"].append(envelope.source_release_lag_ms)
detector_latency["queue_wait_ms"].append(
max(0.0, (started - envelope.decoded_monotonic) * 1000.0)
)
try:
preprocess_started = time.perf_counter()
tensor = _preprocess(envelope.image, valid_mask, detector_profile)
detector_latency["preprocess_ms"].append(
(time.perf_counter() - preprocess_started) * 1000.0
)
output_tensor, request_ms = _infer(
args.triton_url, detector_profile["model"], tensor
)
detector_latency["triton_request_ms"].append(request_ms)
post_started = time.perf_counter()
detections, _rejected = _detections(output_tensor, detector_profile, valid_mask)
detector_latency["postprocess_ms"].append(
(time.perf_counter() - post_started) * 1000.0
)
tracking_started = time.perf_counter()
tracks = tracker.update(detections, envelope.frame_index)
detector_latency["tracking_ms"].append(
(time.perf_counter() - tracking_started) * 1000.0
)
except Exception:
detector_failures += 1
raise
completed = time.perf_counter()
processing_ms = (completed - started) * 1000.0
result_age_ms = max(0.0, (completed - envelope.scheduled_monotonic) * 1000.0)
detector_latency["processing_ms"].append(processing_ms)
detector_latency["result_age_ms"].append(result_age_ms)
detector_labels.update(str(item["label"]) for item in detections)
duplicate_pairs += _duplicate_pairs(tracks)
for track in tracks:
unique_tracks.add(track.track_id)
track_labels[track.label] += 1
semantic_binding = _semantic_binding(
latest_semantic.snapshot(),
frame_session_seconds=float(envelope.timeline["session_seconds"]),
ttl_ms=float(replay["semantic_ttl_ms"]),
)
semantic_status[str(semantic_binding["status"])] += 1
detector_document = {
"schema_version": DETECTOR_FRAME_SCHEMA,
"frame_index": envelope.frame_index,
"source_frame_index": envelope.timeline["source_frame_index"],
"session_seconds": round(float(envelope.timeline["session_seconds"]), 9),
"result_age_ms": round(result_age_ms, 6),
"detections": detections,
"tracks": [_track_document(track) for track in tracks],
}
detector_stream.write(
json.dumps(
detector_document,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
merged_stream.write(
json.dumps(
{
"schema_version": MERGED_FRAME_SCHEMA,
"frame_index": envelope.frame_index,
"source_frame_index": envelope.timeline["source_frame_index"],
"session_seconds": detector_document["session_seconds"],
"detector_result_age_ms": round(result_age_ms, 6),
"tracks": detector_document["tracks"],
"semantic": semantic_binding,
},
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
consumed = int(detector_queue.snapshot()["consumed"])
if consumed % 100 == 0:
detector_stream.flush()
merged_stream.flush()
semantic_stream.flush()
gpu_stream.flush()
_assert_disk_floor(output, args.free_bytes_floor, consumed)
print(
json.dumps(
{
"phase": "multirate",
"detector_consumed": consumed,
"detector_dropped": detector_queue.snapshot()["dropped_overflow"],
"semantic_consumed": semantic_queue.snapshot()["consumed"],
"semantic_dropped": semantic_queue.snapshot()["dropped_overflow"],
},
sort_keys=True,
),
flush=True,
)
producer.join(timeout=5)
if producer.is_alive() or producer_errors:
raise RuntimeError("LAB E9 producer failed")
semantic_thread.join(timeout=30)
if semantic_thread.is_alive():
raise RuntimeError("LAB E9 semantic consumer did not terminate")
if semantic_errors:
raise RuntimeError("LAB E9 semantic consumer failed") from semantic_errors[0]
detector_stream.flush()
semantic_stream.flush()
merged_stream.flush()
os.fsync(detector_stream.fileno())
os.fsync(semantic_stream.fileno())
os.fsync(merged_stream.fileno())
replay_finished = time.perf_counter()
detector_state = detector_queue.snapshot()
semantic_state = semantic_queue.snapshot()
source_span = (
float(timeline_rows[-1]["session_seconds"]) - float(timeline_rows[0]["session_seconds"])
) / float(replay["speed"])
replay_wall = replay_finished - replay_started
scheduled_semantic = ((len(frame_paths) - 1) // int(replay["semantic_sample_every_frames"])) + 1
detector_fps = int(detector_state["consumed"]) / max(source_span, replay_wall, 1e-9)
semantic_fps = int(semantic_state["consumed"]) / max(source_span, replay_wall, 1e-9)
detector_drop_fraction = int(detector_state["dropped_overflow"]) / len(frame_paths)
semantic_drop_fraction = int(semantic_state["dropped_overflow"]) / scheduled_semantic
fresh_coverage = semantic_status["fresh"] / max(1, int(detector_state["consumed"]))
detector_percentiles = {name: _percentiles(values) for name, values in detector_latency.items()}
semantic_percentiles = {name: _percentiles(values) for name, values in semantic_latency.items()}
acceptance = profile["acceptance"]
checks = {
"detector_queue_bounded": int(detector_state["maximum_depth"])
<= int(detector_state["capacity"]),
"detector_accounting": int(detector_state["consumed"])
+ int(detector_state["dropped_overflow"])
== len(frame_paths),
"detector_minimum_effective_fps": detector_fps
>= float(acceptance["detector_minimum_effective_fps"]),
"detector_maximum_drop_fraction": detector_drop_fraction
<= float(acceptance["detector_maximum_drop_fraction"]),
"detector_maximum_p95_result_age_ms": float(detector_percentiles["result_age_ms"]["p95"])
<= float(acceptance["detector_maximum_p95_result_age_ms"]),
"semantic_queue_bounded": int(semantic_state["maximum_depth"])
<= int(semantic_state["capacity"]),
"semantic_accounting": int(semantic_state["consumed"])
+ int(semantic_state["dropped_overflow"])
== scheduled_semantic,
"semantic_minimum_effective_fps": semantic_fps
>= float(acceptance["semantic_minimum_effective_fps"]),
"semantic_maximum_drop_fraction": semantic_drop_fraction
<= float(acceptance["semantic_maximum_drop_fraction"]),
"semantic_maximum_p95_completion_age_ms": float(
semantic_percentiles["completion_age_ms"]["p95"]
)
<= float(acceptance["semantic_maximum_p95_completion_age_ms"]),
"minimum_fresh_semantic_coverage": fresh_coverage
>= float(acceptance["minimum_fresh_semantic_coverage"]),
"zero_failures": detector_failures == 0 and not semantic_errors,
}
accepted = all(checks.values())
identity = {
"schema_version": IDENTITY_SCHEMA,
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": job["input"]["session_id"],
"source_id": job["input"]["source_id"],
"selection": {
"frame_count": len(frame_paths),
"source_start_frame_index": timeline_rows[0]["source_frame_index"],
"source_end_frame_index": timeline_rows[-1]["source_frame_index"],
"timeline_start_seconds": timeline_rows[0]["session_seconds"],
"timeline_end_seconds": timeline_rows[-1]["session_seconds"],
"timeline_sha256": _sha256(timeline_path),
},
"configuration": {
"pipeline": PIPELINE_ID,
"profile": profile,
"profile_sha256": profile_sha256,
"detector_profile_sha256": detector_profile_sha256,
"semantic_profile_sha256": semantic_profile_sha256,
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
"orchestrator_sha256": args.orchestrator_sha256,
"container_image": args.container_image,
"valid_fov": valid_fov,
},
"models": {
"detector": detector_profile["model"],
"detector_files": detector_files,
"semantic": semantic_profile["model"],
"semantic_files": semantic_files,
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"e9-multirate-perception-{identity_sha256}"
metrics = {
"source_span_seconds": round(source_span, 6),
"replay_wall_seconds": round(replay_wall, 6),
"detector": {
"frames_expected": len(frame_paths),
"frames_processed": detector_state["consumed"],
"frames_dropped": detector_state["dropped_overflow"],
"drop_fraction": round(detector_drop_fraction, 9),
"effective_frames_per_second": round(detector_fps, 6),
"queue": detector_state,
"latency_ms": detector_percentiles,
"detections": int(sum(detector_labels.values())),
"unique_confirmed_tracks": len(unique_tracks),
"track_observations": int(sum(track_labels.values())),
"same_class_duplicate_pairs_iou_ge_0_8": duplicate_pairs,
},
"semantic": {
"frames_scheduled": scheduled_semantic,
"frames_processed": semantic_state["consumed"],
"frames_dropped": semantic_state["dropped_overflow"],
"drop_fraction": round(semantic_drop_fraction, 9),
"effective_frames_per_second": round(semantic_fps, 6),
"queue": semantic_state,
"latency_ms": semantic_percentiles,
"binding_status_counts": dict(sorted(semantic_status.items())),
"fresh_coverage": round(fresh_coverage, 9),
},
"gpu_telemetry": gpu_telemetry.summary(),
"process_peak_rss_mib": round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024, 3),
"cuda_peak_memory_allocated_mib": round(torch.cuda.max_memory_allocated() / 2**20, 3),
"cuda_peak_memory_reserved_mib": round(torch.cuda.max_memory_reserved() / 2**20, 3),
"disk": {
"free_bytes_before_replay": disk_before,
"free_bytes_after_replay": shutil.disk_usage(output).free,
"free_bytes_floor": args.free_bytes_floor,
},
}
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "accepted" if accepted else "rejected",
"ground_truth": False,
"identity": identity,
"runtime": {
"hostname": platform.node(),
"platform": platform.platform(),
"python": platform.python_version(),
"numpy": np.__version__,
"torch": torch.__version__,
"transformers": transformers.__version__,
"scipy": importlib.metadata.version("scipy"),
"gpu": torch.cuda.get_device_name(),
},
"metrics": metrics,
"acceptance": {
"accepted": accepted,
"checks": checks,
"navigation_or_safety_accepted": False,
},
"limitations": [
"Recorded source-paced replay, not live K1 transport.",
(
"Semantic masks are measured in memory; this gate stores hashes and "
"class totals, not mask images."
),
"Generic Cityscapes and COCO models are not forest-domain or safety validated.",
"LiDAR fusion and 3D world-state publication remain downstream gates.",
],
}
report_path = output / "run-report.json"
_write_json(report_path, report)
artifacts = [
_artifact(
detector_path,
"multirate-detector-frames",
"application/x-ndjson",
DETECTOR_FRAME_SCHEMA,
),
_artifact(
semantic_path,
"multirate-semantic-frames",
"application/x-ndjson",
SEMANTIC_FRAME_SCHEMA,
),
_artifact(
merged_path, "multirate-merged-frames", "application/x-ndjson", MERGED_FRAME_SCHEMA
),
_artifact(gpu_path, "worker-gpu-telemetry", "application/x-ndjson"),
_artifact(report_path, "multirate-run-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": report["created_at_utc"],
"acceptance_state": report["state"],
"ground_truth": False,
"publication_scope": "recorded-multirate-qualification-only",
"frames_processed": detector_state["consumed"],
"artifacts": artifacts,
}
_write_json(output / "result.json", result)
print(
json.dumps(
{
"result_id": result_id,
"accepted": accepted,
"detector_fps": round(detector_fps, 6),
"detector_dropped": detector_state["dropped_overflow"],
"detector_result_age_p95_ms": detector_percentiles["result_age_ms"]["p95"],
"semantic_fps": round(semantic_fps, 6),
"semantic_dropped": semantic_state["dropped_overflow"],
"semantic_completion_age_p95_ms": semantic_percentiles["completion_age_ms"]["p95"],
"fresh_semantic_coverage": round(fresh_coverage, 6),
},
sort_keys=True,
),
flush=True,
)
# A completed rejected qualification is still immutable diagnostic evidence.
# The manifest state, not the process exit code, carries acceptance.
return 0
def _assert_disk_floor(path: Path, floor: int, frame: int) -> None:
if floor < 0:
raise RuntimeError("LAB E9 disk floor is invalid")
free = shutil.disk_usage(path).free
if floor and free < floor:
raise RuntimeError(f"LAB E9 crossed the D-backed free-space floor at frame {frame}")
def main() -> int:
args = _arguments()
if args.command == "preflight":
return _preflight(args)
return _run(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,698 @@
#!/usr/bin/env python3
"""Generate review-only E2 prelabels from the exact E0 model generations."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import platform
import resource
import time
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from typing import Any
from run_recorded_perception_epoch import (
INSTANCE_MASK_THRESHOLD,
INSTANCE_SCORE_THRESHOLD,
SEMANTIC_MODEL_ID,
SEMANTIC_REVISION,
_canonical_json,
_GpuTelemetry,
_model_files,
_palette,
_percentiles,
_read_object,
_sha256,
_valid_sha256,
_write_json,
_write_png,
)
PACK_SCHEMA = "missioncore.perception-evaluation-pack/v1"
PACK_IDENTITY_SCHEMA = "missioncore.perception-evaluation-pack-identity/v1"
CONTRACT_SCHEMA = "missioncore.perception-annotation-contract/v1"
VALID_FOV_SCHEMA = "missioncore.k1-valid-fov-mask/v1"
RESULT_SCHEMA = "missioncore.perception-evaluation-prelabels/v1"
RESULT_IDENTITY_SCHEMA = "missioncore.perception-evaluation-prelabels-identity/v1"
REPORT_SCHEMA = "missioncore.perception-evaluation-prelabels-report/v1"
FRAME_SCHEMA = "missioncore.perception-evaluation-prelabel-frame/v1"
MAPPING_PROFILE = "e0-coco-ade-to-e2-taxonomy/v1"
PREVIEW_COUNT = 16
TARGET_NAMES = {
0: "outside_valid_fov",
1: "person",
2: "bicycle",
3: "motorcycle",
4: "car",
5: "heavy_vehicle",
6: "building_structure",
7: "paved_road",
8: "sidewalk_curb",
9: "ground_dirt",
10: "grass_low_vegetation",
11: "tree_woody_vegetation",
12: "sky",
13: "static_obstacle",
14: "animal",
15: "other_background",
}
INSTANCE_MAPPING = {
"person": 1,
"bicycle": 2,
"motorcycle": 3,
"car": 4,
"bus": 5,
"truck": 5,
"cat": 14,
"dog": 14,
"horse": 14,
"sheep": 14,
"cow": 14,
"elephant": 14,
"bear": 14,
"zebra": 14,
"giraffe": 14,
"bench": 13,
"fire hydrant": 13,
"stop sign": 13,
"parking meter": 13,
"chair": 13,
"potted plant": 13,
}
SEMANTIC_MAPPING = {
"person": 1,
"bicycle": 2,
"minibike": 3,
"car": 4,
"bus": 5,
"truck": 5,
"van": 5,
"building": 6,
"house": 6,
"skyscraper": 6,
"wall": 6,
"door": 6,
"windowpane": 6,
"hovel": 6,
"awning": 6,
"road": 7,
"runway": 7,
"path": 7,
"sidewalk": 8,
"stairs": 8,
"stairway": 8,
"step": 8,
"earth": 9,
"sand": 9,
"field": 9,
"land": 9,
"dirt track": 9,
"grass": 10,
"plant": 10,
"flower": 10,
"tree": 11,
"palm": 11,
"sky": 12,
"animal": 14,
}
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--evaluation-pack", type=Path, required=True)
parser.add_argument("--valid-fov-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--cache", type=Path, required=True)
parser.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
return parser.parse_args()
def _safe_artifact(root: Path, encoded: object) -> Path:
if not isinstance(encoded, str):
raise RuntimeError("evaluation artifact path is not a string")
relative = PurePosixPath(encoded)
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
raise RuntimeError("evaluation artifact path is unsafe")
path = root.joinpath(*relative.parts).resolve(strict=True)
if not path.is_file() or not path.is_relative_to(root):
raise RuntimeError("evaluation artifact escapes the pack")
return path
def _validate_pack(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
resolved = root.resolve(strict=True)
manifest = _read_object(resolved / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
if (
manifest.get("schema_version") != PACK_SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != PACK_IDENTITY_SCHEMA
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"evaluation-pack-{identity_sha256}"
or identity.get("preprocessing_profile") != "fixed-valid-fov-fill/v1"
):
raise RuntimeError("evaluation pack identity is invalid")
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list) or not artifacts:
raise RuntimeError("evaluation pack artifacts are unavailable")
seen: set[str] = set()
for artifact in artifacts:
if not isinstance(artifact, dict) or not isinstance(artifact.get("path"), str):
raise RuntimeError("evaluation artifact descriptor is invalid")
encoded = str(artifact["path"])
if encoded in seen:
raise RuntimeError("evaluation artifact descriptor is duplicated")
path = _safe_artifact(resolved, encoded)
if (
artifact.get("byte_length") != path.stat().st_size
or not _valid_sha256(artifact.get("sha256"))
or _sha256(path) != artifact["sha256"]
):
raise RuntimeError(f"evaluation artifact changed: {encoded}")
seen.add(encoded)
contract = _read_object(resolved / "annotation-contract.json")
if (
contract.get("schema_version") != CONTRACT_SCHEMA
or contract.get("evaluation_identity_sha256") != identity_sha256
or [row.get("id") for row in contract.get("categories", [])]
!= list(range(1, 16))
):
raise RuntimeError("evaluation annotation contract is invalid")
frames = identity.get("frames")
if not isinstance(frames, list) or len(frames) != 64:
raise RuntimeError("evaluation frame set is invalid")
for expected_image_id, frame in enumerate(frames, start=1):
if not isinstance(frame, dict):
raise RuntimeError("evaluation frame descriptor is invalid")
image_id = frame.get("image_id")
frame_index = frame.get("frame_index")
if (
image_id != expected_image_id
or not isinstance(frame_index, int)
or isinstance(frame_index, bool)
):
raise RuntimeError("evaluation frame order changed")
name = f"image-{image_id:03d}-frame-{frame_index:06d}.png"
expected_paths = {
f"images/raw/{name}",
f"images/valid-fov-fill/{name}",
}
if not expected_paths <= seen:
raise RuntimeError("evaluation frame artifacts are incomplete")
return manifest, frames
def _load_valid_fov(root: Path, pack: dict[str, Any]) -> Any:
import numpy as np
from PIL import Image
resolved = root.resolve(strict=True)
manifest = _read_object(resolved / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
artifact = manifest.get("artifact")
pack_identity = pack["identity"]
if (
manifest.get("schema_version") != VALID_FOV_SCHEMA
or not isinstance(identity, dict)
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"valid-fov-mask-{identity_sha256}"
or manifest.get("generation_id") != pack_identity["valid_fov_generation_id"]
or identity.get("calibration_sha256") != pack_identity["calibration_sha256"]
or identity.get("calibration_slot") != pack_identity["calibration_slot"]
or identity.get("source_id") != pack_identity["source_id"]
or identity.get("admitted_resolution") != [800, 600]
or not isinstance(artifact, dict)
or artifact.get("path") != "mask.png"
or not _valid_sha256(artifact.get("sha256"))
):
raise RuntimeError("valid-FOV binding is invalid")
mask_path = (resolved / "mask.png").resolve(strict=True)
if mask_path.parent != resolved or _sha256(mask_path) != artifact["sha256"]:
raise RuntimeError("valid-FOV artifact changed")
with Image.open(mask_path) as opened:
mask = np.asarray(opened, dtype=np.uint8)
if mask.shape != (600, 800) or not np.isin(mask, (0, 255)).all():
raise RuntimeError("valid-FOV mask pixels are invalid")
return mask > 0
def _resource_snapshot() -> dict[str, float]:
usage = resource.getrusage(resource.RUSAGE_SELF)
result = {
"user_cpu_seconds": float(usage.ru_utime),
"system_cpu_seconds": float(usage.ru_stime),
"minor_page_faults": float(usage.ru_minflt),
"major_page_faults": float(usage.ru_majflt),
"voluntary_context_switches": float(usage.ru_nvcsw),
"involuntary_context_switches": float(usage.ru_nivcsw),
}
try:
for line in Path("/proc/self/io").read_text(encoding="ascii").splitlines():
name, value = line.split(":", 1)
if name in {"read_bytes", "write_bytes", "rchar", "wchar"}:
result[f"proc_io_{name}"] = float(value.strip())
except (OSError, UnicodeDecodeError, ValueError):
pass
return result
def _resource_delta(before: dict[str, float], after: dict[str, float]) -> dict[str, float]:
return {
name: round(value - before[name], 6)
for name, value in after.items()
if name in before
}
def _semantic_target_map(labels: dict[int, str]) -> Any:
import numpy as np
mapping = np.full(256, 15, dtype=np.uint8)
for source_id, name in labels.items():
mapping[source_id] = SEMANTIC_MAPPING.get(name.strip().lower(), 15)
return mapping
def _preview_indices(count: int) -> set[int]:
admitted = min(PREVIEW_COUNT, count)
if admitted == 1:
return {0}
return {
(position * (count - 1) + (admitted - 1) // 2) // (admitted - 1)
for position in range(admitted)
}
def _preview(image: Any, semantic: Any, instance_map: Any, instances: list[dict[str, Any]]) -> Any:
import numpy as np
from PIL import Image, ImageDraw
colors = np.zeros_like(image)
for category_id in range(1, 16):
colors[semantic == category_id] = _palette(category_id)
blended = np.clip(
image.astype(np.float32) * 0.52 + colors.astype(np.float32) * 0.48,
0,
255,
).astype(np.uint8)
canvas = Image.fromarray(blended)
draw = ImageDraw.Draw(canvas)
for instance in instances:
instance_id = int(instance["instance_id"])
color = _palette(500 + instance_id)
x1, y1, x2, y2 = (int(round(value)) for value in instance["box_xyxy"])
draw.rectangle((x1, y1, x2, y2), outline=color, width=2)
label = f"{instance['draft_category']} {float(instance['score']):.0%}"
draw.text((x1, max(0, y1 - 13)), label, fill=color)
if not np.any(instance_map == instance_id):
raise RuntimeError("preview instance lost its mask")
return np.asarray(canvas, dtype=np.uint8)
def _artifact(path: Path, root: Path) -> dict[str, Any]:
return {
"path": path.relative_to(root).as_posix(),
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
def _run(args: argparse.Namespace) -> int:
pack_root = args.evaluation_pack.resolve(strict=True)
output_root = args.output.resolve()
cache_root = args.cache.resolve(strict=True)
if output_root.exists():
raise RuntimeError("prelabel output must be absent")
pack, frames = _validate_pack(pack_root)
valid_mask = _load_valid_fov(args.valid_fov_root, pack)
import numpy as np
import torch
import torch.nn.functional as functional
import torchvision
import transformers
from huggingface_hub import snapshot_download
from PIL import Image
from torchvision.models.detection import (
MaskRCNN_ResNet50_FPN_V2_Weights,
maskrcnn_resnet50_fpn_v2,
)
from transformers import AutoImageProcessor, BeitForSemanticSegmentation
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for E2 prelabels")
output_root.mkdir(mode=0o700, parents=True, exist_ok=False)
instance_root = output_root / "instance-prelabels"
semantic_root = output_root / "semantic-prelabels"
preview_root = output_root / "previews"
instance_root.mkdir(mode=0o700)
semantic_root.mkdir(mode=0o700)
preview_root.mkdir(mode=0o700)
telemetry_path = output_root / "gpu-telemetry.jsonl"
metadata_path = output_root / "frames.jsonl"
device = torch.device("cuda:0")
frame_paths = []
for frame in frames:
name = f"image-{int(frame['image_id']):03d}-frame-{int(frame['frame_index']):06d}.png"
frame_paths.append(pack_root / "images" / "valid-fov-fill" / name)
resource_before = _resource_snapshot()
wall_started = time.perf_counter()
instance_latencies: list[float] = []
semantic_latencies: list[float] = []
instances_by_frame: list[list[dict[str, Any]]] = []
instance_counts: Counter[str] = Counter()
ignored_instance_counts: Counter[str] = Counter()
preview_indices = _preview_indices(len(frames))
torch.cuda.reset_peak_memory_stats()
with telemetry_path.open("x", encoding="utf-8", newline="\n") as telemetry_stream:
with _GpuTelemetry(telemetry_stream, args.telemetry_interval_seconds) as telemetry:
instance_weights = MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT
instance_model = maskrcnn_resnet50_fpn_v2(weights=instance_weights).to(device).eval()
instance_labels = list(instance_weights.meta["categories"])
with Image.open(frame_paths[0]) as opened:
warm_image = opened.convert("RGB")
with torch.inference_mode():
warm_tensor = instance_weights.transforms()(warm_image).to(device)
_ = instance_model([warm_tensor])[0]
torch.cuda.synchronize()
del warm_tensor
with torch.inference_mode():
for order, path in enumerate(frame_paths, start=1):
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
tensor = instance_weights.transforms()(Image.fromarray(image)).to(device)
torch.cuda.synchronize()
started = time.perf_counter()
prediction = instance_model([tensor])[0]
torch.cuda.synchronize()
instance_latencies.append((time.perf_counter() - started) * 1000.0)
scores = prediction["scores"].detach().cpu().numpy()
keep = np.flatnonzero(scores >= INSTANCE_SCORE_THRESHOLD)
index_map = np.zeros(valid_mask.shape, dtype=np.uint16)
instances: list[dict[str, Any]] = []
for output_index in keep:
source_id = int(prediction["labels"][output_index].item())
source_name = instance_labels[source_id].strip().lower()
target_id = INSTANCE_MAPPING.get(source_name)
if target_id is None:
ignored_instance_counts[source_name] += 1
continue
mask = prediction["masks"][output_index, 0].detach().cpu().numpy()
mask = np.logical_and(mask >= INSTANCE_MASK_THRESHOLD, valid_mask)
admitted_mask = np.logical_and(mask, index_map == 0)
mask_pixels = int(np.count_nonzero(admitted_mask))
if mask_pixels < 8:
continue
instance_id = len(instances) + 1
index_map[admitted_mask] = instance_id
box = [
round(float(value), 6)
for value in prediction["boxes"][output_index]
.detach()
.cpu()
.tolist()
]
instance = {
"instance_id": instance_id,
"draft_category_id": target_id,
"draft_category": TARGET_NAMES[target_id],
"source_model_category_id": source_id,
"source_model_category": source_name,
"score": round(float(scores[output_index]), 9),
"box_xyxy": box,
"mask_pixels": mask_pixels,
"review_state": "unreviewed-model-draft",
}
instances.append(instance)
instance_counts[TARGET_NAMES[target_id]] += 1
_write_png(instance_root / f"image-{order:03d}.png", index_map)
instances_by_frame.append(instances)
del tensor, prediction
if order % 16 == 0 or order == len(frames):
print(
json.dumps(
{"phase": "instance", "processed": order, "total": len(frames)},
sort_keys=True,
),
flush=True,
)
checkpoint = Path(instance_weights.url).name
checkpoint_path = Path(torch.hub.get_dir()) / "checkpoints" / checkpoint
del instance_model
torch.cuda.empty_cache()
semantic_snapshot = Path(
snapshot_download(
repo_id=SEMANTIC_MODEL_ID,
revision=SEMANTIC_REVISION,
cache_dir=cache_root / "huggingface",
allow_patterns=("config.json", "preprocessor_config.json", "pytorch_model.bin"),
)
)
processor = AutoImageProcessor.from_pretrained(
semantic_snapshot,
local_files_only=True,
use_fast=False,
)
semantic_model, loading = BeitForSemanticSegmentation.from_pretrained(
semantic_snapshot,
local_files_only=True,
output_loading_info=True,
)
load_problems = {
name: loading.get(name, [])
for name in ("missing_keys", "unexpected_keys", "mismatched_keys", "error_msgs")
if loading.get(name)
}
if load_problems:
raise RuntimeError(
"semantic checkpoint did not load exactly: " + json.dumps(load_problems)
)
semantic_model = semantic_model.to(device).eval()
semantic_labels = {
int(key): str(value) for key, value in semantic_model.config.id2label.items()
}
semantic_mapping = _semantic_target_map(semantic_labels)
with Image.open(frame_paths[0]) as opened:
warm_image = opened.convert("RGB")
warm_inputs = processor(images=warm_image, return_tensors="pt")
warm_inputs = {name: value.to(device) for name, value in warm_inputs.items()}
with torch.inference_mode():
_ = semantic_model(**warm_inputs).logits
torch.cuda.synchronize()
del warm_inputs
target_pixels: Counter[str] = Counter()
with metadata_path.open("x", encoding="utf-8", newline="\n") as metadata_stream:
with torch.inference_mode():
for order, (frame, path, instances) in enumerate(
zip(frames, frame_paths, instances_by_frame, strict=True),
start=1,
):
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
inputs = processor(images=Image.fromarray(image), return_tensors="pt")
inputs = {name: value.to(device) for name, value in inputs.items()}
torch.cuda.synchronize()
started = time.perf_counter()
logits = semantic_model(**inputs).logits
resized = functional.interpolate(
logits,
size=image.shape[:2],
mode="bilinear",
align_corners=False,
)
source_semantic = (
resized.argmax(dim=1)[0].detach().cpu().numpy().astype(np.uint8)
)
torch.cuda.synchronize()
semantic_latencies.append((time.perf_counter() - started) * 1000.0)
semantic = semantic_mapping[source_semantic]
semantic = semantic.copy()
semantic[~valid_mask] = 0
instance_map = np.asarray(
Image.open(instance_root / f"image-{order:03d}.png"),
dtype=np.uint16,
)
for instance in instances:
semantic[instance_map == int(instance["instance_id"])] = int(
instance["draft_category_id"]
)
labels, counts = np.unique(semantic[valid_mask], return_counts=True)
for label_id, count in zip(labels, counts, strict=True):
target_pixels[TARGET_NAMES[int(label_id)]] += int(count)
_write_png(semantic_root / f"image-{order:03d}.png", semantic)
if order - 1 in preview_indices:
_write_png(
preview_root / f"image-{order:03d}.png",
_preview(image, semantic, instance_map, instances),
)
metadata_stream.write(
json.dumps(
{
"schema_version": FRAME_SCHEMA,
"image_id": frame["image_id"],
"frame_index": frame["frame_index"],
"session_seconds": frame["session_seconds"],
"role": frame["role"],
"group_id": frame["group_id"],
"instances": instances,
"review_state": "unreviewed-model-draft",
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
del inputs, logits, resized
if order % 16 == 0 or order == len(frames):
print(
json.dumps(
{
"phase": "semantic",
"processed": order,
"total": len(frames),
},
sort_keys=True,
),
flush=True,
)
metadata_stream.flush()
os.fsync(metadata_stream.fileno())
model_files = _model_files(semantic_snapshot, checkpoint_path)
del semantic_model
torch.cuda.empty_cache()
telemetry_summary = telemetry.summary()
wall_seconds = time.perf_counter() - wall_started
resource_after = _resource_snapshot()
identity = {
"schema_version": RESULT_IDENTITY_SCHEMA,
"evaluation_pack_id": pack["generation_id"],
"evaluation_identity_sha256": pack["identity_sha256"],
"valid_fov_generation_id": pack["identity"]["valid_fov_generation_id"],
"pipeline": "maskrcnn-beit-e2-prelabels/v1",
"mapping_profile": MAPPING_PROFILE,
# String keys make the content identity stable across a JSON round trip.
"target_categories": {str(category_id): name for category_id, name in TARGET_NAMES.items()},
"instance_mapping": INSTANCE_MAPPING,
"semantic_mapping": SEMANTIC_MAPPING,
"instance_score_threshold": INSTANCE_SCORE_THRESHOLD,
"instance_mask_threshold": INSTANCE_MASK_THRESHOLD,
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
"models": {
"instance": {
"id": "torchvision/maskrcnn_resnet50_fpn_v2",
"weights": str(instance_weights),
},
"semantic": {"id": SEMANTIC_MODEL_ID, "revision": SEMANTIC_REVISION},
"files": model_files,
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"evaluation-prelabels-{identity_sha256}"
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "completed-unreviewed-model-draft",
"identity": identity,
"warning": (
"These outputs are model-assisted prelabels, not ground truth and not an accuracy "
"measurement. Every accepted label requires human review."
),
"metrics": {
"frames": len(frames),
"instances": sum(instance_counts.values()),
"instances_by_target": dict(instance_counts.most_common()),
"ignored_instances_by_source": dict(ignored_instance_counts.most_common()),
"semantic_pixels_by_target_inside_valid_fov": dict(target_pixels.most_common()),
"instance_forward_ms": _percentiles(instance_latencies),
"semantic_forward_ms": _percentiles(semantic_latencies),
"combined_forward_mean_ms": round(
_percentiles(instance_latencies)["mean"]
+ _percentiles(semantic_latencies)["mean"],
6,
),
"wall_seconds": round(wall_seconds, 6),
},
"runtime": {
"hostname": platform.node(),
"platform": platform.platform(),
"python": platform.python_version(),
"torch": torch.__version__,
"torchvision": torchvision.__version__,
"transformers": transformers.__version__,
"cuda_runtime": torch.version.cuda,
"gpu": torch.cuda.get_device_name(),
"gpu_compute_capability": list(torch.cuda.get_device_capability()),
"cuda_peak_memory_allocated_mib": round(
torch.cuda.max_memory_allocated() / 2**20,
3,
),
"cuda_peak_memory_reserved_mib": round(
torch.cuda.max_memory_reserved() / 2**20,
3,
),
"process_peak_rss_mib": round(
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024,
3,
),
"resource_delta": _resource_delta(resource_before, resource_after),
"gpu_telemetry": telemetry_summary,
},
}
_write_json(output_root / "run-report.json", report)
artifacts = [
_artifact(path, output_root)
for path in sorted(output_root.rglob("*"))
if path.is_file()
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"review_state": "unreviewed-model-draft",
"artifacts": artifacts,
}
_write_json(output_root / "result.json", result)
print(
json.dumps(
{
"state": "completed-unreviewed-model-draft",
"result_id": result_id,
"frames": len(frames),
"wall_seconds": round(wall_seconds, 6),
},
sort_keys=True,
),
flush=True,
)
return 0
def main() -> int:
return _run(_arguments())
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,875 @@
#!/usr/bin/env python3
"""Benchmark E1 valid-FOV preprocessing variants on one sealed frame slice."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import resource
import time
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from run_recorded_perception_epoch import (
INSTANCE_MASK_THRESHOLD,
INSTANCE_SCORE_THRESHOLD,
SEMANTIC_MODEL_ID,
SEMANTIC_REVISION,
_canonical_json,
_GpuTelemetry,
_instance_overlay,
_model_files,
_percentiles,
_read_object,
_read_timeline,
_semantic_overlay,
_sha256,
_valid_sha256,
_validate_job,
_write_json,
_write_png,
)
QUALIFICATION_SCHEMA = "missioncore.recorded-qualification-slice/v1"
QUALIFICATION_IDENTITY_SCHEMA = "missioncore.recorded-qualification-slice-identity/v1"
VALID_FOV_SCHEMA = "missioncore.k1-valid-fov-mask/v1"
VALID_FOV_IDENTITY_SCHEMA = "missioncore.k1-valid-fov-mask-identity/v1"
REPORT_SCHEMA = "missioncore.perception-preprocessing-qualification-report/v1"
RESULT_SCHEMA = "missioncore.perception-preprocessing-qualification-result/v1"
RESULT_IDENTITY_SCHEMA = "missioncore.perception-preprocessing-qualification-identity/v1"
VARIANTS = ("baseline", "valid-fov-fill", "valid-fov-crop")
PREVIEW_FRAME_COUNT = 12
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--frames", type=Path, required=True)
parser.add_argument("--timeline", type=Path, required=True)
parser.add_argument("--qualification", type=Path, required=True)
parser.add_argument("--valid-fov-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--cache", type=Path, required=True)
parser.add_argument("--calibration-sha256", required=True)
parser.add_argument("--calibration-slot", required=True)
parser.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
return parser.parse_args()
def _load_qualification(path: Path, job: dict[str, Any]) -> tuple[dict[str, Any], list[int]]:
manifest = _read_object(path.resolve(strict=True))
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
input_document = job["input"]
if (
manifest.get("schema_version") != QUALIFICATION_SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != QUALIFICATION_IDENTITY_SCHEMA
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"qualification-slice-{identity_sha256}"
or identity.get("job_id") != job["job_id"]
or identity.get("input_sha256") != job["input_sha256"]
or identity.get("source_id") != input_document["source_id"]
or identity.get("codec_epoch") != input_document["codec_epoch"]
or identity.get("source_frame_count") != input_document["segment_count"]
or identity.get("policy") != "uniform-frame-index-full-epoch/v1"
):
raise RuntimeError("qualification slice identity is invalid")
rows = manifest.get("frames")
selected = identity.get("selected_frames")
if not isinstance(rows, list) or not isinstance(selected, list) or len(rows) != len(selected):
raise RuntimeError("qualification slice frame set is invalid")
indices: list[int] = []
previous = -1
for row, selected_row in zip(rows, selected, strict=True):
if not isinstance(row, dict) or not isinstance(selected_row, dict):
raise RuntimeError("qualification frame descriptor is invalid")
frame_index = row.get("frame_index")
sequence = row.get("sequence")
digest = row.get("segment_sha256")
if (
not isinstance(frame_index, int)
or isinstance(frame_index, bool)
or not previous < frame_index < int(input_document["segment_count"])
or sequence != frame_index + 1
or not _valid_sha256(digest)
or selected_row
!= {
"frame_index": frame_index,
"sequence": sequence,
"segment_sha256": digest,
}
):
raise RuntimeError("qualification frame binding is invalid")
indices.append(frame_index)
previous = frame_index
if not indices:
raise RuntimeError("qualification slice is empty")
return manifest, indices
def _load_valid_fov(
root: Path,
*,
job: dict[str, Any],
calibration_sha256: str,
calibration_slot: str,
) -> tuple[dict[str, Any], Any, tuple[int, int, int, int]]:
import numpy as np
from PIL import Image
resolved = root.resolve(strict=True)
manifest = _read_object(resolved / "manifest.json")
identity = manifest.get("identity")
identity_sha256 = manifest.get("identity_sha256")
artifact = manifest.get("artifact")
geometry = manifest.get("geometry")
if (
manifest.get("schema_version") != VALID_FOV_SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != VALID_FOV_IDENTITY_SCHEMA
or not _valid_sha256(identity_sha256)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("generation_id") != f"valid-fov-mask-{identity_sha256}"
or identity.get("calibration_sha256") != calibration_sha256
or identity.get("calibration_slot") != calibration_slot
or identity.get("source_id") != job["input"]["source_id"]
or identity.get("admitted_resolution") != [800, 600]
or not isinstance(artifact, dict)
or artifact.get("path") != "mask.png"
or artifact.get("media_type") != "image/png"
or not _valid_sha256(artifact.get("sha256"))
or not isinstance(geometry, dict)
):
raise RuntimeError("valid-FOV identity is invalid")
mask_path = (resolved / "mask.png").resolve(strict=True)
if mask_path.parent != resolved or _sha256(mask_path) != artifact["sha256"]:
raise RuntimeError("valid-FOV mask artifact changed")
with Image.open(mask_path) as opened:
if opened.mode != "L" or opened.size != (800, 600):
raise RuntimeError("valid-FOV mask format changed")
mask = np.asarray(opened, dtype=np.uint8)
if not np.isin(mask, (0, 255)).all():
raise RuntimeError("valid-FOV mask is not binary")
crop_value = geometry.get("crop_xyxy_exclusive")
if (
not isinstance(crop_value, list)
or len(crop_value) != 4
or not all(isinstance(value, int) and not isinstance(value, bool) for value in crop_value)
):
raise RuntimeError("valid-FOV crop is invalid")
crop = tuple(int(value) for value in crop_value)
left, top, right, bottom = crop
if not 0 <= left < right <= 800 or not 0 <= top < bottom <= 600:
raise RuntimeError("valid-FOV crop escapes the image")
if int(np.count_nonzero(mask)) != geometry.get("valid_pixel_count"):
raise RuntimeError("valid-FOV pixel count changed")
return manifest, mask > 0, crop
def _variant_image(
image: Any,
valid_mask: Any,
crop: tuple[int, int, int, int],
variant: str,
) -> Any:
import numpy as np
if variant == "baseline":
return image
masked = np.where(valid_mask[..., None], image, 0).astype(np.uint8, copy=False)
if variant == "valid-fov-fill":
return masked
if variant == "valid-fov-crop":
left, top, right, bottom = crop
return masked[top:bottom, left:right]
raise RuntimeError(f"unknown preprocessing variant: {variant}")
def _full_mask(
local_mask: Any,
*,
variant: str,
valid_mask: Any,
crop: tuple[int, int, int, int],
) -> Any:
import numpy as np
if variant != "valid-fov-crop":
result = np.asarray(local_mask, dtype=bool)
else:
result = np.zeros(valid_mask.shape, dtype=bool)
left, top, right, bottom = crop
if local_mask.shape != (bottom - top, right - left):
raise RuntimeError("cropped instance mask shape changed")
result[top:bottom, left:right] = local_mask
if variant != "baseline":
result = np.logical_and(result, valid_mask)
return result
def _full_box(
box_xyxy: list[float],
*,
variant: str,
crop: tuple[int, int, int, int],
) -> list[float]:
if variant == "valid-fov-crop":
left, top, _right, _bottom = crop
return [
box_xyxy[0] + left,
box_xyxy[1] + top,
box_xyxy[2] + left,
box_xyxy[3] + top,
]
return box_xyxy
def _full_semantic(
local: Any,
*,
variant: str,
valid_mask: Any,
crop: tuple[int, int, int, int],
) -> Any:
import numpy as np
if variant != "valid-fov-crop":
result = np.asarray(local, dtype=np.uint8)
else:
result = np.full(valid_mask.shape, 255, dtype=np.uint8)
left, top, right, bottom = crop
if local.shape != (bottom - top, right - left):
raise RuntimeError("cropped semantic mask shape changed")
result[top:bottom, left:right] = local
if variant != "baseline":
result = result.copy()
result[~valid_mask] = 255
return result
def _resource_snapshot() -> dict[str, float]:
usage = resource.getrusage(resource.RUSAGE_SELF)
result = {
"user_cpu_seconds": float(usage.ru_utime),
"system_cpu_seconds": float(usage.ru_stime),
"minor_page_faults": float(usage.ru_minflt),
"major_page_faults": float(usage.ru_majflt),
"voluntary_context_switches": float(usage.ru_nvcsw),
"involuntary_context_switches": float(usage.ru_nivcsw),
}
try:
for line in Path("/proc/self/io").read_text(encoding="ascii").splitlines():
name, value = line.split(":", 1)
if name in {"read_bytes", "write_bytes", "rchar", "wchar"}:
result[f"proc_io_{name}"] = float(value.strip())
except (OSError, UnicodeDecodeError, ValueError):
pass
return result
def _resource_delta(before: dict[str, float], after: dict[str, float]) -> dict[str, float]:
return {
name: round(value - before.get(name, value), 6)
for name, value in after.items()
if name in before
}
def _metric_state() -> dict[str, Any]:
return {
"instance_source_decode_ms": [],
"instance_preprocess_ms": [],
"instance_host_to_device_ms": [],
"instance_forward_ms": [],
"instance_postprocess_ms": [],
"semantic_source_decode_ms": [],
"semantic_preprocess_ms": [],
"semantic_host_to_device_ms": [],
"semantic_forward_ms": [],
"semantic_postprocess_ms": [],
"instances": 0,
"instance_scores": [],
"instance_labels": Counter(),
"huge_masks_over_half_valid_fov": 0,
"huge_boxes_over_half_full_frame": 0,
"predicted_mask_pixels": 0,
"predicted_mask_pixels_outside_valid_fov": 0,
"frames_with_outside_valid_fov_instance_pixels": 0,
"semantic_labels": Counter(),
"semantic_disagreement_with_baseline": [],
}
def _latency_document(state: dict[str, Any]) -> dict[str, Any]:
return {
branch: {
name: _percentiles(
[float(value) for value in state[f"{branch}_{name}"]]
)
for name in (
"source_decode_ms",
"preprocess_ms",
"host_to_device_ms",
"forward_ms",
"postprocess_ms",
)
}
for branch in ("instance", "semantic")
}
def _metric_document(state: dict[str, Any], frame_count: int) -> dict[str, Any]:
scores = [float(value) for value in state["instance_scores"]]
outside = int(state["predicted_mask_pixels_outside_valid_fov"])
total = int(state["predicted_mask_pixels"])
disagreements = [float(value) for value in state["semantic_disagreement_with_baseline"]]
return {
"frames": frame_count,
"latency_ms": _latency_document(state),
"instances": int(state["instances"]),
"instances_per_frame": round(int(state["instances"]) / frame_count, 6),
"instance_score": _percentiles(scores),
"instance_labels": dict(state["instance_labels"].most_common()),
"huge_masks_over_half_valid_fov": int(state["huge_masks_over_half_valid_fov"]),
"huge_boxes_over_half_full_frame": int(state["huge_boxes_over_half_full_frame"]),
"predicted_mask_pixels": total,
"predicted_mask_pixels_outside_valid_fov": outside,
"predicted_mask_outside_fraction": round(outside / total, 9) if total else 0.0,
"frames_with_outside_valid_fov_instance_pixels": int(
state["frames_with_outside_valid_fov_instance_pixels"]
),
"semantic_labels_inside_valid_fov": dict(state["semantic_labels"].most_common()),
"semantic_disagreement_with_baseline_inside_valid_fov": _percentiles(disagreements),
}
def _preview_indices(indices: list[int]) -> set[int]:
admitted = min(PREVIEW_FRAME_COUNT, len(indices))
if admitted == 1:
return {indices[len(indices) // 2]}
positions = {
(position * (len(indices) - 1) + (admitted - 1) // 2) // (admitted - 1)
for position in range(admitted)
}
return {indices[position] for position in positions}
def _run(args: argparse.Namespace) -> int:
if not _valid_sha256(args.calibration_sha256):
raise RuntimeError("calibration SHA-256 is invalid")
job_root = args.job.resolve(strict=True)
frames_root = args.frames.resolve(strict=True)
timeline_path = args.timeline.resolve(strict=True)
output_root = args.output.resolve()
cache_root = args.cache.resolve(strict=True)
if output_root.exists() or not frames_root.is_dir():
raise RuntimeError("output must be absent and frames must be a directory")
job = _validate_job(job_root)
input_document = job["input"]
frame_count = int(input_document["segment_count"])
timeline = input_document["timeline"]
timestamps = _read_timeline(
timeline_path,
frame_count,
float(timeline["start_seconds"]),
float(timeline["end_seconds"]),
)
qualification, selected_indices = _load_qualification(args.qualification, job)
valid_fov, valid_mask, crop = _load_valid_fov(
args.valid_fov_root,
job=job,
calibration_sha256=args.calibration_sha256,
calibration_slot=args.calibration_slot,
)
frame_paths = [frames_root / f"frame-{index + 1:06d}.png" for index in selected_indices]
if not all(path.is_file() for path in frame_paths):
raise RuntimeError("qualification decoded frame set is incomplete")
import numpy as np
import torch
import torch.nn.functional as functional
import torchvision
import transformers
from huggingface_hub import snapshot_download
from PIL import Image
from torchvision.models.detection import (
MaskRCNN_ResNet50_FPN_V2_Weights,
maskrcnn_resnet50_fpn_v2,
)
from transformers import AutoImageProcessor, BeitForSemanticSegmentation
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for preprocessing qualification")
device = torch.device("cuda:0")
output_root.mkdir(mode=0o700, parents=True, exist_ok=False)
previews_root = output_root / "previews"
previews_root.mkdir(mode=0o700)
for variant in VARIANTS:
(previews_root / variant).mkdir(mode=0o700)
telemetry_path = output_root / "gpu-telemetry.jsonl"
metrics = {variant: _metric_state() for variant in VARIANTS}
preview_frame_indices = _preview_indices(selected_indices)
preview_instances: dict[tuple[str, int], tuple[Any, list[dict[str, Any]]]] = {}
valid_pixel_count = int(np.count_nonzero(valid_mask))
total_pixel_count = int(valid_mask.size)
resource_before = _resource_snapshot()
wall_started = time.perf_counter()
with telemetry_path.open("x", encoding="utf-8", newline="\n") as telemetry_stream:
with _GpuTelemetry(telemetry_stream, args.telemetry_interval_seconds) as telemetry:
instance_weights = MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT
instance_model = maskrcnn_resnet50_fpn_v2(weights=instance_weights).to(device).eval()
instance_labels = list(instance_weights.meta["categories"])
with Image.open(frame_paths[0]) as opened:
warm_image = opened.convert("RGB")
with torch.inference_mode():
warm_tensor = instance_weights.transforms()(warm_image).to(device)
_ = instance_model([warm_tensor])[0]
torch.cuda.synchronize()
del warm_tensor
torch.cuda.reset_peak_memory_stats()
with torch.inference_mode():
for order, (frame_index, path) in enumerate(
zip(selected_indices, frame_paths, strict=True),
start=1,
):
decode_started = time.perf_counter()
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
decode_ms = (time.perf_counter() - decode_started) * 1000.0
for variant in VARIANTS:
state = metrics[variant]
state["instance_source_decode_ms"].append(decode_ms)
preprocess_started = time.perf_counter()
variant_image = _variant_image(image, valid_mask, crop, variant)
tensor = instance_weights.transforms()(Image.fromarray(variant_image))
state["instance_preprocess_ms"].append(
(time.perf_counter() - preprocess_started) * 1000.0
)
torch.cuda.synchronize()
transfer_started = time.perf_counter()
tensor = tensor.to(device)
torch.cuda.synchronize()
state["instance_host_to_device_ms"].append(
(time.perf_counter() - transfer_started) * 1000.0
)
torch.cuda.synchronize()
forward_started = time.perf_counter()
prediction = instance_model([tensor])[0]
torch.cuda.synchronize()
state["instance_forward_ms"].append(
(time.perf_counter() - forward_started) * 1000.0
)
post_started = time.perf_counter()
scores = prediction["scores"].detach().cpu().numpy()
keep = np.flatnonzero(scores >= INSTANCE_SCORE_THRESHOLD)
index_map = np.zeros(valid_mask.shape, dtype=np.uint16)
instances: list[dict[str, Any]] = []
frame_outside = 0
for output_index in keep:
local_mask = (
prediction["masks"][output_index, 0].detach().cpu().numpy()
>= INSTANCE_MASK_THRESHOLD
)
full_mask = _full_mask(
local_mask,
variant=variant,
valid_mask=valid_mask,
crop=crop,
)
mask_pixels = int(np.count_nonzero(full_mask))
if mask_pixels < 8:
continue
original_full_mask = _full_mask(
local_mask,
variant="baseline" if variant != "valid-fov-crop" else variant,
valid_mask=np.ones_like(valid_mask),
crop=crop,
)
outside_pixels = int(
np.count_nonzero(np.logical_and(original_full_mask, ~valid_mask))
)
frame_outside += outside_pixels
label_id = int(prediction["labels"][output_index].item())
score = float(scores[output_index])
box_values = (
prediction["boxes"][output_index].detach().cpu().tolist()
)
box = _full_box(
[float(value) for value in box_values],
variant=variant,
crop=crop,
)
instance_id = len(instances) + 1
index_map[np.logical_and(full_mask, index_map == 0)] = instance_id
instances.append(
{
"instance_id": instance_id,
"class_id": label_id,
"label": instance_labels[label_id],
"score": round(score, 9),
"box_xyxy": [round(value, 6) for value in box],
"mask_pixels": mask_pixels,
"outside_valid_fov_pixels": outside_pixels,
}
)
state["instances"] += 1
state["instance_scores"].append(score)
state["instance_labels"][instance_labels[label_id]] += 1
state["predicted_mask_pixels"] += int(
np.count_nonzero(original_full_mask)
)
state["predicted_mask_pixels_outside_valid_fov"] += outside_pixels
if mask_pixels / valid_pixel_count > 0.5:
state["huge_masks_over_half_valid_fov"] += 1
box_area = max(0.0, box[2] - box[0]) * max(0.0, box[3] - box[1])
if box_area / total_pixel_count > 0.5:
state["huge_boxes_over_half_full_frame"] += 1
if frame_outside:
state["frames_with_outside_valid_fov_instance_pixels"] += 1
if frame_index in preview_frame_indices:
preview_instances[(variant, frame_index)] = (index_map, instances)
state["instance_postprocess_ms"].append(
(time.perf_counter() - post_started) * 1000.0
)
del tensor, prediction
if order % 32 == 0 or order == len(selected_indices):
print(
json.dumps(
{
"phase": "instance",
"qualification_frames_processed": order,
"qualification_frames_total": len(selected_indices),
},
sort_keys=True,
),
flush=True,
)
checkpoint = Path(instance_weights.url).name
checkpoint_path = Path(torch.hub.get_dir()) / "checkpoints" / checkpoint
del instance_model
torch.cuda.empty_cache()
semantic_snapshot = Path(
snapshot_download(
repo_id=SEMANTIC_MODEL_ID,
revision=SEMANTIC_REVISION,
cache_dir=cache_root / "huggingface",
allow_patterns=(
"config.json",
"preprocessor_config.json",
"pytorch_model.bin",
),
)
)
processor = AutoImageProcessor.from_pretrained(
semantic_snapshot,
local_files_only=True,
use_fast=False,
)
semantic_model, loading = BeitForSemanticSegmentation.from_pretrained(
semantic_snapshot,
local_files_only=True,
output_loading_info=True,
)
load_problems = {
name: loading.get(name, [])
for name in ("missing_keys", "unexpected_keys", "mismatched_keys", "error_msgs")
if loading.get(name)
}
if load_problems:
raise RuntimeError(
"semantic checkpoint did not load exactly: " + json.dumps(load_problems)
)
semantic_model = semantic_model.to(device).eval()
semantic_labels = {
int(key): str(value) for key, value in semantic_model.config.id2label.items()
}
with Image.open(frame_paths[0]) as opened:
warm_image = opened.convert("RGB")
warm_inputs = processor(images=warm_image, return_tensors="pt")
warm_inputs = {name: value.to(device) for name, value in warm_inputs.items()}
with torch.inference_mode():
_ = semantic_model(**warm_inputs).logits
torch.cuda.synchronize()
del warm_inputs
preview_sequence = {
frame_index: sequence
for sequence, frame_index in enumerate(sorted(preview_frame_indices), start=1)
}
with torch.inference_mode():
for order, (frame_index, path) in enumerate(
zip(selected_indices, frame_paths, strict=True),
start=1,
):
decode_started = time.perf_counter()
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
decode_ms = (time.perf_counter() - decode_started) * 1000.0
semantic_by_variant: dict[str, Any] = {}
for variant in VARIANTS:
state = metrics[variant]
state["semantic_source_decode_ms"].append(decode_ms)
preprocess_started = time.perf_counter()
variant_image = _variant_image(image, valid_mask, crop, variant)
inputs = processor(
images=Image.fromarray(variant_image),
return_tensors="pt",
)
state["semantic_preprocess_ms"].append(
(time.perf_counter() - preprocess_started) * 1000.0
)
torch.cuda.synchronize()
transfer_started = time.perf_counter()
inputs = {name: value.to(device) for name, value in inputs.items()}
torch.cuda.synchronize()
state["semantic_host_to_device_ms"].append(
(time.perf_counter() - transfer_started) * 1000.0
)
torch.cuda.synchronize()
forward_started = time.perf_counter()
logits = semantic_model(**inputs).logits
resized = functional.interpolate(
logits,
size=variant_image.shape[:2],
mode="bilinear",
align_corners=False,
)
local_semantic = (
resized.argmax(dim=1)[0].detach().cpu().numpy().astype(np.uint8)
)
torch.cuda.synchronize()
state["semantic_forward_ms"].append(
(time.perf_counter() - forward_started) * 1000.0
)
post_started = time.perf_counter()
semantic = _full_semantic(
local_semantic,
variant=variant,
valid_mask=valid_mask,
crop=crop,
)
semantic_by_variant[variant] = semantic
labels, counts = np.unique(semantic[valid_mask], return_counts=True)
for label_id, count in zip(labels, counts, strict=True):
state["semantic_labels"][
semantic_labels.get(int(label_id), f"class-{int(label_id)}")
] += int(count)
if frame_index in preview_frame_indices:
display_image = _variant_image(image, valid_mask, crop, variant)
if variant == "valid-fov-crop":
full_display = np.zeros_like(image)
left, top, right, bottom = crop
full_display[top:bottom, left:right] = display_image
display_image = full_display
display_semantic = semantic.copy()
display_semantic[display_semantic == 255] = 0
semantic_overlay, _classes = _semantic_overlay(
display_image,
display_semantic,
semantic_labels,
)
instance_map, instances = preview_instances[(variant, frame_index)]
overlay = _instance_overlay(
semantic_overlay,
instance_map,
instances,
)
sequence = preview_sequence[frame_index]
_write_png(
previews_root / variant / f"frame-{sequence:03d}.png",
overlay,
)
state["semantic_postprocess_ms"].append(
(time.perf_counter() - post_started) * 1000.0
)
del inputs, logits, resized
baseline_semantic = semantic_by_variant["baseline"]
for variant in VARIANTS:
selected_semantic = semantic_by_variant[variant][valid_mask]
disagreement = float(
np.mean(selected_semantic != baseline_semantic[valid_mask])
)
metrics[variant]["semantic_disagreement_with_baseline"].append(
disagreement
)
if order % 32 == 0 or order == len(selected_indices):
print(
json.dumps(
{
"phase": "semantic",
"qualification_frames_processed": order,
"qualification_frames_total": len(selected_indices),
},
sort_keys=True,
),
flush=True,
)
del semantic_model
torch.cuda.empty_cache()
wall_seconds = time.perf_counter() - wall_started
resource_after = _resource_snapshot()
model_files = _model_files(semantic_snapshot, checkpoint_path)
metric_documents = {
variant: _metric_document(metrics[variant], len(selected_indices))
for variant in VARIANTS
}
identity = {
"schema_version": RESULT_IDENTITY_SCHEMA,
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"qualification_generation_id": qualification["generation_id"],
"qualification_identity_sha256": qualification["identity_sha256"],
"valid_fov_generation_id": valid_fov["generation_id"],
"valid_fov_identity_sha256": valid_fov["identity_sha256"],
"calibration_sha256": args.calibration_sha256,
"calibration_slot": args.calibration_slot,
"configuration": {
"pipeline": "recorded-preprocessing-ab-mask-crop/v1",
"variants": list(VARIANTS),
"precision": "fp32",
"batch_size": 1,
"execution": "same-model-load-interleaved-variants-per-frame",
"instance_score_threshold": INSTANCE_SCORE_THRESHOLD,
"instance_mask_threshold": INSTANCE_MASK_THRESHOLD,
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
"preview_frame_count": len(preview_frame_indices),
},
"models": {
"instance": {
"id": "torchvision/maskrcnn_resnet50_fpn_v2",
"weights": str(instance_weights),
},
"semantic": {
"id": SEMANTIC_MODEL_ID,
"revision": SEMANTIC_REVISION,
"checkpoint_load": "exact-no-missing-unexpected-or-mismatched-keys",
},
"files": model_files,
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"qualification-result-{identity_sha256}"
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "completed",
"identity": identity,
"input": {
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": input_document["session_id"],
"source_id": input_document["source_id"],
"codec_epoch": input_document["codec_epoch"],
"source_frame_count": frame_count,
"qualification_frame_count": len(selected_indices),
"selected_frame_indices": selected_indices,
"selected_session_seconds": [timestamps[index] for index in selected_indices],
},
"valid_fov": {
"generation_id": valid_fov["generation_id"],
"calibration_sha256": args.calibration_sha256,
"calibration_slot": args.calibration_slot,
"geometry": valid_fov["geometry"],
},
"metrics": metric_documents,
"runtime": {
"wall_seconds": round(wall_seconds, 6),
"variant_model_evaluations": len(selected_indices) * len(VARIANTS) * 2,
"gpu": torch.cuda.get_device_name(),
"torch": torch.__version__,
"torchvision": torchvision.__version__,
"transformers": transformers.__version__,
"cuda_runtime": torch.version.cuda,
"cuda_peak_memory_allocated_mib": round(
torch.cuda.max_memory_allocated() / 2**20,
3,
),
"cuda_peak_memory_reserved_mib": round(
torch.cuda.max_memory_reserved() / 2**20,
3,
),
"process_peak_rss_mib": round(
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024,
3,
),
"resource_delta": _resource_delta(resource_before, resource_after),
"system_load_average": [round(value, 6) for value in os.getloadavg()],
"gpu_telemetry": telemetry.summary(),
},
"quality_status": {
"ground_truth": "absent",
"decision_scope": "preprocessing proxy comparison only",
"not_accepted": [
"2D mIoU/AP",
"3D geometry",
"distance accuracy",
"tracking",
"safety",
],
},
}
_write_json(output_root / "run-report.json", report)
preview_artifacts = []
for path in sorted(previews_root.glob("*/*.png")):
preview_artifacts.append(
{
"path": path.relative_to(output_root).as_posix(),
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
)
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"artifacts": {
"run_report": {
"path": "run-report.json",
"byte_length": (output_root / "run-report.json").stat().st_size,
"sha256": _sha256(output_root / "run-report.json"),
},
"gpu_telemetry": {
"path": "gpu-telemetry.jsonl",
"byte_length": telemetry_path.stat().st_size,
"sha256": _sha256(telemetry_path),
},
"previews": preview_artifacts,
},
}
_write_json(output_root / "result.json", result)
print(
json.dumps(
{
"state": "completed",
"result_id": result_id,
"qualification_frames": len(selected_indices),
"wall_seconds": round(wall_seconds, 6),
},
sort_keys=True,
),
flush=True,
)
return 0
def main() -> int:
return _run(_arguments())
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,824 @@
#!/usr/bin/env python3
"""Run and seal a complete recorded panoptic-perception camera epoch."""
from __future__ import annotations
import argparse
import hashlib
import importlib.metadata
import json
import os
import platform
import resource
import statistics
import subprocess
import threading
import time
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from typing import Any, TextIO
INSTANCE_SCORE_THRESHOLD = 0.5
INSTANCE_MASK_THRESHOLD = 0.5
SEMANTIC_ALPHA = 0.46
INSTANCE_ALPHA = 0.58
SEMANTIC_MODEL_ID = "microsoft/beit-base-finetuned-ade-640-640"
SEMANTIC_REVISION = "a8b6f5ef4acb2ea55d882989deaa02d39401e2b2"
RESULT_SCHEMA = "missioncore.recorded-perception-result/v2"
REPORT_SCHEMA = "missioncore.perception-run-report/v1"
FRAME_SCHEMA = "missioncore.panoptic-frame/v1"
SAFE_SHA256 = set("0123456789abcdef")
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", required=True)
preflight = subparsers.add_parser("preflight")
preflight.add_argument("--job", type=Path, required=True)
preflight.add_argument("--cache", type=Path, required=True)
run = subparsers.add_parser("run")
run.add_argument("--job", type=Path, required=True)
run.add_argument("--frames", type=Path, required=True)
run.add_argument("--timeline", type=Path, required=True)
run.add_argument("--output", type=Path, required=True)
run.add_argument("--cache", type=Path, required=True)
run.add_argument("--calibration-sha256", required=True)
run.add_argument("--calibration-slot", required=True)
run.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
finalize = subparsers.add_parser("finalize")
finalize.add_argument("--output", type=Path, required=True)
finalize.add_argument("--video", type=Path, required=True)
finalize.add_argument("--masks", type=Path, required=True)
finalize.add_argument("--extract-seconds", type=float, required=True)
finalize.add_argument("--encode-seconds", type=float, required=True)
finalize.add_argument("--wall-seconds", type=float, required=True)
finalize.add_argument("--encoder", required=True)
return parser.parse_args()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _read_object(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise RuntimeError(f"{path.name} is not a JSON object")
return value
def _write_json(path: Path, value: object) -> None:
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
with temporary.open("x", encoding="utf-8", newline="\n") as stream:
json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2, allow_nan=False)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
def _valid_sha256(value: object) -> bool:
return isinstance(value, str) and len(value) == 64 and set(value) <= SAFE_SHA256
def _safe_job_path(root: Path, encoded: object) -> Path:
if not isinstance(encoded, str):
raise RuntimeError("job artifact path is not a string")
relative = PurePosixPath(encoded)
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
raise RuntimeError("job artifact path is unsafe")
path = root.joinpath(*relative.parts).resolve(strict=True)
if not path.is_file() or not path.is_relative_to(root):
raise RuntimeError("job artifact escapes the job root")
return path
def _validate_job(job_root: Path) -> dict[str, Any]:
root = job_root.resolve(strict=True)
job = _read_object(root / "job.json")
input_document = job.get("input")
if (
job.get("schema_version") != "missioncore.compute-job/v1"
or not isinstance(input_document, dict)
or hashlib.sha256(_canonical_json(input_document)).hexdigest() != job.get("input_sha256")
or job.get("job_id") != f"recorded-camera-{str(job.get('input_sha256'))[:24]}"
):
raise RuntimeError("compute job identity is invalid")
files = input_document.get("files")
if not isinstance(files, list) or not files:
raise RuntimeError("compute job has no files")
seen: set[str] = set()
total_bytes = 0
for artifact in files:
if not isinstance(artifact, dict):
raise RuntimeError("compute job artifact descriptor is invalid")
encoded = artifact.get("path")
if not isinstance(encoded, str) or encoded in seen:
raise RuntimeError("compute job artifact descriptor is duplicated")
seen.add(encoded)
path = _safe_job_path(root, encoded)
byte_length = artifact.get("byte_length")
digest = artifact.get("sha256")
if (
not isinstance(byte_length, int)
or byte_length < 1
or path.stat().st_size != byte_length
or not _valid_sha256(digest)
or _sha256(path) != digest
):
raise RuntimeError(f"compute job artifact changed: {encoded}")
total_bytes += byte_length
if total_bytes != input_document.get("byte_length"):
raise RuntimeError("compute job byte length changed")
return job
def _read_timeline(path: Path, expected_count: int, start: float, end: float) -> list[float]:
timestamps: list[float] = []
with path.open("r", encoding="utf-8") as stream:
for expected_index, line in enumerate(stream):
value = json.loads(line)
if not isinstance(value, dict) or value.get("frame_index") != expected_index:
raise RuntimeError("decoded frame timeline index changed")
session_seconds = value.get("session_seconds")
if not isinstance(session_seconds, (int, float)):
raise RuntimeError("decoded frame timestamp is invalid")
timestamp = float(session_seconds)
if timestamps and timestamp <= timestamps[-1]:
raise RuntimeError("decoded frame timestamps are not increasing")
timestamps.append(timestamp)
if len(timestamps) != expected_count:
raise RuntimeError("decoded frame count differs from the camera job")
if timestamps[0] < start - 0.001 or timestamps[-1] > end + 0.001:
raise RuntimeError("decoded frame timeline escapes the camera epoch")
return timestamps
def _palette(index: int) -> tuple[int, int, int]:
digest = hashlib.sha256(f"mission-core-segment-{index}".encode()).digest()
return (64 + digest[0] % 176, 64 + digest[1] % 176, 64 + digest[2] % 176)
def _blend(image: Any, colors: Any, alpha: float) -> Any:
import numpy as np
return np.clip(
image.astype(np.float32) * (1.0 - alpha) + colors.astype(np.float32) * alpha,
0,
255,
).astype(np.uint8)
def _semantic_overlay(
image: Any,
index_map: Any,
labels: dict[int, str],
) -> tuple[Any, list[dict[str, Any]]]:
import numpy as np
colors = np.zeros_like(image)
counts = np.bincount(index_map.reshape(-1), minlength=max(labels) + 1)
present = np.flatnonzero(counts)
for index in present:
colors[index_map == index] = _palette(int(index))
total = int(index_map.size)
classes = [
{
"id": int(index),
"label": labels.get(int(index), f"class-{int(index)}"),
"pixels": int(counts[index]),
"fraction": round(float(counts[index]) / total, 9),
}
for index in sorted(present, key=lambda value: int(counts[value]), reverse=True)
]
return _blend(image, colors, SEMANTIC_ALPHA), classes
def _instance_overlay(image: Any, index_map: Any, instances: list[dict[str, Any]]) -> Any:
import numpy as np
from PIL import Image, ImageDraw
result = image.copy()
for item in instances:
instance_id = int(item["instance_id"])
mask = index_map == instance_id
color = np.asarray(_palette(500 + instance_id), dtype=np.uint8)
if bool(mask.any()):
result[mask] = _blend(
result[mask],
np.broadcast_to(color, result[mask].shape),
INSTANCE_ALPHA,
)
canvas = Image.fromarray(result)
draw = ImageDraw.Draw(canvas)
for item in instances:
color = _palette(500 + int(item["instance_id"]))
x1, y1, x2, y2 = (int(round(value)) for value in item["box_xyxy"])
draw.rectangle((x1, y1, x2, y2), outline=color, width=2)
label = f"{item['label']} {float(item['score']):.0%}"
text_box = draw.textbbox((x1, max(0, y1 - 13)), label)
draw.rectangle(text_box, fill=(7, 8, 10))
draw.text((x1, max(0, y1 - 13)), label, fill=color)
return np.asarray(canvas, dtype=np.uint8)
def _write_png(path: Path, array: Any) -> None:
from PIL import Image
Image.fromarray(array).save(path, format="PNG", optimize=False)
def _percentiles(values: list[float]) -> dict[str, float]:
if not values:
return {"mean": 0.0, "p50": 0.0, "p95": 0.0, "max": 0.0}
ordered = sorted(values)
def percentile(fraction: float) -> float:
index = min(len(ordered) - 1, max(0, round((len(ordered) - 1) * fraction)))
return round(ordered[index], 6)
return {
"mean": round(statistics.fmean(values), 6),
"p50": percentile(0.5),
"p95": percentile(0.95),
"max": round(max(values), 6),
}
class _GpuTelemetry:
def __init__(self, stream: TextIO, interval_seconds: float) -> None:
if not 0.25 <= interval_seconds <= 60:
raise RuntimeError("telemetry interval is outside bounds")
self._stream = stream
self._interval = interval_seconds
self._stop = threading.Event()
self._thread = threading.Thread(target=self._run, name="gpu-telemetry", daemon=True)
self.samples: list[dict[str, float]] = []
def __enter__(self) -> _GpuTelemetry:
self._thread.start()
return self
def __exit__(self, *_: object) -> None:
self._stop.set()
self._thread.join(timeout=self._interval + 5)
def _run(self) -> None:
query = (
"timestamp,utilization.gpu,utilization.memory,memory.used,memory.total,"
"temperature.gpu,power.draw"
)
while not self._stop.is_set():
before = time.time()
try:
completed = subprocess.run(
[
"nvidia-smi",
f"--query-gpu={query}",
"--format=csv,noheader,nounits",
],
capture_output=True,
check=True,
text=True,
timeout=5,
)
values = [item.strip() for item in completed.stdout.splitlines()[0].split(",")]
sample = {
"epoch_seconds": round(before, 6),
"gpu_utilization_percent": float(values[1]),
"gpu_memory_utilization_percent": float(values[2]),
"gpu_memory_used_mib": float(values[3]),
"gpu_memory_total_mib": float(values[4]),
"gpu_temperature_c": float(values[5]),
"gpu_power_w": float(values[6]),
}
self.samples.append(sample)
self._stream.write(json.dumps(sample, sort_keys=True) + "\n")
self._stream.flush()
except (OSError, subprocess.SubprocessError, ValueError, IndexError):
pass
self._stop.wait(max(0.0, self._interval - (time.time() - before)))
def summary(self) -> dict[str, object]:
fields = (
"gpu_utilization_percent",
"gpu_memory_utilization_percent",
"gpu_memory_used_mib",
"gpu_temperature_c",
"gpu_power_w",
)
return {
"sample_count": len(self.samples),
"interval_seconds": self._interval,
**{
field: _percentiles([float(sample[field]) for sample in self.samples])
for field in fields
},
}
def _model_files(semantic_snapshot: Path, checkpoint_path: Path) -> list[dict[str, object]]:
files = [
{"name": f"beit/{path.name}", "bytes": path.stat().st_size, "sha256": _sha256(path)}
for path in sorted(semantic_snapshot.iterdir())
if path.is_file()
]
files.append(
{
"name": f"torchvision/{checkpoint_path.name}",
"bytes": checkpoint_path.stat().st_size,
"sha256": _sha256(checkpoint_path),
}
)
return files
def _preflight(args: argparse.Namespace) -> int:
job = _validate_job(args.job.resolve(strict=True))
cache_root = args.cache.resolve(strict=True)
import torch
import torchvision # noqa: F401
import transformers # noqa: F401
from huggingface_hub import snapshot_download
from torchvision.models.detection import MaskRCNN_ResNet50_FPN_V2_Weights
if not torch.cuda.is_available() or torch.cuda.device_count() < 1:
raise RuntimeError("CUDA device 0 is unavailable")
cuda_probe = torch.zeros(1, device="cuda:0")
torch.cuda.synchronize()
del cuda_probe
checkpoint = Path(MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT.url).name
checkpoint_path = Path(torch.hub.get_dir()) / "checkpoints" / checkpoint
if not checkpoint_path.is_file() or checkpoint_path.stat().st_size < 1:
raise RuntimeError("cached instance checkpoint is unavailable")
semantic_snapshot = Path(
snapshot_download(
repo_id=SEMANTIC_MODEL_ID,
revision=SEMANTIC_REVISION,
cache_dir=cache_root / "huggingface",
allow_patterns=("config.json", "preprocessor_config.json", "pytorch_model.bin"),
)
)
required = ("config.json", "preprocessor_config.json", "pytorch_model.bin")
if any(not (semantic_snapshot / name).is_file() for name in required):
raise RuntimeError("cached semantic checkpoint is incomplete")
print(
json.dumps(
{
"state": "preflight-ready",
"job_id": job["job_id"],
"cuda_device": torch.cuda.get_device_name(),
},
sort_keys=True,
),
flush=True,
)
return 0
def _run(args: argparse.Namespace) -> int:
if not _valid_sha256(args.calibration_sha256):
raise RuntimeError("calibration SHA-256 is invalid")
job_root = args.job.resolve(strict=True)
frames_root = args.frames.resolve(strict=True)
timeline_path = args.timeline.resolve(strict=True)
output_root = args.output.resolve()
cache_root = args.cache.resolve()
if output_root.exists() or not frames_root.is_dir():
raise RuntimeError("output must be absent and frames must be a directory")
job = _validate_job(job_root)
input_document = job["input"]
timeline = input_document["timeline"]
segment_count = int(input_document["segment_count"])
frame_paths = [frames_root / f"frame-{index:06d}.png" for index in range(1, segment_count + 1)]
if not all(path.is_file() for path in frame_paths):
raise RuntimeError("decoded frame set is incomplete")
if len(list(frames_root.glob("frame-*.png"))) != segment_count:
raise RuntimeError("decoded frame set contains unexpected files")
timestamps = _read_timeline(
timeline_path,
segment_count,
float(timeline["start_seconds"]),
float(timeline["end_seconds"]),
)
import numpy as np
import torch
import torch.nn.functional as functional
import torchvision
import transformers
from huggingface_hub import snapshot_download
from PIL import Image
from torchvision.models.detection import (
MaskRCNN_ResNet50_FPN_V2_Weights,
maskrcnn_resnet50_fpn_v2,
)
from transformers import AutoImageProcessor, BeitForSemanticSegmentation
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for a perception epoch run")
cuda_index = 0
device = torch.device(f"cuda:{cuda_index}")
cache_root.mkdir(mode=0o700, parents=True, exist_ok=True)
output_root.mkdir(mode=0o700, parents=True, exist_ok=False)
instance_root = output_root / "instance-masks"
semantic_root = output_root / "semantic-masks"
overlay_root = output_root / "overlay-frames"
instance_root.mkdir(mode=0o700)
semantic_root.mkdir(mode=0o700)
overlay_root.mkdir(mode=0o700)
telemetry_path = output_root / "gpu-telemetry.jsonl"
started = time.perf_counter()
instance_latencies: list[float] = []
semantic_latencies: list[float] = []
instances_by_frame: list[list[dict[str, Any]]] = []
total_instances = 0
with telemetry_path.open("x", encoding="utf-8", newline="\n") as telemetry_stream:
with _GpuTelemetry(telemetry_stream, args.telemetry_interval_seconds) as telemetry:
instance_weights = MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT
instance_model = maskrcnn_resnet50_fpn_v2(weights=instance_weights).to(device).eval()
instance_labels = list(instance_weights.meta["categories"])
with torch.inference_mode():
for frame_index, path in enumerate(frame_paths):
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
tensor = instance_weights.transforms()(Image.fromarray(image)).to(device)
torch.cuda.synchronize()
before = time.perf_counter()
prediction = instance_model([tensor])[0]
torch.cuda.synchronize()
instance_latencies.append((time.perf_counter() - before) * 1000.0)
scores = prediction["scores"].detach().cpu().numpy()
keep = np.flatnonzero(scores >= INSTANCE_SCORE_THRESHOLD)
index_map = np.zeros(image.shape[:2], dtype=np.uint16)
instances: list[dict[str, Any]] = []
for output_index in keep:
mask = (
prediction["masks"][output_index, 0].detach().cpu().numpy()
>= INSTANCE_MASK_THRESHOLD
)
mask_pixels = int(mask.sum())
if mask_pixels < 8:
continue
label_id = int(prediction["labels"][output_index].item())
instance_id = len(instances) + 1
index_map[np.logical_and(mask, index_map == 0)] = instance_id
instances.append(
{
"instance_id": instance_id,
"class_id": label_id,
"label": instance_labels[label_id],
"score": round(float(scores[output_index]), 9),
"box_xyxy": [
round(float(value), 6)
for value in prediction["boxes"][output_index]
.detach()
.cpu()
.tolist()
],
"mask_pixels": mask_pixels,
}
)
_write_png(instance_root / f"frame-{frame_index + 1:06d}.png", index_map)
instances_by_frame.append(instances)
total_instances += len(instances)
if (frame_index + 1) % 100 == 0 or frame_index + 1 == segment_count:
print(
json.dumps(
{
"phase": "instance",
"frames_processed": frame_index + 1,
"frames_total": segment_count,
},
sort_keys=True,
),
flush=True,
)
checkpoint = Path(instance_weights.url).name
checkpoint_path = Path(torch.hub.get_dir()) / "checkpoints" / checkpoint
del instance_model
torch.cuda.empty_cache()
semantic_snapshot = Path(
snapshot_download(
repo_id=SEMANTIC_MODEL_ID,
revision=SEMANTIC_REVISION,
cache_dir=cache_root / "huggingface",
allow_patterns=("config.json", "preprocessor_config.json", "pytorch_model.bin"),
)
)
processor = AutoImageProcessor.from_pretrained(
semantic_snapshot,
local_files_only=True,
use_fast=False,
)
semantic_model, loading = BeitForSemanticSegmentation.from_pretrained(
semantic_snapshot,
local_files_only=True,
output_loading_info=True,
)
load_problems = {
name: loading.get(name, [])
for name in ("missing_keys", "unexpected_keys", "mismatched_keys", "error_msgs")
if loading.get(name)
}
if load_problems:
raise RuntimeError(
"semantic checkpoint did not load exactly: "
+ json.dumps(load_problems)
)
semantic_model = semantic_model.to(device).eval()
semantic_labels = {
int(key): str(value)
for key, value in semantic_model.config.id2label.items()
}
frame_metadata = output_root / "frames.jsonl"
with frame_metadata.open("x", encoding="utf-8", newline="\n") as metadata_stream:
with torch.inference_mode():
for frame_index, (path, session_seconds) in enumerate(
zip(frame_paths, timestamps, strict=True)
):
with Image.open(path) as opened:
image = np.asarray(opened.convert("RGB"), dtype=np.uint8)
inputs = processor(images=Image.fromarray(image), return_tensors="pt")
inputs = {name: value.to(device) for name, value in inputs.items()}
torch.cuda.synchronize()
before = time.perf_counter()
logits = semantic_model(**inputs).logits
resized = functional.interpolate(
logits,
size=image.shape[:2],
mode="bilinear",
align_corners=False,
)
semantic = resized.argmax(dim=1)[0].detach().cpu().numpy().astype(np.uint8)
torch.cuda.synchronize()
semantic_latencies.append((time.perf_counter() - before) * 1000.0)
semantic_overlay, classes = _semantic_overlay(
image,
semantic,
semantic_labels,
)
instance_map = np.asarray(
Image.open(instance_root / f"frame-{frame_index + 1:06d}.png"),
dtype=np.uint16,
)
overlay = _instance_overlay(
semantic_overlay,
instance_map,
instances_by_frame[frame_index],
)
_write_png(semantic_root / f"frame-{frame_index + 1:06d}.png", semantic)
_write_png(overlay_root / f"frame-{frame_index + 1:06d}.png", overlay)
metadata_stream.write(
json.dumps(
{
"schema_version": FRAME_SCHEMA,
"frame_index": frame_index,
"sequence": frame_index + 1,
"session_seconds": round(session_seconds, 9),
"instances": instances_by_frame[frame_index],
"semantic_classes": classes,
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
+ "\n"
)
if (frame_index + 1) % 100 == 0 or frame_index + 1 == segment_count:
print(
json.dumps(
{
"phase": "semantic",
"frames_processed": frame_index + 1,
"frames_total": segment_count,
},
sort_keys=True,
),
flush=True,
)
metadata_stream.flush()
os.fsync(metadata_stream.fileno())
del semantic_model
torch.cuda.empty_cache()
inference_elapsed = time.perf_counter() - started
model_files = _model_files(semantic_snapshot, checkpoint_path)
report = {
"schema_version": REPORT_SCHEMA,
"run_id": f"{job['job_id']}-panoptic-v1",
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"state": "inference-complete-awaiting-publication",
"input": {
"job_id": job["job_id"],
"input_sha256": job["input_sha256"],
"session_id": input_document["session_id"],
"source_id": input_document["source_id"],
"codec_epoch": input_document["codec_epoch"],
"segment_count": segment_count,
"byte_length": input_document["byte_length"],
"timeline_start_seconds": timeline["start_seconds"],
"timeline_end_seconds": timeline["end_seconds"],
"frame_timeline_sha256": _sha256(timeline_path),
},
"calibration": {
"content_identity_sha256": args.calibration_sha256,
"camera_slot": args.calibration_slot,
},
"configuration": {
"pipeline": "recorded-panoptic-maskrcnn-beit/v1",
"instance_score_threshold": INSTANCE_SCORE_THRESHOLD,
"instance_mask_threshold": INSTANCE_MASK_THRESHOLD,
"semantic_alpha": SEMANTIC_ALPHA,
"instance_alpha": INSTANCE_ALPHA,
"batch_size": 1,
"frame_policy": "all-frames-no-sampling",
"runner_sha256": _sha256(Path(__file__).resolve(strict=True)),
},
"models": {
"instance": {
"id": "torchvision/maskrcnn_resnet50_fpn_v2",
"weights": str(instance_weights),
"score_threshold": INSTANCE_SCORE_THRESHOLD,
},
"semantic": {
"id": SEMANTIC_MODEL_ID,
"revision": SEMANTIC_REVISION,
"checkpoint_load": "exact-no-missing-unexpected-or-mismatched-keys",
},
"files": model_files,
},
"runtime": {
"hostname": platform.node(),
"platform": platform.platform(),
"python": platform.python_version(),
"torch": torch.__version__,
"torchvision": torchvision.__version__,
"transformers": transformers.__version__,
"cuda_runtime": torch.version.cuda,
"gpu": torch.cuda.get_device_name(),
"gpu_compute_capability": list(torch.cuda.get_device_capability()),
},
"metrics": {
"frames_expected": segment_count,
"frames_processed": segment_count,
"frames_failed": 0,
"frames_skipped": 0,
"instances": total_instances,
"inference_wall_seconds": round(inference_elapsed, 6),
"inference_frames_per_second": round(segment_count / inference_elapsed, 6),
"instance_latency_ms": _percentiles(instance_latencies),
"semantic_latency_ms": _percentiles(semantic_latencies),
"cuda_peak_memory_allocated_mib": round(
torch.cuda.max_memory_allocated() / 2**20,
3,
),
"cuda_peak_memory_reserved_mib": round(
torch.cuda.max_memory_reserved() / 2**20,
3,
),
"process_peak_rss_mib": round(
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024,
3,
),
"system_load_average": [round(value, 6) for value in os.getloadavg()],
"gpu_telemetry": telemetry.summary(),
},
"versions": {
name: importlib.metadata.version(name)
for name in ("numpy", "pillow", "torch", "torchvision", "transformers")
},
}
_write_json(output_root / "run-report.partial.json", report)
print(json.dumps({"state": "inference-complete", "frames": segment_count}, sort_keys=True))
return 0
def _artifact(
path: Path,
kind: str,
media_type: str,
schema_version: str | None = None,
) -> dict[str, object]:
result: dict[str, object] = {
"kind": kind,
"path": path.name,
"media_type": media_type,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
if schema_version is not None:
result["schema_version"] = schema_version
return result
def _finalize(args: argparse.Namespace) -> int:
output_root = args.output.resolve(strict=True)
video = args.video.resolve(strict=True)
masks = args.masks.resolve(strict=True)
if video.parent != output_root or masks.parent != output_root:
raise RuntimeError("published artifacts must be direct result children")
partial_path = output_root / "run-report.partial.json"
frames_path = output_root / "frames.jsonl"
telemetry_path = output_root / "gpu-telemetry.jsonl"
partial = _read_object(partial_path)
if partial.get("schema_version") != REPORT_SCHEMA:
raise RuntimeError("partial run report is incompatible")
input_document = partial["input"]
configuration = partial["configuration"]
models = partial["models"]
identity = {
"schema_version": "missioncore.recorded-perception-identity/v2",
"job_id": input_document["job_id"],
"input_sha256": input_document["input_sha256"],
"calibration": partial["calibration"],
"configuration": configuration,
"models": models,
"publication": {
"video_encoder": args.encoder,
"video_media_type": "video/mp4",
"mask_archive_media_type": "application/gzip",
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"result-{identity_sha256}"
final_report = {
**partial,
"state": "published",
"result_id": result_id,
"publication": {
"extract_seconds": round(args.extract_seconds, 6),
"encode_seconds": round(args.encode_seconds, 6),
"wall_seconds": round(args.wall_seconds, 6),
"end_to_end_frames_per_second": round(
int(partial["metrics"]["frames_processed"]) / args.wall_seconds,
6,
),
"encoder": args.encoder,
},
}
report_path = output_root / "run-report.json"
_write_json(report_path, final_report)
artifacts = [
_artifact(video, "panoptic-overlay-video", "video/mp4"),
_artifact(masks, "panoptic-mask-archive", "application/gzip"),
_artifact(frames_path, "panoptic-frame-metadata", "application/x-ndjson", FRAME_SCHEMA),
_artifact(telemetry_path, "worker-gpu-telemetry", "application/x-ndjson"),
_artifact(report_path, "perception-run-report", "application/json", REPORT_SCHEMA),
]
result = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": final_report["created_at_utc"],
"job_id": input_document["job_id"],
"input_sha256": input_document["input_sha256"],
"session_id": input_document["session_id"],
"source_id": input_document["source_id"],
"codec_epoch": input_document["codec_epoch"],
"timestamp_basis": "session-time-seconds",
"timeline_start_seconds": input_document["timeline_start_seconds"],
"timeline_end_seconds": input_document["timeline_end_seconds"],
"frames_processed": final_report["metrics"]["frames_processed"],
"artifacts": artifacts,
}
_write_json(output_root / "result.json", result)
partial_path.unlink()
print(json.dumps({"result_id": result_id, "identity_sha256": identity_sha256}, sort_keys=True))
return 0
def main() -> int:
args = _arguments()
if args.command == "preflight":
return _preflight(args)
if args.command == "run":
return _run(args)
return _finalize(args)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,425 @@
#!/usr/bin/env python3
"""Run a bounded, recorded-only segmentation probe on an external GPU worker."""
from __future__ import annotations
import argparse
import hashlib
import importlib.metadata
import json
import os
import platform
import shutil
import statistics
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
MAX_INPUT_IMAGES = 16
MAX_INPUT_BYTES = 32 * 1024 * 1024
MAX_DIMENSION = 4096
INSTANCE_SCORE_THRESHOLD = 0.5
SEMANTIC_MODEL_ID = "microsoft/beit-base-finetuned-ade-640-640"
SEMANTIC_REVISION = "a8b6f5ef4acb2ea55d882989deaa02d39401e2b2"
RESULT_SCHEMA = "missioncore.recorded-segmentation-experiment/v1"
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--cache", type=Path, required=True)
return parser.parse_args()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _palette(index: int) -> tuple[int, int, int]:
digest = hashlib.sha256(f"mission-core-segment-{index}".encode()).digest()
return (64 + digest[0] % 176, 64 + digest[1] % 176, 64 + digest[2] % 176)
def _blend(image: Any, colors: Any, alpha: float) -> Any:
import numpy as np
return np.clip(
image.astype(np.float32) * (1.0 - alpha) + colors.astype(np.float32) * alpha,
0,
255,
).astype(np.uint8)
def _semantic_overlay(
image: Any, index_map: Any, labels: dict[int, str]
) -> tuple[Any, list[dict[str, Any]]]:
import numpy as np
colors = np.zeros_like(image)
counts = np.bincount(index_map.reshape(-1), minlength=max(labels) + 1)
present = np.flatnonzero(counts)
for index in present:
colors[index_map == index] = _palette(int(index))
total = int(index_map.size)
classes = [
{
"id": int(index),
"label": labels.get(int(index), f"class-{int(index)}"),
"pixels": int(counts[index]),
"fraction": round(float(counts[index]) / total, 9),
}
for index in sorted(present, key=lambda value: int(counts[value]), reverse=True)
]
return _blend(image, colors, 0.46), classes
def _instance_overlay(image: Any, instances: list[dict[str, Any]], masks: list[Any]) -> Any:
import numpy as np
result = image.copy()
for index, (instance, mask) in enumerate(zip(instances, masks, strict=True), start=1):
color = np.asarray(_palette(500 + index), dtype=np.uint8)
result[mask] = _blend(result[mask], np.broadcast_to(color, result[mask].shape), 0.56)
x1, y1, x2, y2 = (int(round(value)) for value in instance["box_xyxy"])
result[max(0, y1) : min(result.shape[0], y1 + 2), max(0, x1) : min(result.shape[1], x2)] = (
color
)
result[max(0, y2 - 2) : min(result.shape[0], y2), max(0, x1) : min(result.shape[1], x2)] = (
color
)
result[max(0, y1) : min(result.shape[0], y2), max(0, x1) : min(result.shape[1], x1 + 2)] = (
color
)
result[max(0, y1) : min(result.shape[0], y2), max(0, x2 - 2) : min(result.shape[1], x2)] = (
color
)
return result
def _write_png(path: Path, array: Any) -> None:
from PIL import Image
Image.fromarray(array).save(path, format="PNG", optimize=False)
def _artifact(path: Path) -> dict[str, object]:
return {"name": path.name, "bytes": path.stat().st_size, "sha256": _sha256(path)}
def main() -> int:
args = _arguments()
input_root = args.input.expanduser().resolve(strict=True)
output_root = args.output.expanduser().resolve()
cache_root = args.cache.expanduser().resolve()
if not input_root.is_dir() or output_root.exists():
raise RuntimeError("input must be a directory and output must not exist")
images = sorted(input_root.glob("camera-*.png"))
if not images or len(images) > MAX_INPUT_IMAGES:
raise RuntimeError("input image count is outside the recorded probe bound")
for path in images:
if not path.is_file() or path.stat().st_size > MAX_INPUT_BYTES:
raise RuntimeError("input image is missing or too large")
import numpy as np
import torch
import torch.nn.functional as functional
import torchvision
import transformers
from huggingface_hub import snapshot_download
from PIL import Image
from torchvision.models.detection import (
MaskRCNN_ResNet50_FPN_V2_Weights,
maskrcnn_resnet50_fpn_v2,
)
from transformers import AutoImageProcessor, BeitForSemanticSegmentation
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for this worker experiment")
device = torch.device("cuda:0")
cache_root.mkdir(mode=0o700, parents=True, exist_ok=True)
staging = output_root.with_name(f".{output_root.name}.incomplete")
staging.mkdir(mode=0o700, parents=True, exist_ok=False)
started = time.perf_counter()
try:
decoded: list[tuple[Path, Any]] = []
width: int | None = None
height: int | None = None
for path in images:
with Image.open(path) as opened:
opened.verify()
with Image.open(path) as opened:
rgb = opened.convert("RGB")
if max(rgb.size) > MAX_DIMENSION:
raise RuntimeError("input image dimension is outside the probe bound")
if width is None:
width, height = rgb.size
elif rgb.size != (width, height):
raise RuntimeError("input images do not share one physical epoch resolution")
decoded.append((path, np.asarray(rgb, dtype=np.uint8)))
instance_weights = MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT
instance_model = maskrcnn_resnet50_fpn_v2(weights=instance_weights).to(device).eval()
instance_labels = list(instance_weights.meta["categories"])
instance_results: dict[str, dict[str, Any]] = {}
instance_latencies: list[float] = []
with torch.inference_mode():
for path, image in decoded:
tensor = instance_weights.transforms()(Image.fromarray(image)).to(device)
torch.cuda.synchronize()
before = time.perf_counter()
prediction = instance_model([tensor])[0]
torch.cuda.synchronize()
latency_ms = (time.perf_counter() - before) * 1000.0
instance_latencies.append(latency_ms)
scores = prediction["scores"].detach().cpu().numpy()
keep = np.flatnonzero(scores >= INSTANCE_SCORE_THRESHOLD)
masks: list[Any] = []
instances: list[dict[str, Any]] = []
index_map = np.zeros(image.shape[:2], dtype=np.uint16)
for output_index in keep:
mask = prediction["masks"][output_index, 0].detach().cpu().numpy() >= 0.5
if int(mask.sum()) < 8:
continue
label_id = int(prediction["labels"][output_index].item())
instance_id = len(instances) + 1
index_map[np.logical_and(mask, index_map == 0)] = instance_id
masks.append(mask)
instances.append(
{
"instance_id": instance_id,
"class_id": label_id,
"label": instance_labels[label_id],
"score": round(float(scores[output_index]), 9),
"box_xyxy": [
round(float(value), 6)
for value in prediction["boxes"][output_index]
.detach()
.cpu()
.tolist()
],
"mask_pixels": int(mask.sum()),
}
)
stem = path.stem
_write_png(staging / f"{stem}.instances.png", index_map)
_write_png(
staging / f"{stem}.instance-overlay.png",
_instance_overlay(image, instances, masks),
)
instance_results[stem] = {
"latency_ms": round(latency_ms, 6),
"instances": instances,
}
del instance_model
torch.cuda.empty_cache()
semantic_snapshot = Path(
snapshot_download(
repo_id=SEMANTIC_MODEL_ID,
revision=SEMANTIC_REVISION,
cache_dir=cache_root / "huggingface",
allow_patterns=(
"config.json",
"preprocessor_config.json",
"pytorch_model.bin",
),
)
)
processor = AutoImageProcessor.from_pretrained(
semantic_snapshot,
local_files_only=True,
use_fast=False,
)
semantic_model, semantic_loading = BeitForSemanticSegmentation.from_pretrained(
semantic_snapshot,
local_files_only=True,
output_loading_info=True,
)
semantic_load_problems = {
name: semantic_loading.get(name, [])
for name in ("missing_keys", "unexpected_keys", "mismatched_keys", "error_msgs")
if semantic_loading.get(name)
}
if semantic_load_problems:
raise RuntimeError(
"semantic checkpoint did not load exactly: "
+ json.dumps(semantic_load_problems, sort_keys=True)
)
semantic_model = semantic_model.to(device).eval()
semantic_labels = {
int(key): str(value) for key, value in semantic_model.config.id2label.items()
}
semantic_results: dict[str, dict[str, Any]] = {}
semantic_latencies: list[float] = []
with torch.inference_mode():
for path, image in decoded:
inputs = processor(images=Image.fromarray(image), return_tensors="pt")
inputs = {name: value.to(device) for name, value in inputs.items()}
torch.cuda.synchronize()
before = time.perf_counter()
logits = semantic_model(**inputs).logits
resized = functional.interpolate(
logits,
size=image.shape[:2],
mode="bilinear",
align_corners=False,
)
semantic = resized.argmax(dim=1)[0].detach().cpu().numpy().astype(np.uint8)
torch.cuda.synchronize()
latency_ms = (time.perf_counter() - before) * 1000.0
semantic_latencies.append(latency_ms)
overlay, classes = _semantic_overlay(image, semantic, semantic_labels)
stem = path.stem
_write_png(staging / f"{stem}.semantic.png", semantic)
_write_png(staging / f"{stem}.semantic-overlay.png", overlay)
semantic_results[stem] = {
"latency_ms": round(latency_ms, 6),
"classes": classes,
}
del semantic_model
torch.cuda.empty_cache()
rows = []
for path, image in decoded:
stem = path.stem
instance_overlay = np.asarray(Image.open(staging / f"{stem}.instance-overlay.png"))
semantic_overlay = np.asarray(Image.open(staging / f"{stem}.semantic-overlay.png"))
rows.append(np.concatenate((image, instance_overlay, semantic_overlay), axis=1))
_write_png(staging / "segmentation-mosaic.png", np.concatenate(rows, axis=0))
checkpoint = Path(instance_weights.url).name
checkpoint_path = Path(torch.hub.get_dir()) / "checkpoints" / checkpoint
model_files = [
{
"name": f"beit/{path.name}",
"bytes": path.stat().st_size,
"sha256": _sha256(path),
}
for path in sorted(semantic_snapshot.iterdir())
if path.is_file()
]
model_files.append(
{
"name": f"torchvision/{checkpoint}",
"bytes": checkpoint_path.stat().st_size,
"sha256": _sha256(checkpoint_path),
}
)
inputs_manifest = [
{"name": path.name, "bytes": path.stat().st_size, "sha256": _sha256(path)}
for path in images
]
identity = {
"inputs": inputs_manifest,
"instance_model": "torchvision/maskrcnn_resnet50_fpn_v2/default",
"instance_threshold": INSTANCE_SCORE_THRESHOLD,
"semantic_model": SEMANTIC_MODEL_ID,
"semantic_revision": SEMANTIC_REVISION,
"model_files": model_files,
}
manifest: dict[str, Any] = {
"schema_version": RESULT_SCHEMA,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"classification": "private-derived-recorded-perception-experiment",
"generation_sha256": hashlib.sha256(_canonical_json(identity)).hexdigest(),
"input": inputs_manifest,
"models": {
"instance": {
"id": "torchvision/maskrcnn_resnet50_fpn_v2",
"weights": str(instance_weights),
"weights_url": instance_weights.url,
"score_threshold": INSTANCE_SCORE_THRESHOLD,
"license": "BSD-3-Clause (TorchVision code/weight distribution)",
},
"semantic": {
"id": SEMANTIC_MODEL_ID,
"revision": SEMANTIC_REVISION,
"dataset": "ADE20K scene_parse_150",
"license": "Apache-2.0 (model-card metadata)",
"checkpoint_load": "exact-no-missing-unexpected-or-mismatched-keys",
},
"files": model_files,
},
"runtime": {
"base_image": os.environ.get("MISSION_CORE_BASE_IMAGE", "unknown"),
"python": platform.python_version(),
"torch": torch.__version__,
"torchvision": torchvision.__version__,
"transformers": transformers.__version__,
"cuda_runtime": torch.version.cuda,
"cudnn": torch.backends.cudnn.version(),
"gpu": torch.cuda.get_device_name(0),
"packages": sorted(
(
{
"name": distribution.metadata["Name"],
"version": distribution.version,
}
for distribution in importlib.metadata.distributions()
if distribution.metadata["Name"]
),
key=lambda item: str(item["name"]).lower(),
),
},
"metrics": {
"frame_count": len(decoded),
"instance_latency_ms": {
"mean": round(statistics.fmean(instance_latencies), 6),
"max": round(max(instance_latencies), 6),
},
"semantic_latency_ms": {
"mean": round(statistics.fmean(semantic_latencies), 6),
"max": round(max(semantic_latencies), 6),
},
"elapsed_seconds": round(time.perf_counter() - started, 6),
},
"frames": {
stem: {
"instance": instance_results[stem],
"semantic": semantic_results[stem],
}
for stem in sorted(instance_results)
},
"acceptance": {
"recorded_segmentation_artifact": "generated",
"quality": "operator-review-required",
"live": "not-tested",
"safety": "not-accepted",
},
}
artifacts = sorted(staging.glob("*.png"))
manifest["outputs"] = [_artifact(path) for path in artifacts]
(staging / "manifest.redacted.json").write_bytes(
json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True).encode("utf-8")
+ b"\n"
)
os.rename(staging, output_root)
except BaseException:
shutil.rmtree(staging, ignore_errors=True)
raise
print(
json.dumps({"output": output_root.name, "generation_sha256": manifest["generation_sha256"]})
)
return 0
if __name__ == "__main__":
raise SystemExit(main())