Guidance for AI Assistants (Claude Code, Antigravity, Cursor, Windsurf, etc.) when working with the vailá repository.
See also: AGENTS.md — shared rules for all AI agents.
vailá (Versatile Anarcho Integrated Liberation Ánalysis) — open-source Python 3.12 multimodal toolbox for biomechanical data analysis. Integrates IMU, motion capture, markerless tracking (MediaPipe, YOLO), force plates, EMG, GNSS/GPS through a Tkinter-based GUI.
- GitHub: https://github.com/vaila-multimodaltoolbox/vaila
- Python: strictly
>=3.12,<3.13 - License: AGPLv3
- Build backend:
hatchlingmanaged viauv
The project uses the full Astral Rust-based toolchain:
| Tool | Purpose | Replaces |
|---|---|---|
uv |
Package manager, venv, Python installer | pip, poetry, pyenv, virtualenv |
ruff |
Linter + formatter | flake8, black, isort, pyupgrade |
ty |
Static type checker (beta, Rust, 10-100x faster than mypy) | mypy, Pyright |
Never use bare
pip install,black,isort,flake8, ormypy— always use the Astral equivalents viauv run.
# Run the application
uv run vaila.py
# Sync dependencies (reads uv.lock + pyproject.toml)
uv sync # default: universal CPU pyproject
uv sync --extra gpu # only after CUDA template is active (see AGENTS.md Hybrid)
uv sync --extra sam # SAM 3 optional stack; video still needs NVIDIA CUDA
uv sync --frozen # CI mode: fail if lock is outdated
# RECOMMENDED: unified interactive bootstrap (auto-detects OS + NVIDIA + extras)
bash bin/setup_pyproject.sh # Linux / macOS / WSL / Git Bash
pwsh bin/setup_pyproject.ps1 # Windows PowerShell
bash bin/setup_pyproject.sh --target=linux-cuda --extras=gpu,sam --yes
# Legacy per-platform wrappers (thin shims):
# bin/use_pyproject_universal_cpu.sh | use_pyproject_linux_cuda.sh | use_pyproject_macos_metal.sh
# bin/use_pyproject_universal_cpu.ps1 | use_pyproject_win_cuda.ps1
# Manage dependencies
uv add <package> # Add runtime dependency
uv add --dev <package> # Add dev dependency
uv remove <package> # Remove dependency
uv lock # Regenerate uv.lock
uv lock --upgrade # Upgrade all packages
# Python version management
uv python install 3.12 # Install Python 3.12
uv python pin 3.12 # Pin project to 3.12
uv venv --python 3.12 # Create venv with specific version
# Global tools (outside project venv)
uv tool install ruff # Install ruff globally
uv tool install ty # Install ty globally
uv tool upgrade ruff # Upgrade ruff globally
uvx ruff check vaila/ # Run ruff ephemerally (no install)
# Export for legacy tooling
uv export --format requirements-txt > requirements.txt
uv export --format requirements-txt --no-hashes --frozen > requirements.txt# Linting
uv run ruff check vaila/ # Lint all files
uv run ruff check vaila/ --fix # Lint + auto-fix safe issues
uv run ruff check vaila/ --fix-only # Apply fixes only, no output
uv run ruff check vaila/ --diff # Preview what --fix would change
# Formatting (replaces black)
uv run ruff format vaila/ # Format all files
uv run ruff format vaila/ --check # CI mode: check without writing
uv run ruff format vaila/ --diff # Preview what format would change
# Single file
uv run ruff check vaila/my_module.py --fix
uv run ruff format vaila/my_module.pyInline suppression:
x = some_var # noqa: F841
x = some_var # noqa: F841, E501Config in pyproject.toml:
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "NPY", "UP", "B", "C4", "SIM"]
ignore = ["E501", "N806", "N803"] # scientific uppercase vars are OK
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # intentional re-exports# Type checking
uv run ty check vaila/ # Check all files
uv run ty check vaila/my_module.py # Check single file
uv run ty check vaila/ --watch # Watch mode: re-checks on save
# Override rule severity on CLI
uv run ty check vaila/ --error unresolved-import
uv run ty check vaila/ --warn possibly-unbound
uv run ty check vaila/ --ignore division-by-zeroInline suppression:
x: int = "hello" # ty: ignore[invalid-assignment]
x: int = "hello" # ty: ignore[invalid-assignment, unresolved-import]Config in pyproject.toml:
[tool.ty.rules]
unresolved-import = "warn" # "error" | "warn" | "ignore"
possibly-unbound = "warn"
division-by-zero = "error"
unused-ignore-comment = "warn"
[tool.ty.src]
include = ["vaila", "tests"]
exclude = ["vaila/_generated"]
tyis in beta — not a drop-in replacement for mypy/Pyright; different design choices and defaults. Use alongside ruff, not instead of it.
uv run ruff check vaila/ --fix # fix lint issues
uv run ruff format vaila/ # format code
uv run ty check vaila/ # type check
uv run pytest tests/ -v # run testsWhenever you edit any Python script (*.py) in this repo, also update metadata so users see consistent date/version across app, docs, and help.
- Edited script header: update top module docstring/header:
- Update Date: today
- Version: global vailá version (same as
vaila.pyheader/banner)
- Main entry point: if change impacts GUI/CLI banner, update
vaila.pyheader and any banner strings. - Install scripts: if install/run UX impacted, review/update:
install_vaila_linux.sh,install_vaila_mac.sh,install_vaila_win.ps1,install-hooks.sh
- Repo README: update root
README.mdlineLast updated: YYYY-MM-DDto today. - Help docs:
- main index
vaila/help/index.md+vaila/help/index.html(“Generated on”) - changed module help
vaila/help/<module>.md+vaila/help/<module>.html(Version + Updated)
- main index
vaila.py defines Vaila(tk.Tk), organized into three frames:
| Frame | Purpose |
|---|---|
| Frame A | File Manager — rename, import, export, copy, move, remove, tree, find, SSH transfer |
| Frame B | Multimodal Analysis — IMU, MoCap, Markerless 2D/3D, EMG, Force Plate, GNSS |
| Frame C | Tools — CSV editing, C3D conversion, DLT reconstruction, video/image, visualization |
Lazy imports are used in all handler methods to avoid loading the full dependency graph at startup.
Two dispatch patterns:
- Direct import + call — runs in the same process
- Subprocess via
run_vaila_module()— separate process (avoids Tkinter conflicts)
~100 self-contained analysis modules. Each module:
- Has a
run_*()oranalyze_*()entry point called from the GUI - Uses Tkinter
filedialogfor user input prompts - Reads CSV/C3D via
pandas/numpy/ezc3d - Writes results (CSV + PNG plots) to timestamped output subdirectories
Key shared modules:
| Module | Role |
|---|---|
data_processing.py |
CSV/C3D reading with auto-header detection |
filtering.py / filter_utils.py |
Butterworth and FIR filter implementations |
common_utils.py |
Header detection and data reshaping |
dialogsuser.py / dialogsuser_cluster.py |
Reusable Tkinter input dialogs |
filemanager.py |
File management (rename, copy, move, SSH transfer) |
hardware_manager.py |
GPU/CPU detection, TensorRT export — do not duplicate |
interp_smooth_split.py |
Interpolation, smoothing, splitting (GUI + CLI) |
Copy the correct template to pyproject.toml before running uv python pin / uv venv:
| Template | Target |
|---|---|
pyproject_win_cuda12.toml |
Windows + NVIDIA CUDA 12.1 |
pyproject_linux_cuda12.toml |
Linux + NVIDIA CUDA 12.8 |
pyproject_macos.toml |
macOS Apple Silicon (Metal/MPS) |
pyproject_universal_cpu.toml |
CPU-only fallback |
Install scripts handle this automatically: install_vaila_linux.sh, install_vaila_mac.sh, install_vaila_win.ps1.
Every module must support both package import and standalone execution:
try:
from .readcsv import read_csv_file # package import
from .filtering import butter_filter
except ImportError:
from readcsv import read_csv_file # standalone fallback
from filtering import butter_filter- GUI framework: Tkinter only — never introduce Qt, wx, Dear PyGui, etc.
- Scientific variable names (X, Y, Z, F, R, T, etc.) are valid — suppressed via ruff
N806/N803 - Output dirs: always timestamped →
processed_<type>_YYYYMMDD_HHMMSS/ - No hard-coded absolute paths
- No files ≥20 MiB (git hook enforced)
uv run pytest tests/ -v # all tests
uv run pytest tests/test_vaila_and_jump.py -v # biomechanical calculations
uv run pytest tests/test_tugturn.py -v # TUG/Turn analysis
uv run pytest tests/test_dlt_rec.py -v # DLT/Rec math
uv run pytest tests/test_dlt_rec_integration.py -v # DLT/Rec pipelineSample data lives in tests/vaila_and_jump/ (CSV + TOML).
- Create
vaila/my_module.pywithrun_my_module()as entry point - Apply dual-import pattern at the top
- Use helpers from
dialogsuser.pyfor user prompts - Write results to a timestamped output dir
- Wire button in
vaila.pywith lazy import - Lint and type-check:
uv run ruff check vaila/my_module.py --fix && uv run ty check vaila/my_module.py - Add unit test in
tests/
uv run ruff check vaila/ --fix && uv run ruff format vaila/ && uv run ty check vaila/uv run vaila/interp_smooth_split.py -i /path/to/csv_dir -c smooth_config.tomlOpen-source under AGPL-3.0 — never commit API keys, tokens, or local credential files. See SECURITY.md and CONTRIBUTING.md. Use .env locally (gitignored); see .env.example for a safe template.
Step-by-step workflows and specialized agent roles are stored in the .claude/ directory. This structure is intended to be used by any AI assistant (Claude Code, Antigravity, Cursor, etc.).
- Crop Face (
vaila/crop_faces_atletas.py): integrated at Frame C -> Video and Image -> C_B_r1_c2. The GUI flow must remain input directory first, output directory second, then automatic official model download into Git-ignoredvaila/models/crop_face/face_detector.task; manual.task/.tfliteselection remains fallback when network download fails. Provision explicitly withuv run python vaila/crop_faces_atletas.py --download-model. Creator metadata remainsAbel Gonçalves Chinaglia; help docs arevaila/help/crop_faces_atletas.mdand.html. - Main Help button: open
vaila/help/index.htmlwithwebbrowser.open_new_tab(Path(...).as_uri()). Avoidos.system("open ...")/ shell openers for this button because Linux file associations may launch an IDE/editor instead of the browser. - Smart Load Tracking CSV (
vaila/getpixelvideo.py, v0.3.55): auto-detects SAM3 (sam_tracks.csv,sam_bbox_tracks.csvalias,sam_frames_meta.csv,sam_points.csv) and YOLO (all_id_detection.csv,person_id_NN.csv) formats, then prompts for a bbox → marker anchor (1=center 2=bottom 3=top 4=left 5=right, Enter = keep overlay only). See.claude/skills/getpixelvideo-tracking-loader/SKILL.md. - getpixelvideo Save no longer freezes after bbox load (v0.3.55): state flag
bbox_converted_to_markersroutes Save throughsave_coordinates(vectorised NumPy bulk assignment; 248k-bbox case writes in ~0.5 s) instead ofexport_labeling_datasetwhich extracted every annotated frame. New_flush_save_message(screen, text)paints a "Saving…" banner before any long write. ML-dataset writers (export_labeling_dataset,export_pose_dataset,_export_all_labels_view) now print a>> vaila/getpixelvideo:banner + per-splittqdmbar to the terminal so the user sees progress even while pygame is blocked. Gotcha for future code: absl logging (installed by mediapipe/opencv on import) silently eats[bracketed]prefixes from stdout — use>>instead. Details:docs/sessions/2026-06-15-getpixel-savefreeze-readme-bbox-alias.md§ 8. - SAM3 verbose README + bbox alias (
vaila/vaila_sam.py, v0.3.55): every run writes a verboseREADME_sam.txt(schema/units/role for every produced file) plus asam_bbox_tracks.csvPOSIX hardlink (copy fallback) next tosam_tracks.csvso the bbox file is easy to spot. Shared helpers_write_sam_run_readme()/_make_sam_bbox_tracks_alias()+ constantSAM_OUTPUT_FILE_GLOSSARY. See.claude/skills/sam3-video/SKILL.md§ Output Format. - SAM3 Cross-Chunk Tracklet Linking (
vaila/vaila_sam.py, v0.3.54): chunked path now stitches chunk-local IDs into persistent global IDs via a 2-frame sliding overlap + bipartite IoU + centroid-distance cost matrix solved with Hungarian (SciPylinear_sum_assignment, greedy fallback). Helper_build_cross_chunk_id_maps(min_iou=0.05, max_centroid_dist_px=180.0). See.claude/skills/sam3-video/SKILL.md§ Cross-Chunk Tracklet Linking anddocs/sessions/2026-06-14-getpixel-sam3-crosschunk.md.
Role cards for domain experts. Use these when the task fits their specific domain:
Reusable "how-to" guides for complex workflows:
- vailá Core: create a new analysis module, port a MATLAB algorithm, getpixelvideo-tracking-loader — smart Load Tracking CSV (SAM3 / YOLO auto-detect + bbox → marker anchor prompt).
- Sports AI:
- sam3-video — SAM 3 text-prompt video segmentation, GUI help button, prompt presets, Cross-Chunk Tracklet Linking (v0.3.54).
- fifa-skeletal-tracking — FIFA 2026 pipeline (
fifa bootstrap/prepare/boxes/preprocess/baseline/dlt-export/pack),vaila/fifa_to_dlt.py(per-frame DLT forrec2d.py/rec3d.pyvs fixed-camrec2d_one_dlt2d.py), vendoredfifa_starter_lib, gated SAM 3D Body setup, soccer-field DLT2D calibration. - soccer-field-keypoints-yolo — Ultralytics YOLO pitch pose (32 kp), external merged
unified/tree,yolo pose train; seedocs/fifa_workflow.md§4.5 andvaila/help/soccerfield_keypoints_ai.md.
- Reports: xlsx (Excel), pdf, pptx (PowerPoint).
- Automation: mcp-builder (Model Context Protocol), webapp-testing.
- Visualization: web-artifacts-builder.
vailá ships a complete pipeline for the FIFA Skeletal Tracking Light 2026 challenge. The one-line setup is:
bash bin/setup_fifa_sam3d.sh # clone sam_3d_body + gated HF weights
uv run vaila/vaila_sam.py fifa bootstrap \
--videos-dir /data/FIFA/.../Videos \
--data-root /data/FIFA/data
uv run vaila/vaila_sam.py fifa prepare --data-root data/ --video-source /data/FIFA/.../Videos
uv run vaila/vaila_sam.py fifa boxes --data-root data/ --sequences data/sequences_val.txt
uv run vaila/vaila_sam.py fifa preprocess --data-root data/ --sequences data/sequences_val.txt # CUDA
uv run vaila/vaila_sam.py fifa baseline --data-root data/ --sequences data/sequences_full.txt --output outputs/submission_full.npz # add --export-camera to refresh cameras/*.npz
uv run vaila/vaila_sam.py fifa dlt-export --cameras-dir data/cameras --output-dir outputs/dlt_per_frame
uv run vaila/vaila_sam.py fifa pack --submission-full outputs/submission_full.npz --data-root data/ --output-dir outputs/ --split valCompanion tool vaila/soccerfield_calib.py (button Soccer-Field Calib in
Frame C of vaila.py) fits a DLT2D homography from 29 FIFA keypoints and can
emit cameras/<stem>_homography.npz as a fallback when a sequence has no
official cameras/*.npz.
External unified pitch dataset (YOLO retrain): vaila.fifa_dataset_builder writes unified/data.yaml under a user-chosen root outside git. After QA on check_all_labels/, use vaila.fifa_check_labels_dedupe and vaila.fifa_dataset_train_readiness to align unified/, then yolo pose train data=/ABS/.../unified/data.yaml. Full recipe: docs/fifa_workflow.md §4.5.
Specs for common shortcuts like /check or /new-module.