diff --git a/labscript_devices/IMAQdxCamera/blacs_tab.ui b/labscript_devices/IMAQdxCamera/blacs_tab.ui
index ec9c890c..4a6542e8 100644
--- a/labscript_devices/IMAQdxCamera/blacs_tab.ui
+++ b/labscript_devices/IMAQdxCamera/blacs_tab.ui
@@ -44,6 +44,51 @@
+ -
+
+
+ Auto scale image levels and histogram range
+
+
+ Auto Levels
+
+
+
+ :/qtutils/fugue/gradient.png:/qtutils/fugue/gradient.png
+
+
+ true
+
+
+
+ -
+
+
+ Set current image as the background, to be subtracted from displayed images
+
+
+ Set background
+
+
+
+ :/qtutils/fugue/image--plus.png:/qtutils/fugue/image--plus.png
+
+
+
+ -
+
+
+ Clear the background image
+
+
+ Clear background
+
+
+
+ :/qtutils/fugue/image--minus.png:/qtutils/fugue/image--minus.png
+
+
+
-
diff --git a/labscript_devices/IMAQdxCamera/blacs_tabs.py b/labscript_devices/IMAQdxCamera/blacs_tabs.py
index f3b42226..0dd52ac1 100644
--- a/labscript_devices/IMAQdxCamera/blacs_tabs.py
+++ b/labscript_devices/IMAQdxCamera/blacs_tabs.py
@@ -50,13 +50,15 @@ class ImageReceiver(ZMQServer):
"""ZMQServer that receives images on a zmq.REP socket, replies 'ok', and updates the
image widget and fps indicator"""
- def __init__(self, image_view, label_fps):
+ def __init__(self, image_view, label_fps, autolevels_button):
ZMQServer.__init__(self, port=None, dtype='multipart')
self.image_view = image_view
self.label_fps = label_fps
self.last_frame_time = None
self.frame_rate = None
- self.update_event = None
+ self.image = None
+ self.bg_image = None
+ self.autolevels_button = autolevels_button
@inmain_decorator(wait_for_return=True)
def handler(self, data):
@@ -80,14 +82,10 @@ def handler(self, data):
else:
self.frame_rate = 1 / dt
self.last_frame_time = this_frame_time
- if self.image_view.image is None:
- # First time setting an image. Do autoscaling etc:
- self.image_view.setImage(image.swapaxes(-1, -2))
- else:
- # Updating image. Keep zoom/pan/levels/etc settings.
- self.image_view.setImage(
- image.swapaxes(-1, -2), autoRange=False, autoLevels=False
- )
+
+ self.image = image
+ self.update_image()
+
# Update fps indicator:
if self.frame_rate is not None:
self.label_fps.setText(f"{self.frame_rate:.01f} fps")
@@ -105,6 +103,34 @@ def handler(self, data):
QtGui.QApplication.instance().sendPostedEvents()
return self.NO_RESPONSE
+ @inmain_decorator(wait_for_return=True)
+ def update_image(self):
+ image = self.image
+ if self.bg_image is not None:
+ image = self.image.astype(float) - self.bg_image.astype(float)
+ autolevels = self.autolevels_button.isChecked()
+ if self.image_view.image is None:
+ # First time setting an image. Do autoscaling etc:
+ self.image_view.setImage(image.swapaxes(-1, -2))
+ else:
+ self.image_view.setImage(
+ image.swapaxes(-1, -2),
+ autoRange=False,
+ autoLevels=autolevels,
+ autoHistogramRange=autolevels,
+ )
+
+ @inmain_decorator()
+ def set_bg_image(self):
+ self.bg_image = self.image
+ self.update_image()
+
+ @inmain_decorator()
+ def clear_bg_image(self):
+ self.bg_image = None
+ self.update_image()
+
+
class IMAQdxCameraTab(DeviceTab):
# Subclasses may override this if all they do is replace the worker class with a
@@ -128,6 +154,8 @@ def initialise_GUI(self):
self.ui.pushButton_snap.clicked.connect(self.on_snap_clicked)
self.ui.pushButton_attributes.clicked.connect(self.on_attributes_clicked)
self.ui.toolButton_nomax.clicked.connect(self.on_reset_rate_clicked)
+ self.ui.pushButton_set_bg.clicked.connect(self.on_set_bg_clicked)
+ self.ui.pushButton_clear_bg.clicked.connect(self.on_clear_bg_clicked)
self.attributes_dialog = UiLoader().load(attributes_ui_filepath)
self.attributes_dialog.setParent(self.ui.parent())
@@ -146,6 +174,7 @@ def initialise_GUI(self):
)
self.ui.horizontalLayout.addWidget(self.image)
self.ui.pushButton_stop.hide()
+ self.ui.pushButton_clear_bg.hide()
self.ui.doubleSpinBox_maxrate.hide()
self.ui.toolButton_nomax.hide()
self.ui.label_fps.hide()
@@ -163,7 +192,9 @@ def initialise_GUI(self):
widget.setSizePolicy(size_policy)
# Start the image receiver ZMQ server:
- self.image_receiver = ImageReceiver(self.image, self.ui.label_fps)
+ self.image_receiver = ImageReceiver(
+ self.image, self.ui.label_fps, self.ui.pushButtonAutoLevels
+ )
self.acquiring = False
self.supports_smart_programming(self.use_smart_programming)
@@ -173,7 +204,8 @@ def get_save_data(self):
'attribute_visibility': self.attributes_dialog.comboBox.currentText(),
'acquiring': self.acquiring,
'max_rate': self.ui.doubleSpinBox_maxrate.value(),
- 'colormap': repr(self.image.ui.histogram.gradient.saveState())
+ 'colormap': repr(self.image.ui.histogram.gradient.saveState()),
+ 'autolevels': self.ui.pushButtonAutoLevels.isChecked(),
}
def restore_save_data(self, save_data):
@@ -188,6 +220,7 @@ def restore_save_data(self, save_data):
self.image.ui.histogram.gradient.restoreState(
ast.literal_eval(save_data['colormap'])
)
+ self.ui.pushButtonAutoLevels.setChecked(save_data.get('autolevels', False))
def initialise_workers(self):
@@ -259,6 +292,16 @@ def on_stop_clicked(self, button):
self.acquiring = False
self.stop_continuous()
+ def on_set_bg_clicked(self, button):
+ self.image_receiver.set_bg_image()
+ self.ui.pushButton_set_bg.hide()
+ self.ui.pushButton_clear_bg.show()
+
+ def on_clear_bg_clicked(self, button):
+ self.image_receiver.clear_bg_image()
+ self.ui.pushButton_set_bg.show()
+ self.ui.pushButton_clear_bg.hide()
+
def on_copy_clicked(self, button):
text = self.attributes_dialog.plainTextEdit.toPlainText()
clipboard = QtGui.QApplication.instance().clipboard()