AsianPlayer/TriDrive / code /eval /kin_v4_build_stats.py
AsianPlayer's picture
download
raw
33.8 kB
"""cwm3/eval/kin_v4_build_stats.py — diagnostics of the hands_on / hand-event label v4 (pre-registered docs/decisions.md 2026-09-16T22:18:02Z)
against v2 and v3, from the TARGET FILES (results/targets_kin_causal_routes_{v2,v3,v4}; cells present in v4 only) and the v4 fit log.
Sections (all numbers computed here; nothing hand-typed):
1. per regime x split: rows, row_ok, wheel-ellipse defined share (v4) vs wheel-point-reference defined share (v3), S0 finite v2/v3/v4,
hands_on share v2/v3/v4 (nanmean of S_c[:,:,0] over the 25 future steps), EM hand share v4, and the decomposition of the anchor signed
distance d0 (row_ok rows with finite d0): deep interior d0 < -0.04 | inside near the rim -0.04 <= d0 < 0 | rim band 0 <= d0 < 0.04 |
dead band 0.04 <= d0 <= 0.08 | off d0 > 0.08 — i.e. how much of the ON state comes from a wrist projecting INSIDE the wheel ellipse
(lap / hub behind the wheel in 2-D) rather than from the rim band;
2. events per hour (unique (route, 0.2 s frame) events with E_c = 1 & EM_c; hours = unique EM frames x 0.2 s) for hand_return / hand_leave /
head_turn_onset, v2 / v3 / v4, and the share of v3 events with a v4 event of the same type within +-1 s (and vice versa);
3. ellipse geometry sanity per device from route_refs.npz fits__<uid>: refits, passed, pass share, share of the device's pose frames /
manual moving frames with a defined ellipse, median [IQR] of a, b, b/a, centre, coverage over the passed fits; FLAG when the median a or b
of the passed fits is < 0.05 or > 0.6, or any passed fit has a semi-axis outside that range (the RANSAC candidate range is [0.05, 0.6],
so the second condition can only fire through the LO refit);
4. the owner-observation test on the 68 v3 hand_return validation items (results/review_tool/items_relabel_v3.json, block hand_return):
(i) excursion of the v3 OFF run preceding the event from the v3 POINT reference (max of the v3 min-wrist distance over the OFF run;
recomputed from the v3 route_refs wheel reference + pose), its duration, median and share < 0.25 (= rim sliding), and
(ii) whether a v4 hand_return exists within +-1 s of t_event (unique-event times, same regime's test file) and whether the v4 state is
ON throughout the page window [-2 s, +1 s], the range of the v4 signed distance d over that window and the share of window anchors with a
wrist inside the ellipse (d < 0); cross-tabulated against the owner's labels in results/review_tool/labels_latest.json
(V3_* ids: ret_dt=<dt> -> 'return', always_on, never_on, unsure, or unlabelled);
5. the 34 pass-3 engaged hand_return chatter events (results/review_tool/items.json, task B, engaged): share with a v4 hand_return within +-1 s.
Writes results/tables/kin_v4_build_stats.md and .json (+ run json). Read-only on all inputs."""
import json, sys, time
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "common")); sys.path.insert(0, str(ROOT / "eval")); sys.path.insert(0, str(ROOT / "data"))
from runmeta import write_run_json # noqa: E402
import kin_v3_build_stats as K3 # noqa: E402 (unique_events, match_share; frozen v3 script imported, not copied)
import build_kin_causal as BK # noqa: E402
import build_kin_causal_v3 as V3 # noqa: E402
import build_kin_causal_v4 as V4 # noqa: E402
V2 = ROOT / "results" / "targets_kin_causal_routes_v2"; V3D = ROOT / "results" / "targets_kin_causal_routes_v3"; V4D = ROOT / "results" / "targets_kin_causal_routes_v4"
ITEMS_V3 = ROOT / "results" / "review_tool" / "items_relabel_v3.json"; LABELS = ROOT / "results" / "review_tool" / "labels_latest.json"; ITEMS_B = ROOT / "results" / "review_tool" / "items.json"
TABLES = ROOT / "results" / "tables"
EVENTS = K3.EVENTS; H = K3.H
AX_LO, AX_HI = 0.05, 0.6; EXC_TH = 0.25; WIN = (-2.0, 1.0)
def pose_path(u):
p = V4.POSE_PREF / f"{u}.npz"
return p if p.exists() else BK.POSE_R / f"{u}.npz"
def fmt(x, p=3):
return "—" if x is None or (isinstance(x, float) and not np.isfinite(x)) else f"{x:.{p}f}"
def nearest_dt(frames_uid, frames_k, ui, kf):
f = frames_k[frames_uid == ui].astype(np.int64)
if not len(f): return None, False
d = f - kf; j = np.abs(d).argmin(); return float(d[j] / 10.0), bool(abs(d[j]) <= 10)
def v3_off_run_excursion(u, t_event, rr3):
"""Recompute the v3 series of route u (v3 point reference from route_refs + pose): returns dict(excursion, off_run_s, d_at_event, state_at_event)
for the OFF run that precedes the ON run containing the event frame (stamp = first ON frame; t_event grid step covers frames ke-1, ke)."""
z = np.load(pose_path(u)); t = z["t"].astype(np.float64); F = np.isfinite(z["box"][:, 0]) & np.isfinite(z["kpts"][:, 0, 2].astype(np.float32))
raw = BK.raw_from_kpts(z["kpts"], F); wref = rr3[f"wheel_ref__{u}"]; d = V3.wheel_distance(raw["q"], wref); d[~F] = np.nan
k = np.round(t * 10).astype(np.int64); k0 = int(k.min()); M = int(k.max()) - k0 + 1; DD = np.full(M, np.nan, np.float32); DD[k - k0] = d
s_raw, s_held = V3.hyst_hands(DD); ke = int(round(t_event * 10)) - k0
out = dict(excursion=None, off_run_s=None, d_at_event=float(DD[ke]) if 0 <= ke < M else None, found=False)
if not (0 <= ke < M): return out
kon = ke if (np.isfinite(s_raw[ke]) and s_raw[ke] == 1) else (ke - 1 if ke >= 1 and np.isfinite(s_raw[ke - 1]) and s_raw[ke - 1] == 1 else None)
if kon is None: return out
while kon - 1 >= 0 and s_held[kon - 1] == 1: kon -= 1 # start of the ON run (held across short gaps)
j = kon - 1
if j < 0 or not (s_held[j] == 0): return out
i = j
while i - 1 >= 0 and s_held[i - 1] == 0: i -= 1 # start of the OFF run
seg = DD[i:j + 1]
out.update(excursion=float(np.nanmax(seg)) if np.isfinite(seg).any() else None, off_run_s=float((j - i + 1) / 10.0), found=True, k_on=int(kon + k0), k_off_start=int(i + k0))
return out
def main():
t0 = time.time(); TABLES.mkdir(parents=True, exist_ok=True)
cells = [(r, s) for s in ("test", "val", "train") for r in ("manual", "engaged") if (V4D / f"kin_causal_{r}_{s}.npz").exists()]
res = {}; ev_by_cell = {}
for reg, sp in cells:
z2 = np.load(V2 / f"kin_causal_{reg}_{sp}.npz", allow_pickle=True); z3 = np.load(V3D / f"kin_causal_{reg}_{sp}.npz", allow_pickle=True); z4 = np.load(V4D / f"kin_causal_{reg}_{sp}.npz", allow_pickle=True)
uid, t = z4["uid"], z4["t"]; assert np.array_equal(uid, z2["uid"]) and np.array_equal(uid, z3["uid"]) and np.allclose(t, z2["t"]); N = len(t); ok = z4["row_ok"]; assert np.array_equal(ok, z2["row_ok"])
S2, S3, S4 = z2["S_c"][:, :, 0], z3["S_c"][:, :, 0], z4["S_c"][:, :, 0]; S02, S03, S04 = z2["S0_c"][:, 0], z3["S0_c"][:, 0], z4["S0_c"][:, 0]
u2, h2 = K3.unique_events(uid, t, z2["E_c"], z2["EM_c"]); u3, h3 = K3.unique_events(uid, t, z3["E_c"], z3["EM_c"]); u4, h4 = K3.unique_events(uid, t, z4["E_c"], z4["EM_c"])
ev_by_cell[(reg, sp)] = (u3, u4, np.unique(uid), z4)
D0 = z4["dwheel0_c"]
c = dict(N=int(N), n_row_ok=int(ok.sum()), row_ok_share=float(ok.mean()),
wheel_ellipse_defined_share_row_ok_v4=float(z4["wheel_ref_defined"][ok].mean()), wheel_ref_defined_share_row_ok_v3=float(z3["wheel_ref_defined"][ok].mean()),
S0_finite_share_row_ok_v2=float(np.isfinite(S02[ok]).mean()), S0_finite_share_row_ok_v3=float(np.isfinite(S03[ok]).mean()), S0_finite_share_row_ok_v4=float(np.isfinite(S04[ok]).mean()),
hands_on_share_v2=float(np.nanmean(S2)), hands_on_share_v3=float(np.nanmean(S3)), hands_on_share_v4=float(np.nanmean(S4)),
hands_on_at_anchor_v4=float(np.nanmean(S04)), EM_hand_share_v4=float(z4["EM_c"][:, :, 0].mean()), EM_hand_share_v3=float(z3["EM_c"][:, :, 0].mean()),
dwheel0_quantiles_row_ok_v4={f"q{p}": float(np.nanpercentile(D0[ok], p)) for p in (5, 25, 50, 75, 95)},
d0_decomposition_row_ok_v4=(lambda dd: dict(n=int(len(dd)), deep_inside_lt_m004=float((dd < -0.04).mean()), inside_m004_0=float(((dd >= -0.04) & (dd < 0)).mean()), rim_band_0_004=float(((dd >= 0) & (dd < 0.04)).mean()),
dead_band_004_008=float(((dd >= 0.04) & (dd <= 0.08)).mean()), off_gt_008=float((dd > 0.08).mean())))(D0[ok][np.isfinite(D0[ok])]),
head_identical_v4_v2=bool(np.array_equal(z4["E_c"][:, :, 2], z2["E_c"][:, :, 2]) and np.array_equal(z4["S_c"][:, :, 1], z2["S_c"][:, :, 1], equal_nan=True)), events={})
for name in EVENTS:
c["events"][name] = dict(n_v2=int(len(u2[name][0])), n_v3=int(len(u3[name][0])), n_v4=int(len(u4[name][0])), hours_v2=h2[name], hours_v3=h3[name], hours_v4=h4[name],
per_hour_v2=len(u2[name][0]) / h2[name] if h2[name] else None, per_hour_v3=len(u3[name][0]) / h3[name] if h3[name] else None, per_hour_v4=len(u4[name][0]) / h4[name] if h4[name] else None,
v3_with_v4_within_1s=K3.match_share(u3[name], u4[name]), v4_with_v3_within_1s=K3.match_share(u4[name], u3[name]), v4_with_v2_within_1s=K3.match_share(u4[name], u2[name]))
res[f"{reg}_{sp}"] = c; print(f"{reg}/{sp} done {time.time()-t0:.0f}s", flush=True)
# ---- 3. ellipse geometry per device (fit log)
rr4 = np.load(V4D / "route_refs.npz", allow_pickle=True); cols = {c_: i for i, c_ in enumerate([str(x) for x in rr4["fit_cols"]])}
by_dev = {}
for u in [str(x) for x in rr4["uids"]]:
by_dev.setdefault(u.split("__")[1], []).append(u)
dev_rows = []; all_passed = []
for dev, us in sorted(by_dev.items()):
fits = [rr4[f"fits__{u}"] for u in us]; fits = np.concatenate([f for f in fits if len(f)], 0) if any(len(f) for f in fits) else np.zeros((0, len(cols)))
n_fr = sum(len(rr4[f"d4__{u}"]) for u in us); n_def = sum(int(np.isfinite(rr4[f"ell__{u}"]).all(1).sum()) for u in us); n_man = sum(int(rr4[f"n_manual_moving__{u}"]) for u in us)
# defined share over manual moving frames needs the regime flag per frame -> approximate with the fit log instead: share of refits passed; exact share over ALL frames from ell__
passed = fits[fits[:, cols["passed"]] > 0.5] if len(fits) else fits
rec = dict(device=dev, n_routes=len(us), n_frames=int(n_fr), n_manual_moving=int(n_man), n_refits=int(len(fits)), n_passed=int(len(passed)), pass_share=float(len(passed) / len(fits)) if len(fits) else None,
defined_share_frames=float(n_def / n_fr) if n_fr else None)
if len(passed):
a, b, cx, cy, cov = (passed[:, cols[k]] for k in ("a", "b", "cx", "cy", "coverage_deg")); ratio = b / a
q = lambda v: [float(np.percentile(v, p)) for p in (25, 50, 75)]
rec.update(a_q=q(a), b_q=q(b), ratio_q=q(ratio), cx_q=q(cx), cy_q=q(cy), cov_q=q(cov), n_inlier_med=float(np.median(passed[:, cols["n_inlier"]])),
any_axis_out_of_range=bool(((a < AX_LO) | (a > AX_HI) | (b < AX_LO) | (b > AX_HI)).any()))
rec["flag"] = bool(rec["a_q"][1] < AX_LO or rec["a_q"][1] > AX_HI or rec["b_q"][1] < AX_LO or rec["b_q"][1] > AX_HI or rec["any_axis_out_of_range"])
all_passed.append(passed)
else:
rec.update(a_q=None, b_q=None, ratio_q=None, cx_q=None, cy_q=None, cov_q=None, n_inlier_med=None, any_axis_out_of_range=None, flag=None)
dev_rows.append(rec)
AP = np.concatenate(all_passed, 0) if all_passed else np.zeros((0, len(cols)))
geo = dict(n_devices=len(dev_rows), n_devices_any_passed=int(sum(1 for r in dev_rows if r["n_passed"])), n_devices_flagged=int(sum(1 for r in dev_rows if r["flag"])), n_devices_no_refit=int(sum(1 for r in dev_rows if r["n_refits"] == 0)),
n_refits=int(sum(r["n_refits"] for r in dev_rows)), n_passed=int(len(AP)),
passed_quantiles={k: {f"q{p}": float(np.percentile(AP[:, cols[k]], p)) for p in (5, 25, 50, 75, 95)} for k in ("a", "b", "cx", "cy", "coverage_deg", "n_inlier")} if len(AP) else None,
passed_ratio_quantiles={f"q{p}": float(np.percentile(AP[:, cols["b"]] / AP[:, cols["a"]], p)) for p in (5, 25, 50, 75, 95)} if len(AP) else None,
devices=dev_rows)
print(f"geometry: devices {geo['n_devices']}, with a passed fit {geo['n_devices_any_passed']}, flagged {geo['n_devices_flagged']} ({time.time()-t0:.0f}s)", flush=True)
# ---- 4. owner-observation test on the 68 v3 hand_return validation items
obs = None
if ITEMS_V3.exists() and (V3D / "route_refs.npz").exists() and ("engaged", "test") in ev_by_cell and ("manual", "test") in ev_by_cell:
items = [i for i in json.load(open(ITEMS_V3, encoding="utf-8"))["items"] if i.get("task") == "V3E" and i["meta"].get("block") == "hand_return"]
labels = json.load(open(LABELS)) if LABELS.exists() else {}
rr3 = np.load(V3D / "route_refs.npz", allow_pickle=True)
rows = []
for it in sorted(items, key=lambda i: i["id"]):
m = it["meta"]; reg = m["regime"]; u = m["uid"]; te = float(m["t_event"]); kf = int(round(te * 10))
u3, u4, uids_sorted, z4 = ev_by_cell[(reg, "test")]; uidx = {x: i for i, x in enumerate(uids_sorted)}; ui = uidx.get(u, -1)
dt4, hit4 = nearest_dt(u4["hand_return"][0], u4["hand_return"][1], ui, kf); dt3, hit3 = nearest_dt(u3["hand_return"][0], u3["hand_return"][1], ui, kf)
# v4 state over the page window: rows of this route with t in [te-2, te+1] -> S0 (causal state at the anchor)
rws = np.flatnonzero((z4["uid"] == u) & (z4["t"] >= te + WIN[0] - 1e-6) & (z4["t"] <= te + WIN[1] + 1e-6)); s0w = z4["S0_c"][rws, 0]; dw = z4["dwheel0_c"][rws]
v4_state = "undefined" if not np.isfinite(s0w).any() else ("always_on" if np.nanmin(s0w) == 1 else ("never_on" if np.nanmax(s0w) == 0 else "switches"))
d_min = float(np.nanmin(dw)) if np.isfinite(dw).any() else None; d_max = float(np.nanmax(dw)) if np.isfinite(dw).any() else None
inside_share = float((dw[np.isfinite(dw)] < 0).mean()) if np.isfinite(dw).any() else None
exc = v3_off_run_excursion(u, te, rr3)
lab = (labels.get(it["id"]) or {}).get("label", "")
cls = "unlabelled" if not lab else ("return" if lab.startswith("ret_dt=") else lab)
rows.append(dict(id=it["id"], regime=reg, uid=u, t_event=te, owner_label=lab, owner_class=cls, v3_return_within_1s=hit3, v4_return_within_1s=hit4, nearest_v4_return_dt_s=dt4,
v4_state_window=v4_state, v4_d_at_anchor=float(z4["dwheel0_c"][m["row"]]) if "row" in m else None, v4_d_window_min=d_min, v4_d_window_max=d_max, v4_inside_share_window=inside_share,
v3_off_excursion=exc["excursion"], v3_off_run_s=exc["off_run_s"], v3_off_found=exc["found"]))
ex = np.array([r["v3_off_excursion"] for r in rows if r["v3_off_excursion"] is not None], float)
def xtab(sel):
out = {}
for cls in ("return", "always_on", "never_on", "unsure", "unlabelled"):
rs = [r for r in sel if r["owner_class"] == cls]
if rs: out[cls] = dict(n=len(rs), v4_return_within_1s=int(sum(r["v4_return_within_1s"] for r in rs)), v4_always_on_window=int(sum(r["v4_state_window"] == "always_on" for r in rs)),
v3_excursion_lt_025=int(sum(1 for r in rs if r["v3_off_excursion"] is not None and r["v3_off_excursion"] < EXC_TH)),
v4_d_max_window_le_008=int(sum(1 for r in rs if r["v4_d_window_max"] is not None and r["v4_d_window_max"] <= 0.08)),
v4_inside_share_window_mean=float(np.mean([r["v4_inside_share_window"] for r in rs if r["v4_inside_share_window"] is not None])) if any(r["v4_inside_share_window"] is not None for r in rs) else None)
return out
obs = dict(n=len(rows), n_v3_return_within_1s=int(sum(r["v3_return_within_1s"] for r in rows)), n_v4_return_within_1s=int(sum(r["v4_return_within_1s"] for r in rows)),
n_v4_always_on_window=int(sum(r["v4_state_window"] == "always_on" for r in rows)), n_excursion_found=int(len(ex)),
excursion_median=float(np.median(ex)) if len(ex) else None, excursion_q25=float(np.percentile(ex, 25)) if len(ex) else None, excursion_q75=float(np.percentile(ex, 75)) if len(ex) else None,
share_excursion_lt_025=float((ex < EXC_TH).mean()) if len(ex) else None, off_run_median_s=float(np.median([r["v3_off_run_s"] for r in rows if r["v3_off_run_s"] is not None])) if len(ex) else None,
n_labelled=int(sum(r["owner_class"] != "unlabelled" for r in rows)),
by_regime={reg: dict(n=len([r for r in rows if r["regime"] == reg]), v4_return_within_1s=int(sum(r["v4_return_within_1s"] for r in rows if r["regime"] == reg)),
share_excursion_lt_025=float(np.mean([r["v3_off_excursion"] < EXC_TH for r in rows if r["regime"] == reg and r["v3_off_excursion"] is not None])) if any(r["regime"] == reg and r["v3_off_excursion"] is not None for r in rows) else None,
xtab=xtab([r for r in rows if r["regime"] == reg])) for reg in ("manual", "engaged")},
xtab_all=xtab(rows), excursion_vs_v4={"exc<0.25": dict(n=int(sum(1 for r in rows if r["v3_off_excursion"] is not None and r["v3_off_excursion"] < EXC_TH)), v4_return=int(sum(1 for r in rows if r["v3_off_excursion"] is not None and r["v3_off_excursion"] < EXC_TH and r["v4_return_within_1s"]))),
"exc>=0.25": dict(n=int(sum(1 for r in rows if r["v3_off_excursion"] is not None and r["v3_off_excursion"] >= EXC_TH)), v4_return=int(sum(1 for r in rows if r["v3_off_excursion"] is not None and r["v3_off_excursion"] >= EXC_TH and r["v4_return_within_1s"])))},
items=rows)
print(f"68-item test: v4 return within 1 s {obs['n_v4_return_within_1s']}/{obs['n']}; excursion median {fmt(obs['excursion_median'])}, share < 0.25 {fmt(obs['share_excursion_lt_025'])}; labelled {obs['n_labelled']} ({time.time()-t0:.0f}s)", flush=True)
# ---- 5. the 34 pass-3 engaged chatter events
rev = None
if ITEMS_B.exists() and ("engaged", "test") in ev_by_cell:
sel = sorted([i for i in json.load(open(ITEMS_B))["items"] if i["task"] == "B" and i["meta"].get("event") == "hand_return" and i["meta"].get("regime") == "engaged"], key=lambda i: i["id"])
u3, u4, uids_sorted, z4 = ev_by_cell[("engaged", "test")]; uidx = {x: i for i, x in enumerate(uids_sorted)}
rws = []
for it in sel:
m = it["meta"]; te = float(m["t_anchor"]) + 0.2; kf = int(round(te * 10)); ui = uidx.get(m["uid"], -1)
dt4, hit4 = nearest_dt(u4["hand_return"][0], u4["hand_return"][1], ui, kf); dt3, hit3 = nearest_dt(u3["hand_return"][0], u3["hand_return"][1], ui, kf)
rws.append(dict(id=it["id"], uid=m["uid"], t_event=te, v3_return_within_1s=hit3, v4_return_within_1s=hit4, nearest_v4_return_dt_s=dt4))
rev = dict(n=len(rws), n_v3_return_within_1s=int(sum(r["v3_return_within_1s"] for r in rws)), n_v4_return_within_1s=int(sum(r["v4_return_within_1s"] for r in rws)), items=rws)
print(f"34 pass-3 engaged chatter events: v4 hand_return within +-1 s {rev['n_v4_return_within_1s']}/{rev['n']} (v3: {rev['n_v3_return_within_1s']}/{rev['n']})", flush=True)
# ---- build provenance numbers
bs = json.load(open(V4D / "build_stats_latest.json")) if (V4D / "build_stats_latest.json").exists() else {}
prov = {k: bs.get(k) for k in ("refit_frames", "refit_s", "n_refits", "n_refits_passed", "n_refits_fail_points", "n_refits_fail_coverage", "n_refits_no_candidate", "fit_wall_s", "ellipse_undefined_frame_frac", "d_undefined_frame_frac",
"n_manual_moving_frames", "n_pose_frames", "routes_ellipse_never_defined", "n_10hz_confirmed_return", "n_10hz_confirmed_leave", "n_10hz_raw_transitions")}
prov["split_wall_s"] = {k: v.get("wall_s") for k, v in bs.get("splits", {}).items()}
tf = sorted(V4D.glob("logs/build_all*.time"))
if tf:
txt = tf[-1].read_text(); rss = [l for l in txt.splitlines() if "Maximum resident" in l]; wall = [l for l in txt.splitlines() if "Elapsed (wall clock)" in l]
prov["max_rss_kb"] = int(rss[0].split()[-1]) if rss else None; prov["wall_clock"] = wall[0].split("): ")[-1].strip() if wall else None
# ---- markdown
stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
L = [f"# kin causal targets v2 / v3 / v4 — build statistics and v4 diagnostics ({stamp})", "",
f"Source: `eval/kin_v4_build_stats.py` on `{V2.relative_to(ROOT)}`, `{V3D.relative_to(ROOT)}`, `{V4D.relative_to(ROOT)}` (target files; cells present in v4 only) and `{V4D.relative_to(ROOT)}/route_refs.npz` (fit log). "
"v4 = pre-registered hands_on / hand-event label v4 (docs/decisions.md 2026-09-16T22:18:02Z): per-device causal RANSAC + direct-LS ELLIPSE fit of the rim locus from both wrists over manual moving frames "
"(gate >= 200 inliers spanning >= 90 deg of arc, else undefined), hands_on = hysteresis on the min-wrist SIGNED distance to the filled ellipse (ON < 0.04, OFF > 0.08, reset after > 1 s gap), "
">= 1 s persistence before and after a hand event, causal S0; head_turn_onset and all input channels unchanged (asserted bit-identical to v2 in the build).", "",
f"Build provenance: refit every {prov.get('refit_frames')} new manual moving frames ({prov.get('refit_s')} s); {prov.get('n_refits'):,} refits, {prov.get('n_refits_passed'):,} passed the gate "
f"({prov.get('n_refits_fail_points'):,} failed < 200 inliers, {prov.get('n_refits_fail_coverage'):,} failed the 90-deg coverage, {prov.get('n_refits_no_candidate'):,} no candidate); ellipse fitting {prov.get('fit_wall_s')} s of the build; "
f"ellipse undefined on {100*prov.get('ellipse_undefined_frame_frac', float('nan')):.2f} % of the {prov.get('n_pose_frames'):,} pose frames ({prov.get('n_manual_moving_frames'):,} manual moving); routes with the ellipse never defined: {len(prov.get('routes_ellipse_never_defined') or [])}; "
f"10 Hz raw transitions {prov.get('n_10hz_raw_transitions'):,} -> confirmed return {prov.get('n_10hz_confirmed_return'):,} / leave {prov.get('n_10hz_confirmed_leave'):,}; per-split wall {prov.get('split_wall_s')}; "
f"max RSS {prov.get('max_rss_kb')} kB, wall clock {prov.get('wall_clock')} (single invocation, all splits).", "",
"Definitions: hands_on share = nanmean of `S_c[:,:,0]` over the 25 future steps of all rows; S0 finite = share of row_ok rows with a defined hands_on state at the anchor; defined = wheel reference (v3: point; v4: ellipse) defined at the anchor; "
"events = unique (route, 0.2 s grid frame) with `E_c = 1 & EM_c`; hours = unique (route, frame) with `EM_c` True x 0.2 s; match = share of unique events with an event of the same type on the same route within +-1.0 s in the other version.", "",
"## 1. Coverage and state shares", "",
"| regime | split | rows | row_ok | ellipse defined v4 | point ref defined v3 | S0 finite v2 | S0 finite v3 | S0 finite v4 | hands_on share v2 | hands_on share v3 | hands_on share v4 | EM hand share v4 | d0 median v4 [q5, q95] | head identical to v2 |", "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
for key, c in res.items():
reg, sp = key.split("_"); dq = c["dwheel0_quantiles_row_ok_v4"]
L.append(f"| {reg} | {sp} | {c['N']:,} | {c['n_row_ok']:,} ({100*c['row_ok_share']:.2f} %) | {100*c['wheel_ellipse_defined_share_row_ok_v4']:.2f} % | {100*c['wheel_ref_defined_share_row_ok_v3']:.2f} % | {100*c['S0_finite_share_row_ok_v2']:.2f} % | {100*c['S0_finite_share_row_ok_v3']:.2f} % | {100*c['S0_finite_share_row_ok_v4']:.2f} % | "
f"{c['hands_on_share_v2']:.3f} | {c['hands_on_share_v3']:.3f} | {c['hands_on_share_v4']:.3f} | {c['EM_hand_share_v4']:.4f} | {dq['q50']:+.3f} [{dq['q5']:+.3f}, {dq['q95']:+.3f}] | {c['head_identical_v4_v2']} |")
L += ["", "Decomposition of the v4 anchor signed distance d0 over row_ok rows with a defined ellipse (ON state = d0 < 0.04 at entry; a wrist INSIDE the ellipse, d0 < 0, is ON by the pre-registered 'filled region' convention):", "",
"| regime | split | n | deep inside d0 < -0.04 | inside -0.04 <= d0 < 0 | rim band 0 <= d0 < 0.04 | dead band 0.04..0.08 | off d0 > 0.08 |", "|---|---|---|---|---|---|---|---|"]
for key, c in res.items():
reg, sp = key.split("_"); dd = c["d0_decomposition_row_ok_v4"]
L.append(f"| {reg} | {sp} | {dd['n']:,} | {100*dd['deep_inside_lt_m004']:.1f} % | {100*dd['inside_m004_0']:.1f} % | {100*dd['rim_band_0_004']:.1f} % | {100*dd['dead_band_004_008']:.1f} % | {100*dd['off_gt_008']:.1f} % |")
L += ["", "## 2. Events per hour (unique events on the 0.2 s grid) and cross-version matching within +-1 s", "",
"| regime | split | event | n v2 | n v3 | n v4 | hours v4 | per hour v2 | per hour v3 | per hour v4 | v3 events with v4 within 1 s | v4 events with v3 within 1 s | v4 events with v2 within 1 s |", "|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
for key, c in res.items():
reg, sp = key.split("_")
for name in EVENTS:
e = c["events"][name]
L.append(f"| {reg} | {sp} | {name} | {e['n_v2']:,} | {e['n_v3']:,} | {e['n_v4']:,} | {e['hours_v4']:.1f} | {fmt(e['per_hour_v2'], 2)} | {fmt(e['per_hour_v3'], 2)} | {fmt(e['per_hour_v4'], 2)} | {fmt(e['v3_with_v4_within_1s'])} | {fmt(e['v4_with_v3_within_1s'])} | {fmt(e['v4_with_v2_within_1s'])} |")
L += ["", "## 3. Ellipse geometry sanity per device (fit log; passed fits only for the geometry columns)", "",
f"Devices {geo['n_devices']}; with at least one passed fit {geo['n_devices_any_passed']}; without any refit (< 200 manual points) {geo['n_devices_no_refit']}; FLAGGED (median semi-axis of the passed fits < {AX_LO} or > {AX_HI}, or any passed fit outside that range) **{geo['n_devices_flagged']}**. "
f"Refits {geo['n_refits']:,}, passed {geo['n_passed']:,}.", ""]
if geo["passed_quantiles"]:
pq = geo["passed_quantiles"]; rq = geo["passed_ratio_quantiles"]
L += ["| quantity (passed fits, all devices) | q5 | q25 | q50 | q75 | q95 |", "|---|---|---|---|---|---|"]
for k, lab in (("a", "semi-axis a"), ("b", "semi-axis b"), ("cx", "centre x"), ("cy", "centre y"), ("coverage_deg", "arc coverage (deg)"), ("n_inlier", "inliers")):
L.append(f"| {lab} | " + " | ".join(f"{pq[k][f'q{p}']:.3f}" for p in (5, 25, 50, 75, 95)) + " |")
L.append("| aspect ratio b / a | " + " | ".join(f"{rq[f'q{p}']:.3f}" for p in (5, 25, 50, 75, 95)) + " |")
L += ["", "Units: crop-normalised-equivalent (x = x_norm x 1.6, y = y_norm); a wheel spanning the full frame width would have a = 0.8.", "",
"| device | routes | pose frames | manual moving frames | refits | passed | pass share | ellipse defined (share of frames) | a median [q25, q75] | b median [q25, q75] | b/a median | centre (x, y) median | coverage deg median | inliers median | flag |", "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
for r in dev_rows:
if r["n_passed"]:
L.append(f"| {r['device']} | {r['n_routes']} | {r['n_frames']:,} | {r['n_manual_moving']:,} | {r['n_refits']} | {r['n_passed']} | {r['pass_share']:.2f} | {100*r['defined_share_frames']:.1f} % | {r['a_q'][1]:.3f} [{r['a_q'][0]:.3f}, {r['a_q'][2]:.3f}] | {r['b_q'][1]:.3f} [{r['b_q'][0]:.3f}, {r['b_q'][2]:.3f}] | {r['ratio_q'][1]:.2f} | ({r['cx_q'][1]:.3f}, {r['cy_q'][1]:.3f}) | {r['cov_q'][1]:.0f} | {r['n_inlier_med']:.0f} | {'FLAG' if r['flag'] else ''} |")
else:
L.append(f"| {r['device']} | {r['n_routes']} | {r['n_frames']:,} | {r['n_manual_moving']:,} | {r['n_refits']} | 0 | {fmt(r['pass_share'], 2)} | {100*(r['defined_share_frames'] or 0):.1f} % | — | — | — | — | — | — | {'no fit' if r['n_refits'] else 'no refit'} |")
if obs is not None:
L += ["", "## 4. Owner-observation test: the 68 v3 hand_return validation items (relabel_v3.html; owner labels so far in labels_latest.json)", "",
f"Mechanism hypothesis (pre-registration): a hand sliding along the rim leaves the v3 POINT reference by > 0.12 and comes back; v3 then stamps a 'return' although a hand was on the wheel throughout.", "",
f"(i) Excursion of the v3 OFF run preceding the event from the point reference (max of the v3 min-wrist distance; OFF run found for {obs['n_excursion_found']} / {obs['n']} items): median **{fmt(obs['excursion_median'])}** [q25 {fmt(obs['excursion_q25'])}, q75 {fmt(obs['excursion_q75'])}], "
f"share < {EXC_TH} (rim sliding) **{fmt(obs['share_excursion_lt_025'])}**; OFF-run duration median {fmt(obs['off_run_median_s'], 1)} s. "
f"(ii) v4 hand_return within +-1 s of t_event: **{obs['n_v4_return_within_1s']} / {obs['n']}** (v3 sanity: {obs['n_v3_return_within_1s']} / {obs['n']}); v4 state ON throughout the page window [-2 s, +1 s]: {obs['n_v4_always_on_window']} / {obs['n']}.", "",
"| regime | n | v4 return within 1 s | share v3 excursion < 0.25 |", "|---|---|---|---|"]
for reg, b in obs["by_regime"].items(): L.append(f"| {reg} | {b['n']} | {b['v4_return_within_1s']} | {fmt(b['share_excursion_lt_025'])} |")
L += ["", f"Cross-tab against the owner's labels ({obs['n_labelled']} / {obs['n']} labelled at generation time):", "",
"| regime | owner label class | n | v4 return within 1 s | v4 ON throughout window | v4 d never > 0.08 in window | mean share of window with a wrist inside the ellipse | v3 excursion < 0.25 |", "|---|---|---|---|---|---|---|---|"]
for reg, b in obs["by_regime"].items():
for cls, x in b["xtab"].items(): L.append(f"| {reg} | {cls} | {x['n']} | {x['v4_return_within_1s']} | {x['v4_always_on_window']} | {x['v4_d_max_window_le_008']} | {fmt(x['v4_inside_share_window_mean'], 2)} | {x['v3_excursion_lt_025']} |")
for cls, x in obs["xtab_all"].items(): L.append(f"| both | {cls} | {x['n']} | {x['v4_return_within_1s']} | {x['v4_always_on_window']} | {x['v4_d_max_window_le_008']} | {fmt(x['v4_inside_share_window_mean'], 2)} | {x['v3_excursion_lt_025']} |")
ev_ = obs["excursion_vs_v4"]
L += ["", f"Excursion vs v4 event: excursion < 0.25 -> v4 return in {ev_['exc<0.25']['v4_return']} / {ev_['exc<0.25']['n']}; excursion >= 0.25 -> v4 return in {ev_['exc>=0.25']['v4_return']} / {ev_['exc>=0.25']['n']}.", "",
"| item | regime | uid | t_event | owner label | v3 OFF excursion | v3 OFF run (s) | v4 return within 1 s | nearest v4 return (s) | v4 state in window | v4 d0 | v4 d in window [min, max] | share inside |", "|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
for r in obs["items"]:
L.append(f"| {r['id']} | {r['regime']} | {r['uid']} | {r['t_event']:.1f} | {r['owner_label'] or '—'} | {fmt(r['v3_off_excursion'])} | {fmt(r['v3_off_run_s'], 1)} | {r['v4_return_within_1s']} | {'—' if r['nearest_v4_return_dt_s'] is None else f'{r['nearest_v4_return_dt_s']:+.1f}'} | {r['v4_state_window']} | {fmt(r['v4_d_at_anchor'])} | [{fmt(r['v4_d_window_min'])}, {fmt(r['v4_d_window_max'])}] | {fmt(r['v4_inside_share_window'], 2)} |")
if rev is not None:
L += ["", "## 5. The 34 pass-3 engaged hand_return chatter events (pass 4: 0 / 34 real returns)", "",
f"v4 hand_return within +-1 s of t_event = t_anchor + 0.2 (engaged/test, unique-event times): **{rev['n_v4_return_within_1s']} / {rev['n']}** (v3: {rev['n_v3_return_within_1s']} / {rev['n']}).", "",
"| item | uid | t_event | v3 return within 1 s | v4 return within 1 s | nearest v4 return (s) |", "|---|---|---|---|---|---|"]
for r in rev["items"]:
L.append(f"| {r['id']} | {r['uid']} | {r['t_event']:.1f} | {r['v3_return_within_1s']} | {r['v4_return_within_1s']} | {'—' if r['nearest_v4_return_dt_s'] is None else f'{r['nearest_v4_return_dt_s']:+.1f}'} |")
L += ["", f"Build provenance: `{V4D.relative_to(ROOT)}/build_stats_latest.json`, `run_build_kin_causal_routes_v4_*.json`, `logs/build_all.log` / `.time`."]
(TABLES / "kin_v4_build_stats.md").write_text("\n".join(L) + "\n")
json.dump(dict(generated_utc=stamp, cells=res, geometry=geo, owner_observation_68=obs, reviewed_34=rev, provenance=prov), open(TABLES / "kin_v4_build_stats.json", "w"), indent=1)
write_run_json(TABLES, "kin_v4_build_stats", [Path(__file__), Path(K3.__file__), Path(V4.__file__)], [V4D / f"kin_causal_{r}_{s}.npz" for r, s in cells] + [V3D / f"kin_causal_{r}_{s}.npz" for r, s in cells] + [V2 / f"kin_causal_{r}_{s}.npz" for r, s in cells]
+ [V4D / "route_refs.npz", V3D / "route_refs.npz"] + [p for p in (ITEMS_V3, LABELS, ITEMS_B) if p.exists()], None, t0,
extra=dict(cells=[f"{r}_{s}" for r, s in cells], geometry={k: v for k, v in geo.items() if k != "devices"}, owner_observation_68=None if obs is None else {k: v for k, v in obs.items() if k != "items"},
reviewed_34=None if rev is None else {k: v for k, v in rev.items() if k != "items"}, provenance=prov), input_listing=False)
print(f"wrote {TABLES / 'kin_v4_build_stats.md'} ({time.time()-t0:.0f}s)", flush=True)
if __name__ == "__main__":
main()

Xet Storage Details

Size:
33.8 kB
·
Xet hash:
1bf45bd23e946008328ee7ca6739e5506a790006bc32a21d2c3891ca529b0f2a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.