def __init__(self, connection, parent=None): """ A :class:`~QtWidgets.QWidget` for a :class:`~msl.equipment.connection_message_based.ConnectionMessageBased` connection. This widget allows for reading/writing messages from/to equipment. Parameters ---------- connection : :class:`~msl.equipment.connection_message_based.ConnectionMessageBased` The connection to the equipment. parent : :class:`QtWidgets.QWidget` The parent widget. Example ------- To view an example of the :class:`MessageBased` widget that will send messages to a *dummy* :class:`~msl.equipment.record_types.EquipmentRecord` in demo mode, run: >>> from msl.examples.qt.equipment import message_based # doctest: +SKIP >>> message_based.show() # doctest: +SKIP """ super(MessageBased, self).__init__(parent=parent) r = connection.equipment_record self.setWindowTitle('{} || {} || {}'.format(r.manufacturer, r.model, r.serial)) self.setAcceptDrops(True) self._conn = connection self._dropped_commands = [] self._abort_execution = False self._command_list = [] self._header = ['Action', 'Delay', 'Message', 'Reply'] self._actions = ['write', 'read', 'query', 'delay'] self._table = QtWidgets.QTableWidget(0, len(self._header), self) self._table.setHorizontalHeaderLabels(self._header) self._table.horizontalHeader().setStretchLastSection(True) self._table.horizontalHeader().setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self._table.horizontalHeader().customContextMenuRequested.connect( self._show_horizontal_popup_menu) self._table.verticalHeader().setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self._table.verticalHeader().customContextMenuRequested.connect( self._show_vertical_popup_menu) self._timeout_spinbox = QtWidgets.QDoubleSpinBox() self._timeout_spinbox.setToolTip( '<html>The timeout value to use for <i>read</i> commands</html>') self._timeout_spinbox.setRange(0, 999999999) if 'ConnectionPyVISA' in '{}'.format( connection.__class__.__bases__): # a PyVISA connection self._timeout_spinbox.setSuffix(' ms') self._timeout_spinbox.setDecimals(0) else: self._timeout_spinbox.setSuffix(' s') self._timeout_spinbox.setDecimals(2) try: self._timeout_spinbox.setValue(self._conn.timeout) except TypeError: # in case the connection is established in demo mode self._timeout_spinbox.setValue(0) self._timeout_spinbox.valueChanged.connect(self._update_timeout) self._use_rows = QtWidgets.QLineEdit() self._use_rows.setToolTip( 'Enter the rows to execute or leave blank to execute all rows.\nFor example: 1,3,5-8' ) self._execute_icon = get_icon(QtWidgets.QStyle.SP_ArrowRight) self._continuous_icon = get_icon(QtWidgets.QStyle.SP_BrowserReload) self._abort_icon = get_icon(QtWidgets.QStyle.SP_BrowserStop) self._clear_icon = get_icon(QtWidgets.QStyle.SP_DialogResetButton) self._remove_icon = get_icon(QtWidgets.QStyle.SP_DialogCancelButton) self._insert_before_icon = get_icon(QtWidgets.QStyle.SP_DialogOkButton) # create an insert_after_icon by transforming the insert_before_icon size = self._insert_before_icon.availableSizes()[-1] pixmap = self._insert_before_icon.pixmap(size).transformed( QtGui.QTransform().scale(-1, 1)) self._insert_after_icon = QtGui.QIcon(pixmap) self._execute_thread = _Execute(self) self._execute_thread.finished.connect(self._check_if_looping) self._execute_thread.sig_error.connect(self._execute_error) self._execute_thread.sig_update_row_color.connect( self._update_row_appearance) self._execute_thread.sig_highlight_row.connect(self._highlight_row) self._execute_thread.sig_update_reply.connect(self._update_reply) self._execute_thread.sig_show_execute_icon.connect( self._show_execute_icon) self._loop_checkbox = QtWidgets.QCheckBox() self._loop_checkbox.setToolTip('Run continuously?') self._loop_checkbox.clicked.connect(self._show_execute_icon) save_icon = get_icon(QtWidgets.QStyle.SP_DriveFDIcon) self._save_button = QtWidgets.QPushButton(save_icon, 'Save') self._save_button.setToolTip('Save the table to a tab-delimited file') self._save_button.clicked.connect(self._save) self._info_button = QtWidgets.QPushButton( get_icon(QtWidgets.QStyle.SP_FileDialogInfoView), '') self._info_button.setToolTip( 'Display the information about the equipment') self._info_button.clicked.connect( lambda clicked, record=r: show_record(record)) self._status_label = QtWidgets.QLabel() self._execute_button = QtWidgets.QPushButton() self._execute_button.clicked.connect(self._execute_start) self._show_execute_icon() self._status_label.setText('Create a new Execution Table or\n' 'Drag & Drop or Copy & Paste\n' 'a previous Execution Table') execute_widget = QtWidgets.QWidget() grid = QtWidgets.QGridLayout() grid.addWidget(QtWidgets.QLabel('Timeout'), 1, 0, alignment=QtCore.Qt.AlignRight) grid.addWidget(self._timeout_spinbox, 1, 1, 1, 2) grid.addWidget(QtWidgets.QLabel('Rows'), 2, 0, alignment=QtCore.Qt.AlignRight) grid.addWidget(self._use_rows, 2, 1, 1, 2) grid.addWidget(self._execute_button, 3, 0, 1, 2) grid.addWidget(self._loop_checkbox, 3, 2, 1, 1, alignment=QtCore.Qt.AlignLeft) grid.addWidget(self._save_button, 4, 0, 1, 2) grid.addWidget(self._info_button, 4, 2, 1, 1) grid.addWidget(self._status_label, 5, 0, 1, 3, alignment=QtCore.Qt.AlignBottom) grid.setRowStretch(5, 1) execute_widget.setLayout(grid) self._create_row() self._table.resizeColumnsToContents() splitter = QtWidgets.QSplitter() splitter.addWidget(self._table) splitter.addWidget(execute_widget) splitter.setStretchFactor(0, 1) splitter.setChildrenCollapsible(False) splitter.setSizes([1, 0]) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(splitter) self.setLayout(hbox)
def __init__(self, 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°]</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()