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):