Beispiel #1
0
    def _SetupUI(self):
        main_layout = QVBoxLayout()
        search_filter_widget = QWidget()
        search_filter_layout = QHBoxLayout(search_filter_widget)
        central_widget = QWidget()
        central_layout = QVBoxLayout(central_widget)

        panel_splitter = QSplitter(Qt.Horizontal)
        panel_splitter.setHandleWidth(4)
        panel_splitter.addWidget(self._source_panel)
        panel_splitter.addWidget(central_widget)
        panel_splitter.setStretchFactor(0, 0)
        panel_splitter.setStretchFactor(1, 1)
        panel_splitter.moveSplitter(LEFT_PANEL_WIDTH, 1)
        panel_splitter.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        search_filter_layout.addWidget(self._search_bar)
        search_filter_layout.addWidget(self._filter_bar)
        search_filter_layout.setContentsMargins(0, 0, 0, 0)

        central_layout.addWidget(search_filter_widget)
        central_layout.addWidget(self._splitter)
        central_layout.addWidget(self._status_bar)
        central_layout.setContentsMargins(0, 0, 0, 0)

        main_layout.addWidget(panel_splitter)
        self.setLayout(main_layout)
Beispiel #2
0
 def setup_ui(self):
     vertical_layout = QVBoxLayout(self)
     vertical_layout.setSpacing(6)
     vertical_layout.setContentsMargins(11, 11, 11, 11)
     splitter_v = QSplitter(self)
     splitter_v.setOrientation(PySide2.QtCore.Qt.Vertical)
     size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
     size_policy.setHorizontalStretch(0)
     size_policy.setVerticalStretch(0)
     size_policy.setHeightForWidth(
         splitter_v.sizePolicy().hasHeightForWidth())
     splitter_v.setSizePolicy(size_policy)
     splitter_h = QSplitter(splitter_v)
     splitter_h.setOrientation(PySide2.QtCore.Qt.Horizontal)
     vertical_layout_widget = QWidget(splitter_h)
     self.vertical_layout_left = QVBoxLayout(vertical_layout_widget)
     self.vertical_layout_left.setSpacing(6)
     self.vertical_layout_left.setSizeConstraint(
         QLayout.SetDefaultConstraint)
     self.vertical_layout_left.setContentsMargins(0, 0, 0, 0)
     splitter_h.addWidget(vertical_layout_widget)
     vertical_layout_widget2 = QWidget(splitter_h)
     self.vertical_layout_right = QVBoxLayout(vertical_layout_widget2)
     self.vertical_layout_right.setContentsMargins(0, 0, 0, 0)
     splitter_h.addWidget(vertical_layout_widget2)
     splitter_v.addWidget(splitter_h)
     vertical_layout_widget3 = QWidget(splitter_v)
     vertical_layout_bottom = QVBoxLayout(vertical_layout_widget3)
     vertical_layout_bottom.setSpacing(6)
     vertical_layout_bottom.setSizeConstraint(QLayout.SetDefaultConstraint)
     vertical_layout_bottom.setContentsMargins(11, 0, 11, 11)
     text_browser = QTextBrowser(vertical_layout_widget3)
     vertical_layout_bottom.addWidget(text_browser)
     splitter_v.addWidget(vertical_layout_widget3)
     vertical_layout.addWidget(splitter_v)
Beispiel #3
0
class ProcessMonitorUI:
    def __init__(self, window: ProcessMonitorWindow):
        self.window = window

        self.main_widget = QWidget(window)
        self.main_layout = QVBoxLayout()
        self.layout_connection = QHBoxLayout()
        self.txt_conenction = QLineEdit()
        self.btn_connect = QPushButton()
        self.layout_connection.addWidget(self.txt_conenction)
        self.layout_connection.addWidget(self.btn_connect)
        self.process_list = ProcessListWidget()

        self.tabs_output = ProcessOutputTabsWidget()

        self.splitter = QSplitter(Qt.Horizontal)
        self.splitter.setMinimumSize(680, 540)
        policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
        policy.setHorizontalStretch(0)
        policy.setVerticalStretch(0)
        policy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
        self.splitter.setSizePolicy(policy)
        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)

        self.main_layout.addLayout(self.layout_connection)
        self.splitter.addWidget(self.process_list)
        self.splitter.addWidget(self.tabs_output)
        self.main_layout.addWidget(self.splitter)

        self.main_widget.setLayout(self.main_layout)
        self.statusbar = QStatusBar(window)

        self.txt_conenction.setPlaceholderText("ws://127.0.0.1:8766")
        self.txt_conenction.setText("ws://127.0.0.1:8766")
        self.btn_connect.setText("Connect")

        window.setCentralWidget(self.main_widget)
        window.setStatusBar(self.statusbar)
        window.setWindowTitle("Process Monitor")
        window.setWindowIcon(window.style().standardIcon(QStyle.SP_BrowserReload))
        self.set_disconnected_ui("Click on Connect to establish a connection")

    def set_disconnected_ui(self, msg: str):
        self.process_list.setDisabled(True)
        self.btn_connect.setDisabled(False)
        self.statusbar.showMessage(f"Disconnected. {msg}")

    def set_connected_ui(self):
        self.process_list.setDisabled(False)
        self.btn_connect.setDisabled(True)
        self.statusbar.showMessage("Connection established.")

    def handle_output(self, output: OutputEvent):
        self.tabs_output.append_output(output.uid, output.output)
Beispiel #4
0
class NavigationWidget(QWidget):
    def __init__(self, index_filename):
        QWidget.__init__(self)

        self.data_index = pd.DataFrame()
        self.data_info = aecg.tools.indexer.StudyInfo()
        self.data_index_stats = aecg.tools.indexer.StudyStats(
            self.data_info, self.data_index)

        # Getting the Models
        self.project_loaded = ''
        self.projectmodel = ProjectTreeModel()

        # Creating a QTreeView for displaying the selected project index
        self.project_treeview = QTreeView()
        self.project_treeview.setModel(self.projectmodel)
        self.phorizontal_header = self.project_treeview.header()
        self.phorizontal_header.setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.phorizontal_header.setStretchLastSection(True)

        self.sp_right = QSplitter(Qt.Orientation.Horizontal)
        self.sp_left = QSplitter(Qt.Orientation.Vertical, self.sp_right)
        # NavigationWidget Layout
        self.main_layout = QVBoxLayout()
        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        # Left side
        # Left side - Top layout
        # Left side - Bottom Layout
        size.setVerticalStretch(4)
        self.project_treeview.setSizePolicy(size)

        self.sp_left.addWidget(self.project_treeview)

        # Right side
        size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        size.setHorizontalStretch(2)
        size.setHeightForWidth(False)
        self.tabdisplays = TabDisplays(self)
        self.sp_right.addWidget(self.tabdisplays)
        self.tabdisplays.validator_data_ready.connect(
            self.load_projectindex_after_validation)
        self.sp_right.setSizePolicy(size)

        # Set the layout to the QWidget
        self.main_layout.addWidget(self.sp_right)
        self.setLayout(self.main_layout)

        self.tabdisplays.setCurrentWidget(self.tabdisplays.validator)

        # Load study index
        if index_filename and (index_filename != ""):
            if os.path.exists(index_filename):
                self.load_projectindex(os.path.normpath(index_filename))
            else:
                QMessageBox.warning(self, f"Study index file not found",
                                    f"{index_filename} not found")

    projectindex_loaded = Signal()
    projectindexstats_loaded = Signal()

    def load_projectindex_after_validation(self):
        self.load_projectindex(
            os.path.normpath(self.tabdisplays.study_info_file.text()))

    def load_projectindex(self, project_idx_file):
        index_loaded = False
        stats_loaded = False
        if project_idx_file != "":
            if os.path.exists(project_idx_file):
                try:
                    self.parent().status.showMessage(
                        f"Loading {project_idx_file}")
                    progress = QProgressDialog("Loading index file...",
                                               "Cancel", 0, 3, self)
                    progress.setWindowTitle("Loading study index")
                    progress.setWindowModality(Qt.WindowModal)
                    progress.setMinimumWidth(300)
                    progress.setMinimumDuration(0)
                    progress.forceShow()
                    progress.setValue(0)
                    wb = load_workbook(project_idx_file, read_only=True)
                    progress.setValue(1)
                    ws = wb['Index']
                    ws.reset_dimensions()
                    data = ws.values
                    cols = next(data)
                    data = list(data)
                    progress.setValue(2)
                    self.data_index = pd.DataFrame(data,
                                                   columns=cols).fillna("")
                    progress.setValue(3)
                    progress.close()
                    # Parse index to the tree
                    num_ecgs = 0
                    if "EGREFID" in self.data_index.columns:
                        num_ecgs = self.data_index[[
                            "ZIPFILE", "AECGXML", "EGREFID", "WFTYPE"
                        ]].drop_duplicates().shape[0]
                        progress = QProgressDialog("Parsing index ...",
                                                   "Cancel", 0, num_ecgs, self)
                        progress.setWindowTitle("Loading study index")
                        progress.setLabelText("Parsing index...")
                        progress.setWindowModality(Qt.WindowModal)
                        progress.setMinimumWidth(300)
                        progress.setMinimumDuration(0)
                        progress.forceShow()
                        progress.setValue(0)
                        self.projectmodel = ProjectTreeModel(
                            self.data_index, progress_dialog=progress)
                        self.project_treeview.setModel(self.projectmodel)
                        self.project_treeview.selectionModel().\
                            selectionChanged.connect(self.load_data)
                        progress.close()
                    else:
                        QMessageBox.warning(
                            self, "EGREFID missing",
                            f"EGREFID column missing Index sheet of "
                            f"{project_idx_file}")
                    self.project_loaded = project_idx_file

                    # Reset aECG display
                    self.tabdisplays.aecg_display.aecg_data = None
                    self.tabdisplays.aecg_display.plot_aecg()

                    # Populate study information/validator tab
                    self.tabdisplays.load_study_info(project_idx_file)
                    self.data_index_info = self.tabdisplays.studyindex_info

                    index_loaded = True
                    try:
                        progress = QProgressDialog(
                            "Loading study index stats...", "Cancel", 0, 3,
                            self)
                        progress.setWindowTitle("Loading study index stats")
                        progress.setWindowModality(Qt.WindowModal)
                        progress.setMinimumWidth(300)
                        progress.setMinimumDuration(0)
                        progress.forceShow()
                        progress.setValue(0)
                        ws = wb['Stats']
                        ws.reset_dimensions()
                        data = ws.values
                        cols = next(data)
                        data = list(data)
                        progress.setValue(1)
                        progress.forceShow()
                        statsdf = pd.DataFrame(data, columns=cols).fillna("")
                        progress.setValue(2)
                        progress.forceShow()
                        self.data_index_stats = aecg.tools.indexer.StudyStats()
                        self.data_index_stats.__dict__.update(
                            statsdf.set_index("Property").transpose().
                            reset_index(drop=True).to_dict('index')[0])
                        progress.setValue(3)
                        progress.forceShow()
                        progress.close()
                        stats_loaded = True
                    except Exception as ex:
                        QMessageBox.warning(
                            self, f"Error loading study index stats",
                            f"Error loading study index stats from"
                            f"{project_idx_file}\n{str(ex)}")
                except Exception as ex:
                    QMessageBox.warning(
                        self, f"Error loading study index",
                        f"Error loading study index from {project_idx_file}"
                        f"\n{str(ex)}")
            else:
                QMessageBox.warning(self, f"Study index file not found",
                                    f"{project_idx_file} not found")

            if index_loaded:
                self.projectindex_loaded.emit()
                if stats_loaded:
                    self.projectindexstats_loaded.emit()
            self.parentWidget().status.clearMessage()

    def load_data(self, selected, deselected):
        self.tabdisplays.setCurrentWidget(self.tabdisplays.waveforms)
        rhythm = self.projectmodel.itemData(selected.indexes()[2])[0]
        derived = self.projectmodel.itemData(selected.indexes()[3])[0]
        # Get study directory provided in the GUI
        studydir = self.tabdisplays.effective_aecgs_dir(self, silent=True)
        # Calculate effective study dir
        aecg_xml_file = self.projectmodel.itemData(selected.indexes()[5])[0]
        if aecg_xml_file != "":
            zipfile = self.projectmodel.itemData(selected.indexes()[4])[0]
            if zipfile != "":
                zipfile = os.path.join(studydir, zipfile)
            else:
                aecg_xml_file = os.path.join(studydir, aecg_xml_file)
            # Load aECG file
            aecg_data = aecg.io.read_aecg(aecg_xml_file,
                                          zipfile,
                                          include_digits=True,
                                          in_memory_xml=True,
                                          log_validation=False)
            if aecg_data.xmlfound:
                # Plot aECG
                self.tabdisplays.aecg_display.set_aecg(aecg_data)
                self.tabdisplays.aecg_display.plot_aecg(
                    rhythm,
                    derived,
                    ecg_layout=aecg.utils.ECG_plot_layout(
                        self.tabdisplays.cbECGLayout.currentIndex() + 1))

                # Populate XML viewer
                self.tabdisplays.xml_display.setText(aecg_data.xmlstring())
            else:
                QMessageBox.warning(self, "aECG XML file not found",
                                    f"aECG XML {aecg_xml_file} not found")
                self.parentWidget().update_status_bar()
Beispiel #5
0
class ScatterPlotMatrix(DataView):
    def __init__(self, workbench: WorkbenchModel, parent=None):
        super().__init__(workbench, parent)
        self.__frameModel: FrameModel = None

        # Create widget for the two tables
        sideLayout = QVBoxLayout()
        self.__matrixAttributes = SearchableAttributeTableWidget(
            self, True, False, False, [Types.Numeric, Types.Ordinal])
        matrixLabel = QLabel(
            'Select at least two numeric attributes and press \'Create chart\' to plot'
        )
        matrixLabel.setWordWrap(True)
        self.__createButton = QPushButton('Create chart', self)
        self.__colorByBox = QComboBox(self)
        self.__autoDownsample = QCheckBox('Auto downsample', self)
        self.__useOpenGL = QCheckBox('Use OpenGL', self)
        self.__autoDownsample.setToolTip(
            'If too many points are to be rendered, this will try\n'
            'to plot only a subsample, improving performance with\n'
            'zooming and panning, but increasing rendering time')
        self.__useOpenGL.setToolTip(
            'Enforce usage of GPU acceleration to render charts.\n'
            'It is still an experimental feature but should speed\n'
            'up rendering with huge set of points')

        # Layout for checkboxes
        optionsLayout = QHBoxLayout()
        optionsLayout.addWidget(self.__autoDownsample, 0, Qt.AlignRight)
        optionsLayout.addWidget(self.__useOpenGL, 0, Qt.AlignRight)

        sideLayout.addWidget(matrixLabel)
        sideLayout.addWidget(self.__matrixAttributes)
        sideLayout.addLayout(optionsLayout)
        sideLayout.addWidget(self.__colorByBox, 0, Qt.AlignBottom)
        sideLayout.addWidget(self.__createButton, 0, Qt.AlignBottom)
        self.__matrixLayout: pg.GraphicsLayoutWidget = pg.GraphicsLayoutWidget(
        )
        self.__layout = QHBoxLayout(self)
        self.__comboModel = AttributeProxyModel(
            [Types.String, Types.Ordinal, Types.Nominal], self)

        # Error label to signal errors
        self.errorLabel = QLabel(self)
        self.errorLabel.setWordWrap(True)
        sideLayout.addWidget(self.errorLabel)
        self.errorLabel.hide()

        self.__splitter = QSplitter(self)
        sideWidget = QWidget(self)
        sideWidget.setLayout(sideLayout)
        # chartWidget.setMinimumWidth(300)
        self.__splitter.addWidget(self.__matrixLayout)
        self.__splitter.addWidget(sideWidget)
        self.__splitter.setSizes([600, 300])
        self.__layout.addWidget(self.__splitter)
        self.__splitter.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        # Connect
        self.__createButton.clicked.connect(self.showScatterPlots)

        self.spinner = QtWaitingSpinner(self.__matrixLayout)

    @Slot()
    def showScatterPlots(self) -> None:
        self.__createButton.setDisabled(True)
        # Create plot with selected attributes
        attributes: Set[int] = self.__matrixAttributes.model().checked
        if len(attributes) < 2:
            self.errorLabel.setText('Select at least 2 attributes')
            self.errorLabel.setStyleSheet('color: red')
            self.errorLabel.show()
            return  # stop
        elif self.errorLabel.isVisible():
            self.errorLabel.hide()
        # Get index of groupBy Attribute
        group: int = None
        selectedIndex = self.__colorByBox.currentIndex()
        if self.__comboModel.rowCount() > 0 and selectedIndex != -1:
            index: QModelIndex = self.__comboModel.mapToSource(
                self.__comboModel.index(selectedIndex, 0, QModelIndex()))
            group = index.row() if index.isValid() else None

        # Create a new matrix layout and delete the old one
        matrix = GraphicsPlotLayout(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.useOpenGL(self.__useOpenGL.isChecked())
        matrix.show()

        # Get attributes of interest
        toKeep: List[int] = list(attributes) if group is None else [
            group, *attributes
        ]
        filterDf = self.__frameModel.frame.getRawFrame().iloc[:, toKeep]
        # Create a worker to create scatter-plots on different thread
        worker = Worker(ProcessDataframe(), (filterDf, group, attributes))

        worker.signals.result.connect(self.__createPlots)
        # No need to deal with error/finished signals since there is nothing to do
        worker.setAutoDelete(True)
        self.spinner.start()
        QThreadPool.globalInstance().start(worker)

    def resetScatterPlotMatrix(self) -> None:
        # Create a new matrix layout
        matrix = pg.GraphicsLayoutWidget(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.show()

    @Slot(object, object)
    def __createPlots(
            self, _, result: Tuple[pd.DataFrame, List[str], List[int],
                                   bool]) -> None:
        """ Create plots and render all graphic items """
        # Unpack results
        df, names, attributes, grouped = result

        # Populate the matrix
        for r in range(len(attributes)):
            for c in range(len(attributes)):
                if r == c:
                    name: str = names[r]
                    self.__matrixLayout.addLabel(row=r, col=c, text=name)
                else:
                    xColName: str = names[c]
                    yColName: str = names[r]
                    seriesList = self.__createScatterSeries(
                        df=df,
                        xCol=xColName,
                        yCol=yColName,
                        groupBy=grouped,
                        ds=self.__autoDownsample.isChecked())
                    plot = self.__matrixLayout.addPlot(row=r, col=c)
                    for series in seriesList:
                        plot.addItem(series)
                    # Coordinates and data for later use
                    plot.row = r
                    plot.col = c
                    plot.xName = xColName
                    plot.yName = yColName
        # When all plot are created stop spinner and re-enable button
        self.spinner.stop()
        self.__createButton.setEnabled(True)

    @staticmethod
    def __createScatterSeries(df: Union[pd.DataFrame,
                                        pd.core.groupby.DataFrameGroupBy],
                              xCol: str, yCol: str, groupBy: bool,
                              ds: bool) -> List[pg.PlotDataItem]:
        """
        Creates a list of series of points to be plotted in the scatterplot

        :param df: the input dataframe
        :param xCol: name of the feature to use as x-axis
        :param yCol: name of the feature to use as y-axis
        :param groupBy: whether the feature dataframe is grouped by some attribute
        :param ds: whether to auto downsample the set of points during rendering

        :return:

        """
        allSeries = list()
        if groupBy:
            df: pd.core.groupby.DataFrameGroupBy
            colours = randomColors(len(df))
            i = 0
            for groupName, groupedDf in df:
                # Remove any row with nan values
                gdf = groupedDf.dropna()
                qSeries1 = pg.PlotDataItem(x=gdf[xCol],
                                           y=gdf[yCol],
                                           autoDownsample=ds,
                                           name=str(groupName),
                                           symbolBrush=pg.mkBrush(colours[i]),
                                           symbol='o',
                                           pen=None)
                allSeries.append(qSeries1)
                i += 1
        else:
            df: pd.DataFrame
            # Remove any row with nan values
            df = df.dropna()
            series = pg.PlotDataItem(x=df[xCol],
                                     y=df[yCol],
                                     autoDownsample=ds,
                                     symbol='o',
                                     pen=None)
            allSeries.append(series)
        return allSeries

    @Slot(str, str)
    def onFrameSelectionChanged(self, frameName: str, *_) -> None:
        if not frameName:
            return
        self.__frameModel = self._workbench.getDataframeModelByName(frameName)
        self.__matrixAttributes.setSourceFrameModel(self.__frameModel)
        # Combo box
        attributes = AttributeTableModel(self, False, False, False)
        attributes.setFrameModel(self.__frameModel)
        oldModel = self.__comboModel.sourceModel()
        self.__comboModel.setSourceModel(attributes)
        if oldModel:
            oldModel.deleteLater()
        self.__colorByBox.setModel(self.__comboModel)
        # Reset attribute panel
        self.resetScatterPlotMatrix()