An annotation tool for race swimming videos (using one or multiple static cameras).
- Create a virtual environment:
python -m venv venv- Activate the environment:
- On macOS/Linux:
source venv/bin/activate- On Windows:
venv\Scripts\activate- Install the module:
pip install --upgrade pip
pip install -r requirements.txt- Install the JavaScript dependencies:
npm installThere are 3 main ways to run Aquanote:
- Static mode, using the bundled
videos/flat.jsonindex and local files. This is the mode used for GitHub Pages. - Local Python server mode, using
local.pyto serve local competition data fromvideos/. - API mode, using a VizAPI-compatible backend.
Use Static for read-only demos and GitHub Pages, Local Python server for local annotation work and metadata writes, and API when Aquanote is connected to a shared backend. The application also has a Configuration tab where you can switch modes and set the local server or API URL without editing the code.
Static mode does not need the Python API. It serves the web app and reads the demo data directly from the repository.
npm startThen open:
http://127.0.0.1:8001/?source=static
In static mode, Calibrate and Metadata can update the current browser session, but saving JSON back to disk requires the local Python server or an API backend.
To refresh the static index after adding competitions or runs, regenerate videos/flat.json:
python -m flatdir videos --limit 10 --nested --only type=directory --add espadon=false --add espadonModifie=false --add data_checked=false --no-defaults --min-depth 1 --add-depth 2 --ignore-typical > videos/flat.jsonUse this mode when you want to serve local files from the repository videos/ folder with the small Flask server in local.py.
In one terminal, start the local data server on port 8000:
python local.py --port 8000In another terminal, start the web app on port 8001:
npm startThen open:
http://127.0.0.1:8001/?source=local
If the local server uses a different URL, set it in the Configuration tab or pass it in the URL:
http://127.0.0.1:8001/?source=local&localServerUrl=http%3A%2F%2F127.0.0.1%3A8010
Local competitions must be present in videos/. Competition and run folder names should start with 2 so they are detected by the dropdown filters. Keep the expected underscore _ structure in folder names to avoid display issues in the race dropdown menus.
API mode expects a backend that exposes Aquanote through the /aquanote prefix, such as the VizAPI module:
https://github.com/centralelyon/VizAPI/tree/main/app/modules/aquanote
Start that API separately so it is available at:
http://localhost:8000/aquanote
Then start the web app:
npm startOpen:
http://127.0.0.1:8001/?source=api
If the API is not at http://localhost:8000/aquanote, set it in the Configuration tab or pass it in the URL:
http://127.0.0.1:8001/?source=api&apiUrl=http%3A%2F%2Flocalhost%3A9000%2Faquanote
The API provider uses these endpoints:
GET /aquanote/getCompets
GET /aquanote/getRuns/{compet_id}
GET /aquanote/getDatas/{compet_id}/{run_id}
GET /aquanote/getQuality/{compet_id}/{run_id}
GET /aquanote/files/{compet_id}/{run_id}/{filename}
The Configuration tab has Sportsdata format selectors with a Strict toggle next to each one:
- Sportsdata JSON format selects the preferred metadata model for sportsdata JSON files. In non-strict mode, Aquanote will still try to load partial Swimflow-like metadata and fill missing race fields with defaults.
- Sportsdata load schema filters the CSV files shown for the selected run. Aquanote validates CSV headers in the current run folder against the selected Sportsdata swimming schema before adding them to the data dropdown. In non-strict mode, a detected sportsdata CSV can still load even when validation reports schema issues; those issues are kept as diagnostics instead of blocking the run. Static mode can only discover files listed in metadata or
flat.json. - Sportsdata save format controls the columns used by the download button. The default is Swimming tracking CSV; Swimming basic tracking CSV writes
frameId,swimmerId,eventId,time,distance.
Aquanote uses a specific data structure to store the annotations. It is based on a CSV file for each video, with samples provided in the videos folder. Here is a sample of such a CSV file:
frameId,swimmerId,swimmerName,lane,cumul,eventId,eventX,eventY,event,TempsVideo (s),Temps (s),distance (m),tempo (s),frequence (cylce/min),amplitude (m),vitesse (m/s)
63,4,ligne5,ligne5,3.90,0,46.1036,8,cycle,1.36,1.26,3.90,,,,
113,4,ligne5,ligne5,6.87,1,43.1305,8,cycle,2.36,2.26,6.87,2.00,30.00,5.95,2.97
To analyse the CSV file, a dedicated repository is available:
aquanalysis is a repository that contains scripts and notebooks to compute various performance metrics from the annotated data, such as speed, stroke rate, and stroke length.
The videos provided is in the videos folder are samples. To add your own videos, you need to follow the same structure as in the demo folder.
Each competition should have its own subfolder, and then videos to used should be specified in a metadata JSON file located at the root of each competition folder. For example, for the demo competition the metadata file is 2025_courses_demo_translation_carre_50_finale,
flatdir is a Python module to flatten a directory structure and automatically generate the required JSON files at the root of each competition folder (like the videos/flat.json JSON, add > flat.json to save the output in such a file):
python -m flatdir videos --limit 10 --nested --only type=directory --add espadon=false --add espadonModifie=false --add data_checked=false --no-defaults --min-depth 1 --add-depth 2 --ignore-typical > videos/flat.json
A metadata JSON contains information about pre-processing steps for each race to analyzed and should be created before the annotation phase. This includes temporal calibration (start and end times), spatial calibration (real-world dimensions) and athletes information (names, lanes). An example of such a JSON file is available in the demo folder: videos/2025_courses_demo/2025_courses_demo_translation_carre_50_finale/2025_courses_demo_translation_carre_50_finale.json..
While some pre-processingt steps can be done directly in Aquanote, some require external video and image processing tools. We suggest two tools to achieve this:
ntt for image and video processing using warppers around OpenCV and FFmpeg libraries (for instance).
pipeoptz to orchestrate such processing as pipelines and eventually optimize their parameters automatically.
The repository includes a helper script to generate a synthetic video from a race metadata JSON using the stored homography:
pip install -r scripts/requirements.txt
python scripts/generate_video.pyThis uses the demo 50_finale metadata by default and writes output_video.mp4 when MP4 encoding is available, otherwise it falls back to output_video.avi.
To use another metadata file or output path:
python scripts/generate_video.py \
--metadata videos/2025_courses_demo/2025_courses_demo_translation_carre_100_demifinale/2025_courses_demo_translation_carre_100_demifinale.json \
--output demo_homography.mp4To render the swimming lanes and lane labels on top of the generated frames:
python scripts/generate_video.py --render-lanesTo generate Aquanote assets from a sportsdata basic tracking CSV with a side-pool camera perspective:
python scripts/generate_sportsdata_basic_tracking_camera.pyBy default the script writes two side-pool videos, one for the left part of the pool and one for the right part, with overlap:
{run_name}_fixeGauche.mp4
{run_name}_fixeDroite.mp4
Each video entry in the generated metadata has type_video set to
fixeGauche or fixeDroite. The camera payload is written as
sportsdata_basic_tracking_camera.json next to the generated videos, CSV, and
metadata. A runnable two-camera example is available at:
scripts/example_generate_sportsdata_two_cameras.shTo generate a single video and pass your own camera:
python scripts/generate_sportsdata_basic_tracking_camera.py \
--single-camera \
--camera my_camera.jsonCamera JSON format:
{
"fov": 55,
"aspect": 1.777778,
"near": 0.01,
"far": 1000,
"position": [25, 17, -25],
"target": [25, 0, 10],
"up": [0, 1, 0],
"roll": 0
}All documentation can be viewed by launching the index file in the html folder, in particular the documentation for the main code located in /assets/js. This opens a page in your browser with sorted information about the code.
This documentation was generated via Doxygen (the doxyfile contains its settings). It is not as effective for JavaScript as it is for other languages, so it relies heavily on comments (and their spelling mistakes).
Some folders are not visible in VS Code because they are hidden in .vscode/settings.json. This is meant to declutter the visible files, but feel free to modify this file.



