Source code for mantidimaging.gui.windows.operations.filter_previews

# Copyright (C) 2022 ISIS Rutherford Appleton Laboratory UKRI
# SPDX - License - Identifier: GPL-3.0-or-later

from collections import namedtuple
from logging import getLogger
from typing import Optional

import numpy as np
from PyQt5.QtCore import QPoint, QRect
from PyQt5.QtGui import QGuiApplication, QResizeEvent
from pyqtgraph import ColorMap, GraphicsLayoutWidget, ImageItem, LegendItem, PlotItem
from pyqtgraph.graphicsItems.GraphicsLayout import GraphicsLayout
from pyqtgraph.graphicsItems.HistogramLUTItem import HistogramLUTItem

from mantidimaging.core.utility.histogram import set_histogram_log_scale
from mantidimaging.gui.widgets.palette_changer.view import PaletteChangerView
from mantidimaging.gui.widgets.mi_mini_image_view.view import MIMiniImageView

LOG = getLogger(__name__)

histogram_axes_labels = {'left': 'Count', 'bottom': 'Gray value'}
before_pen = (200, 0, 0)
after_pen = (0, 200, 0)
diff_pen = (0, 0, 200)

OVERLAY_THRESHOLD = 1e-3
OVERLAY_COLOUR_DIFFERENCE = [0, 255, 0, 255]

Coord = namedtuple('Coord', ['row', 'col'])
histogram_coords = Coord(4, 0)
label_coords = Coord(3, 1)


def _data_valid_for_histogram(data):
    return data is not None and any(d is not None for d in data)


[docs] 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._add_auto_colour_action(self.imageview_before) self._add_auto_colour_action(self.imageview_after) self._add_auto_colour_action(self.imageview_difference) self.imageview_before.link_sibling_axis() self.imageview_before.enable_nan_check() self.imageview_after.enable_nan_check()
[docs] 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)
[docs] 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()
[docs] 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))
[docs] 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
[docs] 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)
[docs] def add_negative_overlay(self): self.imageview_after.enable_nonpositive_check()
[docs] def hide_difference_overlay(self): self.image_diff_overlay.setVisible(False)
[docs] def hide_negative_overlay(self): self.imageview_after.enable_nonpositive_check(False)
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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, img_view: MIMiniImageView): """ Adds an "Auto" action to the histogram right-click menu. """ action = img_view.add_auto_color_action() action.triggered.connect(lambda: self._on_change_colour_palette(img_view.hist, img_view.im)) 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()