예제 #1
0
    def set_calibration_file(self, path, enabled=True):
        """Set the calibration file.

        Parameters
        ----------
        path : :obj:`str`
            The path to the calibration file.
        enabled : :obj:`bool`, optional
            Whether to enable or disable the calibration file.
        """
        if not self._supports_calibration:
            prompt.critical(
                'The translation stage, {}, does not support a calibration file'
                .format(self._connection))
            return

        try:
            self._connection.set_calibration_file(path, enabled)
        except IOError:
            prompt.critical('Cannot find calibration file\n' + path)

        if self._connection.is_calibration_active():
            device_cal_path = self._connection.get_calibration_file()
            self._uncalibrated_mm, self._calibrated_mm = np.loadtxt(
                device_cal_path, unpack=True)
            self._calibration_label = 'Calibration file: {}'.format(
                os.path.basename(device_cal_path))
        else:
            self._uncalibrated_mm, self._calibrated_mm = np.array(
                []), np.array([])
            self._calibration_label = ''
예제 #2
0
    def set_jog(self, value, millimeters=True):
        """Set the jog step size.

        Parameters
        ----------
        value : :obj:`int` or :obj:`float`
            The jog step size.
        millimeters : :obj:`bool`, optional
            Whether the `value` is in ``device units`` or in ``real-world units`` (i.e., in millimeters).
        """
        if not millimeters:
            jog = int(value)
            jog_mm = self._connection.get_real_value_from_device_unit(
                jog, UnitType.DISTANCE)
            msg = '{} device units'.format(jog)
        else:
            jog_mm = float(value)
            jog = self._connection.get_device_unit_from_real_value(
                jog_mm, UnitType.DISTANCE)
            msg = '{} mm'.format(jog)
        if jog_mm > self._max_pos_mm or jog_mm < self._min_pos_mm:
            prompt.critical('Invalid jog size of ' + msg)
        else:
            self._connection.set_jog_step_size(jog)
            self._update_jog_tooltip()
예제 #3
0
 def _execute_start(self):
     """start/stop the execution thread"""
     self._abort_execution = self._execute_thread.isRunning()
     if not self._execute_thread.isRunning():
         if not self._generate_command_list():
             # then there was an error generating the command list
             return
         if not self._command_list:
             prompt.critical('There are no messages to send')
             return
         self._execute_thread._error = False
         self._start_thread()
예제 #4
0
    def move_to(self, value, wait=True, millimeters=True):
        """Move to an absolute position.

        Parameters
        ----------
        value : :obj:`int`, :obj:`float` or :obj:`str`
            If :obj:`str` then the name of a preset. Otherwise an absolute position to move to.
        wait : :obj:`bool`
            Wait until the move is finished before returning control to the calling program.
            If :obj:`True` then this is a blocking method.
        millimeters : :obj:`bool`, optional
            Whether the `value` is in ``device units`` or in ``real-world units`` (i.e., in millimeters).
        """
        if isinstance(value, str):
            if value not in self.preset_positions:
                prompt.critical('{} is not a preset. Must be one of: ' +
                                ','.join(self.preset_positions.keys()))
                return
            value = self.preset_positions[value]
            millimeters = True  # the preset values are in real-world units

        if not self._connection.can_move_without_homing_first():
            res = prompt.question(
                'The motor should be homed before a move can be performed.\n\nHome the motor?'
            )
            if res:
                self.go_home(False)
                return

        if not millimeters:
            value_du = value
            value_mm = self._connection.get_real_value_from_device_unit(
                value, UnitType.DISTANCE)
        else:
            value_du = self._connection.get_device_unit_from_real_value(
                value, UnitType.DISTANCE)
            value_mm = value

        if self._min_pos_mm <= value_mm <= self._max_pos_mm:
            self._requested_mm = value_mm
            self._connection.move_to_position(value_du)
            if wait:
                self._wait(value_du)
            self._update_preset_text_block_signals(value)
        else:
            m = 'Invalid move request.\n\n{} is outside the allowed range [{}, {}]'
            prompt.critical(m.format(value, self._min_pos_mm,
                                     self._max_pos_mm))
예제 #5
0
    def _generate_command_list(self):
        """Generate the list of commands to execute"""
        # determine which rows to execute
        if self._use_rows.text():
            rows = []
            for item in self._use_rows.text().split(','):
                item = item.strip()
                if not item:
                    continue
                if item.isdigit():
                    rows.append(int(item))
                elif '-' in item:
                    item_split = item.split('-')
                    if len(item_split) != 2:
                        prompt.critical('Invalid range: ' + item)
                        return False
                    try:
                        start, end = map(int, item_split)
                    except ValueError as err:
                        prompt.critical(
                            'Invalid start-end range: {}'.format(err))
                        return False
                    if start > end:
                        prompt.critical('Start row {} > End row {}'.format(
                            start, end))
                        return False
                    rows.extend([value for value in range(start, end + 1)])
                else:
                    prompt.critical('Invalid row: ' + item)
                    return False
        else:
            rows = [i + 1 for i in range(self._table.rowCount())]

        # determine which rows have commands
        self._command_list = []
        for row in rows:
            if row < 1 or row > self._table.rowCount():
                prompt.critical('Row {} does not exist'.format(row))
                return False
            index = row - 1
            action = self._table.cellWidget(index, 0).currentText()
            delay = self._table.cellWidget(index, 1).value() * 1e-3
            message = self._table.cellWidget(index, 2).text().strip()
            if not message and action not in ('read', 'delay'):
                continue
            self._command_list.append((index, action, delay, message))

        return True
예제 #6
0
    def _new_file(self, path):
        """Loads a new configuration file"""
        try:
            cfg = Config(path)
            database = cfg.database()
        except Exception as e:
            prompt.critical('Invalid configuration file\n\n{}'.format(e))
            return

        self._filebox.setText(path)
        self._equipment_records_table.set_database(database)
        self._connection_records_table.set_database(database)
        self._equipment_table.set_database(database)
        self._constants_table.update_table(cfg.root)
        self._tree.populate_tree()
        self._apply_filter()
예제 #7
0
 def _apply_filter(self):
     """Apply the filter"""
     if not self._filebox.text():
         prompt.warning('You have not loaded a configuration file')
     else:
         try:
             self._equipment_records_table.filter(self._filter.text())
             self._connection_records_table.filter(self._filter.text())
             self._equipment_table.filter(self._filter.text())
         except Exception as e:
             self._equipment_records_table.setRowCount(0)
             self._equipment_records_table.resizeColumnsToContents()
             self._connection_records_table.setRowCount(0)
             self._connection_records_table.resizeColumnsToContents()
             self._equipment_table.setRowCount(0)
             self._equipment_table.resizeColumnsToContents()
             prompt.critical(e.message)
예제 #8
0
    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)
예제 #9
0
 def _execute_error(self, message):
     """called if there was an exception raised in the execution thread"""
     prompt.critical(message)
     self._show_execute_icon()