class BasicWaveformWidget(BasicValueWidget): """ Generic PyDM Widget for testing plugins. Contains a basic waveform Channel. """ __pyqtSignals__ = ( "send_waveform_signal(np.ndarray)", "waveform_updated_signal()", ) send_waveform_signal = pyqtSignal(np.ndarray) waveform_updated_signal = pyqtSignal() def __init__(self, channel=None, parent=None): """ Initialize channel and parent. Start value at "None". :param channel: string channel address to use :type channel: str :param parent: parent QWidget :type parent: QWidget or None """ super(BasicWaveformWidget, self).__init__(channel=channel, parent=parent) @pyqtSlot(np.ndarray) def recv_waveform(self, waveform): """ Recieve waveform from plugin signal. Store in self.value. :param waveform: waveform :type waveform: np.ndarray """ self.value = waveform self.waveform_updated_signal.emit() @pyqtSlot(np.ndarray) def send_waveform(self, waveform): """ Send desired waveform to plugin slot. :param value: waveform to send :type value: np.ndarray """ self.send_waveform_signal.emit(waveform) def channels(self): """ Return list of channels, in this case just a basic waveform channel. :rtyp: list of :class:Channel """ return [ Channel(address=self.channel, waveform_slot=self.recv_waveform, waveform_signal=self.send_waveform_signal) ]
class LogWriter(QObject): """ QObject to do the writing """ terminator = '\n' write_trigger = pyqtSignal(str) def __init__(self, text_widget): super().__init__(parent=text_widget) self.text_widget = text_widget self.write_trigger.connect(self.write_log) def do_write(self, all_msg): self.write_trigger.emit(all_msg) @pyqtSlot(str) def write_log(self, all_msg): if self.text_widget is not None: split_msg = all_msg.split(self.terminator) for msg in reversed(split_msg): cursor = self.text_widget.cursorForPosition(QPoint(0, 0)) cursor.insertText(msg + self.terminator) def log_close(self): self.text_widget = None self.write_trigger.disconnect(self.write_log)
class WriteOnlyParameter(QThread): new_data_signal = pyqtSignal(object) new_severity_signal = pyqtSignal(int) writable = True def __init__(self, connection, client, parameter, *, poll_rate=None): super().__init__() self.connection = connection self.client = client self.parameter = parameter self.to_write = [] def run(self): try: loop = self.client.loop while self.to_write: comm = self.client.comm if comm is None: logger.warning('Not writing %s; disconnected', self.parameter) self.to_write.clear() break with self.client.lock: value = self.to_write.pop(0) cmd = self.parameter.format(value=value) coro = comm.write_read(cmd) try: result = loop.run_until_complete(coro) except Exception as ex: logger.exception('Write failed: %r', cmd) self.new_severity_signal.emit(Severity.MAJOR) self.to_write.clear() break else: logger.info('Write result: %r => %s', cmd, result) self.new_severity_signal.emit(Severity.NO_ALARM) except Exception: logger.exception('Write failed!') def write(self, value): logger.debug('%s write: %s', self.parameter, value) self.to_write.append(value) self.start()
class PostInit(QObject): """ Catch the visibility event for one last sequence of functions after pydm is fully initialized, which is later than we can do things inside __init__. """ post_init = pyqtSignal() do_it = True def eventFilter(self, obj, event): if self.do_it and event.type() == QEvent.WindowActivate: self.do_it = False self.post_init.emit() return True return False
class GuiHandler(QObject, logging.Handler): """ Handler for PyDM Applications A composite of a QObject and a logging handler. This can be added to a ``logging.Logger`` object just like any standard ``logging.Handler`` and will emit logging messages as pyqtSignals .. code:: python # Create a log and GuiHandler logger = logging.getLogger() ui_handler = GuiHandler(level=logging.INFO) # Attach our handler to the log logger.addHandler(ui_handler) # Publish log message via pyqtSignal ui_handler.message.connect(mySlot) Parameters ---------- level: int Level of Handler parent: QObject, optional """ message = pyqtSignal(str) def __init__(self, level=logging.NOTSET, parent=None): logging.Handler.__init__(self, level=level) QObject.__init__(self) # Set the parent widget self.setParent(parent) def emit(self, record): """Emit formatted log messages when received but only if level is set.""" # Avoid garbage to be presented when master log is running with DEBUG. if self.level == logging.NOTSET: return self.message.emit(self.format(record))
class QueueSlots(QObject): """ Dummy object that contains a slot that puts signal results to a queue. Exists for the implementation of PyDMTest.signal_wait. """ __pyqtSignals__ = ("got_value()",) got_value = pyqtSignal() def __init__(self, parent=None): """ Set up QObject and create internal queue. """ super(QueueSlots, self).__init__(parent=parent) self.queue = Queue.Queue() def get(self, timeout=None): """ Retrieve queue value or None. Waits for timeout seconds. :param timeout: get timeout in seconds :type timeout: float or int :rtyp: object or None """ try: return self.queue.get(timeout) except Queue.Empty: return @pyqtSlot() @pyqtSlot(object) def put_to_queue(self, value=None): """ Put incoming value into the queue and signal that we recieved data. :param value: Incoming value :type value: object or None """ self.queue.put(value) self.got_value.emit()
class ValueWidget(BasicValueWidget): """ Generic PyDM Widget for testing plugins. Contains a more thorough value Channel. """ __pyqtSignals__ = ( "send_value_signal([int], [float], [str])", "value_updated_signal()", "conn_updated_signal()", "sevr_updated_signal()", "rwacc_updated_signal()", "enums_updated_signal()", "units_updated_signal()", "prec_updated_signal()", ) conn_updated_signal = pyqtSignal() sevr_updated_signal = pyqtSignal() rwacc_updated_signal = pyqtSignal() enums_updated_signal = pyqtSignal() units_updated_signal = pyqtSignal() prec_updated_signal = pyqtSignal() def __init__(self, channel=None, parent=None): """ Initialize channel and parent. Start all fields as None. :param channel: string channel address to use :type channel: str :param parent: parent QWidget :type parent: QWidget or None """ super(ValueWidget, self).__init__(channel=channel, parent=parent) self.conn = None self.sevr = None self.rwacc = None self.enums = None self.units = None self.prec = None @pyqtSlot(bool) def recv_conn(self, conn): """ Get connection state signal :param conn: connection state :type conn: bool """ self.conn = conn self.conn_updated_signal.emit() @pyqtSlot(int) def recv_sevr(self, sevr): """ Get alarm state signal :param sevr: alarm state :type sevr: int """ self.sevr = sevr self.sevr_updated_signal.emit() @pyqtSlot(bool) def recv_rwacc(self, rwacc): """ Get write access signal :param rwacc: write access state :type rwacc: bool """ self.rwacc = rwacc self.rwacc_updated_signal.emit() @pyqtSlot(tuple) def recv_enums(self, enums): """ Get enum strings signal :param enums: enum strings :type enums: tuple of str """ self.enums = enums self.enums_updated_signal.emit() @pyqtSlot(str) def recv_units(self, units): """ Get units signal :param units: engineering units :type units: str """ self.units = units self.units_updated_signal.emit() @pyqtSlot(int) def recv_prec(self, prec): """ Get precision signal :param prec: data precision :type prec: int """ self.prec = prec self.prec_updated_signal.emit() def channels(self): """ Return list of channels, in this case one channel with every field filled except for the waveform fields. :rtyp: list of :class:Channel """ return [ Channel(address=self.channel, value_slot=self.recv_value, value_signal=self.send_value_signal, connection_slot=self.recv_conn, severity_slot=self.recv_sevr, write_access_slot=self.recv_rwacc, enum_strings_slot=self.recv_enums, unit_slot=self.recv_units, prec_slot=self.recv_prec) ]
class WaveformWidget(ValueWidget): """ Generic PyDM Widget for testing plugins. Contains a more thorough waveform Channel. Lots of copy/paste because pyqt multiple inheritance is broken. """ __pyqtSignals__ = ( "send_waveform_signal(np.ndarray)", "waveform_updated_signal()", "conn_updated_signal()", "sevr_updated_signal()", "rwacc_updated_signal()", "enums_updated_signal()", "units_updated_signal()", "prec_updated_signal()", ) send_waveform_signal = pyqtSignal(np.ndarray) waveform_updated_signal = pyqtSignal() def __init__(self, channel=None, parent=None): super(WaveformWidget, self).__init__(channel=channel, parent=parent) @pyqtSlot(np.ndarray) def recv_waveform(self, waveform): """ Recieve waveform from plugin signal. Store in self.value. :param waveform: waveform :type waveform: np.ndarray """ self.value = waveform self.waveform_updated_signal.emit() @pyqtSlot(np.ndarray) def send_waveform(self, waveform): """ Send desired waveform to plugin slot. :param value: waveform to send :type value: np.ndarray """ self.send_waveform_signal.emit(waveform) def channels(self): """ Return list of channels, in this case one channel with every field filled except for the value fields. :rtyp: list of :class:Channel """ return [ Channel(address=self.channel, waveform_slot=self.recv_waveform, waveform_signal=self.send_waveform_signal, connection_slot=self.recv_conn, severity_slot=self.recv_sevr, write_access_slot=self.recv_rwacc, enum_strings_slot=self.recv_enums, unit_slot=self.recv_units, prec_slot=self.recv_prec) ]
class BasicValueWidget(QWidget): """ Generic PyDM Widget for testing plugins. Contains a basic value Channel. """ __pyqtSignals__ = ( "send_value_signal([int], [float], [str])", "value_updated_signal()", ) send_value_signal = pyqtSignal([int], [float], [str]) value_updated_signal = pyqtSignal() def __init__(self, channel=None, parent=None): """ Initialize channel and parent. Start value at "None". :param channel: string channel address to use :type channel: str :param parent: parent QWidget :type parent: QWidget or None """ super(BasicValueWidget, self).__init__(parent=parent) self.channel = channel self.value = None @pyqtSlot(int) @pyqtSlot(float) @pyqtSlot(str) def recv_value(self, value): """ Recieve value from plugin signal. Store in self.value. :param value: value to store :type value: int, float, or str """ self.value = value self.value_updated_signal.emit() @pyqtSlot(int) @pyqtSlot(float) @pyqtSlot(str) def send_value(self, value): """ Send desired value to plugin slot. :param value: value to send :type value: int, float, or str """ self.send_value_signal[type(value)].emit(value) def channels(self): """ Return list of channels, in this case just a basic value channel. :rtyp: list of :class:Channel """ return [ Channel(address=self.channel, value_slot=self.recv_value, value_signal=self.send_value_signal) ]
class CamViewer(Display): #Emitted when the user changes the value. roi_x_signal = pyqtSignal(str) roi_y_signal = pyqtSignal(str) roi_w_signal = pyqtSignal(str) roi_h_signal = pyqtSignal(str) def __init__(self, display_manager_window): super(CamViewer, self).__init__(display_manager_window) #Set up the list of cameras, and all the PVs vcc_dict = { "image": "ca://CAMR:IN20:186:IMAGE", "max_width": "ca://CAMR:IN20:186:N_OF_COL", "max_height": "ca://CAMR:IN20:186:N_OF_ROW", "roi_x": None, "roi_y": None, "roi_width": None, "roi_height": None } c_iris_dict = { "image": "ca://CAMR:LR20:119:Image:ArrayData", "max_width": "ca://CAMR:LR20:119:MaxSizeX_RBV", "max_height": "ca://CAMR:LR20:119:MaxSizeY_RBV", "roi_x": "ca://CAMR:LR20:119:MinX", "roi_y": "ca://CAMR:LR20:119:MinY", "roi_width": "ca://CAMR:LR20:119:SizeX", "roi_height": "ca://CAMR:LR20:119:SizeY" } test_dict = { "image": "ca://MTEST:Image", "max_width": "ca://MTEST:ImageWidth", "max_height": "ca://MTEST:ImageWidth", "roi_x": None, "roi_y": None, "roi_width": None, "roi_height": None } self.cameras = { "VCC": vcc_dict, "C-Iris": c_iris_dict, "Test": test_dict } self._channels = [] #Populate the camera combo box self.ui.cameraComboBox.clear() for camera in self.cameras: self.ui.cameraComboBox.addItem(camera) #Clear out any image data, reset width, get PVs ready for connection self.initializeCamera(self.ui.cameraComboBox.currentText()) #When the camera combo box changes, disconnect from PVs, re-initialize, then reconnect. self.ui.cameraComboBox.activated[str].connect(self.cameraChanged) #Set up the color map combo box. self.ui.colorMapComboBox.clear() for map_name in self.ui.imageView.color_maps: self.ui.colorMapComboBox.addItem(map_name) self.ui.imageView.setColorMapToPreset( self.ui.colorMapComboBox.currentText()) self.ui.colorMapComboBox.activated[str].connect(self.colorMapChanged) #Set up the color map limit sliders and line edits. #self._color_map_limit_sliders_need_config = True self.ui.colorMapMinSlider.valueChanged.connect(self.setColorMapMin) self.ui.colorMapMaxSlider.valueChanged.connect(self.setColorMapMax) self.ui.colorMapMinLineEdit.returnPressed.connect( self.colorMapMinLineEditChanged) self.ui.colorMapMaxLineEdit.returnPressed.connect( self.colorMapMaxLineEditChanged) #Set up the stuff for single-shot and average modes. self.ui.singleShotRadioButton.setChecked(True) self._average_mode_enabled = False self.ui.singleShotRadioButton.clicked.connect( self.enableSingleShotMode) self.ui.averageRadioButton.clicked.connect(self.enableAverageMode) self.ui.numShotsLineEdit.returnPressed.connect(self.numAverageChanged) #Add a plot for vertical lineouts self.yLineoutPlot = PlotWidget() self.yLineoutPlot.setMaximumWidth(80) self.yLineoutPlot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) self.yLineoutPlot.getPlotItem().invertY() self.yLineoutPlot.hideAxis('bottom') #self.yLineoutPlot.setYLink(self.ui.imageView.getView()) self.ui.imageGridLayout.addWidget(self.yLineoutPlot, 0, 0) self.yLineoutPlot.hide() #We do some mangling of the .ui file here and move the imageView over a cell, kind of ugly. self.ui.imageGridLayout.removeWidget(self.ui.imageView) self.ui.imageGridLayout.addWidget(self.ui.imageView, 0, 1) #Add a plot for the horizontal lineouts self.xLineoutPlot = PlotWidget() self.xLineoutPlot.setMaximumHeight(80) self.xLineoutPlot.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.xLineoutPlot.hideAxis('left') #self.xLineoutPlot.setXLink(self.ui.imageView.getView()) self.ui.imageGridLayout.addWidget(self.xLineoutPlot, 1, 1) self.xLineoutPlot.hide() #Update the lineout plot ranges when the image gets panned or zoomed self.ui.imageView.getView().sigRangeChanged.connect( self.updateLineoutRange) #Instantiate markers. self.marker_dict = {1: {}, 2: {}, 3: {}, 4: {}} marker_size = QPointF(20., 20.) self.marker_dict[1]['marker'] = ImageMarker((0, 0), size=marker_size, pen=mkPen((100, 100, 255), width=3)) self.marker_dict[1]['button'] = self.ui.marker1Button self.marker_dict[1]['xlineedit'] = self.ui.marker1XPosLineEdit self.marker_dict[1]['ylineedit'] = self.ui.marker1YPosLineEdit self.marker_dict[2]['marker'] = ImageMarker((0, 0), size=marker_size, pen=mkPen((255, 100, 100), width=3)) self.marker_dict[2]['button'] = self.ui.marker2Button self.marker_dict[2]['xlineedit'] = self.ui.marker2XPosLineEdit self.marker_dict[2]['ylineedit'] = self.ui.marker2YPosLineEdit self.marker_dict[3]['marker'] = ImageMarker((0, 0), size=marker_size, pen=mkPen((60, 255, 60), width=3)) self.marker_dict[3]['button'] = self.ui.marker3Button self.marker_dict[3]['xlineedit'] = self.ui.marker3XPosLineEdit self.marker_dict[3]['ylineedit'] = self.ui.marker3YPosLineEdit self.marker_dict[4]['marker'] = ImageMarker((0, 0), size=marker_size, pen=mkPen((255, 60, 255), width=3)) self.marker_dict[4]['button'] = self.ui.marker4Button self.marker_dict[4]['xlineedit'] = self.ui.marker4XPosLineEdit self.marker_dict[4]['ylineedit'] = self.ui.marker4YPosLineEdit #Disable auto-ranging the image (it feels strange when the zoom changes as you move markers around.) self.ui.imageView.getView().disableAutoRange() for d in self.marker_dict: marker = self.marker_dict[d]['marker'] marker.setZValue(20) marker.hide() marker.sigRegionChanged.connect(self.markerMoved) self.ui.imageView.getView().addItem(marker) self.marker_dict[d]['button'].toggled.connect(self.enableMarker) curvepen = QPen(marker.pen) curvepen.setWidth(1) self.marker_dict[d]['xcurve'] = self.xLineoutPlot.plot( pen=curvepen) self.marker_dict[d]['ycurve'] = self.yLineoutPlot.plot( pen=curvepen) self.marker_dict[d]['xlineedit'].returnPressed.connect( self.markerPositionLineEditChanged) self.marker_dict[d]['ylineedit'].returnPressed.connect( self.markerPositionLineEditChanged) #Set up zoom buttons self.ui.zoomInButton.clicked.connect(self.zoomIn) self.ui.zoomOutButton.clicked.connect(self.zoomOut) self.ui.zoomToActualSizeButton.clicked.connect(self.zoomToActualSize) #Set up ROI buttons self.ui.setROIButton.clicked.connect(self.setROI) self.ui.resetROIButton.clicked.connect(self.resetROI) @pyqtSlot() def zoomIn(self): self.ui.imageView.getView().scaleBy((0.5, 0.5)) @pyqtSlot() def zoomOut(self): self.ui.imageView.getView().scaleBy((2.0, 2.0)) @pyqtSlot() def zoomToActualSize(self): if len(self.image_data) == 0: return self.ui.imageView.getView().setRange(xRange=(0, self.image_data.shape[0]), yRange=(0, self.image_data.shape[1]), padding=0.0) def disable_all_markers(self): for d in self.marker_dict: self.marker_dict[d]['button'].setChecked(False) self.marker_dict[d]['marker'].setPos((0, 0)) @pyqtSlot(bool) def enableMarker(self, checked): any_markers_visible = False for d in self.marker_dict: marker = self.marker_dict[d]['marker'] button = self.marker_dict[d]['button'] any_markers_visible = any_markers_visible or button.isChecked() marker.setVisible(button.isChecked()) self.markerMoved(d) self.marker_dict[d]['xcurve'].setVisible(button.isChecked()) self.marker_dict[d]['ycurve'].setVisible(button.isChecked()) self.marker_dict[d]['xlineedit'].setEnabled(button.isChecked()) self.marker_dict[d]['ylineedit'].setEnabled(button.isChecked()) if any_markers_visible: self.xLineoutPlot.show() self.yLineoutPlot.show() else: self.xLineoutPlot.hide() self.yLineoutPlot.hide() @pyqtSlot() def markerPositionLineEditChanged(self): for d in self.marker_dict: marker = self.marker_dict[d]['marker'] x_line_edit = self.marker_dict[d]['xlineedit'] y_line_edit = self.marker_dict[d]['ylineedit'] try: new_x = int(x_line_edit.text()) new_y = int(y_line_edit.text()) if new_x <= marker.maxBounds.width( ) and new_y <= marker.maxBounds.height(): marker.setPos((new_x, new_y)) return except: pass coords = marker.getPixelCoords() x_line_edit.setText(str(coords[0])) y_line_edit.setText(str(coords[1])) @pyqtSlot(object) def markerMoved(self, marker): self.updateLineouts() for marker_index in self.marker_dict: marker = self.marker_dict[marker_index]['marker'] x_line_edit = self.marker_dict[marker_index]['xlineedit'] y_line_edit = self.marker_dict[marker_index]['ylineedit'] coords = marker.getPixelCoords() x_line_edit.setText(str(coords[0])) y_line_edit.setText(str(coords[1])) @pyqtSlot(object, object) def updateLineoutRange(self, view, new_ranges): self.ui.xLineoutPlot.setRange(xRange=new_ranges[0], padding=0.0) self.ui.yLineoutPlot.setRange(yRange=new_ranges[1], padding=0.0) def updateLineouts(self): for marker_index in self.marker_dict: marker = self.marker_dict[marker_index]['marker'] xcurve = self.marker_dict[marker_index]['xcurve'] ycurve = self.marker_dict[marker_index]['ycurve'] if marker.isVisible(): result, coords = marker.getArrayRegion( self.image_data, self.ui.imageView.getImageItem()) xcurve.setData(y=result[0], x=np.arange(len(result[0]))) ycurve.setData(y=np.arange(len(result[1])), x=result[1]) @pyqtSlot() def enableSingleShotMode(self): self._average_mode_enabled = False self._average_buffer = np.ndarray(0) @pyqtSlot() def enableAverageMode(self): self._average_mode_enabled = True @pyqtSlot(str) def cameraChanged(self, new_camera): new_camera = str(new_camera) if self.imageChannel == self.cameras[new_camera]["image"]: return self.display_manager_window.close_widget_connections(self) self.disable_all_markers() self.initializeCamera(new_camera) self.display_manager_window.establish_widget_connections(self) def initializeCamera(self, new_camera): new_camera = str(new_camera) self._color_map_limit_sliders_need_config = True self.times = np.zeros(10) self.old_timestamp = 0 self.image_width = 0 #current width (width of ROI) self.image_max_width = 0 #full width. Only used to reset ROI to full. self.image_max_height = 0 #full height. Only used to reset ROI to full. self.image_data = np.zeros(0) self._average_counter = 0 self._average_buffer = np.ndarray(0) self._needs_auto_range = True self.imageChannel = self.cameras[new_camera]["image"] self.widthChannel = self.cameras[new_camera][ "roi_width"] or self.cameras[new_camera]["max_width"] self.maxWidthChannel = self.cameras[new_camera]["max_width"] self.maxHeightChannel = self.cameras[new_camera]["max_height"] self.roiXChannel = self.cameras[new_camera]["roi_x"] self.roiYChannel = self.cameras[new_camera]["roi_y"] self.roiWidthChannel = self.cameras[new_camera]["roi_width"] self.roiHeightChannel = self.cameras[new_camera]["roi_height"] self._channels = [ PyDMChannel(address=self.imageChannel, connection_slot=self.connectionStateChanged, waveform_slot=self.receiveImageWaveform, severity_slot=self.alarmSeverityChanged), PyDMChannel(address=self.widthChannel, value_slot=self.receiveImageWidth), PyDMChannel(address=self.maxWidthChannel, value_slot=self.receiveMaxWidth), PyDMChannel(address=self.maxHeightChannel, value_slot=self.receiveMaxHeight) ] if self.roiXChannel and self.roiYChannel and self.roiWidthChannel and self.roiHeightChannel: self._channels.extend([ PyDMChannel(address=self.roiXChannel, value_slot=self.receiveRoiX, value_signal=self.roi_x_signal, write_access_slot=self.roiWriteAccessChanged), PyDMChannel(address=self.roiYChannel, value_slot=self.receiveRoiY, value_signal=self.roi_y_signal), PyDMChannel(address=self.roiWidthChannel, value_slot=self.receiveRoiWidth, value_signal=self.roi_w_signal), PyDMChannel(address=self.roiHeightChannel, value_slot=self.receiveRoiHeight, value_signal=self.roi_h_signal) ]) self.ui.roiXLineEdit.setEnabled(True) self.ui.roiYLineEdit.setEnabled(True) self.ui.roiWLineEdit.setEnabled(True) self.ui.roiHLineEdit.setEnabled(True) else: self.ui.roiXLineEdit.clear() self.ui.roiXLineEdit.setEnabled(False) self.ui.roiYLineEdit.clear() self.ui.roiYLineEdit.setEnabled(False) self.ui.roiWLineEdit.clear() self.ui.roiWLineEdit.setEnabled(False) self.ui.roiHLineEdit.clear() self.ui.roiHLineEdit.setEnabled(False) @pyqtSlot() def setROI(self): self.roi_x_signal.emit(self.ui.roiXLineEdit.text()) self.roi_y_signal.emit(self.ui.roiYLineEdit.text()) self.roi_w_signal.emit(self.ui.roiWLineEdit.text()) self.roi_h_signal.emit(self.ui.roiHLineEdit.text()) @pyqtSlot() def resetROI(self): self.roi_x_signal.emit(str(0)) self.roi_y_signal.emit(str(0)) self.roi_w_signal.emit(str(self.image_max_width)) self.roi_h_signal.emit(str(self.image_max_height)) @pyqtSlot(str) def colorMapChanged(self, new_map_name): self.ui.imageView.setColorMapToPreset(new_map_name) def configureColorMapLimitSliders(self, max_int): self.ui.colorMapMinSlider.setMaximum(max_int) self.ui.colorMapMaxSlider.setMaximum(max_int) self.ui.colorMapMaxSlider.setValue(max_int) self.ui.colorMapMinSlider.setValue(0) self.setColorMapMin(0) self.setColorMapMax(max_int) self._color_map_limit_sliders_need_config = False @pyqtSlot() def colorMapMinLineEditChanged(self): try: new_min = int(self.ui.colorMapMinLineEdit.text()) except: self.ui.colorMapMinLineEdit.setText( str(self.ui.colorMapMinSlider.value())) return if new_min < 0: new_min = 0 if new_min > self.ui.colorMapMinSlider.maximum(): new_min = self.ui.colorMapMinSlider.maximum() self.ui.colorMapMinSlider.setValue(new_min) @pyqtSlot(int) def setColorMapMin(self, new_min): if new_min > self.ui.colorMapMaxSlider.value(): self.ui.colorMapMaxSlider.setValue(new_min) self.ui.colorMapMaxLineEdit.setText(str(new_min)) self.ui.colorMapMinLineEdit.setText(str(new_min)) self.ui.imageView.setColorMapLimits(new_min, self.ui.colorMapMaxSlider.value()) @pyqtSlot() def colorMapMaxLineEditChanged(self): try: new_max = int(self.ui.colorMapMaxLineEdit.text()) except: self.ui.colorMapMaxLineEdit.setText( str(self.ui.colorMapMaxSlider.value())) return if new_max < 0: new_max = 0 if new_max > self.ui.colorMapMaxSlider.maximum(): new_max = self.ui.colorMapMaxSlider.maximum() self.ui.colorMapMaxSlider.setValue(new_max) @pyqtSlot(int) def setColorMapMax(self, new_max): if new_max < self.ui.colorMapMinSlider.value(): self.ui.colorMapMinSlider.setValue(new_max) self.ui.colorMapMinLineEdit.setText(str(new_max)) self.ui.colorMapMaxLineEdit.setText(str(new_max)) self.ui.imageView.setColorMapLimits(self.ui.colorMapMinSlider.value(), new_max) def createAverageBuffer(self, size, type, initial_val=[]): num_shots = 1 try: num_shots = int(self.ui.numShotsLineEdit.text()) except: self.ui.numShotsLineEdit.setText(str(num_shots)) if num_shots < 1: num_shots = 1 self.ui.numShotsLineEdit.setText(str(num_shots)) if num_shots > 200: num_shots = 200 self.ui.numShotsLineEdit.setText(str(num_shots)) if len(initial_val) > 0: return np.full((num_shots, size), initial_val, dtype=type) else: return np.zeros(shape=(num_shots, size), dtype=type) @pyqtSlot() def numAverageChanged(self): self._average_buffer = np.zeros(0) @pyqtSlot(np.ndarray) def receiveImageWaveform(self, new_waveform): if not self.image_width: return #Calculate the average rate new_timestamp = time.time() if not (self.old_timestamp == 0): delta = new_timestamp - self.old_timestamp self.times = np.roll(self.times, 1) self.times[0] = delta avg_delta = np.mean(self.times) self.ui.dataRateLabel.setText("{:.1f} Hz".format( (1.0 / avg_delta))) self.ui.displayRateLabel.setText("{:.1f} Hz".format( (1.0 / avg_delta))) self.old_timestamp = new_timestamp #If this is the first image, set up the color map slider limits if self._color_map_limit_sliders_need_config: max_int = np.iinfo(new_waveform.dtype).max self.configureColorMapLimitSliders(max_int) #If we are in average mode, add this image to the circular averaging buffer, otherwise just display it. if self._average_mode_enabled: if len(self._average_buffer) == 0: self._average_buffer = self.createAverageBuffer( len(new_waveform), new_waveform.dtype, new_waveform) self._average_counter = 0 self._average_counter = (self._average_counter + 1) % len( self._average_buffer) #self._average_buffer = np.roll(self._average_buffer, 1, axis=0) self._average_buffer[self._average_counter] = new_waveform mean = np.mean(self._average_buffer, axis=0).astype(new_waveform.dtype) self.image_data = mean.reshape((int(self.image_width), -1), order='F') else: self.image_data = new_waveform.reshape((int(self.image_width), -1), order='F') self.setMarkerBounds() self.updateLineouts() self.ui.imageView.receiveImageWaveform(self.image_data) self.calculateStats() if self._needs_auto_range: self.ui.imageView.getView().autoRange(padding=0.0) current_range = self.ui.imageView.getView().viewRange() self._needs_auto_range = False def calculateStats(self): # Full image stats mean = np.mean(self.image_data) std = np.std(self.image_data) width = self.image_data.shape[0] height = self.image_data.shape[1] min_val = np.min(self.image_data) max_val = np.max(self.image_data) self.ui.imageStatsLabel.setText( "Mean: {0:.2f}, Std: {1:.2f}, Min: {2}, Max: {3}, Width: {4}, Height: {5}" .format(mean, std, min_val, max_val, width, height)) # Current view stats current_range = self.ui.imageView.getView().viewRange() view_x_min = int(max(0, current_range[0][0])) view_x_max = int(min(self.image_data.shape[0], current_range[0][1])) view_y_min = int(max(0, current_range[1][0])) view_y_max = int(min(self.image_data.shape[1], current_range[1][1])) view_slice = self.image_data[view_x_min:view_x_max, view_y_min:view_y_max] mean = np.mean(view_slice) std = np.std(view_slice) width = view_slice.shape[0] height = view_slice.shape[1] min_val = np.min(view_slice) max_val = np.max(view_slice) self.ui.viewStatsLabel.setText( "Mean: {0:.2f}, Std: {1:.2f}, Min: {2}, Max: {3}, Width: {4}, Height: {5}" .format(mean, std, min_val, max_val, width, height)) def setMarkerBounds(self): for marker_index in self.marker_dict: marker = self.marker_dict[marker_index]['marker'] marker.maxBounds = QRectF( 0, 0, self.image_data.shape[0] + marker.size()[0] - 1, self.image_data.shape[1] + marker.size()[1] - 1) @pyqtSlot(int) def receiveImageWidth(self, new_width): self.image_width = new_width self.ui.imageView.receiveImageWidth(self.image_width) @pyqtSlot(int) def receiveMaxWidth(self, new_max_width): self.image_max_width = new_max_width @pyqtSlot(int) def receiveMaxHeight(self, new_max_height): self.image_max_height = new_max_height @pyqtSlot(int) def receiveRoiX(self, new_roi_x): self.ui.roiXLineEdit.setText(str(new_roi_x)) @pyqtSlot(int) def receiveRoiY(self, new_roi_y): self.ui.roiYLineEdit.setText(str(new_roi_y)) @pyqtSlot(int) def receiveRoiWidth(self, new_roi_w): self.ui.roiWLineEdit.setText(str(new_roi_w)) @pyqtSlot(int) def receiveRoiHeight(self, new_roi_h): self.ui.roiHLineEdit.setText(str(new_roi_h)) # -2 to +2, -2 is LOLO, -1 is LOW, 0 is OK, etc. @pyqtSlot(int) def alarmStatusChanged(self, new_alarm_state): pass #0 = NO_ALARM, 1 = MINOR, 2 = MAJOR, 3 = INVALID @pyqtSlot(int) def alarmSeverityChanged(self, new_alarm_severity): pass @pyqtSlot(bool) def roiWriteAccessChanged(self, can_write_roi): self.ui.setROIButton.setEnabled(can_write_roi) self.ui.resetROIButton.setEnabled(can_write_roi) self.ui.roiXLineEdit.setReadOnly(not can_write_roi) self.ui.roiYLineEdit.setReadOnly(not can_write_roi) self.ui.roiWLineEdit.setReadOnly(not can_write_roi) self.ui.roiHLineEdit.setReadOnly(not can_write_roi) #false = disconnected, true = connected @pyqtSlot(bool) def connectionStateChanged(self, connected): self.ui.connectedLabel.setText({True: "Yes", False: "No"}[connected]) def ui_filename(self): return 'camviewer.ui' def ui_filepath(self): return path.join(path.dirname(path.realpath(__file__)), self.ui_filename()) def channels(self): return self._channels
class DataThread(QThread): new_data_signal = pyqtSignal([float], [int], [str]) def __init__(self, ip, port, slave_id, designator, addr, length, bit, poll_interval=0.1): super(QThread, self).__init__() self.ip = ip self.port = port self.slave_id = slave_id self.designator = designator self.addr = addr self.length = length self.bit = bit self.poll_interval = poll_interval self.designator_map = { 'HR': { 'read': self.read_hr, 'write': self.write_hr } } self.server = ModbusServer(self.ip, self.port) try: self.read_data = self.designator_map[self.designator]['read'] self.write_data = self.designator_map[self.designator]['write'] except IndexError: self.read_data = lambda *a, **k: None self.write_data = lambda *a, **k: None def run(self): while not self.isInterruptionRequested(): data = self.read_data() if data is not None: self.new_data_signal.emit(data) self.msleep(int(self.poll_interval * 1000)) def write(self, new_value): print("Write!") self.write_data(new_value) def read_hr(self): message = tcp.read_holding_registers(slave_id=self.slave_id, starting_address=self.addr, quantity=1) response = self.server.send_message(message) if response is not None: if self.bit is None: return response[0] else: def get_bit(decimal, N): mask = 1 << N if decimal & mask: return 1 else: return 0 return (get_bit(response[0], self.bit)) else: return None def write_hr(self, new_value): if self.bit is not None: message = tcp.read_holding_registers(slave_id=self.slave_id, starting_address=self.addr, quantity=1) response = self.server.send_message(message) current_value = response[0] if new_value: new_value = current_value | 1 << self.bit else: new_value = current_value & ~(1 << self.bit) message = tcp.write_multiple_registers(slave_id=self.slave_id, starting_address=self.addr, values=[int(new_value)]) response = self.server.send_message(message) return response
class PollThread(QThread): new_data_signal = pyqtSignal(object) new_severity_signal = pyqtSignal(int) writable = False def __init__(self, connection, client, parameter, *, poll_rate=0.1): super().__init__() self.connection = connection self.poll_rate_ms = int(poll_rate * 1000) self.client = client self.parameter = parameter self.start() def run(self): loop = self.client.loop failed_rate = self.poll_rate_ms * 2 while not self.isInterruptionRequested(): if self.client.comm is None: self.new_severity_signal.emit(Severity.INVALID) self.msleep(failed_rate) continue with self.client.lock: sleep_ms = failed_rate comm = self.client.comm if comm is None: self.new_severity_signal.emit(Severity.INVALID) else: try: data = self.query(comm, loop) if asyncio.iscoroutine(data): data = loop.run_until_complete(data) except (BrokenPipeError, ConnectionResetError) as ex: logger.error('Poll failed; connection lost') self.client.comm = None sleep_ms = failed_rate * 2 self.new_severity_signal.emit(Severity.INVALID) except aerotech.FailureResponseException as ex: logger.warning('Query failed: %s', self.parameter) self.new_severity_signal.emit(Severity.MAJOR) except aerotech.FaultResponseException as ex: logger.warning('Query fault: %s', self.parameter) self.new_severity_signal.emit(Severity.MAJOR) except aerotech.TimeoutResponseException as ex: logger.warning('Query timed out: %s', self.parameter) self.new_severity_signal.emit(Severity.MINOR) except Exception as ex: logger.exception('Poll failed') self.new_severity_signal.emit(Severity.MAJOR) else: sleep_ms = self.poll_rate_ms self.new_severity_signal.emit(Severity.NO_ALARM) data = self.fix_value(data) logger.debug('%s => %s', self.parameter, data) self.new_data_signal.emit(data) self.msleep(sleep_ms) def fix_value(self, value): try: return ast.literal_eval(value) except Exception: return value def query(self, comm, loop): # Return a coroutine that gets waited on above return comm.write_read(self.parameter)
class DataThread(QThread): new_data_signal = pyqtSignal([float], [int], [str]) def __init__(self, ip, port, module, addr, poll_interval=0.1): super(QThread, self).__init__() self.ip = ip self.port = port if module.find('S') != 0: module = "S" + module self.module_sn = module self.module = None self.moduleW = None self.addr = addr self.poll_interval = poll_interval self.server = Maq20Server(self.ip, self.port) def run(self): while not self.isInterruptionRequested(): self.update_data() self.msleep(int(self.poll_interval * 1000)) def update_data(self): if self.server.connected: data = self.read_data() if data is not None: self.new_data_signal.emit(data) def write(self, new_value): if self.server.connected: self.write_data(new_value) def read_data(self): self.server.mutex.lock() try: if self.module is None: print("read", self.module, self.ip, self.module_sn, self.addr) self.module = self.server.system.find(self.module_sn) if str(self.addr).find('r') == 0: addr = str(self.addr).replace("r", '') response = self.module.read_register(int(addr)) else: if not self.module.has_range_information(): response = self.module.read_channel_data_counts( int(self.addr)) else: self.module.load_channel_active_ranges() response = self.module.read_channel_data(int(self.addr)) except: self.module = None response = None try: self.server.system.time() except: self.server.connected = False finally: self.server.mutex.unlock() if response is not None: return response else: return None def write_data(self, new_value): self.server.mutexW.lock() try: if self.moduleW is None: self.moduleW = self.server.systemW.find(self.module_sn) print("Write", self.moduleW, self.ip, self.module_sn, self.addr) if str(self.addr).find('r') == 0: print("reg") addr = str(self.addr).replace("r", '') response = self.module.write_register(int(addr), new_value) else: if not self.moduleW.has_range_information(): self.moduleW.write_register(1000 + int(self.addr), new_value) else: self.moduleW.write_channel_data(int(self.addr), new_value) except: self.moduleW = None try: self.server.systemW.time() except: self.server.connectedW = False finally: self.server.mutexW.unlock() self.update_data()
class SignalHolder(QObject): """ Dummy QObject to let us use pyqtSignal """ value_sig = pyqtSignal([int], [float], [str]) empty_sig = pyqtSignal()