Files
hand-motion-pipeline/scripts/audit_spider_dynamic_surfaces.py
T
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

19 lines
2.3 KiB
Python

"""Check original visual hand surfaces against exact closed CAD in dynamic rollout."""
import os
os.environ['OPENBLAS_NUM_THREADS']='1';os.environ['OMP_NUM_THREADS']='1'
from pathlib import Path
import json,numpy as np,mujoco,trimesh,open3d as o3d
from red_box_distance import signed_distance
ROOT=Path(__file__).resolve().parents[1];O=Path(os.environ.get('SPIDER_TASK_OUT',str(ROOT/'output/spider_dynamics_fix_20260915')));m=mujoco.MjModel.from_xml_path(str(O/'datasets/processed/current/l20/bimanual/boxes/scene_act.xml'));d=mujoco.MjData(m);a=np.load(O/'physics_motion.npz');t=np.load(O/'reference_video_rate.npz')['time'];ix=np.argmin(abs(a['time'][:,None]-t[None,:]),axis=0);q=a['qpos'][ix];probes=[]
for g in range(m.ngeom):
n=m.geom(g).name or ''
if '_visual_' not in n or not n.startswith(('right_','left_')):continue
mid=m.geom_dataid[g];v=m.mesh_vert[m.mesh_vertadr[mid]:m.mesh_vertadr[mid]+m.mesh_vertnum[mid]];faces=m.mesh_face[m.mesh_faceadr[mid]:m.mesh_faceadr[mid]+m.mesh_facenum[mid]];cent=v[faces].mean(1);pts=np.r_[v[np.linspace(0,len(v)-1,min(64,len(v)),dtype=int)],cent[np.linspace(0,len(cent)-1,min(64,len(cent)),dtype=int)]];probes.append((g,pts))
report={'sampled_frames':len(q),'hand_surface_samples_per_frame':sum(len(p) for g,p in probes),'scope':'Original visible hand meshes vs closed CAD; deterministic surface sampling, not exhaustive intersection or continuous-time proof.'}
for name,side in [('upper','right'),('lower','left')]:
mesh=trimesh.load(ROOT/'output/collision_fix_20260915/collision_v2'/f'{name}_closed.ply');assert mesh.is_watertight;sc=o3d.t.geometry.RaycastingScene();sc.add_triangles(o3d.t.geometry.TriangleMesh.from_legacy(o3d.geometry.TriangleMesh(o3d.utility.Vector3dVector(mesh.vertices),o3d.utility.Vector3iVector(mesh.faces))));obj=m.body(side+'_object').id;values=[]
for row in q:
d.qpos[:]=row;mujoco.mj_kinematics(m,d);points=np.concatenate([v@d.geom_xmat[g].reshape(3,3).T+d.geom_xpos[g] for g,v in probes]);local=(points-d.xpos[obj])@d.xmat[obj].reshape(3,3);dist=signed_distance(sc,mesh,local);values.append(max(0,-dist.min())*1000)
report[name]={'max_inside_mm':float(max(values)),'frames_over_1mm':int(np.sum(np.array(values)>1))};print(name,report[name],flush=True)
(O/'dynamic_surface_validation.json').write_text(json.dumps(report,indent=2))