Beispiel #1
0
 def _get_matfile_object(self, fullpath: pathlib.Path):
     try:  # MATLAB 7.3 file needs to be loaded as HDF5 [install HDF5 on your pc from hdfgroup.org]
         return h5py.File(fullpath, 'r')
     except OSError as e1:  # MATLAB < 7.3
         try:
             return loadmat(fullpath.as_posix(), struct_as_record=False, squeeze_me=True)
         except Exception as e2:
             Dialog().warningMessage(
                 'Loading {}.mat with h5py.File() failed\r\n'.format(fullpath.stem) + str(
                     e1) + '\r\n' + 'Install HDF5 on your pc from hdfgroup.org\r\n' + 'Now attempting to use loadmat()\r\n')
             Dialog().warningMessage('Sorry, unsuccessful...\r\n' + str(e2))
Beispiel #2
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))
Beispiel #3
0
 def onclick_select(self):
     try:
         fn = [
             pathlib.Path(self.db.DATAPATH, item.text())
             for item in self.listWidget_files.selectedItems()
         ]
         if len(fn) > 1:
             Dialog().warningMessage(
                 'Multiple file selection not implemented')
         self.selected_files = fn
         global selected_files
         selected_files = fn
         self.accept()
     except Exception as e:
         Dialog().warningMessage(str(e))
Beispiel #4
0
    def set_annotation_data(self):
        # NB: used to set initial guesses for annotations, otherwise, one has to start annotation from scratch
        #  one can use here simple findpeaks() type algos, or more signal-specific python algorigthms
        #  also possible to run an algo beforehand (e.g. in Matlab), store the results and load them here

        # NB: OPTIONAL!!! Load existing annotation if an .h5 file with the same name found in self.existing_annotation_folder (be careful with self.outputfile_prefix)
        existing_annotation_file = pathlib.Path(
            self.existing_annotations_folder, self.fullpath.stem + '.h5')
        existing_annotation_file_with_prefix = pathlib.Path(
            self.existing_annotations_folder,
            self.outputfile_prefix + self.fullpath.stem + '.h5')

        existing_annotation_files = self.get_annotation_file(
            self.fullpath.stem)
        if existing_annotation_files is not None:
            latest_file_idx = np.argmax(
                [os.path.getmtime(f) for f in existing_annotation_files])
            try:
                self.load(existing_annotation_files[latest_file_idx])
                qInfo('Loading annotations from {}'.format(
                    existing_annotation_file_with_prefix))
            except Exception as e:
                Dialog().warningMessage(
                    'Loading annotations from {} failed\r\n'.format(
                        existing_annotation_file_with_prefix) + str(e))
        else:
            # # NB: 1. Find\fetch preliminary annotation data
            ecg = self.tracks[self.main_track_label].value
            fs = self.tracks[self.main_track_label].fs
            try:
                qrs_detector = PanTomkinsQRSDetector(ecg,
                                                     fs,
                                                     verbose=True,
                                                     log_data=False,
                                                     plot_data=False,
                                                     show_plot=False)

                # # NB: 2. Use inherited functions to assign annotations to the main signal
                # #  all annotation labels should be also in the provided AnnotationConfig file
                # #  User can use _set_annotation_from_time or _set_annotation_from_idx
                self._set_annotation_from_idx('rpeak',
                                              qrs_detector.qrs_peaks_indices)
            except Exception as e:
                Dialog().warningMessage(
                    'Failed to use beat detector\r\n'
                    'Currently you do not have any initial annotations loaded, but\r\n'
                    'You can fix the issue, or implement another way in set_annotation_data()'
                )
Beispiel #5
0
    def set_annotation_data(self):
        # NB: used to set initial guesses for annotations, otherwise, one has to start annotation from scratch
        #  one can use here simple findpeaks() type algos, or more signal-specific python algorithms
        #  also possible to run an algo beforehand (e.g. in Matlab), store the results and load them here

        # NB: OPTIONAL!!! Load existing annotation if an .h5 file with the same name found in self.existing_annotation_folder (be careful with self.outputfile_prefix)
        existing_annotation_file = pathlib.Path(self.existing_annotations_folder, self.fullpath.stem + '.h5')
        if self.annotation_exists(existing_annotation_file.stem):
            try:
                self.load(existing_annotation_file)
                qInfo('Loading annotations from {}'.format(existing_annotation_file))
            except Exception as e:
                Dialog().warningMessage('Loading annotations from {} failed\r\n'.format(existing_annotation_file) + str(e))
        else:
            # # NB: 1. Find\fetch preliminary annotation data
            f = self._get_matfile_object(self.fullpath)
            offset = np.concatenate(np.array(f['/data/ppg/ts']))[0]  # offset to start at time=0 as signals themselves
            peak = np.concatenate(np.array(f['/data/annotations/ppg/peak/timestamps'])) - offset
            foot = np.concatenate(np.array(f['/data/annotations/ppg/foot/timestamps'])) - offset

            # # NB: 2. Use inherited functions to assign annotations to the main signal
            # #  all annotation labels should be also in the provided AnnotationConfig file
            # #  User can use _set_annotation_from_time or _set_annotation_from_idx
            self._set_annotation_from_time('peak', peak)
            self._set_annotation_from_time('foot', foot)
Beispiel #6
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))
Beispiel #7
0
    def test_database_setup(self):
        """
        place all asserts\tests in one place
        :return: bool
        """
        # check type of the data (floats) and dimensions
        assert self.output_folder.is_dir(), 'self.output_folder is not a directory'
        assert self.existing_annotations_folder.is_dir(), 'self.existing_annotations_folder is not a directory'
        assert self.DATAPATH.is_dir(), 'self.DATAPATH is not a directory'
        assert self.tracks is not None, 'self.tracks is None'
        assert self.annotation_config_file is not None, 'self.annotation_config_file is None'
        assert self.main_track_label is not None, 'self.main_track_label is None'
        assert len(self.track_labels) == len(set(self.track_labels)),'not all track labels are unique'
        assert self.main_track_label in self.track_labels, 'self.main_track_label is not in self.track_labels'
        assert self.main_track_label in self.tracks_to_plot_initially, 'self.main_track_label not in self.tracks_to_plot_initially'
        assert all([tp in self.track_labels for tp in self.tracks_to_plot_initially]), 'not all self.tracks_to_plot_initially are in self.track_labels'
        assert self.main_track_label in self.track_labels, 'self.main_track_label not in self.track_labels'
        assert not any([' ' in l for l in self.track_labels]),'all self.track_labels should be one word, no spaces'

        # make the main track label to always go first for plotting, otherwise errors will appear for other views, when the main view is not created yet
        main_track_idx = np.argwhere([self.main_track_label == l for l in self.tracks_to_plot_initially])[0][0]
        if not main_track_idx == 0:
            tmp = self.tracks_to_plot_initially[0]
            self.tracks_to_plot_initially[0] = self.main_track_label
            self.tracks_to_plot_initially[main_track_idx] = tmp

        l = [l.get_time()[-1] for l in self.tracks.values()]
        x = np.reshape(l, (len(l), 1))
        x = np.concatenate(x - x.transpose())  # all differences between track durations
        if max(abs(x)) > 1:
            Dialog().warningMessage('Some of the tracks differ in their duration more than {} seconds\r\n'.format(max(abs(x))) +
                                    'You can continue working if this is expected.')
        # assert max(abs(x)) < 0.5  # tracks should be the same duration (0.5 sec difference allowed), otherwise smth is wrong
        pass
Beispiel #8
0
    def zoomChanged(self):
        try:  # this is to avoid redrawing if it is already zoomed out to max
            newRange = self.main_window.selectedView.renderer.vb.targetRange(
            )[0]
            viewRange = self.main_window.selectedView.renderer.vb.viewRange(
            )[0]
            # newRange = self.main_vb.viewRange()[0]
            if newRange[-1] >= Database.get().get_longest_track_duration(
            ) and newRange[0] <= 0:  # TODO what if offset not 0
                if self.FLAG_full_zoom_out:
                    return
                else:
                    self.FLAG_full_zoom_out = True
            else:
                self.FLAG_full_zoom_out = False
        except:
            pass

        if self.main_vb.geometry():
            try:
                pixel_width = self.main_vb.viewPixelSize()[0]
                self.main_vb.setLimits(xMin=-pixel_width)
                for vb in self.vbs.values():
                    vb.setLimits(xMin=-pixel_width)
                self.redraw_fiducials()
                if self.display_panel is not None and len(
                        self.display_panel.panel.views) > 0:
                    self.setYRange()
                EpochModeConfig.get().redraw_epochs()
            except Exception as e:
                Dialog().warningMessage(
                    'Exception occured\r\n'
                    'Using more than one frame may have caused this!\r\n' +
                    str(e))
Beispiel #9
0
 def get(cls):
     """
     method allows to get the only Database instance from anywhere in the code by running Database.get()
     :return: object of one of the classes in this module
     """
     if Database._instance is not None:
         return Database._instance
     else:
         Dialog().warningMessage('Databse not initialized yet\r\n return None')
         return None
Beispiel #10
0
 def save(self, **kwargs):
     # NB: save annotation data. By default annotations and partitions are saved as .h5 file.
     #  All tracks can be saved too (see Settings in the menu bar).
     #  One can also define custom save protocol here
     # self.output_folder = self.fullpath.parent  # to save in the same location
     # self.output_folder = get_project_root()  # to save in project root/near the executable
     try:
         self.output_folder = self.fullpath.parent
         super().save(filename=self.fullpath.stem, **kwargs)
     except Exception as e:
         Dialog().warningMessage('Save crashed with: \r\n' + str(e))
Beispiel #11
0
    def add_all(labels: List[str], start: np.ndarray, end: np.ndarray):
        """
        batch adding partitions, e.g. from loaded annotations file

        """
        try:
            Partitions.delete_all()
            assert len(labels) == start.size & start.size == end.size, 'Every loaded partition should have label, start and end'
            for l, s, e in zip(labels, start, end):
                SinglePartition(l, start=s, end=e)
        except Exception as e:
            Dialog().warningMessage('Partitions cannot be loaded\r\n' + str(e))
    def set_annotation_data(self):
        # NB: used to set initial guesses for annotations, otherwise, one has to start annotation from scratch
        #  one can use here simple findpeaks() type algos, or more signal-specific python algorithms
        #  also possible to run an algo beforehand (e.g. in Matlab), store the results and load them here

        # NB: OPTIONAL!!! Load existing annotation if an .h5 file with the same name found in self.existing_annotation_folder (be careful with self.outputfile_prefix)
        existing_annotation_file = pathlib.Path(
            self.existing_annotations_folder, self.fullpath.stem + '.h5')
        if self.annotation_exists(existing_annotation_file.stem):
            try:
                self.load(existing_annotation_file)
                qInfo('Loading annotations from {}'.format(
                    existing_annotation_file))
            except Exception as e:
                Dialog().warningMessage(
                    'Loading annotations from {} failed\r\n'.format(
                        existing_annotation_file) + str(e))
        else:
            # NB: 1. Find\fetch preliminary annotation data
            amp = self.tracks[self.main_track_label].value
            mpd = self.tracks[self.main_track_label].fs * 2
            idx_peak = detect_peaks(amp,
                                    mph=np.median(amp),
                                    mpd=mpd,
                                    valley=False,
                                    show=False,
                                    kpsh=False)
            idx_valley = detect_peaks(amp,
                                      mph=np.median(amp),
                                      mpd=mpd,
                                      valley=True,
                                      show=False,
                                      kpsh=False)
            idx_upstroke = detect_peaks(np.diff(amp),
                                        mph=np.median(np.diff(amp)),
                                        mpd=mpd,
                                        valley=False,
                                        show=False,
                                        kpsh=False)
            idx_downstroke = detect_peaks(np.diff(amp),
                                          mph=np.median(np.diff(amp)),
                                          mpd=mpd,
                                          valley=True,
                                          show=False,
                                          kpsh=False)

            # NB: 2. Use inherited functions to assign annotations to the main signal
            #  all annotation labels should be also in the provided AnnotationConfig file
            #  User can use _set_annotation_from_time or _set_annotation_from_idx
            self._set_annotation_from_idx('peak', idx_peak)
            self._set_annotation_from_idx('valley', idx_valley)
            self._set_annotation_from_idx('upstroke', idx_upstroke)
            self._set_annotation_from_idx('downstroke', idx_downstroke)
Beispiel #13
0
 def to_csv(filename: str):
     d = {'label': Partitions.all_labels(), 'start': Partitions.all_startpoints(), 'end': Partitions.all_endpoints()}
     df = dict_to_df_with_nans(d)
     try:
         df.to_csv(filename + '.csv', index=False)
     except OSError as e:
         try:
             xl = win32com.client.Dispatch("Excel.Application")
             xl.Quit()  # quit excel, as if user hit the close button/clicked file->exit.
             # xl.ActiveWorkBook.Close()  # close the active workbook
             df.to_csv(filename + '.csv', index=False)
         except Exception as e:
             Dialog().warningMessage('Save crashed with:\r\n' + str(e))
Beispiel #14
0
 def to_csv(filename: str):
     data = EpochModeConfig.get()
     try:
         data.window_data.to_csv(filename + '.csv', index=False)
     except OSError as e:
         try:
             xl = win32com.client.Dispatch("Excel.Application")
             xl.Quit(
             )  # quit excel, as if user hit the close button/clicked file->exit.
             # xl.ActiveWorkBook.Close()  # close the active workbook
             data.window_data.to_csv(filename + '.csv', index=False)
         except Exception as e:
             Dialog().warningMessage(
                 'Save of epoch mode annotations crashed with:\r\n' +
                 str(e))
Beispiel #15
0
 def find_files(self):
     if not os.path.isdir(self.db.DATAPATH):
         Dialog().warningMessage(
             'database path is incorrect. No files can be found.')
     tmp = '**/*.' + self.db.filetype if self.db.file_template is None else self.db.file_template
     for filename in Path(self.db.DATAPATH).glob(tmp):
         item = QtWidgets.QListWidgetItem()
         item.setFont(QtGui.QFont('Arial', 16))
         item.setText(filename.as_posix().replace(
             self.db.DATAPATH.as_posix() + '/', ''))
         if self.db.annotation_exists(pathlib.Path(item.text()).stem):
             item.setForeground(QtGui.QColor(QtCore.Qt.darkBlue))
             item.setBackground(QtGui.QColor(QtCore.Qt.lightGray))
         else:
             item.setForeground(QtGui.QColor(QtCore.Qt.blue))
         self.listWidget_files.addItem(item)
     self.listWidget_files.setMinimumWidth(
         self.listWidget_files.sizeHintForColumn(0))
     if self.listWidget_files.count() > 0:
         self.listWidget_files.setCurrentRow(0)
     self.listWidget_files.setFocus()
Beispiel #16
0
    def split_pinned_to(self):
        try:
            from gui.viewer import PALMS
            if self.pinned_to in PALMS.config['pinned_to_options']:
                self.pinned_to = self.pinned_to + ' ' + Database.get().main_track_label

            pinned_to_split = self.pinned_to.split()
            # TODO: make it more generic and foolproof, now it is assumed that pinned_to consists of two words: peak\valley + track_label
            assert_text = '{} pinned_to setting must consist of two words:\r\n' \
                          '"peak" or "valley" + track_label from the database;\r\n' \
                          'Current pinned_to value is {}\r\n' \
                          'Check your AnnotationConfig file {} and run the tool again'.format(self.name, self.pinned_to,
                                                                                              Database.get().annotation_config_file.stem)
            assert len(pinned_to_split) == 2, assert_text
            assert pinned_to_split[0] in PALMS.config['pinned_to_options'], assert_text
            self.pinned_to_location = pinned_to_split[0]
            self.pinned_to_track_label = pinned_to_split[1]
        except Exception as e:
            from utils.utils_gui import Dialog
            Dialog().warningMessage('Error in split_pinned_to():\r\n' + str(e))
            raise ValueError
Beispiel #17
0
    def load_annotation_config_from_csv(self, fileName=None):
        fn = fileName or self.fileName
        if not fn:
            fn, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Load AnnotationConfig", get_project_root().as_posix(),
                                                          " (*.csv);; All Files (*)", options=QtWidgets.QFileDialog.Options())

        if fn:
            from gui import PALMS
            with open(fn, "r") as csv:
                settings_init = pd.read_csv(csv)
                fiducials = []
                for _, row_data in settings_init.iterrows():
                    try:
                        assert all([k in row_data for k in PALMS.config['annotationConfig_columns']])
                    except:
                        Dialog().warningMessage('Columns in chosen {file} file do not match aConfColumns from config.py. Try again! '
                                                ''.format(file=fn))
                        return
                    fiducials.append(SingleFiducialConfig(row_data))

            aConf = AnnotationConfig.get()
            aConf.reset_fiducials_config(fiducials)
            self.show()
Beispiel #18
0
    def initialize_epoch_mode_settings_from_csv(csv):
        if csv is None:
            EpochModeConfig()
            qInfo(
                'Epoch mode init settings not given. Starting with default values'
            )

        try:
            csv_data = pd.read_csv(csv)
            winLen = int(csv_data.winLen.values[0])
            labels = list(csv_data.labels.values)
            description = list(csv_data.description.values)

            try:
                if not all(isinstance(elem, str) for elem in description):
                    description = labels
            except Exception as e:
                description = labels

            keys = list(csv_data['keys'].values)
            for i, k in enumerate(keys):
                if isinstance(k, (int, np.integer)):
                    keys[i] = str(k)
            default_label = csv_data.default_label.values[0]
            if not isinstance(default_label, str) and np.isnan(default_label):
                default_label = None
            EpochModeConfig(winLen=winLen,
                            labels=labels,
                            keys=keys,
                            default_label=default_label,
                            description=description)
        except Exception as e:
            Dialog().warningMessage(
                'Epoch mode settings processing failed with\r\n' + str(e) +
                '\r\nInitializing with defaults')
            EpochModeConfig()
Beispiel #19
0
 def onclick_find(self):
     Dialog().warningMessage('Custom file choice not implemented')
Beispiel #20
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.')
Beispiel #21
0
    def viewPopup(self, point):
        if isinstance(self.sender(), QtWidgets.QHeaderView):
            row = self.sender().logicalIndexAt(point)
        elif isinstance(self.sender(), QtWidgets.QLabel):
            row = self.rowFromWidget(self.sender())
        else:
            logging.error(f'do not know how to handle getting index of ',
                          f'{self.sender()} object')
            self.main_window.application.viewer.status(
                f'do not know how to handle getting index of ',
                f'{self.sender()} object')
            raise TypeError
        view = self.panel.views[row]
        menu = QtWidgets.QMenu(self.verticalHeader())
        menu.clear()
        move_menu = menu.addMenu('&Move View')
        link_menu = menu.addMenu("&Link Track")
        copy_menu = menu.addMenu("Copy View")

        derive_menu = menu.addMenu("Add derived tracks")

        linkAction = QtWidgets.QAction('Create Link in this Panel', self)
        linkAction.triggered.connect(
            partial(self.display_panel.linkTrack, view,
                    self.main_window.model.panels.index(self.panel)))
        link_menu.addAction(linkAction)
        link_menu.addSeparator()
        link_menu.setEnabled(False)

        copyAction = QtWidgets.QAction("Duplicate View in this Panel", self)
        copyAction.triggered.connect(
            partial(self.display_panel.copyView, view,
                    self.main_window.model.panels.index(self.panel)))
        copy_menu.addAction(copyAction)
        copy_menu.addSeparator()
        copy_menu.setEnabled(False)

        addDerivativeAction = QtWidgets.QAction('1st derivative', self)
        addDerivativeAction.triggered.connect(
            partial(self.display_panel.addDerivative, view,
                    self.main_window.model.panels.index(self.panel), 1))
        # TODO: derivative filters GUI
        derive_menu.addAction(addDerivativeAction)

        addDerivativeAction = QtWidgets.QAction('2nd derivative', self)
        addDerivativeAction.triggered.connect(
            partial(self.display_panel.addDerivative, view,
                    self.main_window.model.panels.index(self.panel), 2))
        derive_menu.addAction(addDerivativeAction)

        menu.addSeparator()

        try:
            addRRintervalMenu = menu.addMenu('RR-intevals')
            from logic.operation_mode.annotation import AnnotationConfig
            RRintervalMenu_actions = []
            for f in AnnotationConfig.all_fiducials():
                f_idx = AnnotationConfig.get().find_idx_by_name(f)
                if AnnotationConfig.get(
                ).fiducials[f_idx].annotation.x.size > 2:
                    action = QtWidgets.QAction(f,
                                               self,
                                               checkable=False,
                                               enabled=True)
                    action.triggered.connect(
                        partial(
                            self.main_window.application.add_view_from_track,
                            AnnotationConfig.get().fiducials[f_idx].annotation.
                            create_RRinterval_track(),
                            self.main_window.model.panels.index(self.panel)))
                    RRintervalMenu_actions.append(action)
                    addRRintervalMenu.addAction(action)
        except Exception as e:
            Dialog().warningMessage('Creating RR interval plot failed\r\n'
                                    'The error was:\r\n' + str(e))

        # TODO: load another track
        # TODO: move existing view

        db = Database.get()
        add_menu = menu.addMenu("Add tracks from DB")
        add_menu.setEnabled(False)
        if db is not None and db.ntracks() > 0:
            add_menu.setEnabled(True)
            for label, track in db.tracks.items():
                plot_signal_action = QtWidgets.QAction(label, self)
                plot_signal_action.triggered.connect(
                    partial(self.main_window.application.add_view_from_track,
                            track,
                            self.main_window.model.panels.index(self.panel)))
                add_menu.addAction(plot_signal_action)
                if label not in self.allViewsTrackLabels():
                    plot_signal_action.setEnabled(True)
                else:
                    plot_signal_action.setEnabled(False)

        menu.addSeparator()
        process_action = QtWidgets.QAction('Processor Config', self)
        process_action.triggered.connect(
            partial(self.main_window.application.viewer.processorConfig.show,
                    self.selectedView()))
        menu.addAction(process_action)
        process_action.setEnabled(False)

        menu.addSeparator()
        invertAction = QtWidgets.QAction('&Invert Track', self)
        invertAction.triggered.connect(self.main_window.invertView)
        invertAction.setEnabled(False)
        menu.addAction(invertAction)

        menu.addSeparator()
        deleteAction = QtWidgets.QAction('&Delete Track', self)
        deleteAction.triggered.connect(self.main_window.guiDelView)
        menu.addAction(deleteAction)

        for index, panel in enumerate(self.main_window.model.panels):
            if panel is self.panel:
                continue
            linkAction = QtWidgets.QAction(f'Link to Panel {index + 1}', self)
            linkAction.triggered.connect(
                partial(self.display_panel.linkTrack, view, index))
            link_menu.addAction(linkAction)

            moveAction = QtWidgets.QAction(f'Move To Panel {index + 1}', self)
            moveAction.triggered.connect(
                partial(self.display_panel.moveView, view, index))
            move_menu.addAction(moveAction)

            copyAction = QtWidgets.QAction(f'Copy to Panel {index + 1}', self)
            copyAction.triggered.connect(
                partial(self.display_panel.copyView, view, index))
            copy_menu.addAction(copyAction)

        moveAction = QtWidgets.QAction(f'Move to New Panel', self)
        moveAction.triggered.connect(
            partial(self.display_panel.moveView, view, -1))

        linkAction = QtWidgets.QAction(f'Link to New Panel', self)
        linkAction.triggered.connect(
            partial(self.display_panel.linkTrack, view, -1))

        copyAction = QtWidgets.QAction(f'Copy to New Panel', self)
        copyAction.triggered.connect(
            partial(self.display_panel.copyView, view, -1))

        move_menu.addSeparator()
        link_menu.addSeparator()
        copy_menu.addSeparator()
        move_menu.addAction(moveAction)
        link_menu.addAction(linkAction)
        copy_menu.addAction(copyAction)
        menu.popup(QtGui.QCursor.pos())
Beispiel #22
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
 def save(self, **kwargs):
     try:
         super().save(filename=self.fullpath.stem)
     except Exception as e:
         Dialog().warningMessage('Save crashed with: \r\n' + str(e),
                                 **kwargs)