Skip to content

Commit 4d88a99

Browse files
authored
Merge pull request #35 from PayalLakra/bio_amptool
Adding NPG, Beetle Game, and Updated Readme.
2 parents bcf2599 + 26182bd commit 4d88a99

26 files changed

+569
-166
lines changed

README.md

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,24 @@ Chords Python script is designed to interface with an Arduino-based bio-potentia
3737
```bash
3838
.\venv\Scripts\activate
3939
```
40-
3. Install the required Python libraries:
40+
41+
> [!IMPORTANT]
42+
> You may get an execution policy error if scripts are restricted. To fix it, run:
43+
44+
> ```bash
45+
> Set-ExecutionPolicy Unrestricted -Scope Process
46+
> ```
47+
48+
3. Install the required Python libraries needed to run the python script:
4149
```bash
4250
pip install -r chords_requirements.txt
4351
```
4452
53+
4. Install the required Python libraries needed to run the applications:
54+
```bash
55+
pip install -r app_requirements.txt
56+
```
57+
4558
## Usage
4659
4760
To use the script, run it from the command line with various options:
@@ -57,6 +70,12 @@ To use the script, run it from the command line with various options:
5770
- `-v`, `--verbose`: Enable verbose output with detailed statistics and error reporting.
5871
- `-t` : Enable the timer to run program for a set time in seconds.
5972
73+
### Example:
74+
```bash
75+
python chords.py --lsl -v --csv -t 60
76+
```
77+
- This command executes the Python script `chords.py`, initiates the LSL stream, enables verbose output, activates CSV logging, and sets a timer for 60 seconds:
78+
6079
### Data Logging
6180
6281
- **CSV Output**: The script saves the processed data in a CSV file with a timestamped name.
@@ -78,37 +97,45 @@ pip install -r app_requirements.txt
7897
7998
### Available Applications
8099
81-
#### GUI
82-
83-
- `python gui.py`: Enable the real-time data plotting GUI.
84-
85-
#### TUG OF WAR GAME
86-
87-
- `python game.py`: Enable a GUI to play tug of war game using EEG Signal.
88-
89-
#### HEART RATE
100+
#### ECG with Heart Rate
90101
91102
- `python heartbeat_ecg.py`:Enable a GUI with real-time ECG and heart rate.
92103
93-
#### EMG ENVELOPE
104+
#### EMG with Envelope
94105
95106
- `python emgenvelope.py`: Enable a GUI with real-time EMG & its Envelope.
96107
97-
#### EOG
108+
#### EOG with Blinks
98109
99110
- `python eog.py`: Enable a GUI with real-time EOG that detects blinks and mark them with red dots.
100111
101-
#### EEG
112+
#### EEG with FFT
102113
103114
- `python ffteeg.py`: Enable a GUI with real-time EEG data with its FFT and band powers.
104115
105-
#### Keystroke
116+
#### EEG Tug of War Game
117+
118+
- `python game.py`: Enable a GUI to play tug of war game using EEG Signal.
119+
120+
#### EEG Beetle Game
121+
122+
- `python beetle.py`: Enable a GUI for Beetle Game using EEG signal.
123+
124+
#### GUI
125+
126+
- `python gui.py`: Enable the real-time data plotting GUI.
127+
128+
#### EOG Keystroke Emulator
106129
107130
- `python keystroke.py`: On running, a pop-up opens for connecting, and on pressing Start, blinks are detected to simulate spacebar key presses.
108131
109-
## Running All Applications Together
132+
#### CSV Plotter
133+
134+
- `python csv_plotter.py`: On running, a pop-up window opens with option to load a file, select a channel to plot, and then plot the data.
135+
136+
## Running All Applications Together in a Web-Interface
110137
111-
To run all applications together:
138+
To run all applications simultaneously, execute:
112139
113140
```bash
114141
python app.py
@@ -122,29 +149,32 @@ pip install -r app_requirements.txt
122149
123150
This will launch a Web interface. Use the interface to control the applications:
124151
125-
1. Click the `Start LSL Stream` button to initiate the LSL stream.
152+
1. Click the `Start LSL Stream` button to initiate the LSL stream or `Start NPG Stream` button to initiate the NPG stream.
126153
2. Then, click on any application button to run the desired module.
154+
Important: Keep the `python app.py` script running in the background while using any application.
127155
128156
### Available Applications
129-
- `ffteeg`: Real-time EEG analysis with FFT and brainwave power calculation.
130-
- `heartbeat_ecg`: Analyze ECG data and extract heartbeat metrics.
131-
- `eog`: Real-time EOG monitoring with blink detection.
132-
- `emgenvelope`: Real-time EMG monitor with filtering and RMS envelope.
133-
- `keystroke`: GUI for EOG-based blink detection triggering a keystroke.
134-
- `game`: Launch an EEG game for 2 players (Tug of War).
135-
- `csv_plotter`: Plot data from a CSV file.
136-
- `gui`: Launch the GUI for real time signal visualization.
157+
- `ECG with Heart Rate`: Analyze ECG data and extract heartbeat metrics.
158+
- `EMG with Envelope`: Real-time EMG monitor with filtering and RMS envelope.
159+
- `EOG with Blinks`: Real-time EOG monitoring with blink detection.
160+
- `EEG with FFT`: Real-time EEG analysis with FFT and brainwave power calculation.
161+
- `EEG Tug of War`: A 2-player game where brain activity determines the winner in a battle of focus.
162+
- `EEG Beetle Game`: Use your concentration to control a beetle's movement in this brain-powered challenge.
163+
- `GUI of Channels`: Launch the GUI for real time signal visualization.
164+
- `EOG Keystroke Emulator`: GUI for EOG-based blink detection triggering a keystroke.
165+
- `CSV Plotter`: Plot data from a CSV file.
137166
138167
## Troubleshooting
139168
140169
- **Arduino Not Detected:** Ensure the Arduino is properly connected and powered. Check the serial port and baud rate settings.
141170
- **CSV File Not Created:** Ensure you have write permissions in the directory where the script is run.
142-
- **LSL Stream Issues:** Verify that the `pylsl` library is installed and configured correctly.
171+
- **LSL Stream Issues:** Ensure that the `pylsl` library is properly installed and configured. Additionally, confirm that Bluetooth is turned off.
143172
144173
## Contributors
145174
146175
We are thankful to our awesome contributors, the list below is alphabetically sorted.
147176
177+
- [Aman Maheshwari](https://github.com/Amanmahe)
148178
- [Payal Lakra](https://github.com/payallakra)
149179
150180
The audio file used in `game.py` is sourced from [Pixabay](https://pixabay.com/sound-effects/brass-fanfare-with-timpani-and-windchimes-reverberated-146260/)

app.py

Lines changed: 76 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,71 @@ 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, bufsize=1)
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, bufsize=1)
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 render_template("index.html", lsl_started= False, lsl_status= "Failed to Start", lsl_color= "red", apps_enabled=False)
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 render_template("index.html", lsl_started= True, lsl_status= "Running", lsl_color= "green", apps_enabled=True)
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 render_template("index.html", 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+
if "NPG WebSocket connected!" in npg_process.stdout.readline().strip():
77+
npg_running = True
78+
return render_template("index.html", npg_started=True, npg_status="Running", npg_color="green", apps_enabled=True)
79+
80+
except Exception as e:
81+
npg_running = False
82+
return render_template("index.html", npg_started=False, npg_status=f"Error: {e}", npg_color="red", apps_enabled=False)
4683

4784
@app.route("/run_app", methods=["POST"])
4885
def run_app():
86+
global lsl_running, npg_running
4987
app_name = request.form.get("app_name")
5088

51-
# Check if the app is already running
89+
if not (lsl_running or npg_running):
90+
return render_template("index.html", message="Start LSL or NPG first!", running_apps=app_processes.keys())
91+
5292
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())
93+
return render_template("index.html", message=f"{app_name} is already running", running_apps=app_processes.keys())
5494

5595
try:
5696
# Start the app subprocess
@@ -60,19 +100,19 @@ def run_app():
60100
process = subprocess.Popen(["python", f"{app_name}.py"])
61101

62102
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-
103+
return render_template("index.html", running_apps=app_processes.keys(), message=None)
65104
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())
105+
return render_template("index.html", message=f"Error starting {app_name}: {e}", running_apps=app_processes.keys())
67106

68107
@app.route("/app_status", methods=["GET"])
69108
def app_status():
70109
# Check the status of all apps
71110
try:
72111
statuses = {
73-
app_name: (process.poll() is None) # True if running, False if not
74-
for app_name, process in app_processes.items()
112+
"lsl_started": lsl_running,
113+
"npg_started": npg_running
75114
}
115+
statuses.update({app_name: (process.poll() is None) for app_name, process in app_processes.items()})
76116
return jsonify(statuses)
77117
except Exception as e:
78118
return jsonify({"error": str(e)}), 500
@@ -83,7 +123,7 @@ def stop_lsl():
83123
return jsonify({'status': 'LSL Stream and applications stopped and server is shutting down.'})
84124

85125
def stop_all_processes():
86-
global lsl_process, app_processes
126+
global lsl_process, npg_process, app_processes, lsl_running, npg_running
87127

88128
# Terminate LSL process
89129
if lsl_process and lsl_process.poll() is None:
@@ -92,25 +132,35 @@ def stop_all_processes():
92132
lsl_process.wait(timeout=3)
93133
except subprocess.TimeoutExpired:
94134
lsl_process.kill()
135+
lsl_running = False
136+
137+
if npg_process and npg_process.poll() is None:
138+
npg_process.terminate()
139+
try:
140+
npg_process.wait(timeout=3)
141+
except subprocess.TimeoutExpired:
142+
npg_process.kill()
143+
npg_running = False
95144

96-
# Terminate all app processes
97-
for app_name, process in app_processes.items():
145+
for app_name, process in list(app_processes.items()):
98146
if process.poll() is None:
99147
process.terminate()
100148
try:
101149
process.wait(timeout=3)
102150
except subprocess.TimeoutExpired:
103151
process.kill()
152+
del app_processes[app_name]
104153

105-
app_processes.clear()
106154
print("All processes terminated.")
107155

108156
def handle_sigint(signal_num, frame):
109157
print("\nCtrl+C pressed! Stopping all processes...")
110158
stop_all_processes()
111159
sys.exit(0)
112160

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

115165
if __name__ == "__main__":
116166
app.run(debug=True)

app_requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ pandas==2.2.3
99
tk==0.1.0
1010
PyAutoGUI==0.9.54
1111
Flask==3.1.0
12-
psutil==6.1.1
12+
psutil==6.1.1
13+
websocket-client==1.8.0

0 commit comments

Comments
 (0)