def prepostfilter(reader): scaled_csi = reader.csi_trace no_frames = len(scaled_csi) no_subcarriers = scaled_csi[0]["csi"].shape[0] finalEntry = getCSI(scaled_csi)[15] stdev = running_stdev(finalEntry, 2) hampelData = hampel(finalEntry, 15) smoothedData = running_mean(hampelData.copy(), 15) y = finalEntry y2 = hampelData y3 = smoothedData x = list([x["timestamp"] for x in scaled_csi]) plt.plot(x, y, label="Raw") plt.plot(x, y2, label="Hampel") # plt.plot(x, stdev, label="Standard Deviation") plt.plot(x, y3, "r", label="Hampel + Running Mean") plt.xlabel("Time (s)") plt.ylabel("Amplitude (dBm)") plt.legend(loc="upper right") plt.show()
def updateContents(self, data): self.all_data.append(data) if not self.updateTimestamps(): self.all_data = self.all_data[:-1] return None scaled_csi = self.all_data no_frames = len(scaled_csi) no_subcarriers = 30 finalEntry = [ db(abs(scaled_csi[x]["csi"][14][1][0])) for x in range(no_frames) ] x = list([x["timestamp"] for x in scaled_csi]) #self.plotStand.set_xdata(x) #self.plotStand.set_ydata(finalEntry) hampelData = hampel(finalEntry, 20) #smoothedData = running_mean(hampelData.copy(), 15) self.plotHampel.set_xdata(x) self.plotHampel.set_ydata(hampelData) #self.plotAll.set_xdata(x) #self.plotAll.set_ydata(smoothedData) self.ax.relim() self.ax.autoscale_view() self.fig.canvas.draw() self.fig.canvas.flush_events()
def heatmap(scaled_csi): xax = getTimestamps(scaled_csi) csi, no_frames, no_subcarriers = getCSI(scaled_csi) Fs = 1 / np.mean(np.diff(xax)) print("Sampling Rate: " + str(Fs)) limits = [0, xax[-1], 1, no_subcarriers] #x = subcarrier index #y = time (s) #z = amplitude (dBm) for x in range(no_subcarriers): hampelData = hampel(csi[x].flatten(), 5) smoothedData = running_mean(hampelData, 5) # b, a = signal.butter(9, [1/int(Fs/2), 2/int(Fs/2)], "bandpass") # csi[x] = signal.lfilter(b, a, csi[x].flatten()) csi[x] = smoothedData fig, ax = plt.subplots() im = ax.imshow(csi, cmap="jet", extent=limits, aspect="auto") cbar = ax.figure.colorbar(im, ax=ax) cbar.ax.set_ylabel("Amplitude (dBm)") plt.xlabel("Time (s)") plt.ylabel("Subcarrier Index") plt.show()
def beatsfilter(scaled_csi): xax = getTimestamps(scaled_csi) csi, no_frames, no_subcarriers = getCSI(scaled_csi) Fs = 1 / np.mean(np.diff(xax)) stabilities = [] for x in range(no_subcarriers): finalEntry = csi[x].flatten() variation = stats.variation(finalEntry) if not np.isnan(variation) and variation < 0.5: hampelData = hampel(finalEntry, 5) smoothedData = running_mean(hampelData, 5) filtData = bandpass(5, 1, 1.3, Fs, smoothedData) wLength = 1 * Fs N = np.mod(len(filtData), wLength) windows = np.array_split(filtData, N) psds = [] for window in windows: f, Pxx_den = signal.welch(window, Fs) fMax = f[np.argmax(Pxx_den)] psds.append(fMax) specStab = 1 / np.var(psds) print("Sub: {} has spectral stability: {}".format(x, specStab)) # plt.plot(xax, filtData, alpha=0.5) stabilities.append({ "sub": x, "stability": specStab, "data": filtData }) stabilities.sort(key=lambda x: x["stability"], reverse=True) bestStab = stabilities[0]["stability"] news = [] for stab in stabilities: if stab["stability"] >= (bestStab / 100) * 20: news.append(stab) print("Using {} subcarriers.".format(len(news))) pxxs = [] for sub in news: filtData = sub["data"] f, Pxx_den = signal.welch(filtData, Fs) pxxs.append(Pxx_den) meanPsd = np.mean(pxxs, axis=0) plt.plot(f * 60, meanPsd, alpha=0.5) plt.xlabel("Estimated Heart Rate [bpm]") plt.ylabel("PSD [V**2/Hz]") plt.legend(loc="upper right") plt.show()
def heatmap(scaled_csi): timestamps = getTimestamps(scaled_csi) csi, no_frames, no_subcarriers = getCSI(scaled_csi) Fs = 1 / np.mean(np.diff(timestamps)) print("Sampling Rate: " + str(Fs)) limits = [0, timestamps[-1], 1, no_subcarriers] for x in range(no_subcarriers): hampelData = hampel(csi[x].flatten(), 5) smoothedData = running_mean(hampelData, 5) butteredData = bandpass(7, 0.2, 0.3, Fs, smoothedData) csi[x] = butteredData fig, ax = plt.subplots() im = ax.imshow(csi, cmap="jet", extent=limits, aspect="auto") cbar = ax.figure.colorbar(im, ax=ax) cbar.ax.set_ylabel("Amplitude (dBm)") plt.xlabel("Time (s)") plt.ylabel("Subcarrier Index") plt.show()
def fft(reader): scaled_csi = reader.csi_trace no_frames = len(scaled_csi) no_subcarriers = scaled_csi[0]["csi"].shape[0] finalEntry = getCSI(scaled_csi)[15].flatten() hampelData = hampel(finalEntry, 20) smoothedData = running_mean(hampelData, 30) y = smoothedData.flatten() y -= np.mean(y) b, a = signal.butter(5, [1 / 10, 2 / 10], "bandpass") y = signal.lfilter(b, a, y) x = [i / 20 for i in range(no_frames)] tdelta = (x[-1] - x[0]) / len(x) Fs = 1 / tdelta n = no_frames #rfft is a real Fast Fourier Transform, as we aren't interested in the imaginary parts. #as half of the points lie in the imaginary/negative domain, we get an n/2 point FFT. #rfftfreq produces a list of frequency bins, which requires calculation to produce #interpretable frequencies for BPM tracking. by plotting calculated bpm values, #the graph can be more easily read. #despite rfft producing an n/2 point fft, the calculation still uses n, #because the point spacing is still based on n points. # ((binNumber*samplingRate)/n)*60 produces a per minute rate. ffty = np.fft.rfft(y, len(y)) freq = np.fft.rfftfreq(len(y), tdelta) freqX = [((i * Fs) / n) * 60 for i in range(len(freq))] plt.subplot(211) plt.xlabel("Time (s)") plt.ylabel("Amplitude (Hz)") plt.plot(x, y) plt.subplot(212) plt.xlabel("Breaths Per Minute (BPM)") plt.ylabel("Amplitude (dB/Hz)") axes = plt.gca() axes.set_xlim([0, 30]) plt.plot(freqX, np.abs(ffty)) plt.show()
def updateButterworth(self, data): self.all_data.append(data) self.updateTimestamps() if not self.updateTimestamps(): self.all_data = self.all_data[:-1] return None scaled_csi = self.all_data no_frames = len(scaled_csi) no_subcarriers = scaled_csi[0]["csi"].shape[0] if no_frames < 50: return None #Replace complex CSI with amplitude. finalEntry = [ db(abs(scaled_csi[x]["csi"][15][0][0])) for x in range(no_frames) ] hampelData = hampel(finalEntry, 10) smoothedData = running_mean(hampelData, 30) y = smoothedData x = list([x["timestamp"] for x in scaled_csi]) tdelta = (x[-1] - x[0]) / len(x) Fs = 1 / tdelta n = no_frames y = bandpass(5, 1.0, 1.3, Fs, y) ffty = np.fft.rfft(y, len(y)) freq = np.fft.rfftfreq(len(y), tdelta) freqX = [((i * Fs) / n) * 60 for i in range(len(freq))] self.plotButt.set_xdata(freqX) self.plotButt.set_ydata(np.abs(ffty)) self.ax.relim() self.ax.autoscale_view() self.fig.canvas.draw() self.fig.canvas.flush_events()
def varianceGraph(reader): scaled_csi = reader.csi_trace no_frames = len(scaled_csi) no_subcarriers = scaled_csi[1]["csi"].shape[0] y = [] finalEntry = getCSI(scaled_csi) for x in range(no_subcarriers): hampelData = hampel(finalEntry[x], 10) smoothedData = running_mean(hampelData, 25) y.append(variance(smoothedData)) plt.xlabel("Subcarrier Index") plt.ylabel("Variance") plt.plot(y) plt.show()
def breathingfilter(scaled_csi): xax = getTimestamps(scaled_csi) csi, no_frames, no_subcarriers = getCSI(scaled_csi) # Zeroing out invalid subcarriers. # for i in []: # for x in range(no_frames): # csi[i][x] = 0 # for x in range(no_subcarriers): for x in range(80, 100): finalEntry = csi[x].flatten() hampelData = hampel(finalEntry, 10) smoothedData = running_mean(hampelData, 10) # csi[x] = hampelData csi[x] = smoothedData plt.plot(xax, csi[x], alpha=0.5, label=x) plt.xlabel("Time (s)") plt.ylabel("Amplitude") plt.legend(loc="upper right") plt.show()
def specstabfilter(reader, Fs): scaled_csi = reader.csi_trace no_frames, no_subcarriers, finalEntries = getCSI(scaled_csi) #Using CSI phase difference data. stabilities = [] for x in range(no_subcarriers): finalEntry = finalEntries[x] #CardioFi paper uses 100Hz uniformly sampled data, which may mean we need to use different #filter parameters. For now, we will copy them verbatim. #Currently the initial hampel filter is run using T1=0.5s t1=0.4 # dynamic_detrending is run with c=5 l=3 alpha=1.2 Fs=Fs # the second hampel filter is run using T1=0.5s t1=0.1 # the bandpass filter is run at 5th order with a range of 1-2Hz. hampelData = hampel(finalEntry, 20) detrended = dynamic_detrend(hampelData, 5, 3, 1.2, Fs) rehampeledData = hampel(detrended, 20) filtData = bandpass(7, 1, 1.5, Fs, rehampeledData) #Spectral Stability section. #The spectral stability metric aims to score subcarriers based on the #variance of their heart rate estimations. #To do this, both the length of the data being used for estimations, #and a sliding window length must be selected. #While the dataLength should ideally be around 40s to mirror that used #in the CardioFi paper, this is difficult to replicate using the data #which was produced over the last few months. #Shorter data and window lengths are being experimented with but do not #appear to be working well. This is evidenced by the number of subcarriers #being selected using the metric, which is typically lower than that seen #in the CardioFi paper. #Notably, it is also difficult to take consistent ground truth heart rate #measurements over a 40 second period. wLength = 5 * Fs dataLength = 10 * Fs windows = [] position = 0 #This sliding window system splits filtData into wLength-sized windows, #except for the final window which contains the remainder of the data. #Each sliding window has an overlap of 1 second (or Fs). while position < dataLength: if (position + wLength) > dataLength: window = filtData[position:] else: window = filtData[position:position + wLength] windows.append(window) position += wLength - Fs #PSD measurements are then taken for each window, and the variance of #these measurements are then collected to produce a final score for #this subcarrier. psds = [] for window in windows: f, Pxx_den = signal.welch(window, Fs) fMax = f[np.argmax(Pxx_den)] psds.append(fMax) specStab = 1 / np.var(psds) if specStab == np.inf: specStab = 0 stabilities.append([x, specStab, filtData]) # print("Sub: {} has spectral stability: {}".format(x, specStab)) # print("Sub: {} mean pred: {}".format(x, np.mean(psds)*60)) stabilities.sort(key=lambda x: x[1], reverse=True) bestStab = stabilities[0][1] news = [x for x in stabilities if x[1] > bestStab * 0.2] print("Using {} subcarriers.".format(len(news))) pxxs = [] for sub in news: data = sub[2] f, Pxx_den = signal.welch(data, Fs) pxxs.append(Pxx_den) #The mean PSD of the top subcarriers is then selected, with the #peak taken as the final heart rate estimation. meanPsd = np.mean(pxxs, axis=0) return f[np.argmax(meanPsd)] * 60
def shorttime(reader, subcarrier): scaled_csi = reader.csi_trace no_frames = len(scaled_csi) no_subcarriers = scaled_csi[0]["csi"].shape[0] finalEntry = np.zeros((no_frames, 1)) hampelData = np.zeros((no_frames, 1)) smoothedData = np.zeros((no_frames, 1)) #Replace complex CSI with amplitude. finalEntry = [ db( abs(scaled_csi[x]["csi"][subcarrier][reader.x_antenna][ reader.y_antenna])) for x in range(no_frames) ] hampelData = hampel(finalEntry, 30) smoothedData = running_mean(hampelData, 20) y = smoothedData tdelta = 0.01 x = list([x * tdelta for x in range(0, no_frames)]) Fs = 1 / tdelta n = no_frames fmin = 0.7 fmax = 2.3 b, a = signal.butter(3, [fmin / (Fs / 2), fmax / (Fs / 2)], "bandpass") y = signal.lfilter(b, a, y) f, t, Zxx = signal.stft(y, Fs, nperseg=2000, noverlap=1750) #bin indices for the observed amplitude peak in each segment. Zxx = np.abs(Zxx) maxAx = np.argmax(Zxx, axis=0) freqs = [] for i in maxAx: arc = f[i] if arc >= fmin and arc <= fmax: arc *= 60 freqs.append(arc) else: freqs.append(None) bpms = [] for freq in freqs: if freq != None: bpms.append(freq) print(bpms) print("Average HR: " + str(np.average(bpms))) #We're only interested in frequencies between 0.5 and 2. freq_slice = np.where((f >= fmin) & (f <= fmax)) f = f[freq_slice] Zxx = Zxx[freq_slice, :][0] plt.pcolormesh(t, f * 60, np.abs(Zxx)) plt.plot(t, freqs, color="red", marker="x") plt.title('STFT Magnitude') # plt.ylabel('Frequency [Hz]') plt.ylabel('Heart Rate [BPM]') plt.xlabel('Time [sec]') plt.show()