class QCodesDBInspector(QtGui.QMainWindow): """ Main window of the inspectr tool. """ #: `Signal ()` -- Emitted when when there's an update to the internally #: cached data (the *data base data frame* :)). dbdfUpdated = QtCore.pyqtSignal() #: Signal (`dict`) -- emitted to communicate information about a given #: run to the widget that displays the information _sendInfo = QtCore.pyqtSignal(dict) def __init__(self, parent=None, dbPath=None): """Constructor for :class:`QCodesDBInspector`.""" super().__init__(parent) self._plotWindows = {} self.filepath = dbPath self.dbdf = None self.monitor = QtCore.QTimer() # flag for determining what has been loaded so far. # * None: nothing opened yet. # * -1: empty DS open. # * any value > 0: run ID from the most recent loading. self.latestRunId = None self.setWindowTitle('Plottr | QCoDeS dataset inspectr') ### GUI elements # Main Selection widgets self.dateList = DateList() self.runList = RunList() self.runInfo = RunInfo() rightSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) rightSplitter.addWidget(self.runList) rightSplitter.addWidget(self.runInfo) rightSplitter.setSizes([400, 200]) splitter = QtGui.QSplitter() splitter.addWidget(self.dateList) splitter.addWidget(rightSplitter) splitter.setSizes([100, 500]) self.setCentralWidget(splitter) # status bar self.status = QtGui.QStatusBar() self.setStatusBar(self.status) # toolbar self.toolbar = self.addToolBar('Data monitoring') # toolbar item: monitor interval self.monitorInput = MonitorIntervalInput() self.monitorInput.setToolTip('Set to 0 for disabling') self.monitorInput.intervalChanged.connect(self.setMonitorInterval) self.toolbar.addWidget(self.monitorInput) self.toolbar.addSeparator() # toolbar item: auto-launch plotting self.autoLaunchPlots = FormLayoutWrapper([('Auto-plot new', QtGui.QCheckBox())]) tt = "If checked, and automatic refresh is running, " tt += " launch plotting window for new datasets automatically." self.autoLaunchPlots.setToolTip(tt) self.toolbar.addWidget(self.autoLaunchPlots) # menu bar menu = self.menuBar() fileMenu = menu.addMenu('&File') # action: load db file loadAction = QtGui.QAction('&Load', self) loadAction.setShortcut('Ctrl+L') loadAction.triggered.connect(self.loadDB) fileMenu.addAction(loadAction) # action: updates from the db file refreshAction = QtGui.QAction('&Refresh', self) refreshAction.setShortcut('R') refreshAction.triggered.connect(self.refreshDB) fileMenu.addAction(refreshAction) # sizing self.resize(640, 640) ### Thread workers # DB loading. can be slow, so nice to have in a thread. self.loadDBProcess = LoadDBProcess() self.loadDBThread = QtCore.QThread() self.loadDBProcess.moveToThread(self.loadDBThread) self.loadDBProcess.pathSet.connect(self.loadDBThread.start) self.loadDBProcess.dbdfLoaded.connect(self.DBLoaded) self.loadDBProcess.dbdfLoaded.connect(self.loadDBThread.quit) self.loadDBThread.started.connect(self.loadDBProcess.loadDB) ### connect signals/slots self.dbdfUpdated.connect(self.updateDates) self.dbdfUpdated.connect(self.showDBPath) self.dateList.datesSelected.connect(self.setDateSelection) self.dateList.fileDropped.connect(self.loadFullDB) self.runList.runSelected.connect(self.setRunSelection) self.runList.runActivated.connect(self.plotRun) self._sendInfo.connect(self.runInfo.setInfo) self.monitor.timeout.connect(self.monitorTriggered) if self.filepath is not None: self.loadFullDB(self.filepath) def closeEvent(self, event): """ When closing the inspectr window, do some house keeping: * stop the monitor, if running * close all plot windows """ if self.monitor.isActive(): self.monitor.stop() for runId, info in self._plotWindows.items(): info['window'].close() @QtCore.pyqtSlot() def showDBPath(self): tstamp = time.strftime("%Y-%m-%d %H:%M:%S") path = os.path.abspath(self.filepath) self.status.showMessage(f"{path} (loaded: {tstamp})") ### loading the DB and populating the widgets @QtCore.pyqtSlot() def loadDB(self): """ Open a file dialog that allows selecting a .db file for loading. If a file is selected, opens the db. """ if self.filepath is not None: curdir = os.path.split(self.filepath)[0] else: curdir = os.getcwd() path, _fltr = QtGui.QFileDialog.getOpenFileName( self, 'Open qcodes .db file', curdir, 'qcodes .db files (*.db);;all files (*.*)', ) if path: logger().info(f"Opening: {path}") self.loadFullDB(path=path) def loadFullDB(self, path=None): if path is not None and path != self.filepath: self.filepath = path # makes sure we treat a newly loaded file fresh and not as a # refreshed one. self.latestRunId = None if self.filepath is not None: if not self.loadDBThread.isRunning(): self.loadDBProcess.setPath(self.filepath) def DBLoaded(self, dbdf): self.dbdf = dbdf self.dbdfUpdated.emit() self.dateList.sendSelectedDates() logger().debug('DB reloaded') if self.latestRunId is not None: idxs = self.dbdf.index.values newIdxs = idxs[idxs > self.latestRunId] if self.monitor.isActive( ) and self.autoLaunchPlots.elements['Auto-plot new'].isChecked(): for idx in newIdxs: self.plotRun(idx) self._plotWindows[idx]['window'].setMonitorInterval(2) @QtCore.pyqtSlot() def updateDates(self): if self.dbdf.size > 0: dates = list(self.dbdf.groupby('started date').indices.keys()) self.dateList.updateDates(dates) ### reloading the db @QtCore.pyqtSlot() def refreshDB(self): if self.filepath is not None: if self.dbdf is not None and self.dbdf.size > 0: self.latestRunId = self.dbdf.index.values.max() else: self.latestRunId = -1 self.loadFullDB() @QtCore.pyqtSlot(int) def setMonitorInterval(self, val): self.monitor.stop() if val > 0: self.monitor.start(val * 1000) self.monitorInput.spin.setValue(val) @QtCore.pyqtSlot() def monitorTriggered(self): logger().debug('Refreshing DB') self.refreshDB() ### handling user selections @QtCore.pyqtSlot(list) def setDateSelection(self, dates): if len(dates) > 0: selection = self.dbdf.loc[self.dbdf['started date'].isin( dates)].sort_index(ascending=False) self.runList.setRuns(selection.to_dict(orient='index')) else: self.runList.clear() @QtCore.pyqtSlot(int) def setRunSelection(self, runId): ds = load_dataset_from(self.filepath, runId) snap = None if hasattr(ds, 'snapshot'): snap = ds.snapshot structure = get_ds_structure(ds) for k, v in structure.items(): v.pop('values') contentInfo = {'Data structure': structure, 'QCoDeS Snapshot': snap} self._sendInfo.emit(contentInfo) @QtCore.pyqtSlot(int) def plotRun(self, runId): fc, win = autoplotQcodesDataset(pathAndId=(self.filepath, runId)) self._plotWindows[runId] = { 'flowchart': fc, 'window': win, } win.showTime()
class QCodesDBInspector(QtWidgets.QMainWindow): """ Main window of the inspectr tool. """ #: `Signal ()` -- Emitted when when there's an update to the internally #: cached data (the *data base data frame* :)). dbdfUpdated = Signal() #: Signal (`dict`) -- emitted to communicate information about a given #: run to the widget that displays the information _sendInfo = Signal(dict) def __init__(self, parent: Optional[QtWidgets.QWidget] = None, dbPath: Optional[str] = None): """Constructor for :class:`QCodesDBInspector`.""" super().__init__(parent) self._plotWindows: Dict[int, WindowDict] = {} self.filepath = dbPath self.dbdf: Optional[pandas.DataFrame] = None self.monitor = QtCore.QTimer() # flag for determining what has been loaded so far. # * None: nothing opened yet. # * -1: empty DS open. # * any value > 0: run ID from the most recent loading. self.latestRunId: Optional[int] = None self.setWindowTitle('Plottr | QCoDeS dataset inspectr') ### GUI elements # Main Selection widgets self.dateList = DateList() self._selected_dates: Tuple[str, ...] = () self.runList = RunList() self.runInfo = RunInfo() rightSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) rightSplitter.addWidget(self.runList) rightSplitter.addWidget(self.runInfo) rightSplitter.setSizes([400, 200]) splitter = QtWidgets.QSplitter() splitter.addWidget(self.dateList) splitter.addWidget(rightSplitter) splitter.setSizes([100, 500]) self.setCentralWidget(splitter) # status bar self.status = QtWidgets.QStatusBar() self.setStatusBar(self.status) # toolbar self.toolbar = self.addToolBar('Data monitoring') # toolbar item: monitor interval self.monitorInput = MonitorIntervalInput() self.monitorInput.setToolTip('Set to 0 for disabling') self.monitorInput.intervalChanged.connect(self.setMonitorInterval) self.toolbar.addWidget(self.monitorInput) self.toolbar.addSeparator() # toolbar item: auto-launch plotting self.autoLaunchPlots = FormLayoutWrapper([('Auto-plot new', QtWidgets.QCheckBox())]) tt = "If checked, and automatic refresh is running, " tt += " launch plotting window for new datasets automatically." self.autoLaunchPlots.setToolTip(tt) self.toolbar.addWidget(self.autoLaunchPlots) self.showOnlyStarAction = self.toolbar.addAction( RunList.tag_dict['star']) self.showOnlyStarAction.setToolTip('Show only starred runs') self.showOnlyStarAction.setCheckable(True) self.showOnlyStarAction.triggered.connect(self.updateRunList) self.showAlsoCrossAction = self.toolbar.addAction( RunList.tag_dict['cross']) self.showAlsoCrossAction.setToolTip('Show also crossed runs') self.showAlsoCrossAction.setCheckable(True) self.showAlsoCrossAction.triggered.connect(self.updateRunList) # menu bar menu = self.menuBar() fileMenu = menu.addMenu('&File') # action: load db file loadAction = QtWidgets.QAction('&Load', self) loadAction.setShortcut('Ctrl+L') loadAction.triggered.connect(self.loadDB) fileMenu.addAction(loadAction) # action: updates from the db file refreshAction = QtWidgets.QAction('&Refresh', self) refreshAction.setShortcut('R') refreshAction.triggered.connect(self.refreshDB) fileMenu.addAction(refreshAction) # action: star/unstar the selected run self.starAction = QtWidgets.QAction() self.starAction.setShortcut('Ctrl+Alt+S') self.starAction.triggered.connect(self.starSelectedRun) self.addAction(self.starAction) # action: cross/uncross the selected run self.crossAction = QtWidgets.QAction() self.crossAction.setShortcut('Ctrl+Alt+X') self.crossAction.triggered.connect(self.crossSelectedRun) self.addAction(self.crossAction) # sizing scaledSize = 640 * rint(self.logicalDpiX() / 96.0) self.resize(scaledSize, scaledSize) ### Thread workers # DB loading. can be slow, so nice to have in a thread. self.loadDBProcess = LoadDBProcess() self.loadDBThread = QtCore.QThread() self.loadDBProcess.moveToThread(self.loadDBThread) self.loadDBProcess.pathSet.connect(self.loadDBThread.start) self.loadDBProcess.dbdfLoaded.connect(self.DBLoaded) self.loadDBProcess.dbdfLoaded.connect(self.loadDBThread.quit) self.loadDBThread.started.connect(self.loadDBProcess.loadDB) ### connect signals/slots self.dbdfUpdated.connect(self.updateDates) self.dbdfUpdated.connect(self.showDBPath) self.dateList.datesSelected.connect(self.setDateSelection) self.dateList.fileDropped.connect(self.loadFullDB) self.runList.runSelected.connect(self.setRunSelection) self.runList.runActivated.connect(self.plotRun) self._sendInfo.connect(self.runInfo.setInfo) self.monitor.timeout.connect(self.monitorTriggered) if self.filepath is not None: self.loadFullDB(self.filepath) def closeEvent(self, event: QtGui.QCloseEvent) -> None: """ When closing the inspectr window, do some house keeping: * stop the monitor, if running * close all plot windows """ if self.monitor.isActive(): self.monitor.stop() for runId, info in self._plotWindows.items(): info['window'].close() @Slot() def showDBPath(self) -> None: tstamp = time.strftime("%Y-%m-%d %H:%M:%S") assert self.filepath is not None path = os.path.abspath(self.filepath) self.status.showMessage(f"{path} (loaded: {tstamp})") ### loading the DB and populating the widgets @Slot() def loadDB(self) -> None: """ Open a file dialog that allows selecting a .db file for loading. If a file is selected, opens the db. """ if self.filepath is not None: curdir = os.path.split(self.filepath)[0] else: curdir = os.getcwd() path, _fltr = QtWidgets.QFileDialog.getOpenFileName( self, 'Open qcodes .db file', curdir, 'qcodes .db files (*.db);;all files (*.*)', ) if path: logger().info(f"Opening: {path}") self.loadFullDB(path=path) def loadFullDB(self, path: Optional[str] = None) -> None: if path is not None and path != self.filepath: self.filepath = path # makes sure we treat a newly loaded file fresh and not as a # refreshed one. self.latestRunId = None if self.filepath is not None: if not self.loadDBThread.isRunning(): self.loadDBProcess.setPath(self.filepath) def DBLoaded(self, dbdf: pandas.DataFrame) -> None: if dbdf.equals(self.dbdf): logger().debug('DB reloaded with no changes. Skipping update') return None self.dbdf = dbdf self.dbdfUpdated.emit() self.dateList.sendSelectedDates() logger().debug('DB reloaded') if self.latestRunId is not None: idxs = self.dbdf.index.values newIdxs = idxs[idxs > self.latestRunId] if self.monitor.isActive( ) and self.autoLaunchPlots.elements['Auto-plot new'].isChecked(): for idx in newIdxs: self.plotRun(idx) self._plotWindows[idx]['window'].setMonitorInterval( self.monitorInput.spin.value()) @Slot() def updateDates(self) -> None: assert self.dbdf is not None if self.dbdf.size > 0: dates = list(self.dbdf.groupby('started_date').indices.keys()) self.dateList.updateDates(dates) ### reloading the db @Slot() def refreshDB(self) -> None: if self.filepath is not None: if self.dbdf is not None and self.dbdf.size > 0: self.latestRunId = self.dbdf.index.values.max() else: self.latestRunId = -1 self.loadFullDB() @Slot(float) def setMonitorInterval(self, val: float) -> None: self.monitor.stop() if val > 0: self.monitor.start(int(val * 1000)) self.monitorInput.spin.setValue(val) @Slot() def monitorTriggered(self) -> None: logger().debug('Refreshing DB') self.refreshDB() @Slot() def updateRunList(self) -> None: if self.dbdf is None: return selection = self.dbdf.loc[self.dbdf['started_date'].isin( self._selected_dates)].sort_index(ascending=False) show_only_star = self.showOnlyStarAction.isChecked() show_also_cross = self.showAlsoCrossAction.isChecked() self.runList.setRuns(selection.to_dict(orient='index'), show_only_star, show_also_cross) ### handling user selections @Slot(list) def setDateSelection(self, dates: Sequence[str]) -> None: if len(dates) > 0: assert self.dbdf is not None selection = self.dbdf.loc[self.dbdf['started_date'].isin( dates)].sort_index(ascending=False) old_dates = self._selected_dates if not all(date in old_dates for date in dates): show_only_star = self.showOnlyStarAction.isChecked() show_also_cross = self.showAlsoCrossAction.isChecked() self.runList.setRuns(selection.to_dict(orient='index'), show_only_star, show_also_cross) else: self.runList.updateRuns(selection.to_dict(orient='index')) self._selected_dates = tuple(dates) else: self._selected_dates = () self.runList.clear() @Slot(int) def setRunSelection(self, runId: int) -> None: assert self.filepath is not None ds = load_dataset_from(self.filepath, runId) snap = None if hasattr(ds, 'snapshot'): snap = ds.snapshot structure = cast(Dict[str, dict], get_ds_structure(ds)) # cast away typed dict so we can pop a key for k, v in structure.items(): v.pop('values') contentInfo = { 'Data structure': structure, 'Metadata': ds.metadata, 'QCoDeS Snapshot': snap } self._sendInfo.emit(contentInfo) @Slot(int) def plotRun(self, runId: int) -> None: assert self.filepath is not None fc, win = autoplotQcodesDataset(pathAndId=(self.filepath, runId)) self._plotWindows[runId] = { 'flowchart': fc, 'window': win, } win.showTime() def setTag(self, item: QtWidgets.QTreeWidgetItem, tag: str) -> None: # set tag in the database assert self.filepath is not None runId = int(item.text(0)) ds = load_dataset_from(self.filepath, runId) ds.add_metadata('inspectr_tag', tag) # set tag in self.dbdf assert self.dbdf is not None self.dbdf.at[runId, 'inspectr_tag'] = tag # set tag in the GUI tag_char = self.runList.tag_dict[tag] item.setText(1, tag_char) # refresh the RunInfo widget self.setRunSelection(runId) def tagSelectedRun(self, tag: str) -> None: for item in self.runList.selectedItems(): current_tag_char = item.text(1) tag_char = self.runList.tag_dict[tag] if current_tag_char == tag_char: # if already tagged self.setTag(item, '') # clear tag else: # if not tagged self.setTag(item, tag) # set tag @Slot() def starSelectedRun(self) -> None: self.tagSelectedRun('star') @Slot() def crossSelectedRun(self) -> None: self.tagSelectedRun('cross')