Scouting and vision tools for YOLO and sports analytics.
Project description
WunderScout
⚠️ EXPERIMENTAL PROJECT ⚠️
This is an experimental computer vision project for soccer/football video analysis. It is NOT production-ready and should be used for research, experimentation, and educational purposes only. The API may change without notice, and results may not be accurate or reliable for professional or commercial applications.
WunderScout is a Python library for automated soccer/football video analysis using computer vision and machine learning. It provides tools for player detection, tracking, team classification, and positional heatmap generation from match footage.
Features
- Player & Ball Detection: Automatic detection of players, goalkeepers, and ball in video frames
- Multi-Object Tracking: Track players across frames with unique IDs
- Team Classification: Automatic team assignment using visual embeddings
- Pitch Mapping: Transform image coordinates to real-world pitch coordinates
- Heatmap Generation: Create spatial heatmaps for individual players or entire teams
- CSV Export: Export tracking data in standardized CSV format
- Video Annotation: Generate annotated videos with bounding boxes and labels
Installation
pip install wunderscout
Requirements
- Python 3.8+
- CUDA-capable GPU (recommended for performance)
- Model weights for player detection and field keypoint detection
Quick Start
import wunderscout
# Enable logging (optional)
wunderscout.set_stream_logger('wunderscout', level=logging.INFO)
# Initialize models
models = wunderscout.Models(
player_weights='path/to/player_model.pt',
field_weights='path/to/field_model.pt'
)
# Create detector
detector = wunderscout.Detector(models)
# Process video
frames = detector.run(
video_path='match.mp4',
output_dir='./output' # Optional: save annotated video
)
# Export tracking data
frames.save_csvs('./tracking_data')
# Generate heatmaps
heatmap_gen = wunderscout.HeatmapGenerator()
player_heatmap = heatmap_gen.player(frames, player_id=5)
player_heatmap.save('./heatmaps', heatmap_type='both')
API Reference
Core Classes
Models
Manages the machine learning models for detection and classification.
Models(
player_weights: str,
field_weights: str,
siglip_path: str = None
)
Parameters:
player_weights: Path to YOLO weights file for player/ball detectionfield_weights: Path to YOLO weights file for field keypoint detectionsiglip_path: Path or identifier for SiglipVisionModel (default: "google/siglip-base-patch16-224")
Detector
Main class for running detection and tracking on video footage.
Detector(models: Models)
Methods:
run(video_path, output_dir=None) -> Frames
Process a video file and return tracking results.
Parameters:
video_path(str | Path): Path to input video fileoutput_dir(str | Path | None): Optional directory to save annotated video
Returns:
Frames: Object containing detection results for all frames
Raises:
FileNotFoundError: If video file not foundValueError: If video format is invalid or output_dir is a fileOSError: If video data cannot be readRuntimeError: If calibration fails
Example:
detector = Detector(models)
frames = detector.run('match.mp4', output_dir='./annotated')
Frames
Container for detection results across all video frames.
Methods:
get_all_player_ids() -> list[int]
Get tracker IDs for all detected players.
Returns:
- List of player tracker IDs
get_all_team_ids() -> list[int]
Get all unique team IDs.
Returns:
- List of team IDs (typically [0, 1])
get_team_for_player(player_id: int) -> int
Get team ID for a specific player.
Parameters:
player_id: Player tracker ID
Returns:
- Team ID (0 or 1)
save_csvs(output_path: str | Path) -> SaveResult
Export tracking data to CSV files (one per team).
Parameters:
output_path: Directory path for output CSV files
Returns:
SaveResult: Object containing success/failure information
CSV Format:
- Row 1: Team names
- Row 2: Player IDs
- Row 3: Column headers (Period, Frame, Time, Player coordinates, Ball coordinates)
- Subsequent rows: Frame-by-frame tracking data with pitch coordinates
Example:
result = frames.save_csvs('./output')
print(f"Saved: {result.successful_paths}")
print(f"Errors: {result.errors}")
HeatmapGenerator
Generate spatial heatmaps for players and teams.
HeatmapGenerator(
pitch_length: float = 105.0,
pitch_width: float = 68.0,
histogram_bins: tuple[int, int] = (50, 34),
kde_grid_size: tuple[int, int] = (100, 68)
)
Parameters:
pitch_length: Pitch length in meters (default: 105.0)pitch_width: Pitch width in meters (default: 68.0)histogram_bins: (x_bins, y_bins) for histogram resolutionkde_grid_size: (x_points, y_points) for KDE grid resolution
Methods:
player(frames: Frames, player_id: int, heatmap_type='both') -> Heatmap
Generate heatmap for a single player.
Parameters:
frames: Frames object from detectorplayer_id: Player tracker IDheatmap_type: One of "histogram", "kde", or "both"
Returns:
Heatmap: Heatmap object with data
team(frames: Frames, team_id: int, heatmap_type='both') -> Heatmap
Generate aggregated heatmap for entire team.
Parameters:
frames: Frames object from detectorteam_id: Team ID (0 or 1)heatmap_type: One of "histogram", "kde", or "both"
Returns:
Heatmap: Heatmap object with data
Example:
gen = HeatmapGenerator(pitch_length=105.0, pitch_width=68.0)
# Player heatmap
player_hm = gen.player(frames, player_id=7, heatmap_type='both')
player_hm.save('./heatmaps')
# Team heatmap
team_hm = gen.team(frames, team_id=0, heatmap_type='kde')
team_hm.save('./heatmaps')
Heatmap
Container for heatmap data.
Attributes:
data: Dictionary containing heatmap dataidentifier: Player or team IDprefix: Either "player" or "team"
Methods:
save(output_path: str | Path, heatmap_type='both') -> SaveResult
Save heatmap data to JSON file(s).
Parameters:
output_path: Directory path for output filesheatmap_type: One of "histogram", "kde", or "both"
Returns:
SaveResult: Object containing success/failure information
Output Format:
- Histogram:
{prefix}_{identifier}_histogram.json - KDE:
{prefix}_{identifier}_kde.json
Example:
heatmap = gen.player(frames, player_id=10)
result = heatmap.save('./output', heatmap_type='both')
SaveResult
Result object for save operations.
Attributes:
successful_paths: List of successfully saved file pathsfailed_paths: List of failed file pathserrors: List of error messages
Utility Functions
set_stream_logger(name='wunderscout', level=logging.DEBUG, format_string=None)
Configure stream logging for WunderScout.
Parameters:
name: Logger name (default: 'wunderscout')level: Logging level (e.g.,logging.INFO,logging.DEBUG)format_string: Optional custom format string
Example:
import logging
import wunderscout
wunderscout.set_stream_logger('wunderscout', level=logging.INFO)
Complete Example
import logging
import wunderscout
# Setup logging
wunderscout.set_stream_logger(name='wunderscout', level=logging.INFO)
# Initialize models
models = wunderscout.Models(
player_weights='models/player_detection.pt',
field_weights='models/field_keypoints.pt'
)
# Create detector
detector = wunderscout.Detector(models)
# Process video
frames = detector.run(
video_path='match.mp4',
output_dir='./annotated_videos'
)
# Get player and team information
all_players = frames.get_all_player_ids()
all_teams = frames.get_all_team_ids()
print(f"Detected {len(all_players)} players across {len(all_teams)} teams")
# Export tracking data
csv_result = frames.save_csvs('./tracking_data')
print(f"Exported CSV files: {csv_result.successful_paths}")
# Generate heatmaps
heatmap_gen = wunderscout.HeatmapGenerator()
# Individual player heatmaps
for player_id in all_players[:5]: # First 5 players
team_id = frames.get_team_for_player(player_id)
heatmap = heatmap_gen.player(frames, player_id, heatmap_type='both')
heatmap.save(f'./heatmaps/player_{player_id}')
# Team heatmaps
for team_id in all_teams:
heatmap = heatmap_gen.team(frames, team_id, heatmap_type='kde')
heatmap.save(f'./heatmaps/team_{team_id}')
print("Analysis complete!")
Coordinate System
WunderScout uses a normalized pitch coordinate system:
- Origin (0, 0) at top-left corner
- (1, 1) at bottom-right corner
- Default pitch dimensions: 105m × 68m (standard FIFA pitch)
Coordinates are transformed from image space to pitch space using homography matrices computed from detected field keypoints.
Limitations
- Requires clear visibility of pitch markings for accurate coordinate mapping
- Performance depends on quality of input video and model weights
- Team classification may struggle with similar team colors
- KDE heatmaps require minimum sample size and spatial variation
License
MIT License
Acknowledgments
Built with:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wunderscout-0.2.2.tar.gz.
File metadata
- Download URL: wunderscout-0.2.2.tar.gz
- Upload date:
- Size: 13.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5c0f5c87fcf9d53f649999965c7c78d29fb516c43d9381adb97ee7b10954588
|
|
| MD5 |
d860a7b72984013e72522d97f6e926a9
|
|
| BLAKE2b-256 |
1b674e8d4dc6ed22708bc5e575235835ddd833d37a896e3f98b9c8e1987baf1d
|
Provenance
The following attestation bundles were made for wunderscout-0.2.2.tar.gz:
Publisher:
publish.yml on qhuboo/wunderscout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wunderscout-0.2.2.tar.gz -
Subject digest:
f5c0f5c87fcf9d53f649999965c7c78d29fb516c43d9381adb97ee7b10954588 - Sigstore transparency entry: 1154847017
- Sigstore integration time:
-
Permalink:
qhuboo/wunderscout@a15472d8c232159a8d8129335d89e892e89453be -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/qhuboo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a15472d8c232159a8d8129335d89e892e89453be -
Trigger Event:
push
-
Statement type:
File details
Details for the file wunderscout-0.2.2-py3-none-any.whl.
File metadata
- Download URL: wunderscout-0.2.2-py3-none-any.whl
- Upload date:
- Size: 16.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ce2b26c8b33c1023967212bed2e8fd27465743f57f544cd231a1c20d9661d26
|
|
| MD5 |
6dcff07c9746d8ee6e11fab6223f2e39
|
|
| BLAKE2b-256 |
a5b8d3c477d575623a69daec9d52edb449558945c1f334820b29f883a5f89421
|
Provenance
The following attestation bundles were made for wunderscout-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on qhuboo/wunderscout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wunderscout-0.2.2-py3-none-any.whl -
Subject digest:
0ce2b26c8b33c1023967212bed2e8fd27465743f57f544cd231a1c20d9661d26 - Sigstore transparency entry: 1154847018
- Sigstore integration time:
-
Permalink:
qhuboo/wunderscout@a15472d8c232159a8d8129335d89e892e89453be -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/qhuboo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a15472d8c232159a8d8129335d89e892e89453be -
Trigger Event:
push
-
Statement type: