diff --git a/CHANGELOG b/CHANGELOG
index c3200d9..88f22e9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,5 @@
2.20.0
+ - feat: display and handle quantitative phase imaging data
- fix: filter range control not updating limits, displaying value "100" (#183)
- fix: filter range control not updating handles when resized
2.19.1
diff --git a/shapeout2/gui/quick_view/qv_main.py b/shapeout2/gui/quick_view/qv_main.py
index 70a32a4..0fb8c30 100644
--- a/shapeout2/gui/quick_view/qv_main.py
+++ b/shapeout2/gui/quick_view/qv_main.py
@@ -142,10 +142,37 @@ def __init__(self, *args, **kwargs):
self.legend_trace = self.graphicsView_trace.addLegend(
offset=(-.01, +.01))
+ # qpi_pha cmaps
+ self.cmap_pha = pg.colormap.get('CET-D1A', skipCache=True)
+ self.cmap_pha_with_black = pg.colormap.get('CET-D1A', skipCache=True)
+ self.cmap_pha_with_black.color[0] = [0, 0, 0, 1]
+
+ # image display default range of values that the cmap will cover
+ self.levels_image = (0, 255)
+ self.levels_qpi_pha = (-3.14, 3.14)
+ self.levels_qpi_amp = (0, 2)
+
#: default parameters for the event image
- self.imkw = dict(autoLevels=False,
- levels=(0, 255),
- )
+ self.img_info = {
+ "image": {
+ "view_event": self.imageView_image,
+ "view_poly": self.imageView_image_poly,
+ "cmap": None,
+ "kwargs": dict(autoLevels=False, levels=self.levels_image),
+ },
+ "qpi_pha": {
+ "view_event": self.imageView_image_pha,
+ "view_poly": self.imageView_image_poly_pha,
+ "cmap": self.cmap_pha,
+ "kwargs": dict(autoLevels=False, levels=self.levels_qpi_pha),
+ },
+ "qpi_amp": {
+ "view_event": self.imageView_image_amp,
+ "view_poly": self.imageView_image_poly_amp,
+ "cmap": None,
+ "kwargs": dict(autoLevels=False, levels=self.levels_qpi_amp),
+ },
+ }
# set initial empty dataset
self._rtdc_ds = None
@@ -277,31 +304,79 @@ def rtdc_ds(self, rtdc_ds):
self.comboBox_y.set_dataset(rtdc_ds, default_choice="deform")
self.comboBox_z_hue.set_dataset(rtdc_ds)
- def get_event_image(self, ds, event):
+ def get_event_and_display(self, ds, event, feat, view):
+ """Convenience method to get the event image and display in one step"""
+ cellimg = self.get_event_image(ds, event, feat)
+ self.display_img(feat, view, cellimg)
+
+ def get_event_image(self, ds, event, feat):
+ """Handle the image processing and contour processing for the event"""
state = self.__getstate__()
- imkw = self.imkw.copy()
- cellimg = ds["image"][event]
- # apply background correction
- if "image_bg" in ds:
- if state["event"]["image background"]:
- bgimg = ds["image_bg"][event].astype(np.int16)
- cellimg = cellimg.astype(np.int16)
- cellimg = cellimg - bgimg + int(np.mean(bgimg))
- # automatic contrast
- if state["event"]["image auto contrast"]:
- vmin, vmax = cellimg.min(), cellimg.max()
- cellimg = (cellimg - vmin) / max(vmax - vmin, 1) * 255
- # convert to RGB
- cellimg = cellimg.reshape(
- cellimg.shape[0], cellimg.shape[1], 1)
- cellimg = np.repeat(cellimg, 3, axis=2)
- # clip and convert to int
- cellimg = np.clip(cellimg, 0, 255)
- cellimg = np.require(cellimg, np.uint8, 'C')
+ cellimg = ds[feat][event]
+ cellimg = self.display_image(ds, event, state, cellimg, feat)
+ cellimg = self.display_contour(ds, event, state, cellimg, feat)
+ return cellimg
+
+ def display_image(self, ds, event, state, cellimg, feat):
+ """Apply background, auto-contrast and format conversion"""
+ if feat == "image":
+ # apply background correction
+ if "image_bg" in ds:
+ if state["event"]["image background"]:
+ bgimg = ds["image_bg"][event].astype(np.int16)
+ cellimg = cellimg.astype(np.int16)
+ cellimg = cellimg - bgimg + int(np.mean(bgimg))
+ # automatic contrast
+ if state["event"]["image auto contrast"]:
+ vmin, vmax = cellimg.min(), cellimg.max()
+ cellimg = (cellimg - vmin) / (vmax - vmin) * 255
+ cellimg = self._convert_to_rgb(cellimg)
+ # clip and convert to int
+ cellimg = np.clip(cellimg, 0, 255)
+ cellimg = np.require(cellimg, np.uint8, 'C')
+
+ elif feat == "qpi_pha":
+ if state["event"]["image auto contrast"]:
+ vmin, vmax = self._vmin_max_around_zero(cellimg)
+
+ if state["event"]["image contour"]:
+ # offset required for auto-contrast with contour
+ # two times the contrast range, divided by the cmap length
+ # this essentially adds a cmap point for our contour
+ offset = 2 * ((vmax - vmin) / len(self.cmap_pha.color))
+ vmin = vmin - offset
+ self.img_info[feat]["cmap"] = self.cmap_pha_with_black
+ else:
+ self.img_info[feat]["cmap"] = self.cmap_pha
+ else:
+ vmin, vmax = self.levels_qpi_pha
+ self.img_info[feat]["kwargs"]["levels"] = (vmin, vmax)
+
+ elif feat == "qpi_amp":
+ if state["event"]["image auto contrast"]:
+ vmin, vmax = cellimg.min(), cellimg.max()
+ else:
+ vmin, vmax = self.levels_qpi_amp
+ self.img_info[feat]["kwargs"]["levels"] = (vmin, vmax)
+ # to get the correct contour colour it is easier to view the
+ # amplitude as an RGB image
+ cellimg = self._convert_to_rgb(cellimg)
+
+ return cellimg
+
+ def _vmin_max_around_zero(self, cellimg):
+ vmin_abs, vmax_abs = np.abs(cellimg.min()), np.abs(cellimg.max())
+ v_largest = max(vmax_abs, vmin_abs)
+ vmin, vmax = -v_largest, v_largest
+ return vmin, vmax
+
+ def display_contour(self, ds, event, state, cellimg, feat):
+ """Add the contour to the image if requested"""
# Only load contour data if there is an image column.
# We don't know how big the images should be so we
# might run into trouble displaying random contours.
+ imkw = self.img_info[feat]["kwargs"]
if "mask" in ds and len(ds["mask"]) > event:
mask = ds["mask"][event]
if state["event"]["image contour"]:
@@ -310,22 +385,44 @@ def get_event_image(self, ds, event):
# https://github.com/DC-analysis/dclab/issues/76
cont = mask ^ binary_erosion(mask)
# set red contour pixel values in original image
- cellimg[cont, 0] = int(255*.7)
- cellimg[cont, 1] = 0
- cellimg[cont, 2] = 0
+ if feat == "image" or feat == "qpi_amp":
+ # for RGB images
+ ch_red = imkw["levels"][1] * 0.7
+ ch_other = int(imkw["levels"][0]) if \
+ imkw["levels"][1] == 255 else imkw["levels"][0]
+ # assign channel values for contour
+ cellimg[cont, 0] = int(
+ ch_red) if imkw["levels"][1] == 255 else ch_red
+ cellimg[cont, 1] = ch_other
+ cellimg[cont, 2] = ch_other
+ elif feat == "qpi_pha":
+ # use the lowest value from the colormap
+ cellimg[cont] = imkw["levels"][0]
+
if state["event"]["image zoom"]:
- xv, yv = np.where(mask)
- idminx = xv.min() - 5
- idminy = yv.min() - 5
- idmaxx = xv.max() + 5
- idmaxy = yv.max() + 5
- idminx = idminx if idminx >= 0 else 0
- idminy = idminy if idminy >= 0 else 0
- shx, shy = mask.shape
- idmaxx = idmaxx if idmaxx < shx else shx
- idmaxy = idmaxy if idmaxy < shy else shy
- cellimg = cellimg[idminx:idmaxx, idminy:idmaxy]
- return cellimg, imkw
+ cellimg = self.image_zoom(cellimg, mask)
+
+ return cellimg
+
+ @staticmethod
+ def _convert_to_rgb(cellimg):
+ cellimg = cellimg.reshape(
+ cellimg.shape[0], cellimg.shape[1], 1)
+ return np.repeat(cellimg, 3, axis=2)
+
+ @staticmethod
+ def image_zoom(cellimg, mask):
+ xv, yv = np.where(mask)
+ idminx = xv.min() - 5
+ idminy = yv.min() - 5
+ idmaxx = xv.max() + 5
+ idmaxy = yv.max() + 5
+ idminx = idminx if idminx >= 0 else 0
+ idminy = idminy if idminy >= 0 else 0
+ shx, shy = mask.shape
+ idmaxx = idmaxx if idmaxx < shx else shx
+ idmaxy = idmaxy if idmaxy < shy else shy
+ return cellimg[idminx:idmaxx, idminy:idmaxy]
def get_statistics(self):
if self.rtdc_ds is not None:
@@ -377,26 +474,41 @@ def on_event_scatter_clicked(self, plot, point):
self.toolButton_event.setChecked(True)
self.toolButton_event.toggled.emit(True)
+ def display_img(self, feat, view, cellimg):
+ self.img_info[feat][view].setImage(cellimg,
+ **self.img_info[feat]["kwargs"])
+ if self.img_info[feat]["cmap"] is not None:
+ self.img_info[feat][view].setColorMap(self.img_info[feat]["cmap"])
+ self.img_info[feat][view].show()
+
@QtCore.pyqtSlot(QtCore.QPointF)
def on_event_scatter_hover(self, pos):
"""Update the image view in the polygon widget """
if self.rtdc_ds is not None and self.toolButton_poly.isChecked():
+ ds = self.rtdc_ds
# plotted events
plotted = self.widget_scatter.events_plotted
spos = self.widget_scatter.scatter.mapFromView(pos)
point = self.widget_scatter.scatter.pointAt(spos)
# get corrected index
event = np.where(plotted)[0][point.index()]
- if "image" in self.rtdc_ds:
- try:
- cellimg, imkw = self.get_event_image(self.rtdc_ds, event)
- except IndexError:
- # the plot got updated, and we still have the old data
- cellimg, imkw = self.get_event_image(self.rtdc_ds, 0)
- self.imageView_image_poly.setImage(cellimg, **imkw)
- self.imageView_image_poly.show()
- else:
- self.imageView_image_poly.hide()
+
+ view = "view_poly"
+ for key in self.img_info.keys():
+ self.img_info[key][view].hide()
+
+ try:
+ # if we have qpi data, image might be a different shape
+ if "qpi_pha" in ds:
+ self.get_event_and_display(ds, event, "qpi_pha", view)
+ if "qpi_amp" in ds:
+ self.get_event_and_display(ds, event, "qpi_amp", view)
+ elif "image" in ds:
+ self.get_event_and_display(ds, event, "image", view)
+
+ except IndexError:
+ # the plot got updated, and we still have the old data
+ self.get_event_and_display(ds, 0, "image", view)
@QtCore.pyqtSlot(int)
def on_event_scatter_spin(self, event):
@@ -544,8 +656,8 @@ def on_tool(self, collapse=False):
else:
# keep everything as-is but update the sizes
show_event = self.stackedWidget.currentWidget() is self.page_event
- show_settings = self.stackedWidget.currentWidget() \
- is self.page_settings
+ show_settings = (
+ self.stackedWidget.currentWidget() is self.page_settings)
show_poly = self.stackedWidget.currentWidget() is self.page_poly
# toolbutton checked
@@ -661,12 +773,22 @@ def show_event(self, event):
if self.tabWidget_event.currentIndex() == 0:
# update image
state = self.__getstate__()
- if "image" in ds:
- cellimg, imkw = self.get_event_image(ds, event)
- self.imageView_image.setImage(cellimg, **imkw)
- self.groupBox_image.show()
- else:
- self.groupBox_image.hide()
+ self.groupBox_image.hide()
+
+ view = "view_event"
+ for key in self.img_info.keys():
+ self.img_info[key][view].hide()
+
+ # if we have qpi data, image might be a different shape
+ if "qpi_pha" in ds:
+ self.get_event_and_display(ds, event, "qpi_pha", view)
+ if "qpi_amp" in ds:
+ self.get_event_and_display(ds, event, "qpi_amp", view)
+ elif "image" in ds:
+ self.get_event_and_display(ds, event, "image", view)
+
+ self.groupBox_image.show()
+
if "trace" in ds:
# remove legend items
for item in reversed(self.legend_trace.items):
@@ -705,8 +827,9 @@ def show_event(self, event):
range_t[1] = flpos + 1.5 * flwidth
range_t[2] = flmax
# set legend name
- ln = "{} {}".format(self.slot.fl_name_dict[
- "FL-{}".format(key[2])], key[4:])
+ ln = "{} {}".format(
+ self.slot.fl_name_dict[
+ "FL-{}".format(key[2])], key[4:])
self.legend_trace.addItem(self.trace_plots[key], ln)
self.legend_trace.update()
else:
diff --git a/shapeout2/gui/quick_view/qv_main.ui b/shapeout2/gui/quick_view/qv_main.ui
index 5cf83f6..757cbad 100644
--- a/shapeout2/gui/quick_view/qv_main.ui
+++ b/shapeout2/gui/quick_view/qv_main.ui
@@ -740,17 +740,53 @@
-
-
-
-
- 300
- 100
-
+
+
+ QLayout::SetNoConstraint
-
- background-color:transparent
+
+ 0
-
+
-
+
+
+
+ 300
+ 100
+
+
+
+ background-color:transparent
+
+
+
+ -
+
+
+
+ 300
+ 100
+
+
+
+ background-color:transparent
+
+
+
+ -
+
+
+
+ 300
+ 100
+
+
+
+ background-color:transparent
+
+
+
+
@@ -1065,17 +1101,71 @@
-
-
-
-
- 0
- 0
-
+
+
+ QLayout::SetNoConstraint
-
- opacity: 0
+
+ 0
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 100
+
+
+
+ opacity: 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 100
+
+
+
+ opacity: 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 100
+
+
+
+ opacity: 0
+
+
+
+
-
diff --git a/tests/data/blood_rbc_qpi_data.rtdc b/tests/data/blood_rbc_qpi_data.rtdc
new file mode 100644
index 0000000..045f32c
Binary files /dev/null and b/tests/data/blood_rbc_qpi_data.rtdc differ
diff --git a/tests/test_gui_quickview.py b/tests/test_gui_quickview.py
index 9657b4e..7e249fe 100644
--- a/tests/test_gui_quickview.py
+++ b/tests/test_gui_quickview.py
@@ -409,7 +409,7 @@ def test_subtract_background(qtbot):
# Test if checkbox is visible and checked by default
assert qv.checkBox_image_background.isVisible(), "Checkbox is not visible"
assert qv.checkBox_image_background.isChecked(), (
- "Checkbox is not checked by default")
+ "Checkbox is not checked by default")
# Test if CheckBox is hidden for dataset with no feature "image_bg"
@@ -427,9 +427,374 @@ def test_subtract_background(qtbot):
# Check if "Subtract Background"-CheckBox is hidden
# note: event tool is still open from test above
assert not qv2.checkBox_image_background.isVisible(), (
- " Subtract Background-Checkbox is visible for dataset "
- " that don't contain \"image_bg\"-feature"
- )
+ " Subtract Background-Checkbox is visible for dataset "
+ " that don't contain \"image_bg\"-feature"
+ )
+
+
+def test_auto_contrast(qtbot):
+ """auto contrast should change the displayed image"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "calibration_beads_47.rtdc"
+
+ mw.add_dataslot(paths=[path])
+
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Test if CheckBox is visible for dataset
+ # and if it is checked by default
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contrast.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox is not checked by default")
+
+ # Test if data changes when CheckBox is unchecked
+ image_with_contrast = qv.imageView_image.getImageItem().image
+
+ qtbot.mouseClick(qv.checkBox_image_contrast,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox should be unchecked")
+ image_without_contrast = qv.imageView_image.getImageItem().image
+
+ assert isinstance(image_with_contrast, np.ndarray)
+ assert isinstance(image_without_contrast, np.ndarray)
+ assert np.array_equal(image_with_contrast.shape,
+ image_without_contrast.shape)
+ assert not np.array_equal(image_with_contrast, image_without_contrast)
+
+
+def test_auto_contrast_qpi(qtbot):
+ """auto contrast should change the displayed image"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "blood_rbc_qpi_data.rtdc"
+
+ mw.add_dataslot(paths=[path])
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contrast.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox is not checked by default")
+
+ for view in [qv.imageView_image_amp, qv.imageView_image_pha]:
+ # Test if data changes when CheckBox is unchecked
+ qtbot.mouseClick(qv.checkBox_image_contrast,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox should be unchecked")
+ image_without_contrast = view.getImageItem().image
+
+ qtbot.mouseClick(qv.checkBox_image_contrast,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox should be checked")
+ image_with_contrast = view.getImageItem().image
+
+ assert isinstance(image_with_contrast, np.ndarray)
+ assert isinstance(image_without_contrast, np.ndarray)
+ assert np.array_equal(image_with_contrast.shape,
+ image_without_contrast.shape)
+ assert not np.array_equal(image_with_contrast, image_without_contrast)
+
+
+def test_auto_contrast_vmin_vmax_qpi(qtbot):
+ """auto contrast should change the vmin and vmax displayed to the user"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "blood_rbc_qpi_data.rtdc"
+
+ mw.add_dataslot(paths=[path])
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contrast.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox is not checked by default")
+ # turn off image contour, because our design currently changes the levels
+ qtbot.mouseClick(qv.checkBox_image_contour,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contour.isChecked(), (
+ "Checkbox should be unchecked")
+
+ # Test if data changes when CheckBox is unchecked
+ qtbot.mouseClick(qv.checkBox_image_contrast,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox should be unchecked")
+ assert qv.img_info["qpi_pha"]["kwargs"]["levels"] == (-3.14, +3.14)
+
+ # apply auto-contrast
+ qtbot.mouseClick(qv.checkBox_image_contrast,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert qv.checkBox_image_contrast.isChecked(), (
+ "Checkbox should be checked")
+ assert qv.img_info["qpi_pha"]["kwargs"]["levels"] == (-3.13, +3.13)
+
+
+def test_contour_display(qtbot):
+ """The contours should be a specific colour depending on the image"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "calibration_beads_47.rtdc"
+
+ mw.add_dataslot(paths=[path])
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contour.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contour.isChecked(), (
+ "Checkbox is not checked by default")
+
+ # Check contour data
+ image_with_contour = qv.imageView_image.getImageItem().image
+
+ qtbot.mouseClick(qv.checkBox_image_contour,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contour.isChecked(), (
+ "Checkbox should be unchecked")
+ image_without_contour = qv.imageView_image.getImageItem().image
+
+ assert isinstance(image_with_contour, np.ndarray)
+ assert isinstance(image_without_contour, np.ndarray)
+ assert np.array_equal(image_with_contour.shape,
+ image_without_contour.shape)
+ assert not np.array_equal(image_with_contour, image_without_contour)
+
+ # show that the contour pixels are our "red": [0.7, 0, 0]
+ ch_red = np.array([int(0.7 * 255), 0, 0])
+ assert np.sum(np.all(image_with_contour == ch_red, axis=-1))
+ assert not np.sum(np.all(image_without_contour == ch_red, axis=-1))
+
+
+def test_contour_display_qpi_amp(qtbot):
+ """The contours should be a specific colour depending on the image"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "blood_rbc_qpi_data.rtdc"
+
+ mw.add_dataslot(paths=[path])
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contour.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contour.isChecked(), (
+ "Checkbox is not checked by default")
+
+ # Check contour data qpi
+ image_with_contour = qv.imageView_image_amp.getImageItem().image
+ ch_red = [qv.imageView_image_amp.levelMax * 0.7,
+ qv.imageView_image_amp.levelMin,
+ qv.imageView_image_amp.levelMin]
+
+ # the red pixel should be in the amp image
+ assert not np.sum(np.all(image_with_contour == ch_red, axis=-1))
+
+ # now uncheck the contour
+ qtbot.mouseClick(qv.checkBox_image_contour,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contour.isChecked(), (
+ "Checkbox should be unchecked")
+ image_without_contour = qv.imageView_image_amp.getImageItem().image
+
+ assert np.array_equal(image_with_contour.shape,
+ image_without_contour.shape)
+ assert not np.array_equal(image_with_contour,
+ image_without_contour)
+
+ # the red pixel should not be in the amp image
+ assert not np.sum(np.all(image_without_contour == ch_red, axis=-1))
+
+
+def test_contour_display_qpi_pha(qtbot):
+ """The contours should be a specific colour depending on the image"""
+
+ # Create main window
+ mw = ShapeOut2()
+ qtbot.addWidget(mw)
+
+ path = datapath / "blood_rbc_qpi_data.rtdc"
+
+ mw.add_dataslot(paths=[path])
+ assert len(mw.pipeline.slot_ids) == 1, "we added those"
+ assert len(mw.pipeline.filter_ids) == 1, "automatically added"
+
+ # Activate dataslots
+ slot_id1 = mw.pipeline.slot_ids[0]
+ filt_id = mw.pipeline.filter_ids[0]
+ em1 = mw.block_matrix.get_widget(slot_id1, filt_id)
+
+ # Activate
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton)
+ # Open QuickView-window
+ qtbot.mouseClick(em1, QtCore.Qt.MouseButton.LeftButton,
+ QtCore.Qt.KeyboardModifier.ShiftModifier)
+
+ # Check if QuickView-window is open
+ assert mw.toolButton_quick_view.isChecked(), "Quickview not Open"
+
+ # Get QuickView instance
+ qv = mw.widget_quick_view
+
+ # Open event tool of QuickView
+ event_tool = qv.toolButton_event
+ qtbot.mouseClick(event_tool, QtCore.Qt.MouseButton.LeftButton)
+
+ # Test if checkbox is visible and checked by default
+ assert qv.checkBox_image_contour.isVisible(), "Checkbox is not visible"
+ assert qv.checkBox_image_contour.isChecked(), (
+ "Checkbox is not checked by default")
+
+ # Check contour data qpi_pha, it is not RGB
+ image_with_contour = qv.imageView_image_pha.getImageItem().image
+ lowest_cmap_val = qv.imageView_image_pha.levelMin
+
+ # the cmap's lowest value changed to black, and we use this value
+ # for the contour
+ assert not np.sum(np.all(image_with_contour == lowest_cmap_val, axis=-1))
+
+ # now uncheck the contour
+ qtbot.mouseClick(qv.checkBox_image_contour,
+ QtCore.Qt.MouseButton.LeftButton)
+ assert not qv.checkBox_image_contour.isChecked(), (
+ "Checkbox should be unchecked")
+ image_without_contour = qv.imageView_image_pha.getImageItem().image
+
+ assert np.array_equal(image_with_contour.shape,
+ image_without_contour.shape)
+ assert not np.array_equal(image_with_contour,
+ image_without_contour)
+ # there is one pixel actually set at the lowest value during auto-contrast
+ assert not np.sum(np.all(
+ image_without_contour == lowest_cmap_val, axis=-1))
def test_isoelasticity_lines_with_lut_selection(qtbot):