class SliceableGraphicsView(GraphicsView, SlicingView): sigToggleHorizontalSlice = Signal(bool) sigToggleVerticalSlice = Signal(bool) sigToggleDepthSlice = Signal(bool) sigMakePrimary = Signal(object, object) sigCrosshairMoved = Signal() SUPPORTED_NDIM = 2 def __init__(self, slice_direction, parent=None, xlink=None, ylink=None): super(SliceableGraphicsView, self).__init__(parent=parent) self.slice_direction = slice_direction self.setContentsMargins(0, 0, 0, 0) # Add axes self.view = SliceableAxes(slice_direction) self.view.axes["left"]["item"].setZValue(10) self.view.axes["top"]["item"].setZValue(10) self.setCentralItem(self.view) for sig in [ 'sigToggleVerticalSlice', 'sigToggleHorizontalSlice', 'sigToggleDepthSlice', 'sigMakePrimary' ]: if hasattr(self.view, sig): getattr(self.view, sig).connect(getattr(self, sig)) # Add imageitem self.image_item = ImageItem(axisOrder='row-major') self.image_item.setOpts() self.view.addItem(self.image_item) # add crosshair self.crosshair = BetterCrosshairROI((0, 0), parent=self.view, resizable=False) self.crosshair.sigMoved.connect(self.sigCrosshairMoved) self.view.getViewBox().addItem(self.crosshair) # find top-level parent NDImageView while not isinstance(parent, NDImageView): parent = parent.parent() # Initialize lut, levels self.image_item.setLevels(parent.levels, update=True) self.image_item.setLookupTable(parent.lut, update=True) # Link axes if ylink: self.view.vb.setYLink(ylink) if xlink: self.view.vb.setXLink(xlink) def setData(self, data): # Constrain squareness when units match is_square = data.dims[-2].split('(')[-1] == data.dims[-1].split( '(')[-1] self.view.vb.setAspectLocked(is_square) xvals = data.coords[data.dims[-1]] yvals = data.coords[data.dims[-2]] xmin = float(xvals.min()) xmax = float(xvals.max()) ymin = float(yvals.min()) ymax = float(yvals.max()) # Position the image according to coords shape = data.shape a = [(0, shape[-2]), (shape[-1], shape[-2]), (shape[-1], 0), (0, 0)] # b = [(ymin, xmax), (ymax, xmax), (ymax, xmin), (ymin, xmin)] if self.slice_direction in ['horizontal', 'depth']: b = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] elif self.slice_direction == 'vertical': b = [(xmax, ymax), (xmin, ymax), (xmin, ymin), (xmax, ymin)] quad1 = QPolygonF() quad2 = QPolygonF() for p, q in zip(a, b): quad1.append(QPointF(*p)) quad2.append(QPointF(*q)) transform = QTransform() QTransform.quadToQuad(quad1, quad2, transform) # Bind coords from the xarray to the timeline axis # super(SliceableGraphicsView, self).setImage(img, autoRange, autoLevels, levels, axes, np.asarray(img.coords[img.dims[0]]), pos, scale, transform, autoHistogramRange, levelMode) self.image_item.setImage(np.asarray(data), autoLevels=False) self.image_item.setTransform(transform) # Label the image axes self.view.setLabel('left', data.dims[-2]) self.view.setLabel('bottom', data.dims[-1]) def resetCrosshair(self): transform = self.image_item.viewTransform() new_pos = transform.map(self.image_item.boundingRect().center()) self.crosshair.setPos(new_pos) # self.crosshair.sigMoved.emit(new_pos) def updateImage(self, autoHistogramRange=True): ## Redraw image on screen if self.image is None: return image = self.getProcessedImage() if autoHistogramRange: self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) # Transpose image into order expected by ImageItem if self.imageItem.axisOrder == 'col-major': axorder = ['t', 'x', 'y', 'c'] else: axorder = ['t', 'y', 'x', 'c'] axorder = [ self.axes[ax] for ax in axorder if self.axes[ax] is not None ] ax_swap = [image.dims[ax_index] for ax_index in axorder] image = image.transpose(*ax_swap) # Select time index if self.axes['t'] is not None: self.ui.roiPlot.show() image = image[self.currentIndex] self.imageItem.updateImage(np.asarray(image)) def updateCrosshair(self, x, y): self.crosshair.setPos(x, y) def quickMinMax(self, data): """ Estimate the min/max values of *data* by subsampling. MODIFIED TO USE THE 99TH PERCENTILE instead of max. """ if data is None: return 0, 0 sl = slice(None, None, max(1, int(data.size // 1e6))) data = np.asarray(data[sl]) levels = (np.nanmin(data), np.nanpercentile( np.where(data < np.nanmax(data), data, np.nanmin(data)), 99)) return [levels]
class SliceableGraphicsView(GraphicsView): sigToggleHorizontalSlice = Signal(bool) sigToggleVerticalSlice = Signal(bool) sigMakePrimary = Signal(object, object) def __init__(self): super(SliceableGraphicsView, self).__init__() self.setContentsMargins(0, 0, 0, 0) # Add axes self.view = SliceableAxes() self.view.axes["left"]["item"].setZValue(10) self.view.axes["top"]["item"].setZValue(10) self.setCentralItem(self.view) self.view.sigToggleVerticalSlice.connect(self.sigToggleVerticalSlice) self.view.sigToggleHorizontalSlice.connect(self.sigToggleHorizontalSlice) self.view.sigMakePrimary.connect(self.sigMakePrimary) # Add imageitem self.image_item = ImageItem() self.view.addItem(self.image_item) # add crosshair self.crosshair = BetterCrosshairROI((0, 0), parent=self.view, resizable=False) self.view.getViewBox().addItem(self.crosshair) def setData(self, data): xvals = data.coords[data.dims[-1]] yvals = data.coords[data.dims[-2]] xmin = float(xvals.min()) xmax = float(xvals.max()) ymin = float(yvals.min()) ymax = float(yvals.max()) # Position the image according to coords shape = data.shape a = [(0, shape[-1]), (shape[-2] - 1, shape[-1]), (shape[-2] - 1, 1), (0, 1)] # b = [(ymin, xmax), (ymax, xmax), (ymax, xmin), (ymin, xmin)] b = [(xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)] quad1 = QPolygonF() quad2 = QPolygonF() for p, q in zip(a, b): quad1.append(QPointF(*p)) quad2.append(QPointF(*q)) transform = QTransform() QTransform.quadToQuad(quad1, quad2, transform) # Bind coords from the xarray to the timeline axis # super(SliceableGraphicsView, self).setImage(img, autoRange, autoLevels, levels, axes, np.asarray(img.coords[img.dims[0]]), pos, scale, transform, autoHistogramRange, levelMode) self.image_item.setImage(np.asarray(data), autoLevels=False) self.image_item.setTransform(transform) # Label the image axes self.view.setLabel('left', data.dims[-2]) self.view.setLabel('bottom', data.dims[-1]) def resetCrosshair(self): transform = self.image_item.viewTransform() new_pos = transform.map(self.image_item.boundingRect().center()) self.crosshair.setPos(new_pos) self.crosshair.sigMoved.emit(new_pos) def updateImage(self, autoHistogramRange=True): ## Redraw image on screen if self.image is None: return image = self.getProcessedImage() if autoHistogramRange: self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) # Transpose image into order expected by ImageItem if self.imageItem.axisOrder == 'col-major': axorder = ['t', 'x', 'y', 'c'] else: axorder = ['t', 'y', 'x', 'c'] axorder = [self.axes[ax] for ax in axorder if self.axes[ax] is not None] ax_swap = [image.dims[ax_index] for ax_index in axorder] image = image.transpose(*ax_swap) # Select time index if self.axes['t'] is not None: self.ui.roiPlot.show() image = image[self.currentIndex] self.imageItem.updateImage(np.asarray(image)) def quickMinMax(self, data): """ Estimate the min/max values of *data* by subsampling. MODIFIED TO USE THE 99TH PERCENTILE instead of max. """ if data is None: return 0, 0 sl = slice(None, None, max(1, int(data.size // 1e6))) data = np.asarray(data[sl]) levels = (np.nanmin(data), np.nanpercentile(np.where(data < np.nanmax(data), data, np.nanmin(data)), 99)) return [levels]