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

  1. Code Quality: All functions must include docstrings and type hints
  2. Testing: Include unit tests for core functionality
  3. Performance: Track execution time for processing-intensive operations
  4. 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