Exemplo n.º 1
0
class ScanWindow(ScanWindowBase):
    def __init__(self, operator, scan_name='scan', parent=None):
        self.logger = logging.getLogger(__name__)
        super().__init__(parent)
        self.setWindowTitle('Analog Discovery 2')
        self.operator = operator
        self.scan_name = scan_name

        # # For loading a .ui file (created with QtDesigner):
        # p = os.path.dirname(__file__)
        # uic.loadUi(os.path.join(p, 'design/UI/main_window.ui'), self)

        self.set_UI()

        # create thread and timer objects for scan
        self.scan_timer = QTimer(timeout=self.update_scan)
        self.scan_thread = WorkThread(self.operator.do_scan)

    def set_UI(self):
        """
        Code-based generation of the user-interface based on PyQT
        """

        self.setWindowTitle('Digilent AD2 Scan example')
        # display statusbar
        self.statusBar()
        ### The menu bar:
        mod_config_action = QAction("Con&fig",
                                    self,
                                    triggered=self.mod_scan_config,
                                    shortcut="Ctrl+Shift+C",
                                    statusTip='Modify the scan config')
        quit_action = QAction("&Close",
                              self,
                              triggered=self.close,
                              shortcut="Ctrl+W",
                              statusTip='Close the scan window')

        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu('&File')
        fileMenu.addAction(mod_config_action)
        fileMenu.addAction(quit_action)

        ### General layout
        central_widget = QWidget()
        central_layout = QHBoxLayout(central_widget)

        # Layout for left hand controls
        control_layout = QVBoxLayout()

        ### Scan box
        self.box_scan = QGroupBox('Scan')
        layout_scan = QVBoxLayout()
        self.box_scan.setLayout(layout_scan)
        control_layout.addWidget(self.box_scan)

        layout_scan_form = QFormLayout()
        layout_scan.addLayout(layout_scan_form)
        layout_scan_buttons = QHBoxLayout()
        layout_scan.addLayout(layout_scan_buttons)

        self.scan_start_spinbox = QDoubleSpinBox(
            suffix='V',
            minimum=-100,
            singleStep=0.001,
            valueChanged=self.scan_start_value)
        # self.scan_start_spinbox.valueChanged.connect(self.scan_start_value)

        self.scan_stop_spinbox = QDoubleSpinBox(
            suffix='V',
            minimum=-100,
            singleStep=0.001,
            valueChanged=self.scan_stop_value)

        self.scan_step_spinbox = QDoubleSpinBox(
            suffix='V',
            minimum=-100,
            singleStep=0.001,
            valueChanged=self.scan_step_value)

        self.scan_start_label = QLabel('start')
        self.scan_stop_label = QLabel('stop')
        self.scan_step_label = QLabel('step')
        layout_scan_form.addRow(self.scan_start_label, self.scan_start_spinbox)
        layout_scan_form.addRow(self.scan_stop_label, self.scan_stop_spinbox)
        layout_scan_form.addRow(self.scan_step_label, self.scan_step_spinbox)

        self.start_button = QPushButton('Start', clicked=self.start_scan)
        self.pause_button = QPushButton('Pause', clicked=self.pause_scan)
        self.stop_button = QPushButton('Stop', clicked=self.stop_scan)
        self.kill_button = QPushButton('Kill', clicked=self.kill_scan)
        # Haven't decided what names are best. Suggestions:
        # start, pause, interrupt, stop, abort, quit, kill

        layout_scan_buttons.addWidget(self.start_button)
        layout_scan_buttons.addWidget(self.pause_button)
        layout_scan_buttons.addWidget(self.stop_button)
        layout_scan_buttons.addWidget(self.kill_button)

        self.saver = SaverWidget(self.operator.save_scan)
        layout_scan.addWidget(self.saver)

        ### Graphs:
        self.graph_win = pg.GraphicsWindow()
        self.graph_win.resize(1000, 600)
        self.plot1 = self.graph_win.addPlot()
        self.curve1 = self.plot1.plot(pen='y')

        # Add an empty widget at the bottom of the control layout to make layout nicer
        dummy = QWidget()
        dummy.setSizePolicy(QSizePolicy.MinimumExpanding,
                            QSizePolicy.MinimumExpanding)
        control_layout.addWidget(dummy)
        # Add control layout and graph window to central layout and apply central layout to window
        central_layout.addLayout(control_layout)
        central_layout.addWidget(self.graph_win)
        self.setCentralWidget(central_widget)

        self.apply_properties()
        self.reset_fields()

    def mod_scan_config(self):
        """
        Open the Modify Config window for the scan properties
        """
        conf_win = ModifyConfig(self.operator.properties[self.scan_name],
                                apply_callback=self.apply_properties,
                                parent=self)
        conf_win.show()

    def apply_properties(self):
        """
        Apply properties dictionary to gui elements.
        """
        self.logger.debug('Applying config properties to gui elements')
        self.operator._set_scan_start(self.operator.properties[
            self.scan_name]['start'])  # this optional line checks validity
        self.scan_start_spinbox.setValue(
            self.operator.properties[self.scan_name]['start'])

        self.operator._set_scan_stop(self.operator.properties[
            self.scan_name]['stop'])  # this optional line checks validity
        self.scan_stop_spinbox.setValue(
            self.operator.properties[self.scan_name]['stop'])

        self.operator._set_scan_step(self.operator.properties[
            self.scan_name]['step'])  # this optional line checks validity
        self.scan_step_spinbox.setValue(
            self.operator.properties[self.scan_name]['step'])

        if 'title' in self.operator.properties[self.scan_name]:
            self.box_scan.setTitle(
                self.operator.properties[self.scan_name]['title'])
            self.plot1.setTitle(
                self.operator.properties[self.scan_name]['title'])

        self.plot1.setLabel(
            'bottom',
            self.operator.properties[self.scan_name]['x_label'],
            units=self.operator.properties[self.scan_name]['x_units'])
        self.plot1.setLabel(
            'left',
            self.operator.properties[self.scan_name]['y_label'],
            units=self.operator.properties[self.scan_name]['y_units'])
        self.plot1.setXRange(self.operator.properties[self.scan_name]['start'],
                             self.operator.properties[self.scan_name]['stop'])

        if 'filename' in self.operator.properties[self.scan_name]:
            self.saver.filename.setText(
                self.operator.properties[self.scan_name]['filename'])

    def scan_start_value(self):
        """
        Called when Scan Start spinbox is modified.
        Updates the parameter using a method of operator (which checks validity and also fixes the sign of step) and
        forces the (corrected) parameter in the gui elements
        """
        self.operator._set_scan_start(self.scan_start_spinbox.value())
        self.scan_start_spinbox.setValue(
            self.operator.properties[self.scan_name]['start'])
        self.scan_step_spinbox.setValue(
            self.operator.properties[self.scan_name]['step'])
        set_spinbox_stepsize(self.scan_start_spinbox)
        self.plot1.setXRange(self.operator.properties[self.scan_name]['start'],
                             self.operator.properties[self.scan_name]['stop'])

    def scan_stop_value(self):
        """
        Called when Scan Stop spinbox is modified.
        Updates the parameter using a method of operator (which checks validity and also fixes the sign of step) and
        forces the (corrected) parameter in the gui elements
        """
        self.operator._set_scan_stop(self.scan_stop_spinbox.value())
        self.scan_stop_spinbox.setValue(
            self.operator.properties[self.scan_name]['stop'])
        self.scan_step_spinbox.setValue(
            self.operator.properties[self.scan_name]['step'])
        set_spinbox_stepsize(self.scan_stop_spinbox)
        self.plot1.setXRange(self.operator.properties[self.scan_name]['start'],
                             self.operator.properties[self.scan_name]['stop'])

    def scan_step_value(self):
        """
        Called when Scan Step spinbox is modified.
        Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element
        """
        self.operator._set_scan_step(self.scan_step_spinbox.value())
        self.scan_step_spinbox.setValue(
            self.operator.properties[self.scan_name]['step'])
        set_spinbox_stepsize(self.scan_step_spinbox)

    def reset_fields(self):
        """
        Resets gui elements after a scan is finished, stopped or terminated.
        """
        self.start_button.setEnabled(True)
        self.pause_button.setText('Pause')
        self.pause_button.setEnabled(False)
        self.stop_button.setEnabled(False)
        self.scan_start_spinbox.setEnabled(True)
        self.scan_stop_spinbox.setEnabled(True)
        self.scan_step_spinbox.setEnabled(True)
        # Reset all flow control flags
        self.operator._busy = False
        self.operator._pause = False
        self.operator._stop = False

    def start_scan(self):
        """
        Called when start button is pressed.
        Starts the monitor (thread and timer) and disables some gui elements
        """
        if self.operator._busy:
            self.logger.debug("Operator is busy")
            return
        else:
            self.logger.debug('Starting scan')
            self.start_button.setEnabled(False)
            self.pause_button.setEnabled(True)
            self.stop_button.setEnabled(True)
            # self.operator._stop = False  # enable operator monitor loop to run
            self.scan_thread.start()  # start the operator monitor
            self.scan_timer.start(self.operator.properties[
                self.scan_name]['gui_refresh_time'])  # start the update timer
            self.scan_start_spinbox.setEnabled(False)
            self.scan_stop_spinbox.setEnabled(False)
            self.scan_step_spinbox.setEnabled(False)

    def pause_scan(self):
        """
        Called when pause button is clicked.
        Signals the operator scan to pause. Updates buttons accordingly
        """
        if not self.operator._pause:
            self.operator._pause = True
            self.pause_button.setText('Continue')
        else:
            self.operator._pause = False
            self.pause_button.setText('Pause')

    def stop_scan(self):
        """
        Stop all loop threads:
        - flags the operator to stop
        - uses the Workthread stop method to wait a bit for the operator to finish, or terminate thread if timeout occurs
        """
        self.logger.debug('Stopping operator')
        self.stop_button.setEnabled(False)
        self.operator._stop = True
        if self.scan_thread.isRunning():
            self.scan_thread.stop(
                self.operator.properties[self.scan_name]['stop_timeout'])
        self.operator._busy = False  # Reset in case the monitor was not stopped gracefully, but forcefully stopped
        self.reset_fields()

    def kill_scan(self):
        """
        Forcefully terminates the scan thread
        """
        self.logger.debug('Killing operator threads')
        self.operator._stop = True
        self.scan_thread.terminate()
        self.reset_fields()

    def update_scan(self):
        """
        Checks if new data is available and updates the graph.
        Checks if thread is still running and if not: stops timer and reset gui elements
        (called by timer)
        """
        if self.operator._new_scan_data:
            self.operator._new_scan_data = False
            self.curve1.setData(self.operator.scan_voltages,
                                self.operator.measured_voltages)
        if self.scan_thread.isFinished():
            self.logger.debug('Scan thread is finished')
            self.scan_timer.stop()
            self.reset_fields()

    def closeEvent(self, event):
        """ Gets called when the window is closed. Could be used to do some cleanup before closing. """

        # # Use this bit to display an "Are you sure"-dialogbox
        # quit_msg = "Are you sure you want to exit labphew monitor?"
        # reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No)
        # if reply == QMessageBox.No:
        #     event.ignore()
        #     return
        self.stop_scan()  # stop scan
        self.scan_timer.stop()  # stop scan timer, just to be sure
        event.accept()
Exemplo n.º 2
0
class ScanWindow(ScanWindowBase):
    def __init__(self, operator, parent=None):
        self.logger = logging.getLogger(__name__)
        super().__init__(parent)
        self.setWindowTitle('Blink Scan')
        self.operator = operator

        # # For loading a .ui file (created with QtDesigner):
        # p = os.path.dirname(__file__)
        # uic.loadUi(os.path.join(p, 'design/UI/main_window.ui'), self)

        self.set_UI()

        # create thread and timer objects for scan
        self.scan_timer = QTimer(timeout=self.update_scan)
        self.scan_thread = WorkThread(self.operator.do_scan)

    def set_UI(self):
        """
        Code-based generation of the user-interface based on PyQT
        """

        self.setWindowTitle('Blink Scan example')
        # display statusbar
        self.statusBar()
        ### The menu bar:
        mod_config_action = QAction("Con&fig", self, triggered=self.mod_scan_config, shortcut="Ctrl+Shift+C", statusTip='Modify the scan config')
        save_action = QAction("&Save", self, triggered=self.save, shortcut="Ctrl+S", statusTip='Save the scan data')
        quit_action = QAction("&Close", self, triggered=self.close, shortcut="Ctrl+W", statusTip='Close the scan window')

        self.start_action = QAction("&Start", self, triggered=self.start_scan, shortcut="F5", statusTip='Start the Scan')
        self.pause_action = QAction("&Pause", self, triggered=self.pause_scan, shortcut="Ctrl+P", statusTip='Pause the scan', enabled=False)
        self.stop_action = QAction("S&top", self, triggered=self.stop_scan, shortcut="Ctrl+A", statusTip='Stop the scan after current iteration', enabled=False)
        self.kill_action = QAction("&Kill", self, triggered=self.kill_scan, shortcut="Ctrl+K", statusTip='Immediately kill the scan')

        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu('&File')
        fileMenu.addAction(mod_config_action)
        fileMenu.addAction(save_action)
        fileMenu.addAction(quit_action)
        runMenu = mainMenu.addMenu('&Run Scan')
        runMenu.addAction(self.start_action)
        runMenu.addAction(self.pause_action)
        runMenu.addAction(self.stop_action)
        runMenu.addAction(self.kill_action)

        self.graph_win = pg.GraphicsWindow()
        self.graph_win.resize(800, 500)
        self.plot1 = self.graph_win.addPlot()
        self.curve1 = self.plot1.plot(pen='w')
        self.plot1.setLabel('bottom', 'data points')
        self.plot1.setLabel('left', 'state')

        self.setCentralWidget(self.graph_win)

    def mod_scan_config(self):
        """
        Open the Modify Config window for the scan properties
        """
        conf_win = ModifyConfig(self.operator.properties['scan'], apply_callback=None, parent=self)
        conf_win.show()

    def save(self):
        print(self.operator.properties['scan']['filename'])
        try:
            fname = QFileDialog.getSaveFileName(self, 'Save data as', self.operator.properties['scan']['filename'],
                                                filter="netCDF4 (*.nc);;All Files (*.*)")
        except:
            fname = QFileDialog.getSaveFileName(self, 'Save data as', os.path.join(labphew.parent_path, 'data.nc'),
                                                filter="netCDF4 (*.nc);;All Files (*.*)")
        if fname[0]:
            self.operator.save_scan(fname[0])

    def reset_fields(self):
        """
        Resets gui elements after a scan is finished, stopped or terminated.
        """
        self.start_action.setEnabled(True)
        self.pause_action.setEnabled(False)
        self.stop_action.setEnabled(False)
        self.pause_action.setText('Pause')
        self.operator._busy = False
        self.operator._pause = False
        self.operator._stop = False

    def start_scan(self):
        """
        Called when start button is pressed.
        Starts the monitor (thread and timer) and disables some gui elements
        """
        if self.operator._busy:
            self.logger.debug("Operator is busy")
            return
        else:
            self.logger.debug('Starting scan')
            self.start_action.setEnabled(False)
            self.pause_action.setEnabled(True)
            self.stop_action.setEnabled(True)

            # self.operator._stop = False  # enable operator monitor loop to run
            self.scan_thread.start()  # start the operator monitor
            # Start the update timer with time specified in config if available
            try:
                self.scan_timer.start(self.operator.properties['scan']['gui_refresh_time'])
            except:
                self.scan_timer.start(0.05)  # otherwise use default value

    def pause_scan(self):
        """
        Called when pause button is clicked.
        Signals the operator scan to pause. Updates buttons accordingly
        """
        if not self.operator._pause:
            self.operator._pause = True
            self.pause_action.setText('&Continue')
        else:
            self.operator._pause = False
            self.pause_action.setText('Pause')

    def stop_scan(self):
        """
        Stop all loop threads:
        - flags the operator to stop
        - uses the Workthread stop method to wait a bit for the operator to finish, or terminate thread if timeout occurs
        """
        self.logger.debug('Stopping operator')
        # self.stop_button.setEnabled(False)
        self.operator._stop = True
        if self.scan_thread.isRunning():
            self.scan_thread.stop()  # Stop thread with default timeout before killing it
        self.operator._busy = False  # Reset in case the monitor was not stopped gracefully, but forcefully stopped
        self.reset_fields()

    def kill_scan(self):
        """
        Forcefully terminates the scan thread
        """
        self.logger.debug('Killing operator threads')
        self.operator._stop = True
        self.scan_thread.terminate()
        self.reset_fields()

    def update_scan(self):
        """
        Checks if new data is available and updates the graph.
        Checks if thread is still running and if not: stops timer and reset gui elements
        (called by timer)
        """
        if self.operator._new_scan_data:
            self.operator._new_scan_data = False
            self.curve1.setData(self.operator.point_number, self.operator.measured_state)
        if self.scan_thread.isFinished():
            self.logger.debug('Scan thread is finished')
            self.scan_timer.stop()
            self.reset_fields()

    def closeEvent(self, event):
        """ Gets called when the window is closed. Could be used to do some cleanup before closing. """

        # # Use this bit to display an "Are you sure"-dialogbox
        # quit_msg = "Are you sure you want to exit labphew monitor?"
        # reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No)
        # if reply == QMessageBox.No:
        #     event.ignore()
        #     return
        self.stop_scan()  # stop scan
        self.scan_timer.stop()  # stop scan timer, just to be sure
        event.accept()