Skip to content

Commit 8d1089d

Browse files
committed
Merge branch 'bio_amptool' of https://github.com/PayalLakra/Chords-Python
2 parents 7ed3623 + c46fc0a commit 8d1089d

24 files changed

+427
-125
lines changed

app.py

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import os
55
import signal
66
import sys
7+
import atexit
8+
import threading
79

810
app = Flask(__name__)
911
lsl_process = None
12+
lsl_running = False
13+
npg_running = False
14+
npg_process = None
1015
app_processes = {}
1116

1217
def is_process_running(name):
@@ -21,36 +26,70 @@ def home():
2126

2227
@app.route("/start_lsl", methods=["POST"])
2328
def start_lsl():
24-
global lsl_process
25-
if lsl_process and lsl_process.poll() is None:
29+
global lsl_process, lsl_running
30+
31+
if lsl_running:
2632
return jsonify({"status": "LSL stream already running", "lsl_started": True})
33+
2734
try:
28-
# Start the LSL stream as a subprocess
2935
if sys.platform == "win32":
30-
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
36+
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW, text=True)
3137
else:
32-
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
33-
output = lsl_process.stderr.readline().decode().strip() # Read the initial stderr line
38+
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
3439

40+
output = lsl_process.stderr.readline().strip()
3541
print(output)
36-
if output == "No":
37-
return render_template("index.html", lsl_started=False, lsl_status="Failed to Start", lsl_color="red")
42+
43+
if "No" in output:
44+
lsl_running = False
45+
return jsonify({"lsl_started": False, "lsl_status": "Failed to Start", "lsl_color": "red"})
3846
else:
39-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green")
40-
except subprocess.TimeoutExpired:
41-
return render_template(
42-
"index.html", lsl_started=False, lsl_status="Timeout Error", lsl_color="red"
43-
)
47+
lsl_running = True
48+
return jsonify({"lsl_started": True, "lsl_status": "Running", "lsl_color": "green"})
49+
4450
except Exception as e:
45-
return render_template("index.html", lsl_started=False, lsl_status=f"Error: {e}", lsl_color="red")
51+
return jsonify({"lsl_started": False, "lsl_status": f"Error: {e}", "lsl_color": "red"})
52+
53+
def read_npg_output():
54+
global npg_process
55+
56+
if npg_process:
57+
for line in iter(npg_process.stdout.readline, ''):
58+
print(line.strip()) # Print npg.py output to the terminal
59+
60+
@app.route("/start_npg", methods=["POST"])
61+
def start_npg():
62+
global npg_process, npg_running
63+
64+
if npg_running:
65+
return jsonify({"status": "NPG already running", "npg_started": True})
66+
67+
try:
68+
if sys.platform == "win32":
69+
npg_process = subprocess.Popen(["python", "npg.py"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NO_WINDOW, text=True, bufsize=1)
70+
else:
71+
npg_process = subprocess.Popen(["python3", "npg.py"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
72+
73+
# Start a separate thread to read npg.py output
74+
threading.Thread(target=read_npg_output, daemon=True).start()
75+
76+
npg_running = True
77+
return render_template("index.html", npg_started=True, npg_status="Running", npg_color="green", apps_enabled=True)
78+
79+
except Exception as e:
80+
npg_running = False
81+
return render_template("index.html", npg_started=False, npg_status=f"Error: {e}", npg_color="red", apps_enabled=False)
4682

4783
@app.route("/run_app", methods=["POST"])
4884
def run_app():
85+
global lsl_running, npg_running
4986
app_name = request.form.get("app_name")
5087

51-
# Check if the app is already running
88+
if not (lsl_running or npg_running):
89+
return render_template("index.html", message="Start LSL or NPG first!", running_apps=app_processes.keys())
90+
5291
if app_name in app_processes and app_processes[app_name].poll() is None:
53-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"{app_name} is already Running", running_apps=app_processes.keys())
92+
return render_template("index.html", message=f"{app_name} is already running", running_apps=app_processes.keys())
5493

5594
try:
5695
# Start the app subprocess
@@ -60,19 +99,19 @@ def run_app():
6099
process = subprocess.Popen(["python", f"{app_name}.py"])
61100

62101
app_processes[app_name] = process
63-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", running_apps=app_processes.keys(), message=None)
64-
102+
return render_template("index.html", running_apps=app_processes.keys(), message=None)
65103
except Exception as e:
66-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"Error starting {app_name}: {e}", running_apps=app_processes.keys())
104+
return render_template("index.html", message=f"Error starting {app_name}: {e}", running_apps=app_processes.keys())
67105

68106
@app.route("/app_status", methods=["GET"])
69107
def app_status():
70108
# Check the status of all apps
71109
try:
72110
statuses = {
73-
app_name: (process.poll() is None) # True if running, False if not
74-
for app_name, process in app_processes.items()
111+
"lsl_started": lsl_running,
112+
"npg_started": npg_running
75113
}
114+
statuses.update({app_name: (process.poll() is None) for app_name, process in app_processes.items()})
76115
return jsonify(statuses)
77116
except Exception as e:
78117
return jsonify({"error": str(e)}), 500
@@ -83,7 +122,7 @@ def stop_lsl():
83122
return jsonify({'status': 'LSL Stream and applications stopped and server is shutting down.'})
84123

85124
def stop_all_processes():
86-
global lsl_process, app_processes
125+
global lsl_process, npg_process, app_processes, lsl_running, npg_running
87126

88127
# Terminate LSL process
89128
if lsl_process and lsl_process.poll() is None:
@@ -92,25 +131,35 @@ def stop_all_processes():
92131
lsl_process.wait(timeout=3)
93132
except subprocess.TimeoutExpired:
94133
lsl_process.kill()
134+
lsl_running = False
135+
136+
if npg_process and npg_process.poll() is None:
137+
npg_process.terminate()
138+
try:
139+
npg_process.wait(timeout=3)
140+
except subprocess.TimeoutExpired:
141+
npg_process.kill()
142+
npg_running = False
95143

96-
# Terminate all app processes
97-
for app_name, process in app_processes.items():
144+
for app_name, process in list(app_processes.items()):
98145
if process.poll() is None:
99146
process.terminate()
100147
try:
101148
process.wait(timeout=3)
102149
except subprocess.TimeoutExpired:
103150
process.kill()
151+
del app_processes[app_name]
104152

105-
app_processes.clear()
106153
print("All processes terminated.")
107154

108155
def handle_sigint(signal_num, frame):
109156
print("\nCtrl+C pressed! Stopping all processes...")
110157
stop_all_processes()
111158
sys.exit(0)
112159

113-
signal.signal(signal.SIGINT, handle_sigint) # Register signal handler for Ctrl+C
160+
# Register signal handler for Ctrl+C
161+
signal.signal(signal.SIGINT, handle_sigint)
162+
atexit.register(stop_all_processes)
114163

115164
if __name__ == "__main__":
116165
app.run(debug=True)

beetle.py

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@
22
import pylsl
33
import numpy as np
44
import time
5-
from pylsl import StreamInlet, resolve_stream
5+
from pylsl import StreamInlet, resolve_streams, resolve_byprop
66
from scipy.signal import iirnotch, butter, lfilter
77
import math
88
from PIL import Image
99

1010
# Initialize LSL stream
11-
streams = resolve_stream('name', 'BioAmpDataStream')
12-
if not streams:
13-
print("No LSL stream found!")
14-
exit()
11+
print("Searching for available LSL streams...")
12+
streams = resolve_streams()
13+
available_streams = [s.name() for s in streams]
14+
15+
if not available_streams:
16+
print("No LSL streams found!")
17+
18+
for stream_name in available_streams:
19+
print(f"Trying to connect to {stream_name}...")
20+
resolved_streams = resolve_byprop('name', stream_name, timeout=2)
1521

16-
inlet = StreamInlet(streams[0])
17-
print("LSL Stream Started")
22+
if resolved_streams:
23+
print(f"Successfully connected to {stream_name}!")
24+
inlet = StreamInlet(resolved_streams[0])
25+
break
26+
else:
27+
print(f"Failed to connect to {stream_name}.")
28+
29+
if inlet is None:
30+
print("Could not connect to any stream.")
1831
sampling_rate = int(inlet.info().nominal_srate())
1932
print(f"Sampling rate: {sampling_rate} Hz")
2033

@@ -27,7 +40,7 @@
2740

2841
# Beetle properties
2942
beetle_x, beetle_y = 380, 530
30-
focus_speed_upward = 15
43+
focus_speed_upward = 10
3144
focus_speed_downward = 5
3245
focus_timeout = 2
3346
focus_threshold = None
@@ -37,25 +50,19 @@
3750
screen = pygame.display.set_mode((800, 600))
3851
pygame.display.set_caption('Beetle Game')
3952

40-
# Load GIF and extract frames
41-
gif_path = 'media/RobotBug.gif'
42-
gif = Image.open(gif_path)
43-
44-
frames = []
45-
for frame in range(gif.n_frames):
46-
gif.seek(frame)
47-
frame_surface = pygame.image.fromstring(gif.tobytes(), gif.size, gif.mode)
48-
frame_surface = pygame.transform.scale(frame_surface, (120, 120))
49-
frames.append(frame_surface)
53+
sprite_count = 10
54+
beetle_sprites = [pygame.image.load(f'media/beetle{i}.JPG') for i in range(1, sprite_count + 1)]
55+
beetle_sprites = [pygame.transform.scale(sprite, (140, 160)) for sprite in beetle_sprites]
5056

51-
frame_index = 0
52-
frame_delay = 100 # Time per frame in milliseconds
53-
last_frame_time = pygame.time.get_ticks()
57+
# Animation Variables
58+
current_sprite = 0
59+
animation_speed = 100
60+
sprite_timer = 0
5461

5562
# Function to display a message on the screen
5663
def show_message(message, duration=3):
5764
start_time = time.time()
58-
font = pygame.font.Font(None, 50)
65+
font = pygame.font.SysFont("Arial", 50)
5966
text = font.render(message, True, (0, 0, 0))
6067
text_rect = text.get_rect(center=(400, 300))
6168

@@ -84,7 +91,6 @@ def calculate_focus_level(eeg_data, sampling_rate=500):
8491
gamma_power = math.sqrt(np.sum((fft_data[(freqs >= 30) & (freqs <= 45)]) ** 2))
8592

8693
power = (beta_power + gamma_power) / (delta_power + theta_power + alpha_power + beta_power + gamma_power)
87-
print(power)
8894
return power
8995

9096
# Calibration Phase
@@ -109,21 +115,23 @@ def calculate_focus_level(eeg_data, sampling_rate=500):
109115
std_focus = np.std(baseline_focus_levels)
110116

111117
focus_threshold = mean_focus + 1.5 * std_focus
112-
print(f"Calibration Complete. Focus Threshold set at: {focus_threshold:.2f}")
118+
print(f"Calibration Complete. Focus Threshold set at: {focus_threshold:.3f}")
113119
else:
114120
print("Calibration failed due to insufficient data.")
115121
exit()
116122

117123
# Show Game Start Message
118124
show_message("Game Starting...", 1)
125+
game_start_time = time.time()
126+
game_duration = 45 # Game lasts 45 seconds
119127

120128
# Update beetle position
121129
def update_beetle_position(focus_level, is_focus_stable):
122130
global beetle_y
123131
if is_focus_stable:
124-
beetle_y = max(10 + frames[0].get_height() // 2, beetle_y - focus_speed_upward)
132+
beetle_y = max(10 + beetle_sprites[0].get_height() // 2, beetle_y - focus_speed_upward)
125133
else:
126-
beetle_y = min(580 - frames[0].get_height() // 2, beetle_y + focus_speed_downward)
134+
beetle_y = min(580 - beetle_sprites[0].get_height() // 2, beetle_y + focus_speed_downward)
127135

128136
print("STARTING GAME...")
129137
running = True
@@ -143,6 +151,8 @@ def update_beetle_position(focus_level, is_focus_stable):
143151
buffer.append(filtered_sample)
144152

145153
current_time = time.time()
154+
elapsed_time = int(current_time - game_start_time)
155+
146156
if current_time - last_time >= 1:
147157
last_time = current_time
148158
buffer = buffer[int(len(buffer) * 0.2):]
@@ -152,6 +162,7 @@ def update_beetle_position(focus_level, is_focus_stable):
152162
buffer = []
153163

154164
focus_level = calculate_focus_level(eeg_data)
165+
print(focus_level)
155166

156167
if focus_level > focus_threshold:
157168
focus_timer = min(focus_timeout, focus_timer + (current_time - last_focus_time))
@@ -164,17 +175,32 @@ def update_beetle_position(focus_level, is_focus_stable):
164175

165176
last_focus_time = current_time
166177

167-
# Update GIF animation
168-
current_tick = pygame.time.get_ticks()
169-
if current_tick - last_frame_time >= frame_delay:
170-
frame_index = (frame_index + 1) % len(frames)
171-
last_frame_time = current_tick
178+
# Update sprite animation
179+
sprite_timer += (current_time - last_focus_time)
180+
if sprite_timer >= animation_speed:
181+
sprite_timer = 0
182+
current_sprite = (current_sprite + 1) % len(beetle_sprites)
183+
184+
# Check win condition
185+
if beetle_y <= 80:
186+
show_message("You Win!", 1)
187+
running = False
188+
189+
# Check game over condition
190+
if elapsed_time >= game_duration:
191+
show_message("Game Over! Try Again.", 1)
192+
running = False
172193

194+
# Draw everything
173195
screen.fill("#FFFFFF")
174196
pygame.draw.rect(screen, (0, 0, 0), (10, 10, 780, 580), 5)
175-
beetle_image = frames[frame_index] # Get current frame
176-
screen.blit(beetle_image, (beetle_x - beetle_image.get_width() // 2, beetle_y - beetle_image.get_height() // 2))
177-
197+
screen.blit(beetle_sprites[current_sprite], (beetle_x - 40, beetle_y - 40))
198+
199+
# Display Timer
200+
font = pygame.font.SysFont("Arial", 30)
201+
timer_text = font.render(f"Time: {game_duration - elapsed_time}s", True, (0, 0, 0))
202+
screen.blit(timer_text, (650, 20))
203+
178204
pygame.display.update()
179205

180206
except KeyboardInterrupt:

chords.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"MEGA-2560-R3": {"sampling_rate": 250, "Num_channels": 16},
6666
"MEGA-2560-CLONE": {"sampling_rate": 250, "Num_channels": 16},
6767
"GIGA-R1": {"sampling_rate": 500, "Num_channels": 6},
68+
"NPG-LITE": {"sampling_rate": 500, "Num_channels": 3},
6869
}
6970

7071
# Initialize gloabal variables for Incoming Data

emgenvelope.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,25 @@ def __init__(self):
3939
self.setCentralWidget(central_widget)
4040

4141
# Set up LSL stream inlet
42-
streams = pylsl.resolve_stream('name', 'BioAmpDataStream')
43-
if not streams:
44-
print("No LSL stream found!")
42+
print("Searching for available LSL streams...")
43+
available_streams = pylsl.resolve_streams()
44+
45+
if not available_streams:
46+
print("No LSL streams found! Exiting...")
47+
sys.exit(0)
48+
49+
self.inlet = None
50+
for stream in available_streams:
51+
try:
52+
self.inlet = pylsl.StreamInlet(stream)
53+
print(f"Connected to LSL stream: {stream.name()}")
54+
break
55+
except Exception as e:
56+
print(f"Failed to connect to {stream.name()}: {e}")
57+
58+
if self.inlet is None:
59+
print("Unable to connect to any LSL stream! Exiting...")
4560
sys.exit(0)
46-
self.inlet = pylsl.StreamInlet(streams[0])
4761

4862
# Sampling rate
4963
self.sampling_rate = int(self.inlet.info().nominal_srate())

0 commit comments

Comments
 (0)