DEV Community

Cover image for One letter higher: checking my saddle height with a side-view video, in the browser
Dominique Siacci
Dominique Siacci

Posted on

One letter higher: checking my saddle height with a side-view video, in the browser

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

My road bike got a professional fit last year. My home trainer, a Zwift Ride, is set with letters, A to X, and I didn't know whether the letter my saddle sits on was the right one. On Thursday evening I put my phone on a tripod, two or three metres from the bike on the drive side, and pedalled for two minutes. Then I raised the saddle by one letter and did it again.

I'm an amateur cyclist, not a bike fitter. What I wanted was a number for one question, from what a phone video can actually measure.

Saddle Check is a web page. You drop a video of yourself pedalling, filmed from the side. An open pose model, MediaPipe Pose, runs in the browser, on your own computer or phone, and the video is never uploaded. The page gives you:

  1. the knee bend at the bottom of the pedal stroke, stroke by stroke, against the published range for riders measured while pedalling (33 to 43°);
  2. the saddle change in millimetres, and in letters or notches if you give their spacing, that would bring the knee to the middle of that range;
  3. the other angles (hip, trunk, shoulder, elbow, ankle), next to book or manufacturer guidelines, labelled as such: inside or outside, never "good" or "bad";
  4. an animated skeleton on a plain background (never the video picture, unless you ask), the curve of each angle through the stroke, and a report to download.

Who would pay? A bike fit costs from €90 (static) to €399 (full) in the French shops I checked. Apps exist: FramIQ, on iPhone, also analyses the video on the device, for $25 per analysis or $79 a year; MyVeloFit uploads the video to its servers, for $35 or $75 a year. Saddle Check is a prototype, but a paid version could find its place in that landscape, below those prices and with nothing to install: a report to bring to a bike shop, a follow-up after each adjustment, a version for a club that shares indoor bikes. The rider's own device does the computing, so an analysis costs almost nothing to serve, and even a low price would leave a margin.

Demo

The demo uses the page's synthetic rider, so no real person appears in it. To run the page yourself, the README has the three commands that serve it from your own machine; the same synthetic-rider button is there when you have no video at hand.

Code

Saddle Check

Bike fit angles from a side-view phone video, measured in the browser. You drop a video of yourself pedalling; an open-weight pose model (MediaPipe Pose Landmarker) runs inside the page and finds your joints in every frame; the page measures your knee, hip, ankle, trunk, shoulder and elbow angles at every pedal stroke, compares the medians with published ranges, and turns the knee angle into a saddle change in millimetres.

The video never leaves your device. The page and the model are static files; once the model is loaded, the page locks its own network access (see Privacy).

Demo video, with the page's synthetic rider: youtu.be/JUidqkAi33U.

Use it

The page is a set of static files. To serve it from your machine:

npm ci
npm run build   # copies the MediaPipe runtime, downloads the models, checks their SHA-256
npm run serve   # http://127.0.0.1:8080
Enter fullscreen mode Exit fullscreen mode

How

…

Plain JavaScript, no framework, no runtime dependency besides MediaPipe, Apache 2.0, 32 tests. The measuring code (web/core/) is the same for the page and for the scripts that produced the numbers in this post.

How I Built It

Reading every frame, without sending it. The page reads the video frame by frame with WebCodecs, the browsers' decoding API, fed by a small reader for MP4 and QuickTime boxes: each frame is decoded once, in order. My first version asked a <video> element for each frame, which decodes again from the last key frame: 226 s for a 14 s clip, against 36 s now, with the same numbers. Each frame goes through MediaPipe Pose Landmarker, Google's open-weight pose model (Apache 2.0, the "full" variant), which returns 33 body landmarks.

Cutting the network, and proving it. Once the model is loaded, the page adds a security policy to itself that forbids every connection (connect-src 'none'), then checks that it holds: it tries to read a file from its own site and makes sure the browser refuses. A padlock shows in the header. In Chromium's console, that reads: "Connecting to '…/lock-probe.txt' violates the following Content Security Policy directive: "connect-src 'none'". The action has been blocked." Anyone can check it in their own browser.

The crank is the ruler. Millimetres need a scale, and it's in the picture: with cleats, the shoe is fixed to the pedal, and the pedal axle is the only point of the foot that turns in a circle around the bottom bracket. The page searches the frame of the foot (the heel and toe landmarks) for the point whose path is the most regular circle. Its radius in pixels is the crank: 170 mm on my bike.

The right leg. Seen from the side, the model sometimes swaps the left and right labels of the legs. The page follows the leg nearest to the camera by continuity and corrects those swaps (12 to 18 per video in mine).

One pedal stroke. Positions are cleaned (unsure landmarks dropped, a zero-lag 6 Hz low-pass filter), the crank angle splits the video into strokes, top to top, and the angles are read at the bottom. The page keeps the median over clean strokes, with its confidence interval.

Ranges, with their level of evidence. For the knee at the bottom of the stroke while pedalling: 33 to 43° of bend (Millour et al. 2019; Swart & Holliday 2019); that range drives the saddle change. The most quoted range, 25 to 35° (Holmes), is measured standing still with a goniometer; pedalling bends the knee about 8° more. For the hip, the trunk, the shoulder or the elbow, I only found book or manufacturer guidelines: the page shows them without saying "good" or "bad".

The saddle change. The thigh, the shank and the hip-to-ankle line make a triangle. The law of cosines says how much longer that line must get for the knee to reach the middle of the range, and the page turns it into millimetres along the seat post.

I built it this week with an AI coding agent.

Does It Measure Anything?

Five videos at 60 frames per second, tripod and bike taped to the floor: V1, V2 (after getting off and back on) and V4 (saddle set back to its letter) at the same height; V3 one letter higher, 10 mm; S, still, crank down, to compare with an inclinometer.

Video Saddle Knee bend at the bottom (median, 95 % interval) Clean strokes
V1 start 42.3° (41.8 to 42.5) 114 of 133
V2 start 43.0° (42.6 to 43.5) 76 of 92
V4 start 42.8° (42.2 to 43.1) 96 of 115
V3 +10 mm 37.9° (37.3 to 38.2) 122 of 134

Repeatability. Three videos at the same height, one after getting off the bike and one after resetting the saddle: 0.7° between the lowest and the highest.

Sensitivity. One letter higher, the knee bends 4.8° less (95 % interval 4.3 to 5.4). The leg triangle predicted 3.4°. The direction and the size are right. My ankle at the bottom didn't change clearly, but my hip moved 12.4 mm in the picture for a 10 mm saddle change: I may have sat a little differently, or the scale is a few percent off, and these videos can't tell which.

The page called it, this time. On my three starting videos, it suggested raising the saddle by about one letter (11 mm) to reach the middle of the range, 38°. One letter higher, it measured 37.9°.

Reference. Still, crank down, the model reads a knee bend of 33.2°. My inclinometer (the level of the iPhone Measure app held along the thigh, then the shank, read by me, alone on the bike) gives 34°, and the software of last year's fit, made for my road bike, said 34° too. Pedalling, the page measures 42.7°, 9.4° more than still, close to the 8° the studies describe. One thing doesn't match: the thigh and the shank taken one by one differ by 11 and 12° between the model and the inclinometer, turned the same way, which cancels out in the knee angle. One of the two was tilted by about 12°, my readings or the camera's horizon, and these videos can't tell which.

What these numbers don't say. My 0.7° says the page is consistent, not that it's right to 0.7°: the only study of MediaPipe in cycling I found (Bini, Nascimento & Nibali 2024) reports typical errors of 3 to 6° at the knee.

What Went Wrong

I had tuned the threshold that decides "this point of the foot really turns in a circle" on synthetic riders, where the path stays within 2 % of a circle. On my real videos, the foot landmarks the model returns stray from the circle by about a tenth of the crank. The page refused the pedal axle and fell back on the loop of the ankle: no millimetres, and a badly placed bottom of the stroke, with a knee at 39° instead of 42°.

Looking at the numbers, I saw that this scatter changes from one stroke to the next, while a foot sliding on a pedal bends the loop the same way every time. So the page now averages the pedal path angle by angle over dozens of strokes before judging the circle, and it still refuses a synthetic foot sliding 70 mm. The radius it finds still varies by 5 % from one video to another, so the page gives its millimetres to within 10 %. The angles don't depend on the scale.

Why Does Open Innovation Matter?

An open model can go where the video is. Because the model is open, the page can run it on the rider's own device instead of a server, so the video can stay there. The page also switches off its own network once the model is loaded: the browser console shows it, and the code shows the rest.

An analysis costs almost nothing. The model runs on the rider's device and the page is a set of static files: there is no computing server to pay for, which is what would keep a paid version cheap.

No lock-in. The code is under Apache 2.0, the report is an HTML file you keep, and the ranges are listed with their sources: a bike shop can check where every number comes from.

Prize Categories

Best Use of DigitalOcean. A DigitalOcean droplet ran the page itself in headless Chromium for the many automated tests: every number in this post comes from those runs on my five videos, and the demo video was recorded there too. The repository has the scripts to do it again.


So, will I raise my saddle? The page's answer is about one letter: at my current height, my knee bends 42.7° at the bottom of the stroke, inside the published range but near its top; one letter higher (V3), it bends 37.9°, right in the middle. I'm not sure yet that I'll do it: sitting still, my trainer already matches last year's fit, and 42.7° is still inside the range.

How did you set your saddle height, and would you move it by a centimetre because a video told you to?

Top comments (0)