diff --git a/recOrder/acq/acquisition_workers.py b/recOrder/acq/acquisition_workers.py
index f80e7c3e..4b19ab5e 100644
--- a/recOrder/acq/acquisition_workers.py
+++ b/recOrder/acq/acquisition_workers.py
@@ -33,6 +33,20 @@
from recOrder.plugin.main_widget import MainWidget
+def _check_scale_mismatch(
+ recon_scale: np.array,
+ ngff_scale: tuple[float, float, float, float, float],
+) -> None:
+ if not np.allclose(np.array(ngff_scale[2:]), recon_scale, rtol=1e-2):
+ show_warning(
+ f"Requested reconstruction scale = {recon_scale} "
+ f"and OME-Zarr metadata scale = {ngff_scale[2:]} are not equal. "
+ "recOrder's reconstruction uses the GUI's "
+ "Z-step, pixel size, and magnification, "
+ "while napari's viewer uses the input array's metadata."
+ )
+
+
def _generate_reconstruction_config_from_gui(
reconstruction_config_path,
mode,
@@ -117,8 +131,8 @@ class PolarizationAcquisitionSignals(WorkerBaseSignals):
Custom Signals class that includes napari native signals
"""
- phase_image_emitter = Signal(object)
- bire_image_emitter = Signal(object)
+ phase_image_emitter = Signal(tuple)
+ bire_image_emitter = Signal(tuple)
phase_reconstructor_emitter = Signal(object)
aborted = Signal()
@@ -128,7 +142,7 @@ class BFAcquisitionSignals(WorkerBaseSignals):
Custom Signals class that includes napari native signals
"""
- phase_image_emitter = Signal(object)
+ phase_image_emitter = Signal(tuple)
phase_reconstructor_emitter = Signal(object)
aborted = Signal()
@@ -258,7 +272,7 @@ def work(self):
# Reconstruct snapped images
self.n_slices = stack.shape[2]
- phase = self._reconstruct()
+ phase, scale = self._reconstruct()
self._check_abort()
# Warn the user about axial
@@ -267,11 +281,18 @@ def work(self):
"Inverting the phase contrast. This affects the visualization and saved reconstruction."
)
+ # Warn user about mismatched scales
+ recon_scale = np.array(
+ (self.calib_window.z_step,)
+ + 2 * (self.calib_window.ps / self.calib_window.mag,)
+ )
+ _check_scale_mismatch(recon_scale, scale)
+
logging.info("Finished Acquisition")
logging.debug("Finished Acquisition")
# Emit the images and let thread know function is finished
- self.phase_image_emitter.emit(phase)
+ self.phase_image_emitter.emit((phase, scale))
def _reconstruct(self):
"""
@@ -301,8 +322,9 @@ def _reconstruct(self):
# Read reconstruction to pass to emitters
with open_ome_zarr(reconstruction_path, mode="r") as dataset:
phase = dataset["0/0/0/0"][0]
+ scale = dataset["0/0/0"].scale
- return phase
+ return phase, scale
def _cleanup_acq(self):
# Get display windows
@@ -479,7 +501,7 @@ def work(self):
# Reconstruct snapped images
self._check_abort()
self.n_slices = stack.shape[2]
- birefringence, phase = self._reconstruct()
+ birefringence, phase, scale = self._reconstruct()
self._check_abort()
# Warn the user about rotations and flips
@@ -492,12 +514,19 @@ def work(self):
"Applying a flip to orientation channel. This affects the visualization and saved reconstruction."
)
+ # Warn user about mismatched scales
+ recon_scale = np.array(
+ (self.calib_window.z_step,)
+ + 2 * (self.calib_window.ps / self.calib_window.mag,)
+ )
+ _check_scale_mismatch(recon_scale, scale)
+
logging.info("Finished Acquisition")
logging.debug("Finished Acquisition")
# Emit the images and let thread know function is finished
- self.bire_image_emitter.emit(birefringence)
- self.phase_image_emitter.emit(phase)
+ self.bire_image_emitter.emit((birefringence, scale))
+ self.phase_image_emitter.emit((phase, scale))
def _check_exposure(self) -> None:
"""
@@ -579,8 +608,9 @@ def _reconstruct(self):
phase = czyx_data[4]
except:
phase = None
+ scale = dataset["0/0/0"].scale
- return birefringence, phase
+ return birefringence, phase, scale
def _cleanup_acq(self):
# Get display windows
diff --git a/recOrder/calib/calibration_workers.py b/recOrder/calib/calibration_workers.py
index 6a780d69..1a5efd20 100644
--- a/recOrder/calib/calibration_workers.py
+++ b/recOrder/calib/calibration_workers.py
@@ -53,8 +53,8 @@ class BackgroundSignals(WorkerBaseSignals):
Custom Signals class that includes napari native signals
"""
- bg_image_emitter = Signal(object)
- bire_image_emitter = Signal(object)
+ bg_image_emitter = Signal(tuple)
+ bire_image_emitter = Signal(tuple)
bg_path_update_emitter = Signal(Path)
aborted = Signal()
@@ -358,6 +358,7 @@ def work(self):
with open_ome_zarr(reconstruction_path, mode="r") as dataset:
self.retardance = dataset["0/0/0/0"][0, 0, 0]
self.birefringence = dataset["0/0/0/0"][0, :, 0]
+ scale = dataset["0/0/0"].scale
# Save metadata file and emit imgs
meta_file = bg_path / "polarization_calibration.txt"
@@ -381,8 +382,10 @@ def work(self):
self._check_abort()
# Emit background images + background birefringence
- self.bg_image_emitter.emit(imgs)
- self.bire_image_emitter.emit((self.retardance, self.birefringence[1]))
+ self.bg_image_emitter.emit((imgs, scale))
+ self.bire_image_emitter.emit(
+ ((self.retardance, self.birefringence[1]), scale)
+ )
# Emit bg path
self.bg_path_update_emitter.emit(bg_path)
diff --git a/recOrder/io/utils.py b/recOrder/io/utils.py
index 6f9e9f9a..8ae2d8cc 100644
--- a/recOrder/io/utils.py
+++ b/recOrder/io/utils.py
@@ -1,7 +1,7 @@
import os
import textwrap
from pathlib import Path
-from typing import Literal
+from typing import Literal, Union
import numpy as np
import psutil
@@ -130,7 +130,10 @@ def generic_hsv_overlay(
def ret_ori_overlay(
- retardance, orientation, ret_max=10, cmap: Literal["JCh", "HSV"] = "JCh"
+ retardance,
+ orientation,
+ ret_max: Union[float, Literal["auto"]] = 10,
+ cmap: Literal["JCh", "HSV"] = "JCh",
):
"""
This function will create an overlay of retardance and orientation with two different colormap options.
@@ -154,6 +157,9 @@ def ret_ori_overlay(
f"{retardance.shape} vs. {orientation.shape}"
)
+ if ret_max == "auto":
+ ret_max = np.percentile(np.ravel(retardance), 99.99)
+
# Prepare input and output arrays
ret_ = np.clip(retardance, 0, ret_max) # clip and copy
# Convert 180 degree range into 360 to match periodicity of hue.
diff --git a/recOrder/plugin/gui.py b/recOrder/plugin/gui.py
index 007402ed..84cf37a7 100644
--- a/recOrder/plugin/gui.py
+++ b/recOrder/plugin/gui.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'gui.ui'
#
-# Created by: PyQt5 UI code generator 5.15.9
+# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -25,6 +25,11 @@ def setupUi(self, Form):
Form.setLayoutDirection(QtCore.Qt.LeftToRight)
self.gridLayout_7 = QtWidgets.QGridLayout(Form)
self.gridLayout_7.setObjectName("gridLayout_7")
+ self.label_logo = QtWidgets.QLabel(Form)
+ self.label_logo.setText("")
+ self.label_logo.setAlignment(QtCore.Qt.AlignCenter)
+ self.label_logo.setObjectName("label_logo")
+ self.gridLayout_7.addWidget(self.label_logo, 0, 0, 1, 1)
self.recon_status = QtWidgets.QGroupBox(Form)
font = QtGui.QFont()
font.setBold(True)
@@ -936,6 +941,16 @@ def setupUi(self, Form):
self.scrollAreaWidgetContents_5.setObjectName("scrollAreaWidgetContents_5")
self.gridLayout_4 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_5)
self.gridLayout_4.setObjectName("gridLayout_4")
+ self.label_orientation_image = QtWidgets.QLabel(self.scrollAreaWidgetContents_5)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.label_orientation_image.sizePolicy().hasHeightForWidth())
+ self.label_orientation_image.setSizePolicy(sizePolicy)
+ self.label_orientation_image.setText("")
+ self.label_orientation_image.setAlignment(QtCore.Qt.AlignCenter)
+ self.label_orientation_image.setObjectName("label_orientation_image")
+ self.gridLayout_4.addWidget(self.label_orientation_image, 6, 0, 1, 1)
self.DisplayOptions = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_5)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -951,22 +966,39 @@ def setupUi(self, Form):
self.gridLayout_17.setContentsMargins(-1, 12, -1, -1)
self.gridLayout_17.setVerticalSpacing(6)
self.gridLayout_17.setObjectName("gridLayout_17")
- self.le_sat_max = QtWidgets.QLineEdit(self.DisplayOptions)
+ self.cb_hue = QtWidgets.QComboBox(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.le_sat_max.setFont(font)
- self.le_sat_max.setFrame(False)
- self.le_sat_max.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.le_sat_max.setObjectName("le_sat_max")
- self.gridLayout_17.addWidget(self.le_sat_max, 8, 3, 1, 1)
- self.qbutton_create_overlay = QtWidgets.QPushButton(self.DisplayOptions)
+ self.cb_hue.setFont(font)
+ self.cb_hue.setFocusPolicy(QtCore.Qt.StrongFocus)
+ self.cb_hue.setObjectName("cb_hue")
+ self.gridLayout_17.addWidget(self.cb_hue, 8, 1, 1, 1)
+ self.slider_saturation = QtWidgets.QSlider(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.qbutton_create_overlay.setFont(font)
- self.qbutton_create_overlay.setObjectName("qbutton_create_overlay")
- self.gridLayout_17.addWidget(self.qbutton_create_overlay, 15, 0, 1, 4)
+ self.slider_saturation.setFont(font)
+ self.slider_saturation.setOrientation(QtCore.Qt.Horizontal)
+ self.slider_saturation.setObjectName("slider_saturation")
+ self.gridLayout_17.addWidget(self.slider_saturation, 10, 2, 1, 2)
+ self.line = QtWidgets.QFrame(self.DisplayOptions)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setItalic(False)
+ self.line.setFont(font)
+ self.line.setLineWidth(3)
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+ self.gridLayout_17.addWidget(self.line, 6, 0, 1, 4)
+ self.label_saturation = QtWidgets.QLabel(self.DisplayOptions)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setItalic(False)
+ self.label_saturation.setFont(font)
+ self.label_saturation.setObjectName("label_saturation")
+ self.gridLayout_17.addWidget(self.label_saturation, 10, 0, 1, 1)
self.le_overlay_slice = QtWidgets.QLineEdit(self.DisplayOptions)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -978,15 +1010,16 @@ def setupUi(self, Form):
font.setItalic(False)
self.le_overlay_slice.setFont(font)
self.le_overlay_slice.setObjectName("le_overlay_slice")
- self.gridLayout_17.addWidget(self.le_overlay_slice, 14, 0, 1, 1)
- self.slider_saturation = QtWidgets.QSlider(self.DisplayOptions)
+ self.gridLayout_17.addWidget(self.le_overlay_slice, 15, 0, 1, 1)
+ self.le_sat_min = QtWidgets.QLineEdit(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.slider_saturation.setFont(font)
- self.slider_saturation.setOrientation(QtCore.Qt.Horizontal)
- self.slider_saturation.setObjectName("slider_saturation")
- self.gridLayout_17.addWidget(self.slider_saturation, 9, 2, 1, 2)
+ self.le_sat_min.setFont(font)
+ self.le_sat_min.setInputMask("")
+ self.le_sat_min.setFrame(False)
+ self.le_sat_min.setObjectName("le_sat_min")
+ self.gridLayout_17.addWidget(self.le_sat_min, 9, 2, 1, 1)
self.cb_colormap = QtWidgets.QComboBox(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
@@ -1004,22 +1037,15 @@ def setupUi(self, Form):
self.cb_value.setFont(font)
self.cb_value.setFocusPolicy(QtCore.Qt.StrongFocus)
self.cb_value.setObjectName("cb_value")
- self.gridLayout_17.addWidget(self.cb_value, 11, 1, 1, 1)
- self.cb_hue = QtWidgets.QComboBox(self.DisplayOptions)
- font = QtGui.QFont()
- font.setBold(False)
- font.setItalic(False)
- self.cb_hue.setFont(font)
- self.cb_hue.setFocusPolicy(QtCore.Qt.StrongFocus)
- self.cb_hue.setObjectName("cb_hue")
- self.gridLayout_17.addWidget(self.cb_hue, 7, 1, 1, 1)
- self.label_value = QtWidgets.QLabel(self.DisplayOptions)
+ self.gridLayout_17.addWidget(self.cb_value, 12, 1, 1, 1)
+ self.le_val_min = QtWidgets.QLineEdit(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.label_value.setFont(font)
- self.label_value.setObjectName("label_value")
- self.gridLayout_17.addWidget(self.label_value, 11, 0, 1, 1)
+ self.le_val_min.setFont(font)
+ self.le_val_min.setFrame(False)
+ self.le_val_min.setObjectName("le_val_min")
+ self.gridLayout_17.addWidget(self.le_val_min, 11, 2, 1, 1)
self.cb_saturation = QtWidgets.QComboBox(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
@@ -1027,14 +1053,16 @@ def setupUi(self, Form):
self.cb_saturation.setFont(font)
self.cb_saturation.setFocusPolicy(QtCore.Qt.StrongFocus)
self.cb_saturation.setObjectName("cb_saturation")
- self.gridLayout_17.addWidget(self.cb_saturation, 9, 1, 1, 1)
- self.chb_display_volume = QtWidgets.QCheckBox(self.DisplayOptions)
+ self.gridLayout_17.addWidget(self.cb_saturation, 10, 1, 1, 1)
+ self.le_sat_max = QtWidgets.QLineEdit(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.chb_display_volume.setFont(font)
- self.chb_display_volume.setObjectName("chb_display_volume")
- self.gridLayout_17.addWidget(self.chb_display_volume, 14, 1, 1, 1)
+ self.le_sat_max.setFont(font)
+ self.le_sat_max.setFrame(False)
+ self.le_sat_max.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.le_sat_max.setObjectName("le_sat_max")
+ self.gridLayout_17.addWidget(self.le_sat_max, 9, 3, 1, 1)
self.slider_value = QtWidgets.QSlider(self.DisplayOptions)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -1047,17 +1075,21 @@ def setupUi(self, Form):
self.slider_value.setFont(font)
self.slider_value.setOrientation(QtCore.Qt.Horizontal)
self.slider_value.setObjectName("slider_value")
- self.gridLayout_17.addWidget(self.slider_value, 11, 2, 1, 2)
- self.line = QtWidgets.QFrame(self.DisplayOptions)
+ self.gridLayout_17.addWidget(self.slider_value, 12, 2, 1, 2)
+ self.label_value = QtWidgets.QLabel(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.line.setFont(font)
- self.line.setLineWidth(3)
- self.line.setFrameShape(QtWidgets.QFrame.HLine)
- self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
- self.line.setObjectName("line")
- self.gridLayout_17.addWidget(self.line, 6, 0, 1, 4)
+ self.label_value.setFont(font)
+ self.label_value.setObjectName("label_value")
+ self.gridLayout_17.addWidget(self.label_value, 12, 0, 1, 1)
+ self.chb_display_volume = QtWidgets.QCheckBox(self.DisplayOptions)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setItalic(False)
+ self.chb_display_volume.setFont(font)
+ self.chb_display_volume.setObjectName("chb_display_volume")
+ self.gridLayout_17.addWidget(self.chb_display_volume, 15, 1, 1, 1)
self.label_colormap = QtWidgets.QLabel(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
@@ -1065,47 +1097,44 @@ def setupUi(self, Form):
self.label_colormap.setFont(font)
self.label_colormap.setObjectName("label_colormap")
self.gridLayout_17.addWidget(self.label_colormap, 3, 0, 1, 1)
- self.label_hue = QtWidgets.QLabel(self.DisplayOptions)
- font = QtGui.QFont()
- font.setBold(False)
- font.setItalic(False)
- self.label_hue.setFont(font)
- self.label_hue.setObjectName("label_hue")
- self.gridLayout_17.addWidget(self.label_hue, 7, 0, 1, 1)
- self.label_saturation = QtWidgets.QLabel(self.DisplayOptions)
- font = QtGui.QFont()
- font.setBold(False)
- font.setItalic(False)
- self.label_saturation.setFont(font)
- self.label_saturation.setObjectName("label_saturation")
- self.gridLayout_17.addWidget(self.label_saturation, 9, 0, 1, 1)
- self.le_sat_min = QtWidgets.QLineEdit(self.DisplayOptions)
+ self.le_val_max = QtWidgets.QLineEdit(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.le_sat_min.setFont(font)
- self.le_sat_min.setInputMask("")
- self.le_sat_min.setFrame(False)
- self.le_sat_min.setObjectName("le_sat_min")
- self.gridLayout_17.addWidget(self.le_sat_min, 8, 2, 1, 1)
- self.le_val_min = QtWidgets.QLineEdit(self.DisplayOptions)
+ self.le_val_max.setFont(font)
+ self.le_val_max.setFrame(False)
+ self.le_val_max.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.le_val_max.setObjectName("le_val_max")
+ self.gridLayout_17.addWidget(self.le_val_max, 11, 3, 1, 1)
+ self.label_hue = QtWidgets.QLabel(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.le_val_min.setFont(font)
- self.le_val_min.setFrame(False)
- self.le_val_min.setObjectName("le_val_min")
- self.gridLayout_17.addWidget(self.le_val_min, 10, 2, 1, 1)
- self.le_val_max = QtWidgets.QLineEdit(self.DisplayOptions)
+ self.label_hue.setFont(font)
+ self.label_hue.setObjectName("label_hue")
+ self.gridLayout_17.addWidget(self.label_hue, 8, 0, 1, 1)
+ self.qbutton_create_overlay = QtWidgets.QPushButton(self.DisplayOptions)
font = QtGui.QFont()
font.setBold(False)
font.setItalic(False)
- self.le_val_max.setFont(font)
- self.le_val_max.setFrame(False)
- self.le_val_max.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.le_val_max.setObjectName("le_val_max")
- self.gridLayout_17.addWidget(self.le_val_max, 10, 3, 1, 1)
- self.gridLayout_4.addWidget(self.DisplayOptions, 2, 0, 1, 1, QtCore.Qt.AlignTop)
+ self.qbutton_create_overlay.setFont(font)
+ self.qbutton_create_overlay.setObjectName("qbutton_create_overlay")
+ self.gridLayout_17.addWidget(self.qbutton_create_overlay, 16, 0, 1, 4)
+ self.gridLayout_4.addWidget(self.DisplayOptions, 8, 0, 1, 1)
+ self.label = QtWidgets.QLabel(self.scrollAreaWidgetContents_5)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
+ self.label.setSizePolicy(sizePolicy)
+ self.label.setObjectName("label")
+ self.gridLayout_4.addWidget(self.label, 1, 0, 1, 1)
+ self.retMaxSlider = QtWidgets.QSlider(self.scrollAreaWidgetContents_5)
+ self.retMaxSlider.setMaximum(50)
+ self.retMaxSlider.setSliderPosition(25)
+ self.retMaxSlider.setOrientation(QtCore.Qt.Horizontal)
+ self.retMaxSlider.setObjectName("retMaxSlider")
+ self.gridLayout_4.addWidget(self.retMaxSlider, 2, 0, 1, 1)
self.label_orientation_legend = QtWidgets.QLabel(self.scrollAreaWidgetContents_5)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -1114,29 +1143,16 @@ def setupUi(self, Form):
self.label_orientation_legend.setSizePolicy(sizePolicy)
self.label_orientation_legend.setAlignment(QtCore.Qt.AlignCenter)
self.label_orientation_legend.setObjectName("label_orientation_legend")
- self.gridLayout_4.addWidget(self.label_orientation_legend, 0, 0, 1, 1)
- self.label_orientation_image = QtWidgets.QLabel(self.scrollAreaWidgetContents_5)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.label_orientation_image.sizePolicy().hasHeightForWidth())
- self.label_orientation_image.setSizePolicy(sizePolicy)
- self.label_orientation_image.setText("")
- self.label_orientation_image.setAlignment(QtCore.Qt.AlignCenter)
- self.label_orientation_image.setObjectName("label_orientation_image")
- self.gridLayout_4.addWidget(self.label_orientation_image, 1, 0, 1, 1)
+ self.gridLayout_4.addWidget(self.label_orientation_legend, 3, 0, 1, 1)
+ spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.gridLayout_4.addItem(spacerItem, 7, 0, 1, 1)
self.scrollArea_5.setWidget(self.scrollAreaWidgetContents_5)
self.gridLayout_18.addWidget(self.scrollArea_5, 0, 0, 2, 2)
self.tabWidget.addTab(self.Display, "")
self.gridLayout_7.addWidget(self.tabWidget, 2, 0, 1, 1)
- self.label_logo = QtWidgets.QLabel(Form)
- self.label_logo.setText("")
- self.label_logo.setAlignment(QtCore.Qt.AlignCenter)
- self.label_logo.setObjectName("label_logo")
- self.gridLayout_7.addWidget(self.label_logo, 0, 0, 1, 1)
self.retranslateUi(Form)
- self.tabWidget.setCurrentIndex(0)
+ self.tabWidget.setCurrentIndex(2)
self.tabWidget_2.setCurrentIndex(2)
self.cb_loglevel.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Form)
@@ -1313,19 +1329,20 @@ def retranslateUi(self, Form):
self.qbutton_stop_acq.setText(_translate("Form", "STOP"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Acquisition), _translate("Form", "Acquisition / Reconstruction"))
self.DisplayOptions.setTitle(_translate("Form", "Display Options"))
- self.le_sat_max.setText(_translate("Form", "80"))
- self.qbutton_create_overlay.setText(_translate("Form", "Create Overlay"))
+ self.label_saturation.setText(_translate("Form", "Saturation"))
self.le_overlay_slice.setPlaceholderText(_translate("Form", "Slice"))
+ self.le_sat_min.setText(_translate("Form", "20"))
self.cb_colormap.setItemText(0, _translate("Form", "HSV"))
self.cb_colormap.setItemText(1, _translate("Form", "JCh (Perceptually Uniform)"))
+ self.le_val_min.setText(_translate("Form", "20"))
+ self.le_sat_max.setText(_translate("Form", "80"))
self.label_value.setText(_translate("Form", "Value"))
self.chb_display_volume.setText(_translate("Form", "Use Full Volume"))
self.label_colormap.setText(_translate("Form", "BirefringenceOverlay Colormap"))
- self.label_hue.setText(_translate("Form", "Hue"))
- self.label_saturation.setText(_translate("Form", "Saturation"))
- self.le_sat_min.setText(_translate("Form", "20"))
- self.le_val_min.setText(_translate("Form", "20"))
self.le_val_max.setText(_translate("Form", "80"))
+ self.label_hue.setText(_translate("Form", "Hue"))
+ self.qbutton_create_overlay.setText(_translate("Form", "Create Overlay"))
+ self.label.setText(_translate("Form", "Overlay Retardance Maximum "))
self.label_orientation_legend.setText(_translate("Form", "Retardance Orientation Overlay Legend"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Display), _translate("Form", "Display"))
from pyqtgraph import PlotWidget
diff --git a/recOrder/plugin/gui.ui b/recOrder/plugin/gui.ui
index 73c94475..dde9cae4 100644
--- a/recOrder/plugin/gui.ui
+++ b/recOrder/plugin/gui.ui
@@ -32,6 +32,16 @@
Qt::LeftToRight
+ -
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
-
@@ -124,7 +134,7 @@
QTabWidget::North
- 0
+ 2
Qt::ElideMiddle
@@ -1752,7 +1762,23 @@
-
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+ -
@@ -1776,27 +1802,50 @@
6
-
-
-
+
-
+
false
false
-
- 80
+
+ Qt::StrongFocus
-
- false
+
+
+ -
+
+
+
+ false
+ false
+
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+ Qt::Horizontal
- -
-
+
-
+
+
+
+ false
+ false
+
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+
+ -
+
false
@@ -1804,11 +1853,11 @@
- Create Overlay
+ Saturation
- -
+
-
@@ -1827,16 +1876,22 @@
- -
-
+
-
+
false
false
-
- Qt::Horizontal
+
+
+
+
+ 20
+
+
+ false
@@ -1863,7 +1918,7 @@
- -
+
-
@@ -1876,21 +1931,8 @@
- -
-
-
-
- false
- false
-
-
-
- Qt::StrongFocus
-
-
-
- -
-
+
-
+
false
@@ -1898,11 +1940,14 @@
- Value
+ 20
+
+
+ false
- -
+
-
@@ -1915,8 +1960,8 @@
- -
-
+
-
+
false
@@ -1924,11 +1969,17 @@
- Use Full Volume
+ 80
+
+
+ false
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
+
-
@@ -1947,24 +1998,8 @@
- -
-
-
-
- false
- false
-
-
-
- 3
-
-
- Qt::Horizontal
-
-
-
- -
-
+
-
+
false
@@ -1972,12 +2007,12 @@
- BirefringenceOverlay Colormap
+ Value
- -
-
+
-
+
false
@@ -1985,12 +2020,12 @@
- Hue
+ Use Full Volume
- -
-
+
-
+
false
@@ -1998,31 +2033,31 @@
- Saturation
+ BirefringenceOverlay Colormap
- -
-
+
-
+
false
false
-
-
-
- 20
+ 80
false
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
- -
-
+
-
+
false
@@ -2030,15 +2065,12 @@
- 20
-
-
- false
+ Hue
- -
-
+
-
+
false
@@ -2046,21 +2078,15 @@
- 80
-
-
- false
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Create Overlay
- -
-
+
-
+
0
@@ -2068,15 +2094,25 @@
- Retardance Orientation Overlay Legend
+ Overlay Retardance Maximum
-
- Qt::AlignCenter
+
+
+ -
+
+
+ 50
+
+
+ 25
+
+
+ Qt::Horizontal
- -
-
+
-
+
0
@@ -2084,13 +2120,26 @@
-
+ Retardance Orientation Overlay Legend
Qt::AlignCenter
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
@@ -2099,16 +2148,6 @@
- -
-
-
-
-
-
- Qt::AlignCenter
-
-
-
diff --git a/recOrder/plugin/main_widget.py b/recOrder/plugin/main_widget.py
index afac8f15..f8bff315 100644
--- a/recOrder/plugin/main_widget.py
+++ b/recOrder/plugin/main_widget.py
@@ -236,6 +236,11 @@ def __init__(self, napari_viewer: Viewer):
self.viewer.layers.events.moved.connect(self.handle_layers_updated)
self.viewer.layers.events.inserted.connect(self.handle_layers_updated)
+ # Birefringence overlay controls
+ self.ui.retMaxSlider.sliderMoved[int].connect(
+ self.handle_ret_max_slider_move
+ )
+
# Commenting for 0.3.0. Consider debugging or deleting for 1.0.0.
# self.ui.cb_colormap.currentIndexChanged[int].connect(
# self.enter_colormap
@@ -341,6 +346,7 @@ def __init__(self, napari_viewer: Viewer):
self.reconstruction_data_path = None
self.reconstruction_data = None
self.calib_assessment_level = None
+ self.ret_max = 25
recorder_dir = dirname(dirname(dirname(os.path.abspath(__file__))))
self.worker = None
@@ -1133,6 +1139,7 @@ def _add_or_update_image_layer(
name: str,
cmap: str = "gray",
move_to_top: bool = True,
+ scale: tuple = 5 * (1,),
):
"""Add image layer of the given name if it does not exist, update existing layer otherwise.
@@ -1147,6 +1154,8 @@ def _add_or_update_image_layer(
move_to_top : bool, optional
whether to move the updated layer to the top of layers list, by default True
"""
+ scale = scale[-image.ndim :] # match shapes
+
if name in self.viewer.layers:
self.viewer.layers[name].data = image
if move_to_top:
@@ -1155,19 +1164,33 @@ def _add_or_update_image_layer(
self.viewer.layers.move(src_index, dest_index=-1)
else:
if cmap == "rgb":
- self.viewer.add_image(image, name=name, rgb=True)
+ self.viewer.add_image(
+ image,
+ name=name,
+ rgb=True,
+ scale=scale,
+ )
else:
- self.viewer.add_image(image, name=name, colormap=cmap)
+ self.viewer.add_image(
+ image,
+ name=name,
+ colormap=cmap,
+ scale=scale,
+ )
- @Slot(object)
+ @Slot(tuple)
def handle_bg_image_update(self, value):
- self._add_or_update_image_layer(value, "Background Images")
+ data, scale = value
+ self._add_or_update_image_layer(data, "Background Images", scale=scale)
- @Slot(object)
+ @Slot(tuple)
def handle_bg_bire_image_update(self, value):
- self._add_or_update_image_layer(value[0], "Background Retardance")
+ data, scale = value
+ self._add_or_update_image_layer(
+ data[0], "Background Retardance", scale=scale
+ )
self._add_or_update_image_layer(
- value[1], "Background Orientation", cmap="hsv"
+ data[1], "Background Orientation", cmap="hsv", scale=scale
)
def handle_layers_updated(self, event: Event):
@@ -1194,7 +1217,10 @@ def handle_layers_updated(self, event: Event):
f"'{retardance_name}', '{orientation_name}'"
)
self._draw_bire_overlay(
- retardance_name, orientation_name, overlay_name
+ retardance_name,
+ orientation_name,
+ overlay_name,
+ scale=layers[-1].scale,
)
# always display layers that start with "Orientation" in hsv
@@ -1205,7 +1231,11 @@ def handle_layers_updated(self, event: Event):
self.viewer.layers[orientation_name].colormap = "hsv"
def _draw_bire_overlay(
- self, retardance_name: str, orientation_name: str, overlay_name: str
+ self,
+ retardance_name: str,
+ orientation_name: str,
+ overlay_name: str,
+ scale: tuple,
):
def _layer_data(name: str):
data = self.viewer.layers[name].data
@@ -1215,40 +1245,58 @@ def _layer_data(name: str):
# this object will remain a dask `Array` after calling `compute()`
if any([("get_" in k) for k in data.dask.keys()]):
data: da.Array = data.compute()
+ else:
+ chunks = (data.ndim - 2) * (1,) + data.shape[
+ -2:
+ ] # needs to match
+ data = da.from_array(data, chunks=chunks)
return data
- def _draw(overlay):
- self._add_or_update_image_layer(overlay, overlay_name, cmap="rgb")
-
- retardance = _layer_data(retardance_name)
- orientation = _layer_data(orientation_name)
- worker = create_worker(
+ self.overlay_scale = scale
+ self.overlay_name = overlay_name
+ self.overlay_retardance = _layer_data(retardance_name)
+ self.overlay_orientation = _layer_data(orientation_name)
+ self.update_overlay_dask_array()
+
+ def update_overlay_dask_array(self):
+ self.rgb_chunks = (
+ (self.overlay_retardance.ndim - 2) * (1,)
+ + self.overlay_retardance.shape[-2:]
+ + (3,)
+ )
+ overlay = da.map_blocks(
ret_ori_overlay,
- retardance=retardance,
- orientation=orientation,
- ret_max=np.percentile(np.ravel(retardance), 99.99),
+ self.overlay_retardance,
+ self.overlay_orientation,
+ chunks=self.rgb_chunks,
+ new_axis=-1,
+ ret_max=self.ret_max,
cmap=self.colormap,
)
- worker.start()
- logging.info(f"Updating the birefringence overlay layer.")
- if retardance.size >= 2 * 2048 * 2048:
- show_info("Generating large overlay. This might take a moment...")
- worker.returned.connect(_draw)
- @Slot(object)
- def handle_bire_image_update(self, value: NDArray):
+ self._add_or_update_image_layer(
+ overlay, self.overlay_name, cmap="rgb", scale=self.overlay_scale
+ )
+
+ @Slot(tuple)
+ def handle_bire_image_update(self, value):
+ data, scale = value
+
# generate overlay in a separate thread
for i, channel in enumerate(("Retardance", "Orientation")):
name = channel
cmap = "gray" if channel != "Orientation" else "hsv"
- self._add_or_update_image_layer(value[i], name, cmap=cmap)
+ self._add_or_update_image_layer(
+ data[i], name, cmap=cmap, scale=scale
+ )
- @Slot(object)
+ @Slot(tuple)
def handle_phase_image_update(self, value):
+ phase, scale = value
name = "Phase2D" if self.acq_mode == "2D" else "Phase3D"
# Add new layer if none exists, otherwise update layer data
- self._add_or_update_image_layer(value, name)
+ self._add_or_update_image_layer(phase, name, scale=scale)
if "Phase" not in [
self.ui.cb_saturation.itemText(i)
@@ -2203,6 +2251,11 @@ def save_config(self):
self.config_reader.save_yaml(dir_=dir_, name=name)
+ @Slot(int)
+ def handle_ret_max_slider_move(self, value):
+ self.ret_max = value
+ self.update_overlay_dask_array()
+
# Commenting for 0.3.0. Consider debugging or deleting for 1.0.0.
# @Slot(int)
# def update_sat_scale(self):
diff --git a/recOrder/tests/acq_tests/test_acq.py b/recOrder/tests/acq_tests/test_acq.py
new file mode 100644
index 00000000..0152e230
--- /dev/null
+++ b/recOrder/tests/acq_tests/test_acq.py
@@ -0,0 +1,16 @@
+from unittest.mock import patch
+
+import numpy as np
+from recOrder.acq.acquisition_workers import _check_scale_mismatch
+
+
+def test_check_scale_mismatch():
+ warn_fn_path = "recOrder.acq.acquisition_workers.show_warning"
+ identity = np.array((1.0, 1.0, 1.0))
+ with patch(warn_fn_path) as mock:
+ _check_scale_mismatch(identity, (1, 1, 1, 1, 1))
+ mock.assert_not_called()
+ _check_scale_mismatch(identity, (1, 1, 1, 1, 1.001))
+ mock.assert_not_called()
+ _check_scale_mismatch(identity, (1, 1, 1, 1, 1.1))
+ mock.assert_called_once()