Esempio n. 1
0
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()
Esempio n. 2
0
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')