AsianPlayer/TriDrive / code /data /qc_kin_causal.py
AsianPlayer's picture
download
raw
9.62 kB
"""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.