The torchvision detector drop-in, headless pyrender/OpenGL shims, and the detector+smoke tests existed only on ultra's disk inside gitignored .engine/. Captured byte-identical copies into engine_patches/ (mirrors the HSMR subtree) and moved setup_hsmr.sh to the repo root (tracked, path-relative) with a --patch step that overlays them onto the clone. Acceptance PASSED: fresh shallow clone of HSMR in a temp dir + './setup_hsmr.sh --patch' + validated venv -> test_detector.py green (7/7 demo imgs, 15 patches), no hand edits. A re-clone can no longer silently drop the ARM fixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
4.6 KiB
Python
101 lines
4.6 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
HSMR smoke test on Apple Silicon (MPS) — Path B engine verification.
|
|
|
|
Answers the Lane B smoke-test questions on HSMR's bundled demo clips (no John footage,
|
|
no SKEL license needed beyond the model files John downloaded):
|
|
1. Does the HSMR forward run on MPS? (device + CPU-fallback)
|
|
2. Timing (target < 2x realtime).
|
|
3. Pose sanity (shape, NaNs) + an overlay render to eyeball quality.
|
|
|
|
Detection uses the torchvision person detector (lib.modeling.pipelines.vitdet, the
|
|
detectron2-free drop-in). The one remaining gate is SKEL: build_inference_pipeline
|
|
instantiates the SKEL body model, so this completes only once John's SKEL files are in place.
|
|
|
|
Run (once SKEL files are in data_inputs/body_models/skel/):
|
|
cd ~/Documents/MOCAPGOD/.engine/HSMR && source .venv/bin/activate
|
|
PYTHONPATH=. python smoke_test.py -i data_inputs/demo/example_videos/gymnasts.mp4
|
|
PYTHONPATH=. python smoke_test.py -i data_inputs/demo/example_imgs # image folder
|
|
"""
|
|
import os, time, argparse
|
|
os.environ.setdefault('PYTORCH_ENABLE_MPS_FALLBACK', '1') # some ops lack MPS kernels
|
|
|
|
import _headless # noqa: F401 fake pyrender/OpenGL for headless macOS — MUST precede lib.* imports
|
|
|
|
# One import line: every helper is an attribute of hsmr_demo (via its star-imports).
|
|
from lib.kits.hsmr_demo import (
|
|
load_inputs, imgs_det2patches, build_inference_pipeline, prepare_mesh,
|
|
visualize_full_img, IMG_MEAN_255, IMG_STD_255, asb, assemble_dict,
|
|
get_logger, save_video, save_img, np, torch, Path, DEFAULT_HSMR_ROOT,
|
|
build_detector,
|
|
)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('-i', '--input_path', required=True)
|
|
ap.add_argument('-o', '--output_path', default='data_outputs/smoke')
|
|
ap.add_argument('-d', '--device', default='mps')
|
|
ap.add_argument('-m', '--model_root', default=None)
|
|
ap.add_argument('--rec_bs', type=int, default=64)
|
|
ap.add_argument('--ignore_skel', action='store_true')
|
|
args = ap.parse_args()
|
|
|
|
log = get_logger(brief=True)
|
|
model_root = Path(args.model_root) if args.model_root else DEFAULT_HSMR_ROOT
|
|
out = Path(args.output_path); out.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Inputs (reuse the demo loader; it only reads .input_path / .input_type).
|
|
fake = argparse.Namespace(input_path=args.input_path, input_type='auto')
|
|
raw_imgs, meta = load_inputs(fake)
|
|
log.info(f'device={args.device} type={meta["type"]} frames={len(raw_imgs)}')
|
|
|
|
# Detection -> patches (real torchvision person detector, on CPU).
|
|
dets, ratios = build_detector(device=args.device)(raw_imgs)
|
|
patches, det_meta = imgs_det2patches(raw_imgs, dets, ratios, 5)
|
|
log.info(f'patches={len(patches)}')
|
|
|
|
# Build pipeline — instantiates SKEL, so this is where a missing SKEL file fails.
|
|
pipe = build_inference_pipeline(model_root=model_root, device=args.device)
|
|
|
|
# Recovery loop (mirrors run_demo), timed.
|
|
t0 = time.time()
|
|
pd_params, pd_cam_t = [], []
|
|
for bw in asb(total=len(patches), bs_scope=args.rec_bs, enable_tqdm=True):
|
|
p = patches[bw.sid:bw.eid]
|
|
pn = ((p - IMG_MEAN_255) / IMG_STD_255).transpose(0, 3, 1, 2)
|
|
with torch.no_grad():
|
|
o = pipe(pn)
|
|
pd_params.append({k: v.detach().cpu().clone() for k, v in o['pd_params'].items()})
|
|
pd_cam_t.append(o['pd_cam_t'].detach().cpu().clone())
|
|
dt = time.time() - t0
|
|
pd_params = assemble_dict(pd_params, expand_dim=False)
|
|
pd_cam_t = torch.cat(pd_cam_t, dim=0)
|
|
|
|
poses = pd_params['poses']
|
|
fps = len(patches) / dt if dt else 0
|
|
log.info(f'RECOVERY ok: {len(patches)} patches in {dt:.2f}s ({fps:.2f} fps) '
|
|
f'poses={tuple(poses.shape)} nan={bool(torch.isnan(poses).any())}')
|
|
|
|
# Dump raw params regardless (this is the Path-B payload: SKEL rotations).
|
|
np.savez(out / 'params.npz', poses=poses.numpy(),
|
|
betas=pd_params['betas'].numpy(), cam_t=pd_cam_t.numpy())
|
|
log.info(f'params -> {out/"params.npz"}')
|
|
|
|
# Mesh geometry for visual QC in Blender (pyrender/EGL is dead headless on macOS, and
|
|
# Blender is the fleet renderer + Lane C's tool). prepare_mesh uses skel_model, no GL.
|
|
try:
|
|
m_skin, m_skel = prepare_mesh(pipe, pd_params)
|
|
np.savez(out / 'mesh_qc.npz',
|
|
skin_v=m_skin['v'].detach().cpu().numpy(), skin_f=np.asarray(m_skin['f']),
|
|
skel_v=m_skel['v'].detach().cpu().numpy(), skel_f=np.asarray(m_skel['f']))
|
|
log.info(f'mesh geometry -> {out/"mesh_qc.npz"} (import in Blender to eyeball)')
|
|
except Exception as e:
|
|
log.warning(f'prepare_mesh failed (params still saved): {e!r}')
|
|
|
|
log.info('smoke test complete.')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|