def smooth(self, data): sm_size = int(0.08 * self.sample_rate) smoothed_data, _ = st.smoother(signal=data, kernel='hamming', size=sm_size, mirror=True) return smoothed_data
def diffSCR(data,fs,min_amplitude): #onset at local minimum; peak at diff maximum df = np.diff(data) df = np.append(df,df[-1]) size = int(1. * fs) df = tools.smoother(df,'bartlett',size)['signal'] #plotsignal([data,df],False) onsets, pks, amps = find_zeropeak(df,min_amplitude,'diff') return onsets,pks,amps,df
def compute_intervals(peaks, smooth=False, size=3): intervals = [0] for i in range(len(peaks)-1): intervals.append(peaks[i+1]-peaks[i]) intervals = np.array(intervals) if smooth and (len(intervals) > 1): intervals, _ = smoother(signal=intervals, kernel='boxcar', size=size, mirror=True) return intervals
def read_sample(path, name, sampling_rate, preprocess=True): dataset = pandas.read_csv(path + name + '.txt', delimiter='\t', skiprows=4, skipfooter=1, engine='python') x_signal = dataset.values[:total_length * sampling_rate, 0] y = dataset.values[:total_length * sampling_rate, 1] y_signal = normalize_data(pandas.DataFrame(y), max_range, min_range) if preprocess is True: smoothed_signal, params = smoother(y_signal[:, 0]) y_signal = np.reshape(smoothed_signal, [smoothed_signal.shape[0], 1]) if sampling_rate != rate: down_sample_factor = int(sampling_rate // rate) y_signal = decimate(y_signal, down_sample_factor) #x_signal = decimate(x_signal, down_sample_factor) y_signal = normalize_data(pandas.DataFrame(y), max_range, min_range) return x_signal, y_signal
def __call__(self, signal): import biodata from scipy.signal import find_peaks from biosppy.signals.tools import get_heart_rate, smoother list_smoothing_heart = [None, 10, 100] list_smoothing_rate = [None, 1000, 10000] list_smoothing_eda = [None, 10] list_features = [] heart_raw = biodata.enveloppe_filter(signal[:, 1]) heart_peaks, heart_properties = find_peaks( heart_raw, distance=self.distance_heart, width=self.width_heart, prominence=self.prominence_heart) for smoothing in list_smoothing_heart: if smoothing is None: size = 1 smoothing = False else: size = smoothing smoothing = True bpm = get_heart_rate(heart_peaks, sampling_rate=self.sampling_rate, smooth=smoothing, size=size) amplitudes = heart_properties["prominences"] if smoothing: amplitudes, _ = smoother(signal=amplitudes, kernel='boxcar', size=size, mirror=True) bpm = biodata.interpolate(bpm[1], bpm[0], len(signal)) amplitudes = biodata.interpolate(amplitudes, heart_peaks, len(signal)) list_features += [bpm, amplitudes] eda_raw = biodata.enveloppe_filter(signal[:, 2]) eda_peaks, eda_properties = find_peaks(eda_raw, distance=self.distance_eda, width=self.width_eda, prominence=self.prominence_eda) for smoothing in list_smoothing_eda: if smoothing is None: size = 1 smoothing = False else: size = smoothing smoothing = True intervals = biodata.compute_intervals(eda_peaks, smooth=smoothing, size=size) amplitudes = eda_properties["prominences"] if smoothing: amplitudes, _ = smoother(signal=amplitudes, kernel='boxcar', size=size, mirror=True) intervals = biodata.interpolate(intervals, eda_peaks, len(signal)) amplitudes = biodata.interpolate(amplitudes, eda_peaks, len(signal)) list_features += [intervals, amplitudes] for smoothing in list_smoothing_rate: if smoothing is None: size = 1 smoothing = False else: size = smoothing smoothing = True rate = biodata.rate_of_change(eda_raw, size=size) list_features.append(rate) features = np.array(list_features).transpose() if self.normalization is None: pass elif self.normalization == "standardized": features = (features - features.mean(0)) / features.std(0) elif self.normalization == "normalized": features = (features - features.min(0)) / (features.max(0) - features.min(0)) else: raise ValueError return torch.tensor(features).float()
def resp(signal=None, sampling_rate=1000., show=True): """Process a raw Respiration signal and extract relevant signal features using default parameters. Parameters ---------- signal : array Raw Respiration signal. sampling_rate : int, float, optional Sampling frequency (Hz). show : bool, optional If True, show a summary plot. Returns ------- ts : array Signal time axis reference (seconds). filtered : array Filtered Respiration signal. zeros : array Indices of Respiration zero crossings. resp_rate_ts : array Inspiration rate time axis reference (seconds). resp_rate : array Instantaneous respiration rate (Hz). """ # check inputs if signal is None: raise TypeError("Please specify an input signal.") # ensure numpy signal = np.array(signal) sampling_rate = float(sampling_rate) # filter signal # 0.1 ~~ 0.35 Hzのバンドパスフィルタ filtered, _, _ = st.filter_signal(signal=signal, ftype='butter', band='bandpass', order=2, frequency=[0.1, 0.35], sampling_rate=sampling_rate) # compute zero crossings filtered = filtered - np.mean(filtered) # zeros df = np.diff(np.sign(filtered)) inspiration = np.nonzero(df > 0)[0] expiration = np.nonzero(df < 0)[0] if len(inspiration) < 2: rate_idx = [] rate = [] else: # compute resp peaks between inspiration and expiration peaks = [] for i in range(len(inspiration) - 1): cycle = filtered[inspiration[i]:inspiration[i + 1]] peaks.append(np.argmax(cycle) + inspiration[i]) # list to array peaks = np.array(peaks) # compute respiration rate rate_idx = inspiration[1:] rate = sampling_rate * (1. / np.diff(inspiration)) # physiological limits # 0.35Hz以下のresp_rateは省かれる indx = np.nonzero(rate <= 0.35) rate_idx = rate_idx[indx] rate = rate[indx] # smooth with moving average size = 3 rate, _ = st.smoother(signal=rate, kernel='boxcar', size=size, mirror=True) # get time vectors length = len(signal) T = (length - 1) / sampling_rate ts = np.linspace(0, T, length, endpoint=True) ts_rate = ts[rate_idx] # plot if show: plotting.plot_resp(ts=ts, raw=signal, filtered=filtered, zeros=zeros, resp_rate_ts=ts_rate, resp_rate=rate, path=None, show=True) # output args = (ts, filtered, ts_rate, rate, inspiration, expiration, peaks) names = ('ts', 'filtered', 'resp_rate_ts', 'resp_rate', 'inspiration', 'expiration', 'peaks') return utils.ReturnTuple(args, names)
def ecg( Kb=130, Ap=0.2, Kp=100, Kpq=40, Aq=0.1, Kq1=25, Kq2=5, Ar=0.7, Kr=40, As=0.2, Ks=30, Kcs=5, sm=96, Kst=100, At=0.15, Kt=220, si=2, Ki=200, var=0.01, sampling_rate=10000, ): # normal values by default """Concatenates the segments and waves to make an ECG signal. The default values are physiological. Follows the approach by Dolinský, Andráš, Michaeli and Grimaldi [Model03]. If the parameters introduced aren't within physiological values (limits based on the website [ECGwaves]), a warning will raise. Parameters ---------- Kb : int, optional B segment width (miliseconds). Ap : float, optional P wave amplitude (milivolts). Kp : int, optional P wave width (miliseconds). Kpq : int, optional PQ segment width (miliseconds). Aq : float, optional Q wave amplitude (milivolts). Kq1 : int, optional First 5/6 of the Q wave width (miliseconds). Kq2 : int, optional Last 1/6 of the Q wave width (miliseconds). Ar : float, optional R wave amplitude (milivolts). Kr : int, optional R wave width (miliseconds). As : float, optional S wave amplitude (milivolts). Ks : int, optional S wave width (miliseconds). Kcs : int, optional Parameter which allows slight adjustment of S wave shape by cutting away a portion at the end. sm : int, optional Slope parameter in the ST segment. Kst : int, optional ST segment width (miliseconds). At : float, optional 1/2 of the T wave amplitude (milivolts). Kt : int, optional T wave width (miliseconds). si : int, optional Parameter for setting the transition slope between T wave and isoelectric line. Ki : int, optional I segment width (miliseconds). var : float, optional Value between 0.0 and 1.0 that adds variability to the obtained signal, by changing each parameter following a normal distribution with mean value `parameter_value` and std `var * parameter_value`. sampling_rate : int, optional Sampling frequency (Hz). Returns ------- ecg : array Amplitude values of the ECG wave. t : array Time values accoring to the provided sampling rate. params : dict Input parameters of the function Example ------- sampling_rate = 10000 beats = 3 noise_amplitude = 0.05 ECGtotal = np.array([]) for i in range(beats): ECGwave = ecg(sampling_rate=sampling_rate, var=0.1) ECGtotal = np.concatenate((ECGtotal, ECGwave)) t = np.arange(0, len(ECGtotal)) / sampling_rate # add powerline noise (50 Hz) noise = noise_amplitude * np.sin(50 * (2 * pi) * t) ECGtotal += noise plt.plot(t, ECGtotal) plt.xlabel("Time (ms)") plt.ylabel("Amplitude (mV)") plt.grid() plt.title("ECG") plt.show() References ---------- .. [Model03] Pavol DOLINSKÝ, Imrich ANDRÁŠ, Linus MICHAELI, Domenico GRIMALDI, "MODEL FOR GENERATING SIMPLE SYNTHETIC ECG SIGNALS", Acta Electrotechnica et Informatica, Vol. 18, No. 3, 2018, 3–8 .. [ECGwaves] https://ecgwaves.com/ """ if Kp > 120 and Ap >= 0.25: warnings.warn("P wave isn't within physiological values.") if Kq1 + Kq2 > 30 or Aq > 0.25 * Ar: warnings.warn("Q wave isn't within physiological values.") if 120 > Kp + Kpq or Kp + Kpq > 220: warnings.warn("PR interval isn't within physiological limits.") if Kq1 + Kq2 + Kr + Ks - Kcs > 120: warnings.warn( "QRS complex duration isn't within physiological limits.") if Kq1 + Kq2 + Kr + Ks - Kcs + Kst + Kt > 450: warnings.warn( "QT segment duration isn't within physiological limits for men.") if Kq1 + Kq2 + Kr + Ks - Kcs + Kst + Kt > 470: warnings.warn( "QT segment duration isn't within physiological limits for women.") if var < 0 or var > 1: raise TypeError("Variability value should be between 0.0 and 1.0") if var > 0: # change the parameter according to the provided variability nd = lambda x: np.random.normal(x, x * var) Kb = round(np.clip(nd(Kb), 0, 130)) Ap = np.clip(nd(Ap), -0.2, 0.5) Kp = np.clip(nd(Kp), 10, 100) Kpq = round(np.clip(nd(Kpq), 0, 60)) Aq = np.clip(nd(Aq), 0, 0.5) Kq1 = round(np.clip(nd(Kq1), 0, 70)) Kq2 = round(np.clip(nd(Kq2), 0, 50)) Ar = np.clip(nd(Ar), 0.5, 2) Kr = round(np.clip(nd(Kr), 10, 150)) As = np.clip(nd(As), 0, 1) Ks = round(np.clip(nd(Ks), 10, 200)) Kcs = round(np.clip(nd(Kcs), -5, 150)) sm = round(np.clip(nd(sm), 1, 150)) Kst = round(np.clip(nd(Kst), 0, 110)) At = np.clip(nd(At), -0.5, 1) Kt = round(np.clip(nd(Kt), 50, 300)) si = round(np.clip(nd(si), 0, 50)) # variable i is the time between samples (in miliseconds) i = 1000 / sampling_rate l = int(1 / i) B_to_S = (B(Kb, l) + P(Ap, Kp, i) + Pq(Kpq, l) + Q1(Aq, Kq1, i) + Q2(Aq, Kq2, i) + R(Ar, Kr, i) + S(As, Ks, Kcs, i)) St_to_I = (St(As, Ks, Kcs, sm, Kst, i) + T(As, Ks, Kcs, sm, Kst, At, Kt, i) + I(As, Ks, Kcs, sm, Kst, At, Kt, si, Ki, i)) # The signal is filtered in two different sizes ECG1_filtered, n1 = st.smoother(B_to_S, size=50) ECG2_filtered, n2 = st.smoother(St_to_I, size=500) # The signal is concatenated ECGwave = np.concatenate((ECG1_filtered, ECG2_filtered)) # Time array t = np.arange(0, len(ECGwave)) / sampling_rate # output params = { "Kb": 130, "Ap": 0.2, "Kp": 100, "Kpq": 40, "Aq": 0.1, "Kq1": 25, "Kq2": 5, "Ar": 0.7, "Kr": 40, "As": 0.2, "Ks": 30, "Kcs": 5, "sm": 96, "Kst": 100, "At": 0.15, "Kt": 220, "si": 2, "Ki": 200, "var": 0.01, "sampling_rate": 10000, } args = (ECGwave, t, params) names = ("ecg", "t", "params") return utils.ReturnTuple(args, names)
band='bandpass', order=order, frequency=[2, 50], sampling_rate=sampling_rate) filtered_data_ch2, _, _ = st.filter_signal(signal=data_ch2_arr, ftype='FIR', band='bandpass', order=order, frequency=[2, 50], sampling_rate=sampling_rate) # Smooth filtered_data_ch1, _ = st.smoother(signal=filtered_data_ch1, kernel='hamming', size=sm_size, mirror=True) filtered_data_ch2, _ = st.smoother(signal=filtered_data_ch2, kernel='hamming', size=sm_size, mirror=True) # Peaks peaks_ch1 = find_peaks(filtered_data_ch1, sampling_rate) peaks_ch2 = find_peaks(filtered_data_ch2, sampling_rate) for i in range(0, len(peaks_ch1)): peak1 = -5
def calculateECGFeatures(dataFrame, smooth=True, normalize=True): ''' Inputs: dataFrame pandas.dataFrame containing clip of raw ECG data, should have two columns of Timestamps (ms) and Sample (V) smooth optional Boolean, default value is True, flag for whether to smooth input raw ECG data or not normalize option Boolean, default value is True, flag for whether to normalize input raw ECG data or not Outputs: features pandas.dataFrame for input clip of data, each column corresponds to a different feature, included features are as follows: - mean - ultra low frequency power - median - very low frequency power - max - low frequency power - variance - high frequency power - standard deviation - LF/HF ratio - absolute deviation - total power - kurtois - R-R interval - skew ''' # ---------------------------------------------------------------------------------------------------- # Data Preprocessing --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------- time = dataFrame['Timestamp (ms)'].values data = dataFrame['Sample (V)'].values ## Smooth raw ECG data if smooth: smooth_signal = tools.smoother( data, kernel='median', size=5) # Convolutional 5x5 kernel window data = smooth_signal['signal'] ## Normalize raw ECG data if normalize: norm_signal = tools.normalize(data) data = norm_signal['signal'] # ---------------------------------------------------------------------------------------------------- # Begin Features Extraction Code --------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------- ## Calculate basic statistic features s_mean, s_med, s_max, s_var, s_std_dev, s_abs_dev, s_kurtois, s_skew = tools.signal_stats( data) ## Obtain Power Spectra power_freqs, power_spectrum = tools.power_spectrum(data, sampling_rate=2.0, decibel=False) ## Calculate Ultra Low-Frequency Power (ULF Band: <= 0.003 Hz) # power_ulf = tools.band_power(freqs=power_freqs, power=power_spectrum, frequency=[0, 0.003]) # power_ulf = np.absolute(power_ulf['avg_power']) # Calculate Very Low-Frequency Power (VLF Band: 0.0033 - 0.04 Hz) power_vlf = tools.band_power(freqs=power_freqs, power=power_spectrum, frequency=[0.0033, 0.04]) power_vlf = np.absolute(power_vlf['avg_power']) # Calculate Low-Frequency Power (LF Band: 0.04 - 0.15 Hz Hz) power_lf = tools.band_power(freqs=power_freqs, power=power_spectrum, frequency=[0.04, 0.15]) power_lf = np.absolute(power_lf['avg_power']) ## Calculate High-Frequency Power (HF Band: 0.15 - 0.40 Hz) power_hf = tools.band_power(freqs=power_freqs, power=power_spectrum, frequency=[0.15, 0.40]) power_hf = np.absolute(power_hf['avg_power']) ## Calculate LF/HF Ratio lf_hf_ratio = power_lf / power_hf ## Calculate Total Power (VLF + LF + HF power) power_total = power_vlf + power_lf + power_hf # Calculate R peak indices r_peaks = ecg.engzee_segmenter(data, sampling_rate=500.0) if np.size(r_peaks) != 1: rr_indices = time[r_peaks] rr_intervals = np.mean(np.diff(rr_indices)) else: rr_intervals = np.nan # NaN indicates not enough R peaks within window to calculate R-R interval # ---------------------------------------------------------------------------------------------------- # Collect Features and convert into dataFrame -------------------------------------------------------- # ---------------------------------------------------------------------------------------------------- signal_features = { 'mean': s_mean, 'median': s_med, 'max': s_max, 'variance': s_var, 'std_dev': s_std_dev, 'abs_dev': s_abs_dev, 'kurtois': s_kurtois, 'skew': s_skew, # 'ulf_power': power_ulf, 'vlf_power': power_vlf, 'lf_power': power_lf, 'hf_power': power_hf, 'lf_hf_ratio': lf_hf_ratio, 'rr_interval': rr_intervals, } features = pd.Series(signal_features) a = [ s_mean, s_med, s_max, s_var, s_std_dev, s_abs_dev, s_kurtois, s_skew, # power_ulf, power_vlf, power_lf, power_hf, lf_hf_ratio, rr_intervals # rr_indices ] return a