Padex
Open-source Python toolkit that transforms padel match broadcast footage into structured tracking data, event streams, and tactical analytics. The first comprehensive CV-based analytics pipeline purpose-built for padel.
padex = padel + x (analytics/extraction)
Why Padex
Padel is one of the fastest-growing sports in the world, yet it has virtually no open-source analytics tooling. Unlike football — which has mature ecosystems like StatsBomb, Kloppy, and socceraction — padel lacks standardized data formats, open tracking pipelines, and community-driven analytics frameworks.
Padel's enclosed court (glass walls + metal fences), fixed dimensions (20m × 10m), and 4-player format make it uniquely tractable for CV-based tracking. Padex leverages this to deliver a complete end-to-end pipeline: from raw broadcast video to interactive match reports.
Features
- Player Detection & Tracking — YOLO-based detection with ByteTrack, team classification via jersey color clustering
- Ball Tracking — SAHI + YOLO with Kalman filtering and visibility states (visible / occluded / inferred)
- Court Calibration — Automatic court line detection and homography for pixel-to-real-world coordinate transformation
- Bounce Detection — Ground and wall bounce classification with surface identification
- Shot Detection — Contact-based shot segmentation with type classification
- Point Segmentation — Automatic rally/point boundary detection
- Tactical Metrics — Rally-level, player-level, and team-level analytics (winners, errors, net control, wall utilization, etc.)
- Video Annotation — Annotated output video with player bboxes, ball markers, court overlay, and mini court
- Match Reports — Interactive HTML reports with embedded Plotly dashboards
Architecture
Padex is organized into three layers, each building on the previous:
Layer 1: Tracking Layer 2: Events Layer 3: Tactics
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Court Calibration│ │ Bounce Detection │ │ Rally Metrics │
│ Player Detection │ ───▶ │ Shot Detection │ ───▶ │ Player Metrics │
│ Ball Tracking │ │ Point Segmentation│ │ Team Metrics │
└──────────────────┘ └──────────────────┘ │ Reports │
Parquet JSONL └──────────────────┘
JSON / HTML
Quick Start
Installation
# Clone the repository
git clone https://github.com/rondo-labs/padex.git
cd padex
# Install with uv
uv sync
Run End-to-End Analysis
uv run python examples/run_analysis.py input.mp4 --output output.mp4 --device auto
This runs the full pipeline in three passes:
- Tracking — Court calibration, player detection, ball tracking, bounce/shot/point detection, analytics
- Annotation — Generates annotated video with overlays (players, ball, court lines, mini court, stats)
- Reporting — Produces JSON analytics and an interactive HTML match report
Device auto-detection supports CUDA, MPS (Apple Silicon), and CPU.
Output Structure
match_20260301_teamA_vs_teamB/
├── metadata.json
├── tracking/
│ ├── players.parquet
│ ├── ball.parquet
│ └── court_calibration.json
├── events/
│ ├── shots.jsonl
│ ├── points.jsonl
│ └── structure.json
└── analytics/
├── rally_metrics.jsonl
├── player_metrics.json
└── team_metrics.json
Package Structure
src/padex/
├── tracking/ # Layer 1 — CV pipeline
│ ├── pipeline.py # End-to-end tracking orchestration
│ ├── court.py # Court detection + homography
│ ├── player.py # Player detection + tracking + team classification
│ ├── ball.py # Ball detection + Kalman filtering
│ └── device.py # Hardware auto-detection (CUDA/MPS/CPU)
├── events/ # Layer 2 — Event detection
│ ├── shot.py # Shot detection + classification
│ ├── bounce.py # Bounce detection + surface classification
│ ├── point.py # Point/rally segmentation
│ └── taxonomy.py # Shot type definitions
├── tactics/ # Layer 3 — Analytics
│ ├── metrics.py # Metric computation
│ ├── heatmap.py # Spatial analysis
│ └── report.py # JSON + HTML report generation
├── schemas/ # Data contracts (Pydantic models)
│ ├── tracking.py
│ ├── events.py
│ └── tactics.py
├── io/ # Data I/O
│ ├── video.py # Video reader/writer
│ ├── parquet.py # Parquet read/write
│ └── jsonl.py # JSONL read/write
└── viz/ # Visualization
├── frame.py # Video frame annotation
├── mini_court.py # Real-time 2D court overlay
├── court.py # Plotly court rendering
├── dashboard.py # Interactive Plotly dashboard
└── animation.py # Rally replay
Tech Stack
- Python ≥ 3.11
- ultralytics — YOLO v8/v11 for detection and pose estimation
- opencv-python — Video I/O and frame processing
- polars + pyarrow — Efficient columnar data handling
- pydantic — Schema validation and data contracts
- sahi — Sliced inference for small object detection (ball)
- plotly — Interactive charts and dashboards
- scipy — Signal processing for bounce/shot detection
- lap — Linear assignment for multi-object tracking
Development
# Install with dev dependencies
uv sync --group dev
# Run tests
uv run pytest tests/ -v
# Lint
uv run ruff check src/
# Type check
uv run mypy src/padex/
Data Schema
Padex uses a schema-first design. All data flowing between layers validates against Pydantic models defined in src/padex/schemas/.
Key conventions:
- Coordinate system: Origin at bottom-left, x = 0–10m (width), y = 0–20m (length), net at y = 10.0
- ID format: Hierarchical —
S_002_05_03_007= set 2, game 5, point 3, shot 7 - Shot trajectories: Ordered bounce sequences embedded in shot events, preserving the full causal chain
- Ball visibility: Three states —
visible,occluded,inferred
Full specification in padel_data_schema_v0.1.0.docx.
Roadmap
- Project setup and schema definitions
- Court detection and homography calibration
- Player detection, tracking, and team classification
- Ball detection with Kalman filtering
- Bounce and shot detection
- Point/rally segmentation
- Tactical metrics computation
- Video annotation with overlays and mini court
- Interactive HTML match reports
- Advanced shot type classification (bandeja vs víbora vs smash from pose data)
- 3D ball trajectory reconstruction
- Multi-camera support
- Real-time streaming pipeline
- Public model weights and example datasets
License
See LICENSE for details.
Metadata
Release files for padex 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| padex-0.1.0.tar.gz | 102.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| padex-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 199.6 kB
Release files / padex-0.1.0.tar.gz
| Download URL | padex-0.1.0.tar.gz |
|---|---|
| Size | 102.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
11be8770758f7a46e412d53c21d88280a26cc9d1353a4231cb45763688a30fcc
|
|
BLAKE2b-256 checksum How to use checksums |
00066899505264a339d378d5e94236adf33abf35929d0cf8b39be27a833330c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / padex-0.1.0-py3-none-any.whl
| Download URL | padex-0.1.0-py3-none-any.whl |
|---|---|
| Size | 96.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c94af7af72c4dfd2eb672aa421bdb7c7e6ed833e6c56e715a8ed551e8375348b
|
|
BLAKE2b-256 checksum How to use checksums |
688bfbfd9ed59976c6e77e95ff5de726e9c442e969731402d81d93a17ac662e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|