Beispiel #1
0
class BusMonitorWidget(QGroupBox):
    DEFAULT_PLOT_X_RANGE = 120
    BUS_LOAD_PLOT_MAX_SAMPLES = 5000

    def __init__(self, parent, node, iface_name):
        super(BusMonitorWidget, self).__init__(parent)
        self.setTitle('CAN bus activity (%s)' % iface_name.split(os.path.sep)[-1])

        self._node = node
        self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook)

        self._columns = [
            BasicTable.Column('Dir',
                              lambda e: (e[0].upper()),
                              searchable=False),
            BasicTable.Column('Local Time', TimestampRenderer(), searchable=False),
            BasicTable.Column('CAN ID',
                              lambda e: (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8),
                                         colorize_can_id(e[1]))),
            BasicTable.Column('Data Hex',
                              lambda e: (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[1].MAX_DATA_LENGTH),
                                         colorize_transfer_id(e))),
            BasicTable.Column('Data ASCII',
                              lambda e: (''.join([(chr(x) if 32 <= x <= 126 else '.') for x in e[1].data]),
                                         colorize_transfer_id(e))),
            BasicTable.Column('Src',
                              lambda e: render_node_id_with_color(e[1], 'src')),
            BasicTable.Column('Dst',
                              lambda e: render_node_id_with_color(e[1], 'dst')),
            BasicTable.Column('Data Type',
                              lambda e: render_data_type_with_color(e[1]),
                              resize_mode=QHeaderView.Stretch),
        ]

        self._log_widget = RealtimeLogWidget(self, columns=self._columns, font=get_monospace_font(),
                                             post_redraw_hook=self._redraw_hook)
        self._log_widget.on_selection_changed = self._update_measurement_display

        def flip_row_mark(row, col):
            if col == 0:
                item = self._log_widget.table.item(row, col)
                if item.icon().isNull():
                    item.setIcon(get_icon('circle'))
                    flash(self, 'Row %d was marked, click again to unmark', row, duration=3)
                else:
                    item.setIcon(QIcon())

        self._log_widget.table.cellPressed.connect(flip_row_mark)

        self._stat_update_timer = QTimer(self)
        self._stat_update_timer.setSingleShot(False)
        self._stat_update_timer.timeout.connect(self._update_stat)
        self._stat_update_timer.start(500)

        self._traffic_stat = TrafficStatCounter()

        self._stat_frames_tx = QLabel('N/A', self)
        self._stat_frames_rx = QLabel('N/A', self)
        self._stat_traffic = QLabel('N/A', self)

        self._load_plot = PlotWidget(background=(0, 0, 0))
        self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE), padding=0)
        self._load_plot.setMaximumHeight(150)
        self._load_plot.setMinimumHeight(100)
        self._load_plot.setMinimumWidth(100)
        self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self._load_plot.showGrid(x=True, y=True, alpha=0.4)
        self._load_plot.setToolTip('Frames per second')
        self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True, y=False)
        self._load_plot.enableAutoRange()
        self._bus_load_plot = self._load_plot.plot(name='Frames per second', pen=mkPen(QColor(Qt.lightGray), width=1))
        self._bus_load_samples = [], []
        self._started_at_mono = time.monotonic()

        layout = QVBoxLayout(self)

        layout.addWidget(self._log_widget, 1)

        stat_vars_layout = QGridLayout(self)
        stat_layout_next_row = 0

        def add_stat_row(label, value):
            nonlocal stat_layout_next_row
            stat_vars_layout.addWidget(QLabel(label, self), stat_layout_next_row, 0)
            stat_vars_layout.addWidget(value, stat_layout_next_row, 1)
            value.setMinimumWidth(75)
            stat_layout_next_row += 1

        add_stat_row('Frames transmitted:', self._stat_frames_tx)
        add_stat_row('Frames received:', self._stat_frames_rx)
        add_stat_row('Frames per second:', self._stat_traffic)
        stat_vars_layout.setRowStretch(stat_layout_next_row, 1)

        stat_layout = QHBoxLayout(self)
        stat_layout.addLayout(stat_vars_layout)
        stat_layout.addWidget(self._load_plot, 1)

        layout.addLayout(stat_layout, 0)
        self.setLayout(layout)

    def close(self):
        self._hook_handle.remove()

    def _update_stat(self):
        bus_load, ts_mono = self._traffic_stat.get_frames_per_second()
        self._stat_traffic.setText(str(int(bus_load + 0.5)))

        if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES:
            self._bus_load_samples[0].pop(0)
            self._bus_load_samples[1].pop(0)

        self._bus_load_samples[1].append(bus_load)
        self._bus_load_samples[0].append(ts_mono - self._started_at_mono)

        self._bus_load_plot.setData(*self._bus_load_samples)

        (xmin, xmax), _ = self._load_plot.viewRange()
        diff = xmax - xmin
        xmax = self._bus_load_samples[0][-1]
        xmin = self._bus_load_samples[0][-1] - diff
        self._load_plot.setRange(xRange=(xmin, xmax), padding=0)

    def _redraw_hook(self):
        self._stat_frames_tx.setText(str(self._traffic_stat.tx))
        self._stat_frames_rx.setText(str(self._traffic_stat.rx))

    def _frame_hook(self, direction, frame):
        self._traffic_stat.add_frame(direction, frame)
        self._log_widget.add_item_async((direction, frame))

    def _update_measurement_display(self, selected_rows_cols):
        if not selected_rows_cols:
            return

        min_row = min([row for row, _ in selected_rows_cols])
        max_row = max([row for row, _ in selected_rows_cols])

        def get_row_ts(row):
            return TimestampRenderer.parse_timestamp(self._log_widget.table.item(row, 1).text())

        def get_load_str(num_frames, dt):
            if dt >= 1e-6:
                return 'average load %.1f FPS' % (num_frames / dt)
            return 'average load is unknown'

        if min_row == max_row:
            num_frames = min_row
            first_ts = get_row_ts(0)
            current_ts = get_row_ts(min_row)
            dt = current_ts - first_ts
            flash(self, '%d frames from beginning, %.3f sec since first frame, %s',
                  num_frames, dt, get_load_str(num_frames, dt))
        else:
            num_frames = max_row - min_row + 1
            first_ts = get_row_ts(min_row)
            last_ts = get_row_ts(max_row)
            dt = last_ts - first_ts
            flash(self, '%d frames, timedelta %.6f sec, %s',
                  num_frames, dt, get_load_str(num_frames, dt))
Beispiel #2
0
    def __init__(self,
                 time_series,
                 topographies,
                 filters,
                 channel_names,
                 fs,
                 scores,
                 pos,
                 scores_name='Mutual info',
                 marks=None,
                 *args):
        super(ScoredComponentsTable, self).__init__(*args)

        # attributes
        self.row_items_max_height = 125
        self.time_series = time_series
        self.marks = marks
        self.channel_names = channel_names
        self.fs = fs

        # set size and names
        self.columns = [
            'Selection', scores_name, 'Topography',
            'Time series (push to switch mode)'
        ]
        self.setColumnCount(len(self.columns))
        self.setRowCount(time_series.shape[1])
        self.setHorizontalHeaderLabels(self.columns)

        # columns widgets
        self.checkboxes = []
        self.topographies_items = []
        self.plot_items = []
        self.scores = []
        _previous_plot_link = None
        for ind in range(self.rowCount()):

            # checkboxes
            checkbox = QtWidgets.QCheckBox()
            self.checkboxes.append(checkbox)
            self.setCellWidget(ind, self.columns.index('Selection'), checkbox)

            # topographies and filters
            topo_filter = TopoFilterCavas(self, self.channel_names,
                                          topographies[:, ind], filters[:,
                                                                        ind],
                                          self.row_items_max_height, pos)
            self.topographies_items.append(topo_filter)
            self.setCellWidget(ind, self.columns.index('Topography'),
                               topo_filter)

            # plots
            plot_widget = PlotWidget(enableMenu=False)
            if _previous_plot_link is not None:
                plot_widget.setXLink(_previous_plot_link)
                # plot_widget.setYLink(_previous_plot_link)
            _previous_plot_link = plot_widget
            plot_widget.plot(y=self.time_series[:, ind])
            if self.marks is not None:
                plot_widget.plot(y=self.marks *
                                 np.max(self.time_series[:, ind]),
                                 pen=(1, 3))
                plot_widget.plot(y=-self.marks *
                                 np.max(self.time_series[:, ind]),
                                 pen=(1, 3))
            plot_widget.plot(x=np.arange(self.time_series.shape[0]) / fs)

            plot_widget.setMaximumHeight(self.row_items_max_height)
            plot_widget.plotItem.getViewBox().state['wheelScaleFactor'] = 0
            self.plot_items.append(plot_widget)
            self.setCellWidget(ind, 3, plot_widget)

            # scores
            score_widget = BarLabelWidget(scores[ind], max(scores),
                                          min(scores))
            self.scores.append(score_widget)
            self.setCellWidget(ind, self.columns.index(scores_name),
                               score_widget)

        # formatting
        self.current_row = None
        self.horizontalHeader().setStretchLastSection(True)
        self.resizeColumnsToContents()
        self.resizeRowsToContents()

        # clickable 3 column header
        self.horizontalHeader().sectionClicked.connect(
            self.handle_header_click)
        self.is_spectrum_mode = False

        # reorder
        self.order = np.argsort(scores)
        self.reorder()

        # checkbox signals
        for checkbox in self.checkboxes:
            checkbox.stateChanged.connect(self.checkboxes_state_changed)

        # selection context menu
        header = self.horizontalHeader()
        header.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self.handle_header_menu)

        # ctrl+a short cut
        QtWidgets.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_A),
            self).activated.connect(self.ctrl_plus_a_event)

        # checkbox cell clicked
        self.cellClicked.connect(self.cell_was_clicked)
Beispiel #3
0
class CamViewer(Display):
    # Emitted when the user changes the value.
    roi_x_signal = Signal(str)
    roi_y_signal = Signal(str)
    roi_w_signal = Signal(str)
    roi_h_signal = Signal(str)

    def __init__(self, parent=None, args=None):
        super(CamViewer, self).__init__(parent=parent, args=args)

        # Set up the list of cameras, and all the PVs
        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.cameras = {"Testing IOC Image": test_dict }
        self._channels = []
        self.imageChannel = None

        # Populate the camera combo box
        self.ui.cameraComboBox.clear()
        for camera in self.cameras:
            self.ui.cameraComboBox.addItem(camera)

        # 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 key, map_name in cmap_names.items():
            self.ui.colorMapComboBox.addItem(map_name, userData=key)
        self.ui.imageView.colorMap = self.ui.colorMapComboBox.currentData()
        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=5))
        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=5))
        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=5))
        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=5))
        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)

        self.destroyed.connect(functools.partial(widget_destroyed, self.channels))


    @Slot()
    def zoomIn(self):
        self.ui.imageView.getView().scaleBy((0.5, 0.5))

    @Slot()
    def zoomOut(self):
        self.ui.imageView.getView().scaleBy((2.0, 2.0))

    @Slot()
    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))

    @Slot(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()

    @Slot()
    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))
            except:
                pass
            coords = marker.getPixelCoords()
            x_line_edit.setText(str(coords[0]))
            y_line_edit.setText(str(coords[1]))

    @Slot(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]))

    @Slot(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])

    @Slot()
    def enableSingleShotMode(self):
        self._average_mode_enabled = False
        self._average_buffer = np.ndarray(0)

    @Slot()
    def enableAverageMode(self):
        self._average_mode_enabled = True

    @Slot(str)
    def cameraChanged(self, new_camera):
        new_camera = str(new_camera)
        if self.imageChannel == self.cameras[new_camera]["image"]:
            return
        close_widget_connections(self)
        self.disable_all_markers()
        self.initializeCamera(new_camera)

    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, value_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)
        establish_widget_connections(self)

    @Slot()
    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())

    @Slot()
    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))

    @Slot(str)
    def colorMapChanged(self, _):
        self.ui.imageView.colorMap = self.ui.colorMapComboBox.currentData()

    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

    @Slot()
    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)

    @Slot(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())

    @Slot()
    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)

    @Slot(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)

    @Slot()
    def numAverageChanged(self):
        self._average_buffer = np.zeros(0)

    @Slot(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.image_value_changed(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)

    @Slot(int)
    def receiveImageWidth(self, new_width):
        self.image_width = new_width
        self.ui.imageView.image_width_changed(self.image_width)

    @Slot(int)
    def receiveMaxWidth(self, new_max_width):
        self.image_max_width = new_max_width

    @Slot(int)
    def receiveMaxHeight(self, new_max_height):
        self.image_max_height = new_max_height

    @Slot(int)
    def receiveRoiX(self, new_roi_x):
        self.ui.roiXLineEdit.setText(str(new_roi_x))

    @Slot(int)
    def receiveRoiY(self, new_roi_y):
        self.ui.roiYLineEdit.setText(str(new_roi_y))

    @Slot(int)
    def receiveRoiWidth(self, new_roi_w):
        self.ui.roiWLineEdit.setText(str(new_roi_w))

    @Slot(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.
    @Slot(int)
    def alarmStatusChanged(self, new_alarm_state):
        pass

    # 0 = NO_ALARM, 1 = MINOR, 2 = MAJOR, 3 = INVALID
    @Slot(int)
    def alarmSeverityChanged(self, new_alarm_severity):
        pass

    @Slot(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
    @Slot(bool)
    def connectionStateChanged(self, connected):
        if connected:
            self.ui.imageView.redraw_timer.start()
        else:
            self.ui.imageView.redraw_timer.stop()
        self.ui.connectedLabel.setText({True: "Yes", False: "No"}[connected])

    def ui_filename(self):
        return 'camviewer.ui'

    def channels(self):
        return self._channels
Beispiel #4
0
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
    def __init__(self, time_series, topographies, filters, channel_names, fs, scores, scores_name='Mutual info', marks=None, *args):
        super(ScoredComponentsTable, self).__init__(*args)

        # attributes
        self.row_items_max_height = 125
        self.time_series = time_series
        self.marks = marks
        self.channel_names = channel_names
        self.fs = fs

        # set size and names
        self.columns = ['Selection', scores_name, 'Topography', 'Time series (push to switch mode)']
        self.setColumnCount(len(self.columns))
        self.setRowCount(time_series.shape[1])
        self.setHorizontalHeaderLabels(self.columns)

        # columns widgets
        self.checkboxes = []
        self.topographies_items = []
        self.plot_items = []
        self.scores = []
        _previous_plot_link = None
        for ind in range(self.rowCount()):

            # checkboxes
            checkbox = QtWidgets.QCheckBox()
            self.checkboxes.append(checkbox)
            self.setCellWidget(ind, self.columns.index('Selection'), checkbox)

            # topographies and filters
            topo_filter = TopoFilterCavas(self, self.channel_names, topographies[:, ind], filters[:, ind],
                                          self.row_items_max_height)
            self.topographies_items.append(topo_filter)
            self.setCellWidget(ind, self.columns.index('Topography'), topo_filter)

            # plots
            plot_widget = PlotWidget(enableMenu=False)
            if _previous_plot_link is not None:
                plot_widget.setXLink(_previous_plot_link)
                # plot_widget.setYLink(_previous_plot_link)
            _previous_plot_link = plot_widget
            plot_widget.plot(y=self.time_series[:, ind])
            if self.marks is not None:
                plot_widget.plot(y=self.marks*np.max(self.time_series[:, ind]), pen=(1,3))
                plot_widget.plot(y=-self.marks * np.max(self.time_series[:, ind]), pen=(1, 3))
            plot_widget.plot(x=np.arange(self.time_series.shape[0]) / fs)

            plot_widget.setMaximumHeight(self.row_items_max_height)
            plot_widget.plotItem.getViewBox().state['wheelScaleFactor'] = 0
            self.plot_items.append(plot_widget)
            self.setCellWidget(ind, 3, plot_widget)

            # scores
            score_widget = BarLabelWidget(scores[ind], max(scores), min(scores))
            self.scores.append(score_widget)
            self.setCellWidget(ind, self.columns.index(scores_name), score_widget)

        # formatting
        self.current_row = None
        self.horizontalHeader().setStretchLastSection(True)
        self.resizeColumnsToContents()
        self.resizeRowsToContents()

        # clickable 3 column header
        self.horizontalHeader().sectionClicked.connect(self.handle_header_click)
        self.is_spectrum_mode = False

        # reorder
        self.order = np.argsort(scores)
        self.reorder()

        # checkbox signals
        for checkbox in self.checkboxes:
            checkbox.stateChanged.connect(self.checkboxes_state_changed)

        # selection context menu
        header = self.horizontalHeader()
        header.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self.handle_header_menu)

        # ctrl+a short cut
        QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_A), self).activated.connect(self.ctrl_plus_a_event)

        # checkbox cell clicked
        self.cellClicked.connect(self.cell_was_clicked)
Beispiel #6
0
class BusMonitorWidget(QGroupBox):
    DEFAULT_PLOT_X_RANGE = 120
    BUS_LOAD_PLOT_MAX_SAMPLES = 5000

    def __init__(self, parent, node, iface_name):
        super(BusMonitorWidget, self).__init__(parent)
        self.setTitle('CAN bus activity (%s)' %
                      iface_name.split(os.path.sep)[-1])

        self._node = node
        self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook)

        self._columns = [
            BasicTable.Column('Dir',
                              lambda e: (e[0].upper()),
                              searchable=False),
            BasicTable.Column('Local Time',
                              TimestampRenderer(),
                              searchable=False),
            BasicTable.Column(
                'CAN ID', lambda e:
                (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8),
                 colorize_can_id(e[1]))),
            BasicTable.Column(
                'Data Hex', lambda e:
                (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[
                    1].MAX_DATA_LENGTH), colorize_transfer_id(e))),
            BasicTable.Column(
                'Data ASCII', lambda e:
                (''.join([(chr(x) if 32 <= x <= 126 else '.')
                          for x in e[1].data]), colorize_transfer_id(e))),
            BasicTable.Column(
                'Src', lambda e: render_node_id_with_color(e[1], 'src')),
            BasicTable.Column(
                'Dst', lambda e: render_node_id_with_color(e[1], 'dst')),
            BasicTable.Column('Data Type',
                              lambda e: render_data_type_with_color(e[1]),
                              resize_mode=QHeaderView.Stretch),
        ]

        self._log_widget = RealtimeLogWidget(
            self,
            columns=self._columns,
            font=get_monospace_font(),
            post_redraw_hook=self._redraw_hook)
        self._log_widget.on_selection_changed = self._update_measurement_display

        def flip_row_mark(row, col):
            if col == 0:
                item = self._log_widget.table.item(row, col)
                if item.icon().isNull():
                    item.setIcon(get_icon('circle'))
                    flash(self,
                          'Row %d was marked, click again to unmark',
                          row,
                          duration=3)
                else:
                    item.setIcon(QIcon())

        self._log_widget.table.cellPressed.connect(flip_row_mark)

        self._stat_update_timer = QTimer(self)
        self._stat_update_timer.setSingleShot(False)
        self._stat_update_timer.timeout.connect(self._update_stat)
        self._stat_update_timer.start(500)

        self._traffic_stat = TrafficStatCounter()

        self._stat_frames_tx = QLabel('N/A', self)
        self._stat_frames_rx = QLabel('N/A', self)
        self._stat_traffic = QLabel('N/A', self)

        self._load_plot = PlotWidget(background=(0, 0, 0))
        self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE),
                                 padding=0)
        self._load_plot.setMaximumHeight(150)
        self._load_plot.setMinimumHeight(100)
        self._load_plot.setMinimumWidth(100)
        self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self._load_plot.showGrid(x=True, y=True, alpha=0.4)
        self._load_plot.setToolTip('Frames per second')
        self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True,
                                                                   y=False)
        self._load_plot.enableAutoRange()
        self._bus_load_plot = self._load_plot.plot(name='Frames per second',
                                                   pen=mkPen(QColor(
                                                       Qt.lightGray),
                                                             width=1))
        self._bus_load_samples = [], []
        self._started_at_mono = time.monotonic()

        layout = QVBoxLayout(self)

        layout.addWidget(self._log_widget, 1)

        stat_vars_layout = QGridLayout(self)
        stat_layout_next_row = 0

        def add_stat_row(label, value):
            nonlocal stat_layout_next_row
            stat_vars_layout.addWidget(QLabel(label, self),
                                       stat_layout_next_row, 0)
            stat_vars_layout.addWidget(value, stat_layout_next_row, 1)
            value.setMinimumWidth(75)
            stat_layout_next_row += 1

        add_stat_row('Frames transmitted:', self._stat_frames_tx)
        add_stat_row('Frames received:', self._stat_frames_rx)
        add_stat_row('Frames per second:', self._stat_traffic)
        stat_vars_layout.setRowStretch(stat_layout_next_row, 1)

        stat_layout = QHBoxLayout(self)
        stat_layout.addLayout(stat_vars_layout)
        stat_layout.addWidget(self._load_plot, 1)

        layout.addLayout(stat_layout, 0)
        self.setLayout(layout)

    def close(self):
        self._hook_handle.remove()

    def _update_stat(self):
        bus_load, ts_mono = self._traffic_stat.get_frames_per_second()
        self._stat_traffic.setText(str(int(bus_load + 0.5)))

        if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES:
            self._bus_load_samples[0].pop(0)
            self._bus_load_samples[1].pop(0)

        self._bus_load_samples[1].append(bus_load)
        self._bus_load_samples[0].append(ts_mono - self._started_at_mono)

        self._bus_load_plot.setData(*self._bus_load_samples)

        (xmin, xmax), _ = self._load_plot.viewRange()
        diff = xmax - xmin
        xmax = self._bus_load_samples[0][-1]
        xmin = self._bus_load_samples[0][-1] - diff
        self._load_plot.setRange(xRange=(xmin, xmax), padding=0)

    def _redraw_hook(self):
        self._stat_frames_tx.setText(str(self._traffic_stat.tx))
        self._stat_frames_rx.setText(str(self._traffic_stat.rx))

    def _frame_hook(self, direction, frame):
        self._traffic_stat.add_frame(direction, frame)
        self._log_widget.add_item_async((direction, frame))

    def _update_measurement_display(self, selected_rows_cols):
        if not selected_rows_cols:
            return

        min_row = min([row for row, _ in selected_rows_cols])
        max_row = max([row for row, _ in selected_rows_cols])

        def get_row_ts(row):
            return TimestampRenderer.parse_timestamp(
                self._log_widget.table.item(row, 1).text())

        def get_load_str(num_frames, dt):
            if dt >= 1e-6:
                return 'average load %.1f FPS' % (num_frames / dt)
            return 'average load is unknown'

        if min_row == max_row:
            num_frames = min_row
            first_ts = get_row_ts(0)
            current_ts = get_row_ts(min_row)
            dt = current_ts - first_ts
            flash(self,
                  '%d frames from beginning, %.3f sec since first frame, %s',
                  num_frames, dt, get_load_str(num_frames, dt))
        else:
            num_frames = max_row - min_row + 1
            first_ts = get_row_ts(min_row)
            last_ts = get_row_ts(max_row)
            dt = last_ts - first_ts
            flash(self, '%d frames, timedelta %.6f sec, %s', num_frames, dt,
                  get_load_str(num_frames, dt))