diff --git a/software/control/_def.py b/software/control/_def.py index 73afff70..6aae1d4e 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -74,15 +74,6 @@ class Acquisition: NUMBER_OF_FOVS_PER_AF = 3 IMAGE_FORMAT = "bmp" IMAGE_DISPLAY_SCALING_FACTOR = 0.3 - PSEUDO_COLOR = False - MERGE_CHANNELS = False - PSEUDO_COLOR_MAP = { - "405": {"hex": 0x0000FF}, # blue - "488": {"hex": 0x00FF00}, # green - "561": {"hex": 0xFFCF00}, # yellow - "638": {"hex": 0xFF0000}, # red - "730": {"hex": 0x770000}, # dark red - } DX = 0.9 DY = 0.9 DZ = 1.5 @@ -657,8 +648,10 @@ class SOFTWARE_POS_LIMIT: IS_HCS = False DYNAMIC_REGISTRATION = False STITCH_COMPLETE_ACQUISITION = False + +# Pseudo color settings CHANNEL_COLORS_MAP = { - "405": {"hex": 0x3300FF, "name": "blue"}, + "405": {"hex": 0x20ADF8, "name": "bop blue"}, "488": {"hex": 0x1FFF00, "name": "green"}, "561": {"hex": 0xFFCF00, "name": "yellow"}, "638": {"hex": 0xFF0000, "name": "red"}, @@ -667,6 +660,8 @@ class SOFTWARE_POS_LIMIT: "G": {"hex": 0x1FFF00, "name": "green"}, "B": {"hex": 0x3300FF, "name": "blue"}, } +SAVE_IN_PSEUDO_COLOR = False +MERGE_CHANNELS = False # Emission filter wheel USE_ZABER_EMISSION_FILTER_WHEEL = False diff --git a/software/control/core/core.py b/software/control/core/core.py index 829d1ea0..79879212 100644 --- a/software/control/core/core.py +++ b/software/control/core/core.py @@ -1908,20 +1908,21 @@ def save_image(self, image, file_ID, config, current_path): elif MULTIPOINT_BF_SAVING_OPTION == "Green Channel Only": image = image[:, :, 1] - if Acquisition.PSEUDO_COLOR: + if SAVE_IN_PSEUDO_COLOR: image = self.return_pseudo_colored_image(image, config) - if Acquisition.MERGE_CHANNELS: + if MERGE_CHANNELS: self._save_merged_image(image, file_ID, current_path) iio.imwrite(saving_path, image) def _save_merged_image(self, image, file_ID, current_path): self.image_count += 1 + if self.image_count == 1: self.merged_image = image else: - self.merged_image += image + self.merged_image = np.maximum(self.merged_image, image) if self.image_count == len(self.selected_configurations): if image.dtype == np.uint16: @@ -1931,19 +1932,22 @@ def _save_merged_image(self, image, file_ID, current_path): iio.imwrite(saving_path, self.merged_image) self.image_count = 0 + return def return_pseudo_colored_image(self, image, config): if "405 nm" in config.name: - image = self.grayscale_to_rgb(image, Acquisition.PSEUDO_COLOR_MAP["405"]["hex"]) + image = self.grayscale_to_rgb(image, CHANNEL_COLORS_MAP["405"]["hex"]) elif "488 nm" in config.name: - image = self.grayscale_to_rgb(image, Acquisition.PSEUDO_COLOR_MAP["488"]["hex"]) + image = self.grayscale_to_rgb(image, CHANNEL_COLORS_MAP["488"]["hex"]) elif "561 nm" in config.name: - image = self.grayscale_to_rgb(image, Acquisition.PSEUDO_COLOR_MAP["561"]["hex"]) + image = self.grayscale_to_rgb(image, CHANNEL_COLORS_MAP["561"]["hex"]) elif "638 nm" in config.name: - image = self.grayscale_to_rgb(image, Acquisition.PSEUDO_COLOR_MAP["638"]["hex"]) + image = self.grayscale_to_rgb(image, CHANNEL_COLORS_MAP["638"]["hex"]) elif "730 nm" in config.name: - image = self.grayscale_to_rgb(image, Acquisition.PSEUDO_COLOR_MAP["730"]["hex"]) + image = self.grayscale_to_rgb(image, CHANNEL_COLORS_MAP["730"]["hex"]) + else: + image = np.stack([image] * 3, axis=-1) return image @@ -1961,7 +1965,7 @@ def update_napari(self, image, config_name, k): self.napari_layers_init.emit(image.shape[0], image.shape[1], image.dtype) pos = self.stage.get_pos() objective_magnification = str(int(self.objectiveStore.get_current_objective_info()["magnification"])) - self.napari_layers_update.emit(image, pos.x_mm, pos.y_mm, k, objective_magnification + "x_" + config_name) + self.napari_layers_update.emit(image, pos.x_mm, pos.y_mm, k, objective_magnification + "x " + config_name) def handle_dpc_generation(self, current_round_images): keys_to_check = [ @@ -3767,11 +3771,9 @@ def update_laser_af_settings( self.autofocus_configurations[objective].set_reference_image(crop_image) -class ConfigurationManager(QObject): +class ConfigurationManager: """Main configuration manager that coordinates channel and autofocus configurations.""" - signal_profile_loaded = Signal() - def __init__( self, channel_manager: ChannelConfigurationManager, @@ -3821,8 +3823,6 @@ def load_profile(self, profile_name: str) -> None: if self.laser_af_manager: self.laser_af_manager.load_configurations(objective) - self.signal_profile_loaded.emit() - def create_new_profile(self, profile_name: str) -> None: """Create a new profile using current configurations.""" new_profile_path = self.base_config_path / profile_name diff --git a/software/control/gui_hcs.py b/software/control/gui_hcs.py index a299e32d..b4315b27 100644 --- a/software/control/gui_hcs.py +++ b/software/control/gui_hcs.py @@ -572,9 +572,7 @@ def waitForMicrocontroller(self, timeout=5.0, error_message=None): def loadWidgets(self): # Initialize all GUI widgets if ENABLE_SPINNING_DISK_CONFOCAL: - self.spinningDiskConfocalWidget = widgets.SpinningDiskConfocalWidget( - self.xlight, self.channelConfigurationManager - ) + self.spinningDiskConfocalWidget = widgets.SpinningDiskConfocalWidget(self.xlight) if ENABLE_NL5: import control.NL5Widget as NL5Widget @@ -1022,7 +1020,7 @@ def makeConnections(self): self.wellSelectionWidget.signal_wellSelected.connect(self.wellplateMultiPointWidget.update_well_coordinates) self.objectivesWidget.signal_objective_changed.connect(self.wellplateMultiPointWidget.update_coordinates) - self.configurationManager.signal_profile_loaded.connect( + self.profileWidget.signal_profile_changed.connect( lambda: self.liveControlWidget.update_microscope_mode_by_name( self.liveControlWidget.currentConfiguration.name ) @@ -1040,7 +1038,7 @@ def slot_settings_changed_laser_af(): self.laserAutofocusControlWidget.update_init_state() self.laserAutofocusSettingWidget.update_values() - self.configurationManager.signal_profile_loaded.connect(slot_settings_changed_laser_af) + self.profileWidget.signal_profile_changed.connect(slot_settings_changed_laser_af) self.objectivesWidget.signal_objective_changed.connect(slot_settings_changed_laser_af) self.laserAutofocusSettingWidget.signal_newExposureTime.connect( self.cameraSettingWidget_focus_camera.set_exposure_time @@ -1082,6 +1080,16 @@ def slot_settings_changed_laser_af(): self.piezoWidget.update_displacement_um_display ) + if ENABLE_SPINNING_DISK_CONFOCAL: + self.spinningDiskConfocalWidget.signal_toggle_confocal_widefield.connect( + self.channelConfigurationManager.toggle_confocal_widefield + ) + self.spinningDiskConfocalWidget.signal_toggle_confocal_widefield.connect( + lambda: self.liveControlWidget.update_microscope_mode_by_name( + self.liveControlWidget.currentConfiguration.name + ) + ) + self.camera.set_callback(self.streamHandler.on_new_frame) def setup_movement_updater(self): diff --git a/software/control/widgets.py b/software/control/widgets.py index 445a5f45..08dc96ea 100644 --- a/software/control/widgets.py +++ b/software/control/widgets.py @@ -643,10 +643,11 @@ def show_cross_correlation_result(self, value): class SpinningDiskConfocalWidget(QWidget): - def __init__(self, xlight, config_manager=None): - super(SpinningDiskConfocalWidget, self).__init__() - self.config_manager = config_manager + signal_toggle_confocal_widefield = Signal(bool) + + def __init__(self, xlight): + super(SpinningDiskConfocalWidget, self).__init__() self.xlight = xlight @@ -660,8 +661,7 @@ def __init__(self, xlight, config_manager=None): self.disk_position_state = self.xlight.get_disk_position() - if self.config_manager: - self.config_manager.toggle_confocal_widefield(self.disk_position_state) + self.signal_toggle_confocal_widefield.emit(self.disk_position_state) # signal initial state if self.disk_position_state == 1: self.btn_toggle_widefield.setText("Switch to Widefield") @@ -780,9 +780,8 @@ def toggle_disk_position(self): else: self.disk_position_state = self.xlight.set_disk_position(1) self.btn_toggle_widefield.setText("Switch to Widefield") - if self.config_manager is not None: - self.config_manager.toggle_confocal_widefield(self.disk_position_state) self.enable_all_buttons() + self.signal_toggle_confocal_widefield.emit(self.disk_position_state) def toggle_motor(self): self.disable_all_buttons() @@ -5271,6 +5270,7 @@ def finishedSaving(self, output_path, dtype): self.output_path = output_path def extractWavelength(self, name): + # TODO: Use the 'color' attribute of the ChannelMode object # Split the string and find the wavelength number immediately after "Fluorescence" parts = name.split() if "Fluorescence" in parts: