Provided modular architecture for animated bouncing ball analysis
- Organized project into src directory with subpackages (analysis, data, visualization, utils) - Added comprehensive README with project overview and structure - Implemented data loading, bounce detection, and visualization modules - Created example scripts and Jupyter notebook for project usage - Added requirements.txt for dependency management - Included output files for different ball types (golf, lacrosse, metal)
This commit is contained in:
361
src/examples/bouncing_ball_analysis.ipynb
Normal file
361
src/examples/bouncing_ball_analysis.ipynb
Normal file
@@ -0,0 +1,361 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Bouncing Ball Analysis Example\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to use the modular structure of the bouncing ball analysis project to:\n",
|
||||
"1. Load data for different ball types\n",
|
||||
"2. Detect bounces and calculate Coefficient of Restitution (COR)\n",
|
||||
"3. Create static and animated visualizations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, let's import the necessary modules and set up the environment."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from IPython.display import display, HTML\n",
|
||||
"\n",
|
||||
"# Add the src directory to the Python path\n",
|
||||
"sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname('__file__'), '..')))\n",
|
||||
"\n",
|
||||
"# Import modules from the project\n",
|
||||
"from data.loader import load_trial, GOLF_BALL_PATHS, LACROSSE_BALL_PATHS, METAL_BALL_PATHS\n",
|
||||
"from analysis.bounce_detection import process_trial, process_trials\n",
|
||||
"from visualization.static_plots import plot_position_vs_time, plot_trial_with_bounces, plot_cor_table, plot_all_cors\n",
|
||||
"from visualization.animation import create_ball_animation, create_ball_animation_with_model, display_animation\n",
|
||||
"\n",
|
||||
"# Create output directory\n",
|
||||
"output_dir = \"../../output\"\n",
|
||||
"os.makedirs(output_dir, exist_ok=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Loading and Visualizing Raw Data\n",
|
||||
"\n",
|
||||
"Let's start by loading data for a golf ball dropped from 11 inches and visualizing the raw position vs. time data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Select a specific ball and height\n",
|
||||
"ball_type = \"Golf\"\n",
|
||||
"height = \"11 inches\"\n",
|
||||
"file_path = GOLF_BALL_PATHS[height]\n",
|
||||
"\n",
|
||||
"# Load the trial data\n",
|
||||
"df = load_trial(file_path)\n",
|
||||
"print(f\"Loaded data with {len(df)} rows\")\n",
|
||||
"\n",
|
||||
"# Display the first few rows of the data\n",
|
||||
"df.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the raw position vs. time data\n",
|
||||
"plot_position_vs_time(df, height, ball_type)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Detecting Bounces and Calculating COR\n",
|
||||
"\n",
|
||||
"Now, let's detect the bounces in the data and calculate the Coefficient of Restitution (COR)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Process the trial to detect bounces and calculate COR\n",
|
||||
"result = process_trial(\n",
|
||||
" df, \n",
|
||||
" initial_height=float(height.split()[0]),\n",
|
||||
" ball_type=ball_type\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Print the results\n",
|
||||
"print(f\"Number of bounces detected: {len(result['peak_indices'])}\")\n",
|
||||
"print(f\"Average COR: {result['Average COR']:.4f}\")\n",
|
||||
"print(f\"Initial height: {result['Initial Height']} inches\")\n",
|
||||
"\n",
|
||||
"# Display the bounce heights\n",
|
||||
"if 'bounce_heights' in result:\n",
|
||||
" print(\"\\nBounce Heights (inches):\")\n",
|
||||
" for i, height in enumerate(result['bounce_heights']):\n",
|
||||
" print(f\"Bounce {i+1}: {height:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the trial with detected bounces\n",
|
||||
"plot_trial_with_bounces(\n",
|
||||
" df, \n",
|
||||
" result['peak_indices'], \n",
|
||||
" df['Position'].values,\n",
|
||||
" height, \n",
|
||||
" ball_type,\n",
|
||||
" initial_height=float(height.split()[0]),\n",
|
||||
" cor=result['Average COR']\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Creating Animations\n",
|
||||
"\n",
|
||||
"Let's create an animation of the bouncing ball."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Create a simple animation of the bouncing ball\n",
|
||||
"anim = create_ball_animation(\n",
|
||||
" df,\n",
|
||||
" ball_radius=0.5,\n",
|
||||
" fps=30,\n",
|
||||
" duration=2.0, # Only show the first 2 seconds\n",
|
||||
" title=f\"{ball_type} Ball - {height}\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Display the animation\n",
|
||||
"display_animation(anim)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Create an animation with both the actual data and an ideal model\n",
|
||||
"anim_with_model = create_ball_animation_with_model(\n",
|
||||
" df, \n",
|
||||
" result['peak_indices'], \n",
|
||||
" result['Average COR'], \n",
|
||||
" float(height.split()[0]),\n",
|
||||
" ball_radius=0.5,\n",
|
||||
" fps=30,\n",
|
||||
" duration=2.0, # Only show the first 2 seconds\n",
|
||||
" title=f\"{ball_type} Ball - {height} (with Model)\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Display the animation\n",
|
||||
"display_animation(anim_with_model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Analyzing Multiple Trials\n",
|
||||
"\n",
|
||||
"Now, let's analyze all the golf ball trials and compare the COR values."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Process all golf ball trials\n",
|
||||
"golf_trials = []\n",
|
||||
"\n",
|
||||
"for height, path in GOLF_BALL_PATHS.items():\n",
|
||||
" print(f\"Processing golf ball from {height}...\")\n",
|
||||
" \n",
|
||||
" # Load the trial data\n",
|
||||
" df = load_trial(path)\n",
|
||||
" \n",
|
||||
" # Process the trial\n",
|
||||
" result = process_trial(\n",
|
||||
" df, \n",
|
||||
" initial_height=float(height.split()[0]),\n",
|
||||
" ball_type=\"Golf\"\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Store the result\n",
|
||||
" result['Initial Height'] = float(height.split()[0])\n",
|
||||
" result['Ball Type'] = \"Golf\"\n",
|
||||
" result['Path'] = path\n",
|
||||
" golf_trials.append(result)\n",
|
||||
"\n",
|
||||
"# Create a summary DataFrame\n",
|
||||
"golf_summary = pd.DataFrame(golf_trials)\n",
|
||||
"\n",
|
||||
"# Display the summary\n",
|
||||
"golf_summary[['Initial Height', 'Average COR', 'Num Bounces']]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot COR vs. initial height for golf balls\n",
|
||||
"plot_cor_table(golf_summary, \"Golf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Comparing Different Ball Types\n",
|
||||
"\n",
|
||||
"Finally, let's compare the COR values for different ball types."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Process lacrosse ball trials\n",
|
||||
"lacrosse_trials = []\n",
|
||||
"\n",
|
||||
"for height, path in LACROSSE_BALL_PATHS.items():\n",
|
||||
" print(f\"Processing lacrosse ball from {height}...\")\n",
|
||||
" \n",
|
||||
" # Load the trial data\n",
|
||||
" df = load_trial(path)\n",
|
||||
" \n",
|
||||
" # Process the trial\n",
|
||||
" result = process_trial(\n",
|
||||
" df, \n",
|
||||
" initial_height=float(height.split()[0]),\n",
|
||||
" ball_type=\"Lacrosse\"\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Store the result\n",
|
||||
" result['Initial Height'] = float(height.split()[0])\n",
|
||||
" result['Ball Type'] = \"Lacrosse\"\n",
|
||||
" result['Path'] = path\n",
|
||||
" lacrosse_trials.append(result)\n",
|
||||
"\n",
|
||||
"# Create a summary DataFrame\n",
|
||||
"lacrosse_summary = pd.DataFrame(lacrosse_trials)\n",
|
||||
"\n",
|
||||
"# Process metal ball trials\n",
|
||||
"metal_trials = []\n",
|
||||
"\n",
|
||||
"for height, path in METAL_BALL_PATHS.items():\n",
|
||||
" print(f\"Processing metal ball from {height}...\")\n",
|
||||
" \n",
|
||||
" # Load the trial data\n",
|
||||
" df = load_trial(path)\n",
|
||||
" \n",
|
||||
" # Process the trial\n",
|
||||
" result = process_trial(\n",
|
||||
" df, \n",
|
||||
" initial_height=float(height.split()[0]),\n",
|
||||
" ball_type=\"Metal\"\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" # Store the result\n",
|
||||
" result['Initial Height'] = float(height.split()[0])\n",
|
||||
" result['Ball Type'] = \"Metal\"\n",
|
||||
" result['Path'] = path\n",
|
||||
" metal_trials.append(result)\n",
|
||||
"\n",
|
||||
"# Create a summary DataFrame\n",
|
||||
"metal_summary = pd.DataFrame(metal_trials)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot all CORs together\n",
|
||||
"plot_all_cors(golf_summary, lacrosse_summary, metal_summary)\n",
|
||||
"\n",
|
||||
"# Save the combined plot\n",
|
||||
"plt.savefig(os.path.join(output_dir, \"all_cors_comparison.png\"), dpi=300)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Conclusion\n",
|
||||
"\n",
|
||||
"In this notebook, we've demonstrated how to use the modular structure of the bouncing ball analysis project to:\n",
|
||||
"1. Load data for different ball types\n",
|
||||
"2. Detect bounces and calculate Coefficient of Restitution (COR)\n",
|
||||
"3. Create static and animated visualizations\n",
|
||||
"4. Compare COR values for different ball types\n",
|
||||
"\n",
|
||||
"The modular structure makes it easy to perform these analyses and visualizations with clean, reusable code."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.10"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
Reference in New Issue
Block a user