Scouting and vision tools for YOLO and sports analytics.
Project description
wunderscout
A Python library for extracting player and ball tracking data from soccer match footage using YOLO, Siglip embeddings, and homography.
Features
- Detection & Tracking: Uses YOLO for player/ball/pitch-keypoint detection and ByteTrack for temporal consistency.
- Automated Team Clustering: Groups players into teams using Siglip vision transformer embeddings and K-Means clustering via UMAP dimensionality reduction.
- Pitch Mapping: Transforms 2D image coordinates to a normalized 0-1 coordinate system using pitch keypoint homography.
- Goalkeeper Attribution: Assigns goalkeepers to teams based on proximity to team centroids.
- Data Export: Generates Team1 and Team2 CSV files containing frame-by-frame XY coordinates.
- Heatmap Generation: Creates histogram and KDE-based spatial density maps for individual players and teams.
Installation
pip install wunderscout
Quick Start
from wunderscout import Detector, DataExporter, HeatmapGenerator
# Initialize with paths to trained YOLO weights
detector = Detector(
player_weights="players.pt",
field_weights="pitch.pt"
)
# Run processing
result = detector.run(
video_path="input_match.mp4",
output_video_path="output_match.mp4"
)
# Export tracking data to CSV
exporter = DataExporter()
exporter.save_csvs(result, "output/tracking.csv")
# Generate heatmaps
heatmap_gen = HeatmapGenerator()
player_heatmap = heatmap_gen.generate_player_heatmap(result, player_id=5, method="both")
heatmap_gen.save_heatmap(player_heatmap, "output/player_5.json")
Core Components
Detector
Main pipeline coordinator that orchestrates detection, tracking, and classification.
Parameters:
player_weights(str): Path to YOLO player detection modelfield_weights(str): Path to YOLO field keypoint detection model
Methods:
run(video_path, output_video_path=None): Process video and returnTrackingResult
Models
Handles YOLO detection and SiGLIP embedding extraction.
Key Methods:
detect_players(frame, conf=0.3): Detect players, goalkeepers, referees, and balldetect_field(frame, conf=0.3): Detect pitch keypoints for homographyget_embeddings(pil_crops, batch_size=32): Extract visual embeddings for team classificationget_calibration_crops(video_path, stride=30): Sample player crops for calibration
Detection Class IDs:
- 0: Ball
- 1: Goalkeeper
- 2: Player
- 3: Referee
TeamClassifier
Clusters players into teams using UMAP + K-Means with temporal smoothing.
Methods:
fit(embeddings): Train clustering model on calibration embeddingsget_consensus_team(tracker_id, embedding): Assign team ID with 50-frame rolling consensusresolve_goalkeepers_team_id(players, goalkeepers): Assign goalkeepers by centroid proximityget_final_assignments(): Get final team assignments (dict: tracker_id → team_id)
PitchMapper
Projects pixel coordinates to normalized pitch coordinates [0, 1] × [0, 1] using homography.
Methods:
get_matrix(keypoints_xy, keypoints_conf): Compute homography from detected pitch keypoints (requires ≥4 keypoints with conf > 0.5)transform(points, H=None): Transform points to normalized pitch coordinates
HeatmapGenerator
Generates spatial density maps using histogram binning and/or Gaussian KDE.
Parameters:
pitch_length(float): Pitch length in meters (default: 105.0)pitch_width(float): Pitch width in meters (default: 68.0)histogram_bins(tuple): Histogram resolution (default: (50, 34))kde_grid_size(tuple): KDE grid resolution (default: (100, 68))min_samples_for_kde(int): Minimum samples for KDE (default: 10)
Methods:
generate_player_heatmap(result, player_id, method="both"): Generate heatmap for one playergenerate_team_heatmap(result, team, method="both"): Generate aggregated team heatmapgenerate_all_players_heatmaps(result, method="both"): Generate heatmaps for all playerssave_heatmap(heatmap_data, output_path, pretty=False): Save heatmap to JSON
DataExporter
Exports tracking data to CSV format.
Methods:
save_csvs(result, output_path): Export tracking data (creates{base_name}_Team1.csvand{base_name}_Team2.csv)
TrackingResult
Data class containing tracking results.
Attributes:
frames(dict): Frame-by-frame tracking data{frame_idx: {"players": {player_id: (x, y)}, "ball": (x, y) or None}}team_assignments(dict): Player ID → Team ID mappingtotal_frames(int): Total number of processed framesfps(float): Video frame rate
Methods:
get_team_players(team): Get player IDs for team 0 or 1get_all_player_ids(): Get all tracked player IDsget_player_trajectory(player_id): Get position history for one playerget_ball_trajectory(): Get ball position history
Training Custom Models
from wunderscout import Trainer
trainer = Trainer(api_key="your_roboflow_api_key")
# Train player detection model
trainer.train_players(
workspace="soccer-analytics",
project="player-detection",
version=1,
epochs=300,
output_dir="runs/training/player"
)
# Train field keypoint model
trainer.train_field(
workspace="soccer-analytics",
project="field-keypoints",
version=1,
epochs=300,
output_dir="runs/training/field"
)
Output Formats
CSV Export
Two CSV files (one per team) with structure:
,,,Team1,Team1,Team1,Team1,,
,,,3,3,7,7,,
Period,Frame,Time [s],Player3_X,Player3_Y,Player7_X,Player7_Y,Ball_X,Ball_Y
1,0,0.00,0.234,0.567,0.789,0.345,0.500,0.500
1,1,0.04,0.235,0.568,NaN,NaN,0.501,0.499
Coordinate System:
- Range: [0, 1] × [0, 1]
- (0, 0) = Top-left corner of pitch
- (1, 1) = Bottom-right corner of pitch
- Missing data: "NaN"
Heatmap JSON
{
"player_id": 5,
"sample_count": 1500,
"histogram": {
"xedges": [0.0, 2.1, 4.2, ..., 105.0],
"yedges": [0.0, 2.0, 4.0, ..., 68.0],
"values": [[count_00, count_01, ...], [count_10, count_11, ...], ...]
},
"kde": {
"x": [0.0, 1.05, 2.1, ..., 105.0],
"y": [0.0, 1.0, 2.0, ..., 68.0],
"values": [[density_00, density_01, ...], [density_10, density_11, ...], ...]
}
}
Pitch Coordinate System
Normalized coordinate system mapping to standard football pitch (105m × 68m).
Coordinate Range:
- X-axis: 0.0 (left goal line) → 1.0 (right goal line)
- Y-axis: 0.0 (top sideline) → 1.0 (bottom sideline)
Key Landmarks:
| Landmark | X | Y | Description |
|---|---|---|---|
| Left goal (top post) | 0.000 | 0.365 | Top edge of left goal |
| Left goal (bottom post) | 0.000 | 0.635 | Bottom edge of left goal |
| Left penalty spot | 0.105 | 0.500 | 11m from goal line |
| Center circle | 0.500 | 0.500 | Pitch center |
| Right penalty spot | 0.895 | 0.500 | 11m from goal line |
| Right goal (top post) | 1.000 | 0.365 | Top edge of right goal |
| Right goal (bottom post) | 1.000 | 0.635 | Bottom edge of right goal |
Dependencies
ultralyticssupervisiontransformersumap-learnscikit-learnopencv-pythonscipymore-itertoolstorchnumpyroboflow
License
MIT
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.1.15.tar.gz.
File metadata
- Download URL: wunderscout-0.1.15.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca4ebd5f27383c69ad1ad9906b2b97b51026057b5dd340da458b007ae6fc6972
|
|
| MD5 |
525acf2f40901de526b8b39d8c3b213f
|
|
| BLAKE2b-256 |
0e777169450424c083d4df35924e2f39fd32bc401f58318b1010a5b5b6ea8165
|
Provenance
The following attestation bundles were made for wunderscout-0.1.15.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.1.15.tar.gz -
Subject digest:
ca4ebd5f27383c69ad1ad9906b2b97b51026057b5dd340da458b007ae6fc6972 - Sigstore transparency entry: 854397644
- Sigstore integration time:
-
Permalink:
qhuboo/wunderscout@8508ebee92ced8d82053aed984e85d02601eb269 -
Branch / Tag:
refs/tags/v0.1.15 - Owner: https://github.com/qhuboo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8508ebee92ced8d82053aed984e85d02601eb269 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wunderscout-0.1.15-py3-none-any.whl.
File metadata
- Download URL: wunderscout-0.1.15-py3-none-any.whl
- Upload date:
- Size: 15.9 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 |
dbf90fe09c9cab945110fb25af2c705870bfb03d848af9514808c43536e3dd14
|
|
| MD5 |
eed20687e4e9c7fd40159046be297211
|
|
| BLAKE2b-256 |
2938806a3cb9091dedcdc73fb7138e3c06695a3398064ce17cae913816083292
|
Provenance
The following attestation bundles were made for wunderscout-0.1.15-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.1.15-py3-none-any.whl -
Subject digest:
dbf90fe09c9cab945110fb25af2c705870bfb03d848af9514808c43536e3dd14 - Sigstore transparency entry: 854397646
- Sigstore integration time:
-
Permalink:
qhuboo/wunderscout@8508ebee92ced8d82053aed984e85d02601eb269 -
Branch / Tag:
refs/tags/v0.1.15 - Owner: https://github.com/qhuboo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8508ebee92ced8d82053aed984e85d02601eb269 -
Trigger Event:
push
-
Statement type: