FIT File Visualizer Script
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "fitparse==1.2.0",
# "matplotlib==3.8.2",
# "numpy==1.26.4"
# ]
# ///
import fitparse
import os
import sys
import matplotlib.pyplot as plt
import numpy as np
def plot_hr_vs_power_over_time(filepath):
"""
Reads a .fit file and creates a plot of heart rate vs. power
with color indicating time.
Args:
filepath (str): The path to the .fit file.
"""
if not os.path.exists(filepath):
print(f"Error: File not found at '{filepath}'")
return
try:
fitfile = fitparse.FitFile(filepath)
# Data extraction
timestamps, heart_rates, powers = [], [], []
for record in fitfile.get_messages("record"):
timestamps.append(record.get_value("timestamp"))
heart_rates.append(record.get_value("heart_rate"))
powers.append(record.get_value("power"))
# Filter out None values
valid_records = [
(t, hr, p)
for t, hr, p in zip(timestamps, heart_rates, powers)
if all(x is not None for x in [t, hr, p])
]
if not valid_records:
print("No complete records found to generate the plot.")
return
timestamps, heart_rates, powers = zip(*valid_records)
start_time = timestamps[0]
# time in minutes
time_deltas = [
(t - start_time).total_seconds() / 60 for t in timestamps
]
plt.figure(figsize=(10, 6))
scatter = plt.scatter(
powers, heart_rates, c=time_deltas, cmap="viridis", alpha=0.7
)
plt.title("Heart Rate vs. Power Over Time")
plt.xlabel("Power (watts)")
plt.ylabel("Heart Rate (bpm)")
plt.grid(True)
# Add colorbar
cbar = plt.colorbar(scatter)
cbar.set_label("Time (minutes)")
plt.savefig("hr_vs_power_over_time.png")
plt.close()
print("Plot saved as hr_vs_power_over_time.png")
except fitparse.FitParseError as e:
print(f"Error parsing FIT file: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
if len(sys.argv) > 1:
fit_file_path = sys.argv[1]
plot_hr_vs_power_over_time(fit_file_path)
else:
print(
"Please provide the path to the .fit file as an argument."
)
* * *