def filter(self, text):
        self.setRowCount(0)
        self.resizeColumnsToContents()

        text = text.strip()
        if not text:
            records = self.records_all
        elif '=' not in text:
            records = self.search_database_for_string(text)
        else:
            records = self.get_records(text)

        if not records:
            return

        self.setRowCount(len(records))
        for row, record in enumerate(records):
            for col, element in enumerate(record.to_xml()):
                if element.tag == 'connection' and record.connection is not None:
                    self.setItem(row, col, QtWidgets.QTableWidgetItem('True'))
                elif element.tag == 'properties':
                    text = u''
                    if not element:
                        self.setItem(row, col,
                                     QtWidgets.QTableWidgetItem(text))
                    else:
                        for sub_element in element:
                            text += u'{}={}; '.format(sub_element.tag,
                                                      sub_element.text)
                            self.setItem(row, col,
                                         QtWidgets.QTableWidgetItem(text))
                else:
                    self.setItem(row, col,
                                 QtWidgets.QTableWidgetItem(element.text))
        self.resizeColumnsToContents()
Beispiel #2
0
def test_screen_geometry():
    # just check that these don't raise an exception
    assert isinstance(utils.screen_geometry(), QtCore.QRect)
    assert isinstance(utils.screen_geometry(QtWidgets.QLabel()), QtCore.QRect)
    assert isinstance(
        utils.screen_geometry(QtWidgets.QLabel(parent=QtWidgets.QLabel())),
        QtCore.QRect)
Beispiel #3
0
    def add_qt_tab(self, label, icons):
        """Add the Qt icons."""
        tab = QtWidgets.QWidget()
        self.tab_widget.addTab(tab, label)

        layout = QtWidgets.QGridLayout()

        self.update_message('Loading Qt icons...')

        count = 0
        num_cols = 4
        for i in icons:
            button = QtWidgets.QPushButton(i)
            ico = convert.to_qicon(getattr(QtWidgets.QStyle, i))
            button.setIcon(ico)
            button.clicked.connect(lambda *args, ic=ico, n=i: self.zoom(ic, n))

            layout.addWidget(button, count // num_cols, count % num_cols)
            count += 1
            self.num_icons += 1

        tab.setLayout(layout)

        self.file_index += 1
        self.progress_bar.setValue(self.file_index)
Beispiel #4
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)
Beispiel #5
0
def show_record(record):
    """Create a :class:`QtWidgets.QDialog` to display the information about a record.

    Parameters
    ----------
    record : :class:`~msl.equipment.record_types.EquipmentRecord` or :class:`~msl.equipment.record_types.ConnectionRecord`
        An Equipment Record or a Connection Record.
    """
    dialog = QtWidgets.QDialog()
    dialog.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
    dialog.setWindowTitle(record.__class__.__name__.replace('R', ' R'))

    widget = QtWidgets.QTextEdit()
    widget.setReadOnly(True)
    widget.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
    widget.setText(record.to_yaml())

    hbox = QtWidgets.QHBoxLayout()
    hbox.addWidget(widget)
    dialog.setLayout(hbox)

    size = widget.document().size()
    sb = widget.horizontalScrollBar().size().height()
    dialog.resize(int((size.width() + sb) * 1.1), int((size.height() + sb) * 1.1))  # add 10%
    dialog.exec_()
 def update_table(self, root):
     items = [(e.tag, e.text, e.attrib) for e in root
              if not e.tag.startswith('equipment')]
     self.setRowCount(len(items))
     for i, item in enumerate(items):
         self.setItem(i, 0, QtWidgets.QTableWidgetItem(item[0]))
         self.setItem(i, 1, QtWidgets.QTableWidgetItem(item[1]))
         text = '; '.join('{}={}'.format(k, v) for k, v in item[2].items())
         self.setItem(i, 2, QtWidgets.QTableWidgetItem(text))
     self.resizeColumnsToContents()
Beispiel #7
0
def show():
    app = application()
    window = QtWidgets.QWidget()
    window.setWindowTitle('Toggle Switch Example')
    hbox = QtWidgets.QHBoxLayout()
    ts = ToggleSwitch(window)
    ts.toggled.connect(print_state)
    hbox.addWidget(ts)
    window.setLayout(hbox)
    window.show()
    app.exec_()
Beispiel #8
0
    def read_n_raw_readings(self, n_meas=250, trig_interval=0.02):
        """

        Parameters
        ----------
        n_meas : int
            number of measurements to collect

        Returns
        -------
        tuple of t0_s, data.
        t0_s is the initial time in number of seconds passed since epoch;
        data is a list of n_meas raw values from the RF counter, in Hz
        """
        # set up for fast graphing
        app = application()
        mw = QtWidgets.QMainWindow()
        mw.setWindowTitle("Capacitor raw data")
        cw = QtWidgets.QWidget()
        mw.setCentralWidget(cw)
        layout = QtWidgets.QVBoxLayout()
        cw.setLayout(layout)
        pw1 = pg.PlotWidget(name='Capacitor raw data')
        curve = pw1.plot()
        layout.addWidget(pw1)
        mw.show()

        self.rfcounter.write("INPUT:LEVEL:AUTO ONCE")   # only need to get frequency level once
        self.rfcounter.write("INIT")                    # starts waiting for a trigger
        data = np.empty(n_meas)

        if self.triggerer is not None:
            self.triggerer.start_trigger()

        t0_s = time_ns()/1e9
        rdgs_per_s = 1/trig_interval

        for i in range(n_meas):
            a = self.rfcounter.query("DATA:REM? 1,WAIT")  # a is a string
            # read one data value taken from memory to buffer; remove value from memory after reading
            data[i] = float(a.strip("\n"))

            if i % rdgs_per_s == 0:  # update plot every second
                curve.setData(data[:i])  # show only the collected data
                app.processEvents()

        if self.triggerer is not None:
            self.triggerer.stop_trigger()

        t1_s = time_ns() / 1e9
        print("Elapsed time: {}".format(t1_s - t0_s))

        return t0_s, data
Beispiel #9
0
def test_save_image():
    file = NamedTemporaryFile()

    # no file extension is specified so cannot determine the image format
    with pytest.raises(OSError, match=r'Cannot save image'):
        utils.save_image(QtWidgets.QWidget(), file.name)

    path = file.name + '.png'
    assert not os.path.isfile(path)
    pixmap = utils.save_image(QtWidgets.QWidget(), path)
    assert os.path.isfile(path)
    assert isinstance(pixmap, QtGui.QPixmap)

    file.close()
Beispiel #10
0
 def _show_vertical_popup_menu(self):
     """Handles a right-click on the selected row button(s)"""
     selected = self._table.selectionModel().selectedRows()
     if selected:
         r = 'row' if len(selected) == 1 else 'rows'
         t = 'the' if len(selected) == 1 else 'each'
         menu = QtWidgets.QMenu(self)
         remove = menu.addAction(self._remove_icon,
                                 'Delete the selected ' + r)
         remove.triggered.connect(
             lambda: self._remove_selected_rows(selected))
         insert_before = menu.addAction(
             self._insert_before_icon,
             'Insert a row before %s selected row' % t)
         insert_before.triggered.connect(
             lambda: self._insert_selected(selected, 0))
         insert_after = menu.addAction(
             self._insert_after_icon,
             'Insert a row after %s selected row' % t)
         insert_after.triggered.connect(
             lambda: self._insert_selected(selected, 1))
         clear = menu.addAction(self._clear_icon,
                                'Remove all of the empty rows')
         clear.triggered.connect(self._remove_empty_rows)
         menu.exec_(QtGui.QCursor.pos())
Beispiel #11
0
 def _show_horizontal_popup_menu(self):
     """handles a right-click on the selected column button(s)"""
     selected = self._table.selectionModel().selectedColumns()
     if len(selected) == len(self._header):
         menu = QtWidgets.QMenu(self)
         clear = menu.addAction(self._clear_icon, 'Clear the table')
         clear.triggered.connect(self._clear_table)
         menu.exec_(QtGui.QCursor.pos())
Beispiel #12
0
def show_hardware_info(connection):
    """Displays the hardware information about a Thorlabs_
    :class:`~msl.equipment.resources.thorlabs.kinesis.motion_control.MotionControl` device
    in a :class:`QtWidgets.QDialog`.

    Parameters
    ----------
    connection : :class:`~msl.equipment.resources.thorlabs.kinesis.motion_control.MotionControl`
        A Thorlabs Motion Control subclass.
    """
    info = connection.get_hardware_info()

    dialog = QtWidgets.QDialog()
    dialog.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
    dialog.setWindowTitle(connection.__class__.__name__)

    widget = QtWidgets.QTextEdit()
    widget.setReadOnly(True)
    widget.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)

    text = 'Serial Number: {}\n'.format(info.serialNumber)
    text += 'Model Number: {}\n'.format(info.modelNumber.decode('utf-8'))
    text += 'Type: {}\n'.format(info.type)
    text += 'Number of Channels: {}\n'.format(info.numChannels)
    text += 'Notes: {}\n'.format(info.notes.decode('utf-8'))
    text += 'Firmware Version: {}\n'.format(
        connection.to_version(info.firmwareVersion))
    text += 'Hardware Version: {}\n'.format(
        connection.to_version(info.hardwareVersion))
    text += 'Modification State: {}'.format(info.modificationState)
    widget.setText(text)

    hbox = QtWidgets.QHBoxLayout()
    hbox.addWidget(widget)
    dialog.setLayout(hbox)

    size = widget.document().size()
    pad = widget.horizontalScrollBar().size().height() * 1.1
    dialog.resize(int(size.width() + pad), int(size.height() + pad))
    dialog.exec_()
    def create_top_level(self):
        top_level_names = []
        for name in self.equipment_table.header:
            if name in self.skip_names:
                continue
            if name in self.rename_map:
                name = self.rename_map[name]
            top_level_names.append(name)

        top_level_names.extend(['Backend',
                                'Interface'])  # for ConnectionRecord's

        for item in sorted(top_level_names):
            QtWidgets.QTreeWidgetItem(self, [item])
Beispiel #14
0
    def add_windows_tab(self):
        """Add the icons from the Windows DLL and EXE files."""
        num_cols = 16
        filename = self.windows_files[self.windows_index]
        self.update_message('Loading icons from {}...'.format(filename))

        tab = QtWidgets.QWidget()
        self.tab_widget.addTab(tab, filename)

        layout = QtWidgets.QGridLayout()

        index = 0
        while True:
            button = QtWidgets.QPushButton(str(index))
            try:
                name = '{}|{}'.format(filename, str(index))
                ico = convert.to_qicon(name)
            except OSError:
                break

            button.setIcon(ico)
            button.clicked.connect(
                lambda *args, ic=ico, n=name: self.zoom(ic, n))
            layout.addWidget(button, index // num_cols, index % num_cols)
            index += 1
            self.num_icons += 1

        self.file_index += 1
        self.progress_bar.setValue(self.file_index)

        tab.setLayout(layout)

        self.windows_index += 1
        if self.windows_index == len(self.windows_files):
            self.timer.stop()
            self.update_message('Loaded {} icons.'.format(self.num_icons))
            self.progress_bar.hide()
Beispiel #15
0
    def __init__(self):

        app = application()

        self.tab_widget = QtWidgets.QTabWidget()

        self.main_window = QtWidgets.QMainWindow()
        self.main_window.setWindowTitle('Standard Icons')
        self.main_window.setCentralWidget(self.tab_widget)
        self.main_window.closeEvent = self.close_event

        # add a progress bar to the status bar
        self.progress_bar = QtWidgets.QProgressBar(
            self.main_window.statusBar())
        self.progress_bar.setAlignment(QtCore.Qt.AlignCenter)
        self.main_window.statusBar().addPermanentWidget(self.progress_bar)
        self.main_window.showMaximized()

        self.num_icons = 0
        self.file_index = 0
        self.zoom_widget = QtWidgets.QDialog()
        self.zoom_widget.setSizeGripEnabled(True)
        self.zoom_widget.resize(QtCore.QSize(256, 256))
        self.zoom_widget.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
        vbox = QtWidgets.QVBoxLayout()
        self.zoom_label = QtWidgets.QLabel()
        self.zoom_label.setScaledContents(True)
        vbox.addWidget(self.zoom_label)
        self.zoom_widget.setLayout(vbox)

        qt_icons = [sp for sp in dir(QtWidgets.QStyle) if sp.startswith('SP_')]

        self.windows_files = [
            'accessibilitycpl', 'compstui', 'ddores', 'dmdskres', 'explorer',
            'gameux', 'ieframe', 'imageres', 'mmcndmgr', 'mmres', 'moricons',
            'netcenter', 'netshell', 'networkexplorer', 'pifmgr', 'pnidui',
            'sensorscpl', 'setupapi', 'shell32', 'wmploc', 'wpdshext'
        ]

        self.num_files = 1 + len(self.windows_files)
        self.progress_bar.setRange(0, self.num_files)

        self.add_qt_tab('Qt Icons', qt_icons)

        if has_clr:
            self.windows_index = 0
            self.timer = QtCore.QTimer()
            self.timer.timeout.connect(self.add_windows_tab)
            self.timer.start(0)
        else:
            self.update_message('Loaded {} icons.'.format(self.num_icons))
            self.progress_bar.hide()

        app.exec()
Beispiel #16
0
    def __init__(self, parent=None):
        super(BlinkingLEDs, self).__init__(parent)

        self.setWindowTitle('Blinking LEDs')

        # The shape can be an enum value or member name (case in-sensitive)
        # The color can be anything that msl.qt.utils.to_qcolor() accepts
        params = [
            {
                'shape': LED.Circle,
                'on_color': Qt.darkGreen,
                'clickable': True
            },
            {
                'shape': 'rouNDed',
                'on_color': (78, 82, 107)
            },
            {
                'shape': 2,
                'on_color': 'cyan',
                'clickable': True
            },
            {
                'shape': 'Triangle',
                'on_color': '#6b3064'
            },
        ]

        self.leds = []
        layout = QtWidgets.QHBoxLayout()
        for kwargs in params:
            led = LED(**kwargs)
            led.toggled.connect(self.led_state_changed)
            led.clicked.connect(self.led_was_clicked)
            layout.addWidget(led)
            self.leds.append(led)
        self.setLayout(layout)

        self._timer = QtCore.QTimer()
        self._timer.timeout.connect(self.toggle_random_led)
        self._timer.start(200)
Beispiel #17
0
    def paintEvent(self, event):
        """Overrides :meth:`QtWidgets.QWidget.paintEvent`."""
        option = QtWidgets.QStyleOption()
        option.initFrom(self)

        h = option.rect.height()
        w = option.rect.width()
        if self._shape == Shapes.Triangle or self._shape == Shapes.Rounded:
            aspect = self._triangle_factor if self._shape == Shapes.Triangle else self._rounded_factor
            ah = w / aspect
            aw = w
            if ah > h:
                ah = h
                aw = h * aspect
            x = abs(aw - w) / 2.0
            y = abs(ah - h) / 2.0
            bounds = QtCore.QRectF(x, y, aw, ah)
        else:
            size = min(w, h)
            x = abs(size - w) / 2.0
            y = abs(size - h) / 2.0
            bounds = QtCore.QRectF(x, y, size, size)

        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)

        if self.isEnabled():
            color = self._on_color if self._is_on else self._off_color
        else:
            color = QtGui.QColor('#BDBDBD')

        lighter = QtGui.QColor(color).lighter(150)  # 150 -> 50% brighter

        color_str = 'rgb(%d,%d,%d)' % (color.red(), color.green(), color.blue())
        lighter_str = 'rgb(%d,%d,%d)' % (lighter.red(), lighter.green(), lighter.blue())

        svg = (SVG_MAP[self._shape] % (color_str, lighter_str)).encode('utf8')
        self._renderer.load(QtCore.QByteArray(svg))
        self._renderer.render(painter, bounds)
Beispiel #18
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)
Beispiel #19
0
import os
from datetime import datetime
from time import time, time_ns, sleep
import numpy as np
import matplotlib.pyplot as plt

import pyqtgraph as pg
from msl.qt import QtWidgets, application

from equip import CetoniSP
from data_handling import fit_sinusoid

app = application()
mw = QtWidgets.QMainWindow()
mw.setWindowTitle("Syringe pump fill level")
cw = QtWidgets.QWidget()
mw.setCentralWidget(cw)
layout = QtWidgets.QVBoxLayout()
cw.setLayout(layout)
pw1 = pg.PlotWidget(name='Syringe pump fill level')
curve = pw1.plot()
layout.addWidget(pw1)


class Experimenter(object):
    def __init__(self):

        self.sp = None
        self.t_data = []  # time in seconds
        self.fl_data = []  # syringe plunger fill level in unit set (mL)
    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)
    def populate_tree(self):
        self.clear()
        self.create_top_level()

        for index in range(self.topLevelItemCount()):
            item = self.topLevelItem(index)
            name = item.text(0)
            if name == 'Manufacturer':
                values = {}
                for record in self.equipment_table.records_all:
                    if record.manufacturer and record.manufacturer not in values:
                        values[record.manufacturer] = [record.model]
                    else:
                        if record.model and record.model not in values[
                                record.manufacturer]:
                            values[record.manufacturer].append(record.model)
                item.setText(0, name + ' ({})'.format(len(values)))
                for manufacturer in sorted(values):
                    model_item = QtWidgets.QTreeWidgetItem(
                        item, [manufacturer])
                    for model in sorted(values[manufacturer]):
                        QtWidgets.QTreeWidgetItem(model_item, [model])
            elif name == 'Year Calibrated':
                years = []
                for record in self.equipment_table.records_all:
                    year = record.date_calibrated.year
                    if year > 1 and year not in years:
                        years.append(year)
                item.setText(0, name + ' ({})'.format(len(years)))
                for year in sorted(years)[::-1]:
                    QtWidgets.QTreeWidgetItem(item, [str(year)])
            elif name == 'Connection':
                bools = {'True': 0, 'False': 0}
                for record in self.equipment_table.records_all:
                    bools[str(record.connection is not None)] += 1
                item.setText(0, name + ' (2)')
                for key in sorted(bools):
                    QtWidgets.QTreeWidgetItem(item, [key])
            elif name == 'Calibration Cycle':
                cycles = []
                for record in self.equipment_table.records_all:
                    if record.calibration_cycle == 0:
                        continue
                    if int(record.calibration_cycle
                           ) == record.calibration_cycle:
                        cycle = int(record.calibration_cycle)
                    else:
                        cycle = record.calibration_cycle
                    if cycle not in cycles:
                        cycles.append(cycle)
                item.setText(0, name + ' ({})'.format(len(cycles)))
                for c in sorted(cycles):
                    QtWidgets.QTreeWidgetItem(item, [str(c)])
            else:
                if name == 'Backend' or name == 'Interface':
                    values = self._get_distinct_connection_records(name)
                else:
                    values = self._get_distinct_equipment_records(name)
                item.setText(0, name + ' ({})'.format(len(values)))
                for value in sorted(values):
                    QtWidgets.QTreeWidgetItem(item, [value])
Beispiel #22
0
    def __init__(self, client, **kwargs):
        super(Gui, self).__init__()
        self.path = kwargs.pop('path', None)
        self.client = client
        self.original_image = None
        self.delta = 0.01  # the amount to translate image on UP DOWN LEFT RIGHT key presses
        self.client_queue = {}  # a queue of requests to send to the RPi

        zoom = kwargs.get('zoom')
        if zoom:
            self.zoom_history = [zoom]  # (x, y, width, height) values between 0.0 and 1.0
        else:
            self.zoom_history = []

        self.ocr_params = {
            'zoom': zoom,
            'rotate': kwargs.get('rotate'),
            'threshold': kwargs.get('threshold'),
            'dilate': kwargs.get('dilate'),
            'erode': kwargs.get('erode'),
            'blur': kwargs.get('blur'),
            'algorithm': kwargs.get('algorithm', 'tesseract').lower(),
            'lang': kwargs.get('lang', 'eng').lower(),
        }

        self.ocr_label = QtWidgets.QLabel()
        self.ocr_label.setFont(QtGui.QFont('Helvetica', 14))

        # the canvas widget to display the image
        self.graphics_view = pg.GraphicsView(background=None)
        self.view_box = pg.ViewBox(invertY=True, lockAspect=True, enableMouse=False, enableMenu=False)
        self.graphics_view.setCentralItem(self.view_box)
        self.canvas = pg.ImageItem(axisOrder='row-major')
        self.view_box.setMouseMode(pg.ViewBox.RectMode)
        self.view_box.addItem(self.canvas)
        self.view_box.mouseDragEvent = self.select_zoom

        graphing_layout = QtWidgets.QVBoxLayout()
        graphing_layout.addWidget(self.ocr_label, alignment=QtCore.Qt.AlignCenter)
        graphing_layout.addWidget(self.graphics_view)

        # the container for all the image-processing widgets
        self.image_processing_group = QtWidgets.QGroupBox('Image Processing')

        # rotate
        self.rotate_label = QtWidgets.QLabel('<html>Rotate [0&deg;]</html>')
        self.rotate_slider = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
        self.rotate_slider.setToolTip('The angle to rotate the image')
        self.rotate_slider.setMinimum(-180)
        self.rotate_slider.setMaximum(180)
        self.rotate_slider.setSingleStep(1)
        self.rotate_slider.setPageStep(15)
        self.rotate_slider.valueChanged.connect(self.update_rotate)
        if self.ocr_params['rotate']:
            self.rotate_slider.setValue(self.ocr_params['rotate'])

        # Gaussian blur
        self.blur_label = QtWidgets.QLabel('Gaussian Blur [0]')
        self.blur_slider = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
        self.blur_slider.setToolTip('The pixel radius to use for the Gaussian blur')
        self.blur_slider.setMinimum(0)
        self.blur_slider.setMaximum(kwargs.pop('max_blur', 9))
        self.blur_slider.setSingleStep(1)
        self.blur_slider.valueChanged.connect(self.update_blur)
        if self.ocr_params['blur']:
            self.blur_slider.setValue(self.ocr_params['blur'])

        # threshold
        self.threshold_label = QtWidgets.QLabel('Threshold [0]')
        self.threshold_checkbox = QtWidgets.QCheckBox()
        self.threshold_checkbox.setToolTip('Apply thresholding?')
        self.threshold_checkbox.clicked.connect(self.update_threshold_checkbox)
        self.threshold_slider = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
        self.threshold_slider.setToolTip('The threshold value')
        self.threshold_slider.setMinimum(0)
        self.threshold_slider.setMaximum(255)
        self.threshold_slider.setSingleStep(1)
        self.threshold_slider.valueChanged.connect(self.update_threshold)
        if self.ocr_params['threshold']:
            self.threshold_slider.setValue(self.ocr_params['threshold'])
            self.threshold_checkbox.setChecked(True)
        else:
            self.threshold_slider.setEnabled(False)
            self.threshold_checkbox.setChecked(False)

        # dilate
        self.dilate_label = QtWidgets.QLabel('Dilate [0]')
        self.dilate_slider = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
        self.dilate_spinbox = QtWidgets.QSpinBox()
        self.dilate_spinbox.setToolTip('The number of iterations to apply dilation at the specified radius')
        self.dilate_spinbox.setMinimum(1)
        self.dilate_spinbox.setMaximum(99)
        self.dilate_spinbox.setSingleStep(1)
        self.dilate_spinbox.valueChanged.connect(self.update_dilate_iter)
        self.dilate_slider.setToolTip('The pixel radius to use for dilation')
        self.dilate_slider.setMinimum(0)
        self.dilate_slider.setMaximum(kwargs.pop('max_dilate', 9))
        self.dilate_slider.setSingleStep(1)
        self.dilate_slider.valueChanged.connect(self.update_dilate)
        if self.ocr_params['dilate']:
            self.dilate_slider.setValue(self.ocr_params['dilate'])

        # erode
        self.erode_label = QtWidgets.QLabel('Erode [0]')
        self.erode_slider = QtWidgets.QSlider(orientation=QtCore.Qt.Horizontal)
        self.erode_spinbox = QtWidgets.QSpinBox()
        self.erode_spinbox.setToolTip('The number of iterations to apply erosion at the specified radius')
        self.erode_spinbox.setMinimum(1)
        self.erode_spinbox.setMaximum(99)
        self.erode_spinbox.setSingleStep(1)
        self.erode_spinbox.valueChanged.connect(self.update_erode_iter)
        self.erode_slider.setToolTip('The pixel radius to use for erosion')
        self.erode_slider.setMinimum(0)
        self.erode_slider.setMaximum(kwargs.pop('max_erode', 9))
        self.erode_slider.setSingleStep(1)
        self.erode_slider.valueChanged.connect(self.update_erode)
        if self.ocr_params['erode']:
            self.erode_slider.setValue(self.ocr_params['erode'])

        # image-processing layout
        ip_layout = QtWidgets.QGridLayout()
        ip_layout.addWidget(self.rotate_label, 0, 0)
        ip_layout.addWidget(self.rotate_slider, 0, 1)
        ip_layout.addWidget(self.blur_label, 1, 0)
        ip_layout.addWidget(self.blur_slider, 1, 1)
        ip_layout.addWidget(self.threshold_label, 2, 0)
        ip_layout.addWidget(self.threshold_slider, 2, 1)
        ip_layout.addWidget(self.threshold_checkbox, 2, 2)
        ip_layout.addWidget(self.dilate_label, 3, 0)
        ip_layout.addWidget(self.dilate_slider, 3, 1)
        ip_layout.addWidget(self.dilate_spinbox, 3, 2)
        ip_layout.addWidget(self.erode_label, 4, 0)
        ip_layout.addWidget(self.erode_slider, 4, 1)
        ip_layout.addWidget(self.erode_spinbox, 4, 2)
        self.image_processing_group.setLayout(ip_layout)

        # the container for all the camera widgets
        self.camera_config_group = QtWidgets.QGroupBox('Camera Settings')

        # ISO
        self.iso_combobox = QtWidgets.QComboBox()
        self.iso_combobox.setToolTip('The ISO setting of the camera')
        self.iso_combobox.addItems(['auto', '100', '200', '320', '400', '500', '640', '800'])
        iso = str(kwargs.pop('iso', 'auto'))
        if iso == '0':
            iso = 'auto'
        self.iso_combobox.setCurrentText(iso)
        self.update_iso(self.iso_combobox.currentText())
        self.iso_combobox.currentTextChanged.connect(self.update_iso)
        self.iso_combobox.setEnabled(not self.path)

        # resolution
        self.resolution_combobox = QtWidgets.QComboBox()
        self.resolution_combobox.setToolTip('The resolution of the camera')
        self.resolution_combobox.addItems(['VGA', 'SVGA', 'XGA', '720p', 'SXGA', 'UXGA', '1080p', 'MAX'])
        self.resolution_combobox.setCurrentText(str(kwargs.pop('resolution', 'VGA')))
        self.update_resolution(self.resolution_combobox.currentText())
        self.resolution_combobox.currentTextChanged.connect(self.update_resolution)
        self.resolution_combobox.setEnabled(not self.path)

        # exposure mode
        self.exposure_mode_combobox = QtWidgets.QComboBox()
        self.exposure_mode_combobox.setToolTip('The exposure mode of the camera')
        self.exposure_mode_combobox.addItems(['off', 'auto', 'night', 'nightpreview',
                                              'backlight', 'spotlight', 'sports', 'snow',
                                              'beach', 'verylong', 'fixedfps', 'antishake', 'fireworks'])
        self.exposure_mode_combobox.setCurrentText(str(kwargs.pop('exposure_mode', 'auto')))
        self.update_exposure_mode(self.exposure_mode_combobox.currentText())
        self.exposure_mode_combobox.currentTextChanged.connect(self.update_exposure_mode)
        self.exposure_mode_combobox.setEnabled(not self.path)

        camera_layout = QtWidgets.QGridLayout()
        camera_layout.addWidget(QtWidgets.QLabel('ISO'), 0, 0)
        camera_layout.addWidget(self.iso_combobox, 0, 1)
        camera_layout.addItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.MinimumExpanding), 0, 2)
        camera_layout.addWidget(QtWidgets.QLabel('Resolution'), 1, 0)
        camera_layout.addWidget(self.resolution_combobox, 1, 1)
        camera_layout.addWidget(QtWidgets.QLabel('Exposure Mode'), 2, 0)
        camera_layout.addWidget(self.exposure_mode_combobox, 2, 1)
        self.camera_config_group.setLayout(camera_layout)

        if self.client is None:
            self.iso_combobox.setEnabled(False)
            self.resolution_combobox.setEnabled(False)
            self.exposure_mode_combobox.setEnabled(False)

        # the container for all the OCR algorithm widgets
        self.ocr_config_group = QtWidgets.QGroupBox('OCR Algorithm Settings')

        # tesseract languages
        self.tess_lang_combobox = QtWidgets.QComboBox()
        self.tess_lang_combobox.addItems(['eng', 'letsgodigital'])
        self.tess_lang_combobox.currentTextChanged.connect(self.update_tess_lang)
        self.tess_lang_combobox.setToolTip('The language to use for Tesseract')

        # tesseract or ssocr
        self.tess_radio = QtWidgets.QRadioButton('Tesseract')
        self.tess_radio.setToolTip('Use Tesseract')
        self.tess_radio.toggled.connect(self.update_algorithm)
        self.ssocr_radio = QtWidgets.QRadioButton('SSOCR')
        self.ssocr_radio.setToolTip('Use SSOCR')
        self.ssocr_radio.toggled.connect(self.update_algorithm)
        if self.ocr_params['algorithm'] == 'ssocr':
            self.ssocr_radio.setChecked(True)
        else:
            self.tess_radio.setChecked(True)

        algo_layout = QtWidgets.QGridLayout()
        algo_layout.addWidget(self.tess_radio, 0, 0)
        algo_layout.addWidget(self.tess_lang_combobox, 0, 1)
        algo_layout.addItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.MinimumExpanding), 0, 2)
        algo_layout.addWidget(self.ssocr_radio, 1, 0)
        self.ocr_config_group.setLayout(algo_layout)

        options_layout = QtWidgets.QVBoxLayout()
        options_layout.addWidget(self.image_processing_group)
        options_layout.addWidget(self.camera_config_group)
        options_layout.addWidget(self.ocr_config_group)
        options_layout.addStretch(1)

        layout = QtWidgets.QHBoxLayout()
        layout.addLayout(graphing_layout)
        layout.addLayout(options_layout)
        self.setLayout(layout)

        if self.path:
            self.setAcceptDrops(True)
            self.capture_thread = None
            self.original_image = utils.to_cv2(self.path)
            height, width = self.original_image.shape[:2]
            try:  # could pass in an already-opened image object (which isn't a path)
                basename = os.path.basename(self.path)
            except OSError:
                basename = 'UNKNOWN'
            self.setWindowTitle('OCR || {} [{} x {}]'.format(basename, width, height))
        else:
            self.setAcceptDrops(False)
            self.original_image = None
            self.capture_index = 0
            self.setWindowTitle('OCR || Capture 0')
            self.capture_thread = Capture()
            self.capture_thread.add_callback(self.capture)
            self.capture_thread.start(self.client)

        self.apply_ocr()
    def __init__(self, parent):
        """Display a QDialog to edit the settings"""
        super(_Settings, self).__init__(flags=QtCore.Qt.WindowCloseButtonHint)

        self.conn = parent._connection

        info = self.conn.get_hardware_info()
        self.setWindowTitle(
            info.modelNumber.decode('utf-8') + ' || ' +
            info.notes.decode('utf-8'))

        # move info
        max_vel, max_acc = self.conn.get_motor_velocity_limits()
        vel, acc = self.conn.get_vel_params()
        vel = self.conn.get_real_value_from_device_unit(vel, UnitType.VELOCITY)
        acc = self.conn.get_real_value_from_device_unit(
            acc, UnitType.ACCELERATION)
        backlash = self.conn.get_real_value_from_device_unit(
            self.conn.get_backlash(), UnitType.DISTANCE)

        # move widgets
        self.acc_spinbox = QtWidgets.QDoubleSpinBox()
        self.acc_spinbox.setMinimum(0)
        self.acc_spinbox.setMaximum(max_acc)
        self.acc_spinbox.setValue(acc)
        self.acc_spinbox.setToolTip(
            '<html><b>Range:</b><br>0 - {} mm/s<sup>2</sup></html>'.format(
                max_acc))

        self.vel_spinbox = QtWidgets.QDoubleSpinBox()
        self.vel_spinbox.setMinimum(0)
        self.vel_spinbox.setMaximum(max_vel)
        self.vel_spinbox.setValue(vel)
        self.vel_spinbox.setToolTip(
            '<html><b>Range:</b><br>0 - {} mm/s</html>'.format(max_vel))

        self.backlash_spinbox = QtWidgets.QDoubleSpinBox()
        self.backlash_spinbox.setMinimum(0)
        self.backlash_spinbox.setMaximum(5)
        self.backlash_spinbox.setValue(backlash)
        self.backlash_spinbox.setToolTip(
            '<html><b>Range:</b><br>0 - 5 mm</html>')

        move_group = QtWidgets.QGroupBox('Move Parameters')
        move_grid = QtWidgets.QGridLayout()
        move_grid.addWidget(QtWidgets.QLabel('Backlash'),
                            0,
                            0,
                            alignment=QtCore.Qt.AlignRight)
        move_grid.addWidget(self.backlash_spinbox, 0, 1)
        move_grid.addWidget(QtWidgets.QLabel('mm'),
                            0,
                            2,
                            alignment=QtCore.Qt.AlignLeft)
        move_grid.addWidget(QtWidgets.QLabel('Maximum Velocity'),
                            1,
                            0,
                            alignment=QtCore.Qt.AlignRight)
        move_grid.addWidget(self.vel_spinbox, 1, 1)
        move_grid.addWidget(QtWidgets.QLabel('mm/s'),
                            1,
                            2,
                            alignment=QtCore.Qt.AlignLeft)
        move_grid.addWidget(QtWidgets.QLabel('Acceleration'),
                            2,
                            0,
                            alignment=QtCore.Qt.AlignRight)
        move_grid.addWidget(self.acc_spinbox, 2, 1)
        move_grid.addWidget(QtWidgets.QLabel('mm/s<sup>2</sup>'),
                            2,
                            2,
                            alignment=QtCore.Qt.AlignLeft)
        move_group.setLayout(move_grid)

        # jog info
        jog_size = self.conn.get_real_value_from_device_unit(
            self.conn.get_jog_step_size(), UnitType.DISTANCE)
        vel, acc = self.conn.get_jog_vel_params()
        jog_vel = self.conn.get_real_value_from_device_unit(
            vel, UnitType.VELOCITY)
        jog_acc = self.conn.get_real_value_from_device_unit(
            acc, UnitType.ACCELERATION)

        # jog widgets
        min_jog, max_jog = 0.002, parent._max_pos_mm / 2.0
        self.jog_size_spinbox = QtWidgets.QDoubleSpinBox()
        self.jog_size_spinbox.setMinimum(min_jog)
        self.jog_size_spinbox.setMaximum(max_jog)
        self.jog_size_spinbox.setDecimals(3)
        self.jog_size_spinbox.setValue(jog_size)
        self.jog_size_spinbox.setToolTip(
            '<html><b>Range:</b><br>{} - {} mm</html>'.format(
                min_jog, max_jog))

        self.jog_acc_spinbox = QtWidgets.QDoubleSpinBox()
        self.jog_acc_spinbox.setMinimum(0)
        self.jog_acc_spinbox.setMaximum(max_acc)
        self.jog_acc_spinbox.setValue(jog_acc)
        self.jog_acc_spinbox.setToolTip(
            '<html><b>Range:</b><br>0 - {} mm/s<sup>2</sup></html>'.format(
                max_acc))

        self.jog_vel_spinbox = QtWidgets.QDoubleSpinBox()
        self.jog_vel_spinbox.setMinimum(0)
        self.jog_vel_spinbox.setMaximum(max_vel)
        self.jog_vel_spinbox.setValue(jog_vel)
        self.jog_vel_spinbox.setToolTip(
            '<html><b>Range:</b><br>0 - {} mm/s</html>'.format(max_vel))

        jog_group = QtWidgets.QGroupBox('Jog Parameters')
        jog_grid = QtWidgets.QGridLayout()
        jog_grid.addWidget(QtWidgets.QLabel('Step Size'),
                           0,
                           0,
                           alignment=QtCore.Qt.AlignRight)
        jog_grid.addWidget(self.jog_size_spinbox, 0, 1)
        jog_grid.addWidget(QtWidgets.QLabel('mm'),
                           0,
                           2,
                           alignment=QtCore.Qt.AlignLeft)
        jog_grid.addWidget(QtWidgets.QLabel('Maximum Velocity'),
                           1,
                           0,
                           alignment=QtCore.Qt.AlignRight)
        jog_grid.addWidget(self.jog_vel_spinbox, 1, 1)
        jog_grid.addWidget(QtWidgets.QLabel('mm/s'),
                           1,
                           2,
                           alignment=QtCore.Qt.AlignLeft)
        jog_grid.addWidget(QtWidgets.QLabel('Acceleration'),
                           2,
                           0,
                           alignment=QtCore.Qt.AlignRight)
        jog_grid.addWidget(self.jog_acc_spinbox, 2, 1)
        jog_grid.addWidget(QtWidgets.QLabel('mm/s<sup>2</sup>'),
                           2,
                           2,
                           alignment=QtCore.Qt.AlignLeft)
        jog_group.setLayout(jog_grid)

        hbox = QtWidgets.QHBoxLayout()
        hbox.addWidget(move_group)
        hbox.addWidget(jog_group)

        update_button = QtWidgets.QPushButton('Update')
        update_button.setToolTip('Update the device settings')
        update_button.clicked.connect(self.update_settings)

        cancel_button = QtWidgets.QPushButton('Cancel')
        cancel_button.setToolTip('Update the device settings')
        cancel_button.clicked.connect(self.close)

        info_button = QtWidgets.QPushButton()
        info_button.setIcon(get_icon('imageres|109'))
        info_button.clicked.connect(
            lambda: show_hardware_info(parent._connection))
        info_button.setToolTip('Display the hardware information')

        button_layout = QtWidgets.QGridLayout()
        button_layout.addWidget(cancel_button, 0, 0)
        button_layout.addItem(
            QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Expanding,
                                  QtWidgets.QSizePolicy.Expanding), 0, 1)
        button_layout.addWidget(update_button, 0, 2)
        button_layout.addWidget(info_button, 0, 3)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addLayout(button_layout)
        self.setLayout(vbox)
    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)