class Scope(QMainWindow): def __init__(self, amp_name, amp_serial, state=mp.Value('i', 1), queue=None): super(Scope, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) redirect_stdout_to_queue(logger, queue, 'INFO') logger.info('Viewer launched') self.amp_name = amp_name self.amp_serial = amp_serial self.state = state self.init_scope() # # Main init function # def init_scope(self): # pg.setConfigOption('background', 'w') # pg.setConfigOption('foreground', 'k') self.init_config_file() self.init_loop() self.init_panel_GUI() self.init_scope_GUI() self.init_timer() # # Initializes config file # def init_config_file(self): self.scope_settings = RawConfigParser(allow_no_value=True, inline_comment_prefixes=('#', ';')) if (len(sys.argv) == 1): self.show_channel_names = 0 self.device_name = "" else: if (sys.argv[1].find("gtec") > -1): self.device_name = "gtec" self.show_channel_names = 1 elif (sys.argv[1].find("biosemi") > -1): self.device_name = "biosemi" self.show_channel_names = 1 elif (sys.argv[1].find("hiamp") > -1): self.device_name = "hiamp" self.show_channel_names = 1 else: self.device_name = "" self.show_channel_names = 0 # self.scope_settings.read(os.getenv("HOME") + "/.scope_settings.ini") self.scope_settings.read( str(path2_viewerFolder / '.scope_settings.ini')) # # Initialize control panel parameter # def init_panel_GUI(self): self.show_TID_events = False self.show_LPT_events = False self.show_Key_events = False # Event handler self.ui.comboBox_scale.activated.connect( self.onActivated_combobox_scale) self.ui.spinBox_time.valueChanged.connect( self.onValueChanged_spinbox_time) self.ui.checkBox_car.stateChanged.connect( self.onActivated_checkbox_car) self.ui.checkBox_bandpass.stateChanged.connect( self.onActivated_checkbox_bandpass) self.ui.checkBox_showTID.stateChanged.connect( self.onActivated_checkbox_TID) self.ui.checkBox_showLPT.stateChanged.connect( self.onActivated_checkbox_LPT) self.ui.checkBox_showKey.stateChanged.connect( self.onActivated_checkbox_Key) self.ui.pushButton_bp.clicked.connect(self.onClicked_button_bp) self.ui.pushButton_rec.clicked.connect(self.onClicked_button_rec) self.ui.pushButton_stoprec.clicked.connect( self.onClicked_button_stoprec) self.ui.pushButton_stoprec.setEnabled(False) self.ui.comboBox_scale.setCurrentIndex(4) self.ui.checkBox_car.setChecked( int(self.scope_settings.get("filtering", "apply_car_filter"))) self.ui.checkBox_bandpass.setChecked( int(self.scope_settings.get("filtering", "apply_bandpass_filter"))) self.ui.checkBox_showTID.setChecked( int(self.scope_settings.get("plot", "show_TID_events"))) self.ui.checkBox_showLPT.setChecked( int(self.scope_settings.get("plot", "show_LPT_events"))) self.ui.checkBox_showKey.setChecked( int(self.scope_settings.get("plot", "show_KEY_events"))) self.ui.statusBar.showMessage("[Not recording]") self.channels_to_show_idx = [] idx = 0 for y in range(0, 4): for x in range(0, NUM_X_CHANNELS): if (idx < self.config['eeg_channels']): # self.table_channels.item(x,y).setTextAlignment(QtCore.Qt.AlignCenter) self.ui.table_channels.item(x, y).setSelected(True) # Qt5 #self.table_channels.setItemSelected(self.table_channels.item(x, y), True) # Qt4 only self.channels_to_show_idx.append(idx) else: self.ui.table_channels.setItem(x, y, QTableWidgetItem("N/A")) self.ui.table_channels.item(x, y).setFlags( QtCore.Qt.NoItemFlags) self.ui.table_channels.item(x, y).setTextAlignment( QtCore.Qt.AlignCenter) idx += 1 self.ui.table_channels.verticalHeader().setStretchLastSection(True) self.ui.table_channels.horizontalHeader().setStretchLastSection(True) self.ui.table_channels.itemSelectionChanged.connect( self.onSelectionChanged_table) self.screen_width = 522 self.screen_height = 160 # self.setGeometry(100,100, self.screen_width, self.screen_height) # self.setFixedSize(self.screen_width, self.screen_height) self.setWindowTitle('EEG Scope Panel') self.setFocusPolicy(QtCore.Qt.ClickFocus) self.setFocus() self.show() # # Initialize scope parameters # def init_scope_GUI(self): self.bool_parser = {True: '1', False: '0'} # PyQTGraph plot initialization self.win = pg.GraphicsWindow() self.win.setWindowTitle('EEG Scope') self.win.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint) self.win.keyPressEvent = self.keyPressEvent self.win.show() self.main_plot_handler = self.win.addPlot() self.win.resize(1280, 800) # Scales available in the GUI. If you change the options in the GUI # you should change them here as well self.scales_range = [1, 10, 25, 50, 100, 250, 500, 1000, 2500, 100000] # Scale in uV self.scale = int(self.scope_settings.get("plot", "scale_plot")) # Time window to show in seconds self.seconds_to_show = int(self.scope_settings.get( "plot", "time_plot")) # Y Tick labels. Use values from the config file. self.channel_labels = [] values = [] ''' For non-LSL systems having no channel names for x in range(0, self.config['eeg_channels']): if (self.show_channel_names): self.channel_labels.append("(" + str(x + 1) + ") " + self.scope_settings.get("internal", "channel_names_" + self.device_name + str( self.config['eeg_channels'])).split(', ')[x]) else: self.channel_labels.append('CH ' + str(x + 1)) ''' ch_names = np.array(self.sr.get_channel_names()) self.channel_labels = ch_names[self.sr.get_eeg_channels()] for x in range(0, len(self.channels_to_show_idx)): values.append((-x * self.scale, self.channel_labels[self.channels_to_show_idx[x]])) values_axis = [] values_axis.append(values) values_axis.append([]) # Update table labels with current names idx = 0 for y in range(0, 4): for x in range(0, NUM_X_CHANNELS): if (idx < self.config['eeg_channels']): self.ui.table_channels.item(x, y).setText( self.channel_labels[idx]) idx += 1 # Plot initialization self.main_plot_handler.getAxis('left').setTicks(values_axis) self.main_plot_handler.setRange( xRange=[0, self.seconds_to_show], yRange=[ +1.5 * self.scale, -0.5 * self.scale - self.scale * self.config['eeg_channels'] ]) self.main_plot_handler.disableAutoRange() self.main_plot_handler.showGrid(y=True) self.main_plot_handler.setLabel(axis='left', text='Scale (uV): ' + str(self.scale)) self.main_plot_handler.setLabel(axis='bottom', text='Time (s)') # X axis self.x_ticks = np.zeros(self.config['sf'] * self.seconds_to_show) for x in range(0, self.config['sf'] * self.seconds_to_show): self.x_ticks[x] = (x * 1) / float(self.config['sf']) # Plotting colors. If channels > 16, colors will roll back to the beginning self.colors = np.array([[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [0, 255, 255], [255, 0, 255], [128, 100, 100], [0, 128, 0], [0, 128, 128], [128, 128, 0], [255, 128, 128], [128, 0, 128], [128, 255, 0], [255, 128, 0], [0, 255, 128], [128, 0, 255]]) # We want a lightweight scope, so we downsample the plotting to 64 Hz self.subsampling_value = self.config['sf'] / 64 # EEG data for plotting self.data_plot = np.zeros((self.config['sf'] * self.seconds_to_show, self.config['eeg_channels'])) self.curve_eeg = [] for x in range(0, len(self.channels_to_show_idx)): self.curve_eeg.append( self.main_plot_handler.plot( x=self.x_ticks, y=self.data_plot[:, self.channels_to_show_idx[x]], pen=pg.mkColor(self.colors[self.channels_to_show_idx[x] % 16, :]))) # self.curve_eeg[-1].setDownsampling(ds=self.subsampling_value, auto=False, method="mean") # Events data self.events_detected = [] self.events_curves = [] self.events_text = [] # CAR initialization self.apply_car = int( self.scope_settings.get("filtering", "apply_car_filter")) self.matrix_car = np.zeros( (self.config['eeg_channels'], self.config['eeg_channels']), dtype=float) self.matrix_car[:, :] = -1 / float(self.config['eeg_channels']) np.fill_diagonal(self.matrix_car, 1 - (1 / float(self.config['eeg_channels']))) # Laplacian initalization. TO BE DONE self.matrix_lap = np.zeros( (self.config['eeg_channels'], self.config['eeg_channels']), dtype=float) np.fill_diagonal(self.matrix_lap, 1) ''' self.matrix_lap[2, 0] = -1 self.matrix_lap[0, 2] = -0.25 self.matrix_lap[0, 2] = -0.25 ''' # BP initialization self.apply_bandpass = int( self.scope_settings.get("filtering", "apply_bandpass_filter")) if (self.apply_bandpass): self.ui.doubleSpinBox_hp.setValue( float( self.scope_settings.get( "filtering", "bandpass_cutoff_frequency").split(' ')[0])) self.ui.doubleSpinBox_lp.setValue( float( self.scope_settings.get( "filtering", "bandpass_cutoff_frequency").split(' ')[1])) self.ui.doubleSpinBox_lp.setMinimum(0.1) self.ui.doubleSpinBox_lp.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_lp.setDecimals(1) self.ui.doubleSpinBox_lp.setSingleStep(1) self.ui.doubleSpinBox_hp.setMinimum(0.1) self.ui.doubleSpinBox_hp.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_hp.setDecimals(1) self.ui.doubleSpinBox_hp.setSingleStep(1) self.ui.pushButton_bp.click() self.ui.checkBox_bandpass.setChecked(self.apply_car) self.ui.checkBox_bandpass.setChecked(self.apply_bandpass) self.update_title_scope() # Help variables self.show_help = 0 self.help = pg.TextItem( "CNBI EEG Scope v0.3 \n" + "----------------------------------------------------------------------------------\n" + "C: De/activate CAR Filter\n" + "B: De/activate Bandpass Filter (with current settings)\n" + "T: Show/hide TiD events\n" + "L: Show/hide LPT events\n" + "K: Show/hide Key events. If not shown, they are NOT recorded!\n" + "0-9: Add a user-specific Key event. Do not forget to write down why you marked it.\n" + "Up, down arrow keys: Increase/decrease the scale, steps of 10 uV\n" + "Left, right arrow keys: Increase/decrease the time to show, steps of 1 s\n" + "Spacebar: Stop the scope plotting, whereas data acquisition keeps running (EXPERIMENTAL)\n" + "Esc: Exits the scope", anchor=(0, 0), border=(70, 70, 70), fill=pg.mkColor(20, 20, 20, 200), color=(255, 255, 255)) # Stop plot functionality self.stop_plot = 0 # Force repaint even when we shouldn't repaint. self.force_repaint = 0 # For some strange reason when the width is > 1 px the scope runs slow. # self.pen_plot = [] # for x in range(0, self.config['eeg_channels']): # self.pen_plot.append(pg.mkPen(self.colors[x%16,:], width=3)) # # Initializes the BCI loop parameters # def init_loop_cnbiloop(self): self.fin = open(self.scope_settings.get("internal", "path_pipe"), 'r') # 12 unsigned ints (4 bytes) data = struct.unpack("<12I", self.fin.read(4 * 12)) self.config = { 'id': data[0], 'sf': data[1], 'labels': data[2], 'samples': data[3], 'eeg_channels': data[4], 'exg_channels': data[5], 'tri_channels': data[6], 'eeg_type': data[8], 'exg_type': data[9], 'tri_type': data[10], 'lbl_type': data[11], 'tim_size': 1, 'idx_size': 1 } self.tri = np.zeros(self.config['samples']) self.eeg = np.zeros( (self.config['samples'], self.config['eeg_channels']), dtype=np.float) self.exg = np.zeros( (self.config['samples'], self.config['exg_channels']), dtype=np.float) # TID initialization self.bci = BCI.BciInterface() # # Initializes the BCI loop parameters # def init_loop(self): self.updating = False self.sr = StreamReceiver(window_size=1, buffer_size=10, amp_serial=self.amp_serial, amp_name=self.amp_name) srate = int(self.sr.sample_rate) # n_channels= self.sr.channels # 12 unsigned ints (4 bytes) ########## TODO: assumkng 32 samples chunk => make it read from LSL header data = [ 'EEG', srate, ['L', 'R'], 32, len(self.sr.get_eeg_channels()), 0, self.sr.get_trigger_channel(), None, None, None, None, None ] logger.info('Trigger channel is %d' % self.sr.get_trigger_channel()) self.config = { 'id': data[0], 'sf': data[1], 'labels': data[2], 'samples': data[3], 'eeg_channels': data[4], 'exg_channels': data[5], 'tri_channels': data[6], 'eeg_type': data[8], 'exg_type': data[9], 'tri_type': data[10], 'lbl_type': data[11], 'tim_size': 1, 'idx_size': 1 } self.tri = np.zeros(self.config['samples']) self.last_tri = 0 self.eeg = np.zeros( (self.config['samples'], self.config['eeg_channels']), dtype=np.float) self.exg = np.zeros( (self.config['samples'], self.config['exg_channels']), dtype=np.float) self.ts_list = [] self.ts_list_tri = [] # # Initializes the QT timer, which will call the update function every 20 ms # def init_timer(self): self.tm = qc.Timer() # leeq QtCore.QCoreApplication.processEvents() QtCore.QCoreApplication.flush() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.update_loop) self.timer.start(20) # QtCore.QTimer.singleShot( 20, self.update_loop ) # # Main update function (connected to the timer) # def update_loop(self): # Sharing variable to stop at the GUI level if not self.state.value: logger.info('Viewer stopped') sys.exit() try: # assert self.updating==False, 'thread destroyed?' # self.updating= True # self.handle_tobiid_input() # Read TiDs self.read_eeg() # Read new chunk if len(self.ts_list) > 0: self.filter_signal() # Filter acquired data self.update_ringbuffers() # Update the plotting infor if (not self.stop_plot): self.repaint() # Call paint event except: logger.exception('Exception. Dropping into a shell.') pdb.set_trace() finally: # self.updating= False # using singleShot instead # QtCore.QTimer.singleShot( 20, self.update_loop ) pass # # Read EEG # def read_eeg(self): # if self.updating==True: print( '##### ERROR: thread destroyed ? ######' ) # self.updating= True try: # data, self.ts_list= self.sr.inlets[0].pull_chunk(max_samples=self.config['sf']) # [frames][channels] data, self.ts_list = self.sr.acquire(blocking=False) # TODO: check and change to these two lines #self.sr.acquire(blocking=False, decim=DECIM) #data, self.ts_list = self.sr.get_window() if len(self.ts_list) == 0: # self.eeg= None # self.tri= None return n = self.config['eeg_channels'] ''' x= np.array( data ) trg_ch= self.config['tri_channels'] if trg_ch is not None: self.tri= np.reshape( x[:,trg_ch], (-1,1) ) # samples x 1 self.eeg= np.reshape( x[:,self.sr.eeg_channels], (-1,n) ) # samples x channels ''' trg_ch = self.config['tri_channels'] if trg_ch is not None: self.tri = np.reshape(data[:, trg_ch], (-1, 1)) # samples x 1 self.eeg = np.reshape(data[:, self.sr.eeg_channels], (-1, n)) # samples x channels if DEBUG_TRIGGER: # show trigger value try: trg_value = max(self.tri) if trg_value > 0: logger.info('Received trigger %s' % trg_value) except: logger.exception('Error! self.tri = %s' % self.tri) # Read exg. self.config.samples*self.config.exg_ch, type float # bexg = np.random.rand( 1, self.config['samples'] * self.config['exg_channels'] ) # self.exg = np.reshape(list(bexg), (self.config['samples'],self.config['exg_channels'])) except WindowsError: # print('**** Access violation in read_eeg():\n%s\n%s'% (sys.exc_info()[0], sys.exc_info()[1])) pass except: logger.exception() pdb.set_trace() # # Read EEG # def read_eeg_cnbiloop(self): # Reading in python is blocking, so it will wait until having the amount of data needed # Read timestamp. 1 value, type double timestamp = struct.unpack("<d", self.fin.read(8 * 1)) # Read index. 1 value, type uint64 index = struct.unpack("<Q", self.fin.read(8 * 1)) # Read labels. self.config.labels, type double labels = struct.unpack("<" + str(self.config['labels']) + "I", self.fin.read(4 * self.config['labels'])) # Read eeg. self.config.samples*self.config.eeg_ch, type float beeg = struct.unpack( "<" + str(self.config['samples'] * self.config['eeg_channels']) + "f", self.fin.read(4 * self.config['samples'] * self.config['eeg_channels'])) self.eeg = np.reshape( list(beeg), (self.config['samples'], self.config['eeg_channels'])) # Read exg. self.config.samples*self.config.exg_ch, type float bexg = struct.unpack( "<" + str(self.config['samples'] * self.config['exg_channels']) + "f", self.fin.read(4 * self.config['samples'] * self.config['exg_channels'])) self.exg = np.reshape( list(bexg), (self.config['samples'], self.config['exg_channels'])) # Read tri. self.config.samples*self.config.tri_ch, type float self.tri = struct.unpack( "<" + str(self.config['samples'] * self.config['tri_channels']) + "i", self.fin.read(4 * self.config['samples'] * self.config['tri_channels'])) # # Bandpas + CAR filtering # def filter_signal(self): if (self.apply_bandpass): for x in range(0, self.eeg.shape[1]): self.eeg[:, x], self.zi[:, x] = lfilter(self.b, self.a, self.eeg[:, x], -1, self.zi[:, x]) # We only apply CAR if selected AND there are at least 2 channels. Otherwise it makes no sense if (self.apply_car) and (len(self.channels_to_show_idx) > 1): self.eeg = np.dot(self.matrix_car, np.transpose(self.eeg)) self.eeg = np.transpose(self.eeg) # # Update ringbuffers and events for plotting # def update_ringbuffers(self): # leeq self.data_plot = np.roll(self.data_plot, -len(self.ts_list), 0) self.data_plot[-len(self.ts_list):, :] = self.eeg # We have to remove those indexes that reached time = 0 delete_indices_e = [] delete_indices_c = [] for x in range(0, len(self.events_detected), 2): xh = int(x / 2) self.events_detected[x] -= len(self.ts_list) # leeq if (self.events_detected[x] < 0) and (not self.stop_plot): delete_indices_e.append(x) delete_indices_e.append(x + 1) delete_indices_c.append(xh) self.events_curves[xh].clear() self.main_plot_handler.removeItem(self.events_text[xh]) self.events_detected = [ i for j, i in enumerate(self.events_detected) if j not in delete_indices_e ] self.events_curves = [ i for j, i in enumerate(self.events_curves) if j not in delete_indices_c ] self.events_text = [ i for j, i in enumerate(self.events_text) if j not in delete_indices_c ] # Find LPT events and add them if (self.show_LPT_events) and (not self.stop_plot): for x in range(len(self.tri)): tri = int(self.tri[x]) if tri != 0 and (tri > self.last_tri): self.addEventPlot("LPT", tri) logger.info('Trigger %d received' % tri) self.last_tri = tri # # Called by repaint() # def paintEvent(self, e): # Distinguish between paint events from timer and event QT widget resizing, clicking etc (sender is None) # We should only paint when the timer triggered the event. # Just in case, there's a flag to force a repaint even when we shouldn't repaint sender = self.sender() if 'force_repaint' not in self.__dict__.keys(): logger.warning('force_repaint is not set! Is it a Qt bug?') self.force_repaint = 0 if (sender is None) and (not self.force_repaint): pass else: self.force_repaint = 0 qp = QPainter() qp.begin(self) # Update the interface self.paintInterface(qp) qp.end() # # Update stuff on the interface. Only graphical updates should be added here # def paintInterface(self, qp): # Update EEG channels for x in range(0, len(self.channels_to_show_idx)): self.curve_eeg[x].setData( x=self.x_ticks, y=self.data_plot[:, self.channels_to_show_idx[x]] - x * self.scale) # Update events for x in range(0, len(self.events_detected), 2): xh = int(x / 2) self.events_curves[xh].setData( x=np.array([ self.x_ticks[self.events_detected[x]], self.x_ticks[self.events_detected[x]] ]), y=np.array([ +1.5 * self.scale, -0.5 * self.scale - self.scale * self.config['eeg_channels'] ])) self.events_text[xh].setPos(self.x_ticks[self.events_detected[x]], self.scale) # # Do necessary stuff when scale has changed # def update_plot_scale(self, new_scale): if (new_scale < 1): new_scale = 1 # commented out by dbdq. # else: # new_scale = new_scale - new_scale%10 self.scale = new_scale # Y Tick labels values = [] for x in range(0, len(self.channels_to_show_idx)): values.append((-x * self.scale, self.channel_labels[self.channels_to_show_idx[x]])) values_axis = [] values_axis.append(values) values_axis.append([]) self.main_plot_handler.getAxis('left').setTicks(values_axis) self.main_plot_handler.setRange( yRange=[+self.scale, -self.scale * len(self.channels_to_show_idx)]) self.main_plot_handler.setLabel(axis='left', text='Scale (uV): ' + str(self.scale)) self.trigger_help() # We force an immediate repaint to avoid "shakiness". if (not self.stop_plot): self.force_repaint = 1 self.repaint() # # Do necessary stuff when seconds to show have changed # def update_plot_seconds(self, new_seconds): # Do nothing unless... if (new_seconds != self.seconds_to_show) and (new_seconds > 0) and ( new_seconds < 100): self.ui.spinBox_time.setValue(new_seconds) self.main_plot_handler.setRange(xRange=[0, new_seconds]) self.x_ticks = np.zeros(self.config['sf'] * new_seconds) for x in range(0, self.config['sf'] * new_seconds): self.x_ticks[x] = (x * 1) / float(self.config['sf']) if (new_seconds > self.seconds_to_show): padded_signal = np.zeros((self.config['sf'] * new_seconds, self.config['eeg_channels'])) padded_signal[padded_signal.shape[0] - self.data_plot.shape[0]:, :] = self.data_plot for x in range(0, len(self.events_detected), 2): self.events_detected[x] += padded_signal.shape[0] - \ self.data_plot.shape[0] self.data_plot = padded_signal else: for x in range(0, len(self.events_detected), 2): self.events_detected[x] -= self.data_plot.shape[0] - \ self.config['sf'] * new_seconds self.data_plot = self.data_plot[self.data_plot.shape[0] - self.config['sf'] * new_seconds:, :] self.seconds_to_show = new_seconds self.trigger_help() # We force an immediate repaint to avoid "shakiness". if (not self.stop_plot): self.force_repaint = 1 self.repaint() # # Handle TOBI iD events # def handle_tobiid_input(self): data = None try: data = self.bci.iDsock_bus.recv(512) self.bci.idStreamer_bus.Append(data) except: self.nS = False self.dec = 0 pass # deserialize ID message if data: if self.bci.idStreamer_bus.Has("<tobiid", "/>"): msg = self.bci.idStreamer_bus.Extract("<tobiid", "/>") self.bci.id_serializer_bus.Deserialize(msg) self.bci.idStreamer_bus.Clear() tmpmsg = int(self.bci.id_msg_bus.GetEvent()) if (self.show_TID_events) and (not self.stop_plot): self.addEventPlot("TID", tmpmsg) elif self.bci.idStreamer_bus.Has("<tcstatus", "/>"): MsgNum = self.bci.idStreamer_bus.Count("<tcstatus") for i in range(1, MsgNum - 1): # Extract most of these messages and trash them msg_useless = self.bci.idStreamer_bus.Extract( "<tcstatus", "/>") # # Add an event to the scope # def addEventPlot(self, event_name, event_id): if (event_name == "TID"): color = pg.mkColor(0, 0, 255) elif (event_name == "KEY"): color = pg.mkColor(255, 0, 0) elif (event_name == "LPT"): color = pg.mkColor(0, 255, 0) else: color = pg.mkColor(255, 255, 255) self.events_detected.append(self.data_plot.shape[0] - 1) self.events_detected.append(event_id) self.events_curves.append( self.main_plot_handler.plot( pen=color, x=np.array([self.x_ticks[-1], self.x_ticks[-1]]), y=np.array([ +1.5 * self.scale, -1.5 * self.scale * self.config['eeg_channels'] ]))) # text = pg.TextItem(event_name + "(" + str(self.events_detected[-1]) + ")", anchor=(1.1,0), fill=(0,0,0), color=color) text = pg.TextItem(str(self.events_detected[-1]), anchor=(1.1, 0), fill=(0, 0, 0), color=color) text.setPos(self.x_ticks[-1], self.scale) self.events_text.append(text) self.main_plot_handler.addItem(self.events_text[-1]) # # Calculation of bandpass coefficients. # Order is computed automatically. # Note that if filter is unstable this function crashes (TODO handle problems) # def butter_bandpass(self, highcut, lowcut, fs, num_ch): low = lowcut / (0.5 * fs) high = highcut / (0.5 * fs) # get the order. TO BE DONE: Sometimes it fails #ord = buttord(high, low, 2, 40) #b, a = butter(ord[0], [high, low], btype='band') b, a = butter(2, [high, low], btype='band') zi = np.zeros([a.shape[0] - 1, num_ch]) return b, a, zi # # Updates the title shown in the scope # def update_title_scope(self): if (hasattr(self, 'main_plot_handler')): self.main_plot_handler.setTitle( title='TLK: ' + self.bool_parser[self.show_TID_events] + self.bool_parser[self.show_LPT_events] + self.bool_parser[self.show_Key_events] + ', CAR: ' + self.bool_parser[self.apply_car] + ', BP: ' + self.bool_parser[self.apply_bandpass] + ' [' + str(self.ui.doubleSpinBox_hp.value()) + '-' + str(self.ui.doubleSpinBox_lp.value()) + '] Hz') # ', BP: ' + self.bool_parser[self.apply_bandpass] + (' [' + str(self.doubleSpinBox_hp.value()) + '-' + str(self.doubleSpinBox_lp.value()) + '] Hz' if self.apply_bandpass else '')) # # Shows / hide help in the scope window # def trigger_help(self): if self.show_help: self.help.setPos(0, self.scale) self.main_plot_handler.addItem(self.help) self.help.setZValue(1) else: self.main_plot_handler.removeItem(self.help) # ---------------------------------------------------------------------------------------------------- # EVENT HANDLERS # ---------------------------------------------------------------------------------------------------- def onClicked_button_rec(self): # Simply call cl_rpc for this. if (len(self.lineEdit_recFilename.text()) > 0): if ".gdf" in self.lineEdit_recFilename.text(): self.ui.pushButton_stoprec.setEnabled(True) self.ui.pushButton_rec.setEnabled(False) # Popen is more efficient than os.open, since it is non-blocking subprocess.Popen([ "cl_rpc", "openxdf", str(self.ui.lineEdit_recFilename.text()), "dummy_log", "dummy_log" ], close_fds=True) self.statusBar.showMessage( "Recording file " + str(self.ui.lineEdit_recFilename.text())) elif ".bdf" in self.ui.lineEdit_recFilename.text(): self.ui.pushButton_stoprec.setEnabled(True) self.ui.pushButton_rec.setEnabled(False) subprocess.Popen([ "cl_rpc", "openxdf", str(self.ui.lineEdit_recFilename.text()), "dummy_log", "dummy_log" ], close_fds=True) self.statusBar.showMessage( "Recording file " + str(self.ui.lineEdit_recFilename.text())) else: pass def onClicked_button_stoprec(self): subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True) self.ui.pushButton_rec.setEnabled(True) self.ui.pushButton_stoprec.setEnabled(False) self.ui.statusBar.showMessage("Not recording") def onActivated_checkbox_bandpass(self): self.apply_bandpass = False self.ui.pushButton_bp.setEnabled(self.ui.checkBox_bandpass.isChecked()) self.ui.doubleSpinBox_hp.setEnabled( self.ui.checkBox_bandpass.isChecked()) self.ui.doubleSpinBox_lp.setEnabled( self.ui.checkBox_bandpass.isChecked()) self.update_title_scope() def onActivated_checkbox_car(self): self.apply_car = self.ui.checkBox_car.isChecked() self.update_title_scope() def onActivated_checkbox_TID(self): self.show_TID_events = self.ui.checkBox_showTID.isChecked() self.update_title_scope() def onActivated_checkbox_LPT(self): self.show_LPT_events = self.ui.checkBox_showLPT.isChecked() self.update_title_scope() def onActivated_checkbox_Key(self): self.show_Key_events = self.ui.checkBox_showKey.isChecked() self.update_title_scope() def onValueChanged_spinbox_time(self): self.update_plot_seconds(self.ui.spinBox_time.value()) def onActivated_combobox_scale(self): self.update_plot_scale( self.scales_range[self.ui.comboBox_scale.currentIndex()]) def onClicked_button_bp(self): if (self.ui.doubleSpinBox_lp.value() > self.ui.doubleSpinBox_hp.value()): self.apply_bandpass = True self.b, self.a, self.zi = self.butter_bandpass( self.ui.doubleSpinBox_hp.value(), self.ui.doubleSpinBox_lp.value(), self.config['sf'], self.config['eeg_channels']) self.update_title_scope() def onSelectionChanged_table(self): # Remove current plot for x in range(0, len(self.channels_to_show_idx)): self.main_plot_handler.removeItem(self.curve_eeg[x]) # Which channels should I plot? self.channels_to_show_idx = [] self.channels_to_hide_idx = [] idx = 0 for y in range(0, 4): for x in range(0, NUM_X_CHANNELS): if (idx < self.config['eeg_channels']): #if (self.table_channels.isItemSelected( # Qt4 only if (QTableWidgetItem.isSelected( # Qt5 self.ui.table_channels.item(x, y))): self.channels_to_show_idx.append(idx) else: self.channels_to_hide_idx.append(idx) idx += 1 # Add new plots self.curve_eeg = [] for x in range(0, len(self.channels_to_show_idx)): self.curve_eeg.append( self.main_plot_handler.plot( x=self.x_ticks, y=self.data_plot[:, self.channels_to_show_idx[x]], pen=self.colors[self.channels_to_show_idx[x] % NUM_X_CHANNELS, :])) self.curve_eeg[-1].setDownsampling(ds=self.subsampling_value, auto=False, method="mean") # Update CAR so it's computed based only on the shown channels if (len(self.channels_to_show_idx) > 1): self.matrix_car = np.zeros( (self.config['eeg_channels'], self.config['eeg_channels']), dtype=float) self.matrix_car[:, :] = -1 / float(len(self.channels_to_show_idx)) np.fill_diagonal(self.matrix_car, 1 - (1 / float(len(self.channels_to_show_idx)))) for x in range(0, len(self.channels_to_hide_idx)): self.matrix_car[self.channels_to_hide_idx[x], :] = 0 self.matrix_car[:, self.channels_to_hide_idx[x]] = 0 # Refresh the plot self.update_plot_scale(self.scale) def keyPressEvent(self, event): key = event.key() if (key == QtCore.Qt.Key_Escape): self.closeEvent(None) if (key == QtCore.Qt.Key_H): self.show_help = not self.show_help self.trigger_help() if (key == QtCore.Qt.Key_Up): # Python's log(x, 10) has a rounding bug. Use log10(x) instead. new_scale = self.scale + max(1, 10**int(math.log10(self.scale))) self.update_plot_scale(new_scale) if (key == QtCore.Qt.Key_Space): self.stop_plot = not self.stop_plot if (key == QtCore.Qt.Key_Down): if self.scale >= 2: # Python's log(x, 10) has a rounding bug. Use log10(x) instead. new_scale = self.scale - max( 1, 10**int(math.log10(self.scale - 1))) self.update_plot_scale(new_scale) if (key == QtCore.Qt.Key_Left): self.update_plot_seconds(self.seconds_to_show - 1) if (key == QtCore.Qt.Key_Right): self.update_plot_seconds(self.seconds_to_show + 1) if (key == QtCore.Qt.Key_L): self.ui.checkBox_showLPT.setChecked( not self.ui.checkBox_showLPT.isChecked()) if (key == QtCore.Qt.Key_T): self.ui.checkBox_showTID.setChecked( not self.ui.checkBox_showTID.isChecked()) if (key == QtCore.Qt.Key_K): self.ui.checkBox_showKey.setChecked( not self.ui.checkBox_showKey.isChecked()) if (key == QtCore.Qt.Key_C): self.ui.checkBox_car.setChecked( not self.ui.checkBox_car.isChecked()) if (key == QtCore.Qt.Key_B): self.ui.checkBox_bandpass.setChecked( not self.ui.checkBox_bandpass.isChecked()) if self.ui.checkBox_bandpass.isChecked(): self.ui.pushButton_bp.click() if ((key >= QtCore.Qt.Key_0) and (key <= QtCore.Qt.Key_9)): if (self.show_Key_events) and (not self.stop_plot): self.addEventPlot("KEY", 990 + key - QtCore.Qt.Key_0) # self.bci.id_msg_bus.SetEvent(990 + key - QtCore.Qt.Key_0) # self.bci.iDsock_bus.sendall(self.bci.id_serializer_bus.Serialize()); # 666 # # Function called when a closing event was triggered. # def closeEvent(self, event): ''' reply = QtGui.QMessageBox.question(self, "Quit", "Are you sure you want to quit?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) if (reply == QtGui.QMessageBox.Yes): if (self.pushButton_stoprec.isEnabled()): subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True) self.fin.close() exit() ''' # leeq if (self.ui.pushButton_stoprec.isEnabled()): subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True) with self.state.get_lock(): self.state.value = 0
class MainView(QMainWindow, SubjectInfo, TaskManager, SequenceManager, ExpProtocol, EventNumber, FilePathManager,\ ChannelScaleManager, ChannelSelector, ChannelFilter, BadEpochMonitor, MRCPExtractor, MainSwitch,\ ScopeSwitch, RecordSwitch, TaskSwitch, EventPlot, SSVEPExpProtocol, EyeTracker, RunTimer, GUITimer): """ MainView class controls the GUI frontend interaction """ def __init__(self, amp_name, amp_serial, state=mp.Value('i', 1), queue=None): """ Initialize experimenter window GUI and subject view window GUI :amp_name: amplifier name passed from LSL :amp_serial: amplifier serial passed from LSL """ super(MainView, self).__init__() self.router = Router() self.ui = main_layout.Ui_MainWindow() self.ui.setupUi(self) self.window = QMainWindow() self.SV_window = subject_layout.Ui_SV() self.SV_window.setupUi(self.window) self.eye_tracker_dialog = QDialog() self.eye_tracker_window = eye_tracker_layout.Ui_Dialog() self.eye_tracker_window.setupUi(self.eye_tracker_dialog) # redirect_stdout_to_queue(logger, queue, 'INFO') logger.info('Viewer launched') self.amp_name = amp_name self.amp_serial = amp_serial self.state = state self.init_all() def init_all(self): """ Initialize specialized functions inside GUI """ self.init_config_file() self.init_loop() self.init_panel_GUI() self.init_event_functions() self.init_SV_GUI() self.init_scope_GUI() self.init_timer() # timer for scope refreshing self.init_Runtimer() # timer for record, train and test self.init_eye_tracker() def init_config_file(self): """ Initialize config file """ self.scope_settings = RawConfigParser(allow_no_value=True, inline_comment_prefixes=('#', ';')) if (len(sys.argv) == 1): self.show_channel_names = 0 self.device_name = "" else: if (sys.argv[1].find("gtec") > -1): self.device_name = "gtec" self.show_channel_names = 1 elif (sys.argv[1].find("biosemi") > -1): self.device_name = "biosemi" self.show_channel_names = 1 elif (sys.argv[1].find("hiamp") > -1): self.device_name = "hiamp" self.show_channel_names = 1 else: self.device_name = "" self.show_channel_names = 0 # self.scope_settings.read(os.getenv("HOME") + "/.scope_settings.ini") self.scope_settings.read('.scope_settings.ini') def init_loop(self): """ Initialize loop related variables like StreamReceiver and self.eeg """ self.updating = False logger.info("init_loop runs") self.sr = StreamReceiver(window_size=1, buffer_size=10, amp_serial=Variables.get_amp_serial(), amp_name=Variables.get_amp_name()) srate = int(self.sr.sample_rate) # n_channels= self.sr.channels # 12 unsigned ints (4 bytes) ########## TODO: assumkng 32 samples chunk => make it read from LSL header data = [ 'EEG', srate, ['L', 'R'], 32, len(self.sr.get_eeg_channels()), 0, self.sr.get_trigger_channel(), None, None, None, None, None ] logger.info('Trigger channel is %d' % self.sr.get_trigger_channel()) self.config = { 'id': data[0], 'sf': data[1], 'labels': data[2], 'samples': data[3], 'eeg_channels': data[4], 'exg_channels': data[5], 'tri_channels': data[6], 'eeg_type': data[8], 'exg_type': data[9], 'tri_type': data[10], 'lbl_type': data[11], 'tim_size': 1, 'idx_size': 1 } self.tri = np.zeros(self.config['samples']) self.last_tri = 0 self.eeg = np.zeros( (self.config['samples'], self.config['eeg_channels']), dtype=np.float) self.exg = np.zeros( (self.config['samples'], self.config['exg_channels']), dtype=np.float) self.ts_list = [] self.ts_list_tri = [] def init_event_functions(self): """ Initialize event listeners for widgets in GUI """ # Control buttons self.ui.pushButton_Main_switch.clicked.connect( self.onClicked_button_Main_switch) self.ui.pushButton_start_SV.clicked.connect( self.onClicked_button_start_SV) self.ui.pushButton_scope_switch.clicked.connect( self.onClicked_button_scope_switch) self.ui.pushButton_rec.clicked.connect(self.onClicked_button_rec) # self.ui.pushButton_start_train.clicked.connect(self.onClicked_button_train) # self.ui.pushButton_start_test.clicked.connect(self.onClicked_button_test) # Subject information self.ui.pushButton_save.clicked.connect( self.onClicked_button_save_subject_information) # Experimental protocol self.ui.pushButton_define_task_done.clicked.connect( self.onClicked_button_define_task_done) self.ui.pushButton_define_task_add.clicked.connect( self.onClicked_button_define_task_add) self.ui.pushButton_create_sequence.clicked.connect( self.onClicked_button_create_sequence) self.ui.pushButton_randomize.clicked.connect( self.onClicked_button_randomize) self.ui.toolButton_choose_image_task.clicked.connect( self.onClicked_toolButton_choose_image_task) self.ui.toolButton_choose_sound_task.clicked.connect( self.onClicked_toolButton_choose_sound_task) self.ui.pushButton_experimental_protocol_finish.clicked.connect( self.onClicked_experimental_protocol_finish) self.ui.pushButton_save_protocol.clicked.connect( self.onClicked_button_save_protocol) self.ui.toolButton_load_protocol.clicked.connect( self.onClicked_toolButton_load_protocol) # Event management tab self.ui.pushButton_save_event_number.clicked.connect( self.onClicked_button_save_event_number) # Oscilloscope self.ui.comboBox_scale.activated.connect( self.onActivated_combobox_scale) self.ui.spinBox_time.valueChanged.connect( self.onValueChanged_spinbox_time) self.ui.checkBox_car.stateChanged.connect( self.onActivated_checkbox_car) self.ui.checkBox_bandpass.stateChanged.connect( self.onActivated_checkbox_bandpass) self.ui.checkBox_notch.stateChanged.connect( self.onActivated_checkbox_notch) self.ui.pushButton_bp.clicked.connect(self.onClicked_button_bp) self.ui.pushButton_apply_notch.clicked.connect( self.onClicked_button_notch) self.ui.table_channels.itemSelectionChanged.connect( self.onSelectionChanged_table) self.ui.table_channels.doubleClicked.connect( self.onDoubleClicked_channel_table) self.ui.pushButton_update_channel_name.clicked.connect( self.onClicked_button_update_channel_name) self.ui.table_channels.viewport().installEventFilter(self) # SSVEP self.ui.pushButton_ssvep_task.clicked.connect( self.onClicked_pushButton_ssvep_task) # eye tracker self.ui.pushButton_open_eye_tracker_ui.clicked.connect( self.onClicked_pushButton_open_eye_tracker_ui) # MRCP tab self.ui.pushButton_temp_clear.clicked.connect( self.onClicked_button_temp_clear) self.ui.pushButton_temp_mean.clicked.connect( self.onClicked_button_temp_mean) self.ui.pushButton_temp_view.clicked.connect( self.onClicked_button_temp_view) self.ui.pushButton_temp_remove.clicked.connect( self.onClicked_button_temp_remove) def init_panel_GUI(self): """ Initialize experimenter GUI """ # Tabs self.ui.tab_experimental_protocol.setEnabled(False) self.ui.tab_subjec_information.setEnabled(False) self.ui.tab_event_and_file_management.setEnabled(False) # self.ui.tab_Oscilloscope.setEnabled(False) self.ui.tab_experiment_type.setEnabled(False) # Experimental protocol self.task_list = [] self.new_task_list = [] self.task_descriptor_list = [] self.task_image_path = "" self.task_image_path_list = [] self.task_sound_path = "" self.task_sound_path_list = [] self.task_table = np.ndarray([]) self.new_task_table = np.ndarray([]) self.task_counter = 0 self.protocol_path = "" # Button self.init_task_name_table() self.ui.groupBox_sequence_manager.setEnabled(False) # Event management tab self.event_timestamp_list = [] self.init_task_event_number_table() self.event_list = [] # Button self.ui.pushButton_save_event_number.clicked.connect( self.onClicked_button_save_event_number) self.event_file_path = "" self.mrcp_template_file_path = "" self.raw_eeg_file_path = "" self.raw_mrcp_file_path = "" self.subject_file_path = "" # Oscilloscope self.ui.comboBox_scale.setCurrentIndex(4) self.ui.checkBox_notch.setChecked(True) # self.ui.checkBox_car.setChecked( # int(self.scope_settings.get("filtering", "apply_car_filter"))) # self.ui.checkBox_bandpass.setChecked( # int(self.scope_settings.get("filtering", "apply_bandpass_filter"))) self.ui.pushButton_apply_notch.setEnabled(True) self.ui.doubleSpinBox_lc_notch.setEnabled(True) self.ui.doubleSpinBox_hc_notch.setEnabled(True) # initialize channel selection panel in main view GUI self.channels_to_show_idx = [] idx = 0 for y in range(0, 4): for x in range(0, NUM_X_CHANNELS): if idx < self.config['eeg_channels']: # self.table_channels.item(x,y).setTextAlignment(QtCore.Qt.AlignCenter) self.ui.table_channels.item(x, y).setSelected(True) # Qt5 # self.table_channels.setItemSelected(self.table_channels.item(x, y), True) # Qt4 only self.channels_to_show_idx.append(idx) else: self.ui.table_channels.setItem(x, y, QTableWidgetItem("N/A")) self.ui.table_channels.item(x, y).setFlags( QtCore.Qt.NoItemFlags) self.ui.table_channels.item(x, y).setTextAlignment( QtCore.Qt.AlignCenter) idx += 1 self.ui.table_channels.verticalHeader().setStretchLastSection(True) self.ui.table_channels.horizontalHeader().setStretchLastSection(True) self.channel_to_scale_row_index = -1 self.channel_to_scale_column_index = -1 self.selected_channel_row_index = 0 self.selected_channel_column_index = 0 self.single_channel_scale = 1 # MRCP tab self.init_class_epoch_counter_table() self.init_class_bad_epoch_table() self.show_TID_events = False self.show_LPT_events = False self.show_Key_events = False self.raw_trial_MRCP = np.ndarray([]) self.processed_trial_MRCP = np.ndarray([]) self.total_trials_MRCP = [] self.total_trials_raw_MRCP = [] self.total_MRCP_inds = [] self.temp_counter = 0 self.temp_counter_list = [] self.input_temp_list = [] self.display_temp_list = [] self.selected_temp = "" self.list_selected_temp = [] self.template_buffer = np.zeros( (6 * int(self.sr.sample_rate), self.config['eeg_channels']), dtype=float) self.b_lp, self.a_lp = Utils.butter_lowpass(3, int(self.sr.sample_rate), 2) self.b_hp, self.a_hp = Utils.butter_highpass(0.05, int(self.sr.sample_rate), 2) self.initial_condition_list_lp = Utils.construct_initial_condition_list( self.b_lp, self.a_lp, self.config['eeg_channels']) self.initial_condition_list_hp = Utils.construct_initial_condition_list( self.b_hp, self.a_hp, self.config['eeg_channels']) self.ui.pushButton_bad_epoch.clicked.connect( self.onClicked_button_bad_epoch) self.screen_width = 522 self.screen_height = 160 # self.setGeometry(100,100, self.screen_width, self.screen_height) # self.setFixedSize(self.screen_width, self.screen_height) self.setWindowTitle('EEG Scope Panel') self.setFocusPolicy(QtCore.Qt.ClickFocus) self.setFocus() logger.info('GUI show') self.show() def init_panel_GUI_stop_recording(self): """ Initialize experimenter GUI when stop recording button pressed. This is used to prepare for next run. """ # Tabs self.ui.tab_experimental_protocol.setEnabled(False) self.ui.tab_subjec_information.setEnabled(False) self.ui.tab_event_and_file_management.setEnabled(False) # self.ui.tab_Oscilloscope.setEnabled(False) self.ui.tab_experiment_type.setEnabled(False) # Experimental protocol self.task_list = [] self.new_task_list = [] self.task_descriptor_list = [] self.task_image_path = "" self.task_image_path_list = [] self.task_sound_path = "" self.task_sound_path_list = [] self.task_table = np.ndarray([]) self.new_task_table = np.ndarray([]) self.task_counter = 0 self.protocol_path = "" # Button self.init_task_name_table() self.ui.groupBox_sequence_manager.setEnabled(False) # Event management tab self.event_timestamp_list = [] self.init_task_event_number_table() self.event_list = [] # Button self.ui.pushButton_save_event_number.clicked.connect( self.onClicked_button_save_event_number) self.event_file_path = "" self.mrcp_template_file_path = "" self.raw_eeg_file_path = "" self.raw_mrcp_file_path = "" self.subject_file_path = "" # Oscilloscope self.ui.comboBox_scale.setCurrentIndex(4) self.ui.checkBox_notch.setChecked(True) # self.ui.checkBox_car.setChecked( # int(self.scope_settings.get("filtering", "apply_car_filter"))) # self.ui.checkBox_bandpass.setChecked( # int(self.scope_settings.get("filtering", "apply_bandpass_filter"))) # self.ui.pushButton_apply_notch.setEnabled(False) self.ui.doubleSpinBox_lc_notch.setEnabled(False) self.ui.doubleSpinBox_hc_notch.setEnabled(False) # # initialize channel selection panel in main view GUI # self.channels_to_show_idx = [] # idx = 0 # for y in range(0, 4): # for x in range(0, NUM_X_CHANNELS): # if idx < self.config['eeg_channels']: # # self.table_channels.item(x,y).setTextAlignment(QtCore.Qt.AlignCenter) # self.ui.table_channels.item(x, y).setSelected(True) # Qt5 # # self.table_channels.setItemSelected(self.table_channels.item(x, y), True) # Qt4 only # self.channels_to_show_idx.append(idx) # else: # self.ui.table_channels.setItem(x, y, # QTableWidgetItem("N/A")) # self.ui.table_channels.item(x, y).setFlags( # QtCore.Qt.NoItemFlags) # self.ui.table_channels.item(x, y).setTextAlignment( # QtCore.Qt.AlignCenter) # idx += 1 self.ui.table_channels.verticalHeader().setStretchLastSection(True) self.ui.table_channels.horizontalHeader().setStretchLastSection(True) self.channel_to_scale_row_index = -1 self.channel_to_scale_column_index = -1 self.selected_channel_row_index = 0 self.selected_channel_column_index = 0 self.single_channel_scale = 1 # MRCP tab self.init_class_epoch_counter_table() self.init_class_bad_epoch_table() self.show_TID_events = False self.show_LPT_events = False self.show_Key_events = False self.raw_trial_MRCP = np.ndarray([]) self.processed_trial_MRCP = np.ndarray([]) self.total_trials_MRCP = [] self.total_trials_raw_MRCP = [] self.total_MRCP_inds = [] self.temp_counter = 0 self.temp_counter_list = [] self.input_temp_list = [] self.display_temp_list = [] self.selected_temp = "" self.list_selected_temp = [] self.template_buffer = np.zeros( (6 * int(self.sr.sample_rate), self.config['eeg_channels']), dtype=float) self.b_lp, self.a_lp = Utils.butter_lowpass(3, int(self.sr.sample_rate), 2) self.b_hp, self.a_hp = Utils.butter_highpass(0.05, int(self.sr.sample_rate), 2) self.initial_condition_list_lp = Utils.construct_initial_condition_list( self.b_lp, self.a_lp, self.config['eeg_channels']) self.initial_condition_list_hp = Utils.construct_initial_condition_list( self.b_hp, self.a_hp, self.config['eeg_channels']) self.ui.pushButton_bad_epoch.clicked.connect( self.onClicked_button_bad_epoch) self.screen_width = 522 self.screen_height = 160 # self.setGeometry(100,100, self.screen_width, self.screen_height) # self.setFixedSize(self.screen_width, self.screen_height) self.setWindowTitle('EEG Scope Panel') self.setFocusPolicy(QtCore.Qt.ClickFocus) self.setFocus() self.show() def init_SV_GUI(self): """ Initialize subject view GUI """ self.SVStatus = 0 self.starttime = 0 self.SV_time = 0 self.idle_time = int(self.ui.idleTimeLineEdit.text()) self.focus_time = self.idle_time + int( self.ui.focusTimeLineEdit.text()) self.prepare_time = self.focus_time + int( self.ui.prepareTimeLineEdit.text()) self.two_time = self.prepare_time + int(self.ui.twoTimeLineEdit.text()) self.one_time = self.two_time + int(self.ui.oneTimeLineEdit.text()) self.task_time = self.one_time + int(self.ui.taskTimeLineEdit.text()) self.relax_time = self.task_time + 2 self.cycle_time = self.relax_time self.is_experiment_on = False def init_scope_GUI(self): """ Initialize oscilloscope GUI """ self.bool_parser = {True: '1', False: '0'} # PyQTGraph plot initialization self.win = pg.GraphicsWindow() self.win.setWindowTitle('EEG Scope') self.win.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint) self.win.keyPressEvent = self.keyPressEvent # self.win.show() self.main_plot_handler = self.win.addPlot() self.win.resize(1280, 800) # Scales available in the GUI. If you change the options in the GUI # you should change them here as well self.scales_range = [1, 10, 25, 50, 100, 250, 500, 1000, 2500, 100000] self.single_scales_range = [ 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.5, 1.7, 1.8, 2 ] # Scale in uV self.scale = 100 # Time window to show in seconds self.seconds_to_show = 10 # Y Tick labels. Use values from the config file. self.channel_labels = [] values = [] ''' For non-LSL systems having no channel names for x in range(0, self.config['eeg_channels']): if (self.show_channel_names): self.channel_labels.append("(" + str(x + 1) + ") " + self.scope_settings.get("internal", "channel_names_" + self.device_name + str( self.config['eeg_channels'])).split(', ')[x]) else: self.channel_labels.append('CH ' + str(x + 1)) ''' ch_names = np.array(self.sr.get_channel_names()) self.channel_labels = ch_names[self.sr.get_eeg_channels()] for x in range(0, len(self.channels_to_show_idx)): values.append((-x * self.scale, self.channel_labels[self.channels_to_show_idx[x]])) values_axis = [] values_axis.append(values) values_axis.append([]) # Update table labels with current names idx = 0 for y in range(0, 4): for x in range(0, NUM_X_CHANNELS): if (idx < self.config['eeg_channels']): self.ui.table_channels.item(x, y).setText( self.channel_labels[idx]) idx += 1 # Plot initialization # Plotting colors. If channels > 16, colors will roll back to the beginning self.colors = np.array([[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [0, 255, 255], [255, 0, 255], [128, 100, 100], [0, 128, 0], [0, 128, 128], [128, 128, 0], [255, 128, 128], [128, 0, 128], [128, 255, 0], [255, 128, 0], [0, 255, 128], [128, 0, 255]]) # pen = pg.mkColor(self.colors) # self.main_plot_handler.getAxis('left').setTextPen('b') self.main_plot_handler.getAxis('left').setTicks(values_axis) self.main_plot_handler.setRange( xRange=[0, self.seconds_to_show], yRange=[ +1.5 * self.scale, -0.5 * self.scale - self.scale * self.config['eeg_channels'] ]) self.main_plot_handler.disableAutoRange() self.main_plot_handler.showGrid(y=True) self.main_plot_handler.setLabel(axis='left', text='Scale (uV): ' + str(self.scale)) self.main_plot_handler.setLabel(axis='bottom', text='Time (s)') # X axis self.x_ticks = np.zeros(self.config['sf'] * self.seconds_to_show) for x in range(0, self.config['sf'] * self.seconds_to_show): self.x_ticks[x] = (x * 1) / float(self.config['sf']) # We want a lightweight scope, so we downsample the plotting to 64 Hz self.subsampling_value = self.config['sf'] / 64 # EEG data for plotting self.data_plot = np.zeros((self.config['sf'] * self.seconds_to_show, self.config['eeg_channels'])) print('self.data plot shape: ', self.data_plot.shape) self.curve_eeg = [] for x in range(0, len(self.channels_to_show_idx)): self.curve_eeg.append( self.main_plot_handler.plot( x=self.x_ticks, y=self.data_plot[:, self.channels_to_show_idx[x]], pen=pg.mkColor(self.colors[self.channels_to_show_idx[x] % 16, :]))) # self.curve_eeg[-1].setDownsampling(ds=self.subsampling_value, auto=False, method="mean") # Events data self.events_detected = [] self.events_curves = [] self.events_text = [] # CAR initialization self.apply_car = False self.matrix_car = np.zeros( (self.config['eeg_channels'], self.config['eeg_channels']), dtype=float) self.matrix_car[:, :] = -1 / float(self.config['eeg_channels']) np.fill_diagonal(self.matrix_car, 1 - (1 / float(self.config['eeg_channels']))) # Laplacian initalization. TO BE DONE self.matrix_lap = np.zeros( (self.config['eeg_channels'], self.config['eeg_channels']), dtype=float) np.fill_diagonal(self.matrix_lap, 1) self.matrix_lap[2, 0] = -1 self.matrix_lap[0, 2] = -0.25 self.matrix_lap[0, 2] = -0.25 # BP initialization self.apply_bandpass = 1 if (self.apply_bandpass): self.ui.doubleSpinBox_lp.setValue(40.0) self.ui.doubleSpinBox_hp.setValue(1.0) self.ui.doubleSpinBox_lp.setMinimum(0) self.ui.doubleSpinBox_lp.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_lp.setSingleStep(1) self.ui.doubleSpinBox_hp.setMinimum(0) self.ui.doubleSpinBox_hp.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_hp.setSingleStep(1) self.ui.pushButton_bp.click() # notch initialization self.apply_notch = 1 if (self.apply_notch): self.ui.doubleSpinBox_lc_notch.setValue(58.0) self.ui.doubleSpinBox_hc_notch.setValue(62.0) self.ui.doubleSpinBox_lc_notch.setMinimum(0.1) self.ui.doubleSpinBox_lc_notch.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_lc_notch.setSingleStep(1) self.ui.doubleSpinBox_hc_notch.setMinimum(0.1) self.ui.doubleSpinBox_hc_notch.setMaximum(self.sr.sample_rate / 2 - 0.1) self.ui.doubleSpinBox_hc_notch.setSingleStep(1) self.ui.pushButton_apply_notch.click() self.ui.checkBox_bandpass.setChecked(self.apply_bandpass) self.b_bandpass_scope_refilter = self.b_bandpass_scope self.a_bandpass_scope_refilter = self.a_bandpass_scope self.zi_bandpass_scope_refilter = self.zi_bandpass_scope self.b_notch_scope_refilter = self.b_notch_scope self.a_notch_scope_refilter = self.a_notch_scope self.zi_notch_scope_refilter = self.zi_notch_scope self.update_title_scope() # Help variables self.show_help = 0 self.help = pg.TextItem( "CNBI EEG Scope v0.3 \n" + "----------------------------------------------------------------------------------\n" + "C: De/activate CAR Filter\n" + "B: De/activate Bandpass Filter (with current settings)\n" + "T: Show/hide TiD events\n" + "L: Show/hide LPT events\n" + "K: Show/hide Key events. If not shown, they are NOT recorded!\n" + "0-9: Add a user-specific Key event. Do not forget to write down why you marked it.\n" + "Up, down arrow keys: Increase/decrease the scale, steps of 10 uV\n" + "Left, right arrow keys: Increase/decrease the time to show, steps of 1 s\n" + "Spacebar: Stop the scope plotting, whereas data acquisition keeps running (EXPERIMENTAL)\n" + "Esc: Exits the scope", anchor=(0, 0), border=(70, 70, 70), fill=pg.mkColor(20, 20, 20, 200), color=(255, 255, 255)) # Stop plot functionality self.stop_plot = 0 # Force repaint even when we shouldn't repaint. self.force_repaint = 1 def init_timer(self): """ Initialize main timer used for refreshing oscilloscope window. This refreshes every 20ms. """ self.os_time_list1 = [] QtCore.QCoreApplication.processEvents() QtCore.QCoreApplication.flush() self.timer = QtCore.QTimer(self) self.timer.setTimerType(QtCore.Qt.PreciseTimer) self.timer.timeout.connect(self.update_loop) self.timer.start(20) def init_Runtimer(self): """ Initialize task related timer which controls the timing for visual cues """ self.time_show = 0 self.os_time_list = [] self.Runtimer = task.LoopingCall(self.Time) def init_eye_tracker(self): self.eye_tracker_window.tableWidget.setRowCount(9) self.eye_tracker_window.pushButton_1.clicked.connect(self.update_cal1) self.eye_tracker_window.pushButton_2.clicked.connect(self.update_cal2) self.eye_tracker_window.pushButton_3.clicked.connect(self.update_cal3) self.eye_tracker_window.pushButton_4.clicked.connect(self.update_cal4) self.eye_tracker_window.pushButton_5.clicked.connect(self.update_cal5) self.eye_tracker_window.pushButton_6.clicked.connect(self.update_cal6) self.eye_tracker_window.pushButton_7.clicked.connect(self.update_cal7) self.eye_tracker_window.pushButton_8.clicked.connect(self.update_cal8) self.eye_tracker_window.pushButton_9.clicked.connect(self.update_cal9) self.eye_tracker_window.pushButton_12.clicked.connect( self.update_current_gaze_loc) self.eye_tracker_window.pushButton_13.clicked.connect( self.recording_data) self.eye_tracker_window.pushButton_14.clicked.connect( self.recording_stop) self.rec_time = int(self.eye_tracker_window.LineEdit_rec.text()) # self.LineEdit_rec.clicked.conntect(self.update_rec_time(int(self.LineEdit_rec.text()))) self.gaze_x = 0 self.gaze_y = 0 self.table_row = 0 self.table_col = 0 # print(self.gaze_x, self.gaze_y) self.UTC_time = 0 # List of values in 9 points self.points = np.zeros((9, 2)) self.gaze_loc = 0 def trigger_help(self): """Shows / hide help in the scope window""" if self.show_help: self.help.setPos(0, self.scale) self.main_plot_handler.addItem(self.help) self.help.setZValue(1) else: self.main_plot_handler.removeItem(self.help) def eventFilter(self, source, event): """ Select single channel to scale by right clicking :param source: channel table content :param event: right mouse button press :return: ID of the selected channel """ if (event.type() == QtCore.QEvent.MouseButtonPress and event.buttons() == QtCore.Qt.RightButton and source is self.ui.table_channels.viewport()): item = self.ui.table_channels.itemAt(event.pos()) # print('Global Pos:', event.globalPos()) if item is not None: self.channel_to_scale_row_index = item.row() self.channel_to_scale_column_index = item.column() print("RRRRRRRRR", self.channel_to_scale_row_index, self.channel_to_scale_column_index) # print('Table Item:', item.row(), item.column()) # self.menu = QMenu(self) # self.menu.addAction(item.text()) #(QAction('test')) # menu.exec_(event.globalPos()) return super(MainView, self).eventFilter(source, event)