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