Buckets:
| """cwm3/data/build_gaze_targets.py — the causal `gaze_off` state as a DRIVER TARGET CHAIN on the s12 anchor grid (roadmap §B3 / coordinator 2026-09-12: | |
| "gaze_off as a driver target chain", the engaged lever). Mirrors build_kin_causal.main_routes (hands_on / head_turned chains) for a third state. | |
| Source state. `gaze_off` = build_face_causal's hysteresis state on |lm_yaw_dev| (OUR OWN RTMW HALPE-136 face landmarks of the route-level causal pose; | |
| on / off thresholds = the TRAIN-split moving-frame P90 / P75 stored in the face pack's `thresholds`, 0.2121 / 0.1033). The face pack only carries the | |
| state at t (F0_c[:, 7]) and its 8 s history (Fh8_c[:, :, 7]); the FUTURE state is needed for a chain target, so the state series is re-derived on the | |
| 10 Hz route grid exactly as build_face_causal pass 1-2 does — same landmarks (build_face_causal.raw_face / IDX), same validity, the SAME causal | |
| per-route references (results/targets_face_causal_routes_v2/route_face_refs.npz, expanding median over MOVING valid frames <= t initialised from the | |
| device's earlier routes) and the same hysteresis (build_kin_causal.hysteresis, > 1 s gap resets) — and asserted equal to F0_c[:, 7] / Fh8_c[:, -1, 7] | |
| on every anchor where both are finite (exact reproduction, not an approximation). Nothing here reads an openpilot DMS quantity. | |
| Outputs (row-aligned with s12_wm/results/targets_{regime}_{split}.npz AND results/targets_kin_causal_routes_v2/kin_causal_{regime}_{split}.npz — | |
| uid / t equality asserted against both) -> <out-dir>/gaze_{regime}_{split}.npz: | |
| uid, dongle, t, row_ok[N] (anchor inside the decoded route series with a frame present, = the face pack's row_ok), route_decoded[N] | |
| S_g f32[N,25] gaze_off state at t+0.2..t+5.0 (NaN = invalid frame / outside the series / s12-censored M) | |
| S0_g f32[N] state at t (NaN invalid) | |
| E_g u8[N,25,2] first-passage onsets: [:, :, 0] gaze_off_on (0 -> 1), [:, :, 1] gaze_off_off (1 -> 0); EM_g bool[N,25,2] their masks | |
| (z_prev and z both finite; build_kin_causal.onsets), E0_g f32[N,2] = [S0_g, 1 - S0_g] | |
| absdev0 f32[N] |lm_yaw_dev| at t (face pack F0_c[:, 1]; the continuous quantity behind the hysteresis -> the current-state RULE comparator) | |
| dwell0 f32[N] gaze_off_dwell_s at t (face pack F0_c[:, 8]) | |
| state_names ["gaze_off"], event_names ["gaze_off_on", "gaze_off_off"], thresholds (json), source, pose_dir | |
| + build_stats_<utc>.json (+ _latest), run_build_gaze_targets_<utc>.json. | |
| python data/build_gaze_targets.py [--out-dir results/targets_gaze_causal_routes_v2] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from collections import defaultdict | |
| 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 / "data")) | |
| from runmeta import write_run_json # noqa: E402 | |
| import build_kin_causal as BK # noqa: E402 (route_key, hysteresis, onsets, S12R) | |
| import build_face_causal as BF # noqa: E402 (raw_face, IDX, POSE_R; the face channel definitions) | |
| S12R = BK.S12R | |
| KINC_V2 = ROOT / "results" / "targets_kin_causal_routes_v2" | |
| FACE = ROOT / "results" / "targets_face_causal_routes_v2" | |
| OUT_DEFAULT = ROOT / "results" / "targets_gaze_causal_routes_v2" | |
| H = 25 | |
| STATES, EVENTS = ["gaze_off"], ["gaze_off_on", "gaze_off_off"] | |
| IDX_YAW_DEV, IDX_GAZE, IDX_DWELL = 1, 7, 8 # build_face_causal.CHANNELS positions | |
| def gaze_series(pose_path, ref, gaze_on, gaze_off): | |
| """one route -> (k0, state[M] f32 NaN-invalid on the 10 Hz grid, frame[M] bool): build_face_causal pass 1 (yaw_dev with the stored causal reference) + pass 2.""" | |
| z = np.load(pose_path) | |
| t = z["t"].astype(np.float64) | |
| W, Hh = [int(v) for v in z["native_wh"]] | |
| kp = z["kpts"][:, BF.IDX] | |
| frame = np.isfinite(z["box"][:, 0]) & np.isfinite(kp[:, 0, 2].astype(np.float32)) | |
| raw = BF.raw_face(kp, W, Hh) | |
| raw["V"] &= frame | |
| raw["q"][~raw["V"]] = np.nan | |
| assert ref.shape == (len(t), len(BF.REFQ)), (pose_path, ref.shape, len(t)) | |
| V = raw["V"] & np.isfinite(ref[:, 0]) & np.isfinite(ref[:, 1]) & np.isfinite(ref[:, 2]) | |
| yaw_dev = (raw["q"][:, 0] - ref[:, 0]).astype(np.float32) | |
| yaw_dev[~V] = np.nan | |
| k = np.round(t * 10).astype(np.int64) | |
| k0, k1 = int(k.min()), int(k.max()) | |
| M = k1 - k0 + 1 | |
| GG = np.full(M, np.nan, np.float32) | |
| FF = np.zeros(M, bool) | |
| GG[k - k0] = yaw_dev | |
| FF[k - k0] = frame | |
| st = BK.hysteresis(np.abs(GG), gaze_on, gaze_off) # NaN where the frame is invalid; > 1 s gap resets (GAP_RESET 10 steps) | |
| return k0, st.astype(np.float32), FF | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--face-dir", default=str(FACE)) | |
| ap.add_argument("--out-dir", default=str(OUT_DEFAULT)) | |
| ap.add_argument("--regimes", default="manual,engaged") | |
| ap.add_argument("--splits", default="train,val,test") | |
| ap.add_argument("--limit-routes", type=int, default=0) | |
| a = ap.parse_args() | |
| t_start = time.time() | |
| FD, OUT = Path(a.face_dir), Path(a.out_dir) | |
| OUT.mkdir(parents=True, exist_ok=True) | |
| regs, sps = a.regimes.split(","), a.splits.split(",") | |
| # thresholds + pose dir from the face pack (any split file carries them) | |
| z0 = np.load(FD / f"facehist_causal_{regs[0]}_{sps[0]}.npz", allow_pickle=True) | |
| thr = json.loads(str(z0["thresholds"])) | |
| gaze_on, gaze_off = float(thr["gaze_off_on"]), float(thr["gaze_off_off"]) | |
| P2 = ROOT / str(z0["pose_dir"]) if not Path(str(z0["pose_dir"])).is_absolute() else Path(str(z0["pose_dir"])) | |
| assert [str(c) for c in z0["channels"]][IDX_GAZE] == "gaze_off" and [str(c) for c in z0["channels"]][IDX_YAW_DEV] == "lm_yaw_dev" | |
| del z0 | |
| def pose_path(u): | |
| p = P2 / f"{u}.npz" | |
| return p if p.exists() else BF.POSE_R / f"{u}.npz" | |
| refs = np.load(FD / "route_face_refs.npz", allow_pickle=True) | |
| assert [str(q) for q in refs["ref_quantities"]] == BF.REFQ | |
| uids = sorted(set().union(*[set(np.unique(np.load(S12R / f"targets_{r}_{s}.npz", allow_pickle=True)["uid"]).tolist()) for r in ("manual", "engaged") for s in ("train", "val", "test")])) | |
| have = [u for u in uids if pose_path(u).exists() and f"ref__{u}" in refs.files] | |
| if a.limit_routes: | |
| have = have[:a.limit_routes] | |
| print(f"s12 routes {len(uids)}, with causal route pose + stored face reference {len(have)}; gaze_off thresholds on {gaze_on:.4f} / off {gaze_off:.4f} (TRAIN P{thr['gaze_pct_on']:.0f} / P{thr['gaze_pct_off']:.0f})", flush=True) | |
| # ---- route series (device order only for progress; the stored references already encode the causal pools) | |
| by_dev = defaultdict(list) | |
| for u in have: | |
| by_dev[u.split("__")[1]].append(u) | |
| series, t_last, n_frames, n_valid = {}, time.time(), 0, 0 | |
| for di, (dev, dus) in enumerate(sorted(by_dev.items())): | |
| dus.sort(key=BK.route_key) | |
| for u in dus: | |
| k0, st, FF = gaze_series(pose_path(u), refs[f"ref__{u}"].astype(np.float32), gaze_on, gaze_off) | |
| series[u] = dict(k0=k0, st=st, F=FF) | |
| n_frames += int(FF.sum()) | |
| n_valid += int(np.isfinite(st).sum()) | |
| if time.time() - t_last > 60: | |
| t_last = time.time() | |
| print(f" devices {di + 1}/{len(by_dev)} routes {len(series)}/{len(have)} {time.time() - t_start:.0f}s", flush=True) | |
| print(f"series for {len(series)} routes: frames {n_frames:,}, state defined on {n_valid / max(n_frames, 1):.3f} of them ({time.time() - t_start:.0f}s)", flush=True) | |
| offs_f = np.arange(1, H + 1) * 2 # t+0.2..t+5.0 on the 10 Hz grid | |
| stats = dict(source="routes", pose_dir=str(P2), pose_fallback_dir=str(BF.POSE_R), face_dir=str(FD), n_routes_s12=len(uids), n_routes_built=len(series), | |
| thresholds=thr, states=STATES, events=EVENTS, n_frames=n_frames, state_defined_frac=n_valid / max(n_frames, 1), splits={}) | |
| n_mismatch_total = 0 | |
| for reg in regs: | |
| for sp in sps: | |
| d = np.load(S12R / f"targets_{reg}_{sp}.npz", allow_pickle=True) | |
| uid, don, t, Mc = d["uid"], d["dongle"], d["t"], d["M"].astype(bool) | |
| N = len(t) | |
| kin = np.load(KINC_V2 / f"kin_causal_{reg}_{sp}.npz", allow_pickle=True) | |
| assert np.array_equal(kin["uid"], uid) and np.allclose(kin["t"], t), f"kin_causal_{reg}_{sp}: uid/t witness" | |
| fz = np.load(FD / f"facehist_causal_{reg}_{sp}.npz", allow_pickle=True) | |
| assert np.array_equal(fz["uid"], uid) and np.allclose(fz["t"], t), f"facehist_causal_{reg}_{sp}: uid/t witness" | |
| F0, F0v, Fh8, Fh8v, row_ok_face = fz["F0_c"].astype(np.float32), fz["F0valid"].astype(bool), fz["Fh8_c"], fz["Fh8valid"].astype(bool), fz["row_ok"].astype(bool) | |
| S = np.full((N, H), np.nan, np.float32) | |
| S0 = np.full(N, np.nan, np.float32) | |
| route_dec = np.zeros(N, bool) | |
| row_ok = np.zeros(N, bool) | |
| order = np.argsort(uid, kind="stable") | |
| bounds = np.flatnonzero(np.r_[1, uid[order][1:] != uid[order][:-1], 1]) | |
| for a_, b_ in zip(bounds[:-1], bounds[1:]): | |
| rows = order[a_:b_] | |
| u = uid[rows[0]] | |
| if u not in series: | |
| continue | |
| rs = series[u] | |
| route_dec[rows] = True | |
| ka = np.round(t[rows] * 10).astype(np.int64) - rs["k0"] | |
| M = len(rs["st"]) | |
| idx = ka[:, None] + offs_f[None, :] | |
| ok = (idx >= 0) & (idx < M) | |
| S[rows] = np.where(ok, rs["st"][np.clip(idx, 0, M - 1)], np.nan) | |
| ok0 = (ka >= 0) & (ka < M) | |
| kc = np.clip(ka, 0, M - 1) | |
| S0[rows] = np.where(ok0, rs["st"][kc], np.nan) | |
| row_ok[rows] = ok0 & rs["F"][kc] | |
| # ---- exact-reproduction checks against the face pack (state at t and the last history step) | |
| both = np.isfinite(S0) & F0v & np.isfinite(F0[:, IDX_GAZE]) | |
| n_mis = int((S0[both] != F0[both, IDX_GAZE]).sum()) | |
| last = Fh8[:, -1, IDX_GAZE].astype(np.float32) | |
| both2 = np.isfinite(S0) & Fh8v[:, -1] & np.isfinite(last) | |
| n_mis2 = int((S0[both2] != last[both2]).sum()) | |
| n_mismatch_total += n_mis + n_mis2 | |
| assert n_mis == 0 and n_mis2 == 0, f"{reg}/{sp}: gaze_off state at t differs from the face pack on {n_mis} / {n_mis2} anchors (of {int(both.sum())})" | |
| assert np.array_equal(row_ok, row_ok_face), f"{reg}/{sp}: row_ok differs from the face pack" | |
| S[~Mc] = np.nan # s12 censoring (as build_kin_causal) | |
| E = np.zeros((N, H, 2), np.uint8) | |
| EM = np.zeros((N, H, 2), bool) | |
| E[:, :, 0], EM[:, :, 0] = BK.onsets(S0, S) | |
| E[:, :, 1], EM[:, :, 1] = BK.onsets(1 - S0, 1 - S) | |
| E0 = np.stack([S0, 1 - S0], 1).astype(np.float32) | |
| assert not (EM & ~Mc[:, :, None]).any() | |
| absdev0 = np.where(F0v, np.abs(F0[:, IDX_YAW_DEV]), np.nan).astype(np.float32) | |
| dwell0 = np.where(F0v, F0[:, IDX_DWELL], np.nan).astype(np.float32) | |
| np.savez_compressed(OUT / f"gaze_{reg}_{sp}.npz", uid=uid, dongle=don, t=t, row_ok=row_ok, route_decoded=route_dec, S_g=S, S0_g=S0, E_g=E, EM_g=EM, E0_g=E0, | |
| absdev0=absdev0, dwell0=dwell0, state_names=np.array(STATES), event_names=np.array(EVENTS), thresholds=np.array(json.dumps(thr)), | |
| source=np.array("routes"), pose_dir=np.array(str(P2)), face_dir=np.array(str(FD))) | |
| st = dict(N=int(N), routes=int(len(np.unique(uid))), routes_built=int(len({x for x in np.unique(uid) if x in series})), coverage_route_decoded=float(route_dec.mean()), | |
| n_row_ok=int(row_ok.sum()), coverage_row_ok=float(row_ok.mean()), S0_defined_frac_row_ok=float(np.isfinite(S0[row_ok]).mean()) if row_ok.any() else None, | |
| S_all25_defined_frac_row_ok=float(np.isfinite(S[row_ok]).all(1).mean()) if row_ok.any() else None, | |
| gaze_off_prev_anchor=float(np.nanmean(S0)) if np.isfinite(S0).any() else None, gaze_off_prev_future=float(np.nanmean(S)) if np.isfinite(S).any() else None, | |
| n_checked_vs_face_F0=int(both.sum()), n_checked_vs_face_Fh8_last=int(both2.sum()), mismatches=n_mis + n_mis2, events={}) | |
| for j, name in enumerate(EVENTS): | |
| z0_, z_ = (S0, S) if j == 0 else (1 - S0, 1 - S) | |
| zp = np.concatenate([z0_[:, None], z_[:, :-1]], 1) | |
| m = EM[:, :, j] | |
| m_tr = m & (zp < 0.5) | |
| inact0 = E0[:, j] < 0.5 | |
| rec = dict(n_inactive_at_anchor=int(inact0.sum()), hazard_per_step=float(E[:, :, j][m_tr].mean()) if m_tr.any() else None) | |
| for tau, kk in (("1s", 5), ("3s", 15), ("5s", 25)): | |
| sel = inact0 & m[:, :kk].all(1) | |
| y = E[sel, :kk, j].any(1) | |
| rec[f"n@{tau}"], rec[f"pos@{tau}"], rec[f"base_rate@{tau}"] = int(sel.sum()), int(y.sum()), (float(y.mean()) if sel.any() else None) | |
| st["events"][name] = rec | |
| stats["splits"][f"{reg}_{sp}"] = st | |
| print(f"{reg}/{sp}: N={N} row_ok={row_ok.mean() * 100:.2f}% S0 defined|row_ok={st['S0_defined_frac_row_ok']:.4f} all25={st['S_all25_defined_frac_row_ok']:.4f} " | |
| f"gaze_off@t={st['gaze_off_prev_anchor']:.4f} checked {int(both.sum())}+{int(both2.sum())} anchors vs face pack, mismatches 0; " | |
| + " ".join(f"{e}@3s={st['events'][e]['pos@3s']}/{st['events'][e]['n@3s']} ({st['events'][e]['base_rate@3s']:.4f})" for e in EVENTS), flush=True) | |
| stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime()) | |
| stats["mismatches_total"] = n_mismatch_total | |
| (OUT / f"build_stats_{stamp}.json").write_text(json.dumps(stats, indent=1)) | |
| (OUT / "build_stats_latest.json").write_text(json.dumps(stats, indent=1)) | |
| write_run_json(OUT, "build_gaze_targets", [Path(__file__), Path(BK.__file__), Path(BF.__file__)], | |
| [S12R / f"targets_{r}_{s}.npz" for r in regs for s in sps] + [FD / f"facehist_causal_{r}_{s}.npz" for r in regs for s in sps] + [FD / "route_face_refs.npz"], | |
| None, t_start, extra=dict(summary={k: v for k, v in stats.items() if k != "splits"}, | |
| coverage={k: dict(N=v["N"], row_ok=v["coverage_row_ok"], S0_defined=v["S0_defined_frac_row_ok"], **{e: v["events"][e]["base_rate@3s"] for e in EVENTS}) for k, v in stats["splits"].items()}, | |
| stats_file=f"build_stats_{stamp}.json", dms_fields_used=[]), input_listing=True) | |
| print("GAZE_TARGETS_DONE", flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 14.9 kB
- Xet hash:
- 458d4bac3211f52d0af20afa0f61174cce5b5480827c3a1d0dbfb754293a32cb
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.