Beispiel #1
0
    def _create_row(self, index=None):
        """Create a new row.

        Parameters
        ----------
        index : :class:`int` or :obj:`None`
            If :obj:`None` then append a row, else the index number (0 based) for
            where to create and insert the row.
        """
        cb = QtWidgets.QComboBox()
        cb.addItems(self._actions)
        sb = QtWidgets.QSpinBox()
        sb.setRange(0, 999999)
        msg = QtWidgets.QLineEdit()
        reply = QtWidgets.QLineEdit()
        reply.setReadOnly(True)
        index = index if index is not None else self._table.rowCount()
        self._table.insertRow(index)
        self._table.setCellWidget(index, 0, cb)
        self._table.setCellWidget(index, 1, sb)
        self._table.setCellWidget(index, 2, msg)
        self._table.setCellWidget(index, 3, reply)
        cb.currentTextChanged.connect(
            lambda text: self._update_row_appearance(text, index))
        self._update_row_appearance(cb.currentText(), index)
    def __init__(self, connection, config=None, parent=None):
        """A :class:`~QtWidgets.QWidget` for controlling a Thorlabs translation stage.

        Parameters
        ----------
        connection : :class:`~msl.equipment.connection.Connection`
            The connection to the translational stage motor controller
            (e.g., LTS150, LTS300, KST101, KDC101, ...).
        config : :class:`~msl.equipment.config.Config`, optional
            A configuration file.

            The following elements can be defined in a :class:`~msl.equipment.config.Config` file to
            initialize a :class:`TranslationStage`:

            .. code-block:: xml

                <!--
                  The following attributes can be defined for a "preset" and a "jog size" element.
                  For a "preset" you must define a name attribute:
                    units - can be either "mm" or "device". If omitted then the default unit value is "mm"
                    name - the text that will displayed in the GUI as the name of the preset
                  If multiple translation stages are being used then you can uniquely identify which stage will
                  have its properties updated by including one of the additional attributes:
                    serial - the serial number of the translation stage motor controller
                    alias - the same alias that is used in the <equipment> XML tag
                  If you do not include one of 'serial' or 'alias' then all stages will be updated to the XML element value.
                -->

                <thorlabs_translation_stage_preset name='Si-PD' serial="123456789">54.232</thorlabs_translation_stage_preset>
                <thorlabs_translation_stage_preset name='InGaAs-PD' units="mm" serial="123456789">75.2</thorlabs_translation_stage_preset>
                <thorlabs_translation_stage_preset name='Reference' units="device" serial="123456789">10503037</thorlabs_translation_stage_preset>

                <!-- Note: In the following you can also specify the calibration path to be a path relative to the configuration file -->
                <thorlabs_translation_stage_calibration_path serial="123456789">path/to/calibration/file.dat</thorlabs_translation_stage_calibration_path>

                <!-- Since the 'serial', 'alias' and 'unit' attributes are not defined then all stages will have the jog size set to 2.0 mm -->
                <thorlabs_translation_stage_jog_size>2.0</thorlabs_translation_stage_jog_size>

        parent : :class:`QtWidgets.QWidget`, optional
            The parent widget.
        """
        super(TranslationStage, self).__init__(parent=parent)

        if signaler is None:
            raise ImportError(
                'This widget requires that the MSL-Equipment package is installed'
            )

        if config is not None and not issubclass(config.__class__, Config):
            raise TypeError(
                'Must pass in a MSL Equipment configuration object. Received {}'
                .format(config.__class__))

        self._connection = connection

        self._supports_calibration = hasattr(self._connection,
                                             'set_calibration_file')
        self._uncalibrated_mm = np.array([])
        self._calibrated_mm = np.array([])
        self._calibration_label = ''

        # set the calibration file
        if config is not None and self._supports_calibration:
            elements = self._find_xml_elements(
                config, 'thorlabs_translation_stage_calibration_path')
            if elements:
                cal_path = elements[0].text
                rel_path = os.path.join(os.path.dirname(config.path), cal_path)
                if os.path.isfile(cal_path):
                    self.set_calibration_file(cal_path)
                elif os.path.isfile(rel_path):
                    self.set_calibration_file(rel_path)
                else:
                    prompt.critical('Cannot find calibration file\n' +
                                    cal_path)

        # set the presets
        self._preset_combobox = QtWidgets.QComboBox()
        self._preset_combobox.setToolTip('Preset positions')
        self._preset_combobox.addItems(['', 'Home'])
        self.preset_positions = {}
        if config is not None:
            for element in self._find_xml_elements(
                    config, 'thorlabs_translation_stage_preset'):
                self.add_preset(element.attrib['name'], float(element.text),
                                element.attrib.get('units', 'mm') == 'mm')

        self._min_pos_mm, self._max_pos_mm = self._connection.get_motor_travel_limits(
        )

        self._position_display = QtWidgets.QLineEdit()
        self._position_display.setReadOnly(True)
        self._position_display.setFont(QtGui.QFont('Helvetica', 24))
        self._position_display.mouseDoubleClickEvent = self._ask_move_to
        fm = QtGui.QFontMetrics(self._position_display.font())
        self._position_display.setFixedWidth(
            fm.width(' {}.xxx'.format(int(self._max_pos_mm))))

        self._home_button = QtWidgets.QPushButton()
        self._home_button.setToolTip('Go to the Home position')
        self._home_button.clicked.connect(self.go_home)
        self._home_button.setIcon(get_icon('ieframe|0'))

        self._stop_button = QtWidgets.QPushButton('Stop')
        self._stop_button.setToolTip('Stop moving immediately')
        self._stop_button.clicked.connect(self._connection.stop_immediate)
        self._stop_button.setIcon(get_icon('wmploc|155'))

        if config is not None:
            elements = self._find_xml_elements(
                config, 'thorlabs_translation_stage_jog_size')
            if elements:
                element = elements[0]
                if element.attrib.get('units', 'mm') == 'mm':
                    jog_mm = float(element.text)
                    jog = self._connection.get_device_unit_from_real_value(
                        jog_mm, UnitType.DISTANCE)
                    s = element.text + ' mm'
                else:
                    jog = int(float(element.text))
                    jog_mm = self._connection.get_real_value_from_device_unit(
                        jog, UnitType.DISTANCE)
                    s = element.text + ' device units'
                if jog_mm > self._max_pos_mm or jog_mm < self._min_pos_mm:
                    prompt.critical('Invalid jog size of ' + s)
                else:
                    self._connection.set_jog_step_size(jog)

        self._jog_forward_button = QtWidgets.QPushButton()
        self._jog_forward_button.clicked.connect(
            lambda: self.jog_forward(False))
        self._jog_forward_button.setIcon(get_icon(QtWidgets.QStyle.SP_ArrowUp))

        self._jog_backward_button = QtWidgets.QPushButton()
        self._jog_backward_button.clicked.connect(
            lambda: self.jog_backward(False))
        self._jog_backward_button.setIcon(
            get_icon(QtWidgets.QStyle.SP_ArrowDown))

        settings_button = QtWidgets.QPushButton()
        settings_button.clicked.connect(self._show_settings)
        settings_button.setIcon(get_icon('shell32|71'))
        settings_button.setToolTip('Edit the jog and move settings')

        grid = QtWidgets.QGridLayout()
        grid.addWidget(QtWidgets.QLabel('Presets:'),
                       0,
                       0,
                       alignment=QtCore.Qt.AlignRight)
        grid.addWidget(self._preset_combobox, 0, 1)
        grid.addWidget(self._stop_button, 0, 2, 1, 2)
        grid.addWidget(self._position_display, 1, 0, 2, 2)
        grid.addWidget(self._home_button, 1, 2)
        grid.addWidget(self._jog_forward_button, 1, 3)
        grid.addWidget(settings_button, 2, 2)
        grid.addWidget(self._jog_backward_button, 2, 3)
        grid.setSpacing(0)
        grid.setRowStretch(3, 1)
        grid.setColumnStretch(4, 1)
        self.setLayout(grid)

        self._connection.start_polling(200)
        self._polling_duration = self._connection.polling_duration() * 1e-3
        self._connection.register_message_callback(callback)
        signaler.signal.connect(self._update_display)

        self._requested_mm = None
        self._update_jog_tooltip()
        self._update_display()

        self._requested_mm = float(self._position_display.text())
        self._preset_combobox.setCurrentText(
            self._get_preset_name(self._requested_mm))
        self._preset_combobox.currentIndexChanged[str].connect(
            self._go_to_preset)
Beispiel #3
0
    def __init__(self, connection, parent=None):
        """
        A :class:`~QtWidgets.QWidget` for a :class:`~msl.equipment.connection_message_based.ConnectionMessageBased`
        connection.

        This widget allows for reading/writing messages from/to equipment.

        Parameters
        ----------
        connection : :class:`~msl.equipment.connection_message_based.ConnectionMessageBased`
            The connection to the equipment.
        parent : :class:`QtWidgets.QWidget`
            The parent widget.

        Example
        -------
        To view an example of the :class:`MessageBased` widget that will send messages to a
        *dummy* :class:`~msl.equipment.record_types.EquipmentRecord` in demo mode, run:

        >>> from msl.examples.qt.equipment import message_based # doctest: +SKIP
        >>> message_based.show() # doctest: +SKIP
        """
        super(MessageBased, self).__init__(parent=parent)

        r = connection.equipment_record
        self.setWindowTitle('{} || {} || {}'.format(r.manufacturer, r.model,
                                                    r.serial))
        self.setAcceptDrops(True)

        self._conn = connection
        self._dropped_commands = []
        self._abort_execution = False
        self._command_list = []

        self._header = ['Action', 'Delay', 'Message', 'Reply']
        self._actions = ['write', 'read', 'query', 'delay']
        self._table = QtWidgets.QTableWidget(0, len(self._header), self)
        self._table.setHorizontalHeaderLabels(self._header)
        self._table.horizontalHeader().setStretchLastSection(True)
        self._table.horizontalHeader().setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        self._table.horizontalHeader().customContextMenuRequested.connect(
            self._show_horizontal_popup_menu)
        self._table.verticalHeader().setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        self._table.verticalHeader().customContextMenuRequested.connect(
            self._show_vertical_popup_menu)

        self._timeout_spinbox = QtWidgets.QDoubleSpinBox()
        self._timeout_spinbox.setToolTip(
            '<html>The timeout value to use for <i>read</i> commands</html>')
        self._timeout_spinbox.setRange(0, 999999999)
        if 'ConnectionPyVISA' in '{}'.format(
                connection.__class__.__bases__):  # a PyVISA connection
            self._timeout_spinbox.setSuffix(' ms')
            self._timeout_spinbox.setDecimals(0)
        else:
            self._timeout_spinbox.setSuffix(' s')
            self._timeout_spinbox.setDecimals(2)
        try:
            self._timeout_spinbox.setValue(self._conn.timeout)
        except TypeError:  # in case the connection is established in demo mode
            self._timeout_spinbox.setValue(0)
        self._timeout_spinbox.valueChanged.connect(self._update_timeout)

        self._use_rows = QtWidgets.QLineEdit()
        self._use_rows.setToolTip(
            'Enter the rows to execute or leave blank to execute all rows.\nFor example: 1,3,5-8'
        )

        self._execute_icon = get_icon(QtWidgets.QStyle.SP_ArrowRight)
        self._continuous_icon = get_icon(QtWidgets.QStyle.SP_BrowserReload)
        self._abort_icon = get_icon(QtWidgets.QStyle.SP_BrowserStop)
        self._clear_icon = get_icon(QtWidgets.QStyle.SP_DialogResetButton)
        self._remove_icon = get_icon(QtWidgets.QStyle.SP_DialogCancelButton)
        self._insert_before_icon = get_icon(QtWidgets.QStyle.SP_DialogOkButton)
        # create an insert_after_icon by transforming the insert_before_icon
        size = self._insert_before_icon.availableSizes()[-1]
        pixmap = self._insert_before_icon.pixmap(size).transformed(
            QtGui.QTransform().scale(-1, 1))
        self._insert_after_icon = QtGui.QIcon(pixmap)

        self._execute_thread = _Execute(self)
        self._execute_thread.finished.connect(self._check_if_looping)
        self._execute_thread.sig_error.connect(self._execute_error)
        self._execute_thread.sig_update_row_color.connect(
            self._update_row_appearance)
        self._execute_thread.sig_highlight_row.connect(self._highlight_row)
        self._execute_thread.sig_update_reply.connect(self._update_reply)
        self._execute_thread.sig_show_execute_icon.connect(
            self._show_execute_icon)

        self._loop_checkbox = QtWidgets.QCheckBox()
        self._loop_checkbox.setToolTip('Run continuously?')
        self._loop_checkbox.clicked.connect(self._show_execute_icon)

        save_icon = get_icon(QtWidgets.QStyle.SP_DriveFDIcon)
        self._save_button = QtWidgets.QPushButton(save_icon, 'Save')
        self._save_button.setToolTip('Save the table to a tab-delimited file')
        self._save_button.clicked.connect(self._save)

        self._info_button = QtWidgets.QPushButton(
            get_icon(QtWidgets.QStyle.SP_FileDialogInfoView), '')
        self._info_button.setToolTip(
            'Display the information about the equipment')
        self._info_button.clicked.connect(
            lambda clicked, record=r: show_record(record))

        self._status_label = QtWidgets.QLabel()

        self._execute_button = QtWidgets.QPushButton()
        self._execute_button.clicked.connect(self._execute_start)
        self._show_execute_icon()

        self._status_label.setText('Create a new Execution Table or\n'
                                   'Drag & Drop or Copy & Paste\n'
                                   'a previous Execution Table')

        execute_widget = QtWidgets.QWidget()
        grid = QtWidgets.QGridLayout()
        grid.addWidget(QtWidgets.QLabel('Timeout'),
                       1,
                       0,
                       alignment=QtCore.Qt.AlignRight)
        grid.addWidget(self._timeout_spinbox, 1, 1, 1, 2)
        grid.addWidget(QtWidgets.QLabel('Rows'),
                       2,
                       0,
                       alignment=QtCore.Qt.AlignRight)
        grid.addWidget(self._use_rows, 2, 1, 1, 2)
        grid.addWidget(self._execute_button, 3, 0, 1, 2)
        grid.addWidget(self._loop_checkbox,
                       3,
                       2,
                       1,
                       1,
                       alignment=QtCore.Qt.AlignLeft)
        grid.addWidget(self._save_button, 4, 0, 1, 2)
        grid.addWidget(self._info_button, 4, 2, 1, 1)
        grid.addWidget(self._status_label,
                       5,
                       0,
                       1,
                       3,
                       alignment=QtCore.Qt.AlignBottom)
        grid.setRowStretch(5, 1)
        execute_widget.setLayout(grid)

        self._create_row()
        self._table.resizeColumnsToContents()

        splitter = QtWidgets.QSplitter()
        splitter.addWidget(self._table)
        splitter.addWidget(execute_widget)
        splitter.setStretchFactor(0, 1)
        splitter.setChildrenCollapsible(False)
        splitter.setSizes([1, 0])

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(splitter)
        self.setLayout(hbox)
    def __init__(self, parent=None):
        """A :class:`~QtWidgets.QWidget` to view a
        :ref:`Configuration File <msl.equipment:configuration_file>`.

        Parameters
        ----------
        parent : :class:`QtWidgets.QWidget`, optional
            The parent :class:`QtWidgets.QWidget`.

        Example
        -------
        To view an example of the :class:`ConfigurationViewer`, run:

        >>> from msl.examples.qt.equipment import configuration_viewer # doctest: +SKIP
        >>> configuration_viewer.show() # doctest: +SKIP
        """
        super(ConfigurationViewer, self).__init__(parent=parent)

        if not has_msl_equipment:
            raise ImportError(
                'This class requires that MSL Equipment is installed')

        self.setAcceptDrops(True)
        self._dropped_path = None
        self._database = None

        #
        # selecting a configuration file
        #
        browse = Button(icon=QtWidgets.QStyle.SP_DialogOpenButton)
        browse.setToolTip('Select a configuration file')
        browse.set_left_click(self._browse_file)

        self._filebox = QtWidgets.QLineEdit()
        self._filebox.setToolTip('Drag \'n drop a configuration file')
        self._filebox.setReadOnly(True)

        select_layout = QtWidgets.QHBoxLayout()
        select_layout.addWidget(browse)
        select_layout.addWidget(self._filebox)
        select_layout.setSpacing(1)

        #
        # the filter field
        #
        self._filter = QtWidgets.QLineEdit()
        self._filter.setToolTip('Search filter for the database')
        self._filter.returnPressed.connect(self._apply_filter)

        filter_button = Button(icon=QtWidgets.QStyle.SP_FileDialogContentsView,
                               tooltip='Apply filter')
        filter_button.set_left_click(self._apply_filter)

        clear_button = Button(icon=QtWidgets.QStyle.SP_LineEditClearButton,
                              tooltip='Clear filter')
        clear_button.set_left_click(self._clear_filter)

        filter_layout = QtWidgets.QHBoxLayout()
        filter_layout.addWidget(filter_button)
        filter_layout.addWidget(self._filter)
        filter_layout.addWidget(clear_button)
        filter_layout.setSpacing(1)

        #
        # the Tree and Tables
        #
        self._equipment_records_table = _RecordTable(EquipmentRecord, self)
        self._connection_records_table = _RecordTable(ConnectionRecord, self)
        self._equipment_table = _RecordTable(EquipmentRecord,
                                             self,
                                             is_dict=True)
        self._constants_table = _ConstantsTable(self)

        self._tree = _Tree(self._equipment_records_table,
                           self._connection_records_table)
        self._tree.sig_selected.connect(self._tree_item_selected)
        self._tree.setToolTip(
            'Double click an item to select it.\n\nHold the CTRL key to select multiple items.'
        )

        tab = QtWidgets.QTabWidget()
        tab.addTab(self._equipment_records_table, 'Equipment Records')
        tab.addTab(self._connection_records_table, 'Connection Records')
        tab.addTab(self._equipment_table, 'Equipment Tags')
        tab.addTab(self._constants_table, 'Constants')
        tab.addTab(Logger(), 'Log')

        splitter = QtWidgets.QSplitter()
        splitter.addWidget(self._tree)
        splitter.addWidget(tab)
        splitter.setStretchFactor(1,
                                  1)  # the tab expands to fill the full width
        splitter.setSizes((self._tree.sizeHint().width() * 1.1, 1))

        main_layout = QtWidgets.QVBoxLayout()
        main_layout.addLayout(select_layout)
        main_layout.addLayout(filter_layout)
        main_layout.addWidget(splitter)

        self.setLayout(main_layout)