Skip to content

Commit 0e0e55d

Browse files
Merge pull request #29 from SoftcodingForYou/make_gui_modular
V2.90.0 - Make GUI modular
2 parents 2db35aa + 56f82fb commit 0e0e55d

18 files changed

Lines changed: 945 additions & 458 deletions

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Neuri GUI
22

3-
print(«Hello Volt»)
3+
print("Hello, Volt!")
44

55
A graphical interface for electrophysiological biosensors that allows you to:
66
1. Set multiple parameters for real-time signal visualization
@@ -13,6 +13,12 @@ Watch the introduction on Youtube:
1313

1414
[![Watch the introdcution](https://img.youtube.com/vi/8DbGR9KUszQ/hqdefault.jpg)](https://www.youtube.com/embed/8DbGR9KUszQ)
1515

16+
## Compatible devices
17+
18+
- Neuri 1.x by Helment
19+
- Neuri-Lolin S3-PRO by Helment
20+
- BioAmp EXG Pill by Upside Down Labs
21+
1622
## Setup
1723

1824
The GUI is distributed as a Python program. The GUI should work with **Python versions 3.9 or higher**.
@@ -55,11 +61,6 @@ ng.Run()
5561

5662
Note that your settings are stored in a "settings.cfg" file inside the current workspace directory of the IDE or terminal.
5763

58-
## Compatible devices
59-
60-
- Neuri 1.x
61-
- Neuri-Lolin S3-PRO
62-
6364
## Troubleshooting
6465

6566
1. If the GUI seems to be stuck, most probably the wrong ports have been selected Check the port names (ie COM12 vs COM13). Pay special attention to the fact that if a port file is specified in the settings.conf, the GUI will default to that port even if there is no device connected to it.

dist/neurigui-2.82.0.tar.gz

-574 KB
Binary file not shown.

dist/neurigui-2.90.0.tar.gz

597 KB
Binary file not shown.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
try:
2+
from compatible_boards import COMPATIBLE_BOARDS
3+
except:
4+
from .compatible_boards import COMPATIBLE_BOARDS
5+
6+
7+
class BoardAgnosticUtils():
8+
9+
def __init__(self, parameter):
10+
11+
# We can not just assign all parameters to for example self.params
12+
# since Python's Process() does not allow to parse several of the
13+
# stored objects in "parameters"
14+
self.streaming_parameter = {
15+
"baud_rate": parameter.baud_rate,
16+
"time_out": parameter.time_out,
17+
"port": parameter.port,
18+
"start_code": parameter.start_code, # Start code that boards reads as sign to startt sampling (int)
19+
"max_chans": parameter.max_chans, # Specifies how many channels the incoming signals has (int)
20+
"buffer_length":parameter.buffer_length, # Length of constructed signal buffer (int)
21+
"buffer_add": parameter.buffer_add, # Length of padding to avoid filter artifacts (int)
22+
"sample_rate": parameter.sample_rate, # Sampling rate (Hz, int)
23+
"PGA": parameter.PGA, # Programmable gain (int)
24+
"saving_interval": parameter.saving_interval, # how often the data gets written to disk (seconds, int)
25+
"udp_ip": parameter.udp_ip, # IP the data shall be forwarded to
26+
"udp_port": parameter.udp_port, # Port of signal forwarding
27+
"set_customsession": parameter.set_customsession, # Whether or not to have a custom output file name (bool)
28+
"sessionName": parameter.sessionName, # Custom file name for output (str)
29+
}
30+
31+
32+
def get_board_specific_utils(self, board):
33+
"""Returns the board_specific functions that allows us to read and
34+
process incoming signals
35+
36+
Args:
37+
board (str): Unique name of board. Defined in parameters.py
38+
39+
Returns:
40+
(object): Class object containing all necessary functions to
41+
work with given board.
42+
"""
43+
for _, board_name in enumerate(list(COMPATIBLE_BOARDS.keys())):
44+
if board == board_name:
45+
return COMPATIBLE_BOARDS[board_name][3]
46+
raise Exception("Could not get board-specific functions collection")
47+
48+
49+
def headless_sampling(self):
50+
pass # This function only exists in order to make timer call something
51+
52+
53+

neuri/backend/compatible_boards.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Manual board management
2+
For every board, we collect here the sampling functions and store them
3+
inside the same class. For this, you need to manage the imports and
4+
dictionnary manually, adding the imported object in fourth position of
5+
the board's [list].
6+
"""
7+
try:
8+
from signal_sampling_neuri_v1 import SamplingUtilsNeuriV1Helment
9+
from signal_sampling_neuri_lolin import SamplingUtilsNeuriLolinHelment
10+
from signal_sampling_bioamp import SamplingUtilsBioAmpUpsideDownLabs
11+
except: # Necessary if running as Python package
12+
from .signal_sampling_neuri_v1 import SamplingUtilsNeuriV1Helment
13+
from .signal_sampling_neuri_lolin import SamplingUtilsNeuriLolinHelment
14+
from .signal_sampling_bioamp import SamplingUtilsBioAmpUpsideDownLabs
15+
16+
17+
COMPATIBLE_BOARDS = {
18+
# Unique name: [Start code (int, None if no code required), Sampling rate (Hz), baud rate (int), Board_specific class (object)]
19+
"Neuri V1 by Helment": [2, 200, 115200, SamplingUtilsNeuriV1Helment],
20+
"Neuri-Lolin S3-PRO by Helment": [2, 200, 115200, SamplingUtilsNeuriLolinHelment],
21+
"BioAmp EXG Pill by Upside Down Labs": [2, 125, 115200, SamplingUtilsBioAmpUpsideDownLabs],
22+
}

neuri/backend/configure_board.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

neuri/backend/io_manager.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import serial
2+
from threading import Thread
3+
from datetime import datetime
4+
5+
class IOManager():
6+
7+
def __init__(self, parameter):
8+
#Output
9+
t0 = str(datetime.now())
10+
t0 = t0.replace(':', '_')
11+
if parameter["set_customsession"]:
12+
file_name = parameter["sessionName"] + '.txt'
13+
else:
14+
file_name = 'Neuri ' + t0 + '.txt'
15+
16+
# Prepare data output
17+
self.output_file= file_name
18+
19+
with open(self.output_file, 'w', encoding= "utf_8") as file:
20+
file.write("Session Started\n")
21+
file.close() # Important for data to get written
22+
23+
24+
def set_up_serial_entry_point(self, baud_rate, time_out, port):
25+
# =================================================================
26+
# Connects to device, preferrably by USB
27+
# INPUT
28+
# baud_rate = baud rate of the port (scalar)
29+
# time_out = how long a message should be waited for
30+
# default = None (infinite), scalar or None
31+
# port = port to connect to ("COMxy", str)
32+
# OUTPUT
33+
# ser = Serial connection (object)
34+
# =================================================================
35+
36+
# Open communication protocol
37+
ser = serial.Serial()
38+
ser.baudrate = baud_rate
39+
ser.timeout = time_out
40+
ser.port = port
41+
print('Ready to connect to board')
42+
return ser
43+
44+
45+
def master_write_data(self, eeg_data, time_stamps, saving_interval):
46+
# =================================================================
47+
# Input:
48+
# eeg_data 2D numpy array [channels x samples] (float)
49+
# time_stamps 1D numpy array (float)
50+
# saving_interval Scalar
51+
# Output:
52+
# No output
53+
# =================================================================
54+
new_buffer = eeg_data[:, -saving_interval:]
55+
new_time_stamps = time_stamps[-saving_interval:]
56+
57+
save_thread = Thread(target=self.write_data_thread,
58+
args=(new_buffer, new_time_stamps))
59+
save_thread.start()
60+
61+
62+
def calc_sample_rate(self, curr_time, prev_iter_time, sample_rate, time_stamps):
63+
64+
time_diff = curr_time - prev_iter_time
65+
# It took (time_diff) ms to fetch (sample_rate) samples.
66+
# Calculate actual sampling rate
67+
actual_sr = int((sample_rate / (time_diff / 1000)))
68+
print('%d ms: Writing data (sampling rate = %d Hz)' %
69+
(time_stamps[-1], actual_sr))
70+
71+
72+
def write_data_thread(self, eeg_data, time_stamps):
73+
# =================================================================
74+
# Input:
75+
# eeg_data 2D numpy array [channels x samples] (float)
76+
# time_stamps 1D numpy array (float)
77+
# Output:
78+
# No output
79+
# =================================================================
80+
# Append the data points to the end of the file
81+
with open(self.output_file, 'a', encoding= "utf_8") as file:
82+
buffer_length = time_stamps.shape[0]
83+
84+
for sample_index in range(buffer_length):
85+
# Format time stamp
86+
time_stamp = time_stamps[sample_index]
87+
# format eeg data
88+
eeg_data_points = eeg_data[:,sample_index].tolist()
89+
eeg_data_points = [str(value) for value in eeg_data_points]
90+
eeg_data_points = ",".join(eeg_data_points)
91+
92+
# Write line to file
93+
file.write(f"{time_stamp}, {eeg_data_points} \n")
94+
file.close() # Important for data to get written
95+
96+
97+
def build_standard_relay_message(self, s_chans):
98+
"""Build a relay jsonized message string that gets forwarded to any
99+
third-party application.
100+
101+
Args:
102+
s_chans (int): Number of channels (specific for boards)
103+
104+
Returns relay_array (str):json.format message string with keys for:
105+
1. timestamps (t)
106+
2. channels (c)
107+
"""
108+
relay_array = {}
109+
relay_array["t"] = ''
110+
for iC in range(s_chans):
111+
relay_array["".join(["c", str(iC+1)])] = ''
112+
return relay_array

neuri/backend/parameter_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import tkinter as tk
22
from PIL import Image, ImageTk
3-
import os
3+
44

55
class ParamVal():
66

0 commit comments

Comments
 (0)