Compare commits
23 commits
alt_plotti
...
master
Author | SHA1 | Date | |
---|---|---|---|
ad77c22a04 | |||
a97d4e22e6 | |||
a1652d3eea | |||
e37726c391 | |||
479b16d801 | |||
506eeb753d | |||
4613748749 | |||
3378e9bc6f | |||
66e5f0ab91 | |||
287a5cf542 | |||
c274dae725 | |||
a7ba4360b2 | |||
06701d452d | |||
2145a7300e | |||
830238fa4c | |||
ac20180d47 | |||
5fcf60921c | |||
1480bf60ca | |||
afa8d84680 | |||
8d0034026d | |||
57d66b81c1 | |||
eb62d47702 | |||
79d5867b47 |
34 changed files with 346 additions and 264 deletions
|
@ -5,8 +5,11 @@ Created on Tue Nov 23 11:11:41 2021
|
||||||
|
|
||||||
@author: adamr
|
@author: adamr
|
||||||
"""
|
"""
|
||||||
import NeurOne
|
import sys, os
|
||||||
import RDA
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from connection import NeurOne, RDA
|
||||||
|
# import RDA
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import matplotlib
|
import matplotlib
|
||||||
|
@ -21,21 +24,19 @@ import matplotlib.cm as cm
|
||||||
import PyQt5.uic as uic
|
import PyQt5.uic as uic
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import json
|
import json
|
||||||
#import scipy.stats as st
|
import scipy.stats as st
|
||||||
import csv
|
import csv
|
||||||
import datetime
|
import datetime
|
||||||
import ctypes
|
import ctypes
|
||||||
import scipy
|
import scipy
|
||||||
import pywt
|
import pywt
|
||||||
import queue
|
|
||||||
|
|
||||||
from cycler import cycler
|
from cycler import cycler
|
||||||
from matplotlib.backend_bases import MouseButton
|
|
||||||
from PyQt5.QtCore import QTimer, Qt
|
from PyQt5.QtCore import QTimer, Qt
|
||||||
from PyQt5.QtGui import QImage, QPixmap, QIcon, QFont
|
from PyQt5.QtGui import QImage, QPixmap, QIcon, QFont
|
||||||
from PyQt5.QtWidgets import (QMainWindow, QFileDialog, QMessageBox, QCheckBox, QLineEdit, QWidget, QPushButton,
|
from PyQt5.QtWidgets import (QMainWindow, QFileDialog, QMessageBox, QCheckBox, QLineEdit, QWidget, QPushButton,
|
||||||
QLabel, QHBoxLayout, QGridLayout, QAction, QApplication, QDialog, QDialogButtonBox,
|
QLabel, QHBoxLayout, QGridLayout, QAction, QApplication, QDialog, QDialogButtonBox,
|
||||||
QVBoxLayout, QFrame)
|
QVBoxLayout, QFrame, QFormLayout)
|
||||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
|
@ -46,9 +47,9 @@ if sys.platform=='darwin':
|
||||||
#from multiprocessing import Queue as QueueOld #Queue doesn't work on MacOS
|
#from multiprocessing import Queue as QueueOld #Queue doesn't work on MacOS
|
||||||
else:
|
else:
|
||||||
from multiprocessing import Process, Queue, Value
|
from multiprocessing import Process, Queue, Value
|
||||||
from Waiting import Waiting_window
|
from utils.Waiting import Waiting_window
|
||||||
from FirstWindow import First_window
|
from utils.FirstWindow import First_window
|
||||||
from Functions import (apply_montage, eye_reg, most_frequent, connect_sig, set_to_gray, update_stem,
|
from utils.Functions import (apply_montage, eye_reg, most_frequent, connect_sig, set_to_gray, update_stem,
|
||||||
adjust_hist, min_zero, pentropy, entropy, check_integrity, doubleMADsfromMedian)
|
adjust_hist, min_zero, pentropy, entropy, check_integrity, doubleMADsfromMedian)
|
||||||
|
|
||||||
if sys.platform=='darwin':
|
if sys.platform=='darwin':
|
||||||
|
@ -146,24 +147,18 @@ class NeurOneOffline():
|
||||||
sendqueue=False,ringbuf_factor=2,dump=False,avgPackets=1):
|
sendqueue=False,ringbuf_factor=2,dump=False,avgPackets=1):
|
||||||
self.ringbuffersize = ringbuffersize
|
self.ringbuffersize = ringbuffersize
|
||||||
tmp_path = '/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/nobeep/subj_8/X47851_iTBS.vhdr'#
|
tmp_path = '/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/nobeep/subj_8/X47851_iTBS.vhdr'#
|
||||||
#'/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/beep/subj_14/X13193_adam.vhdr' #'/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/beep/subj_6/X77384_iTBS.vhdr'
|
#'/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/beep/subj_14/X13193_adam.vhdr' #'/mnt/projects/P_BCT_EEG/DLPFCM1_iTBS/DLPFC/beep/subj_6/X7738_iTBS.vhdr'
|
||||||
num_electr = 18
|
num_electr = 18
|
||||||
eeg_chn = np.arange(0,num_electr,1)
|
eeg_chn = np.arange(0,num_electr,1)
|
||||||
hdr = mne.io.read_raw_brainvision(tmp_path)
|
hdr = mne.io.read_raw_brainvision(tmp_path)
|
||||||
|
|
||||||
# hdr.set_channel_types({'EMGleft': 'emg', 'EOGright': 'eog'})
|
|
||||||
# hdr.set_montage(mne.channels.read_custom_montage('easycap-M10_63_NO.txt'))
|
|
||||||
# mrk_fullpath = tmp_path[:-4]+'vmrk'
|
|
||||||
# eeg_fullpath = tmp_path[:-4]+'eeg' #this two are made by hand instead of function.
|
|
||||||
#Maybe there is some func for this
|
|
||||||
# Annotations returns all events - stimA, stimB, stopA, stopB, start of experiment etc... We chose only stim
|
# Annotations returns all events - stimA, stimB, stopA, stopB, start of experiment etc... We chose only stim
|
||||||
stim = hdr.annotations.onset[np.logical_or(hdr.annotations.description=="Stimulus/A",
|
stim = hdr.annotations.onset[np.logical_or(hdr.annotations.description=="Stimulus/A",
|
||||||
hdr.annotations.description=="Stimulus/B")]
|
hdr.annotations.description=="Stimulus/B")]
|
||||||
# Separate stimA and stimB
|
# Separate stimA and stimB
|
||||||
stimA = hdr.annotations.onset[hdr.annotations.description=="Stimulus/A"]
|
stimA = hdr.annotations.onset[hdr.annotations.description=="Stimulus/A"]
|
||||||
stimB = hdr.annotations.onset[hdr.annotations.description=="Stimulus/B"]
|
stimB = hdr.annotations.onset[hdr.annotations.description=="Stimulus/B"]
|
||||||
#divide for stim A and B
|
|
||||||
#stimR = hdr.annotations.onset[hdr.annotations.description=='Response/R 16']
|
|
||||||
npts = hdr.n_times
|
npts = hdr.n_times
|
||||||
nfft = int(hdr.info['sfreq']) # Sampling rate [Hz]
|
nfft = int(hdr.info['sfreq']) # Sampling rate [Hz]
|
||||||
fs = int(hdr.info['sfreq']) # Sampling rate [Hz]
|
fs = int(hdr.info['sfreq']) # Sampling rate [Hz]
|
||||||
|
@ -215,7 +210,7 @@ def acquire_data(q, size, run, speed, downsample, sleep_time, ip = '192.168.200.
|
||||||
run: Value class from multiprocessing library. That value can be changed in main process
|
run: Value class from multiprocessing library. That value can be changed in main process
|
||||||
downsample: boolean value. Says if data will be downsampled to 1000 Hz
|
downsample: boolean value. Says if data will be downsampled to 1000 Hz
|
||||||
sleep_time: int, set how often function should refresh. Usually it takes a bit more that that"""
|
sleep_time: int, set how often function should refresh. Usually it takes a bit more that that"""
|
||||||
offline = 'offline'
|
# offline = 'offline'
|
||||||
#import NeurOne_v3
|
#import NeurOne_v3
|
||||||
if offline=="offline":
|
if offline=="offline":
|
||||||
NO = NeurOneOffline()
|
NO = NeurOneOffline()
|
||||||
|
@ -247,7 +242,7 @@ class AppForm(QMainWindow):
|
||||||
def __init__(self, passed_params = None, parent=None):
|
def __init__(self, passed_params = None, parent=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
#self.setStyleSheet("background: whitesmoke")
|
#self.setStyleSheet("background: whitesmoke")
|
||||||
# self.offline = 'NeurOne' #"BrainProducts"#False
|
self.offline = None #"offline" #'NeurOne' #"BrainProducts"#False
|
||||||
self.time_start = time.time()
|
self.time_start = time.time()
|
||||||
QMainWindow.__init__(self, parent)
|
QMainWindow.__init__(self, parent)
|
||||||
self.montage_file_path = 'montage_18ch.csv'
|
self.montage_file_path = 'montage_18ch.csv'
|
||||||
|
@ -316,7 +311,7 @@ class AppForm(QMainWindow):
|
||||||
ch_sum = 0
|
ch_sum = 0
|
||||||
for i in range(self.second_to_analyze.shape[0]):
|
for i in range(self.second_to_analyze.shape[0]):
|
||||||
a_seg = np.zeros([2, self.second_to_analyze.shape[1]])
|
a_seg = np.zeros([2, self.second_to_analyze.shape[1]])
|
||||||
a_seg[0,:] = self.second_to_analyze[0]#*1e6
|
a_seg[0,:] = self.second_to_analyze[i]#*1e6
|
||||||
a_seg[1,:] = np.linspace(0,1,self.Fs)
|
a_seg[1,:] = np.linspace(0,1,self.Fs)
|
||||||
matrix_dist = scipy.spatial.distance.cdist(a_seg, a_seg, 'euclidean')
|
matrix_dist = scipy.spatial.distance.cdist(a_seg, a_seg, 'euclidean')
|
||||||
ch_sum += sum(np.diagonal(matrix_dist,1))
|
ch_sum += sum(np.diagonal(matrix_dist,1))
|
||||||
|
@ -332,8 +327,7 @@ class AppForm(QMainWindow):
|
||||||
10: 7.812 - 15.625 Hz
|
10: 7.812 - 15.625 Hz
|
||||||
11: 15.625 - 31.25 Hz
|
11: 15.625 - 31.25 Hz
|
||||||
"""
|
"""
|
||||||
band = band - 9 #weird way, but then call from function works well for particular band
|
band = band - 9
|
||||||
#but works correctly only for this set of features!
|
|
||||||
dwt_pw1, dwt_pw2, dwt_pw3, dwt_pw4 = self.dwt[:4]
|
dwt_pw1, dwt_pw2, dwt_pw3, dwt_pw4 = self.dwt[:4]
|
||||||
#sum of squares
|
#sum of squares
|
||||||
dwt_pw1 = np.sum(dwt_pw1**2)
|
dwt_pw1 = np.sum(dwt_pw1**2)
|
||||||
|
@ -377,15 +371,14 @@ class AppForm(QMainWindow):
|
||||||
'High Gamma FFT Power', 'Spectral entropy', 'Temporal entropy',
|
'High Gamma FFT Power', 'Spectral entropy', 'Temporal entropy',
|
||||||
'Line length', 'DWT Power 0-4 Hz', 'DWT 4-8 Hz',
|
'Line length', 'DWT Power 0-4 Hz', 'DWT 4-8 Hz',
|
||||||
'DWT 8-16 Hz', 'DWT 16-31 Hz','Variance','Correlation']
|
'DWT 8-16 Hz', 'DWT 16-31 Hz','Variance','Correlation']
|
||||||
# One last thing to use your new function is to add string with its name
|
|
||||||
# to the other First_window.py: variable features_names in class First_window
|
|
||||||
|
|
||||||
def all_params(self, passed_params = None, restarted=''):
|
def all_params(self, passed_params = None, restarted=''):
|
||||||
#reads values from TMS_protocol.txt file
|
#reads values from TMS_protocol.txt file
|
||||||
|
|
||||||
#montage matrix is a matrix that is multiplied with the signal
|
#montage matrix is a matrix that is multiplied with the signal
|
||||||
#identity matrix multiply all channels by 1, so we have the same signal as an output
|
#identity matrix multiply all channels by 1, so we have the same signal as an output
|
||||||
settings_file = pd.read_csv('TMS_protocol.txt',sep=':', header=None) #read file with settings
|
settings_file = pd.read_csv('settings/TMS_protocol.txt',sep=':', header=None) #read file with settings
|
||||||
print(restarted)
|
print(restarted)
|
||||||
#open the file to save values during the experiment
|
#open the file to save values during the experiment
|
||||||
self.log_file = open('logs//TMS_log_'+str(f"{datetime.datetime.now():%Y-%m-%d-%H-%M}"+restarted+'.csv'), 'w')
|
self.log_file = open('logs//TMS_log_'+str(f"{datetime.datetime.now():%Y-%m-%d-%H-%M}"+restarted+'.csv'), 'w')
|
||||||
|
@ -394,10 +387,9 @@ class AppForm(QMainWindow):
|
||||||
self.log_file.flush() #flushing is applying changes we made to the file
|
self.log_file.flush() #flushing is applying changes we made to the file
|
||||||
self.Fs = 1000 #5000
|
self.Fs = 1000 #5000
|
||||||
self.size_of_up = 2*self.Fs #5000 how much data we get from NeurOne function
|
self.size_of_up = 2*self.Fs #5000 how much data we get from NeurOne function
|
||||||
self.plot_par = 0
|
|
||||||
self.baseline_reg = 0
|
|
||||||
#file with settings and assigning variables to them
|
#file with settings and assigning variables to them
|
||||||
settings_file = pd.read_csv('TMS_protocol.txt',sep=':', header=None)
|
# settings_file = pd.read_csv('settings/TMS_protocol.txt',sep=':', header=None)
|
||||||
|
|
||||||
#params can be set in GUI, so then no need for using ones from the file
|
#params can be set in GUI, so then no need for using ones from the file
|
||||||
if passed_params is not None:
|
if passed_params is not None:
|
||||||
|
@ -417,9 +409,13 @@ class AppForm(QMainWindow):
|
||||||
self.remove_outliers = passed_params['remove_outliers']
|
self.remove_outliers = passed_params['remove_outliers']
|
||||||
self.ip = passed_params['ip']
|
self.ip = passed_params['ip']
|
||||||
self.port = passed_params['port']
|
self.port = passed_params['port']
|
||||||
self.offline = passed_params['offline']
|
self.offline = self.offline if not self.offline==None else passed_params['offline']
|
||||||
self.exp_trig = passed_params['exp_trig']
|
self.exp_trig = passed_params['exp_trig']
|
||||||
self.exp_time = passed_params['exp_time']
|
self.exp_time = passed_params['exp_time']
|
||||||
|
self.if_percentage = passed_params['percentages']
|
||||||
|
self.received_thr_values = passed_params['thr_values']
|
||||||
|
self.plot_len = passed_params['plot_len']
|
||||||
|
print(self.offline)
|
||||||
else:
|
else:
|
||||||
self.montage_file_path = 'montage_18ch.csv'
|
self.montage_file_path = 'montage_18ch.csv'
|
||||||
self.time_between_bursts = int(settings_file[settings_file[0]=='time_between_trains'].values[0][1])
|
self.time_between_bursts = int(settings_file[settings_file[0]=='time_between_trains'].values[0][1])
|
||||||
|
@ -431,6 +427,8 @@ class AppForm(QMainWindow):
|
||||||
self.num_of_lines = int(settings_file[settings_file[0]=='number_of_lines'].values[0][1])
|
self.num_of_lines = int(settings_file[settings_file[0]=='number_of_lines'].values[0][1])
|
||||||
self.exp_trig_loaded = int(settings_file[settings_file[0]=='expected_triggers'].values[0][1])
|
self.exp_trig_loaded = int(settings_file[settings_file[0]=='expected_triggers'].values[0][1])
|
||||||
self.exp_time_loaded = int(settings_file[settings_file[0]=='expected_time'].values[0][1])
|
self.exp_time_loaded = int(settings_file[settings_file[0]=='expected_time'].values[0][1])
|
||||||
|
self.if_percentage = np.ones(12)
|
||||||
|
self.received_thr_values = [10]*12
|
||||||
self.slow_mode = False
|
self.slow_mode = False
|
||||||
self.used_features = [0,1,2,3,4,5]
|
self.used_features = [0,1,2,3,4,5]
|
||||||
self.use_notch = True
|
self.use_notch = True
|
||||||
|
@ -439,6 +437,8 @@ class AppForm(QMainWindow):
|
||||||
self.ip = '192.168.200.201'
|
self.ip = '192.168.200.201'
|
||||||
self.port = 50000
|
self.port = 50000
|
||||||
self.offline = False
|
self.offline = False
|
||||||
|
self.plot_len = 4 #length of data to plot (last seconds of data array)
|
||||||
|
|
||||||
|
|
||||||
self.unit_label = np.array(self.unit_label)[self.used_features]
|
self.unit_label = np.array(self.unit_label)[self.used_features]
|
||||||
self.log_file_writer.writerow(['time', 'state', self.used_features])
|
self.log_file_writer.writerow(['time', 'state', self.used_features])
|
||||||
|
@ -465,7 +465,7 @@ class AppForm(QMainWindow):
|
||||||
print(self.included_ch)
|
print(self.included_ch)
|
||||||
# self.montage_matrix = np.identity(self.num_of_ch)
|
# self.montage_matrix = np.identity(self.num_of_ch)
|
||||||
if self.montage_file_path in [None, '']:
|
if self.montage_file_path in [None, '']:
|
||||||
self.montage_file_path = 'montage_18ch.csv'
|
self.montage_file_path = 'settings/montage_18ch.csv'
|
||||||
self.montage_matrix = np.array(pd.read_csv(self.montage_file_path, header=None))
|
self.montage_matrix = np.array(pd.read_csv(self.montage_file_path, header=None))
|
||||||
|
|
||||||
|
|
||||||
|
@ -487,7 +487,6 @@ class AppForm(QMainWindow):
|
||||||
self.theta_band= json.loads(settings_file[settings_file[0]=='theta_range'].values[0][1])
|
self.theta_band= json.loads(settings_file[settings_file[0]=='theta_range'].values[0][1])
|
||||||
self.colors = ['b', 'm', 'r', 'k', 'c', ] #colors used for lines if less that 6 of them
|
self.colors = ['b', 'm', 'r', 'k', 'c', ] #colors used for lines if less that 6 of them
|
||||||
self.data_len = 30*self.Fs #length of the data array in seconds
|
self.data_len = 30*self.Fs #length of the data array in seconds
|
||||||
self.plot_len = 4 #length of data to plot (last seconds of data array)
|
|
||||||
self.plot_dividing_factor = 100
|
self.plot_dividing_factor = 100
|
||||||
self.previous_state = np.zeros(6)
|
self.previous_state = np.zeros(6)
|
||||||
if self.num_of_lines>5: #if more than 5 lines then colors of them from colormap
|
if self.num_of_lines>5: #if more than 5 lines then colors of them from colormap
|
||||||
|
@ -510,9 +509,9 @@ class AppForm(QMainWindow):
|
||||||
self.create_main_frame() #create plots, buttons, figures etc...
|
self.create_main_frame() #create plots, buttons, figures etc...
|
||||||
|
|
||||||
#create data array
|
#create data array
|
||||||
self.loaded = np.zeros([self.num_of_ch,self.data_len])
|
self.loaded = np.full([self.num_of_ch,self.data_len], None)
|
||||||
self.loaded_full = np.zeros([self.num_of_ch+1,self.data_len])
|
self.loaded_full = np.full([self.num_of_ch+1,self.data_len], None)
|
||||||
self.data = np.random.rand(self.num_of_ch,self.data_len)
|
self.data = np.full((self.num_of_ch,self.data_len), None)
|
||||||
self.trigg_data = np.zeros(self.data_len) #array to keep trigger data in
|
self.trigg_data = np.zeros(self.data_len) #array to keep trigger data in
|
||||||
self.num=0
|
self.num=0
|
||||||
self.doit=0 #to count number of seconds after last stimuli in train
|
self.doit=0 #to count number of seconds after last stimuli in train
|
||||||
|
@ -525,11 +524,11 @@ class AppForm(QMainWindow):
|
||||||
self.calibration_counter_max = int(1000/self.speed_general)
|
self.calibration_counter_max = int(1000/self.speed_general)
|
||||||
#Lists to keep calibration values for each measurment
|
#Lists to keep calibration values for each measurment
|
||||||
self.f1_cal = []
|
self.f1_cal = []
|
||||||
self.f3_cal = []
|
|
||||||
self.f2_cal = []
|
self.f2_cal = []
|
||||||
|
self.f3_cal = []
|
||||||
self.f4_cal = []
|
self.f4_cal = []
|
||||||
self.f6_cal = []
|
|
||||||
self.f5_cal = []
|
self.f5_cal = []
|
||||||
|
self.f6_cal = []
|
||||||
self.qmbx = None
|
self.qmbx = None
|
||||||
self.red_dots = []
|
self.red_dots = []
|
||||||
|
|
||||||
|
@ -553,10 +552,6 @@ class AppForm(QMainWindow):
|
||||||
self.last_sec = ss.filtfilt(A, B, self.last_sec)
|
self.last_sec = ss.filtfilt(A, B, self.last_sec)
|
||||||
self.last_sec = ss.detrend(self.last_sec, axis=1)
|
self.last_sec = ss.detrend(self.last_sec, axis=1)
|
||||||
|
|
||||||
# plt.figure()
|
|
||||||
# plt.plot(self.last_sec[self.included_ch].T)
|
|
||||||
# plt.plot(eog)
|
|
||||||
#that's stupid, move channel selection before!!!!!!!!!!!!!
|
|
||||||
if self.use_regression:
|
if self.use_regression:
|
||||||
self.last_sec = eye_reg(self.last_sec[self.included_ch], eog)
|
self.last_sec = eye_reg(self.last_sec[self.included_ch], eog)
|
||||||
return self.last_sec
|
return self.last_sec
|
||||||
|
@ -584,8 +579,6 @@ class AppForm(QMainWindow):
|
||||||
else:
|
else:
|
||||||
self.results[idx] = self.functions[feature]()
|
self.results[idx] = self.functions[feature]()
|
||||||
|
|
||||||
#Checks how many fields were filled already, so we know what stage are we on
|
|
||||||
#and where to save the data
|
|
||||||
x = np.where(self.feature1==None)[0][0]
|
x = np.where(self.feature1==None)[0][0]
|
||||||
y = np.where(self.feature1==None)[1][0]
|
y = np.where(self.feature1==None)[1][0]
|
||||||
|
|
||||||
|
@ -606,17 +599,7 @@ class AppForm(QMainWindow):
|
||||||
times1 = time.time()
|
times1 = time.time()
|
||||||
print("Calculation of features: {}".format(times1-times))
|
print("Calculation of features: {}".format(times1-times))
|
||||||
|
|
||||||
# #set xlim of plots
|
|
||||||
# self.axesMap[0,0].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
# self.axesMap[0,1].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
# self.axesMap[1,0].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
# self.axesMap[1,1].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
# self.axesMap[2,0].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
# self.axesMap[2,1].set_xlim(0.5,self.time_between_bursts-self.breaktime+0.5)
|
|
||||||
|
|
||||||
#if value is not within threshold values then background color is red (salmon), otherwise green
|
|
||||||
|
|
||||||
#checks if there is a need to change a color of the background
|
|
||||||
old_prv_state = self.previous_state.copy()
|
old_prv_state = self.previous_state.copy()
|
||||||
if not all(np.isnan(self.thr_1)):
|
if not all(np.isnan(self.thr_1)):
|
||||||
if self.results[0]>=self.thr_1[0] and self.results[0]<=self.thr_1[1]:
|
if self.results[0]>=self.thr_1[0] and self.results[0]<=self.thr_1[1]:
|
||||||
|
@ -722,9 +705,7 @@ class AppForm(QMainWindow):
|
||||||
#If there is a need to redraw we do that. draw() option is slower, but more robust
|
#If there is a need to redraw we do that. draw() option is slower, but more robust
|
||||||
if need_redraw:
|
if need_redraw:
|
||||||
self.canvasMap.draw()
|
self.canvasMap.draw()
|
||||||
#otherwise we can just update the line, or to be precise, I think it just
|
#otherwise we can just update the line, or to be precise,
|
||||||
#draws the line on the old one. In this application it's fine. Faster than previous method.
|
|
||||||
#I can think about blitting, so it could be even faster...
|
|
||||||
else:
|
else:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(2):
|
for j in range(2):
|
||||||
|
@ -738,15 +719,14 @@ class AppForm(QMainWindow):
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Updates data, checks if something should be plotted"""
|
"""Updates data, checks if something should be plotted"""
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
# There were some problems with delay. This way it works, but probably it can be done better
|
|
||||||
# If the queue with data timer is sped up.
|
|
||||||
if self.q.qsize()>0:
|
if self.q.qsize()>0:
|
||||||
self.timer.setInterval(int(1*self.speed_general*0.97))
|
self.timer.setInterval(int(1*self.speed_general*0.97))
|
||||||
if self.q.qsize()<1 and self.timer.interval()!= int(self.speed_general*1.1):
|
if self.q.qsize()<1 and self.timer.interval()!= int(self.speed_general*1.1):
|
||||||
self.timer.setInterval(int(self.speed_general*1.03))
|
self.timer.setInterval(int(self.speed_general*1.03))
|
||||||
|
|
||||||
times = time.time()
|
times = time.time()
|
||||||
self.offline='offline' #remove this!
|
# self.offline='offline' #remove this!
|
||||||
if self.offline=="offline":
|
if self.offline=="offline":
|
||||||
incl = [0,2,6,7,8,10,13,16,18,22,25,28,31,34,41,43,-3,-2,-1] # For offline only
|
incl = [0,2,6,7,8,10,13,16,18,22,25,28,31,34,41,43,-3,-2,-1] # For offline only
|
||||||
loaded_temp = self.q.get()[incl]/10 # Load data
|
loaded_temp = self.q.get()[incl]/10 # Load data
|
||||||
|
@ -765,7 +745,6 @@ class AppForm(QMainWindow):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("ValueError, wait...")
|
print("ValueError, wait...")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
self.loaded_noeye = self.loaded.copy()
|
self.loaded_noeye = self.loaded.copy()
|
||||||
|
|
||||||
step1 = time.time()-time_start
|
step1 = time.time()-time_start
|
||||||
|
@ -787,16 +766,16 @@ class AppForm(QMainWindow):
|
||||||
self.loaded_full = self.loaded.copy() #keeps it for the next second
|
self.loaded_full = self.loaded.copy() #keeps it for the next second
|
||||||
step2 = time.time()-time_start
|
step2 = time.time()-time_start
|
||||||
# ZeroDivisionError means that data is not yet loaded
|
# ZeroDivisionError means that data is not yet loaded
|
||||||
# try:
|
try:
|
||||||
# # [A,B] = ss.butter(2, 0.1/(self.Fs/2), 'highpass')
|
# [A,B] = ss.butter(2, 0.1/(self.Fs/2), 'highpass')
|
||||||
# # self.loaded[:self.num_of_ch,-4*self.Fs:] = ss.filtfilt(A, B, self.loaded[:self.num_of_ch, -4*self.Fs:])
|
# self.loaded[:self.num_of_ch,-4*self.Fs:] = ss.filtfilt(A, B, self.loaded[:self.num_of_ch, -4*self.Fs:])
|
||||||
# # self.loaded[:self.num_of_ch,-4*self.Fs:] = self.loaded[:self.num_of_ch,-4*self.Fs:] - np.mean(self.loaded[:self.num_of_ch,-4*self.Fs:],1, keepdims=True)
|
# self.loaded[:self.num_of_ch,-4*self.Fs:] = self.loaded[:self.num_of_ch,-4*self.Fs:] - np.mean(self.loaded[:self.num_of_ch,-4*self.Fs:],1, keepdims=True)
|
||||||
# self.loaded[:self.num_of_ch,-4*self.Fs:] = ss.detrend(self.loaded[:self.num_of_ch,-4*self.Fs:])
|
self.loaded[:self.num_of_ch,-self.plot_len*self.Fs:] = ss.detrend(self.loaded[:self.num_of_ch,-self.plot_len*self.Fs:])
|
||||||
# except ZeroDivisionError: # This error means that buffer is still not full
|
except ZeroDivisionError: # This error means that buffer is still not full
|
||||||
# if self.qmbx == None:
|
if self.qmbx == None:
|
||||||
# self.qmbx = Waiting_window() # Small window with a message to wait
|
self.qmbx = Waiting_window() # Small window with a message to wait
|
||||||
# print('Waiting for data (should take up to few seconds)')
|
print('Waiting for data (should take up to few seconds)')
|
||||||
# return(0) # Stop function here, because in this case rest of update is not needed
|
return(0) # Stop function here, because in this case rest of update is not needed
|
||||||
|
|
||||||
# Getting to this point of the code means there were no exception before
|
# Getting to this point of the code means there were no exception before
|
||||||
# So the waiting window can be destroyed
|
# So the waiting window can be destroyed
|
||||||
|
@ -826,21 +805,13 @@ class AppForm(QMainWindow):
|
||||||
if len(stim)>0:
|
if len(stim)>0:
|
||||||
for ind in stim[::-1]:
|
for ind in stim[::-1]:
|
||||||
size = self.data_len - (od+ind-int(int_from*self.Fs))
|
size = self.data_len - (od+ind-int(int_from*self.Fs))
|
||||||
# print('size:', size)
|
self.loaded[:, od+ind-int(int_from*self.Fs):od+ind+int(int_to*self.Fs)] = self.loaded[:,min(od+ind+int(int_to*self.Fs), 30000-1)].reshape(-1, 1)
|
||||||
# print(len(self.loaded))
|
|
||||||
# Interpolation - pretty long line, but basically it chooses ranges and
|
|
||||||
# assign boundary value as a baseline and does that in (I guess) more optimal way than using loops
|
|
||||||
self.loaded[:, od+ind-int(int_from*self.Fs):od+ind+int(int_to*self.Fs)] = np.outer(
|
|
||||||
self.loaded[:,min(od+ind+int(int_to*self.Fs), 30000-1)], np.ones(min(size, int((int_from+int_to)*self.Fs))))
|
|
||||||
|
|
||||||
# for i in range(self.loaded.shape[0]):
|
# for i in range(self.loaded.shape[0]):
|
||||||
# self.loaded[i, od+ind-int(int_from*self.Fs):od+ind+int(int_to*self.Fs)] = np.linspace(
|
# self.loaded[i, od+ind-int(int_from*self.Fs):od+ind+int(int_to*self.Fs)] = np.linspace(
|
||||||
# self.loaded[i,min(od+ind-int(int_from*self.Fs),30000-1)],
|
# self.loaded[i,min(od+ind-int(int_from*self.Fs),30000-1)],
|
||||||
# self.loaded[i,max(od+ind+int(int_to*self.Fs),30000-1)],
|
# self.loaded[i,max(od+ind+int(int_to*self.Fs),30000-1)],
|
||||||
# od+ind+int(int_to*self.Fs)-(od+ind-int(int_from*self.Fs)))
|
# od+ind+int(int_to*self.Fs)-(od+ind-int(int_from*self.Fs)))
|
||||||
if self.plot_par%4==0:
|
|
||||||
self.baseline_reg = np.mean(self.loaded[:self.num_of_ch,-5*self.Fs:-4*self.Fs], 1)
|
|
||||||
self.loaded[:self.num_of_ch,-4*self.Fs:] = (self.loaded[:self.num_of_ch,-4*self.Fs:].T - self.baseline_reg).T
|
|
||||||
step6 = time.time()-time_start
|
step6 = time.time()-time_start
|
||||||
# Eye regression for visualisation
|
# Eye regression for visualisation
|
||||||
#plt.figure()
|
#plt.figure()
|
||||||
|
@ -861,8 +832,6 @@ class AppForm(QMainWindow):
|
||||||
|
|
||||||
step9 = time.time()-time_start
|
step9 = time.time()-time_start
|
||||||
|
|
||||||
# If do_calibration is True and there were trigger recently then stop calibration
|
|
||||||
# TODO: THAT'S CONDITION REQUIRED FOR STARTING MEASURMENTS. PROBABLY IT WON'T WORK FOR SOME MORE EXTREME SETTINGS
|
|
||||||
# if self.do_calibration and len(stim_where[stim_where>(self.data_len-max(
|
# if self.do_calibration and len(stim_where[stim_where>(self.data_len-max(
|
||||||
# 2000, round(self.Fs*self.time_between_bursts*0.7, -3)))])>0:
|
# 2000, round(self.Fs*self.time_between_bursts*0.7, -3)))])>0:
|
||||||
if self.do_calibration and len(stim_where[stim_where>(self.data_len-max(2000, self.exp_time+1500))])>0:
|
if self.do_calibration and len(stim_where[stim_where>(self.data_len-max(2000, self.exp_time+1500))])>0:
|
||||||
|
@ -876,7 +845,6 @@ class AppForm(QMainWindow):
|
||||||
return 0 #ends run of this function so nothing else happens
|
return 0 #ends run of this function so nothing else happens
|
||||||
step10 = time.time()-time_start
|
step10 = time.time()-time_start
|
||||||
# If set number of stimuli is detected
|
# If set number of stimuli is detected
|
||||||
# doit --> set length of the measurment between bursts
|
|
||||||
# if self.doit==0 and sum(stim_where>self.data_len-max(
|
# if self.doit==0 and sum(stim_where>self.data_len-max(
|
||||||
# 2000, round(self.Fs*self.time_between_bursts*0.7, -3)))==10:
|
# 2000, round(self.Fs*self.time_between_bursts*0.7, -3)))==10:
|
||||||
print(sum(stim_where>self.data_len-max(2000, self.exp_time+1500)))
|
print(sum(stim_where>self.data_len-max(2000, self.exp_time+1500)))
|
||||||
|
@ -900,9 +868,6 @@ class AppForm(QMainWindow):
|
||||||
elif self.doit>0:
|
elif self.doit>0:
|
||||||
print(self.doit)
|
print(self.doit)
|
||||||
self.last_stim = stim_where[-1]
|
self.last_stim = stim_where[-1]
|
||||||
# In some situations last stimuli might be already from another train, while
|
|
||||||
# First 100ms of loaded signal belong to previous one. That is why this exception exists
|
|
||||||
# EDIT: not sure if it's still needed after other changes I made
|
|
||||||
if any(np.diff(stim_where[stim_where>self.data_len-max(
|
if any(np.diff(stim_where[stim_where>self.data_len-max(
|
||||||
2000, round(self.Fs*self.time_between_bursts*1.2, -3))])>1000):
|
2000, round(self.Fs*self.time_between_bursts*1.2, -3))])>1000):
|
||||||
print('UWAGA NA TO')
|
print('UWAGA NA TO')
|
||||||
|
@ -940,6 +905,7 @@ class AppForm(QMainWindow):
|
||||||
#set data, last plot_len seconds
|
#set data, last plot_len seconds
|
||||||
plot_data = self.data[i,self.data_len-self.plot_len*
|
plot_data = self.data[i,self.data_len-self.plot_len*
|
||||||
self.Fs:self.data_len]
|
self.Fs:self.data_len]
|
||||||
|
plot_data = plot_data[::self.plot_len] #lets speed up plotting by downsampling
|
||||||
#EMG has higher amplitude usually. A special case to make it smaller
|
#EMG has higher amplitude usually. A special case to make it smaller
|
||||||
#self.emg_ch+1 because self.num_of_ch doesn't include trigger
|
#self.emg_ch+1 because self.num_of_ch doesn't include trigger
|
||||||
if i==np.arange(self.num_of_ch)[self.emg_ch+1] and self.emg_ch!='':
|
if i==np.arange(self.num_of_ch)[self.emg_ch+1] and self.emg_ch!='':
|
||||||
|
@ -950,18 +916,13 @@ class AppForm(QMainWindow):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
plot_data = plot_data/self.plot_dividing_factor + self.num_of_ch - i
|
plot_data = plot_data/self.plot_dividing_factor + self.num_of_ch - i
|
||||||
if self.plot_par%4!=0:
|
|
||||||
plot_data_c = plot_data.copy()
|
|
||||||
plot_data[:self.Fs*(self.plot_par%4)], plot_data[self.Fs*(self.plot_par%4):] = plot_data_c[4000-self.Fs*(self.plot_par%4):], plot_data_c[:4000-self.Fs*(self.plot_par%4)]
|
|
||||||
plot_data[self.Fs*(self.plot_par%4)-100:self.Fs*(self.plot_par%4)+100] = np.nan
|
|
||||||
# plot_data = plot_data/(3.5*np.max(np.abs(plot_data))) + self.num_of_ch - i
|
# plot_data = plot_data/(3.5*np.max(np.abs(plot_data))) + self.num_of_ch - i
|
||||||
self.line[i].set_data(np.arange(0,self.plot_len*self.Fs), plot_data)
|
self.line[i].set_data(np.arange(0,self.Fs), plot_data) #self.plot_len*
|
||||||
self.plot_par+=1
|
|
||||||
self.axes.set_ylim(0, self.num_of_ch+1)
|
self.axes.set_ylim(0, self.num_of_ch+1)
|
||||||
#self.axes.set_ylim(0,np.max(self.data[:,-self.plot_len*self.Fs:])+dif) #set ylim to fit everything on the plot
|
#self.axes.set_ylim(0,np.max(self.data[:,-self.plot_len*self.Fs:])+dif) #set ylim to fit everything on the plot
|
||||||
if len(stim)>0:
|
if len(stim)>0:
|
||||||
for ind in stim:
|
for ind in stim:
|
||||||
self.axes.axvline(ind) #plot vertical line for each trigger
|
self.axes.axvline(int(ind/self.plot_len)) #plot vertical line for each trigger
|
||||||
self.num = len(stim)
|
self.num = len(stim)
|
||||||
# plt.figure()
|
# plt.figure()
|
||||||
# plt.plot(ss.detrend(self.data[10,self.data_len-self.plot_len*
|
# plt.plot(ss.detrend(self.data[10,self.data_len-self.plot_len*
|
||||||
|
@ -1019,7 +980,6 @@ class AppForm(QMainWindow):
|
||||||
self.button4.setText("Start calibration")
|
self.button4.setText("Start calibration")
|
||||||
self.button4.setStyleSheet('')
|
self.button4.setStyleSheet('')
|
||||||
#par = self.thr_parameter
|
#par = self.thr_parameter
|
||||||
#Need to clean the figure to prepare is for a different type of plot
|
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
self.axesMap[i//2,i%2].cla()
|
self.axesMap[i//2,i%2].cla()
|
||||||
|
|
||||||
|
@ -1035,19 +995,30 @@ class AppForm(QMainWindow):
|
||||||
self.f1_cal, self.f2_cal, self.f3_cal, self.f4_cal, self.f5_cal, self.f6_cal = cals
|
self.f1_cal, self.f2_cal, self.f3_cal, self.f4_cal, self.f5_cal, self.f6_cal = cals
|
||||||
print(len(self.f1_cal))
|
print(len(self.f1_cal))
|
||||||
|
|
||||||
self.thr_1 = [np.min(self.f1_cal)-0.1*(np.max(self.f1_cal) - np.min(self.f1_cal)),
|
rThr = self.received_thr_values
|
||||||
np.max(self.f1_cal)+0.1*(np.max(self.f1_cal) - np.min(self.f1_cal))]
|
|
||||||
self.thr_2 = [np.min(self.f2_cal)-0.1*0.1*(np.max(self.f2_cal) - np.min(self.f2_cal)),
|
self.thrs = np.zeros([6,2])
|
||||||
np.max(self.f2_cal)+0.1*0.1*(np.max(self.f2_cal) - np.min(self.f2_cal))]
|
cals_temp = [self.f1_cal, self.f2_cal, self.f3_cal, self.f4_cal, self.f5_cal, self.f6_cal]
|
||||||
self.thr_3 = [np.min(self.f3_cal)-0.1*0.1*(np.max(self.f3_cal) - np.min(self.f3_cal)),
|
print(self.if_percentage)
|
||||||
np.max(self.f3_cal)+0.1*0.1*(np.max(self.f3_cal) - np.min(self.f3_cal))]
|
|
||||||
self.thr_4 = [np.min(self.f4_cal)-0.1*0.1*(np.max(self.f4_cal) - np.min(self.f4_cal)),
|
if all(np.array(self.if_percentage) == 0):
|
||||||
np.max(self.f4_cal)+0.1*0.1*(np.max(self.f4_cal) - np.min(self.f4_cal))]
|
self.thrs = [[rThr[0], rThr[1]], [rThr[2], rThr[3]], [rThr[4], rThr[5]],
|
||||||
self.thr_5 = [np.min(self.f5_cal)-0.1*0.1*(np.max(self.f5_cal) - np.min(self.f5_cal)),
|
[rThr[6], rThr[7]], [rThr[8], rThr[9]], [rThr[10], rThr[11]]]
|
||||||
np.max(self.f5_cal)+0.1*0.1*(np.max(self.f5_cal) - np.min(self.f5_cal))]
|
else:
|
||||||
self.thr_6 = [np.min(self.f6_cal)-0.1*0.1*(np.max(self.f6_cal) - np.min(self.f6_cal)),
|
for idx in range(len(cals_temp)):
|
||||||
np.max(self.f6_cal)+0.1*0.1*(np.max(self.f6_cal) - np.min(self.f6_cal))]
|
cal = cals_temp[idx]
|
||||||
self.thrs = [self.thr_1, self.thr_2, self.thr_3, self.thr_4, self.thr_5, self.thr_6]
|
if self.if_percentage[2*idx]:
|
||||||
|
self.thrs[idx, 0] = np.min(cal)-0.01*rThr[2*idx]*(np.max(cal) - np.min(cal))
|
||||||
|
print(np.min(cal), np.max(cal), rThr[2*idx])
|
||||||
|
else:
|
||||||
|
self.thrs[idx, 0] = rThr[2*idx]
|
||||||
|
|
||||||
|
if self.if_percentage[2*idx+1]:
|
||||||
|
self.thrs[idx, 1] = np.max(cal)+0.01*rThr[2*idx+1]*(np.max(cal) - np.min(cal))
|
||||||
|
else:
|
||||||
|
self.thrs[idx, 1] = rThr[2*idx+1]
|
||||||
|
print('ddddd', rThr[2*idx+1])
|
||||||
|
self.thr_1, self.thr_2, self.thr_3, self.thr_4, self.thr_5, self.thr_6 = self.thrs
|
||||||
|
|
||||||
#put axhlines
|
#put axhlines
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
|
@ -1096,35 +1067,12 @@ class AppForm(QMainWindow):
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
self.axesMap[i//2,i%2].set_ylim(min_zero(np.array(self.thrs[i]) +
|
self.axesMap[i//2,i%2].set_ylim(min_zero(np.array(self.thrs[i]) +
|
||||||
np.diff(np.array(self.thrs[i]))*np.array([-2,2])))
|
np.diff(np.array(self.thrs[i]))*np.array([-2,2])))
|
||||||
# self.axesMap[0,0].set_ylim(-1,1)
|
|
||||||
# self.axesMap[0,1].set_ylim(min_zero(np.array(self.thr_2) +
|
|
||||||
# np.diff(np.array(self.thr_2))*np.array([-2,2])))
|
|
||||||
# self.axesMap[1,0].set_ylim(min_zero(np.array(self.thr_1) +
|
|
||||||
# np.diff(np.array(self.thr_1))*np.array([-2,2])))
|
|
||||||
# self.axesMap[1,1].set_ylim(min_zero(np.array(self.thr_3) +
|
|
||||||
# np.diff(np.array(self.thr_3))*np.array([-2,2])))
|
|
||||||
# self.axesMap[2,0].set_ylim(min_zero(np.array(self.thr_5) +
|
|
||||||
# np.diff(np.array(self.thr_5))*np.array([-2,2])))
|
|
||||||
# self.axesMap[2,1].set_ylim(min_zero(np.array(self.thr_6) +
|
|
||||||
# np.diff(np.array(self.thr_6))*np.array([-2,2])))
|
|
||||||
else:
|
else:
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
self.axesMap[i//2,i%2].set_ylim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
self.axesMap[i//2,i%2].set_ylim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
||||||
# self.axesMap[0,0].set_ylim(self.measures_x_lims[0,0],self.measures_x_lims[0,1])
|
|
||||||
# self.axesMap[0,1].set_ylim(self.measures_x_lims[1,0],self.measures_x_lims[1,1])
|
|
||||||
# self.axesMap[1,0].set_ylim(self.measures_x_lims[2,0],self.measures_x_lims[2,1])
|
|
||||||
# self.axesMap[1,1].set_ylim(self.measures_x_lims[3,0],self.measures_x_lims[3,1])
|
|
||||||
# self.axesMap[2,0].set_ylim(self.measures_x_lims[4,0],self.measures_x_lims[4,1])
|
|
||||||
# self.axesMap[2,1].set_ylim(self.measures_x_lims[5,0],self.measures_x_lims[5,1])
|
|
||||||
else:
|
else:
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
self.axesMap[i//2,i%2].set_xlim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
self.axesMap[i//2,i%2].set_xlim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
||||||
# self.axesMap[0,0].set_xlim(self.measures_x_lims[0,0],self.measures_x_lims[0,1])
|
|
||||||
# self.axesMap[0,1].set_xlim(self.measures_x_lims[1,0],self.measures_x_lims[1,1])
|
|
||||||
# self.axesMap[1,0].set_xlim(self.measures_x_lims[2,0],self.measures_x_lims[2,1])
|
|
||||||
# self.axesMap[1,1].set_xlim(self.measures_x_lims[3,0],self.measures_x_lims[3,1])
|
|
||||||
# self.axesMap[2,0].set_xlim(self.measures_x_lims[4,0],self.measures_x_lims[4,1])
|
|
||||||
# self.axesMap[2,1].set_xlim(self.measures_x_lims[5,0],self.measures_x_lims[5,1])
|
|
||||||
self.canvasMap.draw()
|
self.canvasMap.draw()
|
||||||
|
|
||||||
def calibration_process(self):
|
def calibration_process(self):
|
||||||
|
@ -1136,8 +1084,7 @@ class AppForm(QMainWindow):
|
||||||
#calculate fft
|
#calculate fft
|
||||||
self.S = abs(np.fft.rfft(self.second_to_analyze))
|
self.S = abs(np.fft.rfft(self.second_to_analyze))
|
||||||
|
|
||||||
#this is a bit shady, but should work. check it out if doesn't!
|
|
||||||
#do dwt only if any features requires it
|
|
||||||
if any(feature_num in self.used_features for feature_num in [8,9,10,11]):
|
if any(feature_num in self.used_features for feature_num in [8,9,10,11]):
|
||||||
self.dwt = pywt.wavedec(self.second_to_analyze, 'db1', level=8)
|
self.dwt = pywt.wavedec(self.second_to_analyze, 'db1', level=8)
|
||||||
|
|
||||||
|
@ -1168,8 +1115,7 @@ class AppForm(QMainWindow):
|
||||||
self.cals = [self.f1_cal, self.f2_cal, self.f3_cal, self.f4_cal, self.f5_cal,
|
self.cals = [self.f1_cal, self.f2_cal, self.f3_cal, self.f4_cal, self.f5_cal,
|
||||||
self.f6_cal]
|
self.f6_cal]
|
||||||
|
|
||||||
#!!! Temporary, it's wrong but it's overwritten later. It's needed to check if all features are used,
|
|
||||||
#but there could be more optimal solution. Remove it at some point!
|
|
||||||
self.thr_1 = [np.min(self.f1_cal)-0.1*np.min(self.f1_cal),
|
self.thr_1 = [np.min(self.f1_cal)-0.1*np.min(self.f1_cal),
|
||||||
np.max(self.f1_cal)+0.1*np.max(self.f1_cal)]
|
np.max(self.f1_cal)+0.1*np.max(self.f1_cal)]
|
||||||
self.thr_2 = [np.min(self.f2_cal)-0.1*np.min(self.f2_cal),
|
self.thr_2 = [np.min(self.f2_cal)-0.1*np.min(self.f2_cal),
|
||||||
|
@ -1203,12 +1149,6 @@ class AppForm(QMainWindow):
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
if not all(np.isnan(self.thrs[i])):
|
if not all(np.isnan(self.thrs[i])):
|
||||||
self.axesMap[i//2,i%2].set_xlim(self.thrs[i][0], self.thrs[i][1])
|
self.axesMap[i//2,i%2].set_xlim(self.thrs[i][0], self.thrs[i][1])
|
||||||
# self.axesMap[0,0].set_xlim(self.thr_1[0], self.thr_1[1])
|
|
||||||
# self.axesMap[0,1].set_xlim(self.thr_2[0], self.thr_2[1])
|
|
||||||
# self.axesMap[1,0].set_xlim(self.thr_3[0], self.thr_3[1])
|
|
||||||
# self.axesMap[1,1].set_xlim(self.thr_4[0], self.thr_4[1])
|
|
||||||
# self.axesMap[2,0].set_xlim(self.thr_5[0], self.thr_5[1])
|
|
||||||
# self.axesMap[2,1].set_xlim(self.thr_6[0], self.thr_6[1])
|
|
||||||
|
|
||||||
elif len(self.f4_cal)==1:
|
elif len(self.f4_cal)==1:
|
||||||
for line_idx in range(len(self.linetemps)):
|
for line_idx in range(len(self.linetemps)):
|
||||||
|
@ -1224,22 +1164,10 @@ class AppForm(QMainWindow):
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
if not all(np.isnan(self.thrs[i])):
|
if not all(np.isnan(self.thrs[i])):
|
||||||
self.axesMap[i//2,i%2].set_xlim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
self.axesMap[i//2,i%2].set_xlim(self.measures_x_lims[i,0],self.measures_x_lims[i,1])
|
||||||
# self.axesMap[0,0].set_xlim(self.measures_x_lims[0,0],self.measures_x_lims[0,1])
|
|
||||||
# self.axesMap[0,1].set_xlim(self.measures_x_lims[1,0],self.measures_x_lims[1,1])
|
|
||||||
# self.axesMap[1,0].set_xlim(self.measures_x_lims[2,0],self.measures_x_lims[2,1])
|
|
||||||
# self.axesMap[1,1].set_xlim(self.measures_x_lims[3,0],self.measures_x_lims[3,1])
|
|
||||||
# self.axesMap[2,0].set_xlim(self.measures_x_lims[4,0],self.measures_x_lims[4,1])
|
|
||||||
# self.axesMap[2,1].set_xlim(self.measures_x_lims[5,0],self.measures_x_lims[5,1])
|
|
||||||
|
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
if not all(np.isnan(self.thrs[i])):
|
if not all(np.isnan(self.thrs[i])):
|
||||||
self.axesMap[i//2,i%2].set_ylim(self.measures_y_lims[i,0],self.measures_y_lims[i,1])
|
self.axesMap[i//2,i%2].set_ylim(self.measures_y_lims[i,0],self.measures_y_lims[i,1])
|
||||||
# self.axesMap[0,0].set_ylim(self.measures_y_lims[0,0],self.measures_y_lims[0,1])
|
|
||||||
# self.axesMap[0,1].set_ylim(self.measures_y_lims[1,0],self.measures_y_lims[1,1])
|
|
||||||
# self.axesMap[1,0].set_ylim(self.measures_y_lims[2,0],self.measures_y_lims[2,1])
|
|
||||||
# self.axesMap[1,1].set_ylim(self.measures_y_lims[3,0],self.measures_y_lims[3,1])
|
|
||||||
# self.axesMap[2,0].set_ylim(self.measures_y_lims[4,0],self.measures_y_lims[4,1])
|
|
||||||
# self.axesMap[2,1].set_ylim(self.measures_y_lims[5,0],self.measures_y_lims[5,1])
|
|
||||||
|
|
||||||
# self.linetemps = [self.linetemp1, self.linetemp2, self.linetemp3, self.linetemp4,
|
# self.linetemps = [self.linetemp1, self.linetemp2, self.linetemp3, self.linetemp4,
|
||||||
# self.linetemp5, self.linetemp6]
|
# self.linetemp5, self.linetemp6]
|
||||||
|
@ -1280,16 +1208,10 @@ class AppForm(QMainWindow):
|
||||||
tick.label1.set_visible(False)
|
tick.label1.set_visible(False)
|
||||||
tick.label2.set_visible(False)
|
tick.label2.set_visible(False)
|
||||||
|
|
||||||
# for tick in self.axes.yaxis.get_major_ticks():
|
|
||||||
# tick.tick1line.set_visible(False)
|
|
||||||
# tick.tick2line.set_visible(False)
|
|
||||||
# tick.label1.set_visible(False)
|
|
||||||
# tick.label2.set_visible(False)
|
|
||||||
|
|
||||||
self.axes.set_yticks(np.arange(1, (self.num_of_ch)*1.01, 1))
|
self.axes.set_yticks(np.arange(1, (self.num_of_ch)*1.01, 1))
|
||||||
self.axes.set_yticklabels(self.ch_names[::-1])
|
self.axes.set_yticklabels(self.ch_names[::-1])
|
||||||
|
|
||||||
self.axes.set_xticks(np.arange(self.Fs,self.plot_len*self.Fs, self.Fs))
|
self.axes.set_xticks(np.arange(int(self.Fs/self.plot_len), self.Fs, int(self.Fs/self.plot_len))) #self.plot_len*
|
||||||
self.axes.grid(True)
|
self.axes.grid(True)
|
||||||
|
|
||||||
self.canvas = FigureCanvas(self.fig)
|
self.canvas = FigureCanvas(self.fig)
|
||||||
|
@ -1304,19 +1226,6 @@ class AppForm(QMainWindow):
|
||||||
self.axesMap[i//2,i%2].set_title(self.Titles[i], fontsize=9)
|
self.axesMap[i//2,i%2].set_title(self.Titles[i], fontsize=9)
|
||||||
self.axesMap[i//2,i%2].set_ylabel(self.unit_label[i], fontsize=self.labels_size, rotation=270)
|
self.axesMap[i//2,i%2].set_ylabel(self.unit_label[i], fontsize=self.labels_size, rotation=270)
|
||||||
|
|
||||||
# self.axesMap[0,0].set_title(self.Titles[0], fontsize=9)
|
|
||||||
# self.axesMap[0,1].set_title(self.Titles[1], fontsize=9)
|
|
||||||
# self.axesMap[1,0].set_title(self.Titles[2], fontsize=9)
|
|
||||||
# self.axesMap[1,1].set_title(self.Titles[3], fontsize=9)
|
|
||||||
# self.axesMap[2,0].set_title(self.Titles[4], fontsize=9)
|
|
||||||
# self.axesMap[2,1].set_title(self.Titles[5], fontsize=9)
|
|
||||||
|
|
||||||
# self.axesMap[0,0].set_ylabel(self.unit_label[0], fontsize=self.labels_size, rotation=270)
|
|
||||||
# self.axesMap[0,1].set_ylabel(self.unit_label[1], fontsize=self.labels_size, rotation=270)
|
|
||||||
# self.axesMap[1,0].set_ylabel(self.unit_label[2], fontsize=self.labels_size, rotation=270)
|
|
||||||
# self.axesMap[1,1].set_ylabel(self.unit_label[3], fontsize=self.labels_size, rotation=270)
|
|
||||||
# self.axesMap[2,0].set_ylabel(self.unit_label[4], fontsize=self.labels_size, rotation=270)
|
|
||||||
# self.axesMap[2,1].set_ylabel(self.unit_label[5], fontsize=self.labels_size, rotation=270)
|
|
||||||
|
|
||||||
label_size = 7
|
label_size = 7
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
|
@ -1333,7 +1242,7 @@ class AppForm(QMainWindow):
|
||||||
self.line[i], = self.axes.plot([] , color = 'black', linewidth=0.4)
|
self.line[i], = self.axes.plot([] , color = 'black', linewidth=0.4)
|
||||||
else:
|
else:
|
||||||
self.line[i], = self.axes.plot([] , color = 'silver', linewidth=0.3)
|
self.line[i], = self.axes.plot([] , color = 'silver', linewidth=0.3)
|
||||||
self.axes.set_xlim(0, self.plot_len*self.Fs)
|
self.axes.set_xlim(0, self.Fs) #self.plot_len*
|
||||||
self.axes.set_ylim(0, (self.num_of_ch+1)*1)
|
self.axes.set_ylim(0, (self.num_of_ch+1)*1)
|
||||||
#self.axes.axvspan((self.plot_len-1)*self.Fs,
|
#self.axes.axvspan((self.plot_len-1)*self.Fs,
|
||||||
# self.plot_len*self.size_of_up, alpha=0.3, color='lightcoral')
|
# self.plot_len*self.size_of_up, alpha=0.3, color='lightcoral')
|
||||||
|
@ -1350,14 +1259,7 @@ class AppForm(QMainWindow):
|
||||||
|
|
||||||
self.canvas.draw()
|
self.canvas.draw()
|
||||||
self.canvasMap.draw() #update canvas
|
self.canvasMap.draw() #update canvas
|
||||||
#NOT NEEDED
|
|
||||||
for i in range(self.num_of_ch):
|
|
||||||
self.axbackground = self.canvas.copy_from_bbox(self.axes.bbox)
|
|
||||||
|
|
||||||
# texts = []
|
|
||||||
# for ind,name in enumerate(self.ch_names):
|
|
||||||
# texts.append(self.axes.text(-0.03,1-(ind+1)/(len(self.ch_names)+1),
|
|
||||||
# name, transform=self.axes.transAxes, color='r', fontsize=13))
|
|
||||||
|
|
||||||
self.button1 = QPushButton("&Settings")
|
self.button1 = QPushButton("&Settings")
|
||||||
self.button1.setCheckable(False)
|
self.button1.setCheckable(False)
|
||||||
|
@ -1410,7 +1312,7 @@ class Ui(QMainWindow):
|
||||||
def __init__(self, main_file):
|
def __init__(self, main_file):
|
||||||
self.main_file = main_file
|
self.main_file = main_file
|
||||||
super(Ui, self).__init__()
|
super(Ui, self).__init__()
|
||||||
uic.loadUi('soft2.ui', self)
|
uic.loadUi('settings/soft2.ui', self)
|
||||||
self.show()
|
self.show()
|
||||||
self.load_button = self.findChild(QPushButton, 'pushButton')
|
self.load_button = self.findChild(QPushButton, 'pushButton')
|
||||||
self.load_button.clicked.connect(self.get_file)
|
self.load_button.clicked.connect(self.get_file)
|
||||||
|
@ -1458,11 +1360,7 @@ class Ui(QMainWindow):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
app.setQuitOnLastWindowClosed(True)
|
||||||
form = First_window(AppForm) #AppForm()
|
form = First_window(AppForm) #AppForm()
|
||||||
form.show()
|
form.show()
|
||||||
app.exec_()
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
# cut time different from both sides
|
|
||||||
# some deafult settings. Maybe remember last configuration?
|
|
||||||
# EMG and EOG - none, more than one?
|
|
||||||
# change names to final names
|
|
|
@ -1,2 +1,2 @@
|
||||||
full_cap_file_path: /home/adamr/Documents/PYTHON/TMS TV/Up to date version Feb 23/easycap-M10_63_NO.txt
|
full_cap_file_path: C:/Users/Basics/Desktop/new_super_important_study/TMS Trains/Up to date version/estimo-master (1)/estimo-master/settings/easycap-M10_63_NO.txt
|
||||||
cap_file_path: /home/adamr/Documents/PYTHON/TMS TV/Up to date version Feb 23/easycap-M10_16_NO.txt
|
cap_file_path: C:/Users/Basics/Desktop/new_super_important_study/TMS Trains/Up to date version/estimo-master (1)/estimo-master/settings/easycap-M10_16_NO.txt
|
207
README.md
207
README.md
|
@ -1,92 +1,187 @@
|
||||||
# EStiMo
|
# EStiMo
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Features](#features)
|
||||||
|
- [How It Works](#how-it-works)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Running EStiMo for the First Time](#running-estimo-for-the-first-time)
|
||||||
|
- [Establishing Connection with NeurOne or Brain Products](#establishing-connection-with-neurone-or-brain-products)
|
||||||
|
- [Start EStiMo](#start-estimo)
|
||||||
|
- [Configuration Window](#configuration-window)
|
||||||
|
- [Calibration Process](#calibration-process)
|
||||||
|
- [Running Main Recording](#running-main-recording)
|
||||||
|
- [Configuration and Electrode Montage Files](#configuration-and-electrode-montage-files)
|
||||||
|
- [TMS Protocol Structure](#tms-protocol-structure)
|
||||||
|
- [Electrode Selection Structure](#electrode-selection-structure)
|
||||||
|
- [Spatial Locations File Structure](#spatial-locations-file-structure)
|
||||||
|
- [Montage File Structure](#montage-file-structure)
|
||||||
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
## Open-source toolbox for EEG-based Stimulation Monitoring (EStiMo) of brain states during TMS burst delivery
|
||||||
|
https://doi.org/10.1016/j.brs.2024.12.001
|
||||||
|
|
||||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
## Overview:
|
||||||
|
EStiMo is an open-source Python toolbox for real-time EEG monitoring during Transcranial Magnetic Stimulation (TMS) sessions. It performs real-time analysis of Electroencephalography (EEG) signals, computing features online and visually representing them via a user-friendly graphical interface. These computations occur during the intervals between TMS pulse trains, providing valuable insights into brain activity.
|
||||||
|
|
||||||
## Add your files
|
### Features:
|
||||||
|
* Real-Time EEG Analysis: Visualizes cortical activity during TMS sessions.
|
||||||
|
* Flexible Customization: Allows the use of custom montages, channel configurations, and feature settings.
|
||||||
|
* Seamless Integration: Compatible with NeurOne and Brain Products systems for data acquisition.
|
||||||
|
|
||||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
----------------------------------------------------------------------
|
||||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
|
||||||
|
|
||||||
|
## How It Works:
|
||||||
|
EStiMo operates in three main steps:
|
||||||
|
|
||||||
|
* Data Acquisition: EEG signals are streamed from NeurOne or Brain Products RDA systems.
|
||||||
|
* Feature Computation: Real-time calculation of up to six EEG features.
|
||||||
|
* Visualization: Processed data is visualized in an interactive GUI for easy monitoring.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
EStiMo is designed to be conveniently portable and as such, does not necessitate a typical installation procedure. To use the software, ensure a Python 3 environment (recommended: Python 3.9) and follow these steps:
|
||||||
|
|
||||||
|
Clone the repository:
|
||||||
```
|
```
|
||||||
cd existing_repo
|
git clone https://nugit.drcmr.dk/Tools/EStiMo.git
|
||||||
git remote add origin https://git.drcmr.dk/adamr/estimo.git
|
cd EStiMo
|
||||||
git branch -M main
|
```
|
||||||
git push -uf origin main
|
Install dependencies:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Integrate with your tools
|
Troubleshooting:
|
||||||
|
If you encounter issues with dependencies, ensure that your Python environment is correctly configured (consider using virtual environments).
|
||||||
|
|
||||||
- [ ] [Set up project integrations](https://git.drcmr.dk/adamr/estimo/-/settings/integrations)
|
|
||||||
|
|
||||||
## Collaborate with your team
|
----------------------------------------------------------------------
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
|
||||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
|
||||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
|
||||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
|
||||||
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
|
||||||
|
|
||||||
## Test and Deploy
|
### Running EStiMo for the First Time
|
||||||
|
|
||||||
Use the built-in continuous integration in GitLab.
|
#### Establishing Connection with NeurOne OR Brain Products Systems (RDA):
|
||||||
|
|
||||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
__NeuroOne:__
|
||||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
The EStiMo software connects to NeurOne utilizing a serial port. The application anticipates data as input from the device. The last channel is intended to function as a stimulus trigger channel, which returns a value of 0 in the absence of triggers and alternate values when triggers are present. For further specifics regarding the connection setup, kindly refer to Bittium NeurOne real-time DigiOut functionality of NeurOne user manual (https://www.bittium.com/medical/bittium-neurone/).
|
||||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
|
||||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
|
||||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
# Editing this README
|
|
||||||
|
|
||||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
__Brain Product:__
|
||||||
|
The software forms a connection with Brain Products systems via the Remote Data Access (RDA) protocol, which is an integral part of the Brain Products Recorder. Hence, the Recorder is a necessary requirement. The connection is made via the ethernet port. Detailed information about the RDA protocol and connection process can be found in the Brain Product user manual (https://pressrelease.brainproducts.com/real-time-eeg/).
|
||||||
|
|
||||||
## Suggestions for a good README
|
__Quick connection setup__: on the ___server computer___ where Brain Recorder software is running, Enable the RDA option in Configuration > Preferences …, select the Remote Data Access tab and tick the Enable Remote Data Access.
|
||||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
Next, to enable receiving the data through Ethernet cable (maybe known as LAN cable), on the ___client computer___ which EstiMo will run, please go to Control Panel > Network and Sharing Center and open the Network Connection Details of the newly established Ethernet Connection. You need to make sure that the IPv4 address of the client computer is same as server computer. Do not change the IPv4 address on the server computer, only change it on the client computer and make sure they are following the same IPv4 address.
|
||||||
|
|
||||||
## Name
|
|
||||||
Choose a self-explaining name for your project.
|
|
||||||
|
|
||||||
## Description
|
default configurations for the TMS_protocol.txt and electrode_selection.txt files for first-time users
|
||||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
|
||||||
|
|
||||||
## Badges
|
#### Start EStiMo:
|
||||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
|
||||||
|
|
||||||
## Visuals
|
```
|
||||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
python EStiMo_GUI.py
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
##### 1. Configuration Window:
|
||||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
|
||||||
|
|
||||||
## Roadmap
|
Upon execution, the initial settings window will appear. These settings can be manually altered, or a configuration file (a txt file with a specific structure) could be imported instead. The montage can be adjusted by importing a csv file containing a matrix of size (n_channels, n_channels).
|
||||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|
||||||
|
|
||||||
## Contributing
|
EEG channels can be manually selected or deselected to include or exclude them from the feature extraction process (please check [Configuration and Electrode Montage Files](#configuration-and-electrode-montage-files)). Selected channels are highlighted in blue.
|
||||||
State if you are open to contributions and what your requirements are for accepting them.
|
On the right side of the interface, the channels are displayed. Their positions correspond to the actual positions on the cap, guided by the file indicated at the top of the screen. This layout can be changed if needed. It should be noted that when changing, two files must be selected: one depicting the original cap layout, and the other containing only the channels used for feature computation. If all channels are needed, the same file can be loaded twice.
|
||||||
|
|
||||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|

|
||||||
|
|
||||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
At the top of the window, the "Features and connection" bar can be selected, offering a choice of features for the connection setup. Once the settings are appropriately adjusted, the "Run program" button can be clicked to start the software.
|
||||||
|
Up to 6 features among X number of features can be selected. The Threshold is also adjustable on the right side of each feature. Details are available in the EstiMo publication. (https://doi.org/10.1016/j.brs.2024.12.001)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
After selecting the preferred features and establishing the connection, the user can start streaming by clicking the 'Run Program' button.
|
||||||
|
|
||||||
|
|
||||||
|
##### 2. Calibration Process
|
||||||
|
Perform a baseline recording of EEG data during rest. The system will compute average values for each feature. The feature measurements are averaged across channels and displayed as plots on the right side of the screen.
|
||||||
|
Note: Recalibration may be required if the montage, channels, or environment changes.
|
||||||
|
The software includes a calibration function. During this process, it looks at the data to set a baseline for all used features. During calibration, the software calculates the mean value of all channels every second. Once calibration is over, it sets the thresholds (by default) at 10% of the distance between min a max, over, and below the maximum and minimum value recorded during the calibration.
|
||||||
|
|
||||||
|
|
||||||
|
##### 3. Running main recording
|
||||||
|
Following the calibration phase, the main recording and feature measurements can start by Start/Stop button. Each plot's background will turn red if any of the thresholds are crossed. Deatils can be found in the EstiMo paper. (https://doi.org/10.1016/j.brs.2024.12.001).
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration and Electrode Montage Files:
|
||||||
|
### TMS_Protocol.txt structure
|
||||||
|
This file includes several settings that can be changed by adjusting the value that follows the colon. The settings you can change are:
|
||||||
|
|
||||||
|
- time_between_trains: Specifies the interval between consecutive trains in seconds.
|
||||||
|
- cut_time: Defines the duration (in seconds) of the signal segment to ignore between trains. The signal is cut symmetrically, removing half of this value from both ends. This value should be set in seconds.
|
||||||
|
- number_of_channels: Sets the total number of EEG channels used. This would be the EEG channels excluding EOG and EMG channels. For NeuroOne system exclude trigger indicator channels as well. For BrainProducts system: number of streamed channels from Brain Recorder - 2
|
||||||
|
- number_of_lines: Indicates the number of past segment measurements displayed during the readout phase.
|
||||||
|
- eog_channel: Specifies the index of the EOG (electrooculogram) channel. Negative indices can be used to count from the end. Note: The last channel (-1) is always reserved for the trigger indicator.
|
||||||
|
- emg_channel: Specifies the index of the EMG (electromyogram) channel, following the same indexing rules as eog_channel.
|
||||||
|
- included_channels: These are the indexes of channels that are used to calculate features, from 0 (which is the first channel) to N (N represents the number of EEG channels). EOG, EMG, and trigger indicator channels are not included. Indexes correspond to the order of channels streamed from the EEG system. This should be filled as a list of integers.
|
||||||
|
- names: These are the names of the channels that are streamed. This should be filled as a list of integers or strings.
|
||||||
|
- alpha_range: Defines the frequency range for the alpha band (in Hz) as a list of integers (e.g., [8, 15]).
|
||||||
|
- beta_range: Defines the frequency range for the beta band (in Hz) as a list of integers (e.g., [16, 30]).
|
||||||
|
- theta_range: Defines the frequency range for the theta band (in Hz) as a list of integers (e.g., [4, 8]).
|
||||||
|
- expected_triggers: Specifies the number of triggers expected within a single train.
|
||||||
|
- expected_time: This is the time of the single train, measured in milliseconds.
|
||||||
|
|
||||||
|
|
||||||
|
> ✅ **Example**:
|
||||||
|
```
|
||||||
|
time_between_trains: 8
|
||||||
|
cut_time: 1
|
||||||
|
number_of_channels: 18
|
||||||
|
number_of_lines: 4
|
||||||
|
eog_channel: -3
|
||||||
|
emg_channel: -2
|
||||||
|
included_channels: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
|
||||||
|
names: [1,3,7,8,9,11,14,17,19,23,26,29,32,43,50,52, "EOG", "EMG"]
|
||||||
|
alpha_range: [8,15]
|
||||||
|
beta_range: [16,30]
|
||||||
|
theta_range: [4,8]
|
||||||
|
threshold_parameter: 2
|
||||||
|
expected_triggers: 10
|
||||||
|
expected_time: 2000
|
||||||
|
plot_len: 4
|
||||||
|
```
|
||||||
|
|
||||||
|
> ✅ **Example**: From 10-10 EEG montage, only 12 channels are used. In that case, as a first file the whole 10-10 montage should be loaded, and as a second a file containing only chosen 12 electrodes.
|
||||||
|
|
||||||
|
### Structure of the Electrode_selection.txt
|
||||||
|
You can set paths for the files that contain spatial information in this file. The following are the available settings:
|
||||||
|
- full_cap_file_path: This is the path to the file showing the spatial location for the full cap.
|
||||||
|
- cap_file_path: This is the path to the file showing the spatial location only for the streamed channels.
|
||||||
|
|
||||||
|
The requirement for both files is purely based on practical reasons to make potential edits to the protocol easier. It's designed for users who only want to use a selected number of channels from a larger EEG cap montage.
|
||||||
|
|
||||||
|
> :warning: **Please note**: If you're using the entire cap, set both settings to the same path. If you don't have access to any of these files, use one of them for both options. However, this might require manual adjustment in the program settings.
|
||||||
|
|
||||||
|
### Spatial Locations file structure
|
||||||
|
The files with spatial locations are managed using the 'mne' library from Python. The function called mne.channels.read_custom_montage is utilized for this purpose. The coordinates are transformed into 2D space using the same library. The way this function reads the file depends on the file format:
|
||||||
|
```
|
||||||
|
eeglab: '.loc', '.locs', '.eloc'
|
||||||
|
hydrocel: '.sfp'
|
||||||
|
matlab: '.csd'
|
||||||
|
asa electrode: '.elc'
|
||||||
|
generic (Theta-phi in degrees): '.txt'
|
||||||
|
standard BESA spherical: '.elp'
|
||||||
|
brainvision: '.bvef'
|
||||||
|
```
|
||||||
|
> :warning: **Please note**: The software was tested only using generic format.
|
||||||
|
|
||||||
|
### Montage file structure
|
||||||
|
The montage file is an N by N array that functions as an array multiplying the signal (array multiplication). This means the signal can be adjusted according to the user's needs, for example by setting a specific type of reference. The file should be in .csv format, and values should be separated by a comma. The indexes follow the Python standard, which is horizontally from left to right and vertically from top to bottom.
|
||||||
|
|
||||||
## Authors and acknowledgment
|
|
||||||
Show your appreciation to those who have contributed to the project.
|
|
||||||
|
|
||||||
## License
|
|
||||||
For open source projects, say how it is licensed.
|
|
||||||
|
|
||||||
## Project status
|
|
||||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
|
||||||
|
|
BIN
__pycache__/FirstWindow.cpython-38.pyc
Normal file
BIN
__pycache__/FirstWindow.cpython-38.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Functions.cpython-38.pyc
Normal file
BIN
__pycache__/Functions.cpython-38.pyc
Normal file
Binary file not shown.
BIN
__pycache__/NeurOne.cpython-38.pyc
Normal file
BIN
__pycache__/NeurOne.cpython-38.pyc
Normal file
Binary file not shown.
BIN
__pycache__/RDA.cpython-38.pyc
Normal file
BIN
__pycache__/RDA.cpython-38.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Waiting.cpython-38.pyc
Normal file
BIN
__pycache__/Waiting.cpython-38.pyc
Normal file
Binary file not shown.
|
@ -15,6 +15,23 @@ import threading
|
||||||
import queue
|
import queue
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
"""Packets are received every 20 ms in the size that it fits the sampling rate
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
for 1000 Hz packet size will be 20, because 20*50=1000
|
||||||
|
for 2500 Hz packet size will be 50, because 50*50=2500
|
||||||
|
for 50 kHz it will be 1000, because 1000*50=50000
|
||||||
|
"""
|
||||||
|
def average(arr, n, mode='mean'):
|
||||||
|
if mode=='max':
|
||||||
|
end = n * int(len(arr)/n)
|
||||||
|
return np.max(arr[:end].reshape(-1, n), 1)
|
||||||
|
arr = arr.T
|
||||||
|
data_raw_new = np.zeros((arr.shape[0], int(arr.shape[1]/n)))
|
||||||
|
for i in range(arr.shape[0]):
|
||||||
|
a = arr[i]
|
||||||
|
data_raw_new[i,:] = a.reshape(-1, n).mean(1)
|
||||||
|
return data_raw_new.T
|
||||||
|
|
||||||
# Marker class for storing marker information
|
# Marker class for storing marker information
|
||||||
class Marker:
|
class Marker:
|
||||||
|
@ -146,7 +163,6 @@ def sampleLoop(obj):
|
||||||
elif msgtype == 4:
|
elif msgtype == 4:
|
||||||
# Data message, extract data and markers
|
# Data message, extract data and markers
|
||||||
(block, points, markerCount, data, markers) = GetData(rawdata, channelCount)
|
(block, points, markerCount, data, markers) = GetData(rawdata, channelCount)
|
||||||
|
|
||||||
if block!=0:
|
if block!=0:
|
||||||
ds=block-oldblock
|
ds=block-oldblock
|
||||||
if ds!=1:
|
if ds!=1:
|
||||||
|
@ -174,7 +190,13 @@ def sampleLoop(obj):
|
||||||
# Put data at the end of actual buffer
|
# Put data at the end of actual buffer
|
||||||
data_array = data1s.reshape([int(len(data1s)/channelCount), channelCount]) * np.array(resolutions)
|
data_array = data1s.reshape([int(len(data1s)/channelCount), channelCount]) * np.array(resolutions)
|
||||||
data_array = np.vstack([data_array.T, marker_sig]).T #isn't that too slow?
|
data_array = np.vstack([data_array.T, marker_sig]).T #isn't that too slow?
|
||||||
obj.updateRingBuffer(data_array,block)
|
if obj.avgPackets:
|
||||||
|
resampling_coef = int((len(data)/channelCount)/20)
|
||||||
|
data1=average(data_array, resampling_coef, 'mean')
|
||||||
|
data1[:,-1]=average(data_array[:,-1], resampling_coef, 'max')
|
||||||
|
obj.updateRingBuffer(data1,block)
|
||||||
|
else:
|
||||||
|
obj.updateRingBuffer(data_array,block)
|
||||||
data1s = []
|
data1s = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,7 +216,7 @@ def sampleLoop(obj):
|
||||||
|
|
||||||
class RDA():
|
class RDA():
|
||||||
def __init__(self,ip='127.0.0.1', port=51244, buffersize=2**10, sendqueue=False,
|
def __init__(self,ip='127.0.0.1', port=51244, buffersize=2**10, sendqueue=False,
|
||||||
si=1/1000, ringbuffersize = 2**12, avgPackets=False):
|
si=1/1000, ringbuffersize = 2**12, avgPackets=True):
|
||||||
# Create a tcpip socket
|
# Create a tcpip socket
|
||||||
#con = socket(AF_INET, SOCK_STREAM)
|
#con = socket(AF_INET, SOCK_STREAM)
|
||||||
# Connect to recorder host via 32Bit RDA-port
|
# Connect to recorder host via 32Bit RDA-port
|
3
connection/__init__.py
Normal file
3
connection/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import sys, os
|
||||||
|
# sys.path.append("..")
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
BIN
connection/__pycache__/NeurOne.cpython-38.pyc
Normal file
BIN
connection/__pycache__/NeurOne.cpython-38.pyc
Normal file
Binary file not shown.
BIN
connection/__pycache__/RDA.cpython-38.pyc
Normal file
BIN
connection/__pycache__/RDA.cpython-38.pyc
Normal file
Binary file not shown.
BIN
connection/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
connection/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
9
requirements.txt
Normal file
9
requirements.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
matplotlib
|
||||||
|
mne
|
||||||
|
numpy
|
||||||
|
scipy
|
||||||
|
pandas
|
||||||
|
datetime
|
||||||
|
PyWavelets
|
||||||
|
cycler
|
||||||
|
PyQt5
|
2
settings/Electrode_selection.txt
Normal file
2
settings/Electrode_selection.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
full_cap_file_path: settings/easycap-M10_63_NO.txt
|
||||||
|
cap_file_path: settings/easycap-M10_16_NO.txt
|
|
@ -12,3 +12,4 @@ theta_range: [4,8]
|
||||||
threshold_parameter: 2
|
threshold_parameter: 2
|
||||||
expected_triggers: 10
|
expected_triggers: 10
|
||||||
expected_time: 2000
|
expected_time: 2000
|
||||||
|
plot_len: 4
|
|
@ -6,9 +6,10 @@ Created on Mon Jan 24 11:26:23 2022
|
||||||
"""
|
"""
|
||||||
import mne
|
import mne
|
||||||
import os, sys, traceback, time
|
import os, sys, traceback, time
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (QMainWindow, QFileDialog, QMessageBox, QCheckBox, QLineEdit, QWidget, QPushButton,
|
from PyQt5.QtWidgets import (QMainWindow, QFileDialog, QMessageBox, QCheckBox, QLineEdit, QWidget, QPushButton,
|
||||||
QLabel, QHBoxLayout, QGridLayout, QAction, QApplication, QDialog, QDialogButtonBox,
|
QLabel, QHBoxLayout, QGridLayout, QAction, QApplication, QDialog, QDialogButtonBox,
|
||||||
QVBoxLayout, QFrame, QTabWidget, QComboBox, QScrollArea)
|
QVBoxLayout, QFrame, QTabWidget, QComboBox, QScrollArea, QFormLayout)
|
||||||
from PyQt5.QtCore import QTimer, Qt
|
from PyQt5.QtCore import QTimer, Qt
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
|
@ -21,14 +22,13 @@ if sys.platform=='darwin':
|
||||||
#from multiprocessing import Queue as StupidNotWorkingQueue
|
#from multiprocessing import Queue as StupidNotWorkingQueue
|
||||||
else:
|
else:
|
||||||
from multiprocessing import Process, Queue, Value
|
from multiprocessing import Process, Queue, Value
|
||||||
import NeurOne
|
from connection import NeurOne, RDA
|
||||||
import RDA
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from mne.channels.layout import _find_topomap_coords as get_pos
|
from mne.channels.layout import _find_topomap_coords as get_pos
|
||||||
import json
|
import json
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import ctypes
|
||||||
|
|
||||||
if sys.platform=='darwin':
|
if sys.platform=='darwin':
|
||||||
from multiprocessing.queues import Queue as QueueOld
|
from multiprocessing.queues import Queue as QueueOld
|
||||||
|
@ -225,15 +225,15 @@ class First_window(QMainWindow):
|
||||||
self.setWindowTitle('EStiMo Configuration')
|
self.setWindowTitle('EStiMo Configuration')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cap_loc_file = pd.read_csv('Electrode_selection.txt', sep=':', header=None)
|
cap_loc_file = pd.read_csv('settings/Electrode_selection.txt', sep=':', header=None)
|
||||||
self.cap_file_path = cap_loc_file[cap_loc_file[0]=='cap_file_path'].values[0][1].strip()
|
self.cap_file_path = cap_loc_file[cap_loc_file[0]=='cap_file_path'].values[0][1].strip()
|
||||||
self.full_cap_file_path = cap_loc_file[cap_loc_file[0]=='full_cap_file_path'].values[0][1].strip() #'easycap-M10_63_NO.txt'
|
self.full_cap_file_path = cap_loc_file[cap_loc_file[0]=='full_cap_file_path'].values[0][1].strip() #'easycap-M10_63_NO.txt'
|
||||||
except:
|
except:
|
||||||
print("CAP FILE EXCEPTION")
|
print("CAP FILE EXCEPTION")
|
||||||
self.cap_file_path = 'easycap-M10_16_NO.txt'
|
self.cap_file_path = 'settings/easycap-M10_16_NO.txt'
|
||||||
self.full_cap_file_path = 'easycap-M10_63_NO.txt'
|
self.full_cap_file_path = 'settings/easycap-M10_63_NO.txt'
|
||||||
|
|
||||||
self.conf_path = 'TMS_protocol.txt'
|
self.conf_path = 'settings/TMS_protocol.txt'
|
||||||
montage = mne.channels.read_custom_montage(self.cap_file_path)
|
montage = mne.channels.read_custom_montage(self.cap_file_path)
|
||||||
montage_file = pd.read_csv(self.cap_file_path, sep='\t')
|
montage_file = pd.read_csv(self.cap_file_path, sep='\t')
|
||||||
|
|
||||||
|
@ -286,6 +286,7 @@ class First_window(QMainWindow):
|
||||||
self.emg_ch_loaded = int(settings_file[settings_file[0]=='emg_channel'].values[0][1])
|
self.emg_ch_loaded = int(settings_file[settings_file[0]=='emg_channel'].values[0][1])
|
||||||
self.exp_trig_loaded = int(settings_file[settings_file[0]=='expected_triggers'].values[0][1])
|
self.exp_trig_loaded = int(settings_file[settings_file[0]=='expected_triggers'].values[0][1])
|
||||||
self.exp_time_loaded = int(settings_file[settings_file[0]=='expected_time'].values[0][1])
|
self.exp_time_loaded = int(settings_file[settings_file[0]=='expected_time'].values[0][1])
|
||||||
|
self.plot_len_loaded = int(settings_file[settings_file[0]=='plot_len'].values[0][1])
|
||||||
self.BoxChecked = False
|
self.BoxChecked = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ex_type, ex_value, ex_traceback = sys.exc_info()
|
ex_type, ex_value, ex_traceback = sys.exc_info()
|
||||||
|
@ -647,6 +648,7 @@ class First_window(QMainWindow):
|
||||||
self.emg_ch_lab, self.line_emg_ch, emg_ch_layout = add_thing(self, "EMG channel number:", self.emg_ch_loaded)
|
self.emg_ch_lab, self.line_emg_ch, emg_ch_layout = add_thing(self, "EMG channel number:", self.emg_ch_loaded)
|
||||||
self.exp_trig_lab, self.line_exp_trig, exp_trig_layout = add_thing(self, "Number of bursts within the train:", self.exp_trig_loaded)
|
self.exp_trig_lab, self.line_exp_trig, exp_trig_layout = add_thing(self, "Number of bursts within the train:", self.exp_trig_loaded)
|
||||||
self.exp_time_lab, self.line_exp_time, exp_time_layout = add_thing(self, "Expected time of a single train:", self.exp_time_loaded)
|
self.exp_time_lab, self.line_exp_time, exp_time_layout = add_thing(self, "Expected time of a single train:", self.exp_time_loaded)
|
||||||
|
self.plot_len_lab, self.plot_len_time, plot_len_layout = add_thing(self, "Plot width [s]:", self.plot_len_loaded)
|
||||||
|
|
||||||
# You can add feature name if function was added to the function "features" in the main file
|
# You can add feature name if function was added to the function "features" in the main file
|
||||||
features_names = ['None', 'Theta FFT Power', 'Alpha FFT Power', 'Beta FFT Power',
|
features_names = ['None', 'Theta FFT Power', 'Alpha FFT Power', 'Beta FFT Power',
|
||||||
|
@ -690,6 +692,24 @@ class First_window(QMainWindow):
|
||||||
self.notch_box = QCheckBox("Notch filter",self)
|
self.notch_box = QCheckBox("Notch filter",self)
|
||||||
self.outliers_box = QCheckBox("Remove outliers",self)
|
self.outliers_box = QCheckBox("Remove outliers",self)
|
||||||
|
|
||||||
|
labels = []
|
||||||
|
self.line_edits = []
|
||||||
|
self.checkboxes = []
|
||||||
|
|
||||||
|
for i in range(1, 13):
|
||||||
|
label = QLabel(f"Input {i}:")
|
||||||
|
labels.append(label)
|
||||||
|
|
||||||
|
line_edit = QLineEdit()
|
||||||
|
line_edit.setMaximumWidth(50)
|
||||||
|
line_edit.setText("10")
|
||||||
|
self.line_edits.append(line_edit)
|
||||||
|
|
||||||
|
checkbox = QCheckBox("%")
|
||||||
|
checkbox.setChecked(True)
|
||||||
|
checkbox.setMaximumWidth(35)
|
||||||
|
self.checkboxes.append(checkbox)
|
||||||
|
|
||||||
box_layout = QHBoxLayout()
|
box_layout = QHBoxLayout()
|
||||||
box_layout.addWidget(self.box)
|
box_layout.addWidget(self.box)
|
||||||
box_layout.addWidget(self.eye_reg_box)
|
box_layout.addWidget(self.eye_reg_box)
|
||||||
|
@ -736,6 +756,7 @@ class First_window(QMainWindow):
|
||||||
vbox.addLayout(emg_ch_layout)
|
vbox.addLayout(emg_ch_layout)
|
||||||
vbox.addLayout(exp_trig_layout)
|
vbox.addLayout(exp_trig_layout)
|
||||||
vbox.addLayout(exp_time_layout)
|
vbox.addLayout(exp_time_layout)
|
||||||
|
vbox.addLayout(plot_len_layout)
|
||||||
|
|
||||||
scroll = QScrollArea()
|
scroll = QScrollArea()
|
||||||
scroll.setWidget(text_last_ch)
|
scroll.setWidget(text_last_ch)
|
||||||
|
@ -750,11 +771,13 @@ class First_window(QMainWindow):
|
||||||
vbox.addWidget(self.file_path)
|
vbox.addWidget(self.file_path)
|
||||||
|
|
||||||
feature_choice_layout = QVBoxLayout()
|
feature_choice_layout = QVBoxLayout()
|
||||||
|
# feature_choice_layout = QFormLayout()
|
||||||
text_for_combo = QLabel(self)
|
text_for_combo = QLabel(self)
|
||||||
#text_for_combo.setMaximumWidth(370)
|
#text_for_combo.setMaximumWidth(370)
|
||||||
text_for_combo.setWordWrap(True)
|
text_for_combo.setWordWrap(True)
|
||||||
text_for_combo.setText("You can choose up to 6 different measurements that will be "\
|
text_for_combo.setText("You can choose up to 6 different measurements that will be "\
|
||||||
"calculated during the intervention.")
|
"calculated during the intervention. For each of them threshold can be set. If \"%\" option is "\
|
||||||
|
"used the threshold will be calculated as the maximum registered value +/- given percent of distance between them.")
|
||||||
|
|
||||||
connection_settings_text = QLabel(self)
|
connection_settings_text = QLabel(self)
|
||||||
connection_settings_text.setWordWrap(True)
|
connection_settings_text.setWordWrap(True)
|
||||||
|
@ -767,11 +790,20 @@ class First_window(QMainWindow):
|
||||||
self.ip_box, self.line_ip, ip_layout = add_thing(self, 'IP: ', self.ip, 100)
|
self.ip_box, self.line_ip, ip_layout = add_thing(self, 'IP: ', self.ip, 100)
|
||||||
self.port_box, self.line_port, port_layout = add_thing(self, 'Port: ', self.port, 100)
|
self.port_box, self.line_port, port_layout = add_thing(self, 'Port: ', self.port, 100)
|
||||||
|
|
||||||
comboboxes = [text_for_combo, self.combobox1, self.combobox2, self.combobox3,
|
comboboxes = [self.combobox1, self.combobox2, self.combobox3,
|
||||||
self.combobox4, self.combobox5, self.combobox6]
|
self.combobox4, self.combobox5, self.combobox6]
|
||||||
|
|
||||||
for combobox in comboboxes:
|
feature_choice_layout.addWidget(text_for_combo)
|
||||||
feature_choice_layout.addWidget(combobox)
|
for idx,combobox in enumerate(comboboxes):
|
||||||
|
vbox_temp = QHBoxLayout()
|
||||||
|
vbox_temp.addWidget(combobox)
|
||||||
|
vbox_temp.addWidget(self.checkboxes[idx*2])
|
||||||
|
vbox_temp.addWidget(self.line_edits[idx*2])
|
||||||
|
vbox_temp.addWidget(self.checkboxes[idx*2+1])
|
||||||
|
vbox_temp.addWidget(self.line_edits[idx*2+1])
|
||||||
|
|
||||||
|
feature_choice_layout.addLayout(vbox_temp)
|
||||||
|
|
||||||
feature_choice_layout.addStretch(1)
|
feature_choice_layout.addStretch(1)
|
||||||
feature_choice_layout.addWidget(connection_settings_text)
|
feature_choice_layout.addWidget(connection_settings_text)
|
||||||
feature_choice_layout.addWidget(self.combobox_system)
|
feature_choice_layout.addWidget(self.combobox_system)
|
||||||
|
@ -795,6 +827,7 @@ class First_window(QMainWindow):
|
||||||
tabwidget.addTab(self.canvas, 'Select electrodes for features')
|
tabwidget.addTab(self.canvas, 'Select electrodes for features')
|
||||||
tabwidget.addTab(self.canvas2, 'See the whole cap')
|
tabwidget.addTab(self.canvas2, 'See the whole cap')
|
||||||
tabwidget.addTab(self.feature_choice_widget, 'Features and connection')
|
tabwidget.addTab(self.feature_choice_widget, 'Features and connection')
|
||||||
|
# tabwidget.addTab(self.threshold_widget, 'Thresholds')
|
||||||
|
|
||||||
#layout.addWidget(self.canvas, 1, 1, 1, 1)
|
#layout.addWidget(self.canvas, 1, 1, 1, 1)
|
||||||
layout.addLayout(vbox, 1, 0, 1, 1)
|
layout.addLayout(vbox, 1, 0, 1, 1)
|
||||||
|
@ -909,7 +942,11 @@ class First_window(QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
#add EOG and EMG name to make channel names list complete
|
#add EOG and EMG name to make channel names list complete
|
||||||
|
percentages = []
|
||||||
|
values = []
|
||||||
|
for i in range(12):
|
||||||
|
percentages.append(self.checkboxes[i].isChecked())
|
||||||
|
values.append(float(self.line_edits[i].text()))
|
||||||
|
|
||||||
arr_temp = np.arange(int(self.line_num_ch.text()))
|
arr_temp = np.arange(int(self.line_num_ch.text()))
|
||||||
if self.line_eog_ch.text().strip()=="" and self.line_emg_ch.text().strip()=="":
|
if self.line_eog_ch.text().strip()=="" and self.line_emg_ch.text().strip()=="":
|
||||||
|
@ -951,6 +988,9 @@ class First_window(QMainWindow):
|
||||||
'offline': self.offline,
|
'offline': self.offline,
|
||||||
'exp_trig': int(self.line_exp_trig.text()),
|
'exp_trig': int(self.line_exp_trig.text()),
|
||||||
'exp_time': int(self.line_exp_time.text()),
|
'exp_time': int(self.line_exp_time.text()),
|
||||||
|
'percentages': percentages,
|
||||||
|
'thr_values': values,
|
||||||
|
'plot_len': int(self.plot_len_time.text())
|
||||||
}
|
}
|
||||||
print(self.params_to_pass)
|
print(self.params_to_pass)
|
||||||
|
|
|
@ -75,6 +75,14 @@ def connect_sig(data1, data2, fs):
|
||||||
fs: int
|
fs: int
|
||||||
sampling rate
|
sampling rate
|
||||||
"""
|
"""
|
||||||
|
print(data1.shape, data2.shape)
|
||||||
|
if all(data1[:, -fs] == None):
|
||||||
|
print(data2[:, -fs])
|
||||||
|
data_ret = data1.copy()
|
||||||
|
data_ret[:, :-fs] = data2[:, -fs].reshape(-1, 1)
|
||||||
|
data_ret[:,-fs:] = data2[:, -fs:]
|
||||||
|
return data_ret, 800
|
||||||
|
|
||||||
print(data2.shape)
|
print(data2.shape)
|
||||||
data2 = data2
|
data2 = data2
|
||||||
startt = time.time()
|
startt = time.time()
|
||||||
|
@ -83,6 +91,7 @@ def connect_sig(data1, data2, fs):
|
||||||
pts = list()
|
pts = list()
|
||||||
data_ret = np.zeros(data1.shape)
|
data_ret = np.zeros(data1.shape)
|
||||||
data_ret[:, :-fs] = data1[:,fs:]
|
data_ret[:, :-fs] = data1[:,fs:]
|
||||||
|
|
||||||
if data2.shape[0]==0 or data2.shape[1]==0:
|
if data2.shape[0]==0 or data2.shape[1]==0:
|
||||||
return data2
|
return data2
|
||||||
if fs<2000:
|
if fs<2000:
|
||||||
|
@ -93,7 +102,7 @@ def connect_sig(data1, data2, fs):
|
||||||
print("ARBEJDE IKKEEE")
|
print("ARBEJDE IKKEEE")
|
||||||
# data_ret = np.concatenate((data1, data2[:,-int(size):]),1)
|
# data_ret = np.concatenate((data1, data2[:,-int(size):]),1)
|
||||||
data_ret[:,-fs:] = data2[:, -fs:]
|
data_ret[:,-fs:] = data2[:, -fs:]
|
||||||
return data_ret, None
|
return data_ret, 800
|
||||||
#print('hehe', time.time()-startt)
|
#print('hehe', time.time()-startt)
|
||||||
most_fr = most_frequent(np.array(pts))
|
most_fr = most_frequent(np.array(pts))
|
||||||
#print('hehe', time.time()-startt)
|
#print('hehe', time.time()-startt)
|
3
utils/__init__.py
Normal file
3
utils/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import sys, os
|
||||||
|
# sys.path.append("..")
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
BIN
utils/__pycache__/FirstWindow.cpython-38.pyc
Normal file
BIN
utils/__pycache__/FirstWindow.cpython-38.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/Functions.cpython-38.pyc
Normal file
BIN
utils/__pycache__/Functions.cpython-38.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/Waiting.cpython-38.pyc
Normal file
BIN
utils/__pycache__/Waiting.cpython-38.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
0
features.py → utils/features.py
Executable file → Normal file
0
features.py → utils/features.py
Executable file → Normal file
BIN
utils/image-1.png
Normal file
BIN
utils/image-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 252 KiB |
BIN
utils/image-3.png
Normal file
BIN
utils/image-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 233 KiB |
BIN
utils/image-5.png
Normal file
BIN
utils/image-5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 347 KiB |
Loading…
Reference in a new issue