Update EStiMo_GUI.py

updated comments
This commit is contained in:
Armita Faghani 2025-05-08 18:05:51 +00:00
parent a97d4e22e6
commit ad77c22a04

View file

@ -30,10 +30,8 @@ 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,
@ -154,19 +152,13 @@ class NeurOneOffline():
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]
@ -335,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)
@ -380,8 +371,7 @@ 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
@ -562,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
@ -593,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]
@ -616,9 +600,6 @@ class AppForm(QMainWindow):
print("Calculation of features: {}".format(times1-times)) print("Calculation of features: {}".format(times1-times))
#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]:
@ -724,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):
@ -740,8 +719,7 @@ 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):
@ -827,13 +805,6 @@ 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)
# 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))))
# this way is even easier...
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) 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)
# 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(
@ -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')
@ -1015,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()
@ -1120,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)
@ -1152,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),
@ -1246,12 +1208,6 @@ 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])
@ -1304,14 +1260,6 @@ 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)
@ -1416,8 +1364,3 @@ if __name__ == '__main__':
form = First_window(AppForm) #AppForm() form = First_window(AppForm) #AppForm()
form.show() form.show()
sys.exit(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