Skip to content

v0.1.0 — suggested moments you can actually watch

Choose a tag to compare

@ralyodio ralyodio released this 10 Aug 07:25
· 34 commits to master since this release
c98181f

First tagged release. The app went from "a 200MB upload dies at 70% with no error" to "suggested moments you can watch, with the detector's boxes drawn on them".

Uploads

Streaming multipart parsing, so peak memory is one chunk rather than the file. Resumable across dropped connections and server restarts, with a real progress bar and full CRUD. A stall is distinguished from a slow connection and says which it was.

The original failure was two things at once: Node's 300s requestTimeout destroying any upload slower than ~700kB/s, and a response written before the body finished, which reached the browser as a bare ERR_HTTP2_PROTOCOL_ERROR.

Detection

It never worked. Every preset asked for an inference size the model could not accept — 512, 768, 1280 against a graph with a fixed [1,3,416,416] input — so detection failed on every run since the first commit. The model now has the final say on its own input size.

  • Tiled inference for objects too small to survive being squeezed into that input. Measured on real footage: ball confidence 0.54 full-frame against 0.89 from a tile, and frames the full-frame pass missed entirely coming back at 0.88. End to end, 3 ball tracks over 40 seconds became 14. Opt-in as the thorough preset, because it costs 5 inferences per frame.
  • Sport-specific models are usable at all: a model may declare its own classes, its own head format (YOLOv8 as well as YOLOX), and its own pixel convention. All three fail silently when wrong — feeding raw 0-255 pixels to a model expecting 0-1 produced 700 "basketball" detections at 1.00 confidence in a frame of an empty gym.
  • Thread pool sized from the cgroup, not the host. Oversubscribing was measured at 56ms/frame against 194ms/frame.
  • Tracks are replaced, not appended. A project analysed six times held six overlapping copies of every track; 9,961 stored tracks collapsed to 2,790 distinct ones.

Scoring

  • An athlete can be bound to a track. updateAthlete had always accepted a focalTrackId and no surface ever passed one, so the three signals carrying most of the weight were dark on every run ever made — capping every window at 0.087 against a 0.35 threshold.
  • An athlete is more than one track. Tracking splits a child into fragments; binding one followed 24 seconds of a five-minute game. Selecting every fragment took a real game from 0 suggested moments to 5.
  • The athlete survives re-detection. Track ids do not, but positions do, so bindings are re-matched by box overlap instead of asking the user to identify their child a fourth time.
  • Signals that cannot be measured no longer divide the score. No hoop in frame and no audio track is not evidence of a dull moment, and charging every window for it held genuine possessions below the threshold.

Answering "why did this produce nothing"

A zero-moment run now reports what was seen, how long the longest track was, whether an athlete was identified, which signals had data, the best window score, and — the number that separates three unrelated failures — the highest reachable score. When the threshold was unreachable it says so, rather than implying the footage was dull.

Watching the results

  • Each suggested moment has a player that plays exactly its span, streamed with Range support so seeking does not mean downloading everything before it.
  • Detection boxes drawn over that player: the focal athlete bright and labelled, ball, hoop and referee in their own colours, everyone else quiet. With a sentence saying the same thing in words.
  • Rendering is a job, so it reports progress and failures where the person who asked is looking, instead of writing them to the server's stderr and nowhere else.
  • Analysis has a live SSE log, stop/replay/remove, and a footage picker.

Infrastructure

  • A service worker that pinned browsers to stale JS.
  • watchPatterns that let merged fixes sit undeployed.
  • Jobs interrupted by a restart are failed with a reason instead of claiming to run for ever.
  • CI runs 462 tests on every push, including the detector against real weights — which had never run once.

Known limits

  • The shipped COCO model cannot see a hoop; a sport-specific model is required, and selecting one is currently a global environment variable rather than a per-project choice.
  • Nothing extracts audio, so the audio_spike signal has never fired.
  • Every deploy kills any running analysis. That loss is now visible; preventing it needs detection to outlive the web process.
  • Tiling roughly doubles player track fragmentation.