feat: nested closed-loop metrics (object/rb/redlight/brake + tdigest)#280
feat: nested closed-loop metrics (object/rb/redlight/brake + tdigest)#280xtk8532704 wants to merge 8 commits into
Conversation
Read/write t4_dataset_id and t4_dataset_version_id from rosbag log_file_info.json so converted datasets keep the upstream T4 identity. Co-authored-by: Cursor <cursoragent@cursor.com>
danielsanchezaran
left a comment
There was a problem hiding this comment.
Reviewed the nested-metric rewire. Good news first: the t-digest is the external tdigest==0.5.2.2 library (correct API usage — empty-guarded, percentile in [0,100], documented to_dict/update_from_dict merge), and the schema wiring is solid — train.py._flat_scalars, format_summary_lines, run_lifelong_r2lpl_rounds._run_closed_loop_probe's keep tuple, and both mining tools all read keys that aggregate actually emits (no KeyError/silent-zero), division guards short-circuit on 0, strong_brake accel source is real and aligned, and the red-light sign is correct. Two real bugs + a couple of nits inline.
Two findings worth blocking on the first: the road_border collision bucket is silently always zero, and the pooled clearance mean is weighted wrong.
| obj_coll = s.collisions[: s.k] | ||
| obj_miss = finite & (cl <= s.near_miss_thresh) | ||
| rb_finite = np.isfinite(rb) | ||
| rb_coll = rb_finite & (rb <= 0.0) |
There was a problem hiding this comment.
Bug — this bucket is structurally always 0. rb here is the per-timestep min border distance from compute_road_border_penalty(...)[5], which is an unsigned point-to-segment distance (planner_metrics/subscores.py _point_to_segments_min_dist, always ≥ 0, inf when no border data). So rb <= 0.0 is measure-zero — road_border.collision_steps/count/segments/*_rate report 0 even on a blatant curb crossing. The canonical crossing predicate in the same module is per_ts_min < rb_cross_thresh (0.20 m, config.py). The miss bucket (rb <= near_miss_thresh = 0.5) still fires, which hides this in casual inspection. Use rb < rb_cross_thresh (or a signed distance) for the collision bucket. Contrast object, whose collision correctly uses a signed-overlap < 0 test.
| digests.append(digest) | ||
| return { | ||
| "clearance_min_m": float(min(mins)) if mins else float("inf"), | ||
| "clearance_mean_m": float(np.average(means, weights=weights)) if means else float("inf"), |
There was a problem hiding this comment.
Bug: clearance_mean_m per segment is the mean over finite clearance steps only, but here it's weighted by n_steps_run (all steps, including the no-neighbor inf steps that were excluded from that mean). Segments with many neighbor-free steps get over-weighted, biasing the pooled global mean. The correct weight is the per-segment count of finite clearance samples — which the segment row doesn't currently emit, so this needs a new per-segment field (or pool from the digests instead).
| ) | ||
| for chunk, result in zip(kept_chunks, results): | ||
| row = {**_chunk_row(chunk), **result.metrics} | ||
| row = {**_chunk_row(chunk), **result} |
There was a problem hiding this comment.
Nit: {**_chunk_row(chunk), **result} dumps the full _finalize dict — including the _tdigest centroid blob from _clearance_stats — straight into segments.jsonl, bypassing metrics_for_json. Serializable (so not a crash), but it defeats the human-readable-jsonl goal and bloats every mined row with nested centroid dicts. Route through metrics_for_json(...). (mine_collisions_reproducer.py has the same pattern.)
|
Flagging the |
feat: propagate t4_dataset_id through bag_metadata
a621d0d to
0b9152c
Compare
danielsanchezaran
left a comment
There was a problem hiding this comment.
Re-reviewed the rebased/latest head. The lockfile follow-ups do not address the two correctness bugs on the existing inline discussions: road_border.collision_* still derives collision from an unsigned distance <= 0, so crossings are effectively reported as zero; and pooled clearance_mean_m is still weighted by all rollout steps instead of finite clearance samples. Both directly affect closed-loop safety comparisons and R2LPL probe summaries.
The broader workflow suite also exposes an incomplete result/state API migration: scenario_generation/tests/test_closed_loop_metrics.py, test_reproducer_collision_scope.py, both reproducer cursor/unstick modules, and full rlvr/autoresearch/tools/test_r2lpl_lifelong_replay.py yield 177 passed, 4 failed, 1 skipped. One failure still returns the removed SimpleNamespace(metrics=...) shape to mine_direct_reproducer_chunks; three fake rollout states lack the newly-required rb_dists/red_light buffers (and retain the old _finalize contract). Please update those harnesses and run the full R2LPL module, not only the four scenario-generation files listed in the PR.
Workflow impact after correction: this is an intentional breaking migration from flat metrics to nested object/road-border/red-light/brake/reproducer blocks; the in-repo mining and lifelong-probe consumers are updated, but historical segments.jsonl/summary consumers will need migration. The t-digest sidecar fixes sharded p5 aggregation, and the sharded summary now correctly persists both strong_brake_mps2 and final elapsed_sec.
feat: Autoware-aligned yaw-gated unstick with arc-length teleport
Extract per-step scorers into scenario_generation/metrics and emit nested summary blocks for object, road_border, red_light_violation, strong_brake, and reproducer. Clearance distributions use t-digest sidecars so segments.jsonl stays human-readable. Event counts use rising-edge debounce. Stacked on kem/refactor-reproducer-test-PR2; source: kem/cl-rb-redlight-metrics. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
0b9152c to
d3458e8
Compare
|
Heads up on merge order: we're going to land #285 before this one. It adds fail-to-resume mining (realized-lag) — something the reproducer couldn't do before (it was mining 0 of these events) and that the lvl4 campaign needs right now. It's a small change and its core lives in The reason to do it first rather than after: this PR reworks the per-chunk metrics and the summary that #285's |
|
Sounds good — I'll wait for #285 to land, then rebase this PR and merge after that. |
|
Update: #285 is merged (squashed into |
What this PR does
Restructures closed-loop evaluation metrics into a nested schema and adds road-border, red-light, and strong-brake scorers, plus clearance t-digest sidecars. Per-step scoring moves into
scenario_generation/metrics/;closed_loop_eval.aggregate/summary.json/segments.jsonlconsume the new layout.Base: #279 (
kem/refactor-reproducer-test-PR2). Retarget totier4-mainafter #279 merges.Nested summary schema
Each segment row (and the run
summary.json) groups metrics by family instead of flatn_collision_steps-style keys:objectroad_borderred_light_violationstrong_brakereproducerexpand_count,snap_count,normal_steps,repeat_steps,repeat_step_rateterminated_countsEvent
*_countuses rising-edge detection with a short falling-edge debounce so threshold flicker does not re-count the same event.Breaking: old flat keys (
n_collision_steps,n_near_miss_steps,n_snaps, …) are gone. In-repo callers (train.py,valid_predictor_closed_loop.py, R2LPL / mining tools) are updated in this PR.New scorers (
scenario_generation/metrics/)object.py— OBB ego↔neighbor clearance / collision / miss (extracted from rollout)road_border.py— distance toline_stringscurb geometryred_light.py+ego_traj.py— red-light violation using live ego historystrong_brake.py— realized tangential accel at/below--strong_brake_mps2(default −2.5)tdigest.py— clearance distribution helpersClearance t-digest sidecar
Clearance digests are written to
tdigests.jsonl/tdigests_{rank}.jsonlsosegments.jsonlstays human-readable. Multi-GPU merge reattaches sidecars beforeaggregate(p5 via merged digests).--strong_brake_mps2is passed through sharded parent merge so sequential and multi-GPU summaries share the same schema.Testing
tests/test_closed_loop_metrics.py,test_reproducer_unstick.py,test_perception_reproducer_cursor.py,test_reproducer_collision_scope.pysummary.json,segments.jsonl,tdigests.jsonl