Skip to main content

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 detection
  • field_weights: Path to YOLO weights file for field keypoint detection
  • siglip_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 file
  • output_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 found
  • ValueError: If video format is invalid or output_dir is a file
  • OSError: If video data cannot be read
  • RuntimeError: 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 resolution
  • kde_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 detector
  • player_id: Player tracker ID
  • heatmap_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 detector
  • team_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 data
  • identifier: Player or team ID
  • prefix: 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 files
  • heatmap_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 paths
  • failed_paths: List of failed file paths
  • errors: 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wunderscout-0.2.2.tar.gz (13.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wunderscout-0.2.2-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

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

Hashes for wunderscout-0.2.2.tar.gz
Algorithm Hash digest
SHA256 f5c0f5c87fcf9d53f649999965c7c78d29fb516c43d9381adb97ee7b10954588
MD5 d860a7b72984013e72522d97f6e926a9
BLAKE2b-256 1b674e8d4dc6ed22708bc5e575235835ddd833d37a896e3f98b9c8e1987baf1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for wunderscout-0.2.2.tar.gz:

Publisher: publish.yml on qhuboo/wunderscout

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for wunderscout-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0ce2b26c8b33c1023967212bed2e8fd27465743f57f544cd231a1c20d9661d26
MD5 6dcff07c9746d8ee6e11fab6223f2e39
BLAKE2b-256 a5b8d3c477d575623a69daec9d52edb449558945c1f334820b29f883a5f89421

See more details on using hashes here.

Provenance

The following attestation bundles were made for wunderscout-0.2.2-py3-none-any.whl:

Publisher: publish.yml on qhuboo/wunderscout

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page