def _leg2d(self, time, marker_pos, normalized_force_plate_values, cutoff, sample_rate): """This method effectively does the same thing that the Octave routine does.""" # differentiate to get the marker velocities and accelerations marker_vel = process.derivative(time, marker_pos, method='combination') marker_acc = process.derivative(time, marker_vel, method='combination') # filter all the input data with the same filter marker_pos = process.butterworth(marker_pos, cutoff, sample_rate, axis=0) marker_vel = process.butterworth(marker_vel, cutoff, sample_rate, axis=0) marker_acc = process.butterworth(marker_acc, cutoff, sample_rate, axis=0) force_array = process.butterworth(normalized_force_plate_values, cutoff, sample_rate, axis=0) # compute the inverse dynamics inv_dyn = lower_extremity_2d_inverse_dynamics dynamics = inv_dyn(time, marker_pos, marker_vel, marker_acc, force_array) return dynamics
def find_constant_speed(time, speed, plot=False, filter_cutoff=1.0): """Returns the indice at which the treadmill speed becomes constant and the time series when the treadmill speed is constant. Parameters ========== time : array_like, shape(n,) A monotonically increasing array. speed : array_like, shape(n,) A speed array, one sample for each time. Should ramp up and then stablize at a speed. plot : boolean, optional If true a plot will be displayed with the results. filter_cutoff : float, optional The filter cutoff frequency for filtering the speed in Hertz. Returns ======= indice : integer The indice at which the speed is consider constant thereafter. new_time : ndarray, shape(n-indice,) The new time array for the constant speed section. """ sample_rate = 1.0 / (time[1] - time[0]) filtered_speed = process.butterworth(speed, filter_cutoff, sample_rate) acceleration = process.derivative(time, filtered_speed, method='central', padding='second order') last = acceleration[int(0.2 * len(acceleration)):] noise_level = np.max(np.abs(last - np.mean(last))) reversed_acceleration = acceleration[::-1] indice = np.argmax(reversed_acceleration > noise_level) additional_samples = sample_rate * 0.65 new_indice = indice - int(round(additional_samples)) if plot is True: fig, ax = plt.subplots(2, 1) ax[0].plot(time, speed, '.', time, filtered_speed, 'g-') ax[0].plot( np.ones(2) * (time[len(time) - new_indice]), np.hstack((np.max(speed), np.min(speed)))) ax[1].plot(time, acceleration, '.') fig.show() return len(time) - (new_indice), time[len(time) - new_indice]
def find_constant_speed(time, speed, plot=False, filter_cutoff=1.0): """Returns the indice at which the treadmill speed becomes constant and the time series when the treadmill speed is constant. Parameters ========== time : array_like, shape(n,) A monotonically increasing array. speed : array_like, shape(n,) A speed array, one sample for each time. Should ramp up and then stablize at a speed. plot : boolean, optional If true a plot will be displayed with the results. filter_cutoff : float, optional The filter cutoff frequency for filtering the speed in Hertz. Returns ======= indice : integer The indice at which the speed is consider constant thereafter. new_time : ndarray, shape(n-indice,) The new time array for the constant speed section. """ sample_rate = 1.0 / (time[1] - time[0]) filtered_speed = process.butterworth(speed, filter_cutoff, sample_rate) acceleration = process.derivative(time, filtered_speed, method='central', padding='second order') last = acceleration[int(0.2 * len(acceleration)):] noise_level = np.max(np.abs(last - np.mean(last))) reversed_acceleration = acceleration[::-1] indice = np.argmax(reversed_acceleration > noise_level) additional_samples = sample_rate * 0.65 new_indice = indice - additional_samples if plot is True: fig, ax = plt.subplots(2, 1) ax[0].plot(time, speed, '.', time, filtered_speed, 'g-') ax[0].plot(np.ones(2) * (time[len(time) - new_indice]), np.hstack((np.max(speed), np.min(speed)))) ax[1].plot(time, acceleration, '.') fig.show() return len(time) - (new_indice), time[len(time) - new_indice]
def find_constant_speed(time, speed, plot=False): """Returns the indice at which the treadmill speed becomes constant and the time series when the treadmill speed is constant. Parameters ========== time : array_like, shape(n,) A monotonically increasing array. speed : array_like, shape(n,) A speed array, one sample for each time. Should ramp up and then stablize at a speed. plot : boolean, optional If true a plot will be displayed with the results. Returns ======= indice : integer The indice at which the speed is consider constant thereafter. new_time : ndarray, shape(n-indice,) The new time array for the constant speed section. """ sample_rate = 1.0 / (time[1] - time[0]) filtered_speed = process.butterworth(speed, 3.0, sample_rate) acceleration = np.hstack((0.0, np.diff(filtered_speed))) noise_level = np.max(np.abs(acceleration[int(0.2 * len(acceleration)):-1])) reversed_acceleration = acceleration[::-1] indice = np.argmax(reversed_acceleration > noise_level) additional_samples = sample_rate * 0.65 new_indice = indice - additional_samples if plot is True: import matplotlib.pyplot as plt fig, ax = plt.subplots(2, 1) ax[0].plot(time, speed, '.', time, filtered_speed, 'g-') ax[0].plot(np.ones(2) * (time[len(time) - new_indice]), np.hstack((np.max(speed), np.min(speed)))) ax[1].plot(time, np.hstack((0.0, np.diff(filtered_speed))), '.') fig.show() return len(time) - (new_indice), time[len(time) - new_indice]
def low_pass_filter(self, col_names, cutoff, new_col_names=None, order=2): """Low pass filters the specified columns with a Butterworth filter. Parameters ========== col_names : list of strings The column names for the time series which should be numerically time differentiated. cutoff : float The desired low pass cutoff frequency in Hertz. new_col_names : list of strings, optional The desired new column name(s) for the filtered series. If None, then a default name of `Filtered <origin column name>` will be used. order : int The order of the Butterworth filter. """ if new_col_names is None: new_col_names = ['Filtered {}'.format(c) for c in col_names] #time = self.data.index.values.astype(float) time = self.data['TimeStamp'].values.astype(float) sample_rate = 1.0 / np.mean(np.diff(time)) filtered_data = process.butterworth(self.data[col_names].values, cutoff, sample_rate, order=order, axis=0) # TODO : Ideally these could be added to the DataFrame in one # command. for i, col in enumerate(new_col_names): self.data[col] = filtered_data[:, i]
def gait_landmarks_from_grf(time, right_grf, left_grf, threshold=1e-5, filter_frequency=None, **kwargs): """ Obtain gait landmarks (right and left foot strike & toe-off) from ground reaction force (GRF) time series data. Parameters ---------- time : array_like, shape(n,) A monotonically increasing time array. right_grf : array_like, shape(n,) The vertical component of GRF data for the right leg. left_grf : str, shape(n,) Same as above, but for the left leg. threshold : float, optional Below this value, the force is considered to be zero (and the corresponding foot is not touching the ground). filter_frequency : float, optional, default=None If a filter frequency is provided, in Hz, the right and left ground reaction forces will be filtered with a 2nd order low pass filter before the landmarks are identified. This method assumes that there is a constant (or close to constant) sample rate. Returns ------- right_foot_strikes : np.array All times at which right_grfy is non-zero and it was 0 at the preceding time index. left_foot_strikes : np.array Same as above, but for the left foot. right_toe_offs : np.array All times at which left_grfy is 0 and it was non-zero at the preceding time index. left_toe_offs : np.array Same as above, but for the left foot. Notes ----- Source modifed from: https://github.com/fitze/epimysium/blob/master/epimysium/postprocessing.py """ # Helper functions # ---------------- def zero(number): return abs(number) < threshold def birth_times(ordinate): births = list() for i in range(len(ordinate) - 1): # 'Skip' first value because we're going to peak back at previous # index. if zero(ordinate[i]) and (not zero(ordinate[i+1])): births.append(time[i + 1]) return np.array(births) def death_times(ordinate): deaths = list() for i in range(len(ordinate) - 1): if (not zero(ordinate[i])) and zero(ordinate[i+1]): deaths.append(time[i + 1]) return np.array(deaths) # If the ground reaction forces are very noisy, it may help to low pass # filter the signals before searching for the strikes and offs. if filter_frequency is not None: average_sample_rate = 1.0 / np.mean(np.diff(time)) right_grf = process.butterworth(right_grf, filter_frequency, average_sample_rate) left_grf = process.butterworth(left_grf, filter_frequency, average_sample_rate) right_foot_strikes = birth_times(right_grf) left_foot_strikes = birth_times(left_grf) right_toe_offs = death_times(right_grf) left_toe_offs = death_times(left_grf) return right_foot_strikes, left_foot_strikes, right_toe_offs, left_toe_offs
def find_timeshift(niAcc, vnAcc, sampleRate, speed, plotError=False): '''Returns the timeshift, tau, of the VectorNav [VN] data relative to the National Instruments [NI] data. Parameters ---------- niAcc : ndarray, shape(n, ) The acceleration of the NI accelerometer in its local Y direction. vnAcc : ndarray, shape(n, ) The acceleration of the VN-100 in its local Z direction. Should be the same length as NIacc and contains the same signal albiet time shifted. The VectorNav signal should be leading the NI signal. sampleRate : integer or float Sample rate of the signals. This should be the same for each signal. speed : float The approximate forward speed of the bicycle. Returns ------- tau : float The timeshift. Notes ----- The Z direction for `VNacc` is assumed to be aligned with the steer axis and pointing down and the Y direction for the NI accelerometer should be aligned with the steer axis and pointing up. ''' # raise an error if the signals are not the same length N = len(niAcc) if N != len(vnAcc): raise TimeShiftError('Signals are not the same length!') # make a time vector time = time_vector(N, sampleRate) # the signals are opposite sign of each other, so fix that niSig = -niAcc vnSig = vnAcc # some constants for find_bump wheelbase = 1.02 # this is the wheelbase of the rigid rider bike bumpLength = 1. cutoff = 30. # filter the NI Signal filNiSig = butterworth(niSig, cutoff, sampleRate) # find the bump in the filtered NI signal niBump = find_bump(filNiSig, sampleRate, speed, wheelbase, bumpLength) # remove the nan's in the VN signal and the corresponding time v = vnSig[np.nonzero(np.isnan(vnSig) == False)] t = time[np.nonzero(np.isnan(vnSig) == False)] # fit a spline through the data vn_spline = UnivariateSpline(t, v, k=3, s=0) # and filter it filVnSig = butterworth(vn_spline(time), cutoff, sampleRate) # and find the bump in the filtered VN signal vnBump = find_bump(filVnSig, sampleRate, speed, wheelbase, bumpLength) if vnBump is None or niBump is None: guess = 0.3 else: # get an initial guess for the time shift based on the bump indice guess = (niBump[1] - vnBump[1]) / float(sampleRate) # Since vnSig may have nans we should only use contiguous data around # around the bump. The first step is to split vnSig into sections bounded # by the nans and then selected the section in which the bump falls. Then # we select a similar area in niSig to run the time shift algorithm on. if vnBump is None: bumpLocation = 800 # just a random guess so things don't crash else: bumpLocation = vnBump[1] indices, arrays = split_around_nan(vnSig) for pair in indices: if pair[0] <= bumpLocation < pair[1]: bSec = pair # subtract the mean and normalize both signals niSig = normalize(subtract_mean(niSig, hasNans=True), hasNans=True) vnSig = normalize(subtract_mean(vnSig, hasNans=True), hasNans=True) niBumpSec = niSig[bSec[0]:bSec[1]] vnBumpSec = vnSig[bSec[0]:bSec[1]] timeBumpSec = time[bSec[0]:bSec[1]] if len(niBumpSec) < 200: warn('The bump section is only {} samples wide.'.format(str(len(niBumpSec)))) # set up the error landscape, error vs tau # The NI lags the VectorNav and the time shift is typically between 0 and # 1 seconds tauRange = np.linspace(0., 2., num=500) error = np.zeros_like(tauRange) for i, val in enumerate(tauRange): error[i] = sync_error(val, niBumpSec, vnBumpSec, timeBumpSec) if plotError: plt.figure() plt.plot(tauRange, error) plt.xlabel('tau') plt.ylabel('error') plt.show() # find initial condition from landscape tau0 = tauRange[np.argmin(error)] print "The minimun of the error landscape is %f and the provided guess is %f" % (tau0, guess) # Compute the minimum of the function using both the result from the error # landscape and the bump find for initial guesses to the minimizer. Choose # the best of the two. tauBump, fvalBump = fmin(sync_error, guess, args=(niBumpSec, vnBumpSec, timeBumpSec), full_output=True, disp=True)[0:2] tauLandscape, fvalLandscape = fmin(sync_error, tau0, args=(niBumpSec, vnBumpSec, timeBumpSec), full_output=True, disp=True)[0:2] if fvalBump < fvalLandscape: tau = tauBump else: tau = tauLandscape #### if the minimization doesn't do a good job, just use the tau0 ###if np.abs(tau - tau0) > 0.01: ###tau = tau0 ###print "Bad minimizer!! Using the guess, %f, instead." % tau print "This is what came out of the minimization:", tau if not (0.05 < tau < 2.0): raise TimeShiftError('This tau, {} s, is probably wrong'.format(str(tau))) return tau