Download scripts/data/audit_episode_quality.py from zhicao/dreamzero: direct link, hf CLI and curl.
- Browser
- Download file 4.61 kB
-
https://huggingface.co/zhicao/dreamzero/resolve/main/scripts/data/audit_episode_quality.py
- Command line
-
hf download hf://zhicao/dreamzero/scripts/data/audit_episode_quality.py
-
curl -L -o audit_episode_quality.py https://huggingface.co/zhicao/dreamzero/resolve/main/scripts/data/audit_episode_quality.py
4.61 kB
| """Audit every episode of a T-Rex-format LeRobot v2.1 dataset. | |
| Usage: python scripts/data/audit_episode_quality.py --dataset-root data/trex_full | |
| Contact = fingertip |(fx,fy,fz)| minus the per-finger whole-episode median baseline (if >0.5 N) exceeds 0.5 N, | |
| the same definition as data/sharpa/tools/compare_difficulty.py. Writes <root>/meta/episode_quality.json. | |
| """ | |
| import json, glob, os, sys, numpy as np, pyarrow.parquet as pq, time | |
| import argparse | |
| ap=argparse.ArgumentParser(description="Per-episode quality audit for T-Rex-format LeRobot datasets (duration, bimanual motion, contact ratio, force baseline).") | |
| ap.add_argument("--dataset-root",required=True); ap.add_argument("--out",default=None,help="json output (default <root>/meta/episode_quality.json)") | |
| args=ap.parse_args(); root=args.dataset_root | |
| eps={e["episode_index"]:e for e in map(json.loads, open(f"{root}/meta/episodes.jsonl"))} | |
| F_THR=0.5 # N above per-finger baseline = contact | |
| rows=[]; t0=time.time() | |
| files=sorted(glob.glob(f"{root}/data/chunk-*/episode_*.parquet")) | |
| for i,f in enumerate(files): | |
| t=pq.read_table(f, columns=["episode_index","timestamp","observation.state","observation.tactile_force"]) | |
| ei=int(t["episode_index"][0].as_py()) | |
| ts=np.array(t["timestamp"].to_pylist(),dtype=np.float64) | |
| st=np.stack(t["observation.state"].to_numpy(zero_copy_only=False)).astype(np.float32) | |
| tac=np.stack(t["observation.tactile_force"].to_numpy(zero_copy_only=False)).astype(np.float32).reshape(-1,10,6) | |
| fn=np.linalg.norm(tac[:,:,:3],axis=-1) # [T,10] | |
| med=np.median(fn,0); base=np.where(med>0.5, med, 0.0) # remove un-zeroed baseline per finger (same as compare_difficulty.py) | |
| fn=fn-base | |
| contact=fn>F_THR | |
| l=contact[:,:5].any(1); r=contact[:,5:].any(1) | |
| T=len(ts); dur=float(ts[-1]-ts[0]) if T>1 else 0.0 | |
| def path(a): return float(np.abs(np.diff(a,axis=0)).sum()) | |
| def events(m): | |
| on=0; gap=99; prev=False | |
| for v in m: | |
| if v and not prev and gap>9: on+=1 | |
| gap=0 if v else gap+1; prev=v | |
| return on | |
| dq=np.abs(np.diff(st,axis=0)); idle=float((dq.max(1)<1e-3).mean()) if T>1 else 1.0 | |
| rows.append(dict(ep=ei, task=eps[ei]["tasks"][0], frames=T, dur=dur, | |
| contact=float((l|r).mean()), both=float((l&r).mean()), left=float(l.mean()), right=float(r.mean()), | |
| larm=path(st[:,0:7]), rarm=path(st[:,29:36]), lhand=path(st[:,7:29]), rhand=path(st[:,36:58]), | |
| fp99=float(np.percentile(fn,99)), fmax=float(fn.max()), fingers=int((contact.mean(0)>0.05).sum()), | |
| events=events(l)+events(r), idle=idle, baseline_max=float(base.max()), | |
| ts_mono=bool(np.all(np.diff(ts)>0)), dt_med=float(np.median(np.diff(ts))) if T>1 else 0)) | |
| if i%500==0: print(f"{i}/{len(files)} {time.time()-t0:.0f}s", flush=True) | |
| out=args.out or f"{root}/meta/episode_quality.json"; json.dump(rows, open(out,"w")); print("wrote",out) | |
| d=np.array([r["dur"] for r in rows]); print("durations: min %.1f median %.1f p90 %.1f max %.1f; n>=30s: %d; 30-120s: %d"%(d.min(),np.median(d),np.percentile(d,90),d.max(),(d>=30).sum(),((d>=30)&(d<=120)).sum())) | |
| cand=[r for r in rows if 30<=r["dur"]<=120 and r["larm"]>1.0 and r["rarm"]>1.0 and r["left"]>0.1 and r["right"]>0.1] | |
| print("candidates (30-120 s, both arms move >1 rad, each hand in contact >10%):", len(cand)) | |
| cand.sort(key=lambda r: -(r["contact"]*0.6+r["both"]*0.4)) | |
| for r in cand[:15]: | |
| print(f" ep {r['ep']:5d} {r['dur']:6.1f}s contact {r['contact']:.2f} both {r['both']:.2f} L {r['left']:.2f} R {r['right']:.2f} larm {r['larm']:.1f} rarm {r['rarm']:.1f} fingers {r['fingers']} fp99 {r['fp99']:.1f}N | {r['task'][:80]}") | |
| import statistics | |
| print("dataset medians: contact %.2f both %.2f events %d baseline_max %.1fN"%(statistics.median(r['contact'] for r in rows),statistics.median(r['both'] for r in rows),statistics.median(r['events'] for r in rows),statistics.median(r['baseline_max'] for r in rows))) | |
| print("episodes with baseline offset >0.5N on some finger:", sum(r['baseline_max']>0.5 for r in rows)) | |
| # second ranking: prefer 40-90 s, high contact & both, many fingers | |
| cand2=[r for r in cand if 40<=r['dur']<=90 and r['fingers']>=6 and r['events']>=6] | |
| cand2.sort(key=lambda r: -(r['contact']+r['both']+0.02*r['fingers'])) | |
| print("shortlist (40-90 s, >=6 fingers, >=6 events):", len(cand2)) | |
| for r in cand2[:12]: | |
| print(f" ep {r['ep']:5d} {r['dur']:6.1f}s contact {r['contact']:.2f} both {r['both']:.2f} L {r['left']:.2f} R {r['right']:.2f} ev {r['events']:3d} idle {r['idle']:.2f} fingers {r['fingers']} fp99 {r['fp99']:.1f}N base {r['baseline_max']:.1f} | {r['task']}") | |
| print("DONE") | |