class PreviewWidget(GraphicsLayoutWidget): def __init__(self): super(PreviewWidget, self).__init__() self.setMinimumHeight(250) self.setMinimumWidth(250) self.view = self.addViewBox(lockAspect=True, enableMenu=False) self.imageitem = ImageItem() self.textitem = TextItem(anchor=(0.5, 0)) self.textitem.setFont(QFont("Zero Threes")) self.imgdata = None self.imageitem.setOpts(axisOrder="row-major") self.view.addItem(self.imageitem) self.view.addItem(self.textitem) self.textitem.hide() self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # def textItemBounds(axis, frac=1.0, orthoRange=None): # b = self.textitem.boundingRect() # sx, sy = self.view.viewPixelSize() # x, y = sx*b.width(), sy*b.height() # if axis == 0: return (-x/2, x/2) # if axis == 1: return (0, y) # # self.textitem.dataBounds = textItemBounds def sizeHint(self): return QSize(250, 250) def preview_header(self, header: NonDBHeader): try: data = header.meta_array()[0] self.setImage(data) except IndexError: self.imageitem.clear() self.setText("UNKNOWN DATA FORMAT") def setImage(self, imgdata): self.imageitem.clear() self.textitem.hide() self.imgdata = imgdata self.imageitem.setImage(np.log(self.imgdata * (self.imgdata > 0) + (self.imgdata < 1)), autoLevels=True) self.imageitem.setTransform( QTransform(1, 0, 0, -1, 0, self.imgdata.shape[-2])) self.view.autoRange() def setText(self, text): self.textitem.setText(text) self.imageitem.clear() self.textitem.setVisible(True) self.view.autoRange()
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 PreviewWidget(GraphicsLayoutWidget): def __init__(self): super(PreviewWidget, self).__init__() self.setMinimumHeight(250) self.setMinimumWidth(250) self.view = self.addViewBox(lockAspect=True, enableMenu=False) self.imageitem = ImageItem() self.textitem = TextItem(anchor=(0.5, 0)) self.textitem.setFont(QFont("Zero Threes")) self.imgdata = None self.imageitem.setOpts(axisOrder="row-major") self.view.addItem(self.imageitem) self.view.addItem(self.textitem) self.textitem.hide() self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # def textItemBounds(axis, frac=1.0, orthoRange=None): # b = self.textitem.boundingRect() # sx, sy = self.view.viewPixelSize() # x, y = sx*b.width(), sy*b.height() # if axis == 0: return (-x/2, x/2) # if axis == 1: return (0, y) # # self.textitem.dataBounds = textItemBounds def sizeHint(self): return QSize(250, 250) @threads.method(threadkey="preview", showBusy=False) def preview(self, data): if isinstance(data, NonDBHeader): self.preview_header(data) else: self.preview_catalog(data) @staticmethod def guess_stream_field(catalog: BlueskyRun): # TODO: use some metadata (techniques?) for guidance about how to get a preview streams = bluesky_utils.streams_from_run(catalog) if "primary" in streams: streams.remove("primary") streams.insert(0, "primary") for stream in streams: descriptor = bluesky_utils.descriptors_from_stream( catalog, stream)[0] fields = bluesky_utils.fields_from_descriptor(descriptor) for field in fields: field_ndims = bluesky_utils.ndims_from_descriptor( descriptor, field) if field_ndims > 1: return stream, field def preview_catalog(self, catalog: BlueskyRun): threads.invoke_in_main_thread(self.setText, "LOADING...") try: stream, field = self.guess_stream_field(catalog) data = getattr(catalog, stream).to_dask()[field].squeeze() for i in range(len(data.shape) - 2): data = data[0] threads.invoke_in_main_thread(self.setImage, np.asarray(data.compute())) except Exception as ex: msg.logError(ex) threads.invoke_in_main_thread(self.imageitem.clear) threads.invoke_in_main_thread(self.setText, "UNKNOWN DATA FORMAT") def preview_header(self, header: NonDBHeader): try: data = header.meta_array()[0] threads.invoke_in_main_thread(self.setImage, data) except IndexError: threads.invoke_in_main_thread(self.imageitem.clear) threads.invoke_in_main_thread(self.setText, "UNKNOWN DATA FORMAT") def setImage(self, imgdata): self.imageitem.clear() self.textitem.hide() self.imgdata = imgdata self.imageitem.setImage(np.log(self.imgdata * (self.imgdata > 0) + (self.imgdata < 1)), autoLevels=True) self.imageitem.setTransform( QTransform(1, 0, 0, -1, 0, self.imgdata.shape[-2])) self.view.autoRange() def setText(self, text): self.textitem.setText(text) self.imageitem.clear() self.textitem.setVisible(True) self.view.autoRange()
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]
class PreviewWidget(GraphicsLayoutWidget): def __init__(self): super(PreviewWidget, self).__init__() self.setMinimumHeight(250) self.setMinimumWidth(250) self.view = self.addViewBox(lockAspect=True, enableMenu=False) self.imageitem = ImageItem() self.textitem = TextItem(anchor=(0.5, 0)) self.textitem.setFont(QFont("Zero Threes")) self.imageitem.setOpts(axisOrder="row-major") self.view.addItem(self.imageitem) self.view.addItem(self.textitem) self.textitem.hide() self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # def textItemBounds(axis, frac=1.0, orthoRange=None): # b = self.textitem.boundingRect() # sx, sy = self.view.viewPixelSize() # x, y = sx*b.width(), sy*b.height() # if axis == 0: return (-x/2, x/2) # if axis == 1: return (0, y) # # self.textitem.dataBounds = textItemBounds def sizeHint(self): return QSize(250, 250) @threads.method(threadkey="preview", showBusy=False) def preview(self, data): if isinstance(data, NonDBHeader): self.preview_header(data) else: self.preview_catalog(data) def preview_catalog(self, catalog: BlueskyRun): threads.invoke_in_main_thread(self.setText, "LOADING...") try: stream, field = bluesky_utils.guess_stream_field(catalog) data = bluesky_utils.preview(catalog, stream, field) threads.invoke_in_main_thread(self.setImage, data) except Exception as ex: msg.logError(ex) threads.invoke_in_main_thread(self.imageitem.clear) threads.invoke_in_main_thread(self.setText, "UNKNOWN DATA FORMAT") def preview_header(self, header: NonDBHeader): try: data = header.meta_array()[0] threads.invoke_in_main_thread(self.setImage, data) except IndexError: threads.invoke_in_main_thread(self.imageitem.clear) threads.invoke_in_main_thread(self.setText, "UNKNOWN DATA FORMAT") def setImage(self, imgdata): self.imageitem.clear() self.textitem.hide() self.imageitem.setImage(np.log(imgdata * (imgdata > 0) + (imgdata < 1)), autoLevels=True) self.imageitem.setTransform( QTransform(1, 0, 0, -1, 0, imgdata.shape[-2])) self.view.autoRange() def setText(self, text): self.textitem.setText(text) self.imageitem.clear() self.textitem.setVisible(True) self.view.autoRange()
class PreviewWidget(GraphicsLayoutWidget): def __init__(self): super(PreviewWidget, self).__init__() self.setMinimumHeight(250) self.setMinimumWidth(250) self.view = self.addViewBox(lockAspect=True, enableMenu=False) self.imageitem = ImageItem() self.textitem = TextItem(anchor=(0.5, 0)) self.textitem.setFont(QFont("Zero Threes")) self.imgdata = None self.imageitem.setOpts(axisOrder="row-major") self.view.addItem(self.imageitem) self.view.addItem(self.textitem) self.textitem.hide() self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # def textItemBounds(axis, frac=1.0, orthoRange=None): # b = self.textitem.boundingRect() # sx, sy = self.view.viewPixelSize() # x, y = sx*b.width(), sy*b.height() # if axis == 0: return (-x/2, x/2) # if axis == 1: return (0, y) # # self.textitem.dataBounds = textItemBounds def sizeHint(self): return QSize(250, 250) def preview(self, data): if isinstance(data, NonDBHeader): self.preview_header(data) else: self.preview_catalog(data) def preview_catalog(self, catalog: BlueskyRun): try: dask_array = catalog.primary.to_dask() fields = dask_array.keys() # Filter out seq num and uid field = next(field for field in fields if not field in ["seq_num", "uid"]) data = dask_array[field] for i in range(len(data.shape) - 2): data = data[0] self.setImage(np.asarray(data.compute())) except IndexError: self.imageitem.clear() self.setText("UNKNOWN DATA FORMAT") def preview_header(self, header: NonDBHeader): try: data = header.meta_array()[0] self.setImage(data) except IndexError: self.imageitem.clear() self.setText("UNKNOWN DATA FORMAT") def setImage(self, imgdata): self.imageitem.clear() self.textitem.hide() self.imgdata = imgdata self.imageitem.setImage(np.log(self.imgdata * (self.imgdata > 0) + (self.imgdata < 1)), autoLevels=True) self.imageitem.setTransform(QTransform(1, 0, 0, -1, 0, self.imgdata.shape[-2])) self.view.autoRange() def setText(self, text): self.textitem.setText(text) self.imageitem.clear() self.textitem.setVisible(True) self.view.autoRange()