mocapgod/docs/B_ENGINE_SETUP.md
type-two 35403f8f05 Lane B: tick Lane B done, session-5 completion summary + Lane C handoff
PROGRESS.md: Lane B checked off with the session-5 outcome; SESSION 5 DONE block
(all 6 orders) + explicit Lane C handoff (BVH axes, skel_to_mixamorig map, camera
root_space, --from-params). B_ENGINE_SETUP.md: pose_engine.py usage + the engine
facts learned (SKEL 46-q, rest-aware FK ~4cm, Y-down camera fix, mobilenet detector).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:42:28 +10:00

6.1 KiB
Raw Permalink Blame History

Lane B engine — HSMR install & smoke-test state (session 3, 2026-07-17)

Engine per Decision 1 = Path B: HSMR → SKEL, run local on ultra, MPS. Installed under .engine/HSMR (gitignored). This doc = how it's wired + how to run once SKEL lands.

State

  • uv venv py3.10 at .engine/HSMR/.venv; torch 2.13.0, mps.is_available() == True.
  • Ungated weights in .engine/HSMR/data_inputs/: HSMR-ViTH ckpt (released_models/HSMR-ViTH-r1d1/checkpoints/hsmr.ckpt), ViTPose backbone, 3 regressors.
  • Real person detector (torchvision Faster R-CNN, no detectron2) — checked by .engine/HSMR/test_detector.py, green today with no SKEL: 7/7 demo images → 15 patches @256×256 through HSMR's own cropper.
  • Smoke-test runner .engine/HSMR/smoke_test.pyPASSED 2026-07-17 (session 5): SKEL v1.1.1 in place, device=mps, 263 frames → 1315 patches, recovery 31.2 fps, nan=False, data_outputs/smoke/params.npz written (poses (N,46), betas (N,10), cam_t (N,3)).
  • Install script (re-runnable): ./setup_hsmr.sh at the repo root (tracked in git). ./setup_hsmr.sh --patch re-applies the ARM/headless fixes onto an existing/re-cloned HSMR.
  • The ARM fixes are now durable in git (session 5, was: "on ultra's disk only"). Canonical copies live in engine_patches/ (_headless.py, test_detector.py, smoke_test.py, lib/modeling/pipelines/vitdet/__init__.py); setup_hsmr.sh overlays them onto the gitignored .engine/HSMR clone. Verified: fresh clone + --patchtest_detector.py green, no hand edits.

Apple-Silicon blockers — all three resolved (session 4, "Lane B detector")

  • detectron2 won't build on ARM MacRESOLVED. Replaced its whole ViTDet detector with a torchvision Faster R-CNN drop-in in lib/modeling/pipelines/vitdet/__init__.py — same build_detector(...) -> (dets, downsample_ratios) contract, torchvision's person class (1) remapped to HSMR's human id (0), boxes as lurb, ratio 1.0. This also killed a load-time landmine: the old import detectron2 there exploded anything importing lib.kits.hsmr_demo (why session 3 had to stub it). Detector pinned to CPU — cheap next to the ViT-H pass and it dodges MPS RoIAlign/NMS gaps; an in-file # ponytail note marks the mobilenet swap if per-frame detection ever dominates on long videos.
  • pyrender is dead headless on macOS (py_renderer forces PYOPENGL_PLATFORM=egl, no EGL on Mac; pyrender is even referenced in a type annotation, so a plain import-guard won't do). Centralized the permissive pyrender/OpenGL fake into .engine/HSMR/_headless.py — every entrypoint does import _headless before any lib.* import. QC still happens in Blender (fleet renderer + Lane C's tool); the runner dumps params.npz (SKEL poses/betas/cam_t — the Path-B payload) and mesh_qc.npz (verts/faces for Blender).
  • chumpy failed to buildNOT a blocker (verified). The runtime SKELWrapper loads its pkls with plain pickle.load(encoding='latin1') — no chumpy import. chumpy only appears in SKEL's offline model-build script (merge_smplh_mano.py), which we never run. Ignore the WARN.

John's one step — drop the SKEL files (login + licence, so it's yours to do)

Register skel.is.tue.mpg.de → Download → "SKEL and BSM models"skel_models_v1.1.zip (must be v1.1.1 — check changelog_v1.1.1.txt inside; older = silent incompatibility). Unzip, then:

mv /path/to/skel_models_v1.1 ~/Documents/MOCAPGOD/.engine/HSMR/data_inputs/body_models/skel

so that …/data_inputs/body_models/skel/skel_male.pkl exists.

Run the smoke test (once SKEL is in place)

cd ~/Documents/MOCAPGOD/.engine/HSMR && source .venv/bin/activate
PYTHONPATH=. python smoke_test.py -i data_inputs/demo/example_videos/gymnasts.mp4   # or .../example_imgs

Outputs → data_outputs/smoke/: params.npz (rotations), mesh_qc.npz (Blender QC), and console device/fps/NaN. Answers the Lane B smoke questions: MPS forward OK? timing (<2× realtime)? pose sane?

⚠️ HSMR is a per-frame recoverer → root is camera-space cam_t, not world-grounded. Fine for in-place DJ moves; locomotion (walk) needs Lane C's foot-contact root derivation or a grounding pass. This is the treaty's global-trajectory caveat, carried into Path B.

Run the pose engine (B2/B3, session 5) — the real service

pose_engine.py (repo root, tracked) wraps the engine into the pipeline. Run under the engine venv:

cd ~/Documents/MOCAPGOD
.engine/HSMR/.venv/bin/python pose_engine.py                    # drain queue/ → out/<name>/
.engine/HSMR/.venv/bin/python pose_engine.py queue/clip.mp4     # one clip
.engine/HSMR/.venv/bin/python pose_engine.py <clip> --from-params  # re-run SKEL→BVH, skip HMR
.engine/HSMR/.venv/bin/python pose_engine.py --selfcheck        # BVH-math self-test, no engine

Per clip → out/<name>/: motion.bvh (SKEL-24, Y-up, metres, faces Z), motion.json (sidecar, root_space:"camera"), params.npz (raw poses/betas/cam_t for QC / --from-params). Heartbeat: ~/.jobs/pose_engine. Models build once per invocation (queue amortizes); a bad clip is logged and skipped, not fatal.

Key engine facts learned this session (for whoever touches B again):

  • SKEL emits poses = (F, 46) biomechanical q, NOT SMPL's 72 axis-angles. Don't decode q by hand — run SKEL.forward() and consume joints_ori (F,24,3,3 global orientations) + joints_tpose.
  • BVH is rigid-bone: rest-aware FK (offset=rest_J[j]-rest_J[parent], W=Rg@rest_R^T, local=W[parent]^T@W[j]) reconstructs SKEL joints to ~4 cm; the residual is SKEL's coupled knee/ankle translations, which a rotation-only BVH cannot carry (accept it).
  • HSMR camera frame is Y-DOWN (OpenCV; verified head.y < pelvis.y). pose_engine applies diag(1,-1,-1) to reach the treaty's Y-up / faces-Z. Undo/adjust there if a future model differs.
  • Detector is mobilenet now (was R50) — CPU detection was the wall-clock hog. It self-caps input to ~640 px internally, so pre-downscaling does nothing; --detect-every is the real throughput knob.