Disclaimer: extremely vibe coded. This is for a one-off event. The foundation (video stream capture + rtmlib pose detection) was taken from a previous project - done cleanly, everything on top is quick AI code.
Interactive open-day exhibit: a single webcam runs real-time 2D pose detection (RTMO / COCO-17). Students see their own skeleton mirrored live, then an operator runs them through a 5-event athletic mini-pentathlon for a composite score and athlete title.
Design doc: docs/POSE_PENTATHLON_PLAN.md.
conda create -n pentathlon python=3.10 -y
conda run -n pentathlon pip install -r requirements.txt
conda run -n pentathlon python main.py
Requires a CUDA-capable GPU (onnxruntime-gpu, pinned to 1.20.1).
| Key | Action |
|---|---|
| SPACE | Start circuit (from ATTRACT) / advance / next student |
| N | Skip current activity |
| R | Abort, reset to ATTRACT |
| Q | Quit |
- M1 — Mirror + ATTRACT (done) — live mirrored skeleton + pulsing "STEP IN" prompt
- M2 — Display layer + state-machine spine (done) — UI primitives + screen renderers (instructions / countdown / HUD / transition / results); Circuit state machine; full ATTRACT -> ... -> RESULTS flow walkable
- M3 — Formal
ActivityABC (skipped) — duck typing covers it; see CLAUDE.md - M4 — Real events: High Knees + Vertical Jump (MVP) (done) — hysteresis-based rep counting, hip-centre jump tracking, per-event skeleton highlight, vertical power bars with persistent peak markers. Score endpoints in
config.pylikely need lab-tuning. - M5 — Reaction Wall (done) — two simultaneous target circles in a reachable zone, wrist-keypoint hit detection with tunable
HIT_RADIUS_MULT, hit count scored viacfg.SCORE_MAP["reaction_wall"]. Circuit composition is now driven bycfg.CIRCUIT_ACTIVITIES, so individual events can be commented out for testing. - M6 — Punch Power (done) — per-frame
|horizontal wrist velocity| / arm_length(arm_length is a bend-invariant running mean of upper-arm + forearm). Velocity is the 5-frame MA fromPoseDetector; a deadband zeros sub-jitter motion; live power bar with persistent peak marker. Originally paired with Javelin via a shared displacement-burst detector; Javelin deferred to M8. - M7 — Stick the Landing (done) — replaces the original Javelin event as #5. 3-phase composite: (1) hold a single-leg stand for 3 s → static balance sub-score from multi-keypoint sway across elbows + wrists + hips + knees (flailing arms tank the score), (2) hop sideways to a glowing target ~0.75 leg-lengths away on the standing-leg side → landing-accuracy sub-score from hip-x distance to the target, (3) hold steady for 3 s after landing → landing-stability sub-score. Composite = 0.4 balance + 0.4 land-stability + 0.2 accuracy → 0..1000. Graceful degrade: phase timeouts zero that sub-score and continue. First multi-phase activity — introduces a
_StickPhaseenum + per-phase dispatch as a template for future composite events. - M8 — Polish: instruction images, day leaderboard, optional sound; Javelin if time permits.
main.py— capture/pose loop + circuit dispatchcircuit.py—State,StubActivity,Circuit,build_demo_circuitactivities.py— real event classes:HighKneesActivity,VerticalJumpActivity,ReactionWallActivity,PunchPowerActivity,StickTheLandingActivityui.py— primitives (panel, progress_bar, power_bar, big_digit, text helpers) + screen renderersconfig.py— single source for tracking / UI sizing / state-machine timings /SCORE_MAP
capture.py—CaptureThread(threaded non-blocking webcam capture)pose.py—PoseDetector,OneEuroFilter
Copied verbatim except for two lines stripped from pose.py (a Warp CUDA
sync that's redundant when ONNX is the only GPU consumer).