Skip to content

Commit 288671e

Browse files
authored
Merge pull request #30 from PayalLakra/bio_amptool
Changes done in ffteeg code
2 parents a51ce4f + be88b30 commit 288671e

File tree

2 files changed

+166
-73
lines changed

2 files changed

+166
-73
lines changed

ffteeg.py

Lines changed: 66 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import numpy as np
2-
from numpy import hamming
2+
from collections import deque
33
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget
44
from PyQt5.QtCore import Qt
55
from pyqtgraph import PlotWidget
66
import pyqtgraph as pg
77
import pylsl
88
import sys
9-
from scipy.signal import butter, filtfilt, iirnotch
9+
from scipy.signal import butter, iirnotch, lfilter, lfilter_zi
1010
from scipy.fft import fft
11+
import math
1112

1213
class EEGMonitor(QMainWindow):
1314
def __init__(self):
@@ -25,8 +26,8 @@ def __init__(self):
2526
self.eeg_plot_widget.setBackground('w')
2627
self.eeg_plot_widget.showGrid(x=True, y=True)
2728
self.eeg_plot_widget.setLabel('bottom', 'EEG Plot')
28-
self.eeg_plot_widget.setYRange(0, 15000, padding=0)
29-
self.eeg_plot_widget.setXRange(0, 10, padding=0)
29+
self.eeg_plot_widget.setYRange(-5000, 5000, padding=0)
30+
self.eeg_plot_widget.setXRange(0, 2, padding=0)
3031
self.eeg_plot_widget.setMouseEnabled(x=False, y=True) # Disable zoom
3132
self.main_layout.addWidget(self.eeg_plot_widget)
3233

@@ -38,9 +39,10 @@ def __init__(self):
3839
self.fft_plot.setBackground('w')
3940
self.fft_plot.showGrid(x=True, y=True)
4041
self.fft_plot.setLabel('bottom', 'FFT')
41-
self.fft_plot.setYRange(0, 25000, padding=0)
42+
# self.fft_plot.setYRange(0, 25000, padding=0)
4243
self.fft_plot.setXRange(0, 50, padding=0) # Set x-axis to 0 to 50 Hz
4344
self.fft_plot.setMouseEnabled(x=False, y=False) # Disable zoom
45+
self.fft_plot.setAutoVisible(y=True) # Allow y-axis to autoscale
4446
self.bottom_layout.addWidget(self.fft_plot)
4547

4648
# Bar graph for brainwave power bands (right side of the second half)
@@ -71,20 +73,20 @@ def __init__(self):
7173
self.sampling_rate = int(self.inlet.info().nominal_srate())
7274
print(f"Sampling rate: {self.sampling_rate} Hz")
7375

74-
# Data and buffers
75-
self.buffer_size = self.sampling_rate * 10 # Fixed-size buffer for 10 seconds
76-
self.eeg_data = np.zeros(self.buffer_size) # Fixed-size array for circular buffer
77-
self.time_data = np.linspace(0, 10, self.buffer_size) # Fixed time array for plotting
78-
self.current_index = 0 # Index for overwriting data
76+
# Data and Buffers
77+
self.one_second_buffer = deque(maxlen=self.sampling_rate) # 1-second buffer
78+
self.buffer_size = self.sampling_rate * 10
79+
self.moving_window_size = self.sampling_rate * 2 # 2-second window
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)
81+
self.eeg_data = np.zeros(self.buffer_size)
82+
self.time_data = np.linspace(0, 10, self.buffer_size)
83+
self.current_index = 0
8384

84-
# Filters
8585
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')
86+
self.b_band, self.a_band = butter(4, [0.5 / (self.sampling_rate / 2), 48.0 / (self.sampling_rate / 2)], btype='band')
87+
88+
self.zi_notch = lfilter_zi(self.b_notch, self.a_notch) * 0
89+
self.zi_band = lfilter_zi(self.b_band, self.a_band) * 0
8890

8991
# Timer for updating the plot
9092
self.timer = pg.QtCore.QTimer()
@@ -95,68 +97,59 @@ def __init__(self):
9597
self.fft_curve = self.fft_plot.plot(pen=pg.mkPen('r', width=1)) # FFT Colour is red
9698

9799
def update_plot(self):
98-
samples, _ = self.inlet.pull_chunk(timeout=0.0, max_samples=30)
100+
samples, _ = self.inlet.pull_chunk(timeout=0.0)
99101
if samples:
100102
for sample in samples:
101-
# Overwrite the oldest data point in the buffer
102-
self.eeg_data[self.current_index] = sample[0]
103-
self.current_index = (self.current_index + 1) % self.buffer_size # Circular increment
104-
105-
if self.current_index >= self.buffer_size:
106-
plot_data = self.eeg_data
107-
else:
108-
plot_data = np.concatenate((self.eeg_data[self.current_index:], self.eeg_data[:self.current_index]))
109-
110-
# Apply filters to the full data for EEG plot
111-
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)
114-
115-
# Update the EEG plot with the filtered data
116-
self.eeg_curve.setData(self.time_data, filtered_eeg)
117-
118-
# Perform FFT on the latest 1-second slice
119-
latest_data = filtered_eeg[-self.sampling_rate:]
120-
window = hamming(len(latest_data))
121-
filtered_eeg_windowed = latest_data * window
122-
123-
# Apply zero-padding
124-
zero_padded_length = 512
125-
filtered_eeg_windowed_padded = np.pad(filtered_eeg_windowed, (0, zero_padded_length - len(filtered_eeg_windowed)), 'constant')
126-
127-
eeg_fft = np.abs(fft(filtered_eeg_windowed_padded))[:len(filtered_eeg_windowed_padded) // 2]
128-
freqs = np.fft.fftfreq(len(filtered_eeg_windowed_padded), 1 / self.sampling_rate)[:len(filtered_eeg_windowed_padded) // 2]
129-
130-
# Update FFT plot
131-
self.fft_curve.setData(freqs, eeg_fft)
132-
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)
149-
self.brainwave_bars.setOpts(height=brainwave_power)
103+
raw_point = sample[0]
104+
105+
notch_filtered, self.zi_notch = lfilter(self.b_notch, self.a_notch, [raw_point], zi=self.zi_notch)
106+
band_filtered, self.zi_band = lfilter(self.b_band, self.a_band, notch_filtered, zi=self.zi_band)
107+
band_filtered = band_filtered[-1] # Get the current filtered point
108+
109+
# Update the EEG plot
110+
self.eeg_data[self.current_index] = band_filtered
111+
self.current_index = (self.current_index + 1) % self.buffer_size
112+
113+
if self.current_index == 0:
114+
plot_data = self.eeg_data
115+
else:
116+
plot_data = np.concatenate((self.eeg_data[self.current_index:], self.eeg_data[:self.current_index]))
117+
118+
recent_data = plot_data[-self.moving_window_size:]
119+
recent_time = np.linspace(0, len(recent_data) / self.sampling_rate, len(recent_data))
120+
self.eeg_curve.setData(recent_time, recent_data)
121+
122+
self.one_second_buffer.append(band_filtered) # Add the filtered point to the 1-second buffer
123+
if len(self.one_second_buffer) == self.sampling_rate: # Process FFT and brainwave power
124+
self.process_fft_and_brainpower()
125+
self.one_second_buffer.clear()
126+
127+
def process_fft_and_brainpower(self):
128+
window = np.hanning(len(self.one_second_buffer)) # Apply Hanning window to the buffer
129+
buffer_windowed = np.array(self.one_second_buffer) * window
130+
131+
# Perform FFT
132+
fft_result = np.abs(fft(buffer_windowed))[:len(buffer_windowed) // 2]
133+
fft_result /= len(buffer_windowed)
134+
freqs = np.fft.fftfreq(len(buffer_windowed), 1 / self.sampling_rate)[:len(buffer_windowed) // 2]
135+
self.fft_curve.setData(freqs, fft_result)
136+
137+
brainwave_power = self.calculate_brainwave_power(fft_result, freqs)
138+
self.brainwave_bars.setOpts(height=brainwave_power)
150139

151140
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)])
157-
141+
delta_power = math.sqrt(np.sum(((fft_data[(freqs >= 0.5) & (freqs <= 4)])**2)/4))
142+
theta_power = math.sqrt(np.sum(((fft_data[(freqs >= 4) & (freqs <= 8)])**2)/5))
143+
alpha_power = math.sqrt(np.sum(((fft_data[(freqs >= 8) & (freqs <= 13)])**2)/6))
144+
beta_power = math.sqrt(np.sum(((fft_data[(freqs >= 13) & (freqs <=30)])**2)/18))
145+
gamma_power = math.sqrt(np.sum(((fft_data[(freqs >= 30) & (freqs <= 45)])**2)/16))
146+
print("Delta Power", delta_power)
147+
print("Theta Power", theta_power)
148+
print("Alpha Power", alpha_power)
149+
print("Beta Power", beta_power)
150+
print("Gamma Power", gamma_power)
158151
return [delta_power, theta_power, alpha_power, beta_power, gamma_power]
159-
152+
160153
if __name__ == "__main__":
161154
app = QApplication(sys.argv)
162155
window = EEGMonitor()

test/ball.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pygame
2+
import pylsl
3+
import numpy as np
4+
import time
5+
from pylsl import StreamInlet, resolve_stream
6+
7+
pygame.init()
8+
9+
# Screen setup
10+
screen = pygame.display.set_mode((800, 600))
11+
pygame.display.set_caption('Focus Game')
12+
13+
# Ball properties
14+
ball_x, ball_y = 400, 550
15+
ball_radius = 20
16+
focus_speed_upward = 8 # Speed when moving upward
17+
focus_speed_downward = 4 # Speed when moving downward
18+
focus_timeout = 2 # Time in seconds to stabilize focus
19+
focus_threshold = 1000000000 # Threshold for beta power
20+
21+
# LSL stream setup
22+
streams = resolve_stream('name', 'BioAmpDataStream')
23+
if not streams:
24+
print("No LSL stream found!")
25+
pygame.quit()
26+
exit()
27+
28+
inlet = StreamInlet(streams[0])
29+
print(" LSL Stream Started")
30+
31+
# Buffer for EEG data and Focus tracking variables
32+
buffer = []
33+
buffer_size = 500
34+
focus_timer = 0
35+
last_focus_time = time.time()
36+
last_time = time.time()
37+
38+
def calculate_focus_level(eeg_data):
39+
fft_result = np.fft.fft(eeg_data)
40+
freqs = np.fft.fftfreq(len(eeg_data), 1 / 500)
41+
positive_freqs = freqs[:len(freqs) // 2]
42+
fft_magnitude = np.abs(fft_result[:len(freqs) // 2])
43+
44+
# Extract beta band power (13-30 Hz)
45+
beta_band = np.where((positive_freqs >= 13) & (positive_freqs <= 30))
46+
beta_power = np.sum(fft_magnitude[beta_band] ** 2)
47+
print(f"Beta Power: {beta_power}")
48+
49+
return beta_power
50+
51+
def update_ball_position(focus_level, is_focus_stable):
52+
global ball_y
53+
if is_focus_stable:
54+
ball_y = max(0, ball_y - focus_speed_upward) # Move upward, bounded by top
55+
else:
56+
ball_y = min(600 - ball_radius, ball_y + focus_speed_downward) # Move downward, bounded by bottom
57+
58+
# Game loop
59+
running = True
60+
while running:
61+
try:
62+
for event in pygame.event.get():
63+
if event.type == pygame.QUIT:
64+
running = False
65+
66+
# Read EEG data from the LSL stream
67+
sample, _ = inlet.pull_sample(timeout=0.1)
68+
if sample:
69+
buffer.append(sample[:1]) # Append new data to buffer
70+
71+
current_time = time.time()
72+
if current_time - last_time >= 1:
73+
last_time = current_time
74+
release_count = int(len(buffer) * 0.2) # Remove oldest 20%
75+
buffer = buffer[release_count:] # Trim buffer
76+
77+
if len(buffer) >= buffer_size: # Process EEG data when the buffer is full
78+
eeg_data = np.array(buffer)[:, 0] # Use only the first channel
79+
buffer = [] # Clear the buffer
80+
81+
focus_level = calculate_focus_level(eeg_data)
82+
83+
if focus_level > focus_threshold:
84+
focus_timer = min(focus_timeout, focus_timer + (current_time - last_focus_time))
85+
else:
86+
focus_timer = max(0, focus_timer - (current_time - last_focus_time))
87+
88+
is_focus_stable = focus_timer >= focus_timeout
89+
update_ball_position(focus_level, is_focus_stable)
90+
last_focus_time = current_time
91+
92+
screen.fill((0, 0, 0))
93+
pygame.draw.circle(screen, (255, 0, 0), (ball_x, ball_y), ball_radius) # Draw the ball
94+
pygame.display.update()
95+
96+
except KeyboardInterrupt:
97+
print("Exiting gracefully...")
98+
running = False
99+
100+
pygame.quit()

0 commit comments

Comments
 (0)