BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A lightweight computer-vision coaching pipeline with health monitoring for the
Project Overseer (a weekly automated reviewer that reads
overseer-status.json to tell whether the pipeline is healthy-but-idle or
broken).
It ships with two interchangeable sport domains — volleyball and
martial arts — so you can point it at whatever footage you're actually
recording (see Switching sports).README 전문 보기 ↗A lightweight computer-vision coaching pipeline with health monitoring for the
Project Overseer (a weekly automated reviewer that reads
overseer-status.json to tell whether the pipeline is healthy-but-idle or
broken).
It ships with two interchangeable sport domains — volleyball and martial arts — so you can point it at whatever footage you're actually recording (see Switching sports).
| File | Purpose |
|---|---|
domains.py | Sport domains (volleyball / martial arts): detector choice, tag vocabulary, report wording, and segmentation defaults. |
decode_video.py | ffmpeg bridge: real video (MP4/MOV/URL) → the gzipped P5/PGM frames the detector reads. CPU-only. |
process_footage.py | One-shot: decode → pipeline → publish reports/<clip>/ + reports/index.json. Driven by the process-footage workflow. |
detect.py | CV front-end: turns raw clip frames into subject-track tracking data (ball or fighter). |
pipeline.py | Runs the full pipeline (detect → highlights → coaching) and the self-test. |
coaching.py | Per-clip coaching report: segment length, subject speed, action-zone heatmap (worded per domain — rally/ball/contact for volleyball, exchange/fighter/strike for martial arts). |
highlights.py | Segments tracking data into plays and emits tagged highlight clips. |
cosmos_tagger.py | Optional clip tag enrichment via NVIDIA Cosmos Reason. |
ingest_watch.py | Watches a drop folder, auto-detects new footage, and enqueues unseen clips. |
write_status.py | Publishes overseer-status.json (heartbeat + ingest signals + idle nudge + self-test verification). |
The pipeline is mechanically sport-agnostic: it tracks one subject point per
frame, segments play from gaps in that point's motion, tags each segment from
timed events, and bins events onto a surface grid. A Domain (domains.py)
bundles the only things that actually differ between sports:
| volleyball | martial arts | |
|---|---|---|
| detector | brightest blob (the ball) | motion energy (the moving fighter) |
| play segment | rally | exchange |
| subject / surface | ball / court | fighter / mat |
| action tags | serve, set, attack, block, dig… | jab, cross, hook, kick, knee, takedown, clinch… |
Why a different detector? Martial arts has no high-contrast ball to track, so the subject is recovered with motion-energy temporal segmentation — thresholding the frame-to-frame pixel difference and taking the centroid of what changed. A fighter standing still produces no motion and reads as the gap between exchanges, the direct analogue of a volleyball going out of play. (Standard dependency-free approach; see e.g. energy-guided temporal segmentation.)
Select the domain with the --domain flag or the COACHVISION_DOMAIN env var
(library default volleyball, preserving prior behaviour and the overseer's
status contract; the deployed automation runs martial_arts via the workflows):
# Process martial-arts footage end-to-end
python pipeline.py clip.pgm.gz --events clip.events.json --domain martial_arts
# Or set it once for the whole session (detect.py / highlights.py / coaching.py
# all read it)
export COACHVISION_DOMAIN=martial_arts
python pipeline.py --self-test # runs the martial-arts reference clip
For the automated weekly workflow, set the repository variable
COACHVISION_DOMAIN (Settings → Secrets and variables → Actions → Variables) to
martial_arts to switch detection, the self-test, and the published status
without touching code. Each domain bundles its own reference clip
(fixtures/), so the self-test proves its detector end-to-end.
The pipeline's machine-readable keys (segment_count, frames_processed, …) stay
stable across domains so the Project Overseer keeps working; only the words in
the human-facing coaching summary change (exchanges/strikes/mat vs.
rallies/contacts/court).
The CV here is pure Python (no torch/opencv/CUDA), so the whole thing runs on
GitHub's free Linux runners. The only piece that touches real video is decoding,
and that's plain ffmpeg (CPU, preinstalled on ubuntu-latest) — no GPU
anywhere. Two new pieces make this a one-button job:
| File | Purpose |
|---|---|
decode_video.py | ffmpeg → gzipped P5/PGM frames (MP4/MOV/… → what detect.py reads). Samples to a low fps/width since the detector is O(pixels). |
process_footage.py | Decode → run the pipeline for a domain → publish reports/<clip>/ and upsert reports/index.json. |
Trigger it from the Actions tab → process-footage → "Run workflow" (the
GitHub mobile app and the REST API can fire the same workflow_dispatch):
domain — martial_arts (default) or volleyball.clip_path — a video already committed under drop/ (e.g. drop/spar.mp4), orclip_url — a link the runner downloads (handy for a phone upload / cloud link).fps — sample rate (default 10).annotate — true (default) draws fighter boxes + skeletons and detects
strike attempts (YOLOv8-pose, CPU); false skips it for a faster run.It commits browsable outputs back to the repo:
reports/
index.json # catalog of every processed clip (PWA-friendly)
<clip>/coaching/report.json # machine-readable coaching report
<clip>/coaching/summary.txt # the coach-facing summary
<clip>/coaching/feedback.md # Claude coaching feedback (only if a key is set)
<clip>/highlights/manifest.json
Optional Claude coaching feedback. For martial-arts runs with
annotateon, the pipeline can hand the computed stats (strike attempts, hand/leg split, fighter presence, exchanges) and a few annotated stills to Claude (vision) and save real, actionable coaching notes to<clip>/coaching/feedback.md— the PWA shows them at the top of the clip dialog. This is cost-gated and optional: add anANTHROPIC_API_KEYrepo secret (Settings → Secrets and variables → Actions) to enable it. With no secret the step is skipped and the run still succeeds. On a dev box with theclaudeCLI already authenticated, setCOACHVISION_FEEDBACK_CLI=1to generate the same feedback throughclaude -pwithout an API key.
Pose weights without CDN access. A bare model name makes ultralytics download
yolov8n-pose.ptfrom its GitHub release CDN. Environments whose egress policy blocks that can vendor the weights into the repo instead: run thefetch-assetsworkflow (or commit the file by hand) to place them atmodels/yolov8n-pose.pt, whichfight_analysisprefers automatically.
reports/index.json is a flat list of clips with relative paths to each
artifact — exactly what a static phone PWA (served from GitHub Pages or the
raw repo) can fetch to render a session gallery, with no server to run. The same
workflow_dispatch is the PWA's "analyze this clip" button.
Detection quality, honestly. With
annotateon, martial-arts runs are pose-driven (fight_analysis.py): YOLOv8-pose detections feed two fighter slots tracked across frames (detector id first, then proximity; static ringside people are excluded from candidacy; a lost slot coasts ~1s and refills only near the opponent). An exchange is the span where the pair is engaged — within ~1.2 fighter-heights of each other — so lulls between points read as real gaps even though everyone stays in frame. Strike attempts are flagged from wrist/ankle snap speed and split hand vs leg per fighter; it does not name techniques (jab vs. hook), and on crowded tournament footage the tracker can briefly latch onto a referee or lose a motion-blurred fighter who crosses in front of the camera — treat counts as approximate signal. Without pose deps the fallback is motion-energy segmentation, which finds where the action is and works best with a steady camera.
docs/ is an installable, offline-capable PWA — a serverless phone interface to
the whole thing. It has no backend: it reads the committed reports/ via the
GitHub contents API and starts analyses by firing the process-footage workflow
through the Actions API.
Deploy it (once): Settings → Pages → Source: GitHub Actions. The
pages.yml workflow then publishes docs/ on every push to main, and the app
lives at https://<owner>.github.io/<repo>/ (for this repo,
https://andyrbrett.github.io/coachvision/). Open it on your phone and
Add to Home Screen to install it.
First run: open Settings in the app and set owner / repo / branch
(defaults AndyRBrett / coachvision / main) and a GitHub token. Reading
sessions from a public repo needs no token; the Analyze button does. Use a
fine-grained PAT scoped to this repo with Actions: Read and write +
Contents: Read, or a classic token with repo + workflow. The token is
stored only in your browser's localStorage.
Three tabs:
reports/index.json; tap a card for its coaching
summary (exchanges/strikes, fighter speed, heatmap).drop/ path), set fps, and
start a run; recent run statuses show inline.The pipeline runs in three stages, each a small stdlib-only module:
clip frames ──detect.py──▶ tracking ──highlights.py──▶ tagged rally manifest
└────────coaching.py────▶ coaching report (length/speed/heatmap)
detect.py is the CV front-end that was missing — the reason the pipeline had
processed 0 frames ever. It reads a clip stored as a gzipped Netpbm (P5/PGM)
frame sequence and recovers the ball position per frame as the centroid of the
brightest blob, emitting the same tracking schema the rest of the pipeline
consumes. No ffmpeg/opencv/GPU required, so it runs anywhere CI does.
A short (~10s) reference clip is bundled as a fixture so the whole pipeline can be proven end-to-end:
python pipeline.py --self-test # detect → highlights → coaching on the reference clip
The self-test fails (non-zero exit) if detection finds no ball, no rallies
segment, or the coaching report comes back empty — so a broken pipeline fails
the build (.github/workflows/tests.yml) instead of silently sitting at zero
frames. It also writes results/selftest.json, which write_status.py surfaces
as pipeline_selftest in the overseer status, distinguishing healthy-but-idle
(pipeline verified, just no new footage) from broken.
The fixtures live in fixtures/ and are regenerated with
python fixtures/make_reference_clip.py (volleyball) and
python fixtures/make_martialarts_clip.py (martial arts).
coaching.py turns a clip's tracking data into a coach-facing summary — the
project's core user-facing value:
python pipeline.py fixtures/reference_clip.pgm.gz --events fixtures/reference_clip.events.json \
--meters-per-pixel 0.1125 # full run: manifest + coaching report + metrics
python coaching.py tracking.json # coaching report only, from existing tracking JSON
Per rally and per clip it reports:
--meters-per-pixel court calibration is given). rally_001: 1.7s, 3 contacts [serve, set, attack], peak 56.57 px/s (6.36 m/s)
...
contact-zone heatmap (court top-left origin):
| @ |
| = =@ |
|@= |
Output is written to coaching/report.json (machine-readable) and
coaching/summary.txt (the coach-facing artifact).
The pipeline can "work but go unused" — a capable CV system that nothing is
feeding. ingest_watch.py closes that gap:
# Watch a folder (local dir or synced cloud bucket) for new footage
COACHVISION_DROP_DIR=drop python ingest_watch.py
COACHVISION_DROP_DIR (default drop/) for video files.ingest_state.json) so each clip is
enqueued once, and only after its size settles across two scans (so a clip
still being copied isn't processed half-written).ingest_queue.json.write_status.py then surfaces an idle nudge so prolonged idleness is
visible instead of silently passing as healthy:
{
"days_since_last_footage": null,
"idle_threshold_days": 14,
"pending_footage": 0,
"needs_footage": true,
"nudge": "No footage has ever been ingested -- drop clips in the watched folder to start."
}
The nudge fires when footage has never been ingested, when the last ingest is
older than COACHVISION_IDLE_THRESHOLD_DAYS (default 14), or when clips are
queued but unprocessed (a stalled pipeline). The weekly workflow runs the scan
before writing status.
Turns ball+player tracking data into rewatchable, tagged clips — the project's stated purpose (coaching feedback).
# Build a manifest (no rendering needed)
python highlights.py examples/sample_tracking.json --output-dir highlights
# Preview the ffmpeg commands, or render the clips (requires ffmpeg)
python highlights.py examples/sample_tracking.json --dry-run
python highlights.py examples/sample_tracking.json --render
Three stages:
max_gap_s).serve/reception/set/attack/block/dig/
… tags whose event timestamps fall inside each rally.highlights/manifest.json (the dashboard artifact),
one entry per rally with an ffmpeg trim+overlay command.ffmpeg rendering is optional and guarded: the command is always recorded in
the manifest but only executed with --render when ffmpeg is installed, so the
core logic stays pure and testable. See examples/sample_tracking.json for the
expected tracking-input schema.
In June 2026 NVIDIA open-sourced the Cosmos 3 family, including Cosmos
Reason — a reasoning vision-language model for multimodal video understanding,
served as an NVIDIA NIM microservice. cosmos_tagger.py can use it to enrich the
heuristic event-window tags with model-derived coaching tags:
export COACHVISION_COSMOS_NIM_URL="https://integrate.api.nvidia.com/v1/chat/completions"
export COACHVISION_COSMOS_API_KEY="nvapi-..." # for hosted NIM
export COACHVISION_COSMOS_MODEL="nvidia/cosmos-reason-3" # optional override
python highlights.py examples/sample_tracking.json --cosmos
It is strictly optional and best-effort: with no endpoint configured (or on any
request failure) the pipeline falls back to heuristic tags and never crashes.
Inference runs behind the NIM HTTP endpoint, so no GPU or NVIDIA SDK is required
on the runner — the module is stdlib-only (urllib).
python -m unittest discover -s tests -v
CI runs the suite on every push/PR (.github/workflows/tests.yml); the weekly
overseer-status workflow scans for footage and publishes status.