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:
1
src/utils/__init__.py
Normal file
1
src/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Utils package initialization
|
||||
80
src/utils/cleanup.py
Normal file
80
src/utils/cleanup.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Cleanup script to remove extraneous files from the root directory.
|
||||
|
||||
This script removes files that have already been organized into the src directory structure.
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
import sys
|
||||
|
||||
def confirm_deletion(files):
|
||||
"""
|
||||
Ask for confirmation before deleting files.
|
||||
|
||||
Parameters:
|
||||
files (list): List of files to delete
|
||||
|
||||
Returns:
|
||||
bool: True if user confirms deletion, False otherwise
|
||||
"""
|
||||
if not files:
|
||||
print("No files to delete.")
|
||||
return False
|
||||
|
||||
print("The following files will be deleted:")
|
||||
for file in files:
|
||||
print(f" - {file}")
|
||||
|
||||
response = input("\nAre you sure you want to delete these files? (y/n): ")
|
||||
return response.lower() == 'y'
|
||||
|
||||
|
||||
def cleanup_root_directory():
|
||||
"""
|
||||
Remove extraneous files from the root directory.
|
||||
"""
|
||||
# Files to remove
|
||||
python_files = [
|
||||
"lab2_part1.py",
|
||||
"lab2_part2.py",
|
||||
"lab2_part1_animated.py",
|
||||
"lab2_part2_animated.py",
|
||||
"animate_bouncing_balls.py"
|
||||
]
|
||||
|
||||
# Data files
|
||||
data_files = glob.glob("*.csv")
|
||||
|
||||
# Image files
|
||||
image_files = glob.glob("*.png")
|
||||
|
||||
# Combine all files
|
||||
all_files = python_files + data_files + image_files
|
||||
|
||||
# Filter out files that don't exist
|
||||
files_to_delete = [file for file in all_files if os.path.exists(file)]
|
||||
|
||||
# Ask for confirmation
|
||||
if confirm_deletion(files_to_delete):
|
||||
# Delete files
|
||||
for file in files_to_delete:
|
||||
try:
|
||||
os.remove(file)
|
||||
print(f"Deleted: {file}")
|
||||
except Exception as e:
|
||||
print(f"Error deleting {file}: {e}")
|
||||
|
||||
print("\nCleanup complete!")
|
||||
else:
|
||||
print("\nCleanup cancelled.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check if script is run from the root directory
|
||||
if not os.path.exists("src/utils/cleanup.py"):
|
||||
print("Error: This script must be run from the project root directory.")
|
||||
print("Please run: python src/utils/cleanup.py")
|
||||
sys.exit(1)
|
||||
|
||||
cleanup_root_directory()
|
||||
170
src/utils/helpers.py
Normal file
170
src/utils/helpers.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Helper functions for the bouncing ball analysis project.
|
||||
"""
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.ticker import MaxNLocator
|
||||
|
||||
|
||||
def ensure_directory(directory):
|
||||
"""
|
||||
Ensure that a directory exists, creating it if necessary.
|
||||
|
||||
Parameters:
|
||||
directory (str): Path to the directory
|
||||
|
||||
Returns:
|
||||
str: Path to the directory
|
||||
"""
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
return directory
|
||||
|
||||
|
||||
def extract_height_from_path(path):
|
||||
"""
|
||||
Extract the initial height from a file path.
|
||||
|
||||
Parameters:
|
||||
path (str): Path to the file
|
||||
|
||||
Returns:
|
||||
float: Initial height in inches
|
||||
"""
|
||||
# Extract the filename without extension
|
||||
filename = os.path.basename(path).split('.')[0]
|
||||
|
||||
# Extract the height value
|
||||
for part in filename.split('_'):
|
||||
try:
|
||||
return float(part)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# If no height found, return None
|
||||
return None
|
||||
|
||||
|
||||
def smooth_data(data, window_size=5):
|
||||
"""
|
||||
Apply a simple moving average to smooth data.
|
||||
|
||||
Parameters:
|
||||
data (numpy.ndarray): Data to smooth
|
||||
window_size (int): Size of the moving average window
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: Smoothed data
|
||||
"""
|
||||
return np.convolve(data, np.ones(window_size)/window_size, mode='valid')
|
||||
|
||||
|
||||
def calculate_statistics(values):
|
||||
"""
|
||||
Calculate basic statistics for a set of values.
|
||||
|
||||
Parameters:
|
||||
values (list or numpy.ndarray): Values to analyze
|
||||
|
||||
Returns:
|
||||
dict: Dictionary containing statistics
|
||||
"""
|
||||
values = np.array(values)
|
||||
return {
|
||||
'mean': np.mean(values),
|
||||
'median': np.median(values),
|
||||
'std': np.std(values),
|
||||
'min': np.min(values),
|
||||
'max': np.max(values),
|
||||
'count': len(values)
|
||||
}
|
||||
|
||||
|
||||
def format_statistics(stats):
|
||||
"""
|
||||
Format statistics as a string.
|
||||
|
||||
Parameters:
|
||||
stats (dict): Dictionary containing statistics
|
||||
|
||||
Returns:
|
||||
str: Formatted statistics string
|
||||
"""
|
||||
return (
|
||||
f"Mean: {stats['mean']:.4f}\n"
|
||||
f"Median: {stats['median']:.4f}\n"
|
||||
f"Std Dev: {stats['std']:.4f}\n"
|
||||
f"Min: {stats['min']:.4f}\n"
|
||||
f"Max: {stats['max']:.4f}\n"
|
||||
f"Count: {stats['count']}"
|
||||
)
|
||||
|
||||
|
||||
def create_bar_chart(data, x_label, y_label, title, integer_ticks=True):
|
||||
"""
|
||||
Create a bar chart from data.
|
||||
|
||||
Parameters:
|
||||
data (dict): Dictionary mapping categories to values
|
||||
x_label (str): Label for the x-axis
|
||||
y_label (str): Label for the y-axis
|
||||
title (str): Title for the chart
|
||||
integer_ticks (bool): Whether to use integer ticks on the y-axis
|
||||
|
||||
Returns:
|
||||
matplotlib.figure.Figure: The figure object
|
||||
"""
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
|
||||
categories = list(data.keys())
|
||||
values = list(data.values())
|
||||
|
||||
bars = ax.bar(categories, values, color='skyblue', edgecolor='black')
|
||||
|
||||
# Add value labels on top of bars
|
||||
for bar in bars:
|
||||
height = bar.get_height()
|
||||
ax.text(
|
||||
bar.get_x() + bar.get_width() / 2.,
|
||||
height + 0.02 * max(values),
|
||||
f'{height:.3f}',
|
||||
ha='center',
|
||||
va='bottom',
|
||||
fontsize=10
|
||||
)
|
||||
|
||||
ax.set_xlabel(x_label, fontsize=12)
|
||||
ax.set_ylabel(y_label, fontsize=12)
|
||||
ax.set_title(title, fontsize=14, pad=15)
|
||||
|
||||
if integer_ticks:
|
||||
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
def save_dataframe_to_csv(df, filename, index=False):
|
||||
"""
|
||||
Save a DataFrame to a CSV file.
|
||||
|
||||
Parameters:
|
||||
df (pandas.DataFrame): DataFrame to save
|
||||
filename (str): Path to the output file
|
||||
index (bool): Whether to include the index in the output
|
||||
|
||||
Returns:
|
||||
str: Path to the saved file
|
||||
"""
|
||||
# Ensure the directory exists
|
||||
directory = os.path.dirname(filename)
|
||||
if directory:
|
||||
ensure_directory(directory)
|
||||
|
||||
# Save the DataFrame
|
||||
df.to_csv(filename, index=index)
|
||||
|
||||
return filename
|
||||
96
src/utils/organize_files.py
Normal file
96
src/utils/organize_files.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Script to organize data files into the new directory structure.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
def create_data_directories():
|
||||
"""
|
||||
Create the necessary directories for data organization.
|
||||
"""
|
||||
# Create data directories
|
||||
os.makedirs("src/data/golf", exist_ok=True)
|
||||
os.makedirs("src/data/lacrosse", exist_ok=True)
|
||||
os.makedirs("src/data/metal", exist_ok=True)
|
||||
os.makedirs("src/data/images", exist_ok=True)
|
||||
|
||||
# Create output directory
|
||||
os.makedirs("output", exist_ok=True)
|
||||
|
||||
print("Created directory structure for data organization.")
|
||||
|
||||
|
||||
def move_data_files():
|
||||
"""
|
||||
Move data files to their appropriate directories.
|
||||
"""
|
||||
# Move golf ball data
|
||||
for file in glob.glob("golf_*.csv"):
|
||||
shutil.copy(file, os.path.join("src/data/golf", file))
|
||||
print(f"Copied {file} to src/data/golf/")
|
||||
|
||||
# Move lacrosse ball data
|
||||
for file in glob.glob("l_*.csv"):
|
||||
shutil.copy(file, os.path.join("src/data/lacrosse", file))
|
||||
print(f"Copied {file} to src/data/lacrosse/")
|
||||
|
||||
# Move metal ball data
|
||||
for file in glob.glob("metal_*.csv"):
|
||||
shutil.copy(file, os.path.join("src/data/metal", file))
|
||||
print(f"Copied {file} to src/data/metal/")
|
||||
|
||||
# Move image files
|
||||
for file in glob.glob("*.png"):
|
||||
shutil.copy(file, os.path.join("src/data/images", file))
|
||||
print(f"Copied {file} to src/data/images/")
|
||||
|
||||
|
||||
def move_script_files():
|
||||
"""
|
||||
Move script files to their appropriate directories.
|
||||
"""
|
||||
# Move original scripts to src directory
|
||||
if os.path.exists("lab2_part1.py"):
|
||||
shutil.copy("lab2_part1.py", os.path.join("src", "lab2_part1.py"))
|
||||
print(f"Copied lab2_part1.py to src/")
|
||||
|
||||
if os.path.exists("lab2_part2.py"):
|
||||
shutil.copy("lab2_part2.py", os.path.join("src", "lab2_part2.py"))
|
||||
print(f"Copied lab2_part2.py to src/")
|
||||
|
||||
# Move animated scripts to visualization directory
|
||||
if os.path.exists("lab2_part1_animated.py"):
|
||||
shutil.copy("lab2_part1_animated.py", os.path.join("src/visualization", "lab2_part1_animated.py"))
|
||||
print(f"Copied lab2_part1_animated.py to src/visualization/")
|
||||
|
||||
if os.path.exists("lab2_part2_animated.py"):
|
||||
shutil.copy("lab2_part2_animated.py", os.path.join("src/visualization", "lab2_part2_animated.py"))
|
||||
print(f"Copied lab2_part2_animated.py to src/visualization/")
|
||||
|
||||
if os.path.exists("animate_bouncing_balls.py"):
|
||||
shutil.copy("animate_bouncing_balls.py", os.path.join("src/visualization", "animate_bouncing_balls.py"))
|
||||
print(f"Copied animate_bouncing_balls.py to src/visualization/")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to organize files.
|
||||
"""
|
||||
print("Starting file organization...")
|
||||
|
||||
# Create directories
|
||||
create_data_directories()
|
||||
|
||||
# Move data files
|
||||
move_data_files()
|
||||
|
||||
# Move script files
|
||||
move_script_files()
|
||||
|
||||
print("File organization complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user