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)
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)
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)
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()
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()