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:
Metadata
Release files for wunderscout 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wunderscout-0.2.2.tar.gz | 13.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wunderscout-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.2 kB
Release files / wunderscout-0.2.2.tar.gz
| Download URL | wunderscout-0.2.2.tar.gz |
|---|---|
| Size | 13.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f5c0f5c87fcf9d53f649999965c7c78d29fb516c43d9381adb97ee7b10954588
|
|
BLAKE2b-256 checksum How to use checksums |
1b674e8d4dc6ed22708bc5e575235835ddd833d37a896e3f98b9c8e1987baf1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logRelease files / wunderscout-0.2.2-py3-none-any.whl
| Download URL | wunderscout-0.2.2-py3-none-any.whl |
|---|---|
| Size | 16.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0ce2b26c8b33c1023967212bed2e8fd27465743f57f544cd231a1c20d9661d26
|
|
BLAKE2b-256 checksum How to use checksums |
a5b8d3c477d575623a69daec9d52edb449558945c1f334820b29f883a5f89421
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency log