mocapgod/engine_patches/test_detector.py
type-two f8037f7b85 Lane B / session-5 item 1: durable-ize the ARM engine fixes into git
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>
2026-07-17 09:34:39 +10:00

72 lines
3.3 KiB
Python

#!/usr/bin/env python
"""Detector smoke check — runs TODAY, no SKEL, no HMR forward pass.
Proves the torchvision person detector (lib/modeling/pipelines/vitdet) imports without
detectron2 and honors the contract lib.kits.hsmr_demo._img_det2patches depends on.
cd ~/Documents/MOCAPGOD/.engine/HSMR && source .venv/bin/activate
PYTHONPATH=. python test_detector.py
"""
from pathlib import Path
import cv2
import torch
import _headless # noqa: F401 fake pyrender/OpenGL for headless macOS — MUST precede lib.* imports
from lib.modeling.pipelines.vitdet import build_detector # must import without detectron2
from lib.kits.hsmr_demo import imgs_det2patches # the real consumer of detector output
DEMO = Path('data_inputs/demo/example_imgs')
DET_THRESHOLD_SCORE = 0.5 # matches hsmr_demo._img_det2patches
def main():
fns = sorted(p for p in DEMO.glob('*')
if p.suffix.lower() in {'.jpg', '.jpeg', '.png', '.webp'})
assert fns, f'no demo images under {DEMO}'
imgs = []
for fn in fns:
bgr = cv2.imread(str(fn))
assert bgr is not None, f'cv2 failed to read {fn}'
imgs.append(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)) # HSMR feeds RGB
dets, ratios = build_detector(device='cpu')(imgs)
assert len(dets) == len(imgs) == len(ratios), 'per-image list length mismatch'
n_person_imgs = 0
for fn, img, d, r in zip(fns, imgs, dets, ratios):
H, W = img.shape[:2]
assert set(d) == {'pred_classes', 'scores', 'pred_boxes'}, f'bad keys {set(d)}'
assert d['pred_classes'].dtype == torch.long
assert (d['pred_classes'] == 0).all(), 'human class must be 0 (CLASS_HUMAN_ID)'
assert d['pred_boxes'].ndim == 2 and d['pred_boxes'].shape[1] == 4, 'boxes must be (N,4)'
assert r == 1.0, 'native-resolution detection -> ratio 1.0'
strong = d['scores'] > DET_THRESHOLD_SCORE
if strong.any():
n_person_imgs += 1
x1, y1, x2, y2 = d['pred_boxes'][strong].unbind(1)
assert (x2 > x1).all() and (y2 > y1).all(), 'boxes must be lurb (x2>x1, y2>y1)'
assert (x1 >= -1).all() and (y1 >= -1).all() \
and (x2 <= W + 1).all() and (y2 <= H + 1).all(), 'box out of frame'
print(f'{fn.name}: {int(strong.sum())} person(s), '
f'top score {float(d["scores"][strong].max()):.2f}')
else:
print(f' · {fn.name}: no confident person')
# Every demo image has an obvious human; allow one miss for robustness slack.
assert n_person_imgs >= len(imgs) - 1, \
f'expected a person in ~all demo imgs, got {n_person_imgs}/{len(imgs)}'
print(f'DETECTOR OK: person found in {n_person_imgs}/{len(imgs)} demo images.')
# Close the loop: feed detector output through HSMR's real cropper (SKEL-free) and
# confirm it yields the 256x256 patches the HMR forward pass expects.
patches, det_meta = imgs_det2patches(imgs, dets, ratios, max_instances_per_img=5)
assert patches.ndim == 4 and patches.shape[1:] == (256, 256, 3), f'bad patch shape {patches.shape}'
assert patches.shape[0] == sum(det_meta['n_patch_per_img']), 'patch count / meta mismatch'
print(f'PATCHES OK: {patches.shape[0]} human patches @ 256x256 — ready for the HMR pass.')
if __name__ == '__main__':
main()