Buckets:
| """cwm3/data/qc_kin_causal.py — agreement between the CAUSAL kinematics targets (results/targets_kin_causal, RTMW causal ROI, | |
| causal per-device references) and the OLD non-causal ones (cwm2/results/targets_kin, YOLO11x pose10hz, route-static crop, | |
| whole-route medians) on rows/steps where both exist. | |
| Comparisons (per regime/split with coverage; pooled over splits too): | |
| * per-channel Pearson r on (row, step) cells of K (future) where both are finite; likewise Kh history at shared offsets | |
| (old Kh is 10 Hz t−3.9..t → the even offsets t−3.8..t coincide with the 20 causal steps). Absolute-x channels | |
| (head_cx, Lwri_x, Rwri_x) are compared after shifting the causal x by the route's OLD crop_x0/400 (the old x is relative | |
| to the route-static crop; the causal x has offset 0) — the shift is a QC-only constant per route, never stored. | |
| * state agreement hands_on / head_turned on cells where both S are finite (plus prevalence of each). | |
| * event-onset agreement within ±1 step per event: for each old onset (E_old=1, EM_old) a match is any new onset in the same | |
| row within ±1 step (and vice versa) → recall_old→new, recall_new→old, F1; restricted to rows where both chains are valid | |
| over the compared steps. | |
| Writes results/targets_kin_causal/qc.json (+ run json).""" | |
| 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")) | |
| from runmeta import write_run_json | |
| EXPLORE = Path("/home/USER/explore") | |
| OLD = EXPLORE / "cwm2" / "results" / "targets_kin"; NEW = ROOT / "results" / "targets_kin_causal" | |
| POSE10 = EXPLORE / "s8_pose" / "cache" / "pose10hz"; POSE10E = EXPLORE / "s8_pose" / "cache" / "pose10hz_eng" | |
| CHANNELS = ["head_cx", "head_cy", "Lwri_x", "Lwri_y", "Rwri_x", "Rwri_y", "head_turn", "head_turn_dev", "Lhand_rest_dist", "Rhand_rest_dist", "min_hand_rest_dist"] | |
| XCH = [0, 2, 4]; EVENTS = ["hand_return", "hand_leave", "head_turn_onset"]; STATES = ["hands_on", "head_turned"] | |
| def crop_x0(uid): | |
| for d in (POSE10, POSE10E): | |
| p = d / f"{uid}.npz" | |
| if p.exists(): return float(np.load(p)["crop_x0"]) | |
| return np.nan | |
| def pearson(a, b): | |
| m = np.isfinite(a) & np.isfinite(b); n = int(m.sum()) | |
| if n < 3: return None, n | |
| a, b = a[m] - a[m].mean(), b[m] - b[m].mean(); den = np.sqrt((a * a).sum() * (b * b).sum()) | |
| return (float((a * b).sum() / den) if den > 0 else None), n | |
| def onset_match(Eo, EMo, En, EMn): | |
| """Eo/En [R,25] onsets; EMo/EMn masks. Rows compared where both masks are all-True over the 25 steps.""" | |
| ok = EMo.all(1) & EMn.all(1); Eo, En = Eo[ok].astype(bool), En[ok].astype(bool) | |
| def dil(E): | |
| P = np.zeros_like(E); P |= E; P[:, 1:] |= E[:, :-1]; P[:, :-1] |= E[:, 1:]; return P | |
| n_o, n_n = int(Eo.sum()), int(En.sum()) | |
| r_on = float((Eo & dil(En)).sum() / n_o) if n_o else None; r_no = float((En & dil(Eo)).sum() / n_n) if n_n else None | |
| f1 = (2 * r_on * r_no / (r_on + r_no)) if (r_on and r_no) else None | |
| return dict(rows_compared=int(ok.sum()), n_onsets_old=n_o, n_onsets_new=n_n, recall_old_to_new=r_on, recall_new_to_old=r_no, f1_pm1=f1, | |
| exact_step_agreement=float((Eo == En).mean()) if ok.any() else None) | |
| def main(): | |
| global NEW | |
| import argparse | |
| ap = argparse.ArgumentParser(); ap.add_argument("--new-dir", default=str(NEW)); a = ap.parse_args() | |
| NEW = Path(a.new_dir) | |
| t_start = time.time(); res = dict(new_dir=str(NEW), utc=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), splits={}, pooled={}) | |
| poolK = {c: ([], []) for c in CHANNELS}; poolH = {c: ([], []) for c in CHANNELS}; poolS = {s: ([], []) for s in STATES}; poolE = {e: [[], [], [], []] for e in EVENTS} | |
| x0cache = {} | |
| for reg in ("manual", "engaged"): | |
| for sp in ("train", "val", "test"): | |
| n = np.load(NEW / f"kin_causal_{reg}_{sp}.npz", allow_pickle=True); nh = np.load(NEW / f"kinhist_causal_{reg}_{sp}.npz", allow_pickle=True) | |
| o = np.load(OLD / f"kin_{reg}_{sp}.npz", allow_pickle=True); oh = np.load(OLD / f"kinhist_{reg}_{sp}.npz") | |
| assert np.array_equal(n["uid"], o["uid"]) and np.allclose(n["t"], o["t"]) | |
| R = np.flatnonzero(n["row_ok"]) | |
| rec = dict(N=int(len(n["t"])), n_row_ok=int(len(R))) | |
| if not len(R): res["splits"][f"{reg}_{sp}"] = rec | dict(note="no clip windows on this split's routes (coverage 0)"); continue | |
| Kn = n["K_c"][R].astype(np.float32); Ko = o["K"][R].astype(np.float32); Vn, Vo = n["Kvalid"][R], o["Kvalid"][R] | |
| Hn = nh["Kh_c"][R].astype(np.float32); Ho = oh["Kh"][R].astype(np.float32) | |
| Ho = Ho if Hn.shape[1] == 40 else Ho[:, 1::2, :] # clip build: 20 steps @5 Hz = old even offsets t-3.8..t; route build: 40 steps @10 Hz = old grid | |
| uids = n["uid"][R]; shift = np.array([x0cache.setdefault(u, crop_x0(u)) for u in uids], np.float32) / 400.0 | |
| Kn[:, :, XCH] -= shift[:, None, None]; Hn[:, :, XCH] -= shift[:, None, None] | |
| both = Vn & Vo; rec["cells_both_valid_K"] = int(both.sum()); rec["cells_valid_new_only"] = int((Vn & ~Vo).sum()); rec["cells_valid_old_only"] = int((~Vn & Vo).sum()) | |
| rec["Kvalid_frac_new"] = float(Vn.mean()); rec["Kvalid_frac_old"] = float(Vo.mean()) | |
| rec["pearson_K"] = {}; rec["pearson_Kh"] = {}; rec["mean_abs_diff_K"] = {} | |
| for i, c in enumerate(CHANNELS): | |
| a, b = Ko[:, :, i][both], Kn[:, :, i][both]; r, cnt = pearson(a, b); rec["pearson_K"][c] = dict(r=r, n=cnt) | |
| m = np.isfinite(a) & np.isfinite(b); rec["mean_abs_diff_K"][c] = float(np.abs(a[m] - b[m]).mean()) if m.any() else None | |
| poolK[c][0].append(a[m]); poolK[c][1].append(b[m]) | |
| ah, bh = Ho[:, :, i].ravel(), Hn[:, :, i].ravel(); mh = np.isfinite(ah) & np.isfinite(bh); r2, cnt2 = pearson(ah, bh); rec["pearson_Kh"][c] = dict(r=r2, n=cnt2) | |
| poolH[c][0].append(ah[mh]); poolH[c][1].append(bh[mh]) | |
| Sn, So = n["S_c"][R], o["S"][R]; rec["states"] = {} | |
| for j, s in enumerate(STATES): | |
| m = np.isfinite(Sn[:, :, j]) & np.isfinite(So[:, :, j]); a, b = So[:, :, j][m], Sn[:, :, j][m] | |
| rec["states"][s] = dict(cells=int(m.sum()), agreement=float((a == b).mean()) if m.any() else None, prevalence_old=float(a.mean()) if m.any() else None, prevalence_new=float(b.mean()) if m.any() else None, | |
| kappa=None) | |
| if m.any(): | |
| po, pn = a.mean(), b.mean(); pe = po * pn + (1 - po) * (1 - pn); acc = (a == b).mean(); rec["states"][s]["kappa"] = float((acc - pe) / (1 - pe)) if pe < 1 else None | |
| poolS[s][0].append(a); poolS[s][1].append(b) | |
| rec["events"] = {} | |
| for j, e in enumerate(EVENTS): | |
| rec["events"][e] = onset_match(o["E"][R][:, :, j], o["EM"][R][:, :, j], n["E_c"][R][:, :, j], n["EM_c"][R][:, :, j]) | |
| for q, arr in enumerate((o["E"][R][:, :, j], o["EM"][R][:, :, j], n["E_c"][R][:, :, j], n["EM_c"][R][:, :, j])): poolE[e][q].append(arr) | |
| res["splits"][f"{reg}_{sp}"] = rec | |
| print(f"{reg}/{sp}: rows {len(R)} both-valid cells {both.sum()} r(min_hand_rest_dist)={rec['pearson_K']['min_hand_rest_dist']['r']} r(head_turn)={rec['pearson_K']['head_turn']['r']} hands_on agr={rec['states']['hands_on']['agreement']} head_turned agr={rec['states']['head_turned']['agreement']}", flush=True) | |
| res["pooled"]["pearson_K"] = {c: dict(zip(("r", "n"), pearson(np.concatenate(poolK[c][0]), np.concatenate(poolK[c][1])))) for c in CHANNELS if poolK[c][0]} | |
| res["pooled"]["pearson_Kh"] = {c: dict(zip(("r", "n"), pearson(np.concatenate(poolH[c][0]), np.concatenate(poolH[c][1])))) for c in CHANNELS if poolH[c][0]} | |
| res["pooled"]["states"] = {} | |
| for s in STATES: | |
| if poolS[s][0]: | |
| a, b = np.concatenate(poolS[s][0]), np.concatenate(poolS[s][1]); po, pn = a.mean(), b.mean(); pe = po * pn + (1 - po) * (1 - pn); acc = (a == b).mean() | |
| res["pooled"]["states"][s] = dict(cells=int(len(a)), agreement=float(acc), kappa=float((acc - pe) / (1 - pe)), prevalence_old=float(po), prevalence_new=float(pn)) | |
| res["pooled"]["events"] = {e: onset_match(*[np.concatenate(poolE[e][q]) for q in range(4)]) for e in EVENTS if poolE[e][0]} | |
| res["notes"] = ["old = non-causal (route-static crop chosen from the whole route; whole-route medians; YOLO11x-pose); new = causal (per-clip prefix ROI; expanding per-device medians; RTMW-x-l)", | |
| "absolute x channels compared after subtracting the route's old crop_x0/400 from the causal x (QC-only)", | |
| "K4 implication: the causal set is usable as INPUT (Kh_c) and as TARGET (K_c/S_c/E_c) wherever row_ok — i.e. on train-route anchors inside clip windows only; val/test have no clips, so causal evaluation there needs new windows"] | |
| (NEW / "qc.json").write_text(json.dumps(res, indent=1)) | |
| summ = dict(pooled_pearson_K={c: round(v["r"], 4) if v["r"] is not None else None for c, v in res["pooled"]["pearson_K"].items()}, pooled_states=res["pooled"]["states"], pooled_events=res["pooled"]["events"], | |
| rows_compared={k: v["n_row_ok"] for k, v in res["splits"].items()}) | |
| write_run_json(NEW, "qc_kin_causal", [Path(__file__)], [NEW / f"kin_causal_{r}_{s}.npz" for r in ("manual", "engaged") for s in ("train", "val", "test")], None, t_start, extra=dict(summary=summ), input_listing=False) | |
| print("QC_KIN_DONE " + json.dumps(summ, default=str), flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 9.62 kB
- Xet hash:
- 4a3867d0b3dd6b2115eee4079ec1c8730059dcc391a331aa70a82cf5b8df8e62
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.