Skip to content

Latest commit

 

History

History
379 lines (273 loc) · 18.7 KB

File metadata and controls

379 lines (273 loc) · 18.7 KB

CLAUDE.md

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.

Project Overview

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.


Astral Toolchain

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, or mypy — always use the Astral equivalents via uv run.


Commands Reference

uv

# 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

ruff

# 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.py

Inline suppression:

x = some_var  # noqa: F841
x = some_var  # noqa: F841, E501

Config 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

ty

# 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-zero

Inline 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"]

ty is in beta — not a drop-in replacement for mypy/Pyright; different design choices and defaults. Use alongside ruff, not instead of it.


Full QA Pipeline (run before every commit)

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 tests

Mandatory: Update metadata on any script change

Whenever you edit any Python script (*.py) in this repo, also update metadata so users see consistent date/version across app, docs, and help.

Checklist

  • Edited script header: update top module docstring/header:
    • Update Date: today
    • Version: global vailá version (same as vaila.py header/banner)
  • Main entry point: if change impacts GUI/CLI banner, update vaila.py header 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.md line Last updated: YYYY-MM-DD to 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)

Architecture

Entry Point & GUI (vaila.py)

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:

  1. Direct import + call — runs in the same process
  2. Subprocess via run_vaila_module() — separate process (avoids Tkinter conflicts)

Package Structure (vaila/)

~100 self-contained analysis modules. Each module:

  • Has a run_*() or analyze_*() entry point called from the GUI
  • Uses Tkinter filedialog for 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)

Platform-Specific Configuration

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.


Coding Conventions

Mandatory dual-import pattern

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

Rules

  • 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)

Testing

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 pipeline

Sample data lives in tests/vaila_and_jump/ (CSV + TOML).


Common Task Recipes

Add a new analysis module

  1. Create vaila/my_module.py with run_my_module() as entry point
  2. Apply dual-import pattern at the top
  3. Use helpers from dialogsuser.py for user prompts
  4. Write results to a timestamped output dir
  5. Wire button in vaila.py with lazy import
  6. Lint and type-check: uv run ruff check vaila/my_module.py --fix && uv run ty check vaila/my_module.py
  7. Add unit test in tests/

Fix all lint + type issues in one shot

uv run ruff check vaila/ --fix && uv run ruff format vaila/ && uv run ty check vaila/

Run a module standalone via CLI

uv run vaila/interp_smooth_split.py -i /path/to/csv_dir -c smooth_config.toml

Security

Open-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.


Agents and skills

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.).

Recent GUI Notes

  • 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-ignored vaila/models/crop_face/face_detector.task; manual .task / .tflite selection remains fallback when network download fails. Provision explicitly with uv run python vaila/crop_faces_atletas.py --download-model. Creator metadata remains Abel Gonçalves Chinaglia; help docs are vaila/help/crop_faces_atletas.md and .html.
  • Main Help button: open vaila/help/index.html with webbrowser.open_new_tab(Path(...).as_uri()). Avoid os.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.csv alias, 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_markers routes Save through save_coordinates (vectorised NumPy bulk assignment; 248k-bbox case writes in ~0.5 s) instead of export_labeling_dataset which 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-split tqdm bar 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 verbose README_sam.txt (schema/units/role for every produced file) plus a sam_bbox_tracks.csv POSIX hardlink (copy fallback) next to sam_tracks.csv so the bbox file is easy to spot. Shared helpers _write_sam_run_readme() / _make_sam_bbox_tracks_alias() + constant SAM_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 (SciPy linear_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 and docs/sessions/2026-06-14-getpixel-sam3-crosschunk.md.

Specialized Agents (.claude/agents/)

Role cards for domain experts. Use these when the task fits their specific domain:

Technical Skills (.claude/skills/)

Reusable "how-to" guides for complex workflows:

FIFA Skeletal Tracking Light 2026

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 val

Companion 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.

Slash Commands (.claude/commands/)

Specs for common shortcuts like /check or /new-module.