Example #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()
Example #2
0
class MonitorWindow(MonitorWindowBase):
    def __init__(self, operator, parent=None):
        """
        Creates the monitor window.

        :param operator: The operator
        :type operator: labphew operator instance
        :param parent: Optional parent GUI
        :type parent: QWidget
        """
        # self.logger = logging.getLogger(__name__)
        super().__init__(parent)
        self.setWindowTitle('Analog Discovery 2')
        self.operator = operator
        self.scan_windows = {
        }  # If any scan windows are loaded, they will be placed in this dict

        # # 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 monitor
        self.monitor_timer = QTimer()
        self.monitor_timer.timeout.connect(self.update_monitor)
        self.monitor_thread = WorkThread(self.operator._monitor_loop)

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

        self.setWindowTitle('Digilent AD2')
        # display statusbar
        self.statusBar()
        ### The menu bar:
        quit_action = QAction("E&xit",
                              self,
                              triggered=self.close,
                              shortcut="Alt+F4",
                              statusTip='Close the scan window')
        self.mainMenu = self.menuBar()
        fileMenu = self.mainMenu.addMenu('&File')
        fileMenu.addAction(quit_action)

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

        # Layout for left hand controls
        control_layout = QVBoxLayout()

        ### Analog Out
        box_ao = QGroupBox('Analog Out')
        layout_ao = QFormLayout()
        box_ao.setLayout(layout_ao)
        control_layout.addWidget(box_ao)

        self.ao1_spinbox = QDoubleSpinBox()
        self.ao1_spinbox.setSuffix('V')
        self.ao1_spinbox.setMinimum(-100)  # limits are checked by the Operator
        self.ao1_spinbox.valueChanged.connect(self.ao1_value)
        self.ao1_spinbox.setDecimals(3)
        self.ao1_spinbox.setSingleStep(0.001)

        self.ao2_spinbox = QDoubleSpinBox()
        self.ao2_spinbox.setSuffix('V')
        self.ao2_spinbox.setMinimum(-100)  # limits are checked by the Operator
        self.ao2_spinbox.valueChanged.connect(self.ao2_value)
        self.ao2_spinbox.setDecimals(3)
        self.ao2_spinbox.setSingleStep(0.001)

        self.ao1_label = QLabel()
        self.ao2_label = QLabel()
        layout_ao.addRow(self.ao1_label, self.ao1_spinbox)
        layout_ao.addRow(self.ao2_label, self.ao2_spinbox)

        ### Monitor
        box_monitor = QGroupBox('Monitor')
        layout_monitor = QVBoxLayout()
        box_monitor.setLayout(layout_monitor)
        control_layout.addWidget(box_monitor)

        layout_monitor_form = QFormLayout()
        layout_monitor.addLayout(layout_monitor_form)
        layout_monitor_buttons = QHBoxLayout()
        layout_monitor.addLayout(layout_monitor_buttons)

        self.time_step_spinbox = QDoubleSpinBox()
        self.time_step_spinbox.setSuffix('s')
        self.time_step_spinbox.setMinimum(.01)
        self.time_step_spinbox.valueChanged.connect(self.time_step)
        self.time_step_spinbox.setSingleStep(0.01)
        layout_monitor_form.addRow(QLabel('Time step'), self.time_step_spinbox)

        self.plot_points_spinbox = QSpinBox()
        self.plot_points_spinbox.setMinimum(2)
        self.plot_points_spinbox.setMaximum(1000)
        self.plot_points_spinbox.valueChanged.connect(self.plot_points)
        self.plot_points_spinbox.setSingleStep(10)
        layout_monitor_form.addRow(QLabel('Plot points'),
                                   self.plot_points_spinbox)

        self.start_button = QPushButton('Start')
        self.start_button.clicked.connect(self.start_monitor)
        self.stop_button = QPushButton('Stop')
        self.stop_button.clicked.connect(self.stop_monitor)

        layout_monitor_buttons.addWidget(self.start_button)
        layout_monitor_buttons.addWidget(self.stop_button)

        ### Graphs:
        self.graph_win = pg.GraphicsWindow()
        self.graph_win.resize(1000, 600)

        self.plot1 = self.graph_win.addPlot()
        self.plot1.setLabel('bottom', 'time', units='s')
        self.plot1.setLabel('left', 'voltage', units='V')
        self.curve1 = self.plot1.plot(pen='y')
        text_update_time = self.operator.properties['monitor'][
            'text_update_time']
        self.label_1 = ValueLabelItem('--',
                                      color='y',
                                      siPrefix=True,
                                      suffix='V',
                                      siPrecision=4,
                                      averageTime=text_update_time,
                                      textUpdateTime=text_update_time)
        self.graph_win.addItem(self.label_1)

        self.graph_win.nextRow()
        self.plot2 = self.graph_win.addPlot()
        self.plot2.setLabel('bottom', 'time', units='s')
        self.plot2.setLabel('left', 'voltage', units='V')
        self.curve2 = self.plot2.plot(pen='c')
        self.label_2 = ValueLabelItem('--',
                                      color='c',
                                      siPrefix=True,
                                      suffix='V',
                                      siPrecision=4,
                                      averageTime=text_update_time,
                                      textUpdateTime=text_update_time)
        self.graph_win.addItem(self.label_2)
        self._last_values_update_time = time()

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

        # To make it look a bit nicer at the beginning (this is not strictly necessary):
        left = -self.operator.properties['monitor'][
            'plot_points'] * self.operator.properties['monitor']['time_step']
        self.plot1.setXRange(left, 0)
        self.plot2.setXRange(left, 0)
        self.plot1.enableAutoRange()
        self.plot2.enableAutoRange()

    def apply_properties(self):
        """
        Apply properties dictionary to gui elements.
        """
        self.ao1_label.setText(self.operator.properties['ao'][1]['name'])
        self.ao2_label.setText(self.operator.properties['ao'][2]['name'])

        self.time_step_spinbox.setValue(
            self.operator.properties['monitor']['time_step'])
        self.plot_points_spinbox.setValue(
            self.operator.properties['monitor']['plot_points'])

        self.plot1.setTitle(self.operator.properties['monitor'][1]['name'])
        self.plot2.setTitle(self.operator.properties['monitor'][2]['name'])

    def load_scan_guis(self, scan_windows):
        """
        Load scan windows and add them to a menu in this monitor window.
        Note that the scan windows should be instantiated before adding them.
        The keys of the dictionary should be strings that will act as the names in the Scan menu.
        The values of the dictionary could be the ScanWindowObjects or a list that also contains some PyQt gui settings
        in a dictionary: [ScanWindowObject, {'shortcut':"Ctrl+Shift+V", 'statusTip':'Voltage sweep scan'}]

        :param scan_windows: scan windows dict
        :type scan_windows: dict
        """
        scanMenu = self.mainMenu.addMenu('&Scans')
        for name, scan_lst in scan_windows.items():
            if type(scan_lst) is not list:
                scan_lst = [scan_lst]
            if len(scan_lst) < 2:
                scan_lst.append({})
            self.scan_windows[name] = scan_lst
            scanMenu.addAction(
                QAction(name,
                        self,
                        triggered=self.open_scan_window,
                        **scan_lst[1]))

    def open_scan_window(self):
        """
        This method is called by the menu and opens Scan Windows that were "attached" to this Monitor gui with load_scan_guis().
        """
        self.stop_monitor()
        name = self.sender().text(
        )  # get the name of the QAction (which is also the key of the scanwindow dictionary)
        self.logger.debug('Opening scan window {}'.format(name))
        self.scan_windows[name][0].show()
        fit_on_screen(self.scan_windows[name][0])

    def ao1_value(self):
        """
        Called when AO Channel 2 spinbox is modified.
        Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element
        """
        value = self.operator.analog_out(1, self.ao1_spinbox.value())
        self.ao1_spinbox.setValue(value)
        set_spinbox_stepsize(self.ao1_spinbox)

    def ao2_value(self):
        """
        Called when AO Channel 2 spinbox is modified.
        Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element
        """
        value = self.operator.analog_out(2, self.ao2_spinbox.value())
        self.ao2_spinbox.setValue(value)
        set_spinbox_stepsize(self.ao2_spinbox)

    def time_step(self):
        """
        Called when time 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_monitor_time_step(self.time_step_spinbox.value())
        self.time_step_spinbox.setValue(
            self.operator.properties['monitor']['time_step'])
        set_spinbox_stepsize(self.time_step_spinbox)

    def plot_points(self):
        """
        Called when plot points 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_monitor_plot_points(
            self.plot_points_spinbox.value())
        self.plot_points_spinbox.setValue(
            self.operator.properties['monitor']['plot_points'])
        set_spinbox_stepsize(self.plot_points_spinbox)

    def start_monitor(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 monitor')
            self.operator._allow_monitor = True  # enable operator monitor loop to run
            self.monitor_thread.start()  # start the operator monitor
            self.monitor_timer.start(
                self.operator.properties['monitor']
                ['gui_refresh_time'])  # start the update timer
            self.plot_points_spinbox.setEnabled(False)
            self.start_button.setEnabled(False)

    def stop_monitor(self):
        """
        Called when stop button is pressed.
        Stops the monitor:
        - 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
        """
        if not self.monitor_thread.isRunning():
            self.logger.debug('Monitor is not running')
            return
        else:
            # set flag to to tell the operator to stop:
            self.logger.debug('Stopping monitor')
            self.operator._stop = True
            self.monitor_thread.stop(
                self.operator.properties['monitor']['stop_timeout'])
            self.operator._allow_monitor = False  # disable monitor again
            self.operator._busy = False  # Reset in case the monitor was not stopped gracefully, but forcefully stopped

    def update_monitor(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_monitor_data:
            self.operator._new_monitor_data = False
            self.curve1.setData(self.operator.analog_monitor_time,
                                self.operator.analog_monitor_1)
            self.curve2.setData(self.operator.analog_monitor_time,
                                self.operator.analog_monitor_2)
            self.label_1.setValue(self.operator.analog_monitor_1[-1])
            self.label_2.setValue(self.operator.analog_monitor_2[-1])

        if self.monitor_thread.isFinished():
            self.logger.debug('Monitor thread is finished')
            self.monitor_timer.stop()
            self.plot_points_spinbox.setEnabled(True)
            self.start_button.setEnabled(True)

    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_monitor()  # stop monitor if it was running
        self.monitor_timer.stop()  # stop monitor timer, just to be nice
        # Close all child scan windows
        for scan_win in self.scan_windows.values():
            scan_win[0].close()
        self.operator.disconnect_devices()
        event.accept()
Example #3
0
class MonitorWindow(MonitorWindowBase):
    def __init__(self, operator, parent=None):
        """
        Creates the monitor window.

        :param operator: The operator
        :type operator: labphew operator instance
        :param parent: Optional parent GUI
        :type parent: QWidget
        """
        self.logger = logging.getLogger(__name__)
        super().__init__(parent)
        self.operator = operator
        self.scan_windows = {}  # If any scan windows are loaded, they will be placed in this dict

        # # 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 monitor
        self.monitor_timer = QTimer()
        self.monitor_timer.timeout.connect(self.update_monitor)
        self.monitor_thread = WorkThread(self.operator._monitor_loop)

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

        self.setWindowTitle('labphew blinks at you')

        self.statusBar()  # Statusbar at the bottom of the screen
        ### The menu bar:
        self.mainMenu = self.menuBar()
        fileMenu = self.mainMenu.addMenu('&File')
        quit_action = QAction("E&xit", self, triggered=self.close, shortcut="Alt+F4", statusTip='Close the scan window')
        fileMenu.addAction(quit_action)

        self.central_widget = QWidget()
        self.button_start = QPushButton('Start Blink Monitor', self.central_widget)
        self.button_stop = QPushButton('Stop', self.central_widget)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, 10)

        self.message = QLabel('Press a button!', self.central_widget)
        self.message.setFont(QFont("Arial", 12, QFont.Normal))

        self.layout = QVBoxLayout(self.central_widget)
        self.layout.addWidget(self.button_start)
        self.layout.addWidget(self.button_stop)
        self.layout.addWidget(self.message, alignment=Qt.AlignCenter)
        self.layout.addWidget(self.slider)
        self.setCentralWidget(self.central_widget)

        self.button_start.clicked.connect(self.start_monitor)
        self.button_stop.clicked.connect(self.stop_monitor)
        self.slider.valueChanged.connect(self.blink_rate)

        self.slider.setValue(5)  # Setting the slider value will also invoke self.blink_rate(value), shich will set blink rate in the device
        # Note that another (better) approach would be to first retrieve the current setting from the device and set the
        # slider to that position.

        self.resize(400, 200)

    def start_monitor(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 monitor')
            self.operator._allow_monitor = True  # enable operator monitor loop to run
            self.monitor_thread.start()  # start the operator monitor
            self.monitor_timer.start(self.operator.properties['monitor']['gui_refresh_time'])  # start the update timer
            self.button_start.setEnabled(False)


    def stop_monitor(self):
        """
        Called when stop button is pressed.
        Stops the monitor:
        - 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
        """
        if not self.monitor_thread.isRunning():
            self.logger.debug('Monitor is not running')
            return
        else:
            # set flag to to tell the operator to stop:
            self.logger.debug('Stopping monitor')
            self.operator._stop = True
            self.operator._allow_monitor = False  # disable monitor again
            self.operator._busy = False  # Reset in case the monitor was not stopped gracefully, but forcefully stopped

    def blink_rate(self, value):
        low = self.operator.properties['blink instrument']['min_blink_period']
        high = self.operator.properties['blink instrument']['max_blink_period']
        self.operator.instrument.set_blink_period( value / 9 * (high-low) )

    def update_monitor(self):
        """
        Checks if new data is available and updates the gui.
        Checks if thread is still running and if not: stops timer (and reset gui elements)
        (called by timer)
        """
        if self.operator._new_monitor_data:
            self.operator._new_monitor_data = False
            blink_time, blink_state = self.operator._monitor_data
            self.message.setText(blink_time)
            if blink_state:
                self.message.setFont(QFont("Arial", 12, QFont.Bold))
                self.message.setStyleSheet("color: red;")
            else:
                self.message.setFont(QFont("Arial", 12, QFont.Thin))
                self.message.setStyleSheet("color: white;")

        if self.monitor_thread.isFinished():
            self.logger.debug('Monitor thread is finished')
            self.monitor_timer.stop()
            self.button_start.setEnabled(True)

    def load_scan_guis(self, scan_windows):
        """
        Load scan windows and add them to a menu in this monitor window.
        Note that the scan windows should be instantiated before adding them.
        The keys of the dictionary should be strings that will act as the names in the Scan menu.
        The values of the dictionary could be the ScanWindowObjects or a list that also contains some PyQt gui settings
        in a dictionary: [ScanWindowObject, {'shortcut':"Ctrl+Shift+V", 'statusTip':'Voltage sweep scan'}]

        :param scan_windows: scan windows dict
        :type scan_windows: dict
        """
        scanMenu = self.mainMenu.addMenu('&Scans')
        for name, scan_lst in scan_windows.items():
            if type(scan_lst) is not list:
                scan_lst = [scan_lst]
            if len(scan_lst) < 2:
                scan_lst.append({})
            self.scan_windows[name] = scan_lst
            scanMenu.addAction(QAction(name, self, triggered=self.open_scan_window, **scan_lst[1]))

    def open_scan_window(self):
        """
        This metohd is called by the menu and opens Scan Windows that were "attached" to this Monitor gui with load_scan_guis().
        """
        self.stop_monitor()
        name = self.sender().text()  # get the name of the QAction (which is also the key of the scanwindow dictionary)
        self.logger.debug('Opening scan window {}'.format(name))
        self.scan_windows[name][0].show()
        fit_on_screen(self.scan_windows[name][0])

    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_monitor()  # stop monitor if it was running
        self.monitor_timer.stop()  # stop monitor timer, just to be sure
        # Close all child scan windows
        for scan_win in self.scan_windows.values():
            scan_win[0].close()
        # It would be good to also disconnect any devices here
        self.operator.instrument.disconnect()
        event.accept()
Example #4
0
class Sensor1(QWidget):
    def __init__(self, operator, parent=None):
        super().__init__(parent)

        self.operator = operator
        #setattr(self.operator, 'save_scan_to_csv', save_scan_to_csv)
        self.scan_windows = {}
        self.logger = logging.getLogger(__name__)

        self.monitor_timer = QTimer()
        self.monitor_timer.timeout.connect(self.update_monitor)
        self.monitor_thread = WorkThread(self.operator._monitor_loop)

        central_layout = QHBoxLayout()

        # Layout for left hand controls
        control_layout = QVBoxLayout()

        ### Monitor
        box_monitor = QGroupBox('Monitor')
        layout_monitor = QVBoxLayout()
        box_monitor.setLayout(layout_monitor)
        control_layout.addWidget(box_monitor)

        layout_monitor_form = QFormLayout()
        layout_monitor.addLayout(layout_monitor_form)
        layout_monitor_buttons = QHBoxLayout()
        layout_monitor.addLayout(layout_monitor_buttons)

        self.time_step_spinbox = QDoubleSpinBox()
        self.time_step_spinbox.setSuffix(' s')
        self.time_step_spinbox.setMinimum(.02)
        self.time_step_spinbox.valueChanged.connect(self.time_step)
        self.time_step_spinbox.setSingleStep(0.05)
        layout_monitor_form.addRow(QLabel('Time step'), self.time_step_spinbox)

        self.plot_points_spinbox = QSpinBox()
        self.plot_points_spinbox.setMinimum(2)
        self.plot_points_spinbox.setMaximum(1000)
        self.plot_points_spinbox.valueChanged.connect(self.plot_points)
        self.plot_points_spinbox.setSingleStep(10)
        layout_monitor_form.addRow(QLabel('Plot points'),
                                   self.plot_points_spinbox)

        self.start_button = QPushButton('Start')
        self.start_button.clicked.connect(self.start_monitor)
        self.stop_button = QPushButton('Stop')
        self.stop_button.clicked.connect(self.stop_monitor)

        # button for opening the scan function
        self.scan_button = QPushButton('Scan')
        self.scan_button.clicked.connect(self.open_scan)

        layout_monitor_buttons.addWidget(self.start_button)
        layout_monitor_buttons.addWidget(self.stop_button)
        layout_monitor_buttons.addWidget(self.scan_button)

        # Graphs:
        self.graph_win = pg.GraphicsWindow()
        self.graph_win.resize(1000, 600)

        self.plot1 = self.graph_win.addPlot()
        self.plot1.setLabel('bottom', 'time', units='s')
        self.plot1.setLabel('left', 'voltage', units='V')
        self.curve1 = self.plot1.plot(pen='y')
        text_update_time = self.operator.properties['monitor'][
            'text_update_time']
        self.label_1 = ValueLabelItem('--',
                                      color='y',
                                      siPrefix=True,
                                      suffix='V',
                                      siPrecision=4,
                                      averageTime=text_update_time,
                                      textUpdateTime=text_update_time)
        self.graph_win.addItem(self.label_1)

        self.graph_win.nextRow()

        self.plot2 = self.graph_win.addPlot()
        self.plot2.setLabel('bottom', 'time', units='s')
        self.plot2.setLabel('left', 'voltage', units='V')
        self.curve2 = self.plot2.plot(pen='c')
        self.label_2 = ValueLabelItem('--',
                                      color='c',
                                      siPrefix=True,
                                      suffix='V',
                                      siPrecision=4,
                                      averageTime=text_update_time,
                                      textUpdateTime=text_update_time)
        self.graph_win.addItem(self.label_2)

        self._last_values_update_time = time()

        # 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.apply_properties()

        # To make it look a bit nicer at the beginning (this is not strictly necessary):
        left = -self.operator.properties['monitor'][
            'plot_points'] * self.operator.properties['monitor']['time_step']
        self.plot1.setXRange(left, 0)
        self.plot2.setXRange(left, 0)
        self.plot1.enableAutoRange()
        self.plot2.enableAutoRange()

        self.setLayout(central_layout)

    def open_scan(self):

        self.ui = ScanWindow(self.operator)
        self.ui.show()

    def apply_properties(self):
        """
        Apply properties dictionary to gui elements.
        """

        self.time_step_spinbox.setValue(
            self.operator.properties['monitor']['time_step'])
        self.plot_points_spinbox.setValue(
            self.operator.properties['monitor']['plot_points'])

        self.plot1.setTitle(self.operator.properties['monitor'][1]['name'])
        self.plot2.setTitle(self.operator.properties['monitor'][2]['name'])

    def ao1_value(self):
        """
        Called when AO Channel 2 spinbox is modified.
        Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element
        """
        value = self.operator.analog_out(1, self.ao1_spinbox.value())
        self.ao1_spinbox.setValue(value)
        set_spinbox_stepsize(self.ao1_spinbox)

    def ao2_value(self):
        """
        Called when AO Channel 2 spinbox is modified.
        Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element
        """
        value = self.operator.analog_out(2, self.ao2_spinbox.value())
        self.ao2_spinbox.setValue(value)
        set_spinbox_stepsize(self.ao2_spinbox)

    def time_step(self):
        """
        Called when time 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_monitor_time_step(self.time_step_spinbox.value())
        self.time_step_spinbox.setValue(
            self.operator.properties['monitor']['time_step'])
        set_spinbox_stepsize(self.time_step_spinbox)

    def plot_points(self):
        """
        Called when plot points 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_monitor_plot_points(
            self.plot_points_spinbox.value())
        self.plot_points_spinbox.setValue(
            self.operator.properties['monitor']['plot_points'])
        set_spinbox_stepsize(self.plot_points_spinbox)

    def start_monitor(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 monitor')
            self.operator._allow_monitor = True  # enable operator monitor loop to run
            self.monitor_thread.start()  # start the operator monitor
            self.monitor_timer.start(
                self.operator.properties['monitor']
                ['gui_refresh_time'])  # start the update timer
            self.plot_points_spinbox.setEnabled(False)
            self.start_button.setEnabled(False)

    def stop_monitor(self):
        """
        Called when stop button is pressed.
        Stops the monitor:
        - 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
        """
        if not self.monitor_thread.isRunning():
            self.logger.debug('Monitor is not running')
            return
        else:
            # set flag to to tell the operator to stop:
            self.logger.debug('Stopping monitor')
            self.operator._stop = True
            self.monitor_thread.stop(
                self.operator.properties['monitor']['stop_timeout'])
            self.operator._allow_monitor = False  # disable monitor again
            self.operator._busy = False  # Reset in case the monitor was not stopped gracefully, but forcefully stopped

    def update_monitor(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_monitor_data:
            self.operator._new_monitor_data = False
            self.curve1.setData(self.operator.analog_monitor_time,
                                self.operator.analog_monitor_1)
            self.curve2.setData(self.operator.analog_monitor_time,
                                self.operator.analog_monitor_2)
            self.label_1.setValue(self.operator.analog_monitor_1[-1])
            self.label_2.setValue(self.operator.analog_monitor_2[-1])

        if self.monitor_thread.isFinished():
            self.logger.debug('Monitor thread is finished')
            self.monitor_timer.stop()
            self.plot_points_spinbox.setEnabled(True)
            self.start_button.setEnabled(True)

    def load_scan_guis(self, scan_windows):
        """
        Load scan windows and add them to a menu in this monitor window.
        Note that the scan windows should be instantiated before adding them.
        The keys of the dictionary should be strings that will act as the names in the Scan menu.
        The values of the dictionary could be the ScanWindowObjects or a list that also contains some PyQt gui settings
        in a dictionary: [ScanWindowObject, {'shortcut':"Ctrl+Shift+V", 'statusTip':'Voltage sweep scan'}]

        :param scan_windows: scan windows dict
        :type scan_windows: dict
        """
        scanMenu = self.mainMenu.addMenu('&Scans')
        for name, scan_lst in scan_windows.items():
            if type(scan_lst) is not list:
                scan_lst = [scan_lst]
            if len(scan_lst) < 2:
                scan_lst.append({})
            self.scan_windows[name] = scan_lst
            scanMenu.addAction(
                QAction(name,
                        self,
                        triggered=self.open_scan_window,
                        **scan_lst[1]))

    def open_scan_window(self):
        """
        This method is called by the menu and opens Scan Windows that were "attached" to this Monitor gui with load_scan_guis().
        """
        self.stop_monitor()
        name = self.sender().text(
        )  # get the name of the QAction (which is also the key of the scanwindow dictionary)
        self.logger.debug('Opening scan window {}'.format(name))
        self.scan_windows[name][0].show()
        fit_on_screen(self.scan_windows[name][0])
Example #5
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()