示例#1
0
    def _update_preview_image(image_data: Optional[np.ndarray],
                              image: ImageItem,
                              redraw_histogram: Optional[Callable[[Any],
                                                                  None]]):
        image.clear()
        image.setImage(image_data)

        if redraw_histogram:
            # Update histogram
            redraw_histogram(image.getHistogram())
示例#2
0
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()
示例#3
0
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()
示例#4
0
class FilterPreviews(GraphicsLayoutWidget):
    image_before: ImageItem
    image_after: ImageItem
    image_diff: ImageItem
    histogram_before: Optional[PlotItem]
    histogram_after: Optional[PlotItem]
    histogram: Optional[PlotItem]

    def __init__(self, parent=None, **kwargs):
        super(FilterPreviews, self).__init__(parent, **kwargs)

        widget_location = self.mapToGlobal(QPoint(self.width() / 2, 0))
        # allow the widget to take up to 80% of the desktop's height
        self.ALLOWED_HEIGHT: QRect = QGuiApplication.screenAt(widget_location).availableGeometry().height() * 0.8

        self.before_histogram_data = None
        self.after_histogram_data = None
        self.histogram = None
        self.before_histogram = None
        self.after_histogram = None
        self.combined_histograms = True
        self.histogram_legend_visible = True

        self.addLabel("Image before")
        self.addLabel("Image after")
        self.addLabel("Image difference")
        self.nextRow()

        self.image_before, self.image_before_vb, self.image_before_hist = self.image_in_vb(name="before")
        self.image_after, self.image_after_vb, self.image_after_hist = self.image_in_vb(name="after")
        self.image_difference, self.image_difference_vb, self.image_difference_hist = self.image_in_vb(
            name="difference")

        self.image_after_overlay = ImageItem()
        self.image_after_overlay.setZValue(10)
        self.image_after_vb.addItem(self.image_after_overlay)

        # Ensure images resize equally
        self.image_layout: GraphicsLayout = self.addLayout(colspan=6)
        self.image_layout.addItem(self.image_before_vb, 0, 0)
        self.image_layout.addItem(self.image_before_hist, 0, 1)
        self.image_layout.addItem(self.image_after_vb, 0, 2)
        self.image_layout.addItem(self.image_after_hist, 0, 3)
        self.image_layout.addItem(self.image_difference_vb, 0, 4)
        self.image_layout.addItem(self.image_difference_hist, 0, 5)
        self.nextRow()

        before_details = self.addLabel("")
        after_details = self.addLabel("")
        difference_details = self.addLabel("")

        self.display_formatted_detail = {
            self.image_before: lambda val: before_details.setText(f"Before: {val:.6f}"),
            self.image_after: lambda val: after_details.setText(f"After: {val:.6f}"),
            self.image_difference: lambda val: difference_details.setText(f"Difference: {val:.6f}"),
        }

        for img in self.image_before, self.image_after, self.image_difference:
            img.hoverEvent = lambda ev: self.mouse_over(ev)

        self.init_histogram()

    def resizeEvent(self, ev: QResizeEvent):
        if ev is not None:
            size = ev.size()
            self.image_layout.setFixedHeight(min(size.height() * 0.7, self.ALLOWED_HEIGHT))
        super().resizeEvent(ev)

    def image_in_vb(self, name=None):
        im = ImageItem()
        vb = ViewBox(invertY=True, lockAspect=True, name=name)
        vb.addItem(im)
        hist = HistogramLUTItem(im)
        return im, vb, hist

    def clear_items(self):
        self.image_before.clear()
        self.image_after.clear()
        self.image_difference.clear()
        self.image_after_overlay.clear()

    def init_histogram(self):
        self.histogram = self.addPlot(row=histogram_coords["combined"].row,
                                      col=histogram_coords["combined"].col,
                                      labels=histogram_axes_labels,
                                      lockAspect=True,
                                      colspan=3)
        self.addLabel("Pixel values", row=label_coords["combined"].row, col=label_coords["combined"].col)

        self.legend = self.histogram.addLegend()

    def update_histogram_data(self):
        # Plot any histogram that has data, and add a legend if both exist
        before_data = self.image_before.getHistogram()
        after_data = self.image_after.getHistogram()
        if _data_valid_for_histogram(before_data):
            if self.combined_histograms:
                before_plot = self.histogram.plot(*before_data, pen=before_pen, clear=True)
                self.legend.addItem(before_plot, "Before")
            else:
                self.before_histogram.plot(*before_data, pen=before_pen, clear=True)
        if _data_valid_for_histogram(after_data):
            if self.combined_histograms:
                after_plot = self.histogram.plot(*after_data, pen=after_pen)
                self.legend.addItem(after_plot, "After")
            else:
                self.after_histogram.plot(*after_data, pen=after_pen, clear=True)

    def init_separate_histograms(self):
        hc = histogram_coords
        self.before_histogram = self.addPlot(row=hc["before"].row,
                                             col=hc["before"].col,
                                             labels=histogram_axes_labels,
                                             lockAspect=True)
        self.after_histogram = self.addPlot(row=hc["after"].row,
                                            col=hc["after"].col,
                                            labels=histogram_axes_labels,
                                            lockAspect=True)
        lc = label_coords
        self.addLabel("Pixel values before", row=lc["before"].row, col=lc["before"].col)
        self.addLabel("Pixel values after", row=lc["after"].row, col=lc["after"].col)
        if _data_valid_for_histogram(self.before_histogram_data):
            self.before_histogram.plot(*self.before_histogram_data, pen=before_pen)
        if _data_valid_for_histogram(self.after_histogram_data):
            self.after_histogram.plot(*self.after_histogram_data, pen=after_pen)

    def delete_histograms(self):
        coords = set(c for c in histogram_coords.values())
        histograms = (self.getItem(*coord) for coord in coords)
        for histogram in filter(lambda h: h is not None, histograms):
            self.removeItem(histogram)
        self.histogram = None
        self.before_histogram = None
        self.after_histogram = None

    def delete_histogram_labels(self):
        coords = set(c for c in label_coords.values())
        labels = (self.getItem(*coord) for coord in coords)
        for label in filter(lambda h: h is not None, labels):
            self.removeItem(label)

    @property
    def histogram_legend(self) -> Optional[LegendItem]:
        if self.histogram and self.histogram.legend:
            return self.histogram.legend
        return None

    def mouse_over(self, ev):
        # Ignore events triggered by leaving window or right clicking
        if ev.exit:
            return
        pos = CloseEnoughPoint(ev.pos())
        for img in self.image_before, self.image_after, self.image_difference:
            if img.image is not None and pos.x < img.image.shape[0] and pos.y < img.image.shape[1]:
                pixel_value = img.image[pos.y, pos.x]
                self.display_formatted_detail[img](pixel_value)

    def link_all_views(self):
        for view1, view2 in [[self.image_before_vb, self.image_after_vb],
                             [self.image_after_vb, self.image_difference_vb],
                             [self.image_after_hist.vb, self.image_before_hist.vb]]:
            view1.linkView(ViewBox.XAxis, view2)
            view1.linkView(ViewBox.YAxis, view2)

    def unlink_all_views(self):
        for view in self.image_before_vb, self.image_after_vb, self.image_after_hist.vb:
            view.linkView(ViewBox.XAxis, None)
            view.linkView(ViewBox.YAxis, None)

    def add_difference_overlay(self, diff):
        diff = -diff
        diff[diff > 0.0] = 1.0
        pos = np.array([0, 1])
        color = np.array([[0, 0, 0, 0], [255, 0, 0, 255]], dtype=np.ubyte)
        map = ColorMap(pos, color)
        self.image_after_overlay.setOpacity(1)
        self.image_after_overlay.setImage(diff)
        lut = map.getLookupTable(0, 1, 2)
        self.image_after_overlay.setLookupTable(lut)

    def hide_difference_overlay(self):
        self.image_after_overlay.setOpacity(0)

    def auto_range(self):
        # This will cause the previews to all show by just causing autorange on self.image_before_vb
        self.image_before_vb.autoRange()
示例#5
0
class PsdWaterfallPlotWidget(GraphicsLayoutWidget):

    """This class manages and displays the power spectrum distribution (PSD)
       data in a waterfall plot.

    Attributes
    ----------
    arraySize : int
        The size of the data array to display.
    boundingRect : QtCore.QRectF
        The actual coordinate space base on frequency and time of acquisition.
    data : numpy.ndarray
        The 2D array for the PSD data.
    image : pyqtgraph.ImageItem
        The instance of the image item for display.
    timeScale : float
        The total time for the buffer to accumulate at the ROI FPS.
    """

    def __init__(self, parent=None):
        """Initialize the class.

        Parameters
        ----------
        parent : None, optional
            Top-level widget.
        """
        super().__init__(parent)
        self.plot = self.addPlot()
        self.plot.invertY()

        self.image = ImageItem()
        self.image.setOpts(axisOrder='row-major')
        self.plot.addItem(self.image)

        self.data = None
        self.arraySize = None
        self.boundingRect = None
        self.timeScale = None
        self.colorMap = 'viridis'
        self.image.setLookupTable(getLutFromColorMap(self.colorMap))

    def clearPlot(self):
        """Reset all data and clear the plot.
        """
        self.data = None
        self.boundingRect = None
        self.image.clear()

    def getConfiguration(self):
        """Get the current plot configuration.

        Returns
        -------
        int, str
            The set of current configuration parameters.
        """
        return self.arraySize, self.colorMap

    def setConfiguration(self, config):
        """Set the new parameters into the widget.

        Parameters
        ----------
        config : `config.PsdPlotConfig`
            The new parameters to apply.
        """
        numBins = config.numWaterfallBins
        if self.arraySize != numBins:
            self.arraySize = numBins
            # Invalidate data
            self.data = None
            self.boundingRect = None

        colorMap = config.waterfallColorMap
        if self.colorMap != colorMap:
            self.colorMap = colorMap
            self.image.setLookupTable(getLutFromColorMap(self.colorMap))

    def setTimeScale(self, timeScale):
        """Update the stored timescale and invalidate data and bounding rect.

        Parameters
        ----------
        timeScale : float
            The new timescale.
        """
        self.timeScale = timeScale
        self.data = None
        self.boundingRect = None

    def setup(self, arraySize, timeScale, axisLabel):
        """Setup the widget with the array size.

        Parameters
        ----------
        arraySize : int
            The size fo the data array to display in terms of history.
        timeScale : float
            The total time for the buffer to accumulate at the ROI FPS.
        axisLabel : str
            Label for particular centroid coordinate.
        """
        self.arraySize = arraySize
        self.timeScale = timeScale
        self.plot.setLabel('bottom', '{} {}'.format(axisLabel, HTML_NU), units='Hz')
        self.plot.setLabel('left', 'Time', units='s')

    def updatePlot(self, psd, freqs):
        """Update the current plot with the given data.

        Parameters
        ----------
        psd : numpy.array
            The PSD data of a given centroid coordinate.
        freqs : numpy.array
            The frequency array associated with the PSD data.
        """
        if self.data is None:
            self.data = np.zeros((self.arraySize, psd.size))
        else:
            self.data[1:, ...] = self.data[:-1, ...]
        self.data[0, ...] = np.log(psd)

        self.image.setImage(self.data)
        if self.boundingRect is None:
            self.boundingRect = QtCore.QRectF(0, 0, freqs[-1], self.arraySize * self.timeScale)
            self.image.setRect(self.boundingRect)
示例#6
0
class MIMiniImageView(GraphicsLayout, BadDataOverlay):
    def __init__(self, name: str = "MIMiniImageView"):
        super().__init__()

        self.name = name.title()
        self.im = ImageItem()
        self.vb = ViewBox(invertY=True, lockAspect=True, name=name)
        self.vb.addItem(self.im)
        self.hist = HistogramLUTItem(self.im)
        graveyard.append(self.vb)

        # Sub-layout prevents resizing issues when details text changes
        image_layout = self.addLayout(colspan=2)
        image_layout.addItem(self.vb)
        image_layout.addItem(self.hist)
        self.hist.setFixedWidth(100)  # HistogramLUTItem used pixel sizes

        self.nextRow()
        self.details = self.addLabel("", colspan=2)
        self.im.hoverEvent = lambda ev: self.mouse_over(ev)

        self.axis_siblings: "WeakSet[MIMiniImageView]" = WeakSet()
        self.histogram_siblings: "WeakSet[MIMiniImageView]" = WeakSet()

    @property
    def image_item(self) -> ImageItem:
        return self.im

    @property
    def viewbox(self) -> ViewBox:
        return self.vb

    def clear(self):
        self.im.clear()

    def setImage(self, *args, **kwargs):
        self.im.setImage(*args, **kwargs)
        self.check_for_bad_data()

    @staticmethod
    def set_siblings(sibling_views: List["MIMiniImageView"],
                     axis=False,
                     hist=False):
        for view1 in sibling_views:
            for view2 in sibling_views:
                if view2 is not view1:
                    if axis:
                        view1.add_axis_sibling(view2)
                    if hist:
                        view1.add_hist_sibling(view2)

    def add_axis_sibling(self, sibling: "MIMiniImageView"):
        self.axis_siblings.add(sibling)

    def add_hist_sibling(self, sibling: "MIMiniImageView"):
        self.histogram_siblings.add(sibling)

    def get_parts(self) -> Tuple[ImageItem, ViewBox, HistogramLUTItem]:
        return self.im, self.vb, self.hist

    def mouse_over(self, ev):
        # Ignore events triggered by leaving window or right clicking
        if ev.exit:
            return
        pos = CloseEnoughPoint(ev.pos())

        self.show_value(pos)
        for img_view in self.axis_siblings:
            img_view.show_value(pos)

    def show_value(self, pos):
        image = self.im.image
        if image is not None and pos.y < image.shape[
                0] and pos.x < image.shape[1]:
            pixel_value = image[pos.y, pos.x]
            value_string = ("%.6f" % pixel_value)[:8]
            self.details.setText(f"{self.name}: {value_string}")

    def link_sibling_axis(self):
        # Linking multiple viewboxes with locked aspect ratios causes
        # odd resizing behaviour. Use workaround from
        # https://github.com/pyqtgraph/pyqtgraph/issues/1348
        self.vb.setAspectLocked(True)
        for view1, view2 in pairwise(chain([self], self.axis_siblings)):
            view2.vb.linkView(ViewBox.XAxis, view1.vb)
            view2.vb.linkView(ViewBox.YAxis, view1.vb)
            view2.vb.setAspectLocked(False)

    def unlink_sibling_axis(self):
        for img_view in chain([self], self.axis_siblings):
            img_view.vb.linkView(ViewBox.XAxis, None)
            img_view.vb.linkView(ViewBox.YAxis, None)
            img_view.vb.setAspectLocked(True)

    def link_sibling_histogram(self):
        for view1, view2 in pairwise(chain([self], self.histogram_siblings)):
            view1.hist.vb.linkView(ViewBox.YAxis, view2.hist.vb)
        for img_view in chain([self], self.histogram_siblings):
            img_view.hist.sigLevelChangeFinished.connect(
                img_view.update_sibling_histograms)

    def unlink_sibling_histogram(self):
        for img_view in chain([self], self.histogram_siblings):
            img_view.hist.vb.linkView(ViewBox.YAxis, None)
            try:
                img_view.hist.sigLevelChangeFinished.disconnect()
            except TypeError:
                # This is expected if there are slots currently connected
                pass

    def update_sibling_histograms(self):
        hist_range = self.hist.getLevels()
        for img_view in self.histogram_siblings:
            with BlockQtSignals(img_view.hist):
                img_view.hist.setLevels(*hist_range)
class FilterPreviews(GraphicsLayoutWidget):
    image_before: ImageItem
    image_after: ImageItem
    image_diff: ImageItem
    histogram_before: Optional[PlotItem]
    histogram_after: Optional[PlotItem]
    histogram: Optional[PlotItem]

    def __init__(self, parent=None, **kwargs):
        super(FilterPreviews, self).__init__(parent, **kwargs)
        self.before_histogram_data = None
        self.after_histogram_data = None
        self.histogram = None
        self.before_histogram = None
        self.after_histogram = None

        self.combined_histograms = True
        self.histogram_legend_visible = True

        self.addLabel("Image before")
        self.addLabel("Image after")
        self.addLabel("Image difference")
        self.nextRow()

        self.image_before, self.image_before_vb = self.image_in_vb(
            name="before")
        self.image_after, self.image_after_vb = self.image_in_vb(name="after")
        self.image_difference, self.image_difference_vb = self.image_in_vb(
            name="difference")

        self.image_after_overlay = ImageItem()
        self.image_after_overlay.setZValue(10)
        self.image_after_vb.addItem(self.image_after_overlay)

        # Ensure images resize equally
        image_layout = self.addLayout(colspan=3)
        image_layout.addItem(self.image_before_vb, 0, 0)
        image_layout.addItem(self.image_after_vb, 0, 1)
        image_layout.addItem(self.image_difference_vb, 0, 2)
        self.nextRow()

        before_details = self.addLabel("")
        after_details = self.addLabel("")
        difference_details = self.addLabel("")

        self.display_formatted_detail = {
            self.image_before:
            lambda val: before_details.setText(f"Before: {val:.6f}"),
            self.image_after:
            lambda val: after_details.setText(f"After: {val:.6f}"),
            self.image_difference:
            lambda val: difference_details.setText(f"Difference: {val:.6f}"),
        }

        for img in self.image_before, self.image_after, self.image_difference:
            img.hoverEvent = lambda ev: self.mouse_over(ev)

    def image_in_vb(self, name=None):
        im = ImageItem()
        im.setAutoDownsample(False)
        vb = ViewBox(invertY=True, lockAspect=True, name=name)
        vb.addItem(im)
        return im, vb

    def clear_items(self):
        self.image_before.clear()
        self.image_after.clear()
        self.image_difference.clear()
        self.image_after_overlay.clear()
        self.delete_histograms()

    # There seems to be a bug with pyqtgraph.PlotDataItem.setData not forcing a redraw.
    # We work around this by redrawing everything completely every time, which is unreasonably fast anyway.
    def redraw_histograms(self):
        self.delete_histograms()
        self.delete_histogram_labels()

        if self.combined_histograms:
            self.draw_combined_histogram()
        else:
            self.draw_separate_histograms()

    def delete_histograms(self):
        coords = set(c for c in histogram_coords.values())
        histograms = (self.getItem(*coord) for coord in coords)
        for histogram in filter(lambda h: h is not None, histograms):
            self.removeItem(histogram)
        self.histogram = None
        self.before_histogram = None
        self.after_histogram = None
        self.diff_histogram = None

    def delete_histogram_labels(self):
        coords = set(c for c in label_coords.values())
        labels = (self.getItem(*coord) for coord in coords)
        for label in filter(lambda h: h is not None, labels):
            self.removeItem(label)

    def draw_combined_histogram(self):
        self.histogram = self.addPlot(row=histogram_coords["combined"].row,
                                      col=histogram_coords["combined"].col,
                                      labels=histogram_axes_labels,
                                      lockAspect=True,
                                      colspan=3)
        self.addLabel("Pixel values",
                      row=label_coords["combined"].row,
                      col=label_coords["combined"].col)

        legend = self.histogram.addLegend()
        # Plot any histogram that has data, and add a legend if both exist
        if _data_valid_for_histogram(self.before_histogram_data):
            before_plot = self.histogram.plot(*self.before_histogram_data,
                                              pen=before_pen)
            legend.addItem(before_plot, "Before")
        if _data_valid_for_histogram(self.after_histogram_data):
            after_plot = self.histogram.plot(*self.after_histogram_data,
                                             pen=after_pen)
            legend.addItem(after_plot, "After")

    def draw_separate_histograms(self):
        hc = histogram_coords
        self.before_histogram = self.addPlot(row=hc["before"].row,
                                             col=hc["before"].col,
                                             labels=histogram_axes_labels,
                                             lockAspect=True)
        self.after_histogram = self.addPlot(row=hc["after"].row,
                                            col=hc["after"].col,
                                            labels=histogram_axes_labels,
                                            lockAspect=True)
        lc = label_coords
        self.addLabel("Pixel values before",
                      row=lc["before"].row,
                      col=lc["before"].col)
        self.addLabel("Pixel values after",
                      row=lc["after"].row,
                      col=lc["after"].col)
        if _data_valid_for_histogram(self.before_histogram_data):
            self.before_histogram.plot(*self.before_histogram_data,
                                       pen=before_pen)
        if _data_valid_for_histogram(self.after_histogram_data):
            self.after_histogram.plot(*self.after_histogram_data,
                                      pen=after_pen)

    def set_before_histogram(self, data: Tuple[ndarray]):
        self.before_histogram_data = data
        self.redraw_histograms()

    def set_after_histogram(self, data: Tuple[ndarray]):
        self.after_histogram_data = data
        self.redraw_histograms()

    @property
    def histogram_legend(self) -> Optional[LegendItem]:
        if self.histogram and self.histogram.legend:
            return self.histogram.legend
        return None

    def mouse_over(self, ev):
        # Ignore events triggered by leaving window or right clicking
        if ev.exit:
            return
        pos = CloseEnoughPoint(ev.pos())
        for img in self.image_before, self.image_after, self.image_difference:
            if img.image is not None and pos.x < img.image.shape[
                    0] and pos.y < img.image.shape[1]:
                pixel_value = img.image[pos.y, pos.x]
                self.display_formatted_detail[img](pixel_value)

    def link_all_views(self):
        for view1, view2 in zip(
            [self.image_before_vb, self.image_after_vb],
            [self.image_after_vb, self.image_difference_vb]):
            view1.linkView(ViewBox.XAxis, view2)
            view1.linkView(ViewBox.YAxis, view2)

    def unlink_all_views(self):
        for view in self.image_before_vb, self.image_after_vb, self.image_difference_vb:
            view.linkView(ViewBox.XAxis, None)
            view.linkView(ViewBox.YAxis, None)

    def add_difference_overlay(self, diff):
        diff = -diff
        diff[diff > 0.0] = 1.0
        pos = np.array([0, 1])
        color = np.array([[0, 0, 0, 0], [255, 0, 0, 255]], dtype=np.ubyte)
        map = ColorMap(pos, color)
        self.image_after_overlay.setOpacity(1)
        self.image_after_overlay.setImage(diff)
        lut = map.getLookupTable(0, 1, 2)
        self.image_after_overlay.setLookupTable(lut)

    def hide_difference_overlay(self):
        self.image_after_overlay.setOpacity(0)
示例#8
0
 def _update_preview_image(image_data: Optional[np.ndarray],
                           image: ImageItem):
     image.clear()
     image.setImage(image_data)
示例#9
0
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()
示例#10
0
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()
示例#11
0
class Plot2DWidget(PlotWidget):
    def __init__(self, model_wrapper, statusbar, parent=None):
        """
        The Plot2DWidget is responsible for rendering the 2D chromatogram data.
        :param model_wrapper: the wrapper of the model.
        :param parent: the parent of this Widget.
        """
        super().__init__(parent=parent)

        self.listener = Plot2DListener(self, model_wrapper, statusbar)
        """ The listener for the 2D plot """
        self.img = ImageItem()
        """ The image of the chromatogram"""
        self.wrapper_temp = model_wrapper  # TEMPORARY TODO What is this for?
        """A temporary reference to the wrapper?"""

        # Add the image to the plot.
        self.addItem(self.img)

        # Disable right click context menu.
        self.getPlotItem().setMenuEnabled(False)
        self.getPlotItem().getAxis('bottom').enableAutoSIPrefix(False)
        self.getPlotItem().getAxis('left').enableAutoSIPrefix(False)

        model_wrapper.add_observer(self, self.notify)

        # call notify to draw the model. NOTE: The if statement isn't nesessary, it checks in notify if there is
        # a model or not.
        self.notify('model', model_wrapper.model)

    def refresh_x_period(self, x_period):
        if x_period == 0:
            self.getPlotItem().getAxis('bottom').setScale(1)
        else:
            self.getPlotItem().getAxis('bottom').setScale(
                x_period / self.wrapper_temp.model.get_width())

    def refresh_y_period(self, y_period):
        if y_period == 0:
            self.getPlotItem().getAxis('left').setScale(1)
        else:
            self.getPlotItem().getAxis('left').setScale(
                y_period / self.wrapper_temp.model.get_height())

    def refresh_x_unit(self, x_unit):
        if x_unit is TimeUnit.NONE:
            self.getPlotItem().getAxis('bottom').setLabel(units="")
        else:
            self.getPlotItem().getAxis('bottom').setLabel(
                units=x_unit.name.lower())

    def refresh_y_unit(self, y_unit):
        if y_unit is TimeUnit.NONE:
            self.getPlotItem().getAxis('left').setLabel(units="")
        else:
            self.getPlotItem().getAxis('left').setLabel(
                units=y_unit.name.lower())

    def notify(self, name, value):
        """
        Updates the image rendered to match the model.
        :return: None
        """

        if name == 'newIntegration':
            self.addItem(value.selector.roi)
            value.selector.set_viewport(self.img)
        elif name == 'removeIntegration':
            self.removeItem(value.selector.roi)
        elif name in {'model', 'model.viewTransformed'}:
            if value is None or value.get_2d_chromatogram_data() is None:
                self.img.clear()
            else:
                self.img.setImage(value.get_2d_chromatogram_data().clip(
                    value.lower_bound, value.upper_bound),
                                  lut=value.palette)

                self.refresh_x_period(
                    self.wrapper_temp.get_preference(ScaleEnum.X_PERIOD))
                self.refresh_y_period(
                    self.wrapper_temp.get_preference(ScaleEnum.Y_PERIOD))
                self.refresh_x_unit(
                    self.wrapper_temp.get_preference(ScaleEnum.X_UNIT))
                self.refresh_y_unit(
                    self.wrapper_temp.get_preference(ScaleEnum.Y_UNIT))
        elif name == 'model.palette':
            self.img.setLookupTable(value.palette)
        elif name == 'model.lower_bound' or name == 'model.upper_bound':
            self.img.setImage(value.get_2d_chromatogram_data().clip(
                value.lower_bound, value.upper_bound),
                              lut=value.palette)
        elif name == ScaleEnum.X_UNIT.name:
            self.refresh_x_unit(value)
        elif name == ScaleEnum.Y_UNIT.name:
            self.refresh_y_unit(value)
        elif name == ScaleEnum.X_PERIOD.name:
            self.refresh_x_period(value)
        elif name == ScaleEnum.Y_PERIOD.name:
            self.refresh_y_period(value)
class FilterPreviews(GraphicsLayoutWidget):
    image_before: ImageItem
    image_after: ImageItem
    image_diff: ImageItem
    histogram: Optional[PlotItem]

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

        widget_location = self.mapToGlobal(QPoint(self.width() // 2, 0))
        # allow the widget to take up to 80% of the desktop's height
        if QGuiApplication.screenAt(widget_location) is not None:
            screen_height = QGuiApplication.screenAt(
                widget_location).availableGeometry().height()
        else:
            screen_height = max(
                QGuiApplication.primaryScreen().availableGeometry().height(),
                600)
            LOG.info(
                "Unable to detect current screen. Setting screen height to %s"
                % screen_height)
        self.ALLOWED_HEIGHT: QRect = screen_height * 0.8

        self.histogram = None

        self.addLabel("Image before")
        self.addLabel("Image after")
        self.addLabel("Image difference")
        self.nextRow()

        self.imageview_before = MIMiniImageView(name="before")
        self.imageview_after = MIMiniImageView(name="after")
        self.imageview_difference = MIMiniImageView(name="difference")
        self.all_imageviews = [
            self.imageview_before, self.imageview_after,
            self.imageview_difference
        ]
        MIMiniImageView.set_siblings(self.all_imageviews, axis=True)
        MIMiniImageView.set_siblings(
            [self.imageview_before, self.imageview_after], hist=True)

        self.image_before, self.image_before_vb, self.image_before_hist = self.imageview_before.get_parts(
        )
        self.image_after, self.image_after_vb, self.image_after_hist = self.imageview_after.get_parts(
        )
        self.image_difference, self.image_difference_vb, self.image_difference_hist = \
            self.imageview_difference.get_parts()

        self.all_histograms = [
            self.image_before_hist, self.image_after_hist,
            self.image_difference_hist
        ]

        self.image_diff_overlay = ImageItem()
        self.image_diff_overlay.setZValue(10)
        self.image_after_vb.addItem(self.image_diff_overlay)

        # Ensure images resize equally
        self.image_layout: GraphicsLayout = self.addLayout(colspan=3)

        self.image_layout.addItem(self.imageview_before)
        self.image_layout.addItem(self.imageview_after)
        self.image_layout.addItem(self.imageview_difference)
        self.nextRow()

        self.init_histogram()

        # Work around for https://github.com/mantidproject/mantidimaging/issues/565
        self.scene().contextMenu = [
            item for item in self.scene().contextMenu
            if "export" not in item.text().lower()
        ]

        self.auto_colour_actions = []
        self._add_auto_colour_action(self.image_before_hist, self.image_before)
        self._add_auto_colour_action(self.image_after_hist, self.image_after)
        self._add_auto_colour_action(self.image_difference_hist,
                                     self.image_difference)

        self.imageview_before.link_sibling_axis()

        self.imageview_before.enable_nan_check()
        self.imageview_after.enable_nan_check()

    def resizeEvent(self, ev: QResizeEvent):
        if ev is not None and isinstance(self.histogram, PlotItem):
            size = ev.size()
            self.histogram.setFixedHeight(
                min(size.height() * 0.7, self.ALLOWED_HEIGHT) * 0.25)
        super().resizeEvent(ev)

    def clear_items(self, clear_before: bool = True):
        if clear_before:
            self.imageview_before.clear()
        self.imageview_after.clear()
        self.imageview_difference.clear()
        self.image_diff_overlay.clear()

    def init_histogram(self):
        self.histogram = self.addPlot(row=histogram_coords.row,
                                      col=histogram_coords.col,
                                      labels=histogram_axes_labels,
                                      lockAspect=True,
                                      colspan=3)
        self.addLabel("Pixel values",
                      row=label_coords.row,
                      col=label_coords.col)

        self.legend = self.histogram.addLegend()
        self.legend.setOffset((0, 1))

    def update_histogram_data(self):
        # Plot any histogram that has data, and add a legend if both exist
        before_data = self.imageview_before.image_item.getHistogram()
        after_data = self.imageview_after.image_item.getHistogram()
        if _data_valid_for_histogram(before_data):
            before_plot = self.histogram.plot(*before_data,
                                              pen=before_pen,
                                              clear=True)
            self.legend.addItem(before_plot, "Before")

        if _data_valid_for_histogram(after_data):
            after_plot = self.histogram.plot(*after_data, pen=after_pen)
            self.legend.addItem(after_plot, "After")

    @property
    def histogram_legend(self) -> Optional[LegendItem]:
        if self.histogram and self.histogram.legend:
            return self.histogram.legend
        return None

    def link_all_views(self):
        self.imageview_before.link_sibling_axis()

    def unlink_all_views(self):
        self.imageview_before.unlink_sibling_axis()

    def add_difference_overlay(self, diff, nan_change):
        diff = np.absolute(diff)
        diff[diff > OVERLAY_THRESHOLD] = 1.0
        diff[nan_change] = 1.0
        pos = np.array([0, 1])
        color = np.array([[0, 0, 0, 0], OVERLAY_COLOUR_DIFFERENCE],
                         dtype=np.ubyte)
        map = ColorMap(pos, color)
        self.image_diff_overlay.setVisible(True)
        self.image_diff_overlay.setImage(diff)
        lut = map.getLookupTable(0, 1, 2)
        self.image_diff_overlay.setLookupTable(lut)

    def add_negative_overlay(self):
        self.imageview_after.enable_nonpositive_check()

    def hide_difference_overlay(self):
        self.image_diff_overlay.setVisible(False)

    def hide_negative_overlay(self):
        self.imageview_after.enable_nonpositive_check(False)

    def auto_range(self):
        # This will cause the previews to all show by just causing autorange on self.image_before_vb
        self.image_before_vb.autoRange()

    def record_histogram_regions(self):
        self.before_region = self.image_before_hist.region.getRegion()
        self.diff_region = self.image_difference_hist.region.getRegion()
        self.after_region = self.image_after_hist.region.getRegion()

    def restore_histogram_regions(self):
        self.image_before_hist.region.setRegion(self.before_region)
        self.image_difference_hist.region.setRegion(self.diff_region)
        self.image_after_hist.region.setRegion(self.after_region)

    def link_before_after_histogram_scales(self, create_link: bool):
        """
        Connects or disconnects the scales of the before/after histograms.
        :param create_link: Whether the link should be created or removed.
        """
        if create_link:
            self.imageview_after.link_sibling_histogram()
        else:
            self.imageview_after.unlink_sibling_histogram()

    def set_histogram_log_scale(self):
        """
        Sets the y-values of the before and after histogram plots to a log scale.
        """
        set_histogram_log_scale(self.image_before_hist)
        set_histogram_log_scale(self.image_after_hist)

    def _add_auto_colour_action(self, histogram: HistogramLUTItem,
                                image: ImageItem):
        """
        Adds an "Auto" action to the histogram right-click menu.
        :param histogram: The HistogramLUTItem
        :param image: The ImageItem to have the Jenks/Otsu algorithm performed on it.
        """
        self.auto_colour_actions.append(QAction("Auto"))
        self.auto_colour_actions[-1].triggered.connect(
            lambda: self._on_change_colour_palette(histogram, image))

        action = histogram.gradient.menu.actions()[12]
        histogram.gradient.menu.insertAction(action,
                                             self.auto_colour_actions[-1])
        histogram.gradient.menu.insertSeparator(self.auto_colour_actions[-1])

    def _on_change_colour_palette(self, main_histogram: HistogramLUTItem,
                                  image: ImageItem):
        """
        Creates a Palette Changer window when the "Auto" option has been selected.
        :param main_histogram: The HistogramLUTItem.
        :param image: The ImageItem.
        """
        other_histograms = self.all_histograms[:]
        other_histograms.remove(main_histogram)
        change_colour_palette = PaletteChangerView(self, main_histogram,
                                                   image.image,
                                                   other_histograms)
        change_colour_palette.show()