├── README.md ├── fitcsvfileclean.py └── CyclingDynamics.py /README.md: -------------------------------------------------------------------------------- 1 | Cyclingdynamics 2 | 3 | #Requirements 4 | -Python 5 | -Matplotlib 6 | -Pandas 7 | -Numpy 8 | -FFMPEG (with path setup) 9 | -FitSDK 10 | -Java runtime 11 | 12 | Your fit file MUST have ALL L/R Balance, Pedal Smoothness, Torque Effectiveness and Cycling dynamics (Assioma OR Vector Series currently) to process. They all must be turned on and recorded or else the math used will fail. 13 | 14 | They must be recorded with the correct fit descriptors and NOT developer fields 15 | 16 | #instructions 17 | See the youtube video for walkthrough 18 | 19 | 1) Use the fitsdk java commandline tool FitToCSV-data on your fitfile 20 | 2) place the -data.csv in this folder 21 | 3) Run fitcsvfileclean.py to delete duplicate entries and interpolate missing data 22 | 4) Run CyclingDynamics.py to decode to "High speed" and create a polar plot animation that is saved as an .mp4 x264 file 23 | -------------------------------------------------------------------------------- /fitcsvfileclean.py: -------------------------------------------------------------------------------- 1 | #this cleans the fittocsv-data converted file when there are duplicate entries 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | import pandas as pd 6 | import os 7 | 8 | 9 | DEBUG = True 10 | SHOW = True 11 | SAVE = not SHOW 12 | 13 | def log(s): 14 | if DEBUG: 15 | print(s) 16 | 17 | root = os.path.dirname(os.path.realpath(__file__)) # This gets the current local path 18 | root = root + '\\' 19 | log(root) 20 | 21 | full_root = [] 22 | datafiles = [] 23 | filename = [] 24 | df_name = [] 25 | 26 | for file in os.listdir(root): #Finds the data.csv 27 | if file.endswith("data.csv"): # Don't change this, I want the _data.csv files 28 | filename.append(file) 29 | full_root.append(root + file) 30 | log(full_root) 31 | 32 | #This reads all the csv files via Pandas data interperted into a pandas data frame 33 | log("File will be cleaned. If problems press ctrl-c to kill python from the terminal") 34 | if (len(filename) == 1) : 35 | df=pd.read_csv(full_root[0]) 36 | df.drop_duplicates(subset='record.timestamp[s]',keep='first',inplace=True) 37 | df.set_index('record.timestamp[s]', inplace=True, drop=True) 38 | new_index = pd.Index(np.arange(df.index.min(),df.index.max(),1), name="record.timestamp_x[s]") 39 | df = df.reindex(new_index) 40 | df = df.interpolate() 41 | df.to_csv(root + filename[0] + "-clean.csv", sep=',') 42 | else: 43 | print("Too many files with data.csv ending") 44 | -------------------------------------------------------------------------------- /CyclingDynamics.py: -------------------------------------------------------------------------------- 1 | from scipy import signal 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import pandas as pd 5 | import csv 6 | import os 7 | import math 8 | from matplotlib import animation 9 | 10 | # from basic_units import radians, degrees, cos 11 | 12 | DEBUG = True 13 | SHOW = True 14 | SAVE = not SHOW 15 | 16 | 17 | def log(s): 18 | if DEBUG: 19 | print(s) 20 | 21 | # with open('file.csv', 'rb') as csvfile: 22 | root = os.path.dirname(os.path.realpath(__file__)) # This gets the current local path 23 | root = root + '\\' 24 | # log(root) 25 | full_root = [] 26 | datafiles = [] 27 | filename = [] 28 | df_name = [] 29 | 30 | data = [] 31 | 32 | for file in os.listdir(root): #Finds the data.csv 33 | if file.endswith("-clean.csv"): # Don't change this, I want the _data.csv files 34 | filename.append(file) 35 | full_root.append(root + file) 36 | 37 | log(full_root) 38 | begin = False 39 | 40 | #select which file that ends in -clean.csv 41 | df = pd.read_csv(full_root[0], sep =',') 42 | # print(df) 43 | 44 | raw_timestamp = df['record.timestamp_x[s]'] 45 | raw_left_right_balance = df['record.left_right_balance'] 46 | raw_cadence = df['record.cadence[rpm]'] 47 | raw_accumulated_power = df['record.accumulated_power[watts]'] 48 | raw_power = df['record.power[watts]'] 49 | raw_left_power_phase = df['record.left_power_phase[degrees]'] 50 | raw_left_power_phase_peak = df['record.right_power_phase_peak[degrees]'] 51 | raw_right_power_phase = df['record.right_power_phase[degrees]'] 52 | raw_right_power_phase_peak = df['record.right_power_phase_peak[degrees]'] 53 | raw_left_torque_effectiveness = df['record.left_torque_effectiveness[percent]'] 54 | raw_right_torque_effectiveness = df['record.right_torque_effectiveness[percent]'] 55 | raw_left_pedal_smoothness = df['record.left_pedal_smoothness[percent]'] 56 | raw_right_pedal_smoothness = df['record.right_pedal_smoothness[percent]'] 57 | 58 | left_power_phase_start = [] 59 | left_power_phase_end = [] 60 | 61 | for x in range(len(raw_left_power_phase)): 62 | try: 63 | txt = raw_left_power_phase[x] 64 | y = txt.split("|") 65 | left_power_phase_start.append(float(y[0])) 66 | left_power_phase_end.append(float(y[1])) 67 | except: 68 | left_power_phase_start.append(0) 69 | left_power_phase_end.append(0) 70 | 71 | left_power_phase_peak_start = [] 72 | left_power_phase_peak_end = [] 73 | 74 | for x in range(len(raw_left_power_phase_peak)): 75 | try: 76 | txt = raw_left_power_phase_peak[x] 77 | y = txt.split("|") 78 | left_power_phase_peak_start.append(float(y[0])) 79 | left_power_phase_peak_end.append(float(y[1])) 80 | except: 81 | left_power_phase_peak_start.append(0) 82 | left_power_phase_peak_end.append(0) 83 | 84 | 85 | right_power_phase_start = [] 86 | right_power_phase_end = [] 87 | 88 | for x in range(len(raw_right_power_phase)): 89 | try: 90 | txt = raw_right_power_phase[x] 91 | y = txt.split("|") 92 | right_power_phase_start.append(float(y[0])) 93 | right_power_phase_end.append(float(y[1])) 94 | except: 95 | right_power_phase_start.append(0) 96 | right_power_phase_end.append(0) 97 | 98 | log("length raw_right_power_phase, length right_power_phase_start, length right_power_phase_end") 99 | log(len(raw_right_power_phase)) 100 | log(len(right_power_phase_start)) 101 | log(len(right_power_phase_end)) 102 | 103 | right_power_phase_peak_start = [] 104 | right_power_phase_peak_end = [] 105 | 106 | for x in range(len(raw_right_power_phase_peak)): 107 | try: 108 | txt = raw_right_power_phase_peak[x] 109 | y = txt.split("|") 110 | right_power_phase_peak_start.append(float(y[0])) 111 | right_power_phase_peak_end.append(float(y[1])) 112 | except: 113 | right_power_phase_peak_start.append(0) 114 | right_power_phase_peak_end.append(0) 115 | 116 | log("length raw_right_power_phase_peak, length right_power_phase_peak_start, length right_power_phase_end") 117 | log(len(raw_right_power_phase_peak)) 118 | log(len(right_power_phase_peak_start)) 119 | log(len(right_power_phase_peak_end)) 120 | 121 | true_left_right_balance = (raw_left_right_balance-128) 122 | left_power = (true_left_right_balance/100)*raw_power 123 | right_power = raw_power - left_power 124 | rev_time = 1/(raw_cadence/60) 125 | 126 | log("length true_left_right_balance, length left_power, length right_power, length rev_time") 127 | log(len(true_left_right_balance)) 128 | log(len(left_power)) 129 | log(len(right_power)) 130 | log(len(rev_time)) 131 | 132 | 133 | 134 | left_phase_start_time = left_power_phase_start*rev_time/360 135 | left_phase_peak_start_time = left_power_phase_peak_start*rev_time/360 136 | left_phase_peak_end_time = left_power_phase_peak_end*rev_time/360 137 | left_phase_end_time = left_power_phase_end*rev_time/360 138 | left_torque_peak_time = (left_phase_peak_start_time + left_phase_peak_end_time)/2 139 | 140 | right_phase_start_time = right_power_phase_start*rev_time/360 141 | right_phase_peak_start_time = right_power_phase_peak_start*rev_time/360 142 | right_phase_peak_end_time = right_power_phase_peak_end*rev_time/360 143 | right_phase_end_time = right_power_phase_end*rev_time/360 144 | right_torque_peak_time = (right_phase_peak_start_time + right_phase_peak_end_time)/2 145 | 146 | 147 | log("length left_phase_start_time, length left_phase_peak_start_time, length left_torque_peak_time, length left_phase_peak_end_time, length left_torque_peak_time") 148 | log(len(left_phase_start_time)) 149 | log(len(left_phase_peak_start_time)) 150 | log(len(left_torque_peak_time)) 151 | log(len(left_phase_peak_end_time)) 152 | log(len(left_torque_peak_time)) 153 | 154 | left_torque_trough_time = [] 155 | for i in range(len(left_phase_start_time)): 156 | try: 157 | if(left_phase_start_time[i]