- 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)
271 lines
12 KiB
Python
271 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Bouncing Ball Animation Launcher
|
|
|
|
This script provides a simple command-line interface to run different
|
|
animated visualizations of bouncing ball data.
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
import importlib.util
|
|
import os
|
|
|
|
def check_dependencies():
|
|
"""Check if all required dependencies are installed."""
|
|
required_packages = ['pandas', 'numpy', 'matplotlib', 'seaborn', 'scipy']
|
|
missing_packages = []
|
|
|
|
for package in required_packages:
|
|
try:
|
|
importlib.import_module(package)
|
|
except ImportError:
|
|
missing_packages.append(package)
|
|
|
|
if missing_packages:
|
|
print("Missing required packages:")
|
|
for package in missing_packages:
|
|
print(f" - {package}")
|
|
print("\nPlease install them using:")
|
|
print(f"pip install {' '.join(missing_packages)}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def check_files():
|
|
"""Check if the animation script files exist."""
|
|
required_files = ['lab2_part1_animated.py', 'lab2_part2_animated.py']
|
|
missing_files = []
|
|
|
|
for file in required_files:
|
|
if not os.path.exists(file):
|
|
missing_files.append(file)
|
|
|
|
if missing_files:
|
|
print("Missing required script files:")
|
|
for file in missing_files:
|
|
print(f" - {file}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def check_data_files(ball_type=None):
|
|
"""Check if the required data files exist."""
|
|
if ball_type is None or ball_type.lower() == 'golf':
|
|
golf_files = ["golf_11.csv", "golf_12.csv", "golf_13.csv", "golf_14.csv", "golf_15.csv"]
|
|
for file in golf_files:
|
|
if not os.path.exists(file):
|
|
print(f"Warning: Golf ball data file '{file}' not found.")
|
|
return False
|
|
|
|
if ball_type is None or ball_type.lower() == 'lacrosse':
|
|
lacrosse_files = ["l_18.csv", "l_19.csv", "l_20.csv", "l_21.csv", "l_22.csv"]
|
|
for file in lacrosse_files:
|
|
if not os.path.exists(file):
|
|
print(f"Warning: Lacrosse ball data file '{file}' not found.")
|
|
return False
|
|
|
|
if ball_type is None or ball_type.lower() == 'metal':
|
|
metal_files = ["metal_2.csv", "metal_4.csv", "metal_6.csv", "metal_8.csv", "metal_10.csv"]
|
|
for file in metal_files:
|
|
if not os.path.exists(file):
|
|
print(f"Warning: Metal ball data file '{file}' not found.")
|
|
return False
|
|
|
|
return True
|
|
|
|
def run_animation(animation_type, ball_type=None, save=False):
|
|
"""Run the selected animation."""
|
|
if animation_type == 'position':
|
|
# Import the position vs. time animation script
|
|
spec = importlib.util.spec_from_file_location("lab2_part1_animated", "lab2_part1_animated.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
# Run the animation
|
|
if ball_type:
|
|
if ball_type.lower() == 'golf':
|
|
print("Running position vs. time animation for Golf balls...")
|
|
module.animate_position_vs_time(module.golf_file_paths, "Golf Ball", save)
|
|
elif ball_type.lower() == 'lacrosse':
|
|
print("Running position vs. time animation for Lacrosse balls...")
|
|
module.animate_position_vs_time(module.lacrosse_file_paths, "Lacrosse Ball", save)
|
|
elif ball_type.lower() == 'metal':
|
|
print("Running position vs. time animation for Metal balls...")
|
|
module.animate_position_vs_time(module.metal_file_paths, "Metal Ball", save)
|
|
else:
|
|
print(f"Unknown ball type: {ball_type}")
|
|
print("Available ball types: golf, lacrosse, metal")
|
|
else:
|
|
print("Running position vs. time animations for all ball types...")
|
|
module.animate_all_ball_types(save)
|
|
|
|
elif animation_type == 'bounce':
|
|
# Import the bouncing ball animation script
|
|
spec = importlib.util.spec_from_file_location("lab2_part2_animated", "lab2_part2_animated.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
# Set save_animations flag
|
|
module.save_animations = save
|
|
|
|
# Run the animation
|
|
if ball_type:
|
|
if ball_type.lower() == 'golf':
|
|
print("Running bouncing ball animation for Golf balls...")
|
|
module.golf_results = module.process_and_animate_all(
|
|
module.golf_file_paths,
|
|
ball_type='Golf',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.golf_relative_prominence,
|
|
low_height_threshold=module.golf_low_threshold,
|
|
low_height_adjustment=module.golf_low_adjustment,
|
|
high_height_threshold=module.golf_high_threshold,
|
|
high_height_adjustment=module.golf_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
module.plot_cor_table(module.golf_results, 'Golf')
|
|
elif ball_type.lower() == 'lacrosse':
|
|
print("Running bouncing ball animation for Lacrosse balls...")
|
|
module.lacrosse_results = module.process_and_animate_all(
|
|
module.lacrosse_file_paths,
|
|
ball_type='Lacrosse',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.lacrosse_relative_prominence,
|
|
low_height_threshold=module.lacrosse_low_threshold,
|
|
low_height_adjustment=module.lacrosse_low_adjustment,
|
|
high_height_threshold=module.lacrosse_high_threshold,
|
|
high_height_adjustment=module.lacrosse_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
module.plot_cor_table(module.lacrosse_results, 'Lacrosse')
|
|
elif ball_type.lower() == 'metal':
|
|
print("Running bouncing ball animation for Metal balls...")
|
|
module.metal_results = module.process_and_animate_all(
|
|
module.metal_file_paths,
|
|
ball_type='Metal',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.metal_relative_prominence,
|
|
low_height_threshold=module.metal_low_threshold,
|
|
low_height_adjustment=module.metal_low_adjustment,
|
|
high_height_threshold=module.metal_high_threshold,
|
|
high_height_adjustment=module.metal_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
module.plot_cor_table(module.metal_results, 'Metal')
|
|
else:
|
|
print(f"Unknown ball type: {ball_type}")
|
|
print("Available ball types: golf, lacrosse, metal")
|
|
else:
|
|
# Run the animations for all ball types one by one
|
|
print("Running bouncing ball animations for all ball types...")
|
|
|
|
# Golf balls
|
|
print("\nProcessing and animating Golf Ball trials...")
|
|
module.golf_results = module.process_and_animate_all(
|
|
module.golf_file_paths,
|
|
ball_type='Golf',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.golf_relative_prominence,
|
|
low_height_threshold=module.golf_low_threshold,
|
|
low_height_adjustment=module.golf_low_adjustment,
|
|
high_height_threshold=module.golf_high_threshold,
|
|
high_height_adjustment=module.golf_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
|
|
# Lacrosse balls
|
|
print("\nProcessing and animating Lacrosse Ball trials...")
|
|
module.lacrosse_results = module.process_and_animate_all(
|
|
module.lacrosse_file_paths,
|
|
ball_type='Lacrosse',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.lacrosse_relative_prominence,
|
|
low_height_threshold=module.lacrosse_low_threshold,
|
|
low_height_adjustment=module.lacrosse_low_adjustment,
|
|
high_height_threshold=module.lacrosse_high_threshold,
|
|
high_height_adjustment=module.lacrosse_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
|
|
# Metal balls
|
|
print("\nProcessing and animating Metal Ball trials...")
|
|
module.metal_results = module.process_and_animate_all(
|
|
module.metal_file_paths,
|
|
ball_type='Metal',
|
|
min_distance=module.min_distance,
|
|
min_time_diff=module.min_time_diff,
|
|
relative_prominence=module.metal_relative_prominence,
|
|
low_height_threshold=module.metal_low_threshold,
|
|
low_height_adjustment=module.metal_low_adjustment,
|
|
high_height_threshold=module.metal_high_threshold,
|
|
high_height_adjustment=module.metal_high_adjustment,
|
|
max_bounces=7,
|
|
fs=module.fs,
|
|
save_animations=save
|
|
)
|
|
|
|
# Print summary tables
|
|
print("\nGolf Ball COR Table:")
|
|
print(module.golf_results[['Trial', 'Initial Height', 'Average COR']])
|
|
print("\nLacrosse Ball COR Table:")
|
|
print(module.lacrosse_results[['Trial', 'Initial Height', 'Average COR']])
|
|
print("\nMetal Ball COR Table:")
|
|
print(module.metal_results[['Trial', 'Initial Height', 'Average COR']])
|
|
|
|
# Plot COR vs. initial height for each ball type
|
|
module.plot_cor_table(module.golf_results, 'Golf')
|
|
module.plot_cor_table(module.lacrosse_results, 'Lacrosse')
|
|
module.plot_cor_table(module.metal_results, 'Metal')
|
|
|
|
else:
|
|
print(f"Unknown animation type: {animation_type}")
|
|
print("Available animation types: position, bounce")
|
|
|
|
def main():
|
|
"""Main function to parse arguments and run the selected animation."""
|
|
parser = argparse.ArgumentParser(description='Run bouncing ball animations.')
|
|
parser.add_argument('animation_type', choices=['position', 'bounce'],
|
|
help='Type of animation to run: position (for position vs. time) or bounce (for bouncing ball physics)')
|
|
parser.add_argument('--ball', choices=['golf', 'lacrosse', 'metal'],
|
|
help='Type of ball to animate (default: all)')
|
|
parser.add_argument('--save', action='store_true',
|
|
help='Save animations as GIF files')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Check dependencies and files
|
|
if not check_dependencies():
|
|
print("Please install the required dependencies and try again.")
|
|
sys.exit(1)
|
|
|
|
if not check_files():
|
|
print("Please ensure the animation script files are in the current directory and try again.")
|
|
sys.exit(1)
|
|
|
|
if not check_data_files(args.ball):
|
|
print("Warning: Some data files are missing. The animations may not work correctly.")
|
|
response = input("Do you want to continue anyway? (y/n): ")
|
|
if response.lower() != 'y':
|
|
sys.exit(1)
|
|
|
|
# Run the selected animation
|
|
run_animation(args.animation_type, args.ball, args.save)
|
|
|
|
if __name__ == '__main__':
|
|
main() |