Back to Stride Lab
Method engine v1.0.0

Method and limits

What is measured, exactly how, what the error is, and what this cannot do. The last of those is the longest section, which is the correct proportion for a tool that estimates biomechanics from one camera.

What is and is not claimed

What this does

  • Estimates 2D body landmarks per frame with a published pose model, then computes joint and segment angles, gait event times and derived measurements from them.
  • Reports every measurement with a 95% interval built from frame-rate quantization, the disagreement between independent event detectors, and the stride-to-stride spread.
  • Suppresses measurements it cannot support — contact time below 60 fps, frontal-plane measurements on a side view, anything whose landmarks are missing for more than a quarter of the relevant frames.
  • Compares each measurement against a stated range with a stated source and a stated evidence strength, and shows you all three.
  • Runs entirely in your browser.

What this does not do

  • It is not a medical device. It does not diagnose, treat, or prevent anything.
  • It does not predict injury. Where the literature links a measurement to injury risk, that is an association observed in a population. It supports the statement "pelvic drop is associated with injury risk in the literature". It does not support "your injury risk is X".
  • It is not lab-grade and is not described as such anywhere. No claim of "lab quality" or "clinically validated" appears in this app, because no validation study backs one.
  • It does not measure force. Nothing here is a loading rate or an impact force, and no metric is labeled as one — you cannot get force from video.
  • It does not measure pronation. See the rearfoot proxy in the limitations below.
  • It is not 3D. It is a planar analysis from one camera, and every angle inherits that.

The pipeline

Thirteen stages, from a file to a report. Stages A to C are the browser layer; D to L are a pure function with no access to the DOM, the network or a clock, which is what makes it testable against signals whose answers are known.

  1. A
    Ingest and validate — duration, resolution, rotation metadata, and the frame rate measured from the decoded timestamps rather than read from the container, because container metadata about frame rate on phone video is routinely wrong.
  2. B
    Frame extraction and orientation — WebCodecs over a sample table parsed from the MP4, giving the exact presentation timestamp of every frame. Never currentTime seeking: frame-accurate seeking is not guaranteed and every timing measurement here is a difference between two frame timestamps. Where WebCodecs is unavailable, playback with requestVideoFrameCallback is used and the result is flagged as reduced timing precision.

    Frames are then rotated to the orientation you filmed in. A phone recording in portrait stores landscape pixels plus a quarter turn in the container's display matrix, and a decoder hands back the pixels without applying it — so the pose model would be given a runner lying on their side, and would produce a confident, wrong skeleton rather than failing. A mirror flag, which front cameras often add, is undone for the same reason: a flipped frame swaps your left and right, and every per-side measurement would then be reported for the wrong leg.
  3. C
    Pose estimation — MediaPipe Tasks Vision PoseLandmarker, BlazePose GHUM, 33 landmarks. Chosen for the keypoint set, not the speed: it has heel and toe, and foot-strike angle cannot be computed without both.
  4. C′
    Plausibility gating — a pose estimator asked for a landmark it cannot see does not decline; it guesses, and often reports a comfortable confidence while doing so. The far leg of a runner filmed at an angle is the standard case. Bones do not change length, so every rigid segment is checked against its own median across the clip and any frame that disagrees by more than 40% is discarded — a check that needs no reference to how sure the model claimed to be.
  5. D
    Person selection — several people are detected, tracked by overlap and centroid distance through up to eight frames of occlusion, and one is chosen. If two candidates each appear in more than 40% of frames the app stops and asks, rather than guessing: guessing wrong produces a normal-looking report about somebody else.
  6. E
    Signal conditioning — in this order and no other: convert to a y-up frame; gate out samples below 0.5 visibility as missing, not zero; interpolate gaps of three frames or fewer and leave longer ones missing; a Hampel filter to remove single-frame limb swaps before smoothing, because a low-pass smears an outlier across its neighbors rather than removing it; then a zero-phase Butterworth.
  7. F
    View and direction — sagittal or frontal from the projected shoulder width relative to torso height; a view between the thresholds is reported as oblique and flagged. Direction of travel comes from the foot orientation — the toe is in front of the heel for every runner moving forward — and not from body velocity, which is zero on a treadmill.
  8. G
    Scaling — pixels to meters from your height and Winter's segment-length fractions, measured on your own thigh and shin, per frame. A single global scale makes vertical oscillation appear to change as you cross the frame, which is indistinguishable in the output from a real change in technique.
  9. H
    Gait events — five independent foot-strike detectors and three toe-off detectors, clustered and voted. Detailed below.
  10. I
    Stride segmentation — strides that violate any sanity constraint (alternating feet, stance 100–400 ms, stride 500–1100 ms, duty factor 0.20–0.50) are discarded, as are the first and last stride of every clip.
  11. I′
    Is this measurable at all? — three capture conditions produce numbers that are meaningless rather than imprecise, and each is refused rather than approximated. A camera not square to the plane of motion measures every angle in the wrong plane, so those are capped below the confidence at which anything is scored. A runner who does not travel across the frame — a treadmill, or a camera that followed them — has no displacement to measure, so step length, stride length and speed are withheld. And a limb whose length changed was never tracked.
  12. J
    Measurements — per stride, then a 10% trimmed mean with the standard deviation across strides. Left and right always separately, plus the asymmetry index. This is also where the fourteen-segment inertial model produces the whole-body center of mass and the spring-mass stiffness estimates.
  13. K
    Scoring — against stated ranges, weighted by evidence strength, and never computed at all for a measurement below medium confidence.
  14. L
    Recommendations — deterministic rules over the scored measurements. No language model, no classifier. Capped at three findings.
  15. M
    Render — overlay, charts, report.

The filter, and the correction that is usually missing

Trajectories are low-pass filtered with a 2nd-order Butterworth applied forward and then backward, which gives a 4th-order magnitude response with zero phase lag. Phase lag is disqualifying here: a lagged signal shifts every gait event by the same amount, so the bias survives averaging over any number of strides.

Applying a filter twice narrows the passband, so the design cutoff has to be raised to land on the intended effective cutoff:

C = (2^(1/n) - 1)^(1/(2·order))     n = 2 passes, order = 2
C = (√2 - 1)^(1/4) ≈ 0.8022
designFc = effectiveFc / C ≈ 1.247 · effectiveFc

So a 12 Hz effective cutoff is designed at 14.96 Hz. The default is 12 Hz rather than the 6 Hz conventional for walking, because impact transients and fast distal-segment motion in running live above 6 Hz. The validation suite checks this directly: a 2 Hz plus 30 Hz sum at 240 fps must come back with the 2 Hz component within 1% of its amplitude and no sample of lag, and the 30 Hz component below 5%.

Detecting foot strike and toe-off

Everything with a unit of milliseconds depends on these two instants, so they get more machinery than anything else here.

Two facts shape the design. First, against force-plate ground truth and using marker data, the best published kinematic methods land at roughly 22–25 ms for foot strike and about 5 ms for toe-off (Fellin et al. 2010). Pose estimation adds error on top of that, so those are a floor, not a target — and foot strike is four to five times harder than toe-off, so it dominates the contact-time error budget.

Second, and more important: heel-based strike detectors carry a strike-pattern-dependent offset. On a forefoot striker the heel does not reach the ground until roughly 70 ms after the foot does. A detector that keys off the heel therefore places every forefoot striker's contact 70 ms late and shortens their measured contact time by the same amount — a bias applied to one population and not the other, falling exactly on the distinction this app exists to measure. Measured on the synthetic forefoot runner in the test suite before the pattern-independent detector was added: 72 ms mean absolute error, against 6 ms for the rearfoot runner. After: 4 ms and 0.1 ms.

Foot-strike detectors, and the weight each carries in the vote
MethodWhat it keys offWeightHow it fails
M0The lower of heel and toe reaching the ground, taken at the onset of the plateau1.3Needs both foot landmarks; strike-pattern independent by construction
M1Heel vertical position minimum, plateau onset1.0Late on forefoot strikers
M2The heel's descent being arrested — first upward velocity crossing after a real descent1.0Noisy under motion blur; same bias as M1
M3Toe vertical position minimum, plateau onset0.8Late on rearfoot strikers; this is the one that carries forefoot strikers
M4The foot settling to its stance velocity — not to zero, because on a treadmill the planted foot travels at belt speed0.6Blunt; weakest voter
M5Pelvis vertical velocity minimum (Milner & Paquette 2015)0.9Small constant lag, but strike-pattern independent

Toe-off is searched only inside the plausible stance window after a detected strike, using peak knee extension (1.2), the toe starting to rise (0.8) and peak forward foot acceleration (0.6). That window is not an optimization: the knee reaches full extension twice per stride, once approaching toe-off and again in terminal swing, and the two peaks are the same height. No amount of prominence ranking separates them; restricting the search does, by construction.

Candidates within ±40 ms are clustered, and the event time is the weighted median of the cluster followed by the weighted mean of whatever survives a window around it — the median for its resistance to a detector firing in the wrong place, the mean because once the outliers are gone it uses all the information. The dispersion of the cluster is carried forward as that event's own uncertainty, and a stride whose events disagree by more than 25 ms is excluded.

Independently of all of this, cadence is also estimated from the dominant frequency of pelvis vertical motion — the pelvis rises and falls once per step, so that frequency is the step frequency, arrived at without looking at a single event. If the two disagree by more than 5%, the event detection is wrong and the report says so instead of printing a number.

The error budget

Frame-rate quantization dominates every timing measurement. Ground contact lasts about 230 ms. At 30 fps a single frame is 33 ms, so a third of a contact time can be quantization alone.

CaptureFrame period±1 frame on contact timeRelativeWhat this app does
30 fps33.3 ms±33 ms±14%Contact time, flight time and duty factor are suppressed. Cadence and angles are shown.
60 fps16.7 ms±17 ms±7%Shown with an explicit interval
120 fps8.3 ms±8 ms±3.6%Shown with an interval
240 fps4.2 ms±4 ms±1.8%Full precision
below 30 fpsRejected.

The reported interval combines three independent contributions in quadrature:

σ_quant  = T/√12 per event, so T·√(2/12) for an interval between two
σ_method = the dispersion of the detectors that voted for each event
σ_stride = the standard deviation across strides, divided by √n

interval = 1.96 · √(σ_quant² + σ_method² + σ_stride²)

Pose-estimation error is not in that figure. There is no honest way to put it there without a validation study, and the intervals shown are therefore optimistic by an unknown amount. Treat them as the lower bound on the uncertainty, not the whole of it.

Scaling error propagates linearly into every distance. If the anthropometric scale and the pose model's own metric-space landmarks disagree by more than 20%, every length measurement is downgraded to low confidence and the report says why.

The body model

Twenty-five landmarks are tracked and fourteen body segments are built from them. The landmarks come from the pose model; the segments, and the whole-body center of mass that follows, are constructed here.

Landmarks

Nose, both ears, both eyes, shoulders, elbows, wrists, a hand center from the finger landmarks, hips, knees, ankles, heels, big toes, and — where the keypoint set has one — the lateral forefoot. From these, four more points are derived: the pelvis and neck (the two ends of the trunk segment), the head center at the ear midpoint, and mid-trunk.

The ears matter more than they look. They are where Winter places the center of mass of the head-and-neck segment, they survive a profile view far better than the eyes, and they are what makes head position and head stability measurable at all. The hand center is the mean of the finger landmarks rather than the wrist, so a rotated forearm is not mistaken for a hand crossing the midline.

Where a keypoint set genuinely lacks a landmark, the measurements that need it report themselves unavailable and name the missing landmark. The default backend has no lateral forefoot point, so the foot is a line rather than a plane and the foot progression angle cannot be formed — that is stated rather than approximated.

Segments and the center of mass

Fourteen segments — head and neck, trunk, and upper arm, forearm, hand, thigh, shank and foot on each side — each carrying Winter's mass fraction and center-of-mass location. The fractions sum to 1.000 and a test asserts it. The mass-weighted mean of the segment centers is the whole-body center of mass, and it is drawn on the annotated video as a crosshair.

The center of mass is not the pelvis, and the difference is the point. Most video tools report the vertical movement of a hip landmark and call it vertical oscillation. The swinging limbs move opposite to the trunk and partly cancel its rise and fall, so the center of mass moves measurably less — on the synthetic runner, about 7.4 cm against the pelvis's 8.5 cm. Since vertical oscillation of the center of mass is the best-evidenced technique variable available to this app, it is worth measuring the thing rather than a proxy for it. Both are reported, and the pelvis one says which it is.

Stiffness, from a spring-mass model

Vertical and leg stiffness are estimated by the method of Morin et al. (2005), which needs only contact time, flight time, body mass, running speed and leg length — all of which this app already has, and none of which requires a force plate:

Fmax  = m·g·(π/2)·(tf/tc + 1)
Δy    = −Fmax·tc²/(m·π²) + g·tc²/8      center-of-mass drop during contact
Kvert = Fmax / |Δy|
ΔL    = L0 − √(L0² − (v·tc/2)²) + |Δy|  leg compression
Kleg  = Fmax / ΔL

Fmax is an output of the model, not a measurement, and this app never reports it as one. You cannot measure ground reaction force from video. The sine-wave force trace the method assumes is a modeling assumption that happens to predict stiffness well; it is not a force sensor. Stiffness is also reported only when body mass is entered — substituting a population average for somebody's own mass would be inventing the answer, so the measurement is withheld and says why.

What the evidence supports

Van Hooren et al. (2024) pooled the observational literature on running biomechanics and running economy. It is the most useful single source this app has, and it is uncomfortable reading for a running-form tool, so it is reproduced here rather than summarized selectively.

Associations with running economy, pooled (Van Hooren et al. 2024)
VariablerEffectDirection
Vertical oscillation0.35moderate, significantless is better
Vertical stiffness−0.31moderate, significantmore is better
Leg stiffness−0.28moderate, significantmore is better
Cadence−0.20small, significantmore is better
Ground contact time−0.02trivial, not significant
Duty factor−0.06trivial, not significant
Stride length0.12trivial, not significant
Foot-strike patternnot significant
Knee flexionnot significant
Trunk leannot significant
Shank angle at contactnot significant
Brakingnot significant

And the number that matters most: taken in isolation, these variables explain 4–12% of the differences in running economy between people. Nearly everything that makes one runner more economical than another is not visible in a video of their technique. Any tool of this kind that leaves you with the impression otherwise — including this one, if you read only the dashboard — has misled you.

What was changed because of it

  • Duty factor was described in this app as "one of the better-evidenced technique correlates of running economy". It is not. Its band now cites this review, is weighted at the lowest evidence level, and says plainly that the association is trivial and non-significant.
  • Ground contact time carries the same correction.
  • Center-of-mass oscillation, vertical stiffness and leg stiffness were added, because they are the variables with the strongest support and all three were computable from what was already being measured.
  • The key-measurement panel is now ordered by evidence rather than by familiarity.
  • In every case, the DIRECTION of a band is what this review supports. The band EDGES — the numbers that decide whether your value is called typical — are still not sourced.

Every measurement, defined

Generated directly from the metric catalog the app itself uses, so a measurement cannot appear in a report without also appearing here, defined.

The reference ranges

Ranges are speed-conditional where the target genuinely moves with speed. Cadence is the obvious case: a fixed 180 steps-per-minute target is folklore — it comes from a count of elite athletes racing — and applying it to somebody running at 2.8 m/s gives bad advice. Where a speed is not known, a speed-conditional range refuses to resolve rather than picking the middle one, and the measurement is reported unscored.

Read this before reading any range below. Most of these ranges cite "no primary source traced". They are values in common use for adult recreational-to-trained runners at easy-to-moderate pace, and they have not been traced to a study in this build. They are scored at the lowest evidence weight for exactly that reason. Replacing them with sourced, speed- and sex-conditional bands is the single highest-value improvement available to this app, and until that happens the ranges are orientation rather than targets.

Validation status

This app has not been validated against force plates, an instrumented treadmill, or motion capture. No accuracy figure derived from human subjects is claimed anywhere in it, because none has been measured. What follows is what has been tested, stated precisely so it is not mistaken for more than it is.

What has been tested

The engine is verified against a synthetic runner: a parametric, physically consistent 2D model whose cadence, contact time, duty factor, step length, trunk lean and foot-strike angle are known exactly because they were prescribed. Its pelvis follows a half-sine through contact and an exact free-fall parabola through flight, matched for velocity continuity, so the pelvis arrives at contact with its most negative vertical velocity and reaches its lowest point at mid-stance — as a real one does. Injectable keypoint noise, dropout and frame-rate resampling are all part of it.

Mean absolute error against synthetic ground truth, 6 s clips, no injected noise
CaptureStrike patternFoot strikeToe-off
60 fpsrearfoot2.1 ms6.5 ms
60 fpsmidfoot1.7 ms7.3 ms
60 fpsforefoot3.2 ms35.9 ms
120 fpsrearfoot0.5 ms3.1 ms
120 fpsmidfoot0.1 ms3.1 ms
120 fpsforefoot4.0 ms3.1 ms
240 fpsrearfoot0.1 ms2.7 ms
240 fpsmidfoot0.3 ms2.7 ms
240 fpsforefoot4.2 ms2.7 ms

These numbers are not an accuracy claim about real running, and reading them as one would be a serious mistake. They measure whether the geometry, filtering, event detection and metric mathematics are correct given perfect landmarks. They say nothing whatsoever about the error the pose model contributes on a real video, which is the larger term. A number like "0.1 ms" is evidence that the arithmetic is right, not that the app can time your foot strike to a tenth of a millisecond.

What the suite covers

  • 104 automated checks, run with node test/run.mjs, no dependencies.
  • Container orientation, against a hand-built MP4 whose display matrix is known exactly: all four quarter turns, both track-header versions, the display-dimension swap, and a mirrored source that must not read as a rotation.
  • The refusals: a treadmill clip labeled overground must not invent a speed, an oblique camera must not let a planar angle be scored, and an implanted impossible limb must be discarded while a clean clip survives untouched.
  • The inertial model: segment masses summing to a whole body, the center of mass moving less than the pelvis, a lost hand not deleting mass, and the spring-mass stiffness reproducing hand-checked values from the published equations.
  • Filter design, including the dual-pass cutoff correction, and an explicit test that a single causal pass does lag while the forward-backward pass does not — otherwise the zero-phase test proves nothing.
  • A coordinate-convention regression test: a runner leaning forward must produce a positive trunk lean, in both directions of travel. This one test catches the most damaging class of bug in the system.
  • Mirror invariance: a right-to-left clip must produce the same numbers as a left-to-right one.
  • Every measurement recovered from prescribed ground truth, at 30, 60, 120 and 240 fps.
  • Suppression rules, confidence propagation, and the error-budget arithmetic against the table above.
  • Data integrity: every reference range resolves to a real reference entry; every rule resolves to real exercises; every unsourced range is labeled unsourced and weighted lowest.
  • Golden values, pinned twice — against the engine's own committed output and against physical truth, because a golden file alone will happily preserve a bug forever.

What would be needed for a real accuracy figure

  1. Ten or more clips spanning rearfoot and forefoot strikers, treadmill and overground, at 60, 120 and 240 fps, with foot strikes and toe-offs hand-labeled frame by frame.
  2. Better: simultaneous force-plate or instrumented-treadmill ground truth.
  3. Mean absolute error and Bland–Altman limits of agreement for foot strike, toe-off, contact time and cadence, and a confusion matrix for strike pattern.
  4. Splitting by subject, never by clip. A random clip split overstates accuracy by a wide margin.
  5. Publication of the results here, including the bad ones.

The learned gait-phase model

None ships. The specification this app was built to describes a small temporal convolutional network for per-frame stance segmentation, and the complete inference and fusion path for it is implemented — it is one more weighted voter in the event detection above, at weight 1.5 — but no model file is included, because training one honestly needs running video with force-plate ground truth split by subject, and none was available. The analysis metadata records stage2: not-shipped for that reason, so a result can never imply a capability this build does not have. Quoting somebody else's published accuracy as though it were ours would have been the dishonest alternative.

What was explicitly rejected: a black-box model that outputs a "running form score". There is no labeled ground truth for good form, the output would be unauditable, and explainability is the entire product.

Limitations

Written to be the section a biomechanist would not object to.

It is two-dimensional

Every angle here is a projected angle in the image plane. A joint that moves out of that plane is measured shorter than it is, and rotation about the long axis of a segment is invisible. This is not a small caveat on a large claim: it is the nature of the measurement. A camera that is not square to the plane of motion adds a systematic error to every angle, which is why an oblique view is detected and flagged rather than silently analyzed.

Pose estimation is the dominant error term, and it is not quantified here

Stenum, Rossi and Roemmich (2021) is the paper to read before believing any number in this app. Pose-estimation-based gait analysis has real, measurable disagreement with marker-based reference systems, and the foot landmarks — heel and toe, the two this app most depends on — are the least reliable in the set, worst under exactly the condition running produces: motion blur at high foot velocity. The confidence gating and the multi-method vote exist to mitigate that. They do not remove it, and the intervals shown do not include it.

Scaling depends entirely on the height you typed

Pixels become centimeters through your standing height and Winter's segment-length fractions. Those fractions are population averages: an individual with proportionally long shins is scaled wrongly, and so is anybody who mistyped their height. A 10% height error is a 10% error in every distance — stride length, vertical oscillation, overstride, step width — and it is invisible in the output because everything scales together and stays internally consistent.

There is no force here at all

Vertical loading rate, impact peak, ground reaction force: none of these can be computed from video, and none is reported. Where something like loading is discussed it is a rank ordering of kinematic proxies, never a force, and it is never labeled as one.

The rearfoot measurement is a proxy and a weak one

Rearfoot eversion — what people mean by pronation — needs markers on the shoe heel counter and the shank. A single rear-view camera without them cannot resolve it. What is reported is the alignment of the heel under the knee, labeled a proxy, capped at low confidence, and never called pronation. It was retained rather than dropped so that a reader looking for pronation finds an explanation of why it is absent rather than nothing at all; if that trade turns out to mislead more than it informs, dropping it entirely is the right call.

The frontal-plane knee angle is a projection, not valgus

The frontal-plane knee projection angle is the departure from a colinear hip–knee–ankle in the image. It correlates with, and is not, true knee valgus. Hip rotation moves it without any change at the knee.

Reference ranges are the weakest part of this app

Most cite no traced primary source, are not conditioned on sex or training level, and are based on adult recreational-to-trained runners at easy-to-moderate pace. If you are outside that description — a junior, a masters athlete, a sprinter, somebody returning from injury — they may not describe you at all. They are scored at the lowest evidence weight and shown with their source so the weakness is visible rather than implied.

A single clip is a single day

Running technique varies with fatigue, footwear, surface, speed and how you felt that morning. One clip captures one moment. An asymmetry seen once is a hypothesis; the same asymmetry across several clips on different days is a finding. The comparison view refuses to call a change an improvement when it is smaller than the two intervals combined.

Cause and effect run both ways

A measurement outside a typical range is a description, not a fault, and changing it is not automatically an improvement. Technique adaptations redistribute load rather than removing it: raising cadence shortens the stride and reduces load at the knee while increasing it at the ankle. Shorter ground contact is not universally more economical (Lussiana et al. 2019). The cues in this app are suggestions to try, not corrections to apply.

Privacy

The video, the extracted landmarks and every derived number are processed in your browser and stored in this browser on this device, in IndexedDB. There is no account, no server and no upload — this app is served as static files from GitHub Pages, which has nowhere to put a video even if it wanted one.

  • Outbound requests. Two, and only at the moment you first analyze a clip: the pose model weights and its WebAssembly runtime, fetched from a public CDN and then cached. Those are downloads to your device. Nothing is sent from it.
  • Analytics. None in this app.
  • Share links. The summary travels after the # in the URL. Browsers never send a fragment to the server, so a shared link reaches whoever you send it to and nobody else — not GitHub, not any log.
  • Deleting it. Clearing site data for this domain removes everything. Which also means clearing site data loses everything: export a bundle if you want to keep it.
  • Offline. After one visit the app works with the network switched off. The model is cached, and there was never anything to send anyway.

References

Each entry states what this app actually takes from it, rather than being cited for atmosphere.

Back to Stride Lab · All E-Labs apps