def __init__(self, sub_id, proc_id, f_name): self.f_name = f_name self.reader = BlackrockRawIO(f_name, nsx_to_load=5) self.db_wrapper = DBWrapper() # signal settings self.SR = None self.channels = [] self.depths = [] self.depth_times = [] self.rec_start_time = None self.prepare_depth_values() # settings self.subject_settings = {'id': sub_id} self.procedure_settings = {'target_name': None, 'type': 'surgical'} self.buffer_settings = {'sampling_rate': 30000, 'buffer_length': '6.000', 'sample_length': '4.000', 'delay_buffer': '0.500', 'overwrite_depth': True, 'run_buffer': False, 'electrode_settings': {}} self.procedure_name = 'Proc' + str(proc_id) for electrode in self.channels: self.buffer_settings['electrode_settings'][electrode] = {'threshold': True, 'validity': 90.0} self.features_settings = {} # manage settings and start process self.manage_settings()
def __init__(self): super(FeaturesGUI, self).__init__() self.setWindowTitle('FeaturesGUI') self.plot_widget = None # settings dictionaries self.subject_settings = {} self.procedure_settings = {} self.buffer_settings = {} self.features_settings = {} # DB wrapper self.db_wrapper = DBWrapper()
def __init__(self, features_settings): super(FeaturesWidget, self).__init__() # Settings self.feature_categories = DBWrapper().all_features.keys() self.features_settings = features_settings if not self.features_settings: self.features_settings['features'] = {} # Check if default values are defined for cat in self.feature_categories: # defaults to true, compute all features self.features_settings['features'][cat] = True self.features_widgets = {} features_layout = QGridLayout(self) # Add an option to toggle all features self.all_features = QCheckBox('All') self.all_features.setChecked(False) self.all_features.clicked.connect(self.toggle_all) features_layout.addWidget(self.all_features, 0, 0, 1, 1) if 'features' in self.features_settings.keys(): for idx, (label, sett) in enumerate( self.features_settings['features'].items()): self.features_widgets[label] = QCheckBox(label) self.features_widgets[label].setChecked(sett) self.features_widgets[label].clicked.connect(self.toggle) features_layout.addWidget(self.features_widgets[label], idx + 1, 0, 1, 1)
def __init__(self, subject_settings): super(SubjectWidget, self).__init__() self.subject_enums = DBWrapper().return_enums('subject') subject_layout = QGridLayout(self) subject_layout.setColumnMinimumWidth(2, 60) subject_layout.addWidget(QLabel("Id: "), 0, 0, 1, 1) self.id_combo = QComboBox() self.id_combo.setEditable(True) self.id_combo.addItem('') self.id_combo.addItems(DBWrapper().list_all_subjects()) self.id_combo.currentIndexChanged.connect(self.load_subject) self.id_combo.lineEdit().editingFinished.connect(self.check_subject) subject_layout.addWidget(self.id_combo, 0, 1, 1, 4) subject_layout.addWidget(QLabel("Name: "), 1, 0, 1, 1) self.name_edit = QLineEdit() self.name_edit.setMaxLength(135) subject_layout.addWidget(self.name_edit, 1, 1, 1, 4) subject_layout.addWidget(QLabel("Sex: "), 2, 0, 1, 1) self.sex_combo = QComboBox() self.sex_combo.addItems(self.subject_enums['sex'] if 'sex' in self.subject_enums.keys() else []) self.sex_combo.setCurrentIndex(0) subject_layout.addWidget(self.sex_combo, 2, 1, 1, 1) subject_layout.addWidget((QLabel("Date of birth: ")), 3, 0, 1, 1) self.dob_calendar = QCalendarWidget() subject_layout.addWidget(self.dob_calendar, 3, 1, 1, 3) subject_layout.addWidget(QLabel("NSP file comment: "), 4, 0, 1, 1) self.file_comment = QTextEdit("") self.file_comment.setMaximumHeight(150) subject_layout.addWidget(self.file_comment, 4, 1, 1, 4) # Subject Settings self.subject_settings = subject_settings if not self.subject_settings: self.update_settings_from_db(-1) self.update_subject()
def check_all_procedures(self, subject_id, block): self.prev_proc.blockSignals(block) self.all_procedures = DBWrapper().list_all_procedures(subject_id) self.prev_proc.clear() self.prev_proc.addItem('') self.prev_proc.addItems([ ' '.join([ x.target_name, x.recording_config, x.date.strftime('%Y-%m-%d') ]) for x in self.all_procedures ]) self.prev_proc.setCurrentIndex(0) self.prev_proc.blockSignals(False)
def process_settings(self, sub_sett, proc_sett, depth_sett, feat_sett): self.subject_settings = dict(sub_sett) self.procedure_settings = dict(proc_sett) self.depth_settings = dict(depth_sett) self.features_settings = dict(feat_sett) # validate that we have some data in the electrode_settings. If the NSP is not connected we will have # to load the channel names from the DB. Also we want to keep the FeaturesGUI unaware of the DB channels. if len(self.depth_settings['electrode_settings']) == 0: self.depth_settings['electrode_settings'] = {} for lbl in DBWrapper().list_channel_labels(): self.depth_settings['electrode_settings'][lbl] = DEPTHSETTINGS CbSdkConnection().is_simulating = True # set new features self.feature_select.setCurrentIndex(0) # Raw while self.feature_select.count() > 2: # Raw and Mapping self.feature_select.removeItem(2) self.feature_select.addItems(self.features_settings['features'].keys()) # set new channels self.chan_select.setCurrentIndex(0) # None while self.chan_select.count() > 1: self.chan_select.removeItem(1) self.chan_select.addItems( self.depth_settings['electrode_settings'].keys()) # clear and update stacked widget to_delete = [ self.plot_stack.widget(x) for x in range(self.plot_stack.count()) ] for wid in to_delete: self.plot_stack.removeWidget(wid) wid.deleteLater() wid = None self.stack_dict = {} self.create_plots() self.depth_wrapper.send_settings(self.depth_settings) self.features_wrapper.send_settings(self.features_settings) if not self.features_process_running: self.manage_feature_process(True) # self.clear() self.read_from_shared_memory()
class NS5OfflinePlayback: def __init__(self, sub_id, proc_id, f_name): self.f_name = f_name self.reader = BlackrockRawIO(f_name, nsx_to_load=5) self.db_wrapper = DBWrapper() # signal settings self.SR = None self.channels = [] self.depths = [] self.depth_times = [] self.rec_start_time = None self.prepare_depth_values() # settings self.subject_settings = {'id': sub_id} self.procedure_settings = {'target_name': None, 'type': 'surgical'} self.buffer_settings = {'sampling_rate': 30000, 'buffer_length': '6.000', 'sample_length': '4.000', 'delay_buffer': '0.500', 'overwrite_depth': True, 'run_buffer': False, 'electrode_settings': {}} self.procedure_name = 'Proc' + str(proc_id) for electrode in self.channels: self.buffer_settings['electrode_settings'][electrode] = {'threshold': True, 'validity': 90.0} self.features_settings = {} # manage settings and start process self.manage_settings() def prepare_depth_values(self): self.reader.parse_header() # channels self.channels = [x[0] for x in self.reader.header['signal_channels']] self.SR = self.reader.header['signal_channels'][0][2] self.rec_start_time = self.reader.raw_annotations['blocks'][0]['rec_datetime'] # comments rexp = re.compile(r'[a-zA-Z]*\:?(?P<depth>\-?\d*\.\d*)') for com in self.reader.nev_data['Comments'][0]: # some comments aren't depth related tmp_match = rexp.match(com[5].decode('utf-8')) if tmp_match: # older files marked depths at regular intervals and not only when it changed # we need to keep only new depth values d = float(tmp_match.group('depth')) if len(self.depths) == 0 or d != self.depths[-1]: self.depth_times.append(com[0]) self.depths.append(d) def manage_settings(self): win = SettingsDialog(self.subject_settings, self.procedure_settings, self.buffer_settings, self.features_settings) win.update_settings() win.close() sub_id = self.db_wrapper.load_or_create_subject(self.subject_settings) if sub_id == -1: print("Subject not created.") return False else: self.subject_settings['subject_id'] = sub_id self.procedure_settings['subject_id'] = sub_id self.procedure_settings['target_name'] = self.procedure_name proc_id = self.db_wrapper.load_or_create_procedure(self.procedure_settings) self.buffer_settings['procedure_id'] = proc_id self.features_settings['procedure_id'] = proc_id self.process_data() def process_data(self): # get settings buffer_length = int(float(self.buffer_settings['buffer_length']) * self.SR) sample_length = int(float(self.buffer_settings['sample_length']) * self.SR) valid_thresh = [int(x['validity'] / 100 * sample_length) for x in self.buffer_settings['electrode_settings'].values()] bar = QProgressDialog('Processing', 'Stop', 0, len(self.depths), None) bar.setWindowModality(Qt.WindowModal) bar.show() # loop through each comment time and check whether segment is long enough for idx, (time, depth) in enumerate(zip(self.depth_times[:-1], self.depths[:-1])): bar.setValue(idx) bar.setLabelText(self.f_name + " " + str(depth)) if bar.wasCanceled(): break if self.depth_times[idx + 1] - time >= sample_length: # get sample first, if doesn't pass validation, move forward until it does # or until buffer_length is elapsed. data = self.reader.get_analogsignal_chunk(i_start=time, i_stop=time + sample_length).T valid = self.validate_data_sample(data) t_offset = 0 while not all(np.sum(valid, axis=1) > valid_thresh) and \ t_offset + sample_length < buffer_length and \ time + t_offset + sample_length < self.depth_times[idx + 1]: t_offset += 300 # roughly a 100 Hz update rate. data = self.reader.get_analogsignal_chunk(i_start=time + t_offset, i_stop=time + t_offset + sample_length).T valid = self.validate_data_sample(data) # send to db self.db_wrapper.save_depth_datum(depth=depth, data=data, is_good=np.sum(valid, axis=1) > valid_thresh, group_info=self.channels, start_time=self.rec_start_time + datetime.timedelta(seconds=(time + t_offset) / self.SR), stop_time=self.rec_start_time + datetime.timedelta(seconds=(time + t_offset + sample_length) / self.SR)) bar.close() @staticmethod def validate_data_sample(data): # TODO: implement other metrics # SUPER IMPORTANT: when cbpy returns an int16 value, it can be -32768, however in numpy: # np.abs(-32768) = -32768 for 16 bit integers since +32768 does not exist. # We therefore can't use the absolute value for the threshold. threshold = 30000 # arbitrarily set for now validity = (-threshold < data) & (data < threshold) return validity
def update(self): # Depth process output = self.depth_wrapper.worker_status() self.status_label.setPixmap(self.status_icons[output]) if self.depth_wrapper.is_running(): self.depth_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : green; " "border-color : green; " "border-width: 2px}") else: self.depth_process_running = False self.depth_process_btn.setStyleSheet("QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") if self.features_wrapper.is_running(): self.features_process_btn.setStyleSheet( "QPushButton { color: white; " "background-color : green; " "border-color : green; " "border-width: 2px}") else: self.features_process_running = False self.features_process_btn.setStyleSheet( "QPushButton { color: white; " "background-color : red; " "border-color : red; " "border-width: 2px}") if self.sweep_control.isChecked(): self.read_from_shared_memory() # features plot curr_chan_lbl = self.chan_select.currentText() if curr_chan_lbl != 'None': curr_feat = self.feature_select.currentText() do_hp = self.do_hp.isChecked() if do_hp != self.plot_stack.currentWidget().plot_config['do_hp'] or \ self.y_range != self.plot_stack.currentWidget().plot_config['y_range']: self.plot_stack.currentWidget().clear_plot() self.stack_dict[curr_chan_lbl][curr_feat][1] = 0 self.plot_stack.currentWidget().plot_config['do_hp'] = do_hp self.plot_stack.currentWidget( ).plot_config['y_range'] = self.y_range curr_datum = self.stack_dict[curr_chan_lbl][curr_feat][1] if curr_feat == 'Raw': all_data = DBWrapper().load_depth_data(chan_lbl=curr_chan_lbl, gt=curr_datum, do_hp=do_hp, return_uV=True) elif curr_feat == 'Mapping': all_data = DBWrapper().load_mapping_response( chan_lbl=curr_chan_lbl, gt=curr_datum) else: all_data = DBWrapper().load_features_data( category=curr_feat, chan_lbl=curr_chan_lbl, gt=curr_datum) if all_data: self.plot_stack.currentWidget().update_plot(dict(all_data)) self.stack_dict[curr_chan_lbl][curr_feat][1] = max( all_data.keys())
class FeaturesGUI(CustomGUI): def __init__(self): super(FeaturesGUI, self).__init__() self.setWindowTitle('FeaturesGUI') self.plot_widget = None # settings dictionaries self.subject_settings = {} self.procedure_settings = {} self.buffer_settings = {} self.features_settings = {} # DB wrapper self.db_wrapper = DBWrapper() # Override create actions to call the clean_exit script def create_actions(self): # Actions self.actions = { 'Connect': QAction("Connect", self), 'Quit': QAction("Quit", self), 'AddPlot': QAction("Add Plot", self) } self.actions['Connect'].triggered.connect( self.on_action_connect_triggered) self.actions['Quit'].triggered.connect(self.clean_exit) self.actions['AddPlot'].triggered.connect( self.on_action_add_plot_triggered) def clean_exit(self): if self.plot_widget: self.plot_widget.kill_processes() if self.plot_widget.awaiting_close: del self.plot_widget QApplication.instance().quit() # defined in the CustomGUI class, is triggered when the "Add Plot" button # is pressed in the default GUI (Connect, Add Plot, Quit) def on_action_add_plot_triggered(self): # Get all the available information for settings # NSP info, None if not connected sampling_group_id = SAMPLINGGROUPS.index(str(SAMPLINGRATE)) self.group_info = self.cbsdk_conn.get_group_config(sampling_group_id) # we only need to set the default values for the depth buffer here since it requires electrode # information. The rest accepts empty dicts self.buffer_settings['sampling_rate'] = SAMPLINGRATE self.buffer_settings['sampling_group_id'] = sampling_group_id self.buffer_settings['buffer_length'] = '{:.3f}'.format(BUFFERLENGTH) self.buffer_settings['sample_length'] = '{:.3f}'.format(SAMPLELENGTH) self.buffer_settings['delay_buffer'] = '{:.3f}'.format(DELAYBUFFER) self.buffer_settings['overwrite_depth'] = OVERWRITEDEPTH self.buffer_settings['electrode_settings'] = {} if self.group_info: for electrode in self.group_info: self.buffer_settings['electrode_settings'][ electrode['label'].decode('utf-8')] = DEPTHSETTINGS # Open settings dialog and update DBWrapper subject and settings dicts if not self.manage_settings(): return # Configure CB SDK connection self.cbsdk_conn.cbsdk_config = { 'reset': True, 'get_continuous': True, 'get_events': False, 'get_comments': True, 'buffer_parameter': { 'comment_length': 10 } } # NSP Buffer widget # this widget handles the process creation that scans depth values, buffers the data and sends it to the DB. self.plot_widget = FeaturesPlotWidget(self.group_info) self.plot_widget.was_closed.connect(self.clean_exit) self.plot_widget.call_manage_settings.connect(self.get_settings) # send values to widgets self.send_settings() # The custom GUI class has an update function, which calls the # do_plot_update function. This function then calls the update # function of all display widgets. def do_plot_update(self): # since all widgets have different use, they will each handle their own data collection. self.plot_widget.update() def manage_settings(self): # Open prompt to input subject details win = SettingsDialog(self.subject_settings, self.procedure_settings, self.buffer_settings, self.features_settings) result = win.exec_() if result == QDialog.Accepted: win.update_settings() else: return False # Create or load subject # Returns subject_id/-1 whether subject is properly created or not sub_id = self.db_wrapper.load_or_create_subject(self.subject_settings) if sub_id == -1: print("Subject not created.") return False else: self.subject_settings['subject_id'] = sub_id self.procedure_settings['subject_id'] = sub_id proc_id = self.db_wrapper.load_or_create_procedure( self.procedure_settings) self.buffer_settings['procedure_id'] = proc_id self.features_settings['procedure_id'] = proc_id return True def get_settings(self): # open prompt and update values if self.manage_settings(): # update values to widgets self.send_settings() def send_settings(self): self.plot_widget.process_settings(self.subject_settings, self.procedure_settings, self.buffer_settings, self.features_settings)
def update_settings_from_db(self, idx): for key, value in DBWrapper().load_subject_details(idx).items(): self.subject_settings[key] = value
def update_settings_from_db(self, idx): self.procedure_settings.update(DBWrapper().load_procedure_details( idx, exclude=['subject', 'procedure_id']))
class SubjectWidget(QWidget): subject_change = Signal(int) def __init__(self, subject_settings): super(SubjectWidget, self).__init__() self.subject_enums = DBWrapper().return_enums('subject') subject_layout = QGridLayout(self) subject_layout.setColumnMinimumWidth(2, 60) subject_layout.addWidget(QLabel("Id: "), 0, 0, 1, 1) self.id_combo = QComboBox() self.id_combo.setEditable(True) self.id_combo.addItem('') self.id_combo.addItems(DBWrapper().list_all_subjects()) self.id_combo.currentIndexChanged.connect(self.load_subject) self.id_combo.lineEdit().editingFinished.connect(self.check_subject) subject_layout.addWidget(self.id_combo, 0, 1, 1, 4) subject_layout.addWidget(QLabel("Name: "), 1, 0, 1, 1) self.name_edit = QLineEdit() self.name_edit.setMaxLength(135) subject_layout.addWidget(self.name_edit, 1, 1, 1, 4) subject_layout.addWidget(QLabel("Sex: "), 2, 0, 1, 1) self.sex_combo = QComboBox() self.sex_combo.addItems(self.subject_enums['sex'] if 'sex' in self.subject_enums.keys() else []) self.sex_combo.setCurrentIndex(0) subject_layout.addWidget(self.sex_combo, 2, 1, 1, 1) subject_layout.addWidget((QLabel("Date of birth: ")), 3, 0, 1, 1) self.dob_calendar = QCalendarWidget() subject_layout.addWidget(self.dob_calendar, 3, 1, 1, 3) subject_layout.addWidget(QLabel("NSP file comment: "), 4, 0, 1, 1) self.file_comment = QTextEdit("") self.file_comment.setMaximumHeight(150) subject_layout.addWidget(self.file_comment, 4, 1, 1, 4) # Subject Settings self.subject_settings = subject_settings if not self.subject_settings: self.update_settings_from_db(-1) self.update_subject() def update_subject(self): self.name_edit.setText( self.read_dict_value(self.subject_settings, 'name')) self.id_combo.setCurrentText( self.read_dict_value(self.subject_settings, 'id')) self.sex_combo.setCurrentText( self.read_dict_value(self.subject_settings, 'sex')) dob = self.read_dict_value(self.subject_settings, 'birthday') if dob not in [None, '']: q_dob = QDate.fromString(dob, 'yyyy-MM-d') self.dob_calendar.setSelectedDate(q_dob) else: self.dob_calendar.setSelectedDate(QDate.currentDate()) def update_settings_from_db(self, idx): for key, value in DBWrapper().load_subject_details(idx).items(): self.subject_settings[key] = value def load_subject(self): # id is a unique and mandatory field self.check_subject() self.update_subject() def check_subject(self): # when changing the id in the combobox, can be modifying or entering an existing subject id. Check to load data # if so. curr_id = self.id_combo.currentText() if curr_id != '': self.update_settings_from_db(curr_id) self.subject_change.emit(self.subject_settings['subject_id']) else: self.update_settings_from_db(-1) self.subject_change.emit(-1) @staticmethod def read_dict_value(dictionary, value): return str(dictionary[value]) if value in dictionary.keys() else '' def to_dict(self): self.subject_settings['id'] = self.id_combo.currentText() self.subject_settings['name'] = self.name_edit.text() self.subject_settings['sex'] = self.sex_combo.currentText() self.subject_settings['birthday'] = self.dob_calendar.selectedDate( ).toPyDate() self.subject_settings['NSP_comment'] = self.file_comment.toPlainText()
def __init__(self, procedure_settings): super(ProcedureWidget, self).__init__() # Settings self.procedure_settings = procedure_settings # populate with defaults if empty if not self.procedure_settings: self.update_settings_from_db(-1) self.proc_enums = DBWrapper().return_enums('procedure') proc_layout = QGridLayout(self) row = 0 proc_layout.addWidget(QLabel("Previous procedures: "), row, 0, 1, 1) self.prev_proc = QComboBox() self.prev_proc.setEnabled(True) self.check_all_procedures(None, False) self.prev_proc.currentIndexChanged.connect( self.procedure_selection_change) proc_layout.addWidget(self.prev_proc, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Target name: "), row, 0, 1, 1) self.target_name = QLineEdit("") proc_layout.addWidget(self.target_name, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Type: "), row, 0, 1, 1) self.type_combo = self.combo_from_enum('type') proc_layout.addWidget(self.type_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Recording configuration: "), row, 0, 1, 1) self.rec_combo = self.combo_from_enum('recording_config') proc_layout.addWidget(self.rec_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Electrode configuration: "), row, 0, 1, 1) self.electrode_combo = self.combo_from_enum('electrode_config') proc_layout.addWidget(self.electrode_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Distance to target: "), row, 0, 1, 1) self.dist_to_target = self.coord_line_edit() proc_layout.addWidget(self.dist_to_target, row, 1, 1, 1) row += 1 proc_layout.addWidget(QLabel("Entry (x, y, z): "), row, 0, 1, 1) self.entry_x = self.coord_line_edit() proc_layout.addWidget(self.entry_x, row, 1, 1, 1) self.entry_y = self.coord_line_edit() proc_layout.addWidget(self.entry_y, row, 2, 1, 1) self.entry_z = self.coord_line_edit() proc_layout.addWidget(self.entry_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("Target (x, y, z): "), row, 0, 1, 1) self.target_x = self.coord_line_edit() proc_layout.addWidget(self.target_x, row, 1, 1, 1) self.target_y = self.coord_line_edit() proc_layout.addWidget(self.target_y, row, 2, 1, 1) self.target_z = self.coord_line_edit() proc_layout.addWidget(self.target_z, row, 3, 1, 1) row += 1 self.comp_dist_to_target = QLabel( "Computed distance: 0.000 mm; Difference: 0.000 mm") proc_layout.addWidget(self.comp_dist_to_target, row, 0, 1, 2) row += 1 proc_layout.addWidget(QLabel("A (x, y, z): "), row, 0, 1, 1) self.a_x = self.coord_line_edit() proc_layout.addWidget(self.a_x, row, 1, 1, 1) self.a_y = self.coord_line_edit() proc_layout.addWidget(self.a_y, row, 2, 1, 1) self.a_z = self.coord_line_edit() proc_layout.addWidget(self.a_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("E (x, y, z): "), row, 0, 1, 1) self.e_x = self.coord_line_edit() proc_layout.addWidget(self.e_x, row, 1, 1, 1) self.e_y = self.coord_line_edit() proc_layout.addWidget(self.e_y, row, 2, 1, 1) self.e_z = self.coord_line_edit() proc_layout.addWidget(self.e_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("Offset direction: "), row, 0, 1, 1) self.offset_direction_combo = self.combo_from_enum('offset_direction') proc_layout.addWidget(self.offset_direction_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Offset size: "), row, 0, 1, 1) self.offset_size = self.coord_line_edit() proc_layout.addWidget(self.offset_size, row, 1, 1, 1) row += 1 proc_layout.addWidget(QLabel("Medication status: "), row, 0, 1, 1) self.medic_combo = self.combo_from_enum('medication_status') proc_layout.addWidget(self.medic_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QWidget(), row, 1, 1, 3) self.update_procedure()
class ProcedureWidget(QWidget): def __init__(self, procedure_settings): super(ProcedureWidget, self).__init__() # Settings self.procedure_settings = procedure_settings # populate with defaults if empty if not self.procedure_settings: self.update_settings_from_db(-1) self.proc_enums = DBWrapper().return_enums('procedure') proc_layout = QGridLayout(self) row = 0 proc_layout.addWidget(QLabel("Previous procedures: "), row, 0, 1, 1) self.prev_proc = QComboBox() self.prev_proc.setEnabled(True) self.check_all_procedures(None, False) self.prev_proc.currentIndexChanged.connect( self.procedure_selection_change) proc_layout.addWidget(self.prev_proc, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Target name: "), row, 0, 1, 1) self.target_name = QLineEdit("") proc_layout.addWidget(self.target_name, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Type: "), row, 0, 1, 1) self.type_combo = self.combo_from_enum('type') proc_layout.addWidget(self.type_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Recording configuration: "), row, 0, 1, 1) self.rec_combo = self.combo_from_enum('recording_config') proc_layout.addWidget(self.rec_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Electrode configuration: "), row, 0, 1, 1) self.electrode_combo = self.combo_from_enum('electrode_config') proc_layout.addWidget(self.electrode_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Distance to target: "), row, 0, 1, 1) self.dist_to_target = self.coord_line_edit() proc_layout.addWidget(self.dist_to_target, row, 1, 1, 1) row += 1 proc_layout.addWidget(QLabel("Entry (x, y, z): "), row, 0, 1, 1) self.entry_x = self.coord_line_edit() proc_layout.addWidget(self.entry_x, row, 1, 1, 1) self.entry_y = self.coord_line_edit() proc_layout.addWidget(self.entry_y, row, 2, 1, 1) self.entry_z = self.coord_line_edit() proc_layout.addWidget(self.entry_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("Target (x, y, z): "), row, 0, 1, 1) self.target_x = self.coord_line_edit() proc_layout.addWidget(self.target_x, row, 1, 1, 1) self.target_y = self.coord_line_edit() proc_layout.addWidget(self.target_y, row, 2, 1, 1) self.target_z = self.coord_line_edit() proc_layout.addWidget(self.target_z, row, 3, 1, 1) row += 1 self.comp_dist_to_target = QLabel( "Computed distance: 0.000 mm; Difference: 0.000 mm") proc_layout.addWidget(self.comp_dist_to_target, row, 0, 1, 2) row += 1 proc_layout.addWidget(QLabel("A (x, y, z): "), row, 0, 1, 1) self.a_x = self.coord_line_edit() proc_layout.addWidget(self.a_x, row, 1, 1, 1) self.a_y = self.coord_line_edit() proc_layout.addWidget(self.a_y, row, 2, 1, 1) self.a_z = self.coord_line_edit() proc_layout.addWidget(self.a_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("E (x, y, z): "), row, 0, 1, 1) self.e_x = self.coord_line_edit() proc_layout.addWidget(self.e_x, row, 1, 1, 1) self.e_y = self.coord_line_edit() proc_layout.addWidget(self.e_y, row, 2, 1, 1) self.e_z = self.coord_line_edit() proc_layout.addWidget(self.e_z, row, 3, 1, 1) row += 1 proc_layout.addWidget(QLabel("Offset direction: "), row, 0, 1, 1) self.offset_direction_combo = self.combo_from_enum('offset_direction') proc_layout.addWidget(self.offset_direction_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QLabel("Offset size: "), row, 0, 1, 1) self.offset_size = self.coord_line_edit() proc_layout.addWidget(self.offset_size, row, 1, 1, 1) row += 1 proc_layout.addWidget(QLabel("Medication status: "), row, 0, 1, 1) self.medic_combo = self.combo_from_enum('medication_status') proc_layout.addWidget(self.medic_combo, row, 1, 1, 3) row += 1 proc_layout.addWidget(QWidget(), row, 1, 1, 3) self.update_procedure() def check_all_procedures(self, subject_id, block): self.prev_proc.blockSignals(block) self.all_procedures = DBWrapper().list_all_procedures(subject_id) self.prev_proc.clear() self.prev_proc.addItem('') self.prev_proc.addItems([ ' '.join([ x.target_name, x.recording_config, x.date.strftime('%Y-%m-%d') ]) for x in self.all_procedures ]) self.prev_proc.setCurrentIndex(0) self.prev_proc.blockSignals(False) def combo_from_enum(self, enum_name): combo = QComboBox() combo.addItems(self.proc_enums[enum_name] if enum_name in self.proc_enums.keys() else []) combo.setCurrentText('none') return combo def coord_line_edit(self): template = QRegExp(r"[-]?\d*\.?\d{0,3}") validator = QRegExpValidator(template) line = QLineEdit("0.0") line.setValidator(validator) line.setFixedWidth(60) # line.editingFinished.connect(self.update_dist_to_target) line.textChanged.connect(self.update_dist_to_target) return line def update_dist_to_target(self, new_string): if new_string not in ["-", ".", "", "-."]: ddt = np.sqrt( (float(self.target_x.text()) - float(self.entry_x.text()))**2 + (float(self.target_y.text()) - float(self.entry_y.text()))**2 + (float(self.target_z.text()) - float(self.entry_z.text()))**2) diff = float(self.dist_to_target.text()) - ddt self.comp_dist_to_target.setText( "Computed distance: {:.3f} mm; Difference: {:.3f} mm".format( ddt, diff)) def change_subject(self, sub_id, block=False): self.check_all_procedures(sub_id, block) def procedure_selection_change(self): if self.prev_proc.currentIndex() > 0: self.update_settings_from_db( self.all_procedures[self.prev_proc.currentIndex() - 1].procedure_id) else: self.update_settings_from_db(-1) self.update_procedure() def update_settings_from_db(self, idx): self.procedure_settings.update(DBWrapper().load_procedure_details( idx, exclude=['subject', 'procedure_id'])) def update_procedure(self): self.target_name.setText(self.read_dict_value('target_name')) self.type_combo.setCurrentText(self.read_dict_value('type')) self.rec_combo.setCurrentText(self.read_dict_value('recording_config')) self.electrode_combo.setCurrentText( self.read_dict_value('electrode_config')) self.medic_combo.setCurrentText( self.read_dict_value('medication_status')) self.offset_size.setText(str(self.read_dict_value('offset_size'))) self.offset_direction_combo.setCurrentText( self.read_dict_value('offset_direction')) entry = self.read_dict_value('entry') if entry is None: entry = [0., 0., 0.] self.entry_x.setText(str(entry[0])) self.entry_y.setText(str(entry[1])) self.entry_z.setText(str(entry[2])) target = self.read_dict_value('target') if target is None: target = [0., 0., 0.] self.target_x.setText(str(target[0])) self.target_y.setText(str(target[1])) self.target_z.setText(str(target[2])) ddt = self.read_dict_value('distance_to_target') if ddt is None: ddt = 0.000 self.dist_to_target.setText(str(ddt)) # self.update_dist_to_target() a = self.read_dict_value('a') if a is None: a = [0., 0., 0.] self.a_x.setText(str(a[0])) self.a_y.setText(str(a[1])) self.a_z.setText(str(a[2])) e = self.read_dict_value('e') if e is None: e = [0., 0., 0.] self.e_x.setText(str(e[0])) self.e_y.setText(str(e[1])) self.e_z.setText(str(e[2])) def read_dict_value(self, value): return self.procedure_settings[ value] if value in self.procedure_settings.keys() else None def to_dict(self): self.procedure_settings['type'] = self.type_combo.currentText() self.procedure_settings['a'] = np.array([ float(self.a_x.text()), float(self.a_y.text()), float(self.a_z.text()) ], dtype=np.float) self.procedure_settings['distance_to_target'] = float( self.dist_to_target.text()) self.procedure_settings['e'] = np.array([ float(self.e_x.text()), float(self.e_y.text()), float(self.e_z.text()) ], dtype=np.float) self.procedure_settings[ 'electrode_config'] = self.electrode_combo.currentText() self.procedure_settings['entry'] = np.array([ float(self.entry_x.text()), float(self.entry_y.text()), float(self.entry_z.text()) ], dtype=np.float) self.procedure_settings[ 'medication_status'] = self.medic_combo.currentText() self.procedure_settings['target_name'] = self.target_name.text() self.procedure_settings[ 'recording_config'] = self.rec_combo.currentText() self.procedure_settings['target'] = np.array([ float(self.target_x.text()), float(self.target_y.text()), float(self.target_z.text()) ], dtype=np.float) self.procedure_settings[ 'offset_direction'] = self.offset_direction_combo.currentText() self.procedure_settings['offset_size'] = float(self.offset_size.text())