Buckets:
| """cwm3/data/qc_pose_gate.py — quality gate of the causal RTMW pose (results/pose_causal) against the old non-causal | |
| YOLO11x-pose cache (s8_pose/cache/pose10hz{,_eng}/<uid>.npz) on the benchmark clips, at SHARED timestamps. | |
| Shared frames: old `t = fi/20` (even frame indices, 10 Hz) vs new `frame_idx = round(t_abs·20)` (5 Hz, every 4th frame) | |
| → a frame is shared when round(t_old·20) == frame_idx. Both caches are frame-anchored (frame/20), no container fps. | |
| Old → native px (s8_pose/extract_pose.py: decode 640×400, static crop [crop_x0, crop_x0+400] × [0,400], kpts/400): | |
| x_px = (x_n·400 + crop_x0)·W/640 , y_px = y_n·400·H/400 = y_n·H | |
| New → native px: x_px = x_n·W, y_px = y_n·H (HALPE 0–16 = COCO-17 order = the old cache order). | |
| Metrics (COCO joints nose 0, shoulders 5/6, wrists 9/10; a pair is comparable when both conf ≥ 0.3 and finite): | |
| PCK@0.05·H and PCK@0.10·H per joint + pooled, median px error, n pairs; | |
| detection rate old (row not all-NaN) vs new (A: nose ∧ both shoulders conf ≥ 0.3; B: A ∧ both wrists, = Kvalid rule); | |
| hands_on_wheel rule (cwm2/docs/kinematics_targets.md §1 / s8_pose/posefeat.py:84-90): wrist conf ≥ 0.3 else NaN, | |
| per-hand distance to the per-route MEDIAN wrist position, hands_on = min over hands < 0.10 in crop-normalised units. | |
| Implemented on the new coordinates by mapping them INTO the old crop frame (x_cn = (x_px·640/W − crop_x0)/400, y_cn = y_px/H) | |
| so the threshold is identical. Variants: old(whole-route median, exactly posefeat) vs new(median over the benchmark frames | |
| of that route) [the honest comparison]; new(old whole-route medians) [isolates keypoint differences]; old(bench-frame | |
| medians) vs old(whole-route) [isolates the median-estimation effect]. | |
| Outputs results/pose_causal/qc_200.json and qc_contact_sheet_20.png (20 local clips, anchor frame: person_box, new body-17, | |
| old body-17 overlaid).""" | |
| import argparse, json, sys, 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")) | |
| from runmeta import write_run_json | |
| POSE = ROOT / "results" / "pose_causal"; ROI = ROOT / "results" / "roi" | |
| OLD = [Path("/home/USER/s8_pose/cache/pose10hz"), Path("/home/USER/s8_pose/cache/pose10hz_eng")] | |
| CLIP_MAP = Path("/home/USER/cwm2/private/clip_map_full.json") | |
| J = {"nose": 0, "l_shoulder": 5, "r_shoulder": 6, "l_wrist": 9, "r_wrist": 10} | |
| TH, WHEEL_TH, DEC_W, CROP = 0.3, 0.10, 640, 400 | |
| BODY_EDGES = [(5,6),(5,7),(7,9),(6,8),(8,10),(5,11),(6,12),(11,12),(11,13),(13,15),(12,14),(14,16),(0,5),(0,6)] | |
| def load_old(uid): | |
| """manual cache has priority (as cwm2 kinematics build); engaged frames not in manual are appended. -> (fi, kpts[N,17,3] f32, crop_x0) or None""" | |
| parts = [] | |
| for d in OLD: | |
| p = d / f"{uid}.npz" | |
| if p.exists(): | |
| z = np.load(p); parts.append((np.round(z["t"].astype(np.float64) * 20).astype(np.int64), z["kpts"].astype(np.float32), float(z["crop_x0"]))) | |
| if not parts: return None | |
| fi, k, x0 = parts[0] | |
| for fi2, k2, x02 in parts[1:]: | |
| if x02 != x0: # shift engaged x into the manual crop frame (same rule as build_kinematics_targets) | |
| k2 = k2.copy(); k2[..., 0] = k2[..., 0] + (x02 - x0) / CROP | |
| new = ~np.isin(fi2, fi); fi = np.concatenate([fi, fi2[new]]); k = np.concatenate([k, k2[new]]) | |
| o = np.argsort(fi); return fi[o], k[o], x0 | |
| def old_to_px(k, x0, W, H): | |
| px = k.copy(); px[..., 0] = (k[..., 0] * CROP + x0) * W / DEC_W; px[..., 1] = k[..., 1] * H; return px | |
| def new_px_to_cn(px, x0, W, H): | |
| cn = px.copy(); cn[..., 0] = (px[..., 0] * DEC_W / W - x0) / CROP; cn[..., 1] = px[..., 1] / H; return cn | |
| def hands_on(wr_cn, conf, med): | |
| """wr_cn [N,2(L,R),2] crop-normalised wrists, conf [N,2], med {L:(x,y),R:(x,y)} -> float array (1/0/NaN).""" | |
| xy = wr_cn.copy(); xy[conf < TH] = np.nan | |
| dist = np.stack([np.hypot(xy[:, i, 0] - med[i][0], xy[:, i, 1] - med[i][1]) for i in range(2)], 1) | |
| mn = np.fmin(dist[:, 0], dist[:, 1]) | |
| return np.where(np.isnan(mn), np.nan, (mn < WHEEL_TH).astype(np.float32)) | |
| def medians(wr_cn, conf): | |
| xy = wr_cn.copy(); xy[conf < TH] = np.nan | |
| return [(np.nanmedian(xy[:, i, 0]), np.nanmedian(xy[:, i, 1])) for i in range(2)] | |
| def agree(a, b): | |
| ok = ~np.isnan(a) & ~np.isnan(b) | |
| return (float((a[ok] == b[ok]).mean()) if ok.any() else None), int(ok.sum()) | |
| def main(): | |
| ap = argparse.ArgumentParser(); ap.add_argument("--clips", default=str(POSE / "bench_200_clips.txt")); ap.add_argument("--sheet-n", type=int, default=20); a = ap.parse_args() | |
| t_start = time.time() | |
| ids = [l.strip() for l in open(a.clips) if l.strip()] | |
| cm = json.load(open(CLIP_MAP)) | |
| per_clip = []; pairs = defaultdict(list); det = defaultdict(int) | |
| route_new = defaultdict(list) # uid -> list of (wr_cn[N,2,2], conf[N,2], old-frame-aligned hands_on arrays ...) | |
| clip_data = {} | |
| for cid in ids: | |
| p = POSE / f"{cid}.npz" | |
| if not p.exists(): per_clip.append(dict(clip_id=cid, status="no_new_npz")); continue | |
| d = np.load(p); meta = cm[cid]; W, H = [int(v) for v in d["native_wh"]] | |
| old = load_old(str(d["uid"])) | |
| if old is None: per_clip.append(dict(clip_id=cid, status="no_old_cache")); continue | |
| fi_old, k_old, x0 = old | |
| pos = {int(f): i for i, f in enumerate(fi_old)} | |
| sh = [(k, pos[int(f)]) for k, f in enumerate(d["frame_idx"]) if int(f) in pos and d["frame_ok"][k]] | |
| if not sh: per_clip.append(dict(clip_id=cid, status="no_shared_frames")); continue | |
| kn, ko = zip(*sh); kn = np.array(kn); ko = np.array(ko) | |
| new = d["kpts"].astype(np.float32)[kn, :17]; new_px = new.copy(); new_px[..., 0] *= W; new_px[..., 1] *= H | |
| oldk = k_old[ko]; old_px = old_to_px(oldk, x0, W, H) | |
| rec = dict(clip_id=cid, status="ok", uid=str(d["uid"]), n_shared=len(sh), video_source=str(d["video_source"]), roi_source=str(d["roi_source"]), | |
| co_occupant_present=bool(d["co_occupant_present"]), W=W, H=H, joints={}) | |
| # detection | |
| old_det = np.isfinite(oldk[:, 0, 0]); newA = (new[:, [0, 5, 6], 2] >= TH).all(1) & np.isfinite(new[:, [0, 5, 6], 0]).all(1) | |
| newB = newA & (new[:, [9, 10], 2] >= TH).all(1) & np.isfinite(new[:, [9, 10], 0]).all(1) | |
| oldB = old_det & (oldk[:, [0, 5, 6, 9, 10], 2] >= TH).all(1) | |
| det["old_any"] += int(old_det.sum()); det["new_A_nose_shoulders"] += int(newA.sum()); det["new_B_kvalid"] += int(newB.sum()); det["old_B_kvalid"] += int(oldB.sum()); det["n_shared"] += len(sh) | |
| rec.update(det_old=float(old_det.mean()), det_newA=float(newA.mean()), det_newB=float(newB.mean()), det_oldB=float(oldB.mean())) | |
| # PCK | |
| for name, j in J.items(): | |
| ok = np.isfinite(old_px[:, j, 0]) & np.isfinite(new_px[:, j, 0]) & (oldk[:, j, 2] >= TH) & (new[:, j, 2] >= TH) | |
| dpx = np.hypot(*(new_px[ok, j, :2] - old_px[ok, j, :2]).T) | |
| pairs[name].append(dpx / H) | |
| rec["joints"][name] = dict(n=int(ok.sum()), pck05=(float((dpx < 0.05 * H).mean()) if ok.any() else None), med_px=(float(np.median(dpx)) if ok.any() else None)) | |
| # hands_on | |
| wr_old = oldk[:, [9, 10], :2]; c_old = oldk[:, [9, 10], 2] | |
| wr_new_cn = new_px_to_cn(new_px[:, [9, 10], :2], x0, W, H); c_new = new[:, [9, 10], 2] | |
| med_route_old = medians(k_old[:, [9, 10], :2], k_old[:, [9, 10], 2]) # whole-route median, exactly posefeat | |
| clip_data[cid] = dict(wr_old=wr_old, c_old=c_old, wr_new_cn=wr_new_cn, c_new=c_new, med_route_old=med_route_old, uid=str(d["uid"]), rec=rec, | |
| kn=kn, new_px=new_px, old_px=old_px, oldk=oldk, new=new, x0=x0) | |
| route_new[str(d["uid"])].append((wr_new_cn, c_new, wr_old, c_old)) | |
| per_clip.append(rec) | |
| # route-level medians over the benchmark frames (new and old) | |
| med_bench_new, med_bench_old = {}, {} | |
| for uid, lst in route_new.items(): | |
| med_bench_new[uid] = medians(np.concatenate([x[0] for x in lst]), np.concatenate([x[1] for x in lst])) | |
| med_bench_old[uid] = medians(np.concatenate([x[2] for x in lst]), np.concatenate([x[3] for x in lst])) | |
| ho = defaultdict(list) | |
| for cid, c in clip_data.items(): | |
| uid = c["uid"] | |
| h_old_route = hands_on(c["wr_old"], c["c_old"], c["med_route_old"]) | |
| h_old_bench = hands_on(c["wr_old"], c["c_old"], med_bench_old[uid]) | |
| h_new_bench = hands_on(c["wr_new_cn"], c["c_new"], med_bench_new[uid]) | |
| h_new_oldmed = hands_on(c["wr_new_cn"], c["c_new"], c["med_route_old"]) | |
| for k, v in (("old_route", h_old_route), ("old_bench", h_old_bench), ("new_bench", h_new_bench), ("new_oldmed", h_new_oldmed)): ho[k].append(v) | |
| ag, n = agree(h_new_bench, h_old_route); c["rec"]["hands_on_agree_new_bench_vs_old_route"] = ag; c["rec"]["hands_on_n"] = n | |
| HO = {k: np.concatenate(v) for k, v in ho.items()} if ho else {} | |
| def summarize(name): | |
| v = np.concatenate(pairs[name]) if pairs[name] else np.array([]) | |
| return dict(n_pairs=int(v.size), pck05=(float((v < 0.05).mean()) if v.size else None), pck10=(float((v < 0.10).mean()) if v.size else None), | |
| median_err_over_H=(float(np.median(v)) if v.size else None), p90_err_over_H=(float(np.percentile(v, 90)) if v.size else None)) | |
| joints = {n: summarize(n) for n in J} | |
| allv = np.concatenate([np.concatenate(pairs[n]) for n in J if pairs[n]]) if any(pairs.values()) else np.array([]) | |
| pooled = dict(n_pairs=int(allv.size), pck05=(float((allv < 0.05).mean()) if allv.size else None), pck10=(float((allv < 0.10).mean()) if allv.size else None)) | |
| ok_clips = [r for r in per_clip if r["status"] == "ok"] | |
| def strat(key): | |
| out = {} | |
| for val in sorted({str(r[key]) for r in ok_clips}): | |
| rr = [r for r in ok_clips if str(r[key]) == val] | |
| v = [r["joints"][n]["pck05"] for r in rr for n in J if r["joints"][n]["pck05"] is not None] | |
| hh = [r["hands_on_agree_new_bench_vs_old_route"] for r in rr if r.get("hands_on_agree_new_bench_vs_old_route") is not None] | |
| out[val] = dict(n_clips=len(rr), mean_clip_pck05=(float(np.mean(v)) if v else None), mean_clip_hands_on_agreement=(float(np.mean(hh)) if hh else None)) | |
| return out | |
| res = dict(utc=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), n_clips_requested=len(ids), n_clips_compared=len(ok_clips), | |
| status_counts={s: sum(r["status"] == s for r in per_clip) for s in sorted({r["status"] for r in per_clip})}, | |
| n_shared_frames=int(det["n_shared"]), pck=dict(joints=joints, pooled=pooled, definition="PCK@0.05·H (and 0.10·H) on shared frames where both conf >= 0.3; error normalised by native H"), | |
| detection=dict(rates={k: (v / det["n_shared"] if det["n_shared"] else None) for k, v in det.items() if k != "n_shared"}, | |
| diff_newA_minus_old_any=((det["new_A_nose_shoulders"] - det["old_any"]) / det["n_shared"]) if det["n_shared"] else None, | |
| diff_newB_minus_oldB_kvalid=((det["new_B_kvalid"] - det["old_B_kvalid"]) / det["n_shared"]) if det["n_shared"] else None, | |
| definition="old_any: old row has a detection; new_A: nose & both shoulders conf>=0.3; *_B: A & both wrists (Kvalid rule)"), | |
| hands_on=dict(agreement_new_bench_vs_old_route=agree(HO["new_bench"], HO["old_route"]) if HO else None, | |
| agreement_new_oldmed_vs_old_route=agree(HO["new_oldmed"], HO["old_route"]) if HO else None, | |
| agreement_old_bench_vs_old_route=agree(HO["old_bench"], HO["old_route"]) if HO else None, | |
| positive_rate={k: float(np.nanmean(v)) for k, v in HO.items()}, defined_rate={k: float(np.mean(~np.isnan(v))) for k, v in HO.items()}, | |
| definition="min over wrists of hypot(wrist - per-route median wrist) < 0.10 crop-normalised (posefeat.py:84-90); new coords mapped into the old crop frame; (agreement, n_frames_both_defined)"), | |
| by_video_source=strat("video_source"), by_roi_source=strat("roi_source"), by_co_occupant=strat("co_occupant_present"), per_clip=per_clip) | |
| (POSE / "qc_200.json").write_text(json.dumps(res, indent=1)) | |
| # ---- contact sheet | |
| sheet = None | |
| try: | |
| import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt; import decord, cv2 | |
| decord.bridge.set_bridge("native") | |
| cands = [cid for cid in ids if cid in clip_data and clip_data[cid]["rec"]["video_source"] == "local"][:a.sheet_n] | |
| cols = 5; rows = int(np.ceil(len(cands) / cols)); fig, axes = plt.subplots(rows, cols, figsize=(cols * 4.2, rows * 3.0)); axes = np.atleast_1d(axes).ravel() | |
| C_NEW, C_OLD, C_BOX = "#00A6D6", "#F28E2B", "#FFFFFF" | |
| for ax in axes: ax.axis("off") | |
| for ax, cid in zip(axes, cands): | |
| c = clip_data[cid]; meta = cm[cid]; d = np.load(POSE / f"{cid}.npz"); W, H = [int(v) for v in d["native_wh"]] | |
| ks = list(c["kn"]); k_anchor = 25 if 25 in ks else ks[len(ks) // 2]; i = ks.index(k_anchor) | |
| vr = decord.VideoReader(meta["dcam"], ctx=decord.cpu(0), num_threads=4); fr = vr.get_batch([int(d["frame_idx"][k_anchor])]).asnumpy()[0]; del vr | |
| s = 640 / W; im = cv2.resize(fr, (640, int(H * s)), interpolation=cv2.INTER_AREA); ax.imshow(im) | |
| b = d["person_box"] * s; ax.add_patch(plt.Rectangle((b[0], b[1]), b[2] - b[0], b[3] - b[1], fill=False, ec=C_BOX, lw=0.8, ls="--")) | |
| for px, col, conf, lw in ((c["old_px"][i], C_OLD, c["oldk"][i, :, 2], 1.2), (c["new_px"][i], C_NEW, c["new"][i, :, 2], 1.2)): | |
| for a_, b_ in BODY_EDGES: | |
| if conf[a_] >= TH and conf[b_] >= TH and np.isfinite(px[[a_, b_], 0]).all(): ax.plot(px[[a_, b_], 0] * s, px[[a_, b_], 1] * s, "-", color=col, lw=lw) | |
| ok = (conf >= TH) & np.isfinite(px[:, 0]); ax.scatter(px[ok, 0] * s, px[ok, 1] * s, s=6, color=col) | |
| hn = d["kpts"].astype(np.float32)[k_anchor, 94:136]; okh = np.isfinite(hn[:, 0]) & (hn[:, 2] >= TH); ax.scatter(hn[okh, 0] * W * s, hn[okh, 1] * H * s, s=2, color=C_NEW, alpha=0.6) | |
| p5 = [c["rec"]["joints"][n]["pck05"] for n in J if c["rec"]["joints"][n]["pck05"] is not None] | |
| ax.set_title(f"{cid} k={k_anchor} PCK05={np.mean(p5):.2f} {c['rec']['roi_source'][:4]}{' co' if c['rec']['co_occupant_present'] else ''}", fontsize=7) | |
| fig.suptitle(f"qc_pose_gate: new causal RTMW body-17 ({C_NEW} cyan, + hands) vs old YOLO11x pose10hz ({C_OLD} orange); dashed = causal person_box", fontsize=9) | |
| fig.tight_layout(); sheet = POSE / f"qc_contact_sheet_{len(cands)}.png"; fig.savefig(sheet, dpi=110); plt.close(fig) | |
| except Exception as e: | |
| res["contact_sheet_error"] = repr(e)[:300]; (POSE / "qc_200.json").write_text(json.dumps(res, indent=1)) | |
| summ = {k: res[k] for k in ("n_clips_compared", "n_shared_frames", "status_counts")}; summ["pck_pooled"] = pooled; summ["pck_joints"] = {n: (joints[n]["pck05"], joints[n]["n_pairs"]) for n in J} | |
| summ["detection"] = res["detection"]["rates"]; summ["hands_on"] = {k: res["hands_on"][k] for k in ("agreement_new_bench_vs_old_route", "agreement_new_oldmed_vs_old_route", "agreement_old_bench_vs_old_route")}; summ["sheet"] = str(sheet) | |
| write_run_json(POSE, "qc_pose_gate", [__file__], [Path(a.clips)], None, t_start, extra={"summary": summ}) | |
| print("QC_DONE " + json.dumps(summ, default=str), flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 15.6 kB
- Xet hash:
- 5fe53d9709362efb20a6d198828843507c414f2529040cafe50fb90b6edffd79
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.