Skip to content

Commit ef4bd10

Browse files
committed
- PSD is calculated now(Previously ASD is calculated)
- FFT is normalized to the len(filtered_eeg_window_padded) - Hanning window is used now - as it is designed to minimize spectral leakage while hamming may exhibit more spectral leakage.
1 parent 095d0c8 commit ef4bd10

File tree

1 file changed

+19
-34
lines changed

1 file changed

+19
-34
lines changed

ffteeg.py

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import numpy as np
2-
from numpy import hamming
2+
from numpy import hamming, hanning
33
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget
44
from PyQt5.QtCore import Qt
55
from pyqtgraph import PlotWidget
@@ -25,7 +25,7 @@ def __init__(self):
2525
self.eeg_plot_widget.setBackground('w')
2626
self.eeg_plot_widget.showGrid(x=True, y=True)
2727
self.eeg_plot_widget.setLabel('bottom', 'EEG Plot')
28-
self.eeg_plot_widget.setYRange(0, 15000, padding=0)
28+
self.eeg_plot_widget.setYRange(0, 5000, padding=0)
2929
self.eeg_plot_widget.setXRange(0, 10, padding=0)
3030
self.eeg_plot_widget.setMouseEnabled(x=False, y=True) # Disable zoom
3131
self.main_layout.addWidget(self.eeg_plot_widget)
@@ -38,9 +38,10 @@ def __init__(self):
3838
self.fft_plot.setBackground('w')
3939
self.fft_plot.showGrid(x=True, y=True)
4040
self.fft_plot.setLabel('bottom', 'FFT')
41-
self.fft_plot.setYRange(0, 25000, padding=0)
41+
# self.fft_plot.setYRange(0, 25000, padding=0)
4242
self.fft_plot.setXRange(0, 50, padding=0) # Set x-axis to 0 to 50 Hz
4343
self.fft_plot.setMouseEnabled(x=False, y=False) # Disable zoom
44+
self.fft_plot.setAutoVisible(y=True) # Allow y-axis to autoscale
4445
self.bottom_layout.addWidget(self.fft_plot)
4546

4647
# Bar graph for brainwave power bands (right side of the second half)
@@ -77,14 +78,9 @@ def __init__(self):
7778
self.time_data = np.linspace(0, 10, self.buffer_size) # Fixed time array for plotting
7879
self.current_index = 0 # Index for overwriting data
7980

80-
# Moving window for brainwave power
81-
self.moving_window_size = self.sampling_rate * 3 # 3-second window
82-
self.moving_window_buffer = np.zeros(self.moving_window_size)
83-
8481
# Filters
8582
self.b_notch, self.a_notch = iirnotch(50, 30, self.sampling_rate)
86-
self.b_highpass, self.a_highpass = butter(4, 1.5 / (0.5 * self.sampling_rate), btype='high')
87-
self.b_lowpass, self.a_lowpass = butter(4, 45 / (0.5 * self.sampling_rate), btype='low')
83+
self.b_band, self.a_band = butter(4, [0.5 / (self.sampling_rate / 2), 48.0 / (self.sampling_rate / 2)], btype='band')
8884

8985
# Timer for updating the plot
9086
self.timer = pg.QtCore.QTimer()
@@ -109,56 +105,45 @@ def update_plot(self):
109105

110106
# Apply filters to the full data for EEG plot
111107
filtered_eeg = filtfilt(self.b_notch, self.a_notch, plot_data)
112-
filtered_eeg = filtfilt(self.b_highpass, self.a_highpass, filtered_eeg)
113-
filtered_eeg = filtfilt(self.b_lowpass, self.a_lowpass, filtered_eeg)
108+
filtered_eeg = filtfilt(self.b_band, self.a_band, filtered_eeg)
114109

115110
# Update the EEG plot with the filtered data
116111
self.eeg_curve.setData(self.time_data, filtered_eeg)
117112

118113
# Perform FFT on the latest 1-second slice
119114
latest_data = filtered_eeg[-self.sampling_rate:]
120-
window = hamming(len(latest_data))
115+
window = np.hanning(len(latest_data))
121116
filtered_eeg_windowed = latest_data * window
122117

123118
# Apply zero-padding
124119
zero_padded_length = 512
125120
filtered_eeg_windowed_padded = np.pad(filtered_eeg_windowed, (0, zero_padded_length - len(filtered_eeg_windowed)), 'constant')
126121

127122
eeg_fft = np.abs(fft(filtered_eeg_windowed_padded))[:len(filtered_eeg_windowed_padded) // 2]
123+
eeg_fft /= len(filtered_eeg_windowed_padded) # Normalize FFT
128124
freqs = np.fft.fftfreq(len(filtered_eeg_windowed_padded), 1 / self.sampling_rate)[:len(filtered_eeg_windowed_padded) // 2]
129125

130126
# Update FFT plot
131127
self.fft_curve.setData(freqs, eeg_fft)
132128

133-
# Update the 3-second moving window buffer
134-
for sample in latest_data:
135-
self.moving_window_buffer = np.roll(self.moving_window_buffer, -1)
136-
self.moving_window_buffer[-1] = sample
137-
138-
# Apply filters to the moving window buffer
139-
filtered_window = filtfilt(self.b_notch, self.a_notch, self.moving_window_buffer)
140-
filtered_window = filtfilt(self.b_highpass, self.a_highpass, filtered_window)
141-
filtered_window = filtfilt(self.b_lowpass, self.a_lowpass, filtered_window)
142-
143-
# Perform FFT on the moving window buffer
144-
windowed_data = filtered_window * hamming(len(filtered_window))
145-
fft_data = np.abs(fft(windowed_data))[:len(windowed_data) // 2]
146-
window_freqs = np.fft.fftfreq(len(windowed_data), 1 / self.sampling_rate)[:len(windowed_data) // 2]
147-
148-
brainwave_power = self.calculate_brainwave_power(fft_data, window_freqs)
129+
brainwave_power = self.calculate_brainwave_power(eeg_fft, freqs)
149130
self.brainwave_bars.setOpts(height=brainwave_power)
150131

151132
def calculate_brainwave_power(self, fft_data, freqs):
152-
delta_power = np.sum(fft_data[(freqs >= 0.5) & (freqs <= 4)])
153-
theta_power = np.sum(fft_data[(freqs >= 4) & (freqs <= 8)])
154-
alpha_power = np.sum(fft_data[(freqs >= 8) & (freqs <= 13)])
155-
beta_power = np.sum(fft_data[(freqs >= 13) & (freqs <= 30)])
156-
gamma_power = np.sum(fft_data[(freqs >= 30) & (freqs <= 45)])
133+
delta_power = np.sum(fft_data[(freqs >= 0.5) & (freqs <= 4)] ** 2)
134+
theta_power = np.sum(fft_data[(freqs >= 4) & (freqs <= 8)] ** 2)
135+
alpha_power = np.sum(fft_data[(freqs >= 8) & (freqs <= 13)] ** 2)
136+
beta_power = np.sum(fft_data[(freqs >= 13) & (freqs <= 30)] ** 2)
137+
gamma_power = np.sum(fft_data[(freqs >= 30) & (freqs <= 45)] ** 2)
157138

158139
return [delta_power, theta_power, alpha_power, beta_power, gamma_power]
159140

160141
if __name__ == "__main__":
161142
app = QApplication(sys.argv)
162143
window = EEGMonitor()
163144
window.show()
164-
sys.exit(app.exec_())
145+
sys.exit(app.exec_())
146+
147+
# PSD is calculated now(Previously ASD is calculated)
148+
# FFT is normalized to the len(filtered_eeg_window_padded)
149+
# Hanning window is used now - as it is designed to minimize spectral leakage while hamming may exhibit more spectral leakage.

0 commit comments

Comments
 (0)