Skip to content

Commit 853cf79

Browse files
authored
Merge pull request #23 from PayalLakra/bio_amptool
Updated EEG game, app_requirement.txt, and ffteeg.py + Adding new chords data parser
2 parents cac5a70 + f1d935f commit 853cf79

File tree

4 files changed

+449
-63
lines changed

4 files changed

+449
-63
lines changed

app_requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pyqtgraph==0.13.7
22
PyQt5==5.15.11
3-
PySide2==5.15.2.1
43
keyboard==0.13.5
54
scipy==1.14.1
65
pygame==2.6.1
7-
neurokit2==0.2.10
6+
neurokit2==0.2.10
7+
plotly==5.24.1
8+
pandas==2.2.3
9+
tk==0.1.0

applications/ffteeg.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ def __init__(self):
7777
self.time_data = np.linspace(0, 10, self.buffer_size) # Fixed time array for plotting
7878
self.current_index = 0 # Index for overwriting data
7979

80-
# Notch filter at 50 Hz
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+
# Filters
8185
self.b_notch, self.a_notch = iirnotch(50, 30, self.sampling_rate)
82-
# High-pass filter (cutoff at 0.5 Hz)
83-
self.b_highpass, self.a_highpass = butter(4, 0.5 / (0.5 * self.sampling_rate), btype='high')
84-
# Low-pass filter (4th order, cutoff at 45 Hz)
86+
self.b_highpass, self.a_highpass = butter(4, 1.5 / (0.5 * self.sampling_rate), btype='high')
8587
self.b_lowpass, self.a_lowpass = butter(4, 45 / (0.5 * self.sampling_rate), btype='low')
8688

8789
# Timer for updating the plot
@@ -114,22 +116,36 @@ def update_plot(self):
114116
self.eeg_curve.setData(self.time_data, filtered_eeg)
115117

116118
# Perform FFT on the latest 1-second slice
117-
latest_data = plot_data[-self.sampling_rate:] # Most recent 1-second data
119+
latest_data = filtered_eeg[-self.sampling_rate:]
118120
window = hamming(len(latest_data))
119121
filtered_eeg_windowed = latest_data * window
120122

121123
# Apply zero-padding
122-
zero_padded_length = 2048
124+
zero_padded_length = 512
123125
filtered_eeg_windowed_padded = np.pad(filtered_eeg_windowed, (0, zero_padded_length - len(filtered_eeg_windowed)), 'constant')
124126

125-
eeg_fft = np.abs(fft(filtered_eeg_windowed))[:len(filtered_eeg_windowed) // 2] # Positive frequencies only
126-
freqs = np.fft.fftfreq(len(filtered_eeg_windowed), 1 / self.sampling_rate)[:len(filtered_eeg_windowed) // 2]
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]
127129

128130
# Update FFT plot
129131
self.fft_curve.setData(freqs, eeg_fft)
130132

131-
# Calculate brainwave power
132-
brainwave_power = self.calculate_brainwave_power(eeg_fft, freqs)
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)
133149
self.brainwave_bars.setOpts(height=brainwave_power)
134150

135151
def calculate_brainwave_power(self, fft_data, freqs):

applications/game.py

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,22 @@
3939
ball_radius = 20
4040
ball_color = WHITE
4141
ball_pos = [WIDTH // 2, HEIGHT // 2]
42-
ball_speed = 40
42+
ball_speed = 30
4343

4444
# Player properties
4545
player_width = 10
4646
player_height = 100
4747
player1_pos = [50, HEIGHT // 2 - player_height // 2]
4848
player2_pos = [WIDTH - 50 - player_width, HEIGHT // 2 - player_height // 2]
4949

50+
# Font settings
51+
font_size = 50
52+
font = pygame.font.Font(None, font_size)
53+
title_text = "Force Ball Game"
54+
55+
title_surface = font.render(title_text, True, WHITE)
56+
title_rect = title_surface.get_rect(center=(800 // 2, font_size)) # Center at the top middle
57+
5058
clock = pygame.time.Clock()
5159

5260
eeg_queue = queue.Queue() # Initialize EEG queue
@@ -56,6 +64,9 @@
5664
game_started = False
5765
first_attempt = True # Keeps track if it's the first game or a restart
5866

67+
powerData1 = []
68+
powerData2 = []
69+
5970
def bandpower(data, sf, band, window_sec=None, relative=False):
6071
band = np.asarray(band)
6172
low, high = band
@@ -75,22 +86,17 @@ def bandpower(data, sf, band, window_sec=None, relative=False):
7586
return bp
7687

7788
def eeg_data_thread(eeg_queue):
89+
global powerData1, powerData2
7890
streams = resolve_stream('name', 'BioAmpDataStream')
7991
if not streams:
8092
print("No LSL stream found!")
8193
return
8294

8395
inlet = StreamInlet(streams[0])
84-
channel_assignments = {0: 'Player A', 1: 'Player B'}
85-
sampling_frequency = 250
86-
bands = {
87-
'Delta': [0.5, 4],
88-
'Theta': [4, 8],
89-
'Alpha': [8, 13],
90-
'Beta': [13, 30],
91-
'Gamma': [30, 40]
92-
}
93-
buffer_length = sampling_frequency * 4
96+
channel_assignments = {0:'Player A', 1:'Player B'}
97+
sampling_frequency = 500
98+
bands = {'Alpha': [8, 13],'Beta': [13, 30]}
99+
buffer_length = sampling_frequency * 1
94100
data_buffer = {'Channel1': [], 'Channel2': []}
95101
powerData1 = []
96102
powerData2 = []
@@ -100,6 +106,9 @@ def eeg_data_thread(eeg_queue):
100106
baseline1 = baseline2 = 1 # Initialize baselines
101107

102108
while running:
109+
if paused:
110+
time.sleep(0.1) # Pause the thread when the game is paused
111+
continue
103112
try:
104113
sample, timestamp = inlet.pull_sample()
105114
if len(sample) >= 6:
@@ -118,12 +127,8 @@ def eeg_data_thread(eeg_queue):
118127
if len(data_buffer['Channel1']) >= buffer_length:
119128
power_data = {'Channel1': {}, 'Channel2': {}}
120129
for band_name, band_freqs in bands.items():
121-
power_data['Channel1'][band_name] = bandpower(
122-
np.array(data_buffer['Channel1']), sampling_frequency, band_freqs
123-
)
124-
power_data['Channel2'][band_name] = bandpower(
125-
np.array(data_buffer['Channel2']), sampling_frequency, band_freqs
126-
)
130+
power_data['Channel1'][band_name] = bandpower(np.array(data_buffer['Channel1']), sampling_frequency, band_freqs)
131+
power_data['Channel2'][band_name] = bandpower(np.array(data_buffer['Channel2']), sampling_frequency, band_freqs)
127132

128133
powerData1.append(power_data['Channel1']['Beta'] / power_data['Channel1']['Alpha'])
129134
powerData2.append(power_data['Channel2']['Beta'] / power_data['Channel2']['Alpha'])
@@ -156,40 +161,44 @@ def eeg_data_thread(eeg_queue):
156161
eeg_thread.start()
157162

158163
def reset_game():
159-
global ball_pos, force_player1, force_player2, paused, game_started
164+
global ball_pos, force_player1, force_player2, paused, game_started, win_text, win_handled, restart_clicked, powerData1, powerData2
160165
ball_pos = [WIDTH // 2, HEIGHT // 2]
161166
force_player1 = force_player2 = 0
167+
powerData1.clear() # Clear force data for player 1
168+
powerData2.clear() # Clear force data for player 2
162169
paused = False # Ensure the game is not paused after reset
163170
game_started = True # Ensure the game is marked as started
171+
win_text = None # Reset win text
172+
win_handled = False # Reset win handling
173+
restart_clicked = True # Mark the restart button as clicked
164174

165175
# Clear any buffered EEG data
166176
while not eeg_queue.empty():
167177
eeg_queue.get()
168-
print("Empty")
178+
print("Game Reset Successfully.")
179+
180+
def update_ball_position(force_player1, force_player2, threshold=0.7):
181+
global ball_pos, powerData1, powerData2
169182

170-
def update_ball_position(force_player1, force_player2):
171-
global ball_pos
172-
net_force = force_player2 - force_player1 # force direction
173-
ball_pos[0] += net_force * ball_speed * 0.01
183+
# Apply moving average
184+
average_force1 = np.mean(powerData1[-10:]) if len(powerData1) >= 10 else 0
185+
average_force2 = np.mean(powerData2[-10:]) if len(powerData2) >= 10 else 0
186+
187+
net_force = average_force1 - average_force2
188+
189+
# Apply the threshold
190+
if abs(net_force) > threshold:
191+
ball_pos[0] += net_force * ball_speed * 0.01
174192
if ball_pos[0] < ball_radius:
175193
ball_pos[0] = ball_radius
176194
elif ball_pos[0] > WIDTH - ball_radius:
177195
ball_pos[0] = WIDTH - ball_radius
178196

179-
print(f"Force Player 1: {force_player1:.2f}, Force Player 2: {force_player2:.2f}, Net Force: {net_force:.2f}") # Print the forces to the console
180-
181-
def handle_input():
182-
global force_player1, force_player2
183-
keys = pygame.key.get_pressed()
184-
185-
if keys[pygame.K_LEFT]:
186-
force_player1 += 0.25
187-
if keys[pygame.K_RIGHT]:
188-
force_player2 += 0.25
197+
print(f"Force Player 1: {average_force1:.2f}, Force Player 2: {average_force2:.2f}, Net Force: {net_force:.2f}")
189198

190199
def draw_buttons(paused, first_attempt): # Button dimensions and positions
191-
button_width = 120
192-
button_height = 40
200+
button_width = 140
201+
button_height = 50
193202
button_radius = 15 # Radius for rounded corners
194203

195204
# Button positions (y-position is moved up slightly for a better fit)
@@ -235,52 +244,53 @@ def draw_players():
235244

236245
def check_win_condition():
237246
if ball_pos[0] <= ball_radius:
238-
return "PLAYER A WINS!"
239-
elif ball_pos[0] >= WIDTH - ball_radius:
240247
return "PLAYER B WINS!"
248+
elif ball_pos[0] >= WIDTH - ball_radius:
249+
return "PLAYER A WINS!"
241250
return None
242251

243252
def main():
244253
global paused, game_started, first_attempt
245254
force_player1 = force_player2 = 0
246255
win_text = None # Initialize win text
247256
latest_data = (0, 0) # To store the latest EEG data for both players
257+
win_handled = False # Track if win actions are handled
248258

249259
while True:
250260
screen.fill(BLACK)
261+
screen.blit(title_surface, title_rect) # For Title of the game
251262

252-
pygame.draw.circle(screen, ball_color, (int(ball_pos[0]), int(ball_pos[1])), ball_radius) # Draw the ball
263+
pygame.draw.circle(screen, ball_color, (int(ball_pos[0]), int(ball_pos[1])), ball_radius) # Draw the ball
253264

254265
for event in pygame.event.get():
255266
if event.type == pygame.QUIT:
256267
pygame.quit()
257268
sys.exit()
258269

259-
if event.type == pygame.KEYDOWN:
260-
if event.key == pygame.K_ESCAPE:
261-
pygame.quit()
262-
sys.exit()
263-
264270
if event.type == pygame.MOUSEBUTTONDOWN:
265271
mouse_pos = pygame.mouse.get_pos()
266272
if event.button == 1: # Left mouse button
267273
# Check if the mouse click is within the bounds of any button
268274
if pygame.Rect(WIDTH // 4 - 60, HEIGHT - 80, 120, 40).collidepoint(mouse_pos):
269275
# Start or restart the game
270276
reset_game()
277+
time.sleep(1)
271278
game_started = True
272279
first_attempt = False
273-
win_text = None # Reset win text on new game
280+
win_text = None
281+
win_handled = False # Reset win handling
282+
paused = False # Ensure the game is unpaused
283+
print("Game Restarted.")
274284
elif pygame.Rect(WIDTH // 2 - 60, HEIGHT - 80, 120, 40).collidepoint(mouse_pos):
275285
# Pause/Resume the game
276286
paused = not paused
287+
print("Game Paused!" if paused else "Game Resumed!")
277288
elif pygame.Rect(3 * WIDTH // 4 - 60, HEIGHT - 80, 120, 40).collidepoint(mouse_pos):
278289
pygame.quit()
279290
sys.exit()
280291

281292
if game_started:
282293
if not paused:
283-
handle_input()
284294
if not eeg_queue.empty():
285295
force_player1, force_player2 = eeg_queue.get()
286296
latest_data = (force_player1, force_player2) # Store latest data
@@ -295,11 +305,10 @@ def main():
295305
if game_started:
296306
win_text = check_win_condition()
297307
if win_text:
298-
win_sound.play() # Play sound on win
299-
paused = True # Automatically pause the game on win
300-
while not eeg_queue.empty():
301-
eeg_queue.get()
302-
force_player1, force_player2 = latest_data # Store the latest data when the game is won
308+
if not win_handled: # Ensure win actions execute only once
309+
win_sound.play() # Play sound on win
310+
paused = True # Automatically pause the game on win
311+
win_handled = True # Mark win actions as handled
303312

304313
# Draw win text if there is a winner
305314
if win_text:
@@ -310,4 +319,10 @@ def main():
310319
clock.tick(60) # 60 frames per second
311320

312321
if __name__ == "__main__":
313-
main()
322+
main()
323+
324+
# Threshold = 0.7
325+
# Moving Average of last 10 values of powerData.
326+
# Ball Speed = 30
327+
# Adding the font title "Force Ball Game"
328+
# Sleep of 1 sec so that game reset properly

0 commit comments

Comments
 (0)