pose-form
v0.1.0
Published
Exercise form analysis and rep counting from pose landmarks. Zero dependencies, bring your own landmark source.
Maintainers
Readme
Exercise form analysis and rep counting from pose landmarks. Pure TypeScript, zero runtime dependencies - no camera, no ML runtime of its own.
Why · Install · Quick start · What's in the box · Supported movements · Design notes · Limitations · API
You bring 33 BlazePose-style landmarks from wherever you like - MediaPipe Tasks Vision on the web, a native MediaPipe binding, TensorFlow.js MoveNet, a recorded file — and this library turns that stream into reps, phases, depth, form faults, and spoken cues.
flowchart LR
L["33 landmarks<br/>per frame"] --> S["One Euro<br/>smoothing"]
S --> M["20 form<br/>metrics"]
M --> R["rep state<br/>machine"]
R --> E["rules +<br/>cue selector"]
E --> O["reps · phases · depth<br/>one cue at a time"]Why
Rep counting and form checking are usually welded to one camera stack, so they die when the stack changes. Here the analysis is a plain function of landmarks. It runs in a browser, in React Native, in Node, and in a unit test with no device attached.
Install
npm install pose-formQuick start
import { PoseAnalyzer, matchRuleSet } from 'pose-form';
const ruleSet = matchRuleSet('barbell back squat');
if (!ruleSet) throw new Error('No rules for that movement yet');
const analyzer = new PoseAnalyzer({
ruleSet,
// frameWidth / frameHeight. Without it, angles on a non-square frame are wrong.
aspectRatio: 720 / 1280,
});
// Per frame, from your landmark source:
const update = analyzer.push({
timestampMs: performance.now(),
landmarks, // 33 points of { x, y, z, visibility }, normalised 0..1
});
if (!update.tracked) {
// Body not fully in frame. Say so; do not report faults from bad data.
} else {
console.log(update.reps, update.phase, update.depth);
if (update.cue) speak(update.cue.text);
}At the end of a set:
import { localFormReview } from 'pose-form';
const summary = analyzer.summary();
const issues = analyzer.issues();
const review = localFormReview('Barbell Back Squat', summary, issues);
// { overallScore, verdict, summary, issues, strengths, drills, safetyFlag }localFormReview is deterministic and offline. If you also send the clip to a vision model, use this as the fallback when that call fails, so a user never loses their feedback.
What's in the box
| Module | What it does |
| --- | --- |
| Landmark helpers | Named indices for all 33 BlazePose points, visibility gating, dominant-side detection, and a tracking-quality score. isFrameUsable rejects a frame unless the core joints are individually visible, not merely good on average - one hidden hip is enough to invent a fault. |
| Smoothing | A One Euro filter per coordinate: low lag when moving fast, low jitter when still. A fixed low-pass filter cannot do both. |
| 20 metrics | Knee/hip/elbow/shoulder angles, torso lean and pitch, shin angle, body line, hip sag, knee separation (valgus), stance width, shoulder and hip tilt, left/right symmetry, neck angle, wrist stack, elbow flare, squat depth, heel lift. |
| Rep counting | A state machine over the primary joint angle with hysteresis and a minimum rep duration, so jitter at the turnaround cannot double-count. Emits top / descent / bottom / ascent, live depth 0..1, and a RepEvent per rep carrying eccentric and concentric milliseconds, range of motion, peak depth, and whether it counted as full or partial. |
| 12 rule sets | One per movement pattern below. Each rule has a severity, a short spoken cue, and a longer written fix. |
| One cue at a time | The cue selector holds a fault for ~2.5s before speaking and will not repeat it for ~6s. Stacking three corrections on a lifter mid-set makes their form worse, not better. |
| Isometric holds | Plank and similar use a hold timer instead of a rep counter. |
Supported movements
Camera is the placement the rules were written for — the wrong angle produces phantom faults.
| Movement | Rule set id | Camera | Mode |
| --- | --- | --- | --- |
| Squat | squat | either | reps |
| Push-up | pushup | side | reps |
| Hip hinge | hinge | side | reps |
| Lunge | lunge | either | reps |
| Overhead press | overhead_press | side | reps |
| Biceps curl | curl | side | reps |
| Row | row | side | reps |
| Lateral raise | lateral_raise | front | reps |
| Glute bridge | hip_extension | side | reps |
| Crunch | trunk_flexion | side | reps |
| Pull-up | vertical_pull | front | reps |
| Plank | plank | side | hold (timer) |
Design notes
- Bring your own landmarks. No camera, no wasm, no model download, no peer dependency on MediaPipe.
- Aspect ratio matters. Landmarks are normalised per axis, so a 9:16 frame squashes every angle unless you pass
aspectRatio. - Faults are suppressed when tracking is poor rather than reported with a shrug.
- Coordinates follow the MediaPipe convention:
x/ynormalised to the frame,yincreasing downward,zdepth relative to the hips.
Limitations - read this
[!WARNING] Thresholds are heuristics tuned by hand, not clinically validated. They encode common coaching cues, not medical truth.
- A single camera cannot see rotation, load, or intent. Depth from a bad angle is a guess.
- 2D landmarks mean anything toward or away from the camera is weakly observed.
- The rule sets cover common barbell, dumbbell and bodyweight patterns. Olympic lifts, gymnastics and machine work are not modelled.
[!CAUTION] This is not medical advice and not a substitute for a coach. Do not use it to clear anyone to train, to diagnose an injury, or as the only safety check on a heavy lift. If something hurts, stop and see a professional.
API
| Export | Purpose |
| --- | --- |
| PoseAnalyzer | The pipeline. push(frame), summary(), issues(), repEvents, reset() |
| RepCounter, HoldTimer | The state machines, usable on their own against any scalar signal |
| RuleEvaluator, CueSelector | Fault detection and cue throttling |
| matchRuleSet, getRuleSet, listRuleSets, findRuleByCode | Rule set lookup |
| METRICS, readMetric, readAllMetrics | The metric library |
| LandmarkSmoother, OneEuroFilter | Smoothing |
| localFormReview | Deterministic offline scoring |
| LANDMARK, point, sided, trackingQuality, isFrameUsable | Landmark helpers |
Contributing
New rule sets are the most useful contribution. A rule needs a metric, the phases it applies to, a threshold, a severity, and cue/fix text a lifter would actually understand. Open an issue with the movement and the cue before writing code.
Licence
MIT. See LICENSE.
