def clear_annotations_in_this_partition(p: SinglePartition): """ batch removing annotations which fall within a partition. good to have if it is needed to clean large artifact region from spurious fiducials """ try: from logic.operation_mode.annotation import AnnotationConfig from gui.viewer import Viewer aConf = AnnotationConfig.get() for f in aConf.fiducials: ann = f.annotation remove_idx = np.arange(bisect.bisect_right(ann.x, p.start), bisect.bisect_left(ann.x, p.end)) nn = max(remove_idx.shape) result = QtWidgets.QMessageBox.question(Viewer.get(), "Confirm Delete Annotations...", "Are you sure you want to delete {nn} {name} annotations ?".format(nn=nn, name=ann.name), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if result == QtWidgets.QMessageBox.Yes: ann.x = np.delete(ann.x, remove_idx) ann.y = np.delete(ann.y, remove_idx) ann.idx = np.delete(ann.idx, remove_idx) Viewer.get().selectedDisplayPanel.plot_area.redraw_fiducials() Partitions.update_all_bounds() except Exception as e: Dialog().warningMessage('Deleting annotations failed with\r\n' + str(e))
def from_csv(cls, csv): from gui import PALMS settings_init = pd.read_csv(csv) db = Database.get() fiducials = [] for _, row_data in settings_init.iterrows(): assert all([k in row_data for k in PALMS.config['annotationConfig_columns']]) # NB: recover aConf.pinned_to changes from json # it is not needed as one can rewrite annotation config from AnnotationConfigDialog Save button # see also AnnotationConfigDialog where applied data is saved # try: # tmp_singleFiducialConfig = SingleFiducialConfig(row_data) # pinned_to_prev_state = PALMS.config['pinned_to_last_state'][tmp_singleFiducialConfig.name] # pinned_to_prev_state = pinned_to_prev_state.split() # if pinned_to_prev_state[0] in PALMS.config['pinned_to_options'] and \ # pinned_to_prev_state[1] in db.tracks_to_plot_initially: # row_data['pinned_to'] = " ".join(pinned_to_prev_state) # qInfo('Annotation Config updated with config.json data') # except Exception as e: # pass fiducials.append(SingleFiducialConfig(row_data)) aConf = AnnotationConfig.get() aConf.reset_fiducials_config(fiducials) try: # NB: nice to do it here, but Viewer object might still not be created from gui.viewer import Viewer Viewer.get().annotationConfig.aConf_to_table(aConf) Viewer.get().annotationConfig.reset_pinned_to_options_to_existing_views() except: pass return aConf
def which_fiducial_to_delete(click_x): from gui.viewer import Viewer # first check if 'sticky' fiducial option is enabled for one of the fiducials sticky_fiducial = [ item.isChecked() for item in Viewer.get().annotation_menu.sticky_fiducial_menu.actions() ] if any(sticky_fiducial): idx = np.argwhere(sticky_fiducial)[0] return AnnotationConfig.get().find_idx_by_name( Viewer.get().annotation_menu.sticky_fiducial_menu.actions( )[idx[0]].text()) dist = np.inf fiducial_name, fiducial_idx = None, None for f_idx, f in enumerate(AnnotationConfig.get().fiducials): if f.annotation.x.size > 0: closest_idx, _, _ = find_closest(f.annotation.x, np.array([click_x])) dist_new = abs(click_x - f.annotation.x[closest_idx]) if dist_new < dist: dist = dist_new fiducial_name, fiducial_idx = f.name, f_idx if not dist == np.inf: return fiducial_idx
def load(self, fullpath): try: hf = self._get_matfile_object(fullpath) assert all(s in hf.keys() for s in ['annotations', 'partitions']), r'h5 must have {} groups'.format( ['annotations', 'partitions']) assert all([s in hf['partitions'] for s in ['label', 'start', 'end']]), r'h5.partitions must have {} groups'.format( ['label', 'start', 'end']) labels = hf['partitions/label'] labels = [n.decode('ascii', 'ignore') for n in labels] start = np.array(hf['partitions/start']) end = np.array(hf['partitions/end']) assert len(labels) == start.size & start.size == end.size, 'Every partition should have label, start and end' Partitions.add_all(labels, start, end) from logic.operation_mode.annotation import AnnotationConfig assert all([s in AnnotationConfig.all_fiducials() for s in hf['annotations']]), 'All h5.annotations must be in {} groups'.format( AnnotationConfig.all_fiducials()) for f_name in hf['annotations'].keys(): self._set_annotation_from_time(f_name, np.array(hf['annotations/' + f_name + '/ts'])) from gui.viewer import Viewer try: # when loaded during initialization Viewer.get().selectedDisplayPanel.plot_area.redraw_fiducials() # to update and show loaded data except: pass try: # can be removed after thorough testing if 'epoch' in hf.keys(): assert all( [f in hf['epoch'] for f in ['start', 'end', 'is_modified', 'label', 'all_labels', 'keys', 'default_label']]), 'Loaded file contains incorrect epoch mode data' labels = [n.decode('ascii', 'ignore') for n in hf['epoch/label']] epoch_data = pd.DataFrame( {'start': hf['epoch/start'], 'end': hf['epoch/end'], 'is_modified': hf['epoch/is_modified'], 'label': labels}) keys = [n.decode('ascii', 'ignore') for n in hf['epoch/keys']] all_labels = [n.decode('ascii', 'ignore') for n in hf['epoch/all_labels']] description = [n.decode('ascii', 'ignore') for n in hf['epoch/description']] default_label = hf['epoch/default_label'][0] NONE_LABEL = hf['epoch/NONE_LABEL'][0] EpochModeConfig.load_from_hdf5(epoch_data, keys, all_labels, default_label, NONE_LABEL, description=description) except Exception as e: Dialog().warningMessage('Epoch mode data cannot be loaded\r\n' + 'The error was:\r\n' + str(e)) except Exception as e: Dialog().warningMessage('Loading existing annotations failed\r\n' + 'The error was:\r\n' + str(e))
def switch_mode(new_mode: Modes): """it is a callback to Modes checkboxes. defines which mode should be switched on and emits a setOperationMode signal""" from gui.viewer import Viewer old_mode = Mode.mode # if old_mode == new_mode: # switching off current mode --> browsing mode # if new_mode in [Modes.annotation, Modes.partition, Modes.epoch]: # new_mode = Modes.browse # elif not old_mode == new_mode: # pass Mode.mode = new_mode Viewer.get().setOperationMode.emit(new_mode) qInfo('Mode: ' + Mode.mode.value)
def move_left(): EpochWindow.hide() from logic.databases.DatabaseHandler import Database from gui.viewer import Viewer from gui.plot_area import PlotArea track = Database.get().tracks[Database.get().main_track_label] success = EpochModeConfig.CURRENT_WINDOW_IDX.decrease() if success: st, en, label = EpochModeConfig.get().get_window_data( EpochModeConfig.CURRENT_WINDOW_IDX.get()) EpochWindow(label, start=st, end=en) while EpochWindow.is_out_of_scope(): Viewer.get().shiftLeft() EpochWindow.show() PlotArea.get_main_view().renderer.plot_area.setFocus()
def get_main_view(): from gui.viewer import Viewer from logic.databases.DatabaseHandler import Database main_track_label = Database.get().main_track_label if Viewer.get() is None: return None for frame in Viewer.get().frames: all_views = [v for v in frame.displayPanel.panel.views] all_views_labels = [v.track.label for v in all_views] main_track_idx = all_views_labels.index( main_track_label ) if main_track_label in all_views_labels else None if main_track_idx is not None: break if main_track_idx is None: # in case main view is not created yet return None main_view = all_views[main_track_idx] return main_view
def delete(self, x): from gui.viewer import Viewer plot_area = Viewer.get().selectedDisplayPanel.plot_area fiducial_name = self.name fConf = AnnotationConfig.get()[fiducial_name] if fConf.annotation.x.size > 0: # closest_idx, _, _ = find_closest(fConf.annotation.x, np.array([x])) closest_idx = np.argmin(abs(x - fConf.annotation.x)) deleted_x, deleted_y = fConf.annotation.x[closest_idx], fConf.annotation.y[closest_idx] fConf.annotation.x = np.delete(fConf.annotation.x, closest_idx) fConf.annotation.y = np.delete(fConf.annotation.y, closest_idx) fConf.annotation.idx = np.delete(fConf.annotation.idx, closest_idx) Viewer.get().selectedDisplayPanel.plot_area.redraw_fiducials() plot_area.signal_annotation_added.emit(deleted_x, deleted_y, 'deleted') qInfo('{n} deleted'.format(n=fiducial_name)) else: qInfo('No {n} to be deleted'.format(n=fiducial_name))
def which_fiducial_to_add(last_keypress_event_key): from gui.viewer import Viewer # first check if 'sticky' fiducial option is enabled for one of the fiducials sticky_fiducial = [ item.isChecked() for item in Viewer.get().annotation_menu.sticky_fiducial_menu.actions() ] if any(sticky_fiducial): idx = np.argwhere(sticky_fiducial)[0] return AnnotationConfig.get().find_idx_by_name( Viewer.get().annotation_menu.sticky_fiducial_menu.actions( )[idx[0]].text()) # if 'sticky' fiducial option is off, check what key was pressed the last default_fiducial_idx = 0 # default: first fiducial if last_keypress_event_key is not None: qInfo('Last pressed: ' + str(last_keypress_event_key[1])) for fiducial_idx, f in enumerate( AnnotationConfig.get().fiducials): if f.key.lower() == last_keypress_event_key[1].lower(): return fiducial_idx return default_fiducial_idx else: return default_fiducial_idx
def setYRange(self): from gui.viewer import Viewer if self.selected_view() is None: return x_range_start, x_range_end = self.selected_view( ).renderer.vb.viewRange()[0] # x_range # x_range_start, x_range_end = self.sender().viewRange()[0] # x_range for view in self.vbs.keys(): if view.show: if Viewer.get().autoscale_y: ymin, ymax = view.track.get_yrange_between( x_range_start, x_range_end) else: ymin, ymax = view.renderer.track.minY, view.renderer.track.maxY from gui import PALMS if PALMS.get() is not None: view.renderer.vb.setYRange( min=ymin, max=ymax, padding=PALMS.get().config['yrange_margin'] ) # padding==0: scale to [min;max] else: view.renderer.vb.setYRange(min=ymin, max=ymax, padding=0.1)
def save(self, **kwargs): # TODO: popup warning when rewriting existing files try: from gui.viewer import Viewer filename = kwargs.get('filename', self.fullpath.stem) try: filename = self.outputfile_prefix + filename except Exception as e: qInfo('Output file prefix could not be added') fullpath = pathlib.Path(self.output_folder, filename + '.h5') OVERWRITE = kwargs.get('OVERWRITE', Viewer.get().settings_menu.save_overwrite_action.isChecked()) if fullpath.is_file(): # don't overwrite files if not OVERWRITE: path, filename = os.path.split(fullpath) filename = os.path.splitext(filename)[0] newfilename = filename + '_' + strftime("%Y_%m_%d_%H_%M_%S", gmtime()) + fullpath.suffix fullpath = pathlib.Path(path, newfilename) qInfo('Existing file found. Not overwriting') else: qInfo('Existing file OVERWRITTEN!') from logic.operation_mode.annotation import AnnotationConfig aConf = AnnotationConfig.get() hf = h5py.File(fullpath, 'w') group_annotations = hf.create_group('annotations') for f_idx, f in enumerate(aConf.fiducials): group_annotations.create_group(f.name) group_annotations.create_dataset(f.name + '/ts', data=f.annotation.x) group_annotations.create_dataset(f.name + '/idx', data=f.annotation.idx) group_annotations.create_dataset(f.name + '/amp', data=f.annotation.y) group_partitions = hf.create_group('partitions') asciiList = [n.encode("ascii", "ignore") for n in Partitions.all_labels()] group_partitions.create_dataset('label', data=asciiList) group_partitions.create_dataset('start', data=Partitions.all_startpoints()) group_partitions.create_dataset('end', data=Partitions.all_endpoints()) group_epoch = hf.create_group('epoch') group_epoch.create_dataset('start', data=EpochModeConfig.get().window_data['start'].values) group_epoch.create_dataset('end', data=EpochModeConfig.get().window_data['end'].values) group_epoch.create_dataset('is_modified', data=EpochModeConfig.get().window_data['is_modified'].values) asciiList = [n.encode("ascii", "ignore") for n in EpochModeConfig.get().window_data['label'].values] group_epoch.create_dataset('label', data=asciiList) asciiList = [n.encode("ascii", "ignore") for n in EpochModeConfig.get().keys] group_epoch.create_dataset('keys', data=asciiList) asciiList = [n.encode("ascii", "ignore") for n in EpochModeConfig.get().labels] group_epoch.create_dataset('all_labels', data=asciiList) asciiList = [n.encode("ascii", "ignore") for n in EpochModeConfig.get().description] group_epoch.create_dataset('description', data=asciiList) dt = h5py.special_dtype(vlen=str) group_epoch.create_dataset('default_label', (1,), dtype=dt) group_epoch['default_label'][:] = EpochModeConfig.get().default_label group_epoch.create_dataset('NONE_LABEL', (1,), dtype=dt) group_epoch['NONE_LABEL'][:] = EpochModeConfig.get().NONE_LABEL group_meta = hf.create_group('meta') dt = h5py.special_dtype(vlen=str) group_meta.create_dataset('timestamp', (1,), dtype=dt) group_meta['timestamp'][:] = strftime("%Y_%m_%d_%H_%M_%S", gmtime()) group_meta.create_dataset('filename', (1,), dtype=dt) group_meta['filename'][:] = self.fullpath.stem group_meta.create_dataset('filepath', (1,), dtype=dt) group_meta['filepath'][:] = self.fullpath.parent.as_posix() group_meta.create_dataset('main_track_label', (1,), dtype=dt) group_meta['main_track_label'][:] = self.main_track_label if Viewer.get().settings_menu.save_tracks_action.isChecked(): group_tracks = hf.create_group('tracks') for label, track in Database.get().tracks.items(): group_tracks.create_dataset(label + '/ts', data=track.ts) group_tracks.create_dataset(label + '/amp', data=track.value) group_tracks.create_dataset(label + '/offset', data=track.offset) group_tracks.create_dataset(label + '/fs', data=track.fs) hf.close() qInfo('{} saved'.format(fullpath.as_posix())) except Exception as e: try: hf.close() except: pass self._save_as_csv(filename=self.fullpath.stem, save_idx=False) Dialog().warningMessage('Default save crashed\r\n' + e.__repr__() + '\r\nSaved using deprecated method, as CSV files.')
def hide_all_partitions(): from gui.viewer import Viewer for i in Partitions(): Viewer.get().selectedView.renderer.vb.removeItem(i) Viewer.get().selectedView.renderer.vb.removeItem(i.label)
def mousePressEvent(self, event: QtGui.QMouseEvent): """ The following only has effect if the main track is selected in the track table view on the right Annotation mode: **LeftMouseButton** places a new fiducial. By default it is the first fiducial as set in the annotationConfig, unless before that a keyboard button was pressed corresponding to another fiducial or "sticky fiducial" mode is on. **RightMouseButton** removes the nearest fiducial (default), unless "sticky fiducial" mode is on. Keyboard has no effect Partition mode: **CTRL + LeftMouseButton** creates a new partition which takes 2% of the whole track duration or less to avoid overlapping partiotions; **CTRL + RightMouseButton** removes partition under the click **SHIFT + LeftMouseButton** to drag partition borders or move the partition. Repositioning is possible within the limits of neighboring partition. Moving the partition fully inside another one or reducing its size to 1 sample deletes the partition. #NOTE: creating partition of desired size directly by click and drag might not be that convenient and harder to implement Epoch mode: # NB: when adding\removing mouse click operations also adapt self.mouseReleaseEvent() and self.mouseMoveEvent() """ def which_fiducial_to_add(last_keypress_event_key): from gui.viewer import Viewer # first check if 'sticky' fiducial option is enabled for one of the fiducials sticky_fiducial = [ item.isChecked() for item in Viewer.get().annotation_menu.sticky_fiducial_menu.actions() ] if any(sticky_fiducial): idx = np.argwhere(sticky_fiducial)[0] return AnnotationConfig.get().find_idx_by_name( Viewer.get().annotation_menu.sticky_fiducial_menu.actions( )[idx[0]].text()) # if 'sticky' fiducial option is off, check what key was pressed the last default_fiducial_idx = 0 # default: first fiducial if last_keypress_event_key is not None: qInfo('Last pressed: ' + str(last_keypress_event_key[1])) for fiducial_idx, f in enumerate( AnnotationConfig.get().fiducials): if f.key.lower() == last_keypress_event_key[1].lower(): return fiducial_idx return default_fiducial_idx else: return default_fiducial_idx def which_fiducial_to_delete(click_x): from gui.viewer import Viewer # first check if 'sticky' fiducial option is enabled for one of the fiducials sticky_fiducial = [ item.isChecked() for item in Viewer.get().annotation_menu.sticky_fiducial_menu.actions() ] if any(sticky_fiducial): idx = np.argwhere(sticky_fiducial)[0] return AnnotationConfig.get().find_idx_by_name( Viewer.get().annotation_menu.sticky_fiducial_menu.actions( )[idx[0]].text()) dist = np.inf fiducial_name, fiducial_idx = None, None for f_idx, f in enumerate(AnnotationConfig.get().fiducials): if f.annotation.x.size > 0: closest_idx, _, _ = find_closest(f.annotation.x, np.array([click_x])) dist_new = abs(click_x - f.annotation.x[closest_idx]) if dist_new < dist: dist = dist_new fiducial_name, fiducial_idx = f.name, f_idx if not dist == np.inf: return fiducial_idx try: from gui.viewer import Viewer Viewer.get().selectFrame(self.display_panel.parent( )) # first select the frame where the click was made if Mode.is_epoch_mode(): EpochModeConfig.get().process_mouseclick(event) return vb = self.vbs[self.selected_view()] click_x = vb.mapSceneToView(event.pos()).x() if self.selected_view().track.label is Database.get( ).main_track_label: # TODO: do this check properly and uniformly everywhere if Mode.is_annotation_mode(): if AnnotationConfig.get().is_valid(): if event.button( ) == QtCore.Qt.LeftButton: # Left click to mark fiducial_idx = which_fiducial_to_add( self.last_keypress_event_key) AnnotationConfig.get().fiducials[ fiducial_idx].annotation.signal_annotate.emit( click_x) elif event.button( ) == pg.QtCore.Qt.RightButton: # right click to delete fiducial_idx = which_fiducial_to_delete(click_x) if fiducial_idx is not None: AnnotationConfig.get().fiducials[ fiducial_idx].annotation.signal_delete_annotation.emit( click_x) else: qInfo('No annotation found to be deleted') self.last_keypress_event_key = None # need to press extra key every time to annotate secondary fiducial elif Mode.is_partition_mode(): if event.button() == QtCore.Qt.LeftButton: if event.modifiers() == QtCore.Qt.ControlModifier: self.create_new_partition(event) else: super().mousePressEvent(event) qInfo( 'CTRL+Left: create region; SHIFT+Left: move region' ) # event.accept() elif event.button() == QtCore.Qt.RightButton: if event.modifiers() == QtCore.Qt.ControlModifier: p = Partitions.find_partition_by_point(click_x) if p is not None: p.region_deleted() # event.accept() elif event.modifiers() == QtCore.Qt.ShiftModifier: p = Partitions.find_partition_by_point(click_x) if p is not None: self.partition_context_menu(event) else: qInfo( 'No partition found...CTRL+Right: delete region; SHIFT+Right: region context menu' ) else: super().mousePressEvent(event) qInfo( 'CTRL+Right: delete region; SHIFT+Right: region context menu' ) else: super().mousePressEvent(event) elif Mode.mode == Modes.browse: super().mousePressEvent(event) else: if not self.is_main_view_in_current_panel( ): # click on a panel, without the main track Dialog().warningMessage( 'Selected display panel does not contain the main track ({}).\r\n' .format(Database.get().main_track_label) + 'Try clicking on another display panel') else: # click on a correct panel, but the main track is not selected Dialog().warningMessage( 'Selected signal is not the main one.\r\n' 'Click on the ' '{}' ' track in the control area on the right.'.format( Database.get().main_track_label)) return except Exception as e: Dialog().warningMessage('Mouse click processing failed\r\n' 'What unusual did you do?\r\n' + str(e)) return
def add(self, x): fiducial_name = self.name from gui.viewer import Viewer plot_area = Viewer.get().selectedDisplayPanel.plot_area track = plot_area.main_window.selectedTrack # annotation can only be added to the main track, but there are checks on this before fs = track.fs amp = track.value ts = track.get_time() fConf = AnnotationConfig.get()[fiducial_name] insert_index = bisect.bisect_right(fConf.annotation.x, x) min_distance_samples = round(fs * fConf.min_distance) blocked_region = np.array([]) if insert_index > 0: blocked_region = np.arange(fConf.annotation.idx[insert_index - 1], fConf.annotation.idx[insert_index - 1] + min_distance_samples) if len(fConf.annotation.idx) > insert_index: blocked_region = np.append(blocked_region, np.arange(fConf.annotation.idx[insert_index] - min_distance_samples, fConf.annotation.idx[insert_index] + 1)) if insert_index > 0 and fConf.annotation.idx.size > insert_index: allowed_region = np.arange(fConf.annotation.idx[insert_index - 1] + min_distance_samples, fConf.annotation.idx[insert_index] - min_distance_samples) elif insert_index > 0 and fConf.annotation.idx.size == insert_index: allowed_region = np.arange(fConf.annotation.idx[insert_index - 1] + min_distance_samples, ts.shape[0]) elif insert_index == 0 and fConf.annotation.idx.size > insert_index: allowed_region = np.arange(0, fConf.annotation.idx[insert_index] - min_distance_samples) elif insert_index == 0 and fConf.annotation.idx.size == insert_index: allowed_region = np.arange(0, ts.shape[0]) pinned_to_track = plot_area.main_window.selectedPanel.get_view_from_track_label(fConf.pinned_to_track_label).track if fConf.is_pinned: # TODO: pin should take into account blocked region, as more important requirement x = fConf.annotation.pin(x, pinned_to_track, fConf.pinned_to_location, fConf.pinned_window, allowed_region) if x is None: qInfo('{}: duplicate annotation; min_distance is set to {} s'.format(fiducial_name.upper(), fConf.min_distance)) return ind, _, _ = find_closest(ts, np.array([x])) assert len(ind) == 1 insert_index = bisect.bisect_right(fConf.annotation.x, x) # min_distance_samples = round(fs * fConf.min_distance) # blocked_region = np.array([]) # if insert_index > 0: # blocked_region = np.arange(fConf.annotation.idx[insert_index - 1], # fConf.annotation.idx[insert_index - 1] + min_distance_samples) # if len(fConf.annotation.idx) > insert_index: # blocked_region = np.append(blocked_region, np.arange(fConf.annotation.idx[insert_index] - min_distance_samples, # fConf.annotation.idx[insert_index] + 1)) if not ind[0] in blocked_region: fConf.annotation.idx = np.insert(fConf.annotation.idx, insert_index, ind[0]) fConf.annotation.x = np.insert(fConf.annotation.x, insert_index, ts[ind[0]]) y = amp[ind[0]] fConf.annotation.y = np.insert(fConf.annotation.y, insert_index, y) plot_area.signal_annotation_added.emit(x, y, 'added') qInfo('{n}: x= {X} y= {Y}'.format(n=fiducial_name, X=str(np.round(x, 2)), Y=str(np.round(y, 2)))) else: qInfo('{}: duplicate annotation; min_distance is set to {} s'.format(fiducial_name.upper(), fConf.min_distance)) return