def run(inp, opt, cfg): data = arr.interp_undefined(inp['ecg']['vals']) srate = inp['ecg']['srate'] r_list = arr.detect_qrs(data, srate) # detect r-peak ret_rpeak = [] for idx in r_list: dt = idx / srate ret_rpeak.append({'dt': dt, 'val': 1}) return [ret_rpeak]
def run(inp, opt, cfg): data = arr.interp_undefined(inp['ecg']['vals']) srate = inp['ecg']['srate'] span = int(srate * 10) # Cut every 10 second regardless of interval baseline = [0] * len(data) x = np.arange(0, span) for spos in range(0, len(data), span): # start position of this segment if spos + span > len(data): span = len(data) - spos x = x[0:span] med = np.median( data[spos:spos + span]) # compute overall median for the entire waveform for j in range(span): data[ spos + j] -= med # shift each sample of the entire waveform by this median value y = data[spos:spos + span] p = np.polyfit(x, y, 4) y = np.polyval(p, x) for j in range(span): baseline[spos + j] = y[j] data[spos + j] -= y[j] r_list = arr.detect_qrs(data, srate) # detect r-peak for i in range(len(r_list) - 1): # for each rr interval idx1 = r_list[i] idx2 = r_list[i + 1] if idx1 + 1 > idx2: continue med = np.median(data[idx1 + 1:idx2]) for j in range(idx1 + 1, idx2): data[j] -= med return [{'srate': srate, 'vals': data}, {'srate': srate, 'vals': baseline}]
def run(inp, opt, cfg): data = arr.interp_undefined(inp['ecg']['vals']) srate = inp['ecg']['srate'] ecg_500 = data if srate != 500: ecg_500 = arr.resample(data, math.ceil(len(data) / srate * 500)) # resample to 500 Hz srate = 500 ecg_filt = arr.band_pass(ecg_500, srate, 0.01, 100) # filtering ecg_filt = arr.remove_wander_spline(ecg_filt, srate) # remove baseline wander r_list = arr.detect_qrs(ecg_filt, srate) # detect r-peak new_r_list = [] for ridx in r_list: # remove qrs before and after overlap if cfg['overlap'] <= ridx / srate: new_r_list.append(ridx) r_list = new_r_list ret_rpeak = [] for ridx in r_list: ret_rpeak.append({'dt': ridx / srate}) segbeats = 128 segsteps = 32 # int(segbeats/4) # for each segments twavs = [] twars = [] ret_twav = [] ret_twar = [] ret_avg_beat = {'srate': srate, 'vals': [0] * len(ecg_500)} iseg = 0 for seg_start in range( 0, len(r_list) - segbeats, segsteps ): # Separates in 128-beat units regardless of input length iseg += 1 hrs = [] # calculate hrs for i in range(segbeats - 1): hr = srate / (r_list[seg_start + i + 1] - r_list[seg_start + i]) hrs.append(hr) if max(hrs) - min(hrs) > 20: # print('seg ' + iseg + ' excluded HR diff > ' + diff_hr) continue # only -250 to 350 ms from R peak idx_r = int(0.25 * srate) # idx_r == 125 beat_len = int(0.6 * srate) # beat_len == 300 beats = [] for i in range(segbeats): ridx = r_list[seg_start + i] beat = ecg_filt[ridx - idx_r:ridx - idx_r + beat_len] beats.append(beat) beats = np.array(beats) # remove each beat's baseline voltage # no effect because of R peak leveling is below # Baseline correction included estimation of the baseline in the isoelectric PQ # segment by averaging 16 successive samples in this time window pq_width = int(0.008 * srate) # for i in range(segbeats): # idx_base = arr.min_idx(beats[i], idx_r - int(0.15 * srate), idx_r) # min_std = 999999 # for j in range(idx_base - int(0.03 * srate), idx_base + int(0.03 * srate)): # # The baseline is the point at which the standard deviation of around 15ms is minimized. # this_std = np.std(beats[i][j - pq_width:j + pq_width]) # if this_std < min_std: # idx_base = j # min_std = this_std # beats[i] -= np.mean(beats[i][idx_base - pq_width:idx_base + pq_width]) # calculate average beat avg_beat = np.mean(beats, axis=0) # average beat of the segbeats beats # find minimum values from avg_beat in both sides idx_start = idx_r - int(0.15 * srate) # idx_start == 50 idx_end = idx_r + int(0.1 * srate) # idx_end == 175 idx_base = arr.min_idx(avg_beat, idx_start, idx_r) # avg_beat's baseline min_std = 999999 # find minimum std value for j in range(idx_base - int(0.03 * srate), idx_base + int(0.03 * srate)): idx_from = max(0, j - pq_width) idx_to = min(len(avg_beat), j + pq_width) this_std = np.std(avg_beat[idx_from:idx_to]) # print("{} {}".format(j, this_std)) if this_std < min_std: idx_base = j min_std = this_std # print("idx_base={}", idx_base) min_left = np.mean(avg_beat[idx_base - pq_width:idx_base + pq_width]) min_right = np.min(avg_beat[idx_r:idx_end]) # threshold = 5% of max val th_left = min_left + 0.05 * (avg_beat[idx_r] - min_left) th_right = min_right + 0.05 * (avg_beat[idx_r] - min_right) idx_qrs_start = idx_r - int(0.05 * srate) idx_qrs_end = idx_r + int(0.05 * srate) for j in range(idx_r, idx_r - int(0.1 * srate), -1): # idx_r = 125 if avg_beat[j] < th_left: idx_qrs_start = j break for j in range(idx_r, idx_r + int(0.1 * srate)): if avg_beat[j] < th_right: idx_qrs_end = j break # find offset with maximum correlation offsets = [] # for each beat, likes [0, -1, 0, 0, 1, ...] qrs_coeffs = [] offset_width = int(0.01 * srate) # 3 = range for finding offset for i in range(segbeats): # for each beat maxoffset = -offset_width maxce = -999999 for offset in range(-offset_width, offset_width + 1): ce = arr.corr( avg_beat[idx_qrs_start:idx_qrs_end], beats[i][offset + idx_qrs_start:offset + idx_qrs_end]) if maxce < ce: maxoffset = offset maxce = ce offsets.append(maxoffset) qrs_coeffs.append(maxce) # move beats by the offset new_beats = [] for i in range(segbeats): ost = offsets[i] beat = beats[i].tolist() if ost < 0: beat = [0] * -ost + beat[:ost] else: beat = beat[ost:] + [0] * ost new_beats.append(beat) beats = np.array(new_beats) # beats.shape == (segbeats,300) # calculate average beat avg_beat = np.mean(beats, axis=0) # average beat of the segbeats beats # replace vpc as template nreplaced = 0 for i in range(segbeats): ce = arr.corr(avg_beat, beats[i]) if ce < 0.95: nreplaced += 1 beats[i] = copy.deepcopy(avg_beat) offsets[i] = 0 #print('{} beats are replaced'.format(nreplaced)) if nreplaced > 0.1 * segbeats: print('excluded VPC > {}'.format(nreplaced)) continue # qrs level alignment # idx_r == 125 # len(avg_beat) == beat_len == 300 for i in range(segbeats): beats[i] -= beats[i][idx_r] # plot for debugging # plt.plot() # for i in range(segbeats): # col = 'blue' # if i % 2: # col = 'red' # plt.plot(beats[i], c=col, ls='-') # plt.savefig('{:02d}_{}.png'.format(opt['ifile'], iseg)) # plt.close() # gather segbeats beats from idx_r(125) to beat_len(300) # power spectrums of segbeats beats spect = [] for idx_from_r in range(beat_len - idx_r): timed_samples = beats[:, idx_r + idx_from_r] # timed_samples *= np.hamming(len(timed_samples)) segfft = 2**np.abs(np.fft.fft(timed_samples)) spect.append(segfft) # each segbeats beat fft result spect = np.array( spect) # rows == idx_from_r, cols == frequency(0-segbeats) # power spectra are summed into a composite in which # the magnitude at 0.5 cycles/beat indicates raw alternans (in mv2) # cum_spect.shape == segbeats # cumulative spectum of beats st_start = int(0.1 * srate) # idx_qrs_end - idx_r #int(0.1*srate) st_end = int(0.25 * srate) avg_spect = np.mean( spect[st_start:st_end, :], axis=0) # between 100 (50) and 250 ms (125) from rpeak avg_alt = avg_spect[int(0.5 * segbeats)] # cum_spect_noise = cum_spect[int(0.4*segbeats):int(0.46*segbeats)] # noise level: 0.44-0.49 cycles / beat avg_spect_noise = avg_spect[int(0.44 * segbeats):int( 0.49 * segbeats)] # noise level: 0.44-0.49 cycles / beat # cum_spect_noise = cum_spect[int(0.33 * segbeats):int(0.48 * segbeats)] # noise level: 0.44-0.49 cycles / beat avg_noise_avg = np.mean(avg_spect_noise) avg_noise_std = np.std(avg_spect_noise) # return avg beat # avg_beat = np.mean(beats, axis=0) for j in range(len(avg_beat)): if len(ret_avg_beat['vals']) > r_list[seg_start + segbeats - 1] + j: ret_avg_beat['vals'][r_list[seg_start + segbeats - 1] + j] = avg_beat[j] # print('avg alt {}, noise {}'.format(cum_alt, cum_noise_avg)) twar = 0 if avg_alt > avg_noise_avg: twav = 1000 * (avg_alt - avg_noise_avg)**0.5 twar = (avg_alt - avg_noise_avg) / avg_noise_std twavs.append(twav) ret_twav.append({ 'dt': r_list[seg_start + segbeats - 1] / srate, 'val': twav }) twars.append(twar) ret_twar.append({ 'dt': r_list[seg_start + segbeats - 1] / srate, 'val': twar }) # plt.figure(figsize=(30, 5)) # plt.plot(ecg_filt.tolist(), color='black', lw=1) # plt.savefig('e:/{}_raw.pdf'.format(twar), bbox_inches="tight", pad_inches=0.5) # plt.close() # # plt.figure(figsize=(10, 5)) # for i in range(len(beats)): # c = 'red' # if i % 2 == 0: # c = 'blue' # plt.plot(beats[i], color=c, lw=1) # plt.savefig('e:/{}_ecg.pdf'.format(twar), bbox_inches="tight", pad_inches=0.5) # plt.close() # # plt.figure(figsize=(10, 5)) # plt.plot(np.arange(1, 65) / 128, avg_spect[1:65], lw=1) # plt.savefig('e:/{}_spect.pdf'.format(twar), bbox_inches="tight", pad_inches=0.5) # plt.close() dt_last = r_list[-1] / srate - cfg['overlap'] return [{ 'srate': srate, 'vals': ecg_filt.tolist() }, ret_avg_beat, ret_rpeak, ret_twav, ret_twar]
def run(inp, opt, cfg): ecg_data = arr.interp_undefined(inp['ecg']['vals']) ecg_srate = inp['ecg']['srate'] pleth_data = arr.interp_undefined(inp['pleth']['vals']) pleth_srate = inp['pleth']['srate'] pleth_data = arr.band_pass(pleth_data, pleth_srate, 0.5, 15) ecg_rlist = arr.detect_qrs(ecg_data, ecg_srate) pleth_minlist, pleth_maxlist = arr.detect_peaks(pleth_data, pleth_srate) dpleth = np.diff(pleth_data) pleth_dmaxlist = [ ] # index of the maximum slope between peak and nadir in pleth for i in range(len(pleth_minlist)): # maxlist is one less than minlist dmax_idx = arr.max_idx(dpleth, pleth_minlist[i], pleth_maxlist[i + 1]) pleth_dmaxlist.append(dmax_idx) pttmax_list = [] pttmin_list = [] pttdmax_list = [] for i in range(len(ecg_rlist) - 1): if len(pleth_minlist) == 0: continue if len(pleth_maxlist) == 0: continue rpeak_dt = ecg_rlist[i] / ecg_srate rpeak_dt_next = ecg_rlist[i + 1] / ecg_srate if rpeak_dt < cfg['overlap']: continue # find first min in pleth after rpeak_dt in ecg found_minidx = 0 for minidx in pleth_minlist: if minidx > rpeak_dt * pleth_srate: found_minidx = minidx break elif minidx > rpeak_dt_next * pleth_srate: break if found_minidx == 0: continue # find first dmax in pleth after rpeak_dt in ecg found_dmaxidx = 0 for dmaxidx in pleth_dmaxlist: if dmaxidx > rpeak_dt * pleth_srate: found_dmaxidx = dmaxidx break elif dmaxidx > rpeak_dt_next * pleth_srate: break if found_dmaxidx == 0: continue # find first dmax in pleth after rpeak_dt in ecg found_maxidx = 0 for maxidx in pleth_maxlist: if maxidx > rpeak_dt * pleth_srate: found_maxidx = maxidx break elif maxidx > rpeak_dt_next * pleth_srate: break if found_maxidx == 0: continue max_dt = found_maxidx / pleth_srate if max_dt > cfg['interval']: continue min_dt = found_minidx / pleth_srate dmax_dt = found_dmaxidx / pleth_srate pttmax_list.append({'dt': max_dt, 'val': (max_dt - rpeak_dt) * 1000}) pttdmax_list.append({ 'dt': dmax_dt, 'val': (dmax_dt - rpeak_dt) * 1000 }) pttmin_list.append({'dt': min_dt, 'val': (min_dt - rpeak_dt) * 1000}) return [ pttmin_list, pttdmax_list, arr.get_samples(ecg_data, ecg_srate, ecg_rlist), pttmax_list ]