Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 115 additions & 59 deletions preprocessing/sports/tracking_data/tracking_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class Tracking_data:
soccer_data_provider = ["soccer"]
ultimate_data_provider = ["ultimate_track"]
ultimate_data_provider = ["UltimateTrack", "UFA"]
handball_data_provider = []
rocket_league_data_provider = []

Expand All @@ -22,64 +22,120 @@ def __new__(cls, data_provider, *args, **kwargs):


if __name__ == "__main__":
import argparse
import os

# Test Soccer tracking data
print("Testing Soccer tracking data...")
game_id = 0 # Select the index from the list of files in the data_path.
data_path = os.getcwd() + "/test/sports/event_data/data/datastadium/"

try:
# Call the function for soccer directly
soccer_tracker = Soccer_tracking_data()
tracking_home, tracking_away, jerseynum_df = (
soccer_tracker.process_datadium_tracking_data(game_id, data_path, test=True)
)

os.makedirs(os.path.dirname(data_path), exist_ok=True)
tracking_home.to_csv(
os.getcwd()
+ "/test/sports/event_data/data/datastadium/test_tracking_home.csv",
index=False,
)
tracking_away.to_csv(
os.getcwd()
+ "/test/sports/event_data/data/datastadium/test_tracking_away.csv",
index=False,
)
jerseynum_df.to_csv(
os.getcwd() + "/test/sports/event_data/data/datastadium/test_jerseynum.csv",
index=False,
)
print("Soccer test completed successfully!")
except Exception as e:
print(f"Soccer test failed: {e}")

# Test Ultimate Track data
print("\nTesting Ultimate Track data...")
ultimate_game_id = 0 # Select the first CSV file
ultimate_data_path = os.getcwd() + "/test/sports/tracking_data/data/ultimatetrack/"

try:
# Call the function for Ultimate Track directly
ultimate_tracker = Ultimate_tracking_data()
tracking_offense, tracking_defense, team_info_df = (
ultimate_tracker.process_ultimatetrack_tracking_data(
ultimate_game_id, ultimate_data_path, test=True
parser = argparse.ArgumentParser(description="Test tracking data processing")
parser.add_argument(
"data_provider",
type=str,
default="soccer",
help="Data provider to use (e.g., 'soccer', 'UltimateTrack', 'UFA')",
)
args = parser.parse_args()

data_provider = args.data_provider

if data_provider in Tracking_data.soccer_data_provider:
# Test Soccer tracking data
print("Testing Soccer tracking data...")
game_id = 0 # Select the index from the list of files in the data_path.
data_path = os.getcwd() + "/test/sports/event_data/data/datastadium/"

try:
# Call the function for soccer directly
soccer_tracker = Soccer_tracking_data()
tracking_home, tracking_away, jerseynum_df = (
soccer_tracker.process_datadium_tracking_data(
game_id, data_path, test=True
)
)

os.makedirs(os.path.dirname(data_path), exist_ok=True)
tracking_home.to_csv(
os.getcwd()
+ "/test/sports/event_data/data/datastadium/test_tracking_home.csv",
index=False,
)
tracking_away.to_csv(
os.getcwd()
+ "/test/sports/event_data/data/datastadium/test_tracking_away.csv",
index=False,
)
jerseynum_df.to_csv(
os.getcwd()
+ "/test/sports/event_data/data/datastadium/test_jerseynum.csv",
index=False,
)
print("Soccer test completed successfully!")
except Exception as e:
print(f"Soccer test failed: {e}")

elif data_provider in Tracking_data.ultimate_data_provider:
if data_provider == "UFA":
# Test UFA data
print("\nTesting UFA data...")
data_path = os.getcwd() + "/test/sports/tracking_data/UFA/2_1.txt"

try:
# Call the function for UFA directly
ufa_tracker = Ultimate_tracking_data("UFA", data_path)
tracking_offense, tracking_defense, events_df = (
ufa_tracker.preprocessing()
)

# Create output directory if it doesn't exist
output_dir = os.getcwd() + "/test/sports/tracking_data/UFA/metrica/"
base_name = os.path.splitext(os.path.basename(data_path))[0]
os.makedirs(output_dir, exist_ok=True)

tracking_offense.to_csv(
os.path.join(output_dir, f"{base_name}_home.csv"), index=False
)
tracking_defense.to_csv(
os.path.join(output_dir, f"{base_name}_away.csv"), index=False
)
events_df.to_csv(
os.path.join(output_dir, f"{base_name}_events.csv"), index=False
)
print("UFA test completed successfully!")
print(f"UFA data path: {data_path}")
except Exception as e:
print(f"UFA test failed: {e}")

elif data_provider == "UltimateTrack":
# Test Ultimate Track data
print("\nTesting Ultimate Track data...")
data_path = (
os.getcwd() + "/test/sports/tracking_data/UltimateTrack/1_1_1.csv"
)
)

# Create output directory if it doesn't exist
os.makedirs(ultimate_data_path, exist_ok=True)

tracking_offense.to_csv(
ultimate_data_path + "test_tracking_offense.csv", index=False
)
tracking_defense.to_csv(
ultimate_data_path + "test_tracking_defense.csv", index=False
)
team_info_df.to_csv(ultimate_data_path + "test_team_info.csv", index=False)
print("Ultimate Track test completed successfully!")
print(f"Ultimate Track data path: {ultimate_data_path}")
except Exception as e:
print(f"Ultimate Track test failed: {e}")

try:
# Call the function for Ultimate Track directly
ultimatetrack_tracker = Ultimate_tracking_data(
"UltimateTrack", data_path
)
tracking_offense, tracking_defense, events_df = (
ultimatetrack_tracker.preprocessing()
)

# Create output directory if it doesn't exist
output_dir = (
os.getcwd() + "/test/sports/tracking_data/UltimateTrack/metrica/"
)
base_name = os.path.splitext(os.path.basename(data_path))[0]
os.makedirs(output_dir, exist_ok=True)

tracking_offense.to_csv(
os.path.join(output_dir, f"{base_name}_home.csv"), index=False
)
tracking_defense.to_csv(
os.path.join(output_dir, f"{base_name}_away.csv"), index=False
)
events_df.to_csv(
os.path.join(output_dir, f"{base_name}_events.csv"), index=False
)
print("Ultimate Track test completed successfully!")
print(f"Ultimate Track data path: {data_path}")
except Exception as e:
print(f"Ultimate Track test failed: {e}")
77 changes: 77 additions & 0 deletions preprocessing/sports/tracking_data/ultimate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Ultimate Frisbee Tracking Data Preprocessing

This module provides preprocessing functionality for Ultimate Frisbee tracking data from multiple data providers, converting them into a standardized Metrica format for analysis.

## Overview

The Ultimate tracking data preprocessing system supports two main data providers:
- **UFA (Ultimate Frisbee Analytics)**: Professional Ultimate game data
- **Ultimate Track**: Research-grade tracking data with detailed motion features

## Data Providers

### UFA Data Provider
- **Input Format**: CSV/TXT files with player and disc positions
- **Features**: Player positions, velocities, disc tracking, holder identification
- **Output**: Metrica format with home/away team separation

### Ultimate Track Data Provider
- **Input Format**: CSV files with raw tracking data
- **Features**: Enhanced motion analysis with velocity/acceleration calculations
- **Output**: Metrica format with calculated motion features

## Architecture

```
ultimate/
├── ultimate_tracking_class.py # Main interface class
├── ufa_preprocessing/ # UFA data processing
│ ├── preprocessing.py # UFA-specific preprocessing functions
│ ├── preprocess_config.py # UFA configuration constants
│ └── README.md # UFA module documentation
└── ultimatetrack_preprocessing/ # Ultimate Track data processing
├── preprocessing.py # Ultimate Track preprocessing functions
├── preprocess_config.py # Ultimate Track configuration constants
└── __init__.py
```

## Usage

```python
from preprocessing.sports.tracking_data.ultimate import Ultimate_tracking_data

# For UFA data
ufa_tracker = Ultimate_tracking_data("UFA", "/path/to/ufa_data.csv")
home_df, away_df, events_df = ufa_tracker.preprocessing()

# For Ultimate Track data
ut_tracker = Ultimate_tracking_data("UltimateTrack", "/path/to/ut_data.csv")
home_df, away_df, events_df = ut_tracker.preprocessing()
```

## Output Format

All data providers output data in Metrica format with three DataFrames:

### Home/Away DataFrames
- **MultiIndex columns**: Team, Player ID, Coordinate (X/Y)
- **Data**: Player positions over time with disc tracking
- **Frequency**: Configurable based on data provider (10Hz for UFA, 15Hz for Ultimate Track)

### Events DataFrame
- **Columns**: Team, Type, Subtype, Period, Start Frame, Start Time, End Frame, End Time, From, To, Start X, Start Y, End X, End Y
- **Data**: Disc possession events and position data per frame

## Configuration

Each data provider has its own configuration file defining:
- Field dimensions and specifications
- Players per team (7 for Ultimate Frisbee)
- Tracking frequency and coordinate scaling
- Column mappings and processing parameters

## Dependencies

- pandas: Data manipulation and analysis
- numpy: Numerical computations
- Standard Python libraries (os, argparse for CLI usage)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# UFA preprocessing module
from .preprocessing import preprocessing_for_ufa

__all__ = [
"preprocessing_for_ufa",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""Configuration for UFA data processing.

Attributes
----------
field_length : float
The length of a Ultimate field (109.73 meters).
field_width : float
The width of a Ultimate field (37 meters).
players_per_team : int
Number of players per team in Ultimate (7).
tracking_herz : int
Frequency of tracking data (10 frames per second).
coordinate_scale : float
Scale factor for coordinate conversion.
"""

# Ultimate field specifications (in meters for UFA data)
FIELD_LENGTH: float = 109.73 # 109.73 meters total field length
FIELD_WIDTH: float = 48.77 # 48.77 meters width
PLAYING_FIELD_LENGTH: float = 73.15 # 73.15 meters playing field (without end zones)
END_ZONE_LENGTH: float = 18.29 # 18.29 meters each end zone

# Player configuration
PLAYERS_PER_TEAM: int = 7 # Standard Ultimate has 7 players per team
MAX_SUBSTITUTIONS: int = 0 # Unlimited substitutions in Ultimate

# Data processing configuration
TRACKING_HERZ: int = 10 # UFA data frame rate (10 fps)
COORDINATE_SCALE: float = 1.0 # UFA data is in meters

# Team identifiers
OFFENSE_TEAM: str = "offense"
DEFENSE_TEAM: str = "defense"
DISC_ENTITY: str = "disc"

# Data columns mapping for UFA
UFA_COLUMNS = {
"frame": "frame",
"id": "id",
"x": "x",
"y": "y",
"vx": "vx",
"vy": "vy",
"ax": "ax",
"ay": "ay",
"v_mag": "v_mag",
"a_mag": "a_mag",
"v_angle": "v_angle",
"a_angle": "a_angle",
"diff_v_a_angle": "diff_v_a_angle",
"diff_v_angle": "diff_v_angle",
"diff_a_angle": "diff_a_angle",
"class": "class",
"holder": "holder",
"closest": "closest",
"selected": "selected",
"prev_holder": "prev_holder",
"def_selected": "def_selected",
}

# Columns to remove from UFA data
COLUMNS_TO_REMOVE = ["selected", "prev_holder", "def_selected"]

# File name patterns
DEFAULT_FILE_PATTERN = r"(\d+)_(\d+)\.txt" # quarter_possession.txt
Loading