Chapter 24 Exercises: Computer Vision and Tracking Data
Exercise Set Overview
These exercises progress from basic tracking data manipulation to advanced spatial analysis and visualization.
Level 1: Foundational Exercises
Exercise 24.1: Loading and Exploring Tracking Data
Objective: Load and explore the structure of tracking data.
Task: Given a CSV file containing tracking data for a single play, perform initial exploration.
# Starter code
import pandas as pd
import numpy as np
def load_and_explore_tracking(filepath: str) -> dict:
"""
Load tracking data and return summary statistics.
Returns:
Dictionary with:
- n_frames: Number of frames in the play
- n_players: Number of unique players
- frame_rate: Estimated frame rate (frames per second)
- duration: Play duration in seconds
- teams: List of team identifiers
"""
# Your code here
pass
# Test with sample data
# result = load_and_explore_tracking('sample_play.csv')
# print(result)
Expected Output Structure:
{
'n_frames': 45,
'n_players': 22,
'frame_rate': 10,
'duration': 4.5,
'teams': ['home', 'away', 'football']
}
Exercise 24.2: Calculating Basic Speed Metrics
Objective: Calculate speed from position data.
Task: Implement a function that calculates instantaneous speed for each player at each frame.
def calculate_speed(df: pd.DataFrame, frame_rate: int = 10) -> pd.DataFrame:
"""
Calculate speed from position changes.
Args:
df: DataFrame with columns ['player_id', 'frame_id', 'x', 'y']
frame_rate: Frames per second
Returns:
DataFrame with added 'calculated_speed' column (yards/second)
"""
# Your code here
pass
# Verification
# - Speed should never be negative
# - Maximum speed should be reasonable (< 12 yards/second)
# - First frame for each player should have NaN speed
Exercise 24.3: Field Position Standardization
Objective: Standardize coordinates so offense always moves left-to-right.
Task: Implement coordinate transformation based on play direction.
def standardize_coordinates(df: pd.DataFrame,
play_direction: str) -> pd.DataFrame:
"""
Standardize coordinates so offense moves left-to-right.
Args:
df: Tracking DataFrame with x, y, direction, orientation columns
play_direction: 'left' or 'right' (direction offense is moving)
Returns:
DataFrame with transformed coordinates
"""
# Field dimensions: 120 yards long, 53.3 yards wide
# Your code here
pass
Exercise 24.4: Identifying the Ball Carrier
Objective: Identify which player has the ball based on proximity.
Task: For each frame after the snap, determine the ball carrier.
def identify_ball_carrier(tracking_df: pd.DataFrame) -> pd.DataFrame:
"""
Identify ball carrier based on proximity to football.
Args:
tracking_df: Tracking data with 'team' column having 'football' for ball
Returns:
DataFrame with 'is_ball_carrier' boolean column
"""
# Hint: Ball carrier is typically the offensive player closest to the ball
# Your code here
pass
Level 2: Intermediate Exercises
Exercise 24.5: Route Classification
Objective: Classify receiver routes based on their paths.
Task: Implement a route classifier using depth and lateral movement.
class RouteClassifier:
"""Classify receiver routes from tracking data."""
ROUTE_TYPES = ['go', 'slant', 'out', 'in', 'curl', 'corner', 'post', 'flat', 'other']
def __init__(self):
pass
def extract_route_features(self,
receiver_df: pd.DataFrame,
snap_frame: int) -> dict:
"""
Extract features from receiver's path.
Returns:
Dictionary with:
- depth: Yards gained downfield
- lateral: Lateral movement (positive = toward sideline)
- break_point: Frame where direction changed most
- max_speed: Maximum speed during route
"""
pass
def classify_route(self, features: dict) -> str:
"""Classify route type from features."""
pass
# Test cases:
# - Go route: depth > 15, lateral < 5
# - Slant: depth < 8, lateral > 5 crossing toward middle
# - Out: depth < 8, lateral > 5 toward sideline
# - Curl: depth 8-15, ends moving backward
Exercise 24.6: Separation Analysis
Objective: Calculate receiver-defender separation throughout a route.
Task: Track separation between a receiver and their assigned defender.
def analyze_separation(receiver_df: pd.DataFrame,
defender_df: pd.DataFrame,
snap_frame: int) -> dict:
"""
Analyze separation between receiver and defender.
Returns:
Dictionary with:
- separation_at_throw: Separation when ball was thrown
- max_separation: Maximum separation achieved
- avg_separation: Average separation
- separation_created: Max separation - separation at snap
- cushion_at_snap: Initial separation
"""
# Your code here
pass
# Expected insights:
# - Good separation > 2 yards at throw
# - Elite separation creation > 3 yards
Exercise 24.7: Formation Detection
Objective: Automatically detect offensive formation from pre-snap positions.
Task: Classify formations based on player alignments.
class FormationDetector:
"""Detect offensive formations from tracking data."""
def detect_formation(self,
offense_df: pd.DataFrame,
snap_frame: int) -> dict:
"""
Detect offensive formation.
Returns:
Dictionary with:
- personnel: e.g., '11' (1 RB, 1 TE)
- backfield: e.g., 'Shotgun', 'Under Center', 'Pistol'
- receiver_alignment: e.g., '3x1', '2x2', 'Trips'
- strength: 'Left' or 'Right'
"""
pass
def _count_personnel(self, offense_df: pd.DataFrame) -> str:
"""Count RBs and TEs."""
pass
def _detect_backfield(self, qb_df: pd.DataFrame,
center_df: pd.DataFrame) -> str:
"""Determine backfield alignment."""
pass
Exercise 24.8: Defensive Coverage Classification
Objective: Classify defensive coverage schemes.
Task: Determine if defense is playing man or zone coverage.
def classify_coverage(defense_df: pd.DataFrame,
offense_df: pd.DataFrame,
snap_frame: int,
throw_frame: int) -> dict:
"""
Classify defensive coverage type.
Returns:
Dictionary with:
- coverage_type: 'man', 'zone', or 'mixed'
- confidence: 0-1 confidence score
- evidence: List of supporting observations
"""
# Hints for classification:
# - Man coverage: Defenders mirror receiver movements
# - Zone coverage: Defenders stay in areas, don't follow receivers
# - Track correlation between defender and nearest receiver movements
pass
Level 3: Advanced Exercises
Exercise 24.9: Animated Play Visualization
Objective: Create animated visualization of a play.
Task: Build an animated play viewer using matplotlib.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Circle, Rectangle
class PlayAnimator:
"""Create animated visualizations of football plays."""
FIELD_LENGTH = 120
FIELD_WIDTH = 53.3
def __init__(self, tracking_df: pd.DataFrame):
self.df = tracking_df
self.fig = None
self.ax = None
def create_field(self) -> plt.Axes:
"""Draw football field."""
# Include:
# - Green background
# - Yard lines every 10 yards
# - Hash marks
# - End zones
# - Yard numbers
pass
def animate(self, save_path: str = None) -> FuncAnimation:
"""Create animation of the play."""
# Each frame should show:
# - All 22 players as colored circles
# - Jersey numbers inside circles
# - Football position
# - Current game situation text
pass
# Bonus: Add trails showing recent positions
Exercise 24.10: Expected Yards After Catch (YAC) Model
Objective: Build a model to predict YAC based on spatial context at catch.
Task: Use tracking data features to predict yards after catch.
from sklearn.ensemble import GradientBoostingRegressor
class ExpectedYACModel:
"""Predict expected yards after catch from spatial features."""
def __init__(self):
self.model = GradientBoostingRegressor(n_estimators=100)
self.feature_names = []
def extract_features(self,
tracking_df: pd.DataFrame,
catch_frame: int,
receiver_id: str) -> np.ndarray:
"""
Extract features at moment of catch.
Features to include:
- Receiver speed and direction
- Distance to nearest defender
- Number of defenders within 5 yards
- Field position (yards to goal)
- Space in direction of travel
- Sideline distance
"""
pass
def train(self, X: np.ndarray, y: np.ndarray):
"""Train the model."""
pass
def predict(self, tracking_df: pd.DataFrame,
catch_frame: int,
receiver_id: str) -> float:
"""Predict expected YAC."""
pass
# Evaluation: Compare predicted vs actual YAC
# Good model should have MAE < 3 yards
Exercise 24.11: Pressure Detection
Objective: Detect quarterback pressure from tracking data.
Task: Identify when the QB faces pressure based on defender proximity.
class PressureDetector:
"""Detect quarterback pressure from tracking data."""
PRESSURE_RADIUS = 3.0 # yards
HURRY_RADIUS = 5.0 # yards
def __init__(self):
pass
def calculate_pocket_metrics(self,
tracking_df: pd.DataFrame,
frame: int) -> dict:
"""
Calculate pocket metrics at a given frame.
Returns:
- pocket_area: Area of convex hull around OL
- time_to_pressure: Estimated time until defender reaches QB
- nearest_defender_dist: Distance to closest defender
- defenders_in_pocket: Count of defenders within pocket
"""
pass
def detect_pressure_event(self,
tracking_df: pd.DataFrame,
snap_frame: int) -> dict:
"""
Detect if/when pressure occurred.
Returns:
- pressure_detected: Boolean
- pressure_frame: Frame when pressure occurred
- pressure_type: 'hit', 'hurry', 'sack', or 'none'
- time_to_pressure: Seconds from snap
- pressure_source: Player who generated pressure
"""
pass
# Test against known pressure plays
Exercise 24.12: Optimal Target Selection
Objective: Determine the optimal target based on spatial analysis.
Task: At each frame, identify which receiver offers the best opportunity.
class OptimalTargetAnalyzer:
"""Analyze optimal target selection for QB."""
def __init__(self):
pass
def score_target(self,
receiver_df: pd.DataFrame,
defense_df: pd.DataFrame,
qb_df: pd.DataFrame,
frame: int) -> float:
"""
Score a potential target based on:
- Separation from nearest defender
- Distance from QB (throwing difficulty)
- Field position (yards to gain)
- Throwing window size
- Probability of completion (estimated)
"""
pass
def find_optimal_target(self,
tracking_df: pd.DataFrame,
frame: int) -> dict:
"""
Find the best target at a given frame.
Returns:
- optimal_target: Player ID of best option
- score: Quality score
- separation: Separation yards
- distance: Distance from QB
- comparison: Scores for all eligible receivers
"""
pass
def analyze_decision_quality(self,
tracking_df: pd.DataFrame,
actual_target: str,
throw_frame: int) -> dict:
"""
Evaluate if QB made optimal decision.
Returns:
- optimal_target: Who should have been targeted
- actual_target: Who was targeted
- decision_quality: Score comparing actual vs optimal
- missed_opportunity: Yards left on table (expected)
"""
pass
Level 4: Expert Challenges
Exercise 24.13: Complete Play Analysis Pipeline
Objective: Build an end-to-end play analysis system.
Task: Create a comprehensive play analyzer that processes raw tracking data and produces a complete analysis report.
class ComprehensivePlayAnalyzer:
"""Full play analysis from tracking data."""
def __init__(self):
self.route_classifier = RouteClassifier()
self.formation_detector = FormationDetector()
self.pressure_detector = PressureDetector()
self.target_analyzer = OptimalTargetAnalyzer()
def analyze_play(self, tracking_df: pd.DataFrame,
play_info: dict) -> dict:
"""
Comprehensive play analysis.
Returns full analysis including:
1. Pre-snap analysis (formation, personnel, motion)
2. Route analysis (all receivers)
3. Protection analysis (time, pressure)
4. Decision analysis (target selection)
5. Outcome analysis (EPA, success)
6. Visualizations (static and animated)
"""
pass
def generate_report(self, analysis: dict) -> str:
"""Generate human-readable analysis report."""
pass
def compare_to_expectations(self,
analysis: dict,
historical_data: pd.DataFrame) -> dict:
"""Compare play outcome to historical expectations."""
pass
Deliverables: 1. Complete analysis for 10 sample plays 2. Comparison report showing variance from expectations 3. Recommendations for improvement
Exercise 24.14: Real-Time Tracking Processor
Objective: Process tracking data in real-time during a game.
Task: Build a streaming processor that updates analytics as tracking data arrives.
import asyncio
from collections import deque
class RealTimeTrackingProcessor:
"""Process tracking data in real-time."""
def __init__(self, buffer_size: int = 100):
self.frame_buffer = deque(maxlen=buffer_size)
self.current_play_frames = []
self.play_active = False
async def process_frame(self, frame: dict) -> dict:
"""
Process incoming tracking frame.
Returns real-time metrics:
- ball_carrier_speed
- pocket_integrity
- coverage_type
- separation_leader
- pressure_probability
"""
pass
def detect_play_boundaries(self, frame: dict) -> str:
"""Detect play start/end from tracking patterns."""
# Returns: 'snap', 'play_end', or 'in_progress'
pass
async def run(self, frame_generator):
"""Main processing loop."""
async for frame in frame_generator:
result = await self.process_frame(frame)
yield result
# Performance requirement: Process 25 frames/second with < 10ms latency
Submission Guidelines
- Code Quality: All functions must include docstrings and type hints
- Testing: Include unit tests for core functionality
- Performance: Track execution time for processing-intensive operations
- Visualization: Include at least 3 visualizations with your solutions
Evaluation Criteria
| Level | Criteria | Points |
|---|---|---|
| Level 1 | Correct implementation, clean code | 25 |
| Level 2 | Proper algorithms, edge case handling | 30 |
| Level 3 | Advanced techniques, good visualization | 30 |
| Level 4 | Complete system, production quality | 15 |
Resources
- NFL Big Data Bowl datasets (Kaggle)
- matplotlib animation documentation
- scipy.spatial for geometric calculations
- networkx for graph-based spatial analysis