Math Reference
All biomechanics computation in SprintLab is implemented in sprintMath.ts — a pure TypeScript module with no React dependencies.
Coordinate System
SprintLab uses screen (pixel) coordinates throughout: the origin is at the top-left corner of the inference frame, increases rightward, and increases downward.
(0,0) ──────────────────────── +x →
│
│ inference frame
│
│
+y
↓| Axis | Direction | Convention |
|---|---|---|
| rightward | increases left→right across the frame | |
| downward | increases top→bottom (screen convention) | |
| Origin | top-left | pixel |
This is the native coordinate system of the pose inference engine. All keypoint positions, marker sites, and CoM pixel coordinates share this space.
Sprint Direction
SprintLab supports both left-to-right (LTR) and right-to-left (RTL) sprints natively.
Default
The direction always initialises to LTR. No inference is attempted before markers are placed — silent auto-detection from raw CoM trajectory was removed because it produced false RTL classifications on LTR videos.
Marker-based inference
When a sprint marker is placed or moved, direction is inferred from marker geometry and compared to the current setting:
Flying Sprint Mode — both Start and Finish markers placed:
Static Start Mode — Start marker placed, first valid CoM frame available:
If the suggested direction differs from the current direction, a confirmation dialog is shown before any change is applied.
Confirmation dialog
When inferred direction differs from the current setting, an AlertDialog appears:
- Confirm — applies the new direction and resets sprint markers
- Cancel — keeps the current direction unchanged
The athlete always has the final say; inference is advisory only.
Manual override
The → LTR / ← RTL toggle in the viewport header shows the current direction and can be clicked at any time. Switching direction via the toggle immediately resets sprint markers (same reset as confirming the dialog).
An amber banner in the CoM tab appears whenever RTL is active and can be dismissed once the user has acknowledged the setting.
What resets on direction change
Whether the change comes from the confirmation dialog or the manual toggle, the following state is cleared so calculations start clean in the new direction:
| State | Reset to |
|---|---|
| Start marker | cleared |
| Finish marker | cleared |
| CoM events | cleared |
| Annotation mode | off |
Pose data, calibration, video, and confirmed sprint-start frame are not affected.
Effect on calculations
Direction is encoded as a sign factor :
Relative displacement past the start line is always non-negative in the sprint direction:
where is the CoM horizontal position in metres at frame , and is the CoM position when it crosses the start line.
Speed and acceleration are the magnitudes of their respective derivatives (, ), so these scalar metrics are direction-independent.
Coordinate indicator
The axis diagram in the bottom-left corner of the viewport shows the sprint axis direction:
- LTR → blue arrow points right (standard screen convention)
- RTL → blue arrow points left (sprint "forward" is left)
The underlying screen pixel coordinate system does not change; the indicator highlights which screen direction is "forward" for the current sprint.
Notation
| Symbol | Meaning |
|---|---|
| A 2D point in inference-frame pixel coordinates | |
Aspect ratio correction factor from calibration (calibration.aspectRatio) | |
| Inference frame width and height in pixels | |
Pixels per metre from calibration (calibration.pixelsPerMeter) | |
| Frame rate (frames per second) | |
| Total number of frames |
Aspect-Ratio Correction
Inference-frame pixel coordinates are not physically square — the model may run at a different resolution or aspect ratio than the original video. Before computing any angle or distance in physical space, both axes are normalised:
This maps every point into a normalised space where distances are proportional to real-world distances. All angle and length calculations below operate on .
Interior Joint Angle — angleDeg
Used for: hip, knee, ankle, shoulder, elbow, wrist.
Given three points , , , computes the interior angle at vertex :
where the vectors are computed in normalised space:
The result is clamped to before passing to to guard against floating-point overflow:
Range: . Returns when the two arms are coincident (i.e. ).
Joint definitions:
| Joint | (vertex) | ||
|---|---|---|---|
| Hip | Knee | Hip | Shoulder |
| Knee | Hip | Knee | Ankle |
| Ankle | Knee | Ankle | Toe |
| Shoulder | Elbow | Shoulder | Hip |
| Elbow | Shoulder | Elbow | Wrist |
| Wrist | Elbow | Wrist | (proxy toe) |
Segment Angle from Vertical — segAngleDeg
Used for: thigh (hip → knee).
Computes the signed angle of the segment relative to the downward vertical (+ points down in screen space):
Convention:
- — segment points straight down (vertical)
- — segment leans right of vertical
- — segment leans left of vertical
Why this convention for the thigh? The thigh angle from downward vertical is a standard biomechanics reference. During sprinting, the thigh cycles from behind the body (negative) to in front of it (positive), making the signed value directly interpretable as drive phase vs. recovery phase.
Segment Inclination from Horizontal — segInclineDeg
Used for: torso (CoM → nose) and shin (knee → ankle).
Computes the unsigned inclination of the segment from the horizontal:
The absolute values of both components mean the result is always in , regardless of which direction along the segment you measure.
Convention:
- — segment is horizontal
- — segment is perfectly vertical
- — segment is tilted from vertical
Why inclination from horizontal for torso and shin?
For these segments, "vertical" is the reference state — an upright torso and a vertical shin at ground contact. Measuring from horizontal means 90° is the reference and deviations from 90° are directly readable as degrees of lean or tilt, which is more intuitive than measuring from vertical (where the reference would be 0°).
Box Smoothing — smooth
A symmetric box (moving average) filter of window width :
where the neighbourhood is:
The boundary condition is shrinking window: near the edges, only the in-bounds indices contribute, so for or . This preserves array length and avoids zero-padding artefacts.
Applied twice (w = 3) before computing velocity, giving an effective triangular (Bartlett) smoothing kernel.
Numerical Derivative — derivative
Central-difference approximation of the first derivative, then smoothed:
Boundary frames use one-sided replication:
The result is smoothed with a box filter () to suppress noise amplification.
Applied twice to obtain:
- Velocity from angles (deg/s)
- Acceleration from velocity (deg/s²)
Null Filling — buildSeries
Keypoints with score are returned as null. Before any smoothing, nulls are filled using forward-fill then backward-fill:
Forward fill:
Backward fill (for leading nulls):
If all values are null (joint never detected), the array is filled with .
Ground Contact Detection
Detection operates on the combined foot -coordinate — the maximum (lowest point on screen) of heel and toe at each frame:
Let and be the maximum and minimum of all valid foot values across the clip. The contact threshold is:
A frame is classified as on ground if .
Gap filling: consecutive on-ground windows separated by fewer than 4 frames are merged (a brief airborne detection inside a contact is treated as noise).
A contact window is retained only if its duration is in the sprint-plausible range:
Distance Scaling
All pixel distances are converted to metres using the calibration scale factor. Horizontal distances are corrected for the video aspect ratio:
For signed horizontal offsets (e.g. foot position relative to CoM):
For 2D Euclidean distances:
Center of Mass Trajectory
The CoM is approximated as the midpoint of the left and right hip landmarks:
The horizontal CoM position in metres at frame :
After smoothing (), the horizontal speed is the magnitude of the numerical derivative:
Acceleration:
Cumulative horizontal distance travelled:
This is a discrete Riemann sum approximating .
Velocity Calculation Modes
SprintLab uses two distinct velocity strategies depending on the selected sprint mode, each chosen to give the most physically meaningful result for that context.
Static Start Mode — Instantaneous Velocity
In static start mode, the athlete accelerates from rest. The velocity displayed at each frame is the instantaneous horizontal speed of the CoM — the same per-frame derivative computed in the CoM trajectory pipeline:
This directly exploits the frame-by-frame pose data to capture the true instantaneous acceleration profile from first movement through maximum speed. Dividing total displacement by total elapsed time (an average) would obscure this profile. Frames where the CoM has not yet reached the start line are masked to zero.
Why not use average velocity here? During acceleration, the athlete's speed changes continuously. An average (total distance ÷ total time) is dominated by the slow early frames and does not reflect the speed the athlete has actually reached at any given moment. Instantaneous velocity shows the true speed at each frame.
Flying Sprint Mode — Zone Velocity + Instantaneous Sparkline
In flying sprint mode, the athlete is at near-maximum speed throughout the measurement zone. The primary metric is the average zone velocity — the standard approach used in fly-zone testing:
Because the athlete has stopped accelerating appreciably, the average across the zone is a reliable proxy for peak speed and matches the measurement convention used in electronic timing gates.
Additionally, an instantaneous velocity sparkline is rendered for the frames within the fly zone, using the same per-frame derivative . This reveals within-zone velocity variation (e.g., slight deceleration near zone exit) and provides qualitative confirmation that the athlete was truly at constant speed — validating the average-zone-velocity reading.
Summary: Flying mode uses zone average as its scalar velocity metric (standard, comparable across athletes and setups) but adds instantaneous sparkline to expose any within-zone speed variation.