def heel_strikes(data, sample_rate, threshold=0.2, order=4, cutoff=5, plot_test=False, t=None): """ Estimate heel strike times between sign changes in accelerometer data. The iGAIT software assumes that the y-axis is anterior-posterior, and restricts some feature extraction to this orientation. In this program, we compute heel strikes for an arbitrary axis. Re: heel strikes (from Yang, et al., 2012): "The heel contacts are detected by peaks preceding the sign change of AP acceleration [3]. In order to automatically detect a heel contact event, firstly, the AP acceleration is low pass filtered by the 4th order zero lag Butterworth filter whose cut frequency is set to 5 Hz. After that, transitional positions where AP acceleration changes from positive to negative can be identified. Finally the peaks of AP acceleration preceding the transitional positions, and greater than the product of a threshold and the maximum value of the AP acceleration are denoted as heel contact events... This threshold is defined as the ratio to the maximum value of the AP acceleration, for example 0.5 indicates the threshold is set at 50% of the maximum AP acceleration. Its default value is set to 0.4 as determined experimentally in this paper, where this value allowed correct detection of all gait events in control subjects. However, when a more irregular pattern is analysed, the threshold should be less than 0.4. The user can test different threshold values and find the best one according to the gait event detection results." Parameters ---------- data : list or numpy array accelerometer data along one axis (preferably forward direction) sample_rate : float sample rate of accelerometer reading (Hz) threshold : float ratio to the maximum value of the anterior-posterior acceleration order : integer order of the Butterworth filter cutoff : integer cutoff frequency of the Butterworth filter (Hz) plot_test : Boolean plot heel strikes? t : list or numpy array accelerometer time points Returns ------- strikes : numpy array of floats heel strike timings strike_indices : list of integers heel strike timing indices Examples -------- >>> from mhealthx.xio import read_accel_json >>> from mhealthx.signals import compute_sample_rate >>> input_file = '/Users/arno/DriveWork/mhealthx/mpower_sample_data/deviceMotion_walking_outbound.json.items-a2ab9333-6d63-4676-977a-08591a5d837f5221783798792869048.tmp' >>> device_motion = True >>> start = 150 >>> t, axyz, gxyz, uxyz, rxyz, sample_rate, duration = read_accel_json(input_file, start, device_motion) >>> ax, ay, az = axyz >>> from mhealthx.extractors.pyGait import heel_strikes >>> threshold = 0.4 >>> order = 4 >>> cutoff = max([1, sample_rate/10]) >>> plot_test = True >>> data = np.abs(ax) + np.abs(ay) + np.abs(az) >>> strikes, strike_indices = heel_strikes(data, sample_rate, threshold, order, cutoff, plot_test, t) """ import numpy as np from mhealthx.signals import compute_interpeak from mhealthx.signals import butter_lowpass_filter, \ crossings_nonzero_pos2neg # Demean data (not in iGAIT): data -= np.mean(data) # Low-pass filter the AP accelerometer data by the 4th order zero lag # Butterworth filter whose cut frequency is set to 5 Hz: filtered = butter_lowpass_filter(data, sample_rate, cutoff, order) # Find transitional positions where AP accelerometer changes from # positive to negative. transitions = crossings_nonzero_pos2neg(filtered) # Find the peaks of AP acceleration preceding the transitional positions, # and greater than the product of a threshold and the maximum value of # the AP acceleration: strike_indices_smooth = [] filter_threshold = np.abs(threshold * np.max(filtered)) for i in range(1, np.size(transitions)): segment = range(transitions[i-1], transitions[i]) imax = np.argmax(filtered[segment]) if filtered[segment[imax]] > filter_threshold: strike_indices_smooth.append(segment[imax]) # Compute number of samples between peaks using the real part of the FFT: interpeak = compute_interpeak(data, sample_rate) decel = np.int(interpeak / 2) # Find maximum peaks close to maximum peaks of smoothed data: strike_indices = [] for ismooth in strike_indices_smooth: istrike = np.argmax(data[ismooth - decel:ismooth + decel]) istrike = istrike + ismooth - decel strike_indices.append(istrike) if plot_test: from pylab import plt if t: tplot = np.asarray(t) tplot -= tplot[0] else: tplot = np.linspace(0, np.size(data), np.size(data)) plt.plot(tplot, data, 'k-', linewidth=2, label='data') plt.plot(tplot, filtered, 'b-', linewidth=1, label='filtered data') plt.plot(tplot[transitions], filtered[transitions], 'ko', linewidth=1, label='transition points') plt.plot(tplot[strike_indices_smooth], filtered[strike_indices_smooth], 'bs', linewidth=1, label='heel strikes') plt.plot(tplot[strike_indices], data[strike_indices], 'rs', linewidth=1, label='heel strikes') plt.xlabel('Time (s)') plt.grid() plt.legend(loc='lower left', shadow=True) plt.show() strikes = np.asarray(strike_indices) strikes -= strikes[0] strikes = strikes / sample_rate return strikes, strike_indices
def walk_direction_preheel(ax, ay, az, t, sample_rate, stride_fraction=1.0/8.0, threshold=0.5, order=4, cutoff=5, plot_test=False): """ Estimate local walk (not cardinal) direction with pre-heel strike phase. Inspired by Nirupam Roy's B.E. thesis: "WalkCompass: Finding Walking Direction Leveraging Smartphone's Inertial Sensors," this program derives the local walk direction vector from the end of the primary leg's stride, when it is decelerating in its swing. While the WalkCompass relies on clear heel strike signals across the accelerometer axes, this program just uses the most prominent strikes, and estimates period from the real part of the FFT of the data. NOTE:: This algorithm computes a single walk direction, and could compute multiple walk directions prior to detected heel strikes, but does NOT estimate walking direction for every time point, like walk_direction_attitude(). Parameters ---------- ax : list or numpy array x-axis accelerometer data ay : list or numpy array y-axis accelerometer data az : list or numpy array z-axis accelerometer data t : list or numpy array accelerometer time points sample_rate : float sample rate of accelerometer reading (Hz) stride_fraction : float fraction of stride assumed to be deceleration phase of primary leg threshold : float ratio to the maximum value of the summed acceleration across axes plot_test : Boolean plot most prominent heel strikes? Returns ------- direction : numpy array of three floats unit vector of local walk (not cardinal) direction Examples -------- >>> from mhealthx.xio import read_accel_json >>> from mhealthx.signals import compute_sample_rate >>> input_file = '/Users/arno/DriveWork/mhealthx/mpower_sample_data/deviceMotion_walking_outbound.json.items-a2ab9333-6d63-4676-977a-08591a5d837f5221783798792869048.tmp' >>> device_motion = True >>> start = 150 >>> t, axyz, gxyz, uxyz, rxyz, sample_rate, duration = read_accel_json(input_file, start, device_motion) >>> ax, ay, az = axyz >>> from mhealthx.extractors.pyGait import walk_direction_preheel >>> threshold = 0.5 >>> stride_fraction = 1.0/8.0 >>> order = 4 >>> cutoff = max([1, sample_rate/10]) >>> plot_test = True >>> direction = walk_direction_preheel(ax, ay, az, t, sample_rate, stride_fraction, threshold, order, cutoff, plot_test) """ import numpy as np from mhealthx.extractors.pyGait import heel_strikes from mhealthx.signals import compute_interpeak # Sum of absolute values across accelerometer axes: data = np.abs(ax) + np.abs(ay) + np.abs(az) # Find maximum peaks of smoothed data: plot_test2 = False dummy, ipeaks_smooth = heel_strikes(data, sample_rate, threshold, order, cutoff, plot_test2, t) # Compute number of samples between peaks using the real part of the FFT: interpeak = compute_interpeak(data, sample_rate) decel = np.int(np.round(stride_fraction * interpeak)) # Find maximum peaks close to maximum peaks of smoothed data: ipeaks = [] for ipeak_smooth in ipeaks_smooth: ipeak = np.argmax(data[ipeak_smooth - decel:ipeak_smooth + decel]) ipeak += ipeak_smooth - decel ipeaks.append(ipeak) # Plot peaks and deceleration phase of stride: if plot_test: from pylab import plt if isinstance(t, list): tplot = np.asarray(t) - t[0] else: tplot = np.linspace(0, np.size(ax), np.size(ax)) idecel = [x - decel for x in ipeaks] plt.plot(tplot, data, 'k-', tplot[ipeaks], data[ipeaks], 'rs') for id in idecel: plt.axvline(x=tplot[id]) plt.title('Maximum stride peaks') plt.show() # Compute the average vector for each deceleration phase: vectors = [] for ipeak in ipeaks: decel_vectors = np.asarray([[ax[i], ay[i], az[i]] for i in range(ipeak - decel, ipeak)]) vectors.append(np.mean(decel_vectors, axis=0)) # Compute the average deceleration vector and take the opposite direction: direction = -1 * np.mean(vectors, axis=0) # Return the unit vector in this direction: direction /= np.sqrt(direction.dot(direction)) # Plot vectors: if plot_test: from mhealthx.utilities import plot_vectors dx = [x[0] for x in vectors] dy = [x[1] for x in vectors] dz = [x[2] for x in vectors] hx, hy, hz = direction title = 'Average deceleration vectors + estimated walk direction' plot_vectors(dx, dy, dz, [hx], [hy], [hz], title) return direction