1
1
import numpy as np
2
- from numpy import hamming
2
+ from numpy import hamming , hanning
3
3
from PyQt5 .QtWidgets import QApplication , QVBoxLayout , QHBoxLayout , QMainWindow , QWidget
4
4
from PyQt5 .QtCore import Qt
5
5
from pyqtgraph import PlotWidget
@@ -25,7 +25,7 @@ def __init__(self):
25
25
self .eeg_plot_widget .setBackground ('w' )
26
26
self .eeg_plot_widget .showGrid (x = True , y = True )
27
27
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 )
29
29
self .eeg_plot_widget .setXRange (0 , 10 , padding = 0 )
30
30
self .eeg_plot_widget .setMouseEnabled (x = False , y = True ) # Disable zoom
31
31
self .main_layout .addWidget (self .eeg_plot_widget )
@@ -38,9 +38,10 @@ def __init__(self):
38
38
self .fft_plot .setBackground ('w' )
39
39
self .fft_plot .showGrid (x = True , y = True )
40
40
self .fft_plot .setLabel ('bottom' , 'FFT' )
41
- self .fft_plot .setYRange (0 , 25000 , padding = 0 )
41
+ # self.fft_plot.setYRange(0, 25000, padding=0)
42
42
self .fft_plot .setXRange (0 , 50 , padding = 0 ) # Set x-axis to 0 to 50 Hz
43
43
self .fft_plot .setMouseEnabled (x = False , y = False ) # Disable zoom
44
+ self .fft_plot .setAutoVisible (y = True ) # Allow y-axis to autoscale
44
45
self .bottom_layout .addWidget (self .fft_plot )
45
46
46
47
# Bar graph for brainwave power bands (right side of the second half)
@@ -77,14 +78,9 @@ def __init__(self):
77
78
self .time_data = np .linspace (0 , 10 , self .buffer_size ) # Fixed time array for plotting
78
79
self .current_index = 0 # Index for overwriting data
79
80
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
-
84
81
# Filters
85
82
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' )
88
84
89
85
# Timer for updating the plot
90
86
self .timer = pg .QtCore .QTimer ()
@@ -109,56 +105,45 @@ def update_plot(self):
109
105
110
106
# Apply filters to the full data for EEG plot
111
107
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 )
114
109
115
110
# Update the EEG plot with the filtered data
116
111
self .eeg_curve .setData (self .time_data , filtered_eeg )
117
112
118
113
# Perform FFT on the latest 1-second slice
119
114
latest_data = filtered_eeg [- self .sampling_rate :]
120
- window = hamming (len (latest_data ))
115
+ window = np . hanning (len (latest_data ))
121
116
filtered_eeg_windowed = latest_data * window
122
117
123
118
# Apply zero-padding
124
119
zero_padded_length = 512
125
120
filtered_eeg_windowed_padded = np .pad (filtered_eeg_windowed , (0 , zero_padded_length - len (filtered_eeg_windowed )), 'constant' )
126
121
127
122
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
128
124
freqs = np .fft .fftfreq (len (filtered_eeg_windowed_padded ), 1 / self .sampling_rate )[:len (filtered_eeg_windowed_padded ) // 2 ]
129
125
130
126
# Update FFT plot
131
127
self .fft_curve .setData (freqs , eeg_fft )
132
128
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 )
149
130
self .brainwave_bars .setOpts (height = brainwave_power )
150
131
151
132
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 )
157
138
158
139
return [delta_power , theta_power , alpha_power , beta_power , gamma_power ]
159
140
160
141
if __name__ == "__main__" :
161
142
app = QApplication (sys .argv )
162
143
window = EEGMonitor ()
163
144
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