Esempio n. 1
0
    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))
Esempio n. 2
0
 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
Esempio n. 3
0
        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
Esempio n. 4
0
    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))
Esempio n. 5
0
    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)
Esempio n. 6
0
    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()
Esempio n. 7
0
    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
Esempio n. 8
0
    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))
Esempio n. 9
0
 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
Esempio n. 10
0
 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)
Esempio n. 11
0
    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.')
Esempio n. 12
0
 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)
Esempio n. 13
0
    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
Esempio n. 14
0
    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