Esempio n. 1
0
def test_to_qicon():

    assert isinstance(convert.to_qicon(QtGui.QIcon()), QtGui.QIcon)
    assert isinstance(convert.to_qicon(QtGui.QPixmap()), QtGui.QIcon)
    assert isinstance(convert.to_qicon(QtGui.QImage()), QtGui.QIcon)
    assert isinstance(convert.to_qicon(QtWidgets.QStyle.SP_TitleBarMenuButton),
                      QtGui.QIcon)
    assert isinstance(convert.to_qicon(14), QtGui.QIcon)

    base64 = 'iVBORw0KGgoAAAANSUhEUgAAACgAAABCCAYAAAAlkZRRAAAACXBIWXMAAAsTAAALEwEAmpwYAAABpElEQVR' \
             'oge2ZT07CQBSHf29kLXKOsnApBEx7DAl3sT2BZzAx6C1ahYBLFzRxyQ1Q12SeiwoSQonjzMiLmW9Fh+nMlz' \
             'ft/HkFhEOuGnofRLz+3RyVztpVrhryhXjBhu8OlsN2DADQ/NYalS+m93sXVJpzACBGASAxvt+1kGuCoC3On' \
             'kGXc9824iMYBG0JgrZ4X0lMWe+KtKKkdTcvQgT3sRy2Y6URr6+bo3laV/cogpUcX28VpbV1vdtY4iyCYcsv' \
             'lSBoi7j94G474iMoXjDMg7YEQVvEC4rPDxq/xctBdH7CuAGA0/vSOBlkivk0o+iMNcfuVWq6+6uOfot4wdo' \
             'h/riKcqbqYOMrMfQTxEdQvKC4/eAu4iMYBG0RL9gAthd6yg4lco6B+AiKFzSfB1erBVQj8+CyF2PB1sPrAg' \
             'fyea4RP8TiBb+GmDIA0ArF5h/CLUCPx5AKuISAsJJYIV4w8O8hAOj2O9VbTJRNn6YpAHT6nZxQnYun49nmQ' \
             'NTrXcSaKN8t37RRU85AMRvPEgDoXnZT8Pe3un31FXMymTzL/xwrXlA8n2MHdwPYAbB5AAAAAElFTkSuQmCC'

    assert isinstance(convert.to_qicon(QtCore.QByteArray(base64.encode())),
                      QtGui.QIcon)
    assert isinstance(convert.to_qicon(bytearray(base64.encode())),
                      QtGui.QIcon)

    default_size = convert.to_qicon(
        QtWidgets.QStyle.SP_TitleBarMenuButton).availableSizes()[-1]
    assert isinstance(default_size, QtCore.QSize)

    with pytest.raises(TypeError):
        convert.to_qicon(None)

    with pytest.raises(OSError):
        convert.to_qicon(99999)  # not a valid QStyle.StandardPixmap enum value

    with pytest.raises(OSError):
        convert.to_qicon('this is not an icon')

    assert isinstance(
        convert.to_qicon(os.path.join(os.path.dirname(__file__), 'gamma.png')),
        QtGui.QIcon)

    # insert this directory into sys.path and check again
    sys.path.insert(0, os.path.dirname(__file__))
    assert isinstance(convert.to_qicon('gamma.png'), QtGui.QIcon)

    icon_1 = convert.to_qicon('gamma.png').availableSizes()[0]
    icon_2 = convert.to_qicon('gamma.png', size=0.5).availableSizes()[0]
    assert int(icon_1.width() * 0.5) == icon_2.width()
    assert int(icon_1.height() * 0.5) == icon_2.height()
Esempio n. 2
0
def test_to_qcolor():
    def assert_rgba(c, r, g, b, a):
        assert c.red() == r
        assert c.green() == g
        assert c.blue() == b
        assert c.alpha() == a

    # no arguments
    assert isinstance(convert.to_qcolor(), QtGui.QColor)

    # QtGui.QColor
    color = QtGui.QColor(127, 127, 127)
    assert convert.to_qcolor(color) is not color
    assert convert.to_qcolor(color) == color

    # int or Qt.GlobalColor
    assert_rgba(convert.to_qcolor(7), 255, 0, 0, 255)
    assert_rgba(convert.to_qcolor(Qt.red), 255, 0, 0, 255)

    # string
    assert_rgba(convert.to_qcolor('red'), 255, 0, 0, 255)
    assert_rgba(convert.to_qcolor('#00FF00'), 0, 255, 0, 255)
    assert_rgba(convert.to_qcolor('#080000FF'), 0, 0, 255, 8)

    # float
    assert_rgba(convert.to_qcolor(0.0), 0, 0, 0, 255)
    assert_rgba(convert.to_qcolor(0.45), 114, 114, 114, 255)
    assert_rgba(convert.to_qcolor(0.5), 127, 127, 127, 255)
    assert_rgba(convert.to_qcolor(1.0), 255, 255, 255, 255)
    assert_rgba(convert.to_qcolor(1e9), 255, 255, 255, 255)
    assert_rgba(convert.to_qcolor(-1.0), 0, 0, 0, 255)
    assert_rgba(convert.to_qcolor(-0.1), 0, 0, 0, 255)

    # edge cases for int/float
    assert_rgba(convert.to_qcolor(0, 0, 0), 0, 0, 0, 255)
    assert_rgba(convert.to_qcolor(0.0, 0.0, 0.0), 0, 0, 0, 255)
    assert_rgba(convert.to_qcolor(1, 1, 1), 1, 1, 1, 255)
    assert_rgba(convert.to_qcolor(1., 1., 1.), 255, 255, 255, 255)
    assert_rgba(convert.to_qcolor(0, 1., 1), 0, 255, 1, 255)

    # tuple (can mix int 0-255 and float 0.0-1.0)
    assert_rgba(convert.to_qcolor((34, 58, 129)), 34, 58, 129, 255)
    assert_rgba(convert.to_qcolor((34, 58, 129, 50)), 34, 58, 129, 50)
    assert_rgba(convert.to_qcolor((34, 58 / 255., 129)), 34, 58, 129, 255)
    assert_rgba(convert.to_qcolor((34 / 255., 58 / 255., 129 / 255.)), 34, 58,
                129, 255)
    assert_rgba(convert.to_qcolor((34 / 255., 58 / 255., 129 / 255., 0.45)),
                34, 58, 129, 114)

    # wrong number of arguments (get fatal crash with PySide6 during tests)
    if binding.name != 'PySide6':
        for obj in [(1, 2), (1, 2, 3, 4, 5, 6, 7)]:
            with pytest.raises(TypeError):
                convert.to_qcolor(obj)

    # wrong type
    with pytest.raises(TypeError,
                       match=r'Cannot convert \(None,\) to a QColor'):
        convert.to_qcolor(None)
Esempio n. 3
0
def test_drag_enter_paths():
    mime = QtCore.QMimeData()

    event = QtGui.QDragEnterEvent(QtCore.QPoint(0, 0), Qt.CopyAction, mime,
                                  Qt.LeftButton, Qt.NoModifier)
    paths = utils.drag_drop_paths(event)
    assert len(paths) == 0

    url1 = QtCore.QUrl('/path/to/image.jpeg')
    url1.setScheme('file')

    url2 = QtCore.QUrl(
        '')  # does not pass the isValid() and scheme() == 'file' checks

    url3 = QtCore.QUrl('/path/to/image.jpeg')
    url3.setScheme('ftp')  # does not pass the scheme() == 'file' check

    url4 = QtCore.QUrl('/path/to/image.png')
    url4.setScheme('file')

    url5 = QtCore.QUrl('/path/to/image2.jpg')
    url5.setScheme('file')

    mime.setUrls([url1, url2, url3, url4, url5])
    event = QtGui.QDragEnterEvent(QtCore.QPoint(0, 0), Qt.CopyAction, mime,
                                  Qt.LeftButton, Qt.NoModifier)

    paths = utils.drag_drop_paths(event)
    assert len(paths) == 3
    assert '/path/to/image.jpeg' in paths
    assert '/path/to/image.png' in paths
    assert '/path/to/image2.jpg' in paths

    paths = utils.drag_drop_paths(event, pattern='*.jp*g')
    assert len(paths) == 2
    assert '/path/to/image.jpeg' in paths
    assert '/path/to/image2.jpg' in paths

    paths = utils.drag_drop_paths(event, pattern='*.png')
    assert len(paths) == 1
    assert '/path/to/image.png' in paths
Esempio n. 4
0
File: led.py Progetto: MSLNZ/msl-qt
    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)
Esempio n. 5
0
def test_icon_to_base64():
    # reading from a standard Qt icon does not raise any errors
    assert isinstance(
        convert.icon_to_base64(QtWidgets.QStyle.SP_TitleBarMenuButton),
        QtCore.QByteArray)

    # reading from a file does not raise any errors
    icon = convert.to_qicon('gamma.png')
    assert isinstance(convert.icon_to_base64(icon), QtCore.QByteArray)
    assert convert.icon_to_base64(icon).startsWith(b'iVBORw0KGgoAAAA')
    assert convert.icon_to_base64(icon,
                                  fmt='PNG').startsWith(b'iVBORw0KGgoAAAA')
    assert convert.icon_to_base64(icon,
                                  fmt='BMP').startsWith(b'Qk32jgIAAAAAADY')
    assert convert.icon_to_base64(icon,
                                  fmt='JPG').startsWith(b'/9j/4AAQSkZJRgA')
    assert convert.icon_to_base64(icon,
                                  fmt='JPEG').startsWith(b'/9j/4AAQSkZJRgA')

    # GIF is not supported
    with pytest.raises(ValueError):
        convert.icon_to_base64(icon, fmt='GIF')

    # the size of 'gamma.png'
    size = QtCore.QSize(191, 291)

    # convert back to a QIcon and check each pixel
    original = icon.pixmap(size).toImage()
    converted = convert.to_qicon(
        convert.icon_to_base64(icon)).pixmap(size).toImage()
    for x in range(0, size.width()):
        for y in range(0, size.height()):
            rgb_original = original.pixel(x, y)
            rgb_converted = converted.pixel(x, y)
            assert QtGui.QColor(rgb_original).getRgb() == QtGui.QColor(
                rgb_converted).getRgb()
Esempio n. 6
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)
Esempio n. 7
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)
Esempio n. 8
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()
Esempio n. 9
0
def test_to_qfont():

    assert isinstance(convert.to_qfont(), QtGui.QFont)

    font = QtGui.QFont()
    assert convert.to_qfont(font) is not font
    assert convert.to_qfont(font).isCopyOf(font)

    assert convert.to_qfont(100).pointSize() == 100

    assert convert.to_qfont(12.3).pointSize() == 12
    assert convert.to_qfont(12.3).pointSizeF() == 12.3

    assert convert.to_qfont('Papyrus').family() == 'Papyrus'

    f = convert.to_qfont('Ariel')
    assert f.family() == 'Ariel'
    assert f.weight() == QtGui.QFont.Normal
    assert not f.italic()

    f = convert.to_qfont('Ariel', 48)
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Normal
    assert not f.italic()

    f = convert.to_qfont('Ariel', 48, QtGui.QFont.Bold)
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Bold
    assert not f.italic()

    f = convert.to_qfont('Ariel', 48, QtGui.QFont.Bold, True)
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Bold
    assert f.italic()

    # pass in an already-created list as a single argument
    f = convert.to_qfont(['Ariel'])
    assert f.family() == 'Ariel'
    assert f.weight() == QtGui.QFont.Normal
    assert not f.italic()

    f = convert.to_qfont(['Ariel', 48])
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Normal
    assert not f.italic()

    f = convert.to_qfont(['Ariel', 48, QtGui.QFont.Bold])
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Bold
    assert not f.italic()

    f = convert.to_qfont(['Ariel', 48, QtGui.QFont.Bold, True])
    assert f.family() == 'Ariel'
    assert f.pointSize() == 48
    assert f.weight() == QtGui.QFont.Bold
    assert f.italic()

    # the second item in the list will be cast to an integer
    # also test that one can pass in a tuple or a list
    for obj in [('Comic Sans MS', 36), ['Comic Sans MS', 36.6]]:
        f = convert.to_qfont(obj)
        assert f.family() == 'Comic Sans MS'
        assert f.pointSize() == 36
        assert f.weight() == QtGui.QFont.Normal
        assert not f.italic()
        f = convert.to_qfont(*obj)
        assert f.family() == 'Comic Sans MS'
        assert f.pointSize() == 36
        assert f.weight() == QtGui.QFont.Normal
        assert not f.italic()

    # the first item in the list must be a string
    for obj in [None, 1, 23.4, 5j, True, [1]]:
        with pytest.raises(
                TypeError,
                match=r'The first argument\(s\) must be family name\(s\)'):
            convert.to_qfont(obj, 12)
        with pytest.raises(
                TypeError,
                match=r'The first argument\(s\) must be family name\(s\)'):
            convert.to_qfont((obj, 12))

    # the first argument is not a QFont, int, float or string
    for obj in [None, 1 + 6j]:
        with pytest.raises(TypeError, match=r'Cannot create'):
            convert.to_qfont(obj)

    # the second item cannot be cast to an integer
    with pytest.raises(TypeError):
        convert.to_qfont(['Ariel', {}])

    # the third item cannot be cast to an integer
    with pytest.raises(TypeError):
        convert.to_qfont('Ariel', 12, {})