Example #1
0
    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
Example #2
0
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]
Example #3
0
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]
Example #4
0
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]
Example #5
0
    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]
Example #6
0
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