feat(perception): add persistent lidar motion evidence
This commit is contained in:
parent
e6dd8c2cf1
commit
230cba4b21
|
|
@ -0,0 +1,273 @@
|
|||
# LAB E25 — persistent LiDAR support and honest motion evidence
|
||||
|
||||
Date: 2026-07-24
|
||||
Status: diagnostic artifact accepted; motion benchmark not accepted
|
||||
Best immutable replay:
|
||||
`LAB E25.3 · Persistent support · full RAVNOVES00`
|
||||
|
||||
## Objective
|
||||
|
||||
Test a different motion measurement after E24 proved that the center of a
|
||||
completed 3D cuboid is not a stable world-space observation. E25 tracks the
|
||||
LiDAR cells that actually support an object in `k1-map`, compares their
|
||||
occupancy through time and emits `static`, `dynamic`, or `unknown`.
|
||||
|
||||
This is not a box beautification experiment. The target is a bounded,
|
||||
streaming-equivalent evidence branch for an unmanned vehicle. 3D boxes remain
|
||||
an operator-facing presentation. Missing sensor evidence must remain
|
||||
`unknown/occupied`; it must not be converted into an invented motion estimate
|
||||
or free space.
|
||||
|
||||
## Source data and immutable inputs
|
||||
|
||||
- Source session: `20260720T065719Z_viewer_live` (`RAVNOVES00`).
|
||||
- Camera source: `sensor.camera.right`, factory slot `camera_1`.
|
||||
- Source frames: 4,489.
|
||||
- Source timeline: 35.421857292–484.044857292 session seconds.
|
||||
- Full map-frame LiDAR pack: 9,207,270 points.
|
||||
- Semantic masks: 898 frames at 600×800.
|
||||
- Factory camera model: KB4.
|
||||
- Calibration SHA-256:
|
||||
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
|
||||
- Source integrated result:
|
||||
`e10-integrated-perception-34ade557b5636717aa497fc00355b84df7063e483a175f2f5d3f04c03df1c898`.
|
||||
- Source detector: YOLOX-S, COCO-80, 640×640.
|
||||
- Source semantic model:
|
||||
`tue-mps/cityscapes_semantic_eomt_large_1024`, FP16 autocast.
|
||||
|
||||
E25 does not repeat GPU inference. It reuses the accepted detector and semantic
|
||||
outputs and recomputes object support from the immutable full LiDAR pack,
|
||||
factory projection and rig poses. Raw LiDAR, ground returns, camera media,
|
||||
calibration, detector output and semantic masks were not modified.
|
||||
|
||||
## Why E25 was required
|
||||
|
||||
E24 classified motion from the temporal behavior of fitted cuboid centers.
|
||||
That measurement is biased by visibility: while the rig passes a parked car,
|
||||
different portions of the car become visible, and an amodal box fitter can move
|
||||
its completed center even though the physical object is static.
|
||||
|
||||
This creates a structurally false velocity. More smoothing only delays it.
|
||||
Looser association hides missing observations but can join neighboring cars.
|
||||
Stricter association preserves identity but exposes gaps. E25 therefore changes
|
||||
the measurement rather than tuning another center filter.
|
||||
|
||||
## Implemented pipeline
|
||||
|
||||
For every source detector object, E25:
|
||||
|
||||
1. takes the synchronized full LiDAR scan in `k1-map`;
|
||||
2. projects candidate points through the factory KB4 camera calibration;
|
||||
3. applies detector-box inset and compatible semantic labels;
|
||||
4. performs depth and spatial clustering to reject unrelated projected points;
|
||||
5. excludes ground only from the object-proposal branch while preserving the
|
||||
complete ground and raw point cloud for visualization and downstream use;
|
||||
6. quantizes object support into 0.25 m map-frame occupancy cells;
|
||||
7. stores a bounded history of occupied cells and support points;
|
||||
8. compares the current support with a reference 0.3–2.5 s in the past;
|
||||
9. classifies static evidence from persistent overlap;
|
||||
10. classifies dynamic evidence only when overlap is low and displacement,
|
||||
robust speed, direction consistency and repeated current observations agree.
|
||||
|
||||
The E25 branch is deliberately bounded:
|
||||
|
||||
- maximum 192 live tracks;
|
||||
- maximum 32 support snapshots per track;
|
||||
- maximum 256 points and 256 cells per object snapshot;
|
||||
- no lookahead;
|
||||
- no commands, navigation authority or safety authority.
|
||||
|
||||
The final pass also enforces an important evidence invariant: one current LiDAR
|
||||
measurement can belong only to the current `e24-observed` object. An E24
|
||||
`held-hypothesis` may preserve a short-lived track state, but it receives
|
||||
`current=false` and cannot duplicate the same present-time support.
|
||||
|
||||
## Benchmark contract
|
||||
|
||||
Benchmark v2 is bound to source detector IDs and time windows rather than to
|
||||
any nearby object of the expected class. This prevents a parked car from
|
||||
accidentally satisfying a moving-car event.
|
||||
|
||||
Dynamic operator anchors:
|
||||
|
||||
- 58 s: woman with dog, left;
|
||||
- 65 s: person loading a car, right;
|
||||
- 115 s: adult and child, left;
|
||||
- 127 s: pedestrian on road, right;
|
||||
- 155 s: woman with stroller and child, left;
|
||||
- 170 s: oncoming vehicle, right;
|
||||
- 173 s: vulnerable-road-user group, left;
|
||||
- 183 s: vehicle moving with the rig direction, right.
|
||||
|
||||
Static controls:
|
||||
|
||||
- parked vehicles at 70–90 s;
|
||||
- parked vehicles at 132–148 s;
|
||||
- parked vehicles at 161–166 s.
|
||||
|
||||
Frame review corrected the 183 s anchor from source track 391, which is parked,
|
||||
to source track 373, which is the moving vehicle. Static controls report
|
||||
coverage, classified fraction and false-dynamic fraction separately. A lack of
|
||||
classification is therefore not counted as a correct static result.
|
||||
|
||||
## Run series
|
||||
|
||||
### E25.1 — first persistent-support pass
|
||||
|
||||
- Immutable session: `lab-e25-1-persistent-support`.
|
||||
- Result:
|
||||
`e10-integrated-perception-fac9421996ce5d8622c534f66af1a9bfaea25c7ea8a2bfd1ec954330d12ce9ed`.
|
||||
- Benchmark: 7/11.
|
||||
- Processing p95: 3.966 ms/frame.
|
||||
|
||||
An initial CPU-bound attempt was stopped because the NPZ container was
|
||||
decompressing LiDAR members repeatedly for every frame. This was a laboratory
|
||||
loader defect, not tracker cost. Materializing the immutable arrays once in
|
||||
memory reduced the full pass to about 20 seconds.
|
||||
|
||||
### E25.2 — benchmark identity correction
|
||||
|
||||
- Immutable session: `lab-e25-2-persistent-support`.
|
||||
- Result:
|
||||
`e10-integrated-perception-e24715571eb5c40d6f0a853bb0551737b2935e0ccbb65e2b90061c0f422a49e3`.
|
||||
- Benchmark: 7/11.
|
||||
- Processing p95: 3.970 ms/frame.
|
||||
|
||||
This pass binds the 183 s event to moving source track 373 and adds defensive
|
||||
deduplication for source/frame evidence in the evaluator.
|
||||
|
||||
### E25.3 — single-owner current evidence
|
||||
|
||||
- Immutable session: `lab-e25-3-persistent-support`.
|
||||
- Result:
|
||||
`e10-integrated-perception-9034a5cf306b379cc248c1da17639f140fc896bb55e412bef43f89974de892a3`.
|
||||
- Benchmark: 6/11.
|
||||
- Processing mean/p50/p95:
|
||||
2.114/2.078/4.322 ms per frame.
|
||||
- Processing maximum: 84.791 ms/frame.
|
||||
- Peak live tracker states: 16 of 192.
|
||||
- Current support observations: 7,204.
|
||||
- Explicit held states: 3,073.
|
||||
- Full laboratory packaging: approximately 20.5 s.
|
||||
|
||||
The score decreased from 7/11 to 6/11 because held hypotheses no longer reuse
|
||||
current LiDAR evidence. This is the accepted result: it is less flattering and
|
||||
more correct.
|
||||
|
||||
## E25.3 event results
|
||||
|
||||
Passed:
|
||||
|
||||
- woman/dog at 58 s;
|
||||
- adult/child at 115 s;
|
||||
- vulnerable-road-user group at 173 s;
|
||||
- all three parked-vehicle control windows.
|
||||
|
||||
Static control details:
|
||||
|
||||
- 70–90 s: coverage 0.4619, classified fraction 0.77665,
|
||||
306 static, 88 unknown, false-dynamic fraction 0.0;
|
||||
- 132–148 s: coverage 0.5683, classified fraction 0.63948,
|
||||
298 static, 168 unknown, false-dynamic fraction 0.0;
|
||||
- 161–166 s: coverage 0.1722, classified fraction 0.61538,
|
||||
16 static, 10 unknown, false-dynamic fraction 0.0.
|
||||
|
||||
Failed:
|
||||
|
||||
- person loading car at 65 s: two current dynamic hits, minimum is three;
|
||||
- right-side person at 127 s: only 3/21 frames contain usable LiDAR support;
|
||||
- stroller group at 155 s: support exists, but association is ambiguous and
|
||||
forks across source tracks;
|
||||
- oncoming car at 170 s: only 2/15 current LiDAR-support observations, so no
|
||||
reliable temporal history can be formed;
|
||||
- same-direction car at 183 s, source track 373: the available evidence is
|
||||
strong (`overlap=0`, approximately 4.8–4.9 m/s), but only two current dynamic
|
||||
hits remain after held-evidence duplication is removed.
|
||||
|
||||
## Performance and replay
|
||||
|
||||
The tracker timing is CPU diagnostic timing without repeated detector or
|
||||
semantic inference. Its p95 of 4.322 ms/frame is compatible with a future
|
||||
near-real-time evidence branch, but it does not prove end-to-end real-time
|
||||
performance.
|
||||
|
||||
The first cold Rerun publication exposed a fail-soft viewer bug: a provisional
|
||||
cuboid can legitimately have `distance_smoothed_m=null`, while the label
|
||||
renderer called `float(None)`. The renderer now keeps the cuboid and simply
|
||||
omits an unconfirmed range from its label.
|
||||
|
||||
The final E25.3 overlay:
|
||||
|
||||
- Rerun magic: `RRF2`;
|
||||
- payload: 69,809,981 bytes;
|
||||
- saved session: `ready`;
|
||||
- replayable: `true`;
|
||||
- warm endpoint response on the existing localhost server: HTTP 200 in 0.11 s.
|
||||
|
||||
The server on `127.0.0.1:8000` was not stopped or restarted.
|
||||
|
||||
## Main conclusion
|
||||
|
||||
E25 fixes an important E24 failure: persistent map-frame LiDAR support no longer
|
||||
turns cuboid-center drift on parked cars into false motion. All static control
|
||||
windows have a false-dynamic fraction of 0.0.
|
||||
|
||||
E25 also establishes the sensor limit. A LiDAR-only support branch cannot infer
|
||||
motion when the object receives too few current returns. Threshold changes
|
||||
cannot create absent observations. Treating those gaps as a confident velocity
|
||||
would make the output visually smoother but less safe.
|
||||
|
||||
E25.3 is therefore accepted as a diagnostic and evidence-quality result, not as
|
||||
a navigation or safety motion classifier.
|
||||
|
||||
## Change of emphasis for E26
|
||||
|
||||
E26 must add information from an independent source instead of further tuning
|
||||
the LiDAR branch:
|
||||
|
||||
1. maintain a temporally stable camera track in image space;
|
||||
2. remove apparent image motion caused by rig ego-motion;
|
||||
3. turn the factory KB4 pixel ray, rig pose/extrinsic and a bounded
|
||||
ground-contact/range hypothesis into world-motion evidence;
|
||||
4. fuse that evidence conservatively with the E25 LiDAR branch;
|
||||
5. emit explicit `agree`, `single-source/unknown` and `conflict` outcomes;
|
||||
6. keep unknown objects occupied for planning;
|
||||
7. expose occupancy footprint, velocity and uncertainty to planner-facing
|
||||
consumers while retaining boxes for the operator UI.
|
||||
|
||||
Acceptance must be split into independent gates:
|
||||
|
||||
- camera-track association integrity and ID-switch rate;
|
||||
- current LiDAR-support coverage;
|
||||
- false dynamic on parked vehicles;
|
||||
- recall on the known moving people and two moving vehicles;
|
||||
- camera/LiDAR conflict rate;
|
||||
- end-to-end stage latency and bounded memory.
|
||||
|
||||
This follows the established autonomous-driving separation between detection,
|
||||
tracking, occupancy and validation. Foxglove and Rerun visualize those
|
||||
products; they do not supply the motion-estimation algorithm.
|
||||
|
||||
## Validation
|
||||
|
||||
- 30 targeted E10/E24/E25 tests pass after the viewer fail-soft fix.
|
||||
- Ruff passes for the changed modules.
|
||||
- Targeted strict mypy passes for the changed compute modules.
|
||||
- E25.3 passes the integrated-perception validator.
|
||||
- The immutable saved session is ready and replayable on
|
||||
`http://127.0.0.1:8000/`.
|
||||
- Raw evidence was not modified.
|
||||
- Navigation and safety acceptance remain explicitly false.
|
||||
|
||||
## External implementation references
|
||||
|
||||
- Autoware multi-object tracker:
|
||||
<https://autowarefoundation.github.io/autoware_universe/pr-10143/perception/autoware_multi_object_tracker/>
|
||||
- Autoware detection-by-tracker:
|
||||
<https://autowarefoundation.github.io/autoware_universe/main/perception/autoware_detection_by_tracker/>
|
||||
- Autoware probabilistic occupancy grid:
|
||||
<https://autowarefoundation.github.io/autoware_universe/pr-10077/perception/autoware_probabilistic_occupancy_grid_map/>
|
||||
- Autoware obstacle point-cloud validator:
|
||||
<https://autowarefoundation.github.io/autoware_universe/pr-10075/perception/autoware_detected_object_validation/obstacle-pointcloud-based-validator/>
|
||||
- Autoware perception architecture:
|
||||
<https://autowarefoundation.github.io/autoware-documentation/main/design/autoware-architecture-v1/components/perception/>
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
"schema_version": "missioncore.e25-motion-benchmark/v1",
|
||||
"benchmark_id": "ravnoves00-spatially-bound-operator-anchors-v2",
|
||||
"source_session_id": "20260720T065719Z_viewer_live",
|
||||
"timeline": "session_seconds",
|
||||
"annotation_status": "operator-approximate-with-reviewed-detector-track-binding",
|
||||
"events": [
|
||||
{
|
||||
"id": "dynamic-person-woman-dog-left-58s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [54.0, 62.0],
|
||||
"class_group": "person",
|
||||
"target_source_track_ids": [83],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.08
|
||||
},
|
||||
{
|
||||
"id": "dynamic-person-loading-car-right-65s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [62.0, 69.0],
|
||||
"class_group": "person",
|
||||
"target_source_track_ids": [112],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.1
|
||||
},
|
||||
{
|
||||
"id": "static-vehicles-before-moving-70-90s",
|
||||
"kind": "static-control",
|
||||
"window_seconds": [70.0, 90.0],
|
||||
"class_group": "vehicle",
|
||||
"expected_motion": "static",
|
||||
"minimum_source_observations": 80,
|
||||
"minimum_coverage_fraction": 0.2,
|
||||
"minimum_classified_fraction": 0.2,
|
||||
"maximum_false_dynamic_fraction": 0.1
|
||||
},
|
||||
{
|
||||
"id": "dynamic-person-adult-child-left-115s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [111.0, 120.0],
|
||||
"class_group": "person",
|
||||
"target_source_track_ids": [229],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.08
|
||||
},
|
||||
{
|
||||
"id": "dynamic-person-road-right-127s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [124.0, 132.0],
|
||||
"class_group": "person",
|
||||
"target_source_track_ids": [253],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 2,
|
||||
"minimum_span_seconds": 0.15,
|
||||
"minimum_coverage_fraction": 0.08
|
||||
},
|
||||
{
|
||||
"id": "static-vehicles-mid-run-132-148s",
|
||||
"kind": "static-control",
|
||||
"window_seconds": [132.0, 148.0],
|
||||
"class_group": "vehicle",
|
||||
"expected_motion": "static",
|
||||
"minimum_source_observations": 80,
|
||||
"minimum_coverage_fraction": 0.2,
|
||||
"minimum_classified_fraction": 0.2,
|
||||
"maximum_false_dynamic_fraction": 0.1
|
||||
},
|
||||
{
|
||||
"id": "dynamic-person-stroller-group-left-155s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [151.0, 161.0],
|
||||
"class_group": "person",
|
||||
"target_source_track_ids": [322, 328],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.08
|
||||
},
|
||||
{
|
||||
"id": "static-vehicles-before-oncoming-161-166s",
|
||||
"kind": "static-control",
|
||||
"window_seconds": [161.0, 166.0],
|
||||
"class_group": "vehicle",
|
||||
"expected_motion": "static",
|
||||
"minimum_source_observations": 25,
|
||||
"minimum_coverage_fraction": 0.15,
|
||||
"minimum_classified_fraction": 0.15,
|
||||
"maximum_false_dynamic_fraction": 0.1
|
||||
},
|
||||
{
|
||||
"id": "dynamic-vehicle-oncoming-right-170s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [167.0, 173.0],
|
||||
"class_group": "vehicle",
|
||||
"target_source_track_ids": [365],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.15,
|
||||
"minimum_coverage_fraction": 0.1
|
||||
},
|
||||
{
|
||||
"id": "dynamic-person-bike-group-left-173s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [170.0, 179.0],
|
||||
"class_group": "vulnerable_road_user",
|
||||
"target_source_track_ids": [379, 386, 388],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.08
|
||||
},
|
||||
{
|
||||
"id": "dynamic-vehicle-same-direction-right-183s",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [180.0, 188.0],
|
||||
"class_group": "vehicle",
|
||||
"target_source_track_ids": [373],
|
||||
"binding_review": "frame-and-2d-track-reviewed; 391 is a parked vehicle",
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 3,
|
||||
"minimum_span_seconds": 0.2,
|
||||
"minimum_coverage_fraction": 0.1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"schema_version": "missioncore.e25-persistent-support-profile/v1",
|
||||
"profile_id": "lab-e25-map-frame-persistent-support-v1",
|
||||
"mode": "recorded-streaming-qualification",
|
||||
"source": {
|
||||
"source_id": "sensor.camera.right",
|
||||
"coordinate_frame": "k1-map",
|
||||
"calibration_slot": "camera_1",
|
||||
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
|
||||
},
|
||||
"support": {
|
||||
"minimum_evidence_points": {
|
||||
"person": 2,
|
||||
"bicycle": 2,
|
||||
"motorcycle": 2,
|
||||
"vehicle": 2,
|
||||
"default": 2
|
||||
},
|
||||
"maximum_points_per_object": 256
|
||||
},
|
||||
"occupancy": {
|
||||
"voxel_size_m": 0.25,
|
||||
"neighbor_radius_cells": 1,
|
||||
"reference_minimum_age_seconds": {
|
||||
"person": 0.3,
|
||||
"bicycle": 0.3,
|
||||
"motorcycle": 0.3,
|
||||
"vehicle": 0.55,
|
||||
"default": 0.55
|
||||
},
|
||||
"reference_maximum_age_seconds": 2.5,
|
||||
"history_seconds": 2.0,
|
||||
"static_overlap_enter": 0.55,
|
||||
"static_overlap_exit": 0.35,
|
||||
"dynamic_overlap_enter": 0.25,
|
||||
"dynamic_overlap_exit": 0.4,
|
||||
"dynamic_minimum_speed_mps": {
|
||||
"person": 0.4,
|
||||
"bicycle": 0.5,
|
||||
"motorcycle": 0.6,
|
||||
"vehicle": 0.7,
|
||||
"default": 0.7
|
||||
},
|
||||
"dynamic_minimum_displacement_m": {
|
||||
"person": 0.4,
|
||||
"bicycle": 0.45,
|
||||
"motorcycle": 0.55,
|
||||
"vehicle": 0.65,
|
||||
"default": 0.65
|
||||
},
|
||||
"minimum_direction_consistency": 0.6,
|
||||
"dynamic_confirmation_frames": {
|
||||
"person": 2,
|
||||
"bicycle": 2,
|
||||
"motorcycle": 2,
|
||||
"vehicle": 5,
|
||||
"default": 5
|
||||
},
|
||||
"static_confirmation_frames": 4,
|
||||
"contradiction_frames": 2,
|
||||
"maximum_support_hold_seconds": 0.45,
|
||||
"maximum_snapshots_per_track": 32,
|
||||
"maximum_cells_per_snapshot": 256
|
||||
},
|
||||
"bounds": {
|
||||
"maximum_tracks": 192,
|
||||
"maximum_track_idle_seconds": 1.5
|
||||
},
|
||||
"acceptance": {
|
||||
"maximum_processing_p95_ms": 12.0,
|
||||
"maximum_tracks_observed": 192
|
||||
},
|
||||
"authority": {
|
||||
"commands_enabled": false,
|
||||
"navigation_or_safety_accepted": false
|
||||
}
|
||||
}
|
||||
|
|
@ -36,12 +36,14 @@ from .jobs import (
|
|||
)
|
||||
from .lab_instances import (
|
||||
PublishedIntegratedLabInstance,
|
||||
PublishedPersistentSupportLabInstance,
|
||||
PublishedTemporalLabInstance,
|
||||
PublishedWorldMotionLabInstance,
|
||||
publish_e21_lab_instance,
|
||||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_e24_lab_instance,
|
||||
publish_e25_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from .live_perception import (
|
||||
|
|
@ -120,6 +122,7 @@ __all__ = [
|
|||
"LivePerceptionIngress",
|
||||
"IntegratedPerceptionOverlayStore",
|
||||
"PublishedIntegratedLabInstance",
|
||||
"PublishedPersistentSupportLabInstance",
|
||||
"PublishedTemporalLabInstance",
|
||||
"PublishedWorldMotionLabInstance",
|
||||
"IntegratedPerceptionResult",
|
||||
|
|
@ -154,6 +157,7 @@ __all__ = [
|
|||
"publish_e22_lab_instance",
|
||||
"publish_e23_lab_instance",
|
||||
"publish_e24_lab_instance",
|
||||
"publish_e25_lab_instance",
|
||||
"publish_integrated_lab_instance",
|
||||
"validate_multirate_perception_qualification_result",
|
||||
"prepare_recorded_qualification_slice",
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class _PresentedCuboid:
|
|||
track_id: int
|
||||
label: str
|
||||
association_group: str
|
||||
distance_m: float
|
||||
distance_m: float | None
|
||||
support_points: int
|
||||
center: np.ndarray
|
||||
half_size: np.ndarray
|
||||
|
|
@ -126,12 +126,20 @@ class _CuboidPresentationState:
|
|||
raise RecordedPerceptionOverlayError(
|
||||
"integrated perception cuboid track identity is invalid"
|
||||
)
|
||||
distance_value = item.get("distance_smoothed_m")
|
||||
distance_m = (
|
||||
float(distance_value)
|
||||
if isinstance(distance_value, int | float)
|
||||
and not isinstance(distance_value, bool)
|
||||
and np.isfinite(float(distance_value))
|
||||
else None
|
||||
)
|
||||
self._latest[track_id] = _PresentedCuboid(
|
||||
observed_ns=timestamp_ns,
|
||||
track_id=track_id,
|
||||
label=str(item.get("label", "object")),
|
||||
association_group=str(item.get("association_group", "object")),
|
||||
distance_m=float(item["distance_smoothed_m"]),
|
||||
distance_m=distance_m,
|
||||
support_points=int(item["clustered_points"]),
|
||||
center=np.asarray(center).copy(),
|
||||
half_size=np.asarray(half_size).copy(),
|
||||
|
|
@ -160,9 +168,10 @@ class _CuboidPresentationState:
|
|||
color[3] = max(24, round(float(color[3]) * (1.0 - 0.55 * fade)))
|
||||
presented_colors.append(color)
|
||||
age_label = "" if age_ns == 0 else f" · hold {age_ns / 1_000_000:.0f} ms"
|
||||
distance_label = "" if cuboid.distance_m is None else f" · {cuboid.distance_m:.1f} m"
|
||||
labels.append(
|
||||
f"{cuboid.association_group} #{cuboid.track_id} {cuboid.label} · "
|
||||
f"{cuboid.distance_m:.1f} m · {cuboid.support_points} pts{age_label}"
|
||||
f"{cuboid.association_group} #{cuboid.track_id} {cuboid.label}"
|
||||
f"{distance_label} · {cuboid.support_points} pts{age_label}"
|
||||
)
|
||||
return (
|
||||
np.stack([cuboid.center for cuboid in presented]),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ from .integrated_perception import (
|
|||
validate_integrated_perception_result,
|
||||
)
|
||||
from .jobs import CameraComputeJob, validate_camera_compute_job
|
||||
from .occupancy_motion import (
|
||||
PersistentSupportBuild,
|
||||
build_persistent_support_result,
|
||||
)
|
||||
from .temporal_stability import (
|
||||
TemporalStabilityBuild,
|
||||
_quality_metrics,
|
||||
|
|
@ -59,6 +63,14 @@ class PublishedWorldMotionLabInstance:
|
|||
build: WorldMotionBuild
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PublishedPersistentSupportLabInstance:
|
||||
binding: LabSessionBinding
|
||||
job: CameraComputeJob
|
||||
result: IntegratedPerceptionResult
|
||||
build: PersistentSupportBuild
|
||||
|
||||
|
||||
def publish_integrated_lab_instance(
|
||||
*,
|
||||
repository_root: Path,
|
||||
|
|
@ -590,6 +602,113 @@ def publish_e24_lab_instance(
|
|||
)
|
||||
|
||||
|
||||
def publish_e25_lab_instance(
|
||||
*,
|
||||
repository_root: Path,
|
||||
source_result_root: Path,
|
||||
world_motion_profile_path: Path,
|
||||
profile_path: Path,
|
||||
benchmark_path: Path,
|
||||
lab_session_id: str,
|
||||
lab_id: str,
|
||||
display_name: str,
|
||||
) -> PublishedPersistentSupportLabInstance:
|
||||
"""Derive and publish one bounded persistent-support motion LAB run."""
|
||||
|
||||
root = repository_root.expanduser().resolve(strict=True)
|
||||
jobs_root = root / ".runtime" / "compute-jobs"
|
||||
results_root = root / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
packs_root = root / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
source_path = source_result_root.expanduser().resolve(strict=True)
|
||||
source_document = _read_object(source_path / "result.json", source_path)
|
||||
identity = source_document.get("identity")
|
||||
if not isinstance(identity, dict) or not isinstance(identity.get("job_id"), str):
|
||||
raise SessionIntegrityError("E25 source has no job identity")
|
||||
source = validate_integrated_perception_result(
|
||||
jobs_root / identity["job_id"],
|
||||
source_path,
|
||||
packs_root,
|
||||
)
|
||||
if not source.accepted:
|
||||
raise SessionIntegrityError("E25 source result is not accepted")
|
||||
|
||||
lab_job = _publish_lab_job(source.job, jobs_root, lab_session_id)
|
||||
lab_pack = _publish_lab_pack(source, lab_job, packs_root, lab_session_id)
|
||||
build = build_persistent_support_result(
|
||||
source=source,
|
||||
lab_job=lab_job,
|
||||
lab_pack=lab_pack,
|
||||
results_root=results_root,
|
||||
world_motion_profile_path=world_motion_profile_path,
|
||||
profile_path=profile_path,
|
||||
benchmark_path=benchmark_path,
|
||||
)
|
||||
validated = validate_integrated_perception_result(
|
||||
lab_job.job_root,
|
||||
build.result_root,
|
||||
packs_root,
|
||||
)
|
||||
if not validated.accepted:
|
||||
failed = [
|
||||
name for name, accepted in build.report["acceptance"]["checks"].items() if not accepted
|
||||
]
|
||||
raise SessionIntegrityError(
|
||||
f"E25 persistent-support artifact acceptance failed: {', '.join(failed)}"
|
||||
)
|
||||
|
||||
store = SessionStore(root)
|
||||
source_lab = store.get_lab_instance(source.job.session_id)
|
||||
source_session_id = (
|
||||
source.job.session_id if source_lab is None else source_lab.source_session_id
|
||||
)
|
||||
publish_lab_replay_cache(
|
||||
store.data_dir,
|
||||
source_session_id=source_session_id,
|
||||
lab_session_id=lab_session_id,
|
||||
timeline_start_ns=round(validated.timeline_start_seconds * 1_000_000_000),
|
||||
timeline_end_ns=round(validated.timeline_end_seconds * 1_000_000_000),
|
||||
)
|
||||
metrics = build.report["metrics"]
|
||||
binding = store.publish_lab_instance(
|
||||
session_id=lab_session_id,
|
||||
source_session_id=source_session_id,
|
||||
display_name=display_name,
|
||||
lab_id=lab_id,
|
||||
result_kind="e25-persistent-support-motion",
|
||||
result_id=validated.result_id,
|
||||
source_result_id=source.result_id,
|
||||
config_sha256=build.profile_sha256,
|
||||
run_created_at_utc=validated.created_at_utc,
|
||||
duration_seconds=(validated.timeline_end_seconds - validated.timeline_start_seconds),
|
||||
include_recorded_media=False,
|
||||
provenance={
|
||||
"schema_version": "missioncore.e25-lab-publication/v1",
|
||||
"storage_mode": "bounded-persistent-support-and-immutable-source-replay",
|
||||
"source_result_id": source.result_id,
|
||||
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
|
||||
"source_payloads_mutated": False,
|
||||
"coordinate_frame": "k1-map",
|
||||
"measurement": "persistent-object-support-occupancy",
|
||||
"lookahead_frames": 0,
|
||||
"benchmark_sha256": build.benchmark_sha256,
|
||||
"benchmark_passed": metrics["benchmark"]["passed"],
|
||||
"benchmark_passed_events": metrics["benchmark"]["passed_events"],
|
||||
"benchmark_total_events": metrics["benchmark"]["total_events"],
|
||||
"persistent_support_processing_p95_ms": metrics["runtime"][
|
||||
"persistent_support_frame_processing_ms"
|
||||
]["p95"],
|
||||
"peak_tracks": metrics["runtime"]["peak_tracks"],
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
)
|
||||
return PublishedPersistentSupportLabInstance(
|
||||
binding=binding,
|
||||
job=lab_job,
|
||||
result=validated,
|
||||
build=build,
|
||||
)
|
||||
|
||||
|
||||
def _validate_e23_inputs(
|
||||
worker_root: Path,
|
||||
source_report_path: Path,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -23,6 +23,7 @@ from k1link.compute import (
|
|||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_e24_lab_instance,
|
||||
publish_e25_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.analyze import (
|
||||
|
|
@ -656,6 +657,92 @@ def publish_e24_lab(
|
|||
)
|
||||
|
||||
|
||||
@lab_app.command("publish-e25")
|
||||
def publish_e25_lab(
|
||||
result: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Accepted full-session integrated result used as the E25 source.",
|
||||
),
|
||||
],
|
||||
world_profile: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
"--world-profile",
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Bounded E24 world-association profile used before E25 evidence.",
|
||||
),
|
||||
],
|
||||
profile: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Bounded E25 persistent-support profile.",
|
||||
),
|
||||
],
|
||||
benchmark: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Spatially bound E25 motion benchmark.",
|
||||
),
|
||||
],
|
||||
session_id: Annotated[
|
||||
str,
|
||||
typer.Option("--session-id", help="New immutable LAB session id."),
|
||||
],
|
||||
lab_id: Annotated[
|
||||
str,
|
||||
typer.Option("--lab-id", help="LAB marker, for example 'LAB E25.1'."),
|
||||
],
|
||||
display_name: Annotated[
|
||||
str,
|
||||
typer.Option("--display-name", help="Operator-facing saved-session title."),
|
||||
],
|
||||
) -> None:
|
||||
"""Build and publish map-frame persistent-support motion evidence."""
|
||||
|
||||
repository_root = Path(__file__).resolve().parents[4]
|
||||
try:
|
||||
published = publish_e25_lab_instance(
|
||||
repository_root=repository_root,
|
||||
source_result_root=result,
|
||||
world_motion_profile_path=world_profile,
|
||||
profile_path=profile,
|
||||
benchmark_path=benchmark,
|
||||
lab_session_id=session_id,
|
||||
lab_id=lab_id,
|
||||
display_name=display_name,
|
||||
)
|
||||
except (OSError, SessionIntegrityError, RuntimeError, ValueError) as exc:
|
||||
console.print(f"[red]E25 LAB publication failed:[/red] {exc}")
|
||||
raise typer.Exit(code=2) from exc
|
||||
metrics = published.build.report["metrics"]
|
||||
console.print(
|
||||
"[green]E25 LAB instance published.[/green] "
|
||||
f"session={published.binding.session_id}; "
|
||||
f"source={published.binding.source_session_id}; "
|
||||
f"result={published.binding.result_id}; "
|
||||
f"benchmark={metrics['benchmark']['passed_events']}/"
|
||||
f"{metrics['benchmark']['total_events']}; "
|
||||
f"p95={metrics['runtime']['persistent_support_frame_processing_ms']['p95']:.3f}ms; "
|
||||
"source_payloads_mutated=false"
|
||||
)
|
||||
|
||||
|
||||
@app.command("serve")
|
||||
def serve_console(
|
||||
port: Annotated[
|
||||
|
|
|
|||
|
|
@ -267,6 +267,30 @@ def test_recorded_cuboid_presentation_holds_one_failed_association_then_expires(
|
|||
assert expired is None
|
||||
|
||||
|
||||
def test_recorded_cuboid_presentation_omits_unavailable_distance() -> None:
|
||||
state = _CuboidPresentationState(hold_ns=500_000_000)
|
||||
accepted = {
|
||||
"association_group": "person",
|
||||
"clustered_points": 3,
|
||||
"cuboid_status": "accepted-world-track-provisional-e24-v1",
|
||||
"distance_smoothed_m": None,
|
||||
"label": "person",
|
||||
"track_id": 9,
|
||||
}
|
||||
|
||||
presented = state.update(
|
||||
1_000_000_000,
|
||||
[accepted],
|
||||
np.asarray([[1.0, 2.0, 3.0]], dtype=np.float32),
|
||||
np.asarray([[0.35, 0.35, 0.9]], dtype=np.float32),
|
||||
np.asarray([[0.0, 0.0, 0.0, 1.0]], dtype=np.float32),
|
||||
np.asarray([[118, 204, 132, 88]], dtype=np.uint8),
|
||||
)
|
||||
|
||||
assert presented is not None
|
||||
assert presented[4] == ["person #9 person · 3 pts"]
|
||||
|
||||
|
||||
def test_e13_completes_a_visible_car_face_away_from_the_sensor() -> None:
|
||||
fusion, runner = _worker_modules()
|
||||
profile, _digest = runner.read_profile(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,302 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from k1link.compute.occupancy_motion import (
|
||||
PersistentSupportTracker,
|
||||
SupportMeasurement,
|
||||
_apply_occupancy_evidence,
|
||||
evaluate_persistent_support_benchmark,
|
||||
read_persistent_support_benchmark,
|
||||
read_persistent_support_profile,
|
||||
)
|
||||
|
||||
|
||||
def _profile() -> dict[str, object]:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
profile, digest = read_persistent_support_profile(
|
||||
root / "experiments" / "perception" / "e25_persistent_support_profile.json"
|
||||
)
|
||||
assert len(digest) == 64
|
||||
return profile
|
||||
|
||||
|
||||
def _support(x: float, y: float = 2.0) -> np.ndarray:
|
||||
return np.asarray(
|
||||
[
|
||||
[x - 0.2, y - 0.2, 0.4],
|
||||
[x + 0.2, y - 0.2, 0.5],
|
||||
[x - 0.2, y + 0.2, 0.6],
|
||||
[x + 0.2, y + 0.2, 0.7],
|
||||
],
|
||||
dtype=np.float64,
|
||||
)
|
||||
|
||||
|
||||
def test_e25_profile_is_bounded_and_has_no_control_authority() -> None:
|
||||
profile = _profile()
|
||||
|
||||
assert profile["source"]["coordinate_frame"] == "k1-map"
|
||||
assert profile["bounds"]["maximum_tracks"] == 192
|
||||
assert profile["occupancy"]["maximum_snapshots_per_track"] == 32
|
||||
assert profile["authority"] == {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
def test_e25_static_support_survives_visible_surface_center_jitter() -> None:
|
||||
tracker = PersistentSupportTracker(_profile())
|
||||
evidence = {}
|
||||
for frame in range(30):
|
||||
jitter = 0.18 if frame % 2 else -0.18
|
||||
evidence = tracker.observe(
|
||||
track_id=250001,
|
||||
source_track_id=10,
|
||||
group="vehicle",
|
||||
session_seconds=frame * 0.1,
|
||||
points_map=_support(10.0 + jitter),
|
||||
)
|
||||
|
||||
assert evidence["motion_state"] == "static"
|
||||
assert evidence["occupancy_overlap_fraction"] is not None
|
||||
assert evidence["occupancy_overlap_fraction"] >= 0.55
|
||||
|
||||
|
||||
def test_e25_coherent_translation_becomes_dynamic() -> None:
|
||||
tracker = PersistentSupportTracker(_profile())
|
||||
evidence = {}
|
||||
for frame in range(20):
|
||||
evidence = tracker.observe(
|
||||
track_id=250002,
|
||||
source_track_id=20,
|
||||
group="person",
|
||||
session_seconds=frame * 0.1,
|
||||
points_map=_support(4.0 + frame * 0.5),
|
||||
)
|
||||
|
||||
assert evidence["motion_state"] == "dynamic"
|
||||
assert evidence["speed_mps"] is not None
|
||||
assert evidence["speed_mps"] > 1.0
|
||||
assert evidence["occupancy_overlap_fraction"] <= 0.25
|
||||
|
||||
|
||||
def test_e25_does_not_claim_motion_without_older_support() -> None:
|
||||
tracker = PersistentSupportTracker(_profile())
|
||||
|
||||
evidence = tracker.observe(
|
||||
track_id=250003,
|
||||
source_track_id=30,
|
||||
group="vehicle",
|
||||
session_seconds=0.0,
|
||||
points_map=_support(8.0),
|
||||
)
|
||||
|
||||
assert evidence["motion_state"] == "unknown"
|
||||
assert evidence["motion_confidence"] == 0.0
|
||||
|
||||
|
||||
def test_e25_current_support_is_not_attached_to_an_e24_held_hypothesis() -> None:
|
||||
tracker = PersistentSupportTracker(_profile())
|
||||
measurement = SupportMeasurement(
|
||||
source_track_id=40,
|
||||
label="car",
|
||||
group="vehicle",
|
||||
score=0.9,
|
||||
points_map=_support(10.0),
|
||||
source_indices=np.asarray([1, 2, 3, 4], dtype=np.int64),
|
||||
)
|
||||
_apply_occupancy_evidence(
|
||||
[
|
||||
{
|
||||
"track_id": 250040,
|
||||
"source_track_id": 40,
|
||||
"temporal_status": "e24-observed",
|
||||
}
|
||||
],
|
||||
{40: measurement},
|
||||
tracker,
|
||||
0.0,
|
||||
)
|
||||
result = _apply_occupancy_evidence(
|
||||
[
|
||||
{
|
||||
"track_id": 250041,
|
||||
"source_track_id": 40,
|
||||
"temporal_status": "e24-observed",
|
||||
},
|
||||
{
|
||||
"track_id": 250040,
|
||||
"source_track_id": 40,
|
||||
"temporal_status": "e24-held-prediction",
|
||||
},
|
||||
],
|
||||
{40: measurement},
|
||||
tracker,
|
||||
0.1,
|
||||
)
|
||||
|
||||
assert result[0]["occupancy_evidence_current"] is True
|
||||
assert result[1]["occupancy_evidence_current"] is False
|
||||
assert tracker.snapshot()["support_observations"] == 2
|
||||
|
||||
|
||||
def test_e25_target_benchmark_cannot_be_satisfied_by_another_object() -> None:
|
||||
event = {
|
||||
"id": "target-five",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [10.0, 12.0],
|
||||
"class_group": "vehicle",
|
||||
"target_source_track_ids": [5],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 2,
|
||||
"minimum_span_seconds": 0.1,
|
||||
"minimum_coverage_fraction": 0.5,
|
||||
}
|
||||
source_rows = [
|
||||
{
|
||||
"session_seconds": 10.0 + index * 0.2,
|
||||
"objects": [
|
||||
{
|
||||
"track_id": 5,
|
||||
"association_group": "vehicle",
|
||||
}
|
||||
],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
wrong_fusion_rows = [
|
||||
{
|
||||
"session_seconds": 10.0 + index * 0.2,
|
||||
"objects": [
|
||||
{
|
||||
"track_id": 250099,
|
||||
"source_track_id": 99,
|
||||
"association_group": "vehicle",
|
||||
"motion_state": "dynamic",
|
||||
"occupancy_evidence_current": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
|
||||
result = evaluate_persistent_support_benchmark(
|
||||
source_rows,
|
||||
wrong_fusion_rows,
|
||||
{"events": [event]},
|
||||
)
|
||||
|
||||
assert result["passed"] is False
|
||||
assert result["events"][0]["evidence_observations"] == 0
|
||||
|
||||
|
||||
def test_e25_benchmark_deduplicates_world_hypotheses_for_one_source_object() -> None:
|
||||
event = {
|
||||
"id": "target-five",
|
||||
"kind": "target-motion",
|
||||
"window_seconds": [10.0, 12.0],
|
||||
"class_group": "vehicle",
|
||||
"target_source_track_ids": [5],
|
||||
"expected_motion": "dynamic",
|
||||
"minimum_hits": 2,
|
||||
"minimum_span_seconds": 0.1,
|
||||
"minimum_coverage_fraction": 0.5,
|
||||
}
|
||||
source_rows = [
|
||||
{
|
||||
"frame_index": index,
|
||||
"session_seconds": 10.0 + index * 0.2,
|
||||
"objects": [{"track_id": 5, "association_group": "vehicle"}],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
fusion_rows = [
|
||||
{
|
||||
"frame_index": index,
|
||||
"session_seconds": 10.0 + index * 0.2,
|
||||
"objects": [
|
||||
{
|
||||
"track_id": world_track,
|
||||
"source_track_id": 5,
|
||||
"association_group": "vehicle",
|
||||
"motion_state": "dynamic",
|
||||
"motion_confidence": 0.8,
|
||||
"occupancy_cell_count": 4,
|
||||
"occupancy_support_observations": 6,
|
||||
"occupancy_evidence_current": True,
|
||||
}
|
||||
for world_track in (250005, 250006)
|
||||
],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
|
||||
result = evaluate_persistent_support_benchmark(
|
||||
source_rows,
|
||||
fusion_rows,
|
||||
{"events": [event]},
|
||||
)
|
||||
|
||||
assert result["events"][0]["coverage_fraction"] == 1.0
|
||||
assert result["events"][0]["evidence_observations"] == 3
|
||||
assert result["events"][0]["duplicate_evidence_observations"] == 3
|
||||
|
||||
|
||||
def test_e25_static_control_rejects_excess_false_dynamic_evidence() -> None:
|
||||
event = {
|
||||
"id": "parked-vehicles",
|
||||
"kind": "static-control",
|
||||
"window_seconds": [20.0, 22.0],
|
||||
"class_group": "vehicle",
|
||||
"expected_motion": "static",
|
||||
"minimum_source_observations": 3,
|
||||
"minimum_coverage_fraction": 0.5,
|
||||
"minimum_classified_fraction": 0.5,
|
||||
"maximum_false_dynamic_fraction": 0.1,
|
||||
}
|
||||
source_rows = [
|
||||
{
|
||||
"session_seconds": 20.0 + index * 0.2,
|
||||
"objects": [{"track_id": 7, "association_group": "vehicle"}],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
fusion_rows = [
|
||||
{
|
||||
"session_seconds": 20.0 + index * 0.2,
|
||||
"objects": [
|
||||
{
|
||||
"track_id": 250007,
|
||||
"source_track_id": 7,
|
||||
"association_group": "vehicle",
|
||||
"motion_state": "dynamic",
|
||||
"occupancy_evidence_current": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
for index in range(3)
|
||||
]
|
||||
|
||||
result = evaluate_persistent_support_benchmark(
|
||||
source_rows,
|
||||
fusion_rows,
|
||||
{"events": [event]},
|
||||
)
|
||||
|
||||
assert result["passed"] is False
|
||||
assert result["events"][0]["false_dynamic_fraction"] == 1.0
|
||||
|
||||
|
||||
def test_e25_benchmark_contract_uses_reviewed_source_track_bindings() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
benchmark, digest = read_persistent_support_benchmark(
|
||||
root / "experiments" / "perception" / "e25_motion_benchmark.json"
|
||||
)
|
||||
target_events = [event for event in benchmark["events"] if event["kind"] == "target-motion"]
|
||||
|
||||
assert len(digest) == 64
|
||||
assert target_events
|
||||
assert all(event["target_source_track_ids"] for event in target_events)
|
||||
Loading…
Reference in New Issue