Files
liyang ae28d55f81 Update to 2026-09-17 pipeline snapshot; add weights, L20 assets and recording via Git LFS
Source: RGB-D -> Dyn-HaMR -> L20 retargeting -> FoundationPose -> reference repair -> SPIDER,
documented in docs/PIPELINE_LATEST.md and docs/SETUP_AND_WEIGHTS.md. Adds FoundationPose and
nvdiffrast upstream snapshots, requirements/pipeline_venv.txt and the FoundationPose weight
manifest/downloader.

Assets (Git LFS): weights/ (WiLoR detector, HandFlow denoiser, UniDepth-L), FoundationPose
checkpoints, HaMeR checkpoint, Dyn-HaMR HMP model and BMC constraints, L20 URDF/meshes, the
20260915_171525 D405 recording and the two box CADs. MANO models are not redistributed
(third_party/hamer/_DATA/data/mano/README.txt). Environments, caches and run outputs excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:43:37 +08:00

61 lines
3.6 KiB
Python

"""Interactive wall-clock playback of saved SPIDER states (no re-simulation)."""
import argparse,os,time,json
from pathlib import Path
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--directory',type=Path,default=Path(__file__).resolve().parents[1]/'output/spider_dynamics_fix_20260915')
p.add_argument('--mode',choices=['physics','reference'],default='physics')
p.add_argument('--check',action='store_true');a=p.parse_args()
os.environ['MUJOCO_GL']='osmesa' if a.check else 'glfw'
import numpy as np,mujoco
root=a.directory.resolve();task=root/'datasets/processed/current/l20/bimanual/boxes'
m=mujoco.MjModel.from_xml_path(str(task/'scene_act.xml'));d=mujoco.MjData(m)
source=root/('reference_video_rate.npz' if a.mode=='reference' else 'physics_motion.npz')
mode='collision-corrected kinematic reference (middle video panel)' if a.mode=='reference' else 'saved physical state playback, no re-simulation'
with np.load(source) as z:
q=z['qpos'];t=z['time']
if a.mode=='physics':u=z['ctrl']
if a.mode=='reference':
v=np.zeros((len(q),m.nv));u=q[:,m.jnt_qposadr[m.actuator_trnid[:,0]]]
else:
with np.load(task/'0/trajectory_mjwp_act.npz') as z:v=z['qvel'].reshape(len(q),-1)
assert q.shape==(len(t),m.nq) and np.isfinite(q).all() and np.all(np.diff(t)>0)
if a.check:
for i in [0,len(q)//2,len(q)-1]:
d.qpos[:]=q[i];d.qvel[:]=v[i];d.ctrl[:]=u[i];mujoco.mj_forward(m,d)
print(json.dumps({'steps':len(q),'duration_s':float(t[-1]),'nq':m.nq,'finite':True,'mode':mode}));raise SystemExit
import mujoco.viewer
state={'paused':False,'time':0.,'speed':1.,'reset_camera':False}
def key(k):
if k==32:state['paused']=not state['paused']
elif k in [82,114]:state['time']=0.
elif k in [262,263]:state['time']=float(np.clip(state['time']+(1 if k==262 else -1)/30,0,t[-1]));state['paused']=True
elif k==265:state['speed']=min(4.,state['speed']*2)
elif k==264:state['speed']=max(.125,state['speed']/2)
elif k in [67,99]:state['reset_camera']=True
m.vis.headlight.ambient[:]=.7
d.qpos[:]=q[0];mujoco.mj_forward(m,d)
center=np.mean([d.site_xpos[m.site(s+'_object_track').id] for s in ['right','left']],axis=0)
cid=m.camera('source_camera').id;offset=d.cam_xpos[cid]-center
camera={'distance':float(np.linalg.norm(offset)),'azimuth':float(np.degrees(np.arctan2(offset[1],offset[0]))),'elevation':float(-np.degrees(np.arctan2(offset[2],np.linalg.norm(offset[:2]))))}
status=root/'interactive_playback_status.json'
print(mode+' | SPACE pause | R restart | LEFT/RIGHT video-frame step | UP/DOWN speed | C camera reset | mouse orbit/zoom',flush=True)
with mujoco.viewer.launch_passive(m,d,key_callback=key) as viewer:
def reset_camera():
viewer.cam.type=mujoco.mjtCamera.mjCAMERA_FREE;viewer.cam.lookat[:]=center
for k,x in camera.items():setattr(viewer.cam,k,x)
with viewer.lock():
reset_camera();viewer.opt.sitegroup[:]=0;viewer.opt.geomgroup[3:]=0
last=time.perf_counter();last_status=0
while viewer.is_running():
now=time.perf_counter();elapsed=now-last;last=now
if not state['paused']:state['time']=(state['time']+elapsed*state['speed'])%float(t[-1])
i=int(np.clip(np.searchsorted(t,state['time'],side='right')-1,0,len(t)-1))
with viewer.lock():
if state['reset_camera']:reset_camera();state['reset_camera']=False
d.qpos[:]=q[i];d.qvel[:]=v[i];d.ctrl[:]=u[i];d.time=float(t[i]);mujoco.mj_forward(m,d)
viewer.sync()
if now-last_status>1:
status.write_text(json.dumps({'running':True,'pid':os.getpid(),'mode':mode,'source':str(source),'step':i,'time_s':float(t[i]),'speed':state['speed'],'paused':state['paused'],'updated_unix':time.time()},indent=2));last_status=now
time.sleep(.008)
status.write_text(json.dumps({'running':False,'pid':os.getpid(),'updated_unix':time.time()}))