def log_decoding_helper(state, event_queue, amp_name=None, amp_serial=None, autostop=False): """ Helper function to run StreamReceiver object in background """ logger.info('Event acquisition subprocess started.') # wait for the start signal while state.value == 0: time.sleep(0.01) # acquire event values and returns event times and event values sr = StreamReceiver(buffer_size=0, amp_name=amp_name, amp_serial=amp_serial) tm = qc.Timer(autoreset=True) started = False while state.value == 1: chunk, ts_list = sr.acquire() if autostop: if started is True: if len(ts_list) == 0: state.value = 0 break elif len(ts_list) > 0: started = True tm.sleep_atleast(0.001) logger.info('Event acquisition subprocess finishing up ...') buffers, times = sr.get_buffer() events = buffers[:, 0] # first channel is the trigger channel event_index = np.where(events != 0)[0] event_times = times[event_index].reshape(-1).tolist() event_values = events[event_index].tolist() assert len(event_times) == len(event_values) event_queue.put((event_times, event_values))
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 = []
def record(state, amp_name, amp_serial, record_dir, eeg_only): # set data file name filename = time.strftime(record_dir + "/%Y%m%d-%H%M%S-raw.pcl", time.localtime()) qc.print_c('>> Output file: %s' % (filename), 'W') # test writability try: qc.make_dirs(record_dir) open( filename, 'w').write('The data will written when the recording is finished.') except: raise RuntimeError('Problem writing to %s. Check permission.' % filename) # start a server for sending out data filename when software trigger is used outlet = start_server('StreamRecorderInfo', channel_format='string',\ source_id=filename, stype='Markers') # connect to EEG stream server sr = StreamReceiver(amp_name=amp_name, amp_serial=amp_serial, eeg_only=eeg_only) # start recording qc.print_c('\n>> Recording started (PID %d).' % os.getpid(), 'W') qc.print_c('\n>> Press Enter to stop recording', 'G') tm = qc.Timer(autoreset=True) next_sec = 1 while state.value == 1: sr.acquire() if sr.get_buflen() > next_sec: duration = str(datetime.timedelta(seconds=int(sr.get_buflen()))) print('RECORDING %s' % duration) next_sec += 1 tm.sleep_atleast(0.01) # record stop qc.print_c('>> Stop requested. Copying buffer', 'G') buffers, times = sr.get_buffer() signals = buffers events = None # channels = total channels from amp, including trigger channel data = { 'signals': signals, 'timestamps': times, 'events': events, 'sample_rate': sr.get_sample_rate(), 'channels': sr.get_num_channels(), 'ch_names': sr.get_channel_names() } qc.print_c('Saving raw data ...', 'W') qc.save_obj(filename, data) print('Saved to %s\n' % filename) qc.print_c('Converting raw file into a fif format.', 'W') pcl2fif(filename)
def record(recordState, amp_name, amp_serial, record_dir, eeg_only, recordLogger=logger, queue=None): redirect_stdout_to_queue(recordLogger, queue, 'INFO') # set data file name timestamp = time.strftime('%Y%m%d-%H%M%S', time.localtime()) pcl_file = "%s/%s-raw.pcl" % (record_dir, timestamp) eve_file = '%s/%s-eve.txt' % (record_dir, timestamp) recordLogger.info('>> Output file: %s' % (pcl_file)) # test writability try: qc.make_dirs(record_dir) open(pcl_file, 'w').write('The data will written when the recording is finished.') except: raise RuntimeError('Problem writing to %s. Check permission.' % pcl_file) # start a server for sending out data pcl_file when software trigger is used outlet = start_server('StreamRecorderInfo', channel_format='string',\ source_id=eve_file, stype='Markers') # connect to EEG stream server sr = StreamReceiver(buffer_size=0, amp_name=amp_name, amp_serial=amp_serial, eeg_only=eeg_only) # start recording recordLogger.info('\n>> Recording started (PID %d).' % os.getpid()) qc.print_c('\n>> Press Enter to stop recording', 'G') tm = qc.Timer(autoreset=True) next_sec = 1 while recordState.value == 1: sr.acquire() if sr.get_buflen() > next_sec: duration = str(datetime.timedelta(seconds=int(sr.get_buflen()))) recordLogger.info('RECORDING %s' % duration) next_sec += 1 tm.sleep_atleast(0.001) # record stop recordLogger.info('>> Stop requested. Copying buffer') buffers, times = sr.get_buffer() signals = buffers events = None # channels = total channels from amp, including trigger channel data = {'signals':signals, 'timestamps':times, 'events':events, 'sample_rate':sr.get_sample_rate(), 'channels':sr.get_num_channels(), 'ch_names':sr.get_channel_names(), 'lsl_time_offset':sr.lsl_time_offset} recordLogger.info('Saving raw data ...') qc.save_obj(pcl_file, data) recordLogger.info('Saved to %s\n' % pcl_file) # automatically convert to fif and use event file if it exists (software trigger) if os.path.exists(eve_file): recordLogger.info('Found matching event file, adding events.') else: eve_file = None recordLogger.info('Converting raw file into fif.') pcl2fif(pcl_file, external_event=eve_file)
def __init__(self): self.start_recording = False self.new_data = [] self.sr = StreamReceiver(buffer_size=0) self.record_dir = '%s/records' % os.getcwd() self.current_window = np.ndarray([]) self.current_time_stamps = np.ndarray([]) self.MRCP_window_size = 6 print("I RUMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN")
def start_recording_data(self): """ Start recording data, set the flag to Ture and create a new thread """ self.is_recording_running = True self.streamReceiver = StreamReceiver(buffer_size=0, amp_serial=Variables.get_amp_serial(), amp_name=Variables.get_amp_name()) self.thread = threading.Thread(target=self.record) self.thread.start()
# select the same channels used for training w = w[picks] # debug: show max - min # c=1; print( '### %d: %.1f - %.1f = %.1f'% ( picks[c], max(w[c]), min(w[c]), max(w[c])-min(w[c]) ) ) # psde.transform = [channels x freqs] psd = psde.transform(w) return psd if __name__ == '__main__': sr = StreamReceiver(window_size=w_seconds, amp_name=amp_name, amp_serial=amp_serial) sfreq = sr.sample_rate psde = PSDEstimator(sfreq=sfreq, fmin=fmin, fmax=fmax, bandwidth=None, adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None) ch_names = sr.get_channel_names() fq_res = 1 / w_seconds hz_list = [] f = fmin
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)
def __init__(self, lpttype='USB2LPT', portaddr=None, verbose=True): self.evefile = None self.lpttype = lpttype self.verbose = verbose if self.lpttype in ['USB2LPT', 'DESKTOP']: if self.lpttype == 'USB2LPT': if ctypes.sizeof(ctypes.c_voidp) == 4: dllname = 'LptControl_USB2LPT32.dll' # 32 bit else: dllname = 'LptControl_USB2LPT64.dll' # 64 bit if portaddr not in [0x278, 0x378]: self.print('Warning: LPT port address %d is unusual.' % portaddr) elif self.lpttype == 'DESKTOP': if ctypes.sizeof(ctypes.c_voidp) == 4: dllname = 'LptControl_Desktop32.dll' # 32 bit else: dllname = 'LptControl_Desktop64.dll' # 64 bit if portaddr not in [0x278, 0x378]: self.print('Warning: LPT port address %d is unusual.' % portaddr) self.portaddr = portaddr search = [] search.append(os.path.dirname(__file__) + '/' + dllname) search.append(os.path.dirname(__file__) + '/libs/' + dllname) search.append(os.getcwd() + '/' + dllname) search.append(os.getcwd() + '/libs/' + dllname) for f in search: if os.path.exists(f): dllpath = f break else: self.print('ERROR: Cannot find the required library %s' % dllname) raise RuntimeError self.print('Loading %s' % dllpath) self.lpt = ctypes.cdll.LoadLibrary(dllpath) elif self.lpttype == 'ARDUINO': import serial, serial.tools.list_ports BAUD_RATE = 115200 # portaddr should be None or in the form of 'COM1', 'COM2', etc. if portaddr is None: arduinos = [x for x in serial.tools.list_ports.grep('Arduino')] if len(arduinos) == 0: print('No Arduino found. Stop.') sys.exit() for i, a in enumerate(arduinos): print('Found', a[0]) try: com_port = arduinos[0].device except AttributeError: # depends on Python distribution com_port = arduinos[0][0] else: com_port = portaddr self.ser = serial.Serial(com_port, BAUD_RATE) time.sleep(1) # doesn't work without this delay. why? print('Connected to %s.' % com_port) elif self.lpttype == 'SOFTWARE': from pycnbi.stream_receiver.stream_receiver import StreamReceiver self.print('Using software trigger') # get data file location LSL_SERVER = 'StreamRecorderInfo' inlet = cnbi_lsl.start_client(LSL_SERVER) fname = inlet.info().source_id() if fname[-4:] != '.pcl': self.print('ERROR: Received wrong record file name format %s' % fname) sys.exit(-1) evefile = fname[:-8] + '-eve.txt' eveoffset_file = fname[:-8] + '-eve-offset.txt' self.print('Event file is: %s' % evefile) self.evefile = open(evefile, 'a') # check server LSL time server integrity self.print( "Checking LSL server's timestamp integrity for logging software triggers." ) amp_name, amp_serial = pu.search_lsl() sr = StreamReceiver(window_size=1, buffer_size=1, amp_serial=amp_serial, eeg_only=False, amp_name=amp_name) local_time = pylsl.local_clock() server_time = sr.get_window_list()[1][-1] lsl_time_offset = local_time - server_time with open(eveoffset_file, 'a') as f: f.write('Local time: %.6f, Server time: %.6f, Offset: %.6f\n' % (local_time, server_time, lsl_time_offset)) self.print('LSL timestamp offset (%.3f) saved to %s' % (lsl_time_offset, eveoffset_file)) elif self.lpttype == 'FAKE' or self.lpttype is None or self.lpttype is False: self.print('WARNING: Using a fake trigger.') self.lpttype = 'FAKE' self.lpt = None else: self.print('ERROR: Unknown LPT port type %s' % lpttype) sys.exit(-1)
TIME_INDEX = None # integer or None. None = average of raw values of the current window SHOW_PSD = False import os import mne import numpy as np import matplotlib.pyplot as plt from pycnbi.stream_receiver.stream_receiver import StreamReceiver import pycnbi.utils.pycnbi_utils as pu import pycnbi.utils.q_common as qc mne.set_log_level('ERROR') # actually improves performance for multitaper os.environ['OMP_NUM_THREADS'] = '1' amp_name, amp_serial = pu.search_lsl() stream_receiver = StreamReceiver( window_size=1, buffer_size=1, amp_serial=amp_serial, eeg_only=False, amp_name=amp_name) sfreq = stream_receiver.get_sample_rate() watchdog = qc.Timer() tm = qc.Timer(autoreset=True) trg_ch = stream_receiver.get_trigger_channel() last_ts = 0 qc.print_c('Trigger channel: %d' % trg_ch, 'G') if SHOW_PSD: psde = mne.decoding.PSDEstimator(sfreq=sfreq, fmin=1, fmax=50, bandwidth=None, adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None) sfreq = 512 interval = 1. / sfreq fig, ax = plt.subplots(1, 1) time_range = 1
class Hardware(_hardware.HardwareAdditionalMethods): """ Hardware class controls recording data in a different thread """ def __init__(self): logger.info('TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT') self.is_recording_running = False self.recorded_data = np.array([]) self.current_window = np.ndarray([]) self.current_time_stamps = np.ndarray([]) self.MRCP_window_size = 6 self.finished_recording = False self.lsl_time_list = [] self.server_time_list = [] self.offset_time_list = [] self.eeg_file_path = '' def connect_with_hardware(self): pass def is_recording_running(self): """ Check if record start or stop button pressed """ return self.is_recording_running def start_recording_data(self): """ Start recording data, set the flag to Ture and create a new thread """ self.is_recording_running = True self.streamReceiver = StreamReceiver(buffer_size=0, amp_serial=Variables.get_amp_serial(), amp_name=Variables.get_amp_name()) self.thread = threading.Thread(target=self.record) self.thread.start() def stop_recording_data(self): """ Stop recording by setting the flag to False """ self.is_recording_running = False def cancel_recording_data(self): """ Interrupt recording and delete current buffer """ self.is_recording_running = False self.recorded_data = np.array([]) def get_current_window(self): """ Get one window data :return: window data """ return self.current_window def get_LSL_clock(self): """ Get timestamps of LSL :return: current LSL timestamps """ return self.streamReceiver.get_lsl_clock() def get_lsl_offset(self): """ Get offset calculated by LSL, which is the delay of transmission :return: LSL offset """ return self.streamReceiver.get_lsl_offset() def get_server_clock(self): """ Get timestamps from instrument :return: timestamps of instrument """ return self.streamReceiver.get_server_clock() def set_eeg_file_path(self): """ Set recording directory by reading path from Variable class """ self.eeg_file_path = Variables.get_raw_eeg_file_path()
def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None): """ Params ------ classifier: classifier file spatial: spatial filter to use buffer_size: length of the signal buffer in seconds """ self.classifier = classifier self.buffer_sec = buffer_size self.fake = fake self.amp_serial = amp_serial self.amp_name = amp_name if self.fake == False: model = qc.load_obj(self.classifier) if model == None: self.print('Error loading %s' % model) sys.exit(-1) self.cls = model['cls'] self.psde = model['psde'] self.labels = list(self.cls.classes_) self.label_names = [model['classes'][k] for k in self.labels] self.spatial = model['spatial'] self.spectral = model['spectral'] self.notch = model['notch'] self.w_seconds = model['w_seconds'] self.w_frames = model['w_frames'] self.wstep = model['wstep'] self.sfreq = model['sfreq'] if not int(self.sfreq * self.w_seconds) == self.w_frames: raise RuntimeError('sfreq * w_sec %d != w_frames %d' % (int(self.sfreq * self.w_seconds), self.w_frames)) if 'multiplier' in model: self.multiplier = model['multiplier'] else: self.multiplier = 1 # Stream Receiver self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial) if self.sfreq != self.sr.sample_rate: raise RuntimeError('Amplifier sampling rate (%.1f) != model sampling rate (%.1f). Stop.' % ( self.sr.sample_rate, self.sfreq)) # Map channel indices based on channel names of the streaming server self.spatial_ch = model['spatial_ch'] self.spectral_ch = model['spectral_ch'] self.notch_ch = model['notch_ch'] self.ref_new = model['ref_new'] self.ref_old = model['ref_old'] self.ch_names = self.sr.get_channel_names() mc = model['ch_names'] self.picks = [self.ch_names.index(mc[p]) for p in model['picks']] if self.spatial_ch is not None: self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']] if self.spectral_ch is not None: self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']] if self.notch_ch is not None: self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']] if self.ref_new is not None: self.ref_new = self.ch_names.index(mc[model['ref_new']]) if self.ref_old is not None: self.ref_old = self.ch_names.index(mc[model['ref_old']]) # PSD buffer psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames))) self.psd_shape = psd_temp.shape self.psd_size = psd_temp.size self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2])) self.ts_buffer = [] else: # Fake left-right decoder model = None self.psd_shape = None self.psd_size = None # TODO: parameterize directions using fake_dirs self.labels = [11, 9] self.label_names = ['LEFT_GO', 'RIGHT_GO']
class BCIDecoder(object): """ Decoder class The label order of self.labels and self.label_names match likelihood orders computed by get_prob() """ def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None): """ Params ------ classifier: classifier file spatial: spatial filter to use buffer_size: length of the signal buffer in seconds """ self.classifier = classifier self.buffer_sec = buffer_size self.fake = fake self.amp_serial = amp_serial self.amp_name = amp_name if self.fake == False: model = qc.load_obj(self.classifier) if model == None: self.print('Error loading %s' % model) sys.exit(-1) self.cls = model['cls'] self.psde = model['psde'] self.labels = list(self.cls.classes_) self.label_names = [model['classes'][k] for k in self.labels] self.spatial = model['spatial'] self.spectral = model['spectral'] self.notch = model['notch'] self.w_seconds = model['w_seconds'] self.w_frames = model['w_frames'] self.wstep = model['wstep'] self.sfreq = model['sfreq'] if not int(self.sfreq * self.w_seconds) == self.w_frames: raise RuntimeError('sfreq * w_sec %d != w_frames %d' % (int(self.sfreq * self.w_seconds), self.w_frames)) if 'multiplier' in model: self.multiplier = model['multiplier'] else: self.multiplier = 1 # Stream Receiver self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial) if self.sfreq != self.sr.sample_rate: raise RuntimeError('Amplifier sampling rate (%.1f) != model sampling rate (%.1f). Stop.' % ( self.sr.sample_rate, self.sfreq)) # Map channel indices based on channel names of the streaming server self.spatial_ch = model['spatial_ch'] self.spectral_ch = model['spectral_ch'] self.notch_ch = model['notch_ch'] self.ref_new = model['ref_new'] self.ref_old = model['ref_old'] self.ch_names = self.sr.get_channel_names() mc = model['ch_names'] self.picks = [self.ch_names.index(mc[p]) for p in model['picks']] if self.spatial_ch is not None: self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']] if self.spectral_ch is not None: self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']] if self.notch_ch is not None: self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']] if self.ref_new is not None: self.ref_new = self.ch_names.index(mc[model['ref_new']]) if self.ref_old is not None: self.ref_old = self.ch_names.index(mc[model['ref_old']]) # PSD buffer psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames))) self.psd_shape = psd_temp.shape self.psd_size = psd_temp.size self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2])) self.ts_buffer = [] else: # Fake left-right decoder model = None self.psd_shape = None self.psd_size = None # TODO: parameterize directions using fake_dirs self.labels = [11, 9] self.label_names = ['LEFT_GO', 'RIGHT_GO'] def print(self, *args): if len(args) > 0: print('[BCIDecoder] ', end='') print(*args) def get_labels(self): """ Returns ------- Class labels numbers in the same order as the likelihoods returned by get_prob() """ return self.labels def get_label_names(self): """ Returns ------- Class label names in the same order as get_labels() """ return self.label_names def start(self): pass def stop(self): pass def get_prob(self): """ Read the latest window Returns ------- The likelihood P(X|C), where X=window, C=model """ if self.fake: # fake deocder: biased likelihood for the first class probs = [random.uniform(0.0, 1.0)] # others class likelihoods are just set to equal p_others = (1 - probs[0]) / (len(self.labels) - 1) for x in range(1, len(self.labels)): probs.append(p_others) time.sleep(0.0625) # simulated delay for PSD + RF else: self.sr.acquire() w, ts = self.sr.get_window() # w = times x channels w = w.T # -> channels x times # apply filters. Important: maintain the original channel order at this point. pu.preprocess(w, sfreq=self.sfreq, spatial=self.spatial, spatial_ch=self.spatial_ch, spectral=self.spectral, spectral_ch=self.spectral_ch, notch=self.notch, notch_ch=self.notch_ch, multiplier=self.multiplier) # select the same channels used for training w = w[self.picks] # debug: show max - min # c=1; print( '### %d: %.1f - %.1f = %.1f'% ( self.picks[c], max(w[c]), min(w[c]), max(w[c])-min(w[c]) ) ) # psd = channels x freqs psd = self.psde.transform(w.reshape((1, w.shape[0], w.shape[1]))) # update psd buffer ( < 1 msec overhead ) self.psd_buffer = np.concatenate((self.psd_buffer, psd), axis=0) self.ts_buffer.append(ts[0]) if ts[0] - self.ts_buffer[0] > self.buffer_sec: # search speed comparison for ordered arrays: # http://stackoverflow.com/questions/16243955/numpy-first-occurence-of-value-greater-than-existing-value t_index = np.searchsorted(self.ts_buffer, ts[0] - 1.0) self.ts_buffer = self.ts_buffer[t_index:] self.psd_buffer = self.psd_buffer[t_index:, :, :] # numpy delete is slower # assert ts[0] - self.ts_buffer[0] <= self.buffer_sec # make a feautre vector and classify feats = np.concatenate(psd[0]).reshape(1, -1) # compute likelihoods probs = self.cls.predict_proba(feats)[0] return probs def get_prob_unread(self): return self.get_prob() def get_psd(self): """ Returns ------- The latest computed PSD """ return self.psd_buffer[-1].reshape((1, -1)) def is_ready(self): """ Ready to decode? Returns True if buffer is not empty. """ return self.sr.is_ready()
def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None): """ Params ------ classifier: classifier file spatial: spatial filter to use buffer_size: length of the signal buffer in seconds """ self.classifier = classifier self.buffer_sec = buffer_size self.fake = fake self.amp_serial = amp_serial self.amp_name = amp_name if self.fake == False: model = qc.load_obj(self.classifier) if model is None: logger.error('Classifier model is None.') raise ValueError self.cls = model['cls'] self.psde = model['psde'] self.labels = list(self.cls.classes_) self.label_names = [model['classes'][k] for k in self.labels] self.spatial = model['spatial'] self.spectral = model['spectral'] self.notch = model['notch'] self.w_seconds = model['w_seconds'] self.w_frames = model['w_frames'] self.wstep = model['wstep'] self.sfreq = model['sfreq'] if 'decim' not in model: model['decim'] = 1 self.decim = model['decim'] if not int(round(self.sfreq * self.w_seconds)) == self.w_frames: logger.error('sfreq * w_sec %d != w_frames %d' % (int(round(self.sfreq * self.w_seconds)), self.w_frames)) raise RuntimeError if 'multiplier' in model: self.multiplier = model['multiplier'] else: self.multiplier = 1 # Stream Receiver self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial) if self.sfreq != self.sr.sample_rate: logger.error('Amplifier sampling rate (%.3f) != model sampling rate (%.3f). Stop.' % (self.sr.sample_rate, self.sfreq)) raise RuntimeError # Map channel indices based on channel names of the streaming server self.spatial_ch = model['spatial_ch'] self.spectral_ch = model['spectral_ch'] self.notch_ch = model['notch_ch'] #self.ref_ch = model['ref_ch'] # not supported yet self.ch_names = self.sr.get_channel_names() mc = model['ch_names'] self.picks = [self.ch_names.index(mc[p]) for p in model['picks']] if self.spatial_ch is not None: self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']] if self.spectral_ch is not None: self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']] if self.notch_ch is not None: self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']] # PSD buffer #psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames // self.decim))) #self.psd_shape = psd_temp.shape #self.psd_size = psd_temp.size #self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2])) #self.psd_buffer = None self.ts_buffer = [] logger.info_green('Loaded classifier %s (sfreq=%.3f, decim=%d)' % (' vs '.join(self.label_names), self.sfreq, self.decim)) else: # Fake left-right decoder model = None self.psd_shape = None self.psd_size = None # TODO: parameterize directions using fake_dirs self.labels = [11, 9] self.label_names = ['LEFT_GO', 'RIGHT_GO']
class BCIDecoder(object): """ Decoder class The label order of self.labels and self.label_names match likelihood orders computed by get_prob() """ def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None): """ Params ------ classifier: classifier file spatial: spatial filter to use buffer_size: length of the signal buffer in seconds """ self.classifier = classifier self.buffer_sec = buffer_size self.fake = fake self.amp_serial = amp_serial self.amp_name = amp_name if self.fake == False: model = qc.load_obj(self.classifier) if model is None: logger.error('Classifier model is None.') raise ValueError self.cls = model['cls'] self.psde = model['psde'] self.labels = list(self.cls.classes_) self.label_names = [model['classes'][k] for k in self.labels] self.spatial = model['spatial'] self.spectral = model['spectral'] self.notch = model['notch'] self.w_seconds = model['w_seconds'] self.w_frames = model['w_frames'] self.wstep = model['wstep'] self.sfreq = model['sfreq'] if 'decim' not in model: model['decim'] = 1 self.decim = model['decim'] if not int(round(self.sfreq * self.w_seconds)) == self.w_frames: logger.error('sfreq * w_sec %d != w_frames %d' % (int(round(self.sfreq * self.w_seconds)), self.w_frames)) raise RuntimeError if 'multiplier' in model: self.multiplier = model['multiplier'] else: self.multiplier = 1 # Stream Receiver self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial) if self.sfreq != self.sr.sample_rate: logger.error('Amplifier sampling rate (%.3f) != model sampling rate (%.3f). Stop.' % (self.sr.sample_rate, self.sfreq)) raise RuntimeError # Map channel indices based on channel names of the streaming server self.spatial_ch = model['spatial_ch'] self.spectral_ch = model['spectral_ch'] self.notch_ch = model['notch_ch'] #self.ref_ch = model['ref_ch'] # not supported yet self.ch_names = self.sr.get_channel_names() mc = model['ch_names'] self.picks = [self.ch_names.index(mc[p]) for p in model['picks']] if self.spatial_ch is not None: self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']] if self.spectral_ch is not None: self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']] if self.notch_ch is not None: self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']] # PSD buffer #psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames // self.decim))) #self.psd_shape = psd_temp.shape #self.psd_size = psd_temp.size #self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2])) #self.psd_buffer = None self.ts_buffer = [] logger.info_green('Loaded classifier %s (sfreq=%.3f, decim=%d)' % (' vs '.join(self.label_names), self.sfreq, self.decim)) else: # Fake left-right decoder model = None self.psd_shape = None self.psd_size = None # TODO: parameterize directions using fake_dirs self.labels = [11, 9] self.label_names = ['LEFT_GO', 'RIGHT_GO'] def get_labels(self): """ Returns ------- Class labels numbers in the same order as the likelihoods returned by get_prob() """ return self.labels def get_label_names(self): """ Returns ------- Class label names in the same order as get_labels() """ return self.label_names def start(self): pass def stop(self): pass def get_prob(self, timestamp=False): """ Read the latest window Input ----- timestamp: If True, returns LSL timestamp of the leading edge of the window used for decoding. Returns ------- The likelihood P(X|C), where X=window, C=model """ if self.fake: # fake deocder: biased likelihood for the first class probs = [random.uniform(0.0, 1.0)] # others class likelihoods are just set to equal p_others = (1 - probs[0]) / (len(self.labels) - 1) for x in range(1, len(self.labels)): probs.append(p_others) time.sleep(0.0625) # simulated delay t_prob = pylsl.local_clock() else: self.sr.acquire(blocking=True) w, ts = self.sr.get_window() # w = times x channels t_prob = ts[-1] w = w.T # -> channels x times # re-reference channels # TODO: add re-referencing function to preprocess() # apply filters. Important: maintain the original channel order at this point. w = pu.preprocess(w, sfreq=self.sfreq, spatial=self.spatial, spatial_ch=self.spatial_ch, spectral=self.spectral, spectral_ch=self.spectral_ch, notch=self.notch, notch_ch=self.notch_ch, multiplier=self.multiplier, decim=self.decim) # select the same channels used for training w = w[self.picks] # debug: show max - min # c=1; print( '### %d: %.1f - %.1f = %.1f'% ( self.picks[c], max(w[c]), min(w[c]), max(w[c])-min(w[c]) ) ) # psd = channels x freqs psd = self.psde.transform(w.reshape((1, w.shape[0], w.shape[1]))) # make a feautre vector and classify feats = np.concatenate(psd[0]).reshape(1, -1) # compute likelihoods probs = self.cls.predict_proba(feats)[0] # update psd buffer ( < 1 msec overhead ) ''' if self.psd_buffer is None: self.psd_buffer = psd else: self.psd_buffer = np.concatenate((self.psd_buffer, psd), axis=0) # TODO: CHECK THIS BLOCK self.ts_buffer.append(ts[0]) if ts[0] - self.ts_buffer[0] > self.buffer_sec: # search speed comparison for ordered arrays: # http://stackoverflow.com/questions/16243955/numpy-first-occurence-of-value-greater-than-existing-value #t_index = np.searchsorted(self.ts_buffer, ts[0] - 1.0) t_index = np.searchsorted(self.ts_buffer, ts[0] - self.buffer_sec) self.ts_buffer = self.ts_buffer[t_index:] self.psd_buffer = self.psd_buffer[t_index:, :, :] # numpy delete is slower # assert ts[0] - self.ts_buffer[0] <= self.buffer_sec ''' if timestamp: return probs, t_prob else: return probs def get_prob_unread(self, timestamp=False): return self.get_prob(timestamp) def get_psd(self): """ Returns ------- The latest computed PSD """ raise NotImplementedError('Sorry! PSD buffer is under testing.') return self.psd_buffer[-1].reshape((1, -1)) def is_ready(self): """ Ready to decode? Returns True if buffer is not empty. """ return self.sr.is_ready()
if __name__ == '__main__': brainhack = BrainHackEEGProcessing(sampling_frequency=300, eeg_ch_names=EEG_CH_NAMES.copy()) arduino = ArduinoCommHandler(port_name='/dev/ttyACM0', baudrate=115200) arduino.start_communication() leds_values = [0] * 191 leds_values_index_for_test = 0 mne.set_log_level('ERROR') # actually improves performance for multitaper os.environ['OMP_NUM_THREADS'] = '1' amp_name, amp_serial = pu.search_lsl() sr = StreamReceiver( window_size=1, buffer_size=1, amp_name=amp_name, amp_serial=amp_serial, eeg_only=True ) sfreq = sr.get_sample_rate() watchdog = qc.Timer() tm = qc.Timer(autoreset=True) trg_ch = sr.get_trigger_channel() last_ts = 0 # qc.print_c('Trigger channel: %d' % trg_ch, 'G') fmin = 1 fmax = 40 psde = mne.decoding.PSDEstimator( sfreq=sfreq, fmin=fmin, fmax=fmax, bandwidth=None, adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None )
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 RecordingFromHardWare(): def __init__(self): self.start_recording = False self.new_data = [] self.sr = StreamReceiver(buffer_size=0) self.record_dir = '%s/records' % os.getcwd() self.current_window = np.ndarray([]) self.current_time_stamps = np.ndarray([]) self.MRCP_window_size = 6 print("I RUMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN") # ============================================================================= # def runInfinitecording(self): # while self.start_recording: # self.sr.acquire() # if self.sr.get_buflen() > 20: # duration = str(datetime.timedelta(seconds=int(self.sr.get_buflen()))) # #recordLogger.info('RECORDING %s' % duration) # #next_sec += 1 # buffers, times = self.sr.get_buffer() # signals = buffers # events = None # # # channels = total channels from amp, including trigger channel # data = {'signals':signals, 'timestamps':times, 'events':events, # 'sample_rate':self.sr.get_sample_rate(), 'channels':self.sr.get_num_channels(), # 'ch_names':self.sr.get_channel_names(), 'lsl_time_offset':self.sr.lsl_time_offset} # print(data) # self.new_data.append(data) # ============================================================================= def startRecording(self): # do some stuff self.start_recording = True #download_thread = threading.Thread(target=self.runInfinitecording) recordLogger = logger thread = threading.Thread(target=self.record) thread.start() def stopRecording(self): self.start_recording = False #Write into file #self.new_data = [] def isRecordingIsRunning(self): return self.start_recording def set_MRCP_window_size(self, MRCP_window_size): self.MRCP_window_size = MRCP_window_size def record(self): recordLogger = logger #redirect_stdout_to_queue(recordLogger, 'INFO') # set data file name timestamp = time.strftime('%Y%m%d-%H%M%S', time.localtime()) pcl_file = "%s/%s-raw.pcl" % (self.record_dir, timestamp) eve_file = '%s/%s-eve.txt' % (self.record_dir, timestamp) recordLogger.info('>> Output file: %s' % (pcl_file)) # test writability try: qc.make_dirs(self.record_dir) open(pcl_file, 'w').write( 'The data will written when the recording is finished.') except: raise RuntimeError('Problem writing to %s. Check permission.' % pcl_file) # start a server for sending out data pcl_file when software trigger is used outlet = start_server('StreamRecorderInfo', channel_format='string',\ source_id=eve_file, stype='Markers') # connect to EEG stream server #sr = StreamReceiver(buffer_size=0, eeg_only=eeg_only) # start recording recordLogger.info('\n>> Recording started (PID %d).' % os.getpid()) qc.print_c('\n>> Press Enter to stop recording', 'G') tm = qc.Timer(autoreset=True) next_sec = 1 while self.start_recording: self.sr.acquire() if self.sr.get_buflen() > next_sec: duration = str( datetime.timedelta(seconds=int(self.sr.get_buflen()))) #recordLogger.info('RECORDING %s' % duration) next_sec += 1 #current_buffer, current_times = self.sr.get_buffer() self.sr.set_window_size(self.MRCP_window_size) self.current_window, self.current_time_stamps = self.sr.get_window( ) #print("window shape: {} time stamps shape: {}".format(self.current_window.shape, self.current_time_stamps.shape)) tm.sleep_atleast(0.001) # record stop recordLogger.info('>> Stop requested. Copying buffer') buffers, times = self.sr.get_buffer() signals = buffers events = None # channels = total channels from amp, including trigger channel data = { 'signals': signals, 'timestamps': times, 'events': events, 'sample_rate': self.sr.get_sample_rate(), 'channels': self.sr.get_num_channels(), 'ch_names': self.sr.get_channel_names(), 'lsl_time_offset': self.sr.lsl_time_offset } print("data length : {}".format(data['signals'].shape)) self.new_data = data['signals'] recordLogger.info('Saving raw data ...') qc.save_obj(pcl_file, data) recordLogger.info('Saved to %s\n' % pcl_file) # automatically convert to fif and use event file if it exists (software trigger) if os.path.exists(eve_file): recordLogger.info('Found matching event file, adding events.') else: eve_file = None recordLogger.info('Converting raw file into fif.') pcl2fif(pcl_file, external_event=eve_file)