diff --git a/src/navigate/config/config.py b/src/navigate/config/config.py index 6fc100c90..5ab637b7c 100644 --- a/src/navigate/config/config.py +++ b/src/navigate/config/config.py @@ -360,6 +360,7 @@ def verify_experiment_config(manager, configuration): "is_centered": True, "center_x": 1024, "center_y": 1024, + "readout_time": 0, } if ( "CameraParameters" not in configuration["experiment"] @@ -371,58 +372,73 @@ def verify_experiment_config(manager, configuration): "CameraParameters", camera_parameters_dict_sample, ) - camera_setting_dict = configuration["experiment"]["CameraParameters"] - for k in camera_parameters_dict_sample: - if k not in camera_setting_dict.keys(): - camera_setting_dict[k] = camera_parameters_dict_sample[k] - # binning - if camera_setting_dict["binning"] not in ["1x1", "2x2", "4x4"]: - camera_setting_dict["binning"] = "1x1" - # x_pixels and y_pixels - try: - camera_setting_dict["x_pixels"] = int(camera_setting_dict["x_pixels"]) - except ValueError: - camera_setting_dict["x_pixels"] = camera_parameters_dict_sample["x_pixels"] - - try: - camera_setting_dict["y_pixels"] = int(camera_setting_dict["y_pixels"]) - except ValueError: - camera_setting_dict["y_pixels"] = camera_parameters_dict_sample["y_pixels"] - - # image width and height - if camera_setting_dict["x_pixels"] <= 0: - camera_setting_dict["x_pixels"] = camera_parameters_dict_sample["x_pixels"] - if camera_setting_dict["y_pixels"] <= 0: - camera_setting_dict["y_pixels"] = camera_parameters_dict_sample["y_pixels"] - x_binning = int(camera_setting_dict["binning"][0]) - y_binning = int(camera_setting_dict["binning"][2]) - img_x_pixels = camera_setting_dict["x_pixels"] // x_binning - img_y_pixels = camera_setting_dict["y_pixels"] // y_binning - camera_setting_dict["img_x_pixels"] = img_x_pixels - camera_setting_dict["img_y_pixels"] = img_y_pixels - if camera_setting_dict["is_centered"]: - camera_setting_dict["center_x"] = camera_setting_dict["x_pixels"] // 2 - camera_setting_dict["center_y"] = camera_setting_dict["y_pixels"] // 2 - - # sensor mode - if camera_setting_dict["sensor_mode"] not in ["Normal", "Light-Sheet"]: - camera_setting_dict["sensor_mode"] = "Normal" - if camera_setting_dict["readout_direction"] not in [ - "Top-to-Bottom", - "Bottom-to-Top", - "Bidirectional", - "Rev. Bidirectional", - ]: - camera_setting_dict["readout_direction"] = "Top-to-Bottom" - - # databuffer_size, number_of_pixels - for k in ["databuffer_size", "number_of_pixels", "frames_to_average"]: + microscope_names = [""] + list(configuration["configuration"]["microscopes"].keys()) + for microscope_name in microscope_names: + camera_setting_dict = configuration["experiment"]["CameraParameters"] + if microscope_name: + if ( + microscope_name not in camera_setting_dict + or type(camera_setting_dict[microscope_name]) is not DictProxy + ): + update_config_dict( + manager, + camera_setting_dict, + microscope_name, + camera_parameters_dict_sample, + ) + camera_setting_dict = camera_setting_dict[microscope_name] + + for k in camera_parameters_dict_sample: + if k not in camera_setting_dict.keys(): + camera_setting_dict[k] = camera_parameters_dict_sample[k] + # binning + if camera_setting_dict["binning"] not in ["1x1", "2x2", "4x4"]: + camera_setting_dict["binning"] = "1x1" + # x_pixels and y_pixels try: - camera_setting_dict[k] = int(camera_setting_dict[k]) + camera_setting_dict["x_pixels"] = int(camera_setting_dict["x_pixels"]) except ValueError: - camera_setting_dict[k] = camera_parameters_dict_sample[k] - if camera_setting_dict[k] < 1: - camera_setting_dict[k] = camera_parameters_dict_sample[k] + camera_setting_dict["x_pixels"] = camera_parameters_dict_sample["x_pixels"] + + try: + camera_setting_dict["y_pixels"] = int(camera_setting_dict["y_pixels"]) + except ValueError: + camera_setting_dict["y_pixels"] = camera_parameters_dict_sample["y_pixels"] + + # image width and height + if camera_setting_dict["x_pixels"] <= 0: + camera_setting_dict["x_pixels"] = camera_parameters_dict_sample["x_pixels"] + if camera_setting_dict["y_pixels"] <= 0: + camera_setting_dict["y_pixels"] = camera_parameters_dict_sample["y_pixels"] + x_binning = int(camera_setting_dict["binning"][0]) + y_binning = int(camera_setting_dict["binning"][2]) + img_x_pixels = camera_setting_dict["x_pixels"] // x_binning + img_y_pixels = camera_setting_dict["y_pixels"] // y_binning + camera_setting_dict["img_x_pixels"] = img_x_pixels + camera_setting_dict["img_y_pixels"] = img_y_pixels + if camera_setting_dict["is_centered"]: + camera_setting_dict["center_x"] = camera_setting_dict["x_pixels"] // 2 + camera_setting_dict["center_y"] = camera_setting_dict["y_pixels"] // 2 + + # sensor mode + if camera_setting_dict["sensor_mode"] not in ["Normal", "Light-Sheet"]: + camera_setting_dict["sensor_mode"] = "Normal" + if camera_setting_dict["readout_direction"] not in [ + "Top-to-Bottom", + "Bottom-to-Top", + "Bidirectional", + "Rev. Bidirectional", + ]: + camera_setting_dict["readout_direction"] = "Top-to-Bottom" + + # databuffer_size, number_of_pixels + for k in ["databuffer_size", "number_of_pixels", "frames_to_average"]: + try: + camera_setting_dict[k] = int(camera_setting_dict[k]) + except ValueError: + camera_setting_dict[k] = camera_parameters_dict_sample[k] + if camera_setting_dict[k] < 1: + camera_setting_dict[k] = camera_parameters_dict_sample[k] # stage parameters stage_dict_sample = {} diff --git a/src/navigate/controller/controller.py b/src/navigate/controller/controller.py index 0682da3fc..c7428889e 100644 --- a/src/navigate/controller/controller.py +++ b/src/navigate/controller/controller.py @@ -312,11 +312,18 @@ def update_buffer(self): Size dictated by x_pixels, y_pixels, an number_of_frames in configuration file. """ + microscope_name = self.configuration["experiment"]["MicroscopeState"][ + "microscope_name" + ] img_width = int( - self.configuration["experiment"]["CameraParameters"]["img_x_pixels"] + self.configuration["experiment"]["CameraParameters"][microscope_name][ + "img_x_pixels" + ] ) img_height = int( - self.configuration["experiment"]["CameraParameters"]["img_y_pixels"] + self.configuration["experiment"]["CameraParameters"][microscope_name][ + "img_y_pixels" + ] ) if img_width == self.img_width and img_height == self.img_height: return @@ -325,11 +332,6 @@ def update_buffer(self): self.img_width = img_width self.img_height = img_height - # virtual microscopes - for microscope_name in list(self.additional_microscopes.keys()): - self.destroy_virtual_microscope(microscope_name) - self.additional_microscopes = {} - def update_acquire_control(self): """Update the acquire control based on the current experiment parameters.""" self.view.acqbar.stop_stage.config( @@ -356,6 +358,7 @@ def change_microscope(self, microscope_name, zoom=None): self.stage_controller.initialize() self.channels_tab_controller.initialize() self.camera_setting_controller.update_camera_device_related_setting() + self.camera_setting_controller.populate_experiment_values() self.camera_setting_controller.calculate_physical_dimensions() self.camera_view_controller.update_snr() @@ -451,10 +454,15 @@ def update_experiment_setting(self): """ warning_message = self.camera_setting_controller.update_experiment_values() + microscope_name = self.configuration["experiment"]["MicroscopeState"][ + "microscope_name" + ] # set waveform template if self.acquire_bar_controller.mode in ["live", "single", "z-stack"]: - camera_setting = self.configuration["experiment"]["CameraParameters"] + camera_setting = self.configuration["experiment"]["CameraParameters"][ + microscope_name + ] if camera_setting["sensor_mode"] == "Light-Sheet" and camera_setting[ "readout_direction" ] in ["Bidirectional", "Rev. Bidirectional"]: @@ -482,6 +490,13 @@ def update_experiment_setting(self): # TODO: validate experiment dict warning_message += self.channels_tab_controller.verify_experiment_values() + + # additional microscopes + for microscope_name in self.additional_microscopes_configs: + if hasattr(self, f"{microscope_name.lower()}_camera_setting_controller"): + getattr( + self, f"{microscope_name.lower()}_camera_setting_controller" + ).update_experiment_values() if warning_message: return warning_message return "" @@ -557,6 +572,14 @@ def set_mode_of_sub(self, mode): self.camera_setting_controller.set_mode(mode) self.mip_setting_controller.set_mode(mode) self.waveform_tab_controller.set_mode(mode) + + # additional microscopes + for microscope_name in self.additional_microscopes_configs: + if hasattr(self, f"{microscope_name.lower()}_camera_setting_controller"): + getattr( + self, f"{microscope_name.lower()}_camera_setting_controller" + ).set_mode(mode) + if mode == "stop": # GUI Failsafe self.acquire_bar_controller.stop_acquire() @@ -987,15 +1010,18 @@ def capture_image(self, command, mode, *args): return self.acquire_bar_controller.view.acquire_btn.configure(text="Stop") self.acquire_bar_controller.view.acquire_btn.configure(state="normal") + microscope_name = self.configuration["experiment"]["MicroscopeState"][ + "microscope_name" + ] self.camera_view_controller.initialize_non_live_display( self.configuration["experiment"]["MicroscopeState"], - self.configuration["experiment"]["CameraParameters"], + self.configuration["experiment"]["CameraParameters"][microscope_name], ) self.mip_setting_controller.initialize_non_live_display( self.configuration["experiment"]["MicroscopeState"], - self.configuration["experiment"]["CameraParameters"], + self.configuration["experiment"]["CameraParameters"][microscope_name], ) self.stop_acquisition_flag = False @@ -1076,11 +1102,15 @@ def capture_image(self, command, mode, *args): def launch_additional_microscopes(self): """Launch additional microscopes.""" - def display_images(camera_view_controller, show_img_pipe, data_buffer): + def display_images( + microscope_name, camera_view_controller, show_img_pipe, data_buffer + ): """Display images from additional microscopes. Parameters ---------- + microscope_name : str + Microscope name camera_view_controller : CameraViewController Camera View Controller object. show_img_pipe : multiprocessing.Pipe @@ -1091,9 +1121,8 @@ def display_images(camera_view_controller, show_img_pipe, data_buffer): configuration file. """ camera_view_controller.initialize_non_live_display( - data_buffer, self.configuration["experiment"]["MicroscopeState"], - self.configuration["experiment"]["CameraParameters"], + self.configuration["experiment"]["CameraParameters"][microscope_name], ) images_received = 0 while True: @@ -1122,72 +1151,51 @@ def display_images(camera_view_controller, show_img_pipe, data_buffer): break images_received += 1 - # destroy unnecessary additional microscopes - temp = [] - for microscope_name in self.additional_microscopes: - if microscope_name not in self.additional_microscopes_configs: - temp.append(microscope_name) - for microscope_name in temp: + # destroy all additional microscopes + for microscope_name in list(self.additional_microscopes.keys()): self.destroy_virtual_microscope(microscope_name) + self.additional_microscopes = {} # show additional camera view popup for microscope_name in self.additional_microscopes_configs: - if microscope_name not in self.additional_microscopes: - show_img_pipe = self.model.create_pipe( - f"{microscope_name}_show_img_pipe" - ) - data_buffer = self.model.launch_virtual_microscope( - microscope_name, - self.additional_microscopes_configs[microscope_name], - ) + show_img_pipe = self.model.create_pipe(f"{microscope_name}_show_img_pipe") + data_buffer = self.model.launch_virtual_microscope( + microscope_name, + self.additional_microscopes_configs[microscope_name], + ) - self.additional_microscopes[microscope_name] = { - "show_img_pipe": show_img_pipe, - "data_buffer": data_buffer, - } - if ( - self.additional_microscopes[microscope_name].get( - "camera_view_controller", None - ) - is None - ): - popup_window = CameraViewPopupWindow(self.view, microscope_name) - camera_view_controller = CameraViewController( - popup_window.camera_view, self - ) - camera_view_controller.data_buffer = self.additional_microscopes[ - microscope_name - ]["data_buffer"] - popup_window.popup.bind("", camera_view_controller.resize) - self.additional_microscopes[microscope_name][ - "popup_window" - ] = popup_window - self.additional_microscopes[microscope_name][ - "camera_view_controller" - ] = camera_view_controller - popup_window.popup.protocol( - "WM_DELETE_WINDOW", - combine_funcs( - popup_window.popup.dismiss, - lambda: self.additional_microscopes[microscope_name].pop( - "camera_view_controller" - ), + self.additional_microscopes[microscope_name] = { + "show_img_pipe": show_img_pipe, + "data_buffer": data_buffer, + } + popup_window = CameraViewPopupWindow(self.view, microscope_name) + camera_view_controller = CameraViewController( + popup_window.camera_view, self + ) + camera_view_controller.data_buffer = self.additional_microscopes[ + microscope_name + ]["data_buffer"] + camera_view_controller.microscope_name = microscope_name + popup_window.popup.bind("", camera_view_controller.resize) + self.additional_microscopes[microscope_name]["popup_window"] = popup_window + self.additional_microscopes[microscope_name][ + "camera_view_controller" + ] = camera_view_controller + popup_window.popup.protocol( + "WM_DELETE_WINDOW", + combine_funcs( + popup_window.popup.dismiss, + lambda: self.additional_microscopes[microscope_name].pop( + "camera_view_controller" ), - ) - - # clear show_img_pipe - show_img_pipe = self.additional_microscopes[microscope_name][ - "show_img_pipe" - ] - while show_img_pipe.poll(): - image_id = show_img_pipe.recv() - if image_id == "stop": - break + ), + ) # start thread capture_img_thread = threading.Thread( target=display_images, args=( + microscope_name, self.additional_microscopes[microscope_name][ "camera_view_controller" ], diff --git a/src/navigate/controller/sub_controllers/camera_settings.py b/src/navigate/controller/sub_controllers/camera_settings.py index 1fa8a3984..4ff27cbd6 100644 --- a/src/navigate/controller/sub_controllers/camera_settings.py +++ b/src/navigate/controller/sub_controllers/camera_settings.py @@ -46,7 +46,7 @@ class CameraSettingController(GUIController): """Controller for the camera settings.""" - def __init__(self, view, parent_controller=None): + def __init__(self, view, parent_controller=None, microscope_name=None): """Initialize the camera setting controller. Parameters @@ -58,6 +58,9 @@ def __init__(self, view, parent_controller=None): """ super().__init__(view, parent_controller) + #: str: Camera name + self.microscope_name = microscope_name + #: bool: True if in initialization self.in_initialization = True @@ -200,35 +203,24 @@ def populate_experiment_values(self): self.in_initialization = True # Retrieve settings. - #: dict: Camera setting dictionary - self.camera_setting_dict = self.parent_controller.configuration["experiment"][ - "CameraParameters" - ] - #: dict: Microscope state dictionary - self.microscope_state_dict = self.parent_controller.configuration["experiment"][ + # Microscope state dictionary + microscope_state_dict = self.parent_controller.configuration["experiment"][ "MicroscopeState" ] + microscope_name = ( + self.microscope_name + if self.microscope_name + else microscope_state_dict["microscope_name"] + ) + #: dict: Camera setting dictionary + self.camera_setting_dict = self.parent_controller.configuration["experiment"][ + "CameraParameters" + ][microscope_name] # Readout Settings self.mode_widgets["Sensor"].set(self.camera_setting_dict["sensor_mode"]) - if self.camera_setting_dict["sensor_mode"] == "Normal": - self.mode_widgets["Readout"].set("") - self.mode_widgets["Pixels"].set("") - else: - if ( - self.camera_setting_dict["readout_direction"] - not in self.camera_readout_directions - ): - self.camera_setting_dict[ - "readout_direction" - ] = self.camera_readout_directions[0] - self.mode_widgets["Readout"].set( - self.camera_setting_dict["readout_direction"] - ) - self.mode_widgets["Pixels"].set( - self.camera_setting_dict["number_of_pixels"] - ) + self.update_sensor_mode() # ROI Settings if self.camera_setting_dict.get("is_centered", True): @@ -257,15 +249,13 @@ def populate_experiment_values(self): # Camera Framerate Info - 'exposure_time', 'readout_time', # 'framerate', 'frames_to_average' # Exposure time is currently for just the first active channel - channels = self.microscope_state_dict["channels"] + channels = microscope_state_dict["channels"] exposure_time = channels[list(channels.keys())[0]]["camera_exposure_time"] self.framerate_widgets["exposure_time"].set(exposure_time) self.framerate_widgets["frames_to_average"].set( self.camera_setting_dict["frames_to_average"] ) - # readout time - self.update_readout_time() # after initialization self.in_initialization = False @@ -370,7 +360,7 @@ def update_sensor_mode(self, *args): If we are in the Light Sheet mode, then we want the camera self.model['CameraParameters']['sensor_mode']) == 12 - If we are in the normal mode, then we want the camera + If we are in thef normal mode, then we want the camera self.model['CameraParameters']['sensor_mode']) == 1 Should initialize from the configuration file to the default version @@ -505,7 +495,7 @@ def update_fov(self, *args): # reset widgets for widget_name in ["Top_X", "Top_Y", "Bottom_X", "Bottom_Y"]: self.roi_widgets[widget_name].widget._toggle_error(False) - + self.camera_setting_dict["top_x"] = self.roi_widgets["Top_X"].get() self.camera_setting_dict["bottom_x"] = self.roi_widgets["Bottom_X"].get() self.camera_setting_dict["top_y"] = self.roi_widgets["Top_Y"].get() @@ -587,6 +577,9 @@ def calculate_physical_dimensions(self): "MicroscopeState" ] zoom = microscope_state_dict["zoom"] + # TODO: calculate fov_x and fov_y for additional microscopes + if self.microscope_name: + return microscope_name = microscope_state_dict["microscope_name"] pixel_size = self.parent_controller.configuration["configuration"][ "microscopes" @@ -655,9 +648,15 @@ def update_camera_device_related_setting(self): This function will update default width and height according to microscope name. """ - camera_config_dict = ( - self.parent_controller.configuration_controller.camera_config_dict - ) + if self.microscope_name is None: + camera_config_dict = ( + self.parent_controller.configuration_controller.camera_config_dict + ) + else: + camera_config_dict = self.parent_controller.configuration["configuration"][ + "microscopes" + ][self.microscope_name]["camera"] + if camera_config_dict is None: return @@ -667,10 +666,12 @@ def update_camera_device_related_setting(self): self.min_height = camera_config_dict.get("y_pixels_min", 4) self.default_pixel_size = camera_config_dict["pixel_size_in_microns"] - ( - self.default_width, - self.default_height, - ) = self.parent_controller.configuration_controller.camera_pixels + self.default_height = camera_config_dict["y_pixels"] + self.default_width = camera_config_dict["x_pixels"] + # ( + # self.default_width, + # self.default_height, + # ) = self.parent_controller.configuration_controller.camera_pixels self.trigger_source = camera_config_dict["trigger_source"] self.trigger_active = camera_config_dict["trigger_active"] self.readout_speed = camera_config_dict["readout_speed"] @@ -693,6 +694,19 @@ def update_camera_device_related_setting(self): self.roi_widgets["Bottom_X"].widget.config(to=self.default_width) self.roi_widgets["Bottom_Y"].widget.config(to=self.default_height) + # update camera setting_dict + microscope_state_dict = self.parent_controller.configuration["experiment"][ + "MicroscopeState" + ] + microscope_name = ( + self.microscope_name + if self.microscope_name + else microscope_state_dict["microscope_name"] + ) + self.camera_setting_dict = self.parent_controller.configuration["experiment"][ + "CameraParameters" + ][microscope_name] + def update_camera_parameters_silent(self, value): """Update GUI camera parameters diff --git a/src/navigate/controller/sub_controllers/camera_view.py b/src/navigate/controller/sub_controllers/camera_view.py index 8d95b8755..8250080ea 100644 --- a/src/navigate/controller/sub_controllers/camera_view.py +++ b/src/navigate/controller/sub_controllers/camera_view.py @@ -146,6 +146,9 @@ def __init__(self, view, parent_controller=None): #: str: The mode of the camera view controller. self.mode = "stop" + #: str: The microscope name + self.microscope_name = None + #: dict: The flip flags for the camera. self.flip_flags = None @@ -491,9 +494,18 @@ def initialize_non_live_display(self, microscope_state, camera_parameters): self.original_image_width = int(camera_parameters["img_x_pixels"]) self.original_image_height = int(camera_parameters["img_y_pixels"]) - self.flip_flags = ( - self.parent_controller.configuration_controller.camera_flip_flags - ) + if self.microscope_name is None: + self.flip_flags = ( + self.parent_controller.configuration_controller.camera_flip_flags + ) + else: + camera_config = self.parent_controller.configuration["configuration"][ + "microscopes" + ][self.microscope_name]["camera"] + self.flip_flags = { + "x": camera_config.get("flip_x", False), + "y": camera_config.get("flip_y", False) + } self.update_canvas_size() self.reset_display(False) diff --git a/src/navigate/controller/sub_controllers/menus.py b/src/navigate/controller/sub_controllers/menus.py index eb613a4a7..ae3e168d3 100644 --- a/src/navigate/controller/sub_controllers/menus.py +++ b/src/navigate/controller/sub_controllers/menus.py @@ -50,11 +50,13 @@ WaveformParameterPopupWindow, ) from navigate.view.popups.feature_list_popup import FeatureListPopup +from navigate.view.popups.camera_setting_popup import CameraSettingPopup from navigate.controller.sub_controllers.gui import GUIController from navigate.controller.sub_controllers import ( AutofocusPopupController, IlastikPopupController, CameraMapSettingPopupController, + CameraSettingController, WaveformPopupController, MicroscopePopupController, FeaturePopupController, @@ -64,7 +66,7 @@ ) from navigate.tools.file_functions import save_yaml_file, load_yaml_file from navigate.tools.decorators import FeatureList -from navigate.tools.common_functions import load_module_from_file +from navigate.tools.common_functions import load_module_from_file, combine_funcs # Misc. Local Imports @@ -467,6 +469,20 @@ def initialize_menus(self): ], } } + # camera setting menus + for microscope_name in self.parent_controller.configuration["configuration"][ + "microscopes" + ].keys(): + configuration_dict[self.view.menubar.menu_resolution][ + f"{microscope_name} Camera Setting" + ] = [ + "standard", + self.popup_camera_setting(microscope_name), + None, + None, + None, + "disabled", + ] self.populate_menu(configuration_dict) # plugins @@ -687,6 +703,12 @@ def populate_menu(self, menu_dict): menu.bind_all( menu_items[label][3], menu_items[label][1] ) + # set menu state + if len(menu_items[label]) > 5 and menu_items[label][5] in [ + "disabled", + "normal", + ]: + menu.entryconfig(label, state=menu_items[label][5]) def new_experiment(self, *args): """Create a new experiment file.""" @@ -1090,3 +1112,39 @@ def popup_uninstall_plugin(self, *args): self.uninstall_plugin_controller.showup() return self.uninstall_plugin_controller = UninstallPluginController(self.view, self) + + def popup_camera_setting(self, microscope_name): + def func(*args): + controller_name = f"{microscope_name.lower()}_camera_setting_controller" + if hasattr(self.parent_controller, controller_name): + camera_setting_controller = getattr( + self.parent_controller, controller_name + ) + camera_setting_controller.popup.popup.deiconify() + camera_setting_controller.popup.popup.attributes("-topmost", 1) + else: + popup = CameraSettingPopup(self.view, microscope_name) + camera_setting_controller = CameraSettingController( + popup.camera_setting, + self.parent_controller, + microscope_name=microscope_name, + ) + camera_setting_controller.populate_experiment_values() + camera_setting_controller.popup = popup + setattr( + self.parent_controller, controller_name, camera_setting_controller + ) + popup.popup.protocol( + "WM_DELETE_WINDOW", + combine_funcs( + popup.popup.dismiss, + camera_setting_controller.update_experiment_values(), + lambda: delattr(self.parent_controller, controller_name), + ), + ) + if self.parent_controller.acquire_bar_controller.is_acquiring: + camera_setting_controller.set_mode( + self.parent_controller.acquire_bar_controller.mode + ) + + return func diff --git a/src/navigate/controller/sub_controllers/microscope_popup.py b/src/navigate/controller/sub_controllers/microscope_popup.py index 7ef98921f..13ebe11f8 100644 --- a/src/navigate/controller/sub_controllers/microscope_popup.py +++ b/src/navigate/controller/sub_controllers/microscope_popup.py @@ -231,10 +231,23 @@ def confirm_microscope_setting(self): self.parent_controller.view.menubar.menu_resolution.entryconfig( microscope_name, state="normal" ) + # disable camera setting menu item + self.parent_controller.view.menubar.menu_resolution.entryconfig( + f"{microscope_name} Camera Setting", state="disabled" + ) + controller_name = f"{microscope_name.lower()}_camera_setting_controller" + if hasattr(self.parent_controller, controller_name): + camera_setting_controller = getattr(self.parent_controller, controller_name) + camera_setting_controller.popup.popup.dismiss() + delattr(self.parent_controller, controller_name) else: # disable self.parent_controller.view.menubar.menu_resolution.entryconfig( microscope_name, state="disabled" ) + # enable camera setting menu item + self.parent_controller.view.menubar.menu_resolution.entryconfig( + f"{microscope_name} Camera Setting", state="normal" + ) self.exit_func() diff --git a/src/navigate/model/data_sources/bdv_data_source.py b/src/navigate/model/data_sources/bdv_data_source.py index d66ae1396..0ee9cfec6 100644 --- a/src/navigate/model/data_sources/bdv_data_source.py +++ b/src/navigate/model/data_sources/bdv_data_source.py @@ -112,7 +112,7 @@ def get_slice(self, x, y, c, z=0, t=0, p=0, subdiv=0) -> npt.ArrayLike: return self.image[setup][z, y, x] def set_metadata_from_configuration_experiment( - self, configuration: DictProxy + self, configuration: DictProxy, microscope_name: str=None ) -> None: """Sets the metadata from according to the microscope configuration. @@ -120,11 +120,12 @@ def set_metadata_from_configuration_experiment( ---------- configuration : DictProxy The configuration experiment. + microscope_name : str + The microscope name """ - # Set rotation and affine transform information in metadata. self.metadata.get_affine_parameters(configuration=configuration) - return super().set_metadata_from_configuration_experiment(configuration) + return super().set_metadata_from_configuration_experiment(configuration, microscope_name) def write(self, data: npt.ArrayLike, **kw) -> None: """Writes 2D image to the data source. diff --git a/src/navigate/model/data_sources/data_source.py b/src/navigate/model/data_sources/data_source.py index 4e2adf602..d62d10a8d 100644 --- a/src/navigate/model/data_sources/data_source.py +++ b/src/navigate/model/data_sources/data_source.py @@ -205,7 +205,7 @@ def setup(self): pass def set_metadata_from_configuration_experiment( - self, configuration: DictProxy + self, configuration: DictProxy, microscope_name: str = None ) -> None: """Sets the metadata from according to the microscope configuration. @@ -213,8 +213,10 @@ def set_metadata_from_configuration_experiment( ---------- configuration : DictProxy Configuration experiment. + microscope_name : str + The microscope name """ - + self.metadata.active_microscope = microscope_name self.metadata.configuration = configuration self.get_shape_from_metadata() diff --git a/src/navigate/model/data_sources/pyramidal_data_source.py b/src/navigate/model/data_sources/pyramidal_data_source.py index 74df8f819..fb2cf012a 100644 --- a/src/navigate/model/data_sources/pyramidal_data_source.py +++ b/src/navigate/model/data_sources/pyramidal_data_source.py @@ -149,7 +149,7 @@ def nbytes(self) -> int: ).sum() def set_metadata_from_configuration_experiment( - self, configuration: DictProxy + self, configuration: DictProxy, microscope_name: str=None ) -> None: """Sets the metadata from according to the microscope configuration. @@ -157,11 +157,13 @@ def set_metadata_from_configuration_experiment( ---------- configuration : DictProxy The configuration experiment. + microscope_name : str + The microscope name """ self._subdivisions = None self._shapes = None - return super().set_metadata_from_configuration_experiment(configuration) + return super().set_metadata_from_configuration_experiment(configuration, microscope_name) def __getitem__(self, keys): """Magic method to get slice requests passed by, e.g., ds[:,2:3,...]. diff --git a/src/navigate/model/devices/camera/photometrics.py b/src/navigate/model/devices/camera/photometrics.py index 077b9e78e..c6bd41b7f 100644 --- a/src/navigate/model/devices/camera/photometrics.py +++ b/src/navigate/model/devices/camera/photometrics.py @@ -308,7 +308,7 @@ def calculate_light_sheet_exposure_time( full_chip_exposure_time = full_chip_exposure_time * 1000 # equations to calculate ASLM parameters - linedelay = self.camera_parameters["unitforlinedelay"] / 1000 + linedelay = self.camera_parameters.get("unitforlinedelay", 1) / 1000 ASLM_lineExposure = int( np.ceil(full_chip_exposure_time / (1 + (1 + nbrows) / shutter_width)) ) @@ -373,7 +373,7 @@ def set_binning(self, binning_string): self.y_pixels = int(self.y_pixels / self.y_binning) return True - def set_ROI(self, roi_height=3200, roi_width=3200, center_x=1600, center_y=1600): + def set_ROI(self, roi_width=3200, roi_height=3200, center_x=1600, center_y=1600): """Change the size of the active region on the camera. Parameters @@ -419,6 +419,7 @@ def set_ROI(self, roi_height=3200, roi_width=3200, center_x=1600, center_y=1600) # Set ROI self.camera_controller.set_roi(roi_left, roi_top, roi_width, roi_height) + self.x_pixels, self.y_pixels = self.camera_controller.shape() logger.info(f"Photometrics ROI shape, {self.camera_controller.shape()}") return self.x_pixels == roi_width and self.y_pixels == roi_height diff --git a/src/navigate/model/devices/remote_focus/base.py b/src/navigate/model/devices/remote_focus/base.py index 48bd55ffb..62dbf1b67 100644 --- a/src/navigate/model/devices/remote_focus/base.py +++ b/src/navigate/model/devices/remote_focus/base.py @@ -121,11 +121,11 @@ def adjust(self, exposure_times, sweep_times, offset=None): """ # to determine if the waveform has to be triangular sensor_mode = self.configuration["experiment"]["CameraParameters"][ - "sensor_mode" - ] + self.microscope_name + ]["sensor_mode"] readout_direction = self.configuration["experiment"]["CameraParameters"][ - "readout_direction" - ] + self.microscope_name + ]["readout_direction"] self.waveform_dict = dict.fromkeys(self.waveform_dict, None) microscope_state = self.configuration["experiment"]["MicroscopeState"] diff --git a/src/navigate/model/features/common_features.py b/src/navigate/model/features/common_features.py index b79a840a1..ca8aa3454 100644 --- a/src/navigate/model/features/common_features.py +++ b/src/navigate/model/features/common_features.py @@ -752,7 +752,8 @@ def signal_func(self): zip( ["x", "y", "z", "theta", "f"], [ - self.multiposition_table[self.current_idx - 1][i] + self.offset[i] + self.multiposition_table[self.current_idx - 1][i] + + self.offset[i] for i in range(5) ], ) @@ -972,6 +973,8 @@ def __init__( self.image_writer = None if saving_flag: self.image_writer = ImageWriter(model, sub_dir=saving_dir) + + self.prepare_next_channel = PrepareNextChannel(model) #: dict: A dictionary defining the configuration for the z-stack acquisition self.config_table = { @@ -1059,7 +1062,10 @@ def pre_signal_func(self): # restore f_pos, positions self.model.active_microscope.central_focus = None self.model.active_microscope.current_channel = 0 - self.model.active_microscope.prepare_next_channel() + for microscope_name in self.model.virtual_microscopes: + self.model.virtual_microscopes[microscope_name].current_channel = 0 + # prepare next channel + self.prepare_next_channel.signal_func() self.model.logger.debug( f"*** ZStack pre_signal_func: {self.positions}, {self.start_focus}, " @@ -1275,7 +1281,7 @@ def update_channel(self): self.current_channel_in_list + 1 ) % self.channels # not update DAQ tasks if there is a NI Galvo stage - self.model.active_microscope.prepare_next_channel() + self.prepare_next_channel.signal_func() if self.defocus is not None: self.current_focus_position += self.defocus[self.current_channel_in_list] @@ -1456,16 +1462,16 @@ def data_func(self, frame_ids): curr_fov_x = ( float( self.model.configuration["experiment"]["CameraParameters"][ - "x_pixels" - ] + microscope_name + ]["x_pixels"] ) * curr_pixel_size ) curr_fov_y = ( float( self.model.configuration["experiment"]["CameraParameters"][ - "y_pixels" - ] + microscope_name + ]["y_pixels"] ) * curr_pixel_size ) @@ -1520,16 +1526,16 @@ def data_func(self, frame_ids): fov_x = ( float( self.model.configuration["experiment"]["CameraParameters"][ - "x_pixels" - ] + microscope_name + ]["x_pixels"] ) * pixel_size ) fov_y = ( float( self.model.configuration["experiment"]["CameraParameters"][ - "y_pixels" - ] + microscope_name + ]["y_pixels"] ) * pixel_size ) @@ -1578,6 +1584,7 @@ class SetCameraParameters: def __init__( self, model, + microscope_name=None, sensor_mode="Normal", readout_direction=None, rolling_shutter_width=None, @@ -1605,6 +1612,8 @@ def __init__( "signal": {"main": self.signal_func, "cleanup": self.cleanup}, "node": {"device_related": True}, } + #: str: Microscope name + self.microscope_name = microscope_name #: str: The desired sensor mode to set for the camera. self.sensor_mode = sensor_mode @@ -1631,11 +1640,19 @@ def signal_func(self): bool A boolean value indicating the success of the resolution change process. """ + if ( + self.microscope_name is None + or self.microscope_name + not in self.model.configuration["configuration"]["microscopes"].keys() + ): + self.microscope_name = self.model.active_microscope_name update_flag = False update_sensor_mode = False - camera_parameters = self.model.configuration["experiment"]["CameraParameters"] + camera_parameters = self.model.configuration["experiment"]["CameraParameters"][ + self.microscope_name + ] camera_config = self.model.configuration["configuration"]["microscopes"][ - self.model.active_microscope_name + self.microscope_name ]["camera"] updated_value = [None] * 3 if ( diff --git a/src/navigate/model/features/image_writer.py b/src/navigate/model/features/image_writer.py index 46ed7377c..85a5e6cb1 100644 --- a/src/navigate/model/features/image_writer.py +++ b/src/navigate/model/features/image_writer.py @@ -54,6 +54,7 @@ class ImageWriter: def __init__( self, model, + microscope_name=None, data_buffer=None, sub_dir="", image_name=None, @@ -75,6 +76,7 @@ def __init__( image_name : str Name of the image to be saved. If None, a name will be generated """ + self.microscope_name = microscope_name #: navigate.model.model.Model: Navigate Model class for controlling # hardware/acquisition. self.model = model @@ -174,7 +176,7 @@ def __init__( # Pass experiment and configuration to metadata self.data_source.set_metadata_from_configuration_experiment( - self.model.configuration + self.model.configuration, microscope_name ) self.data_source.set_metadata(saving_config) diff --git a/src/navigate/model/features/restful_features.py b/src/navigate/model/features/restful_features.py index ecb282c79..309021deb 100644 --- a/src/navigate/model/features/restful_features.py +++ b/src/navigate/model/features/restful_features.py @@ -199,7 +199,9 @@ def update_setting(self): self.pieces_num = int(curr_pixel_size / pixel_size) self.pieces_size = ceil( float( - self.model.configuration["experiment"]["CameraParameters"]["x_pixels"] + self.model.configuration["experiment"]["CameraParameters"][ + self.model.active_microscope_name + ]["x_pixels"] ) / self.pieces_num ) @@ -209,13 +211,17 @@ def update_setting(self): # calculate corner (x,y) curr_fov_x = ( float( - self.model.configuration["experiment"]["CameraParameters"]["x_pixels"] + self.model.configuration["experiment"]["CameraParameters"][ + self.model.active_microscope_name + ]["x_pixels"] ) * curr_pixel_size ) curr_fov_y = ( float( - self.model.configuration["experiment"]["CameraParameters"]["y_pixels"] + self.model.configuration["experiment"]["CameraParameters"][ + self.model.active_microscope_name + ]["y_pixels"] ) * curr_pixel_size ) diff --git a/src/navigate/model/features/volume_search.py b/src/navigate/model/features/volume_search.py index 54ab41b01..823e3546c 100644 --- a/src/navigate/model/features/volume_search.py +++ b/src/navigate/model/features/volume_search.py @@ -302,8 +302,8 @@ def init_data_func(self): # consider the image as a square img_width = self.model.configuration["experiment"]["CameraParameters"][ - "x_pixels" - ] + microscope_name + ]["x_pixels"] # The target image size in pixels self.mag_ratio = int(curr_pixel_size / target_pixel_size) diff --git a/src/navigate/model/metadata_sources/metadata.py b/src/navigate/model/metadata_sources/metadata.py index c13235519..0c29eaf90 100644 --- a/src/navigate/model/metadata_sources/metadata.py +++ b/src/navigate/model/metadata_sources/metadata.py @@ -151,9 +151,8 @@ def set_from_configuration_experiment(self) -> None: self.configuration.get("experiment") is not None and self.configuration.get("configuration") is not None ): - self.active_microscope = self.configuration["experiment"][ - "MicroscopeState" - ]["microscope_name"] + if self.active_microscope is None: + self.active_microscope = self.configuration["experiment"]["MicroscopeState"]["microscope_name"] self.set_shape_from_configuration_experiment() self.set_stack_order_from_configuration_experiment() @@ -164,17 +163,21 @@ def set_shape_from_configuration_experiment(self) -> None: self.active_microscope ] zoom = state["zoom"] - pixel_size = float(scope["zoom"]["pixel_size"][zoom]) + pixel_size = float(scope["zoom"]["pixel_size"].get(zoom, 1)) self.dx, self.dy = pixel_size, pixel_size self.dz = float(abs(state["step_size"])) self.dt = float(state["timepoint_interval"]) # TODO: do we need to update the XML meta data accordingly? self.shape_x = int( - self.configuration["experiment"]["CameraParameters"]["img_x_pixels"] + self.configuration["experiment"]["CameraParameters"][ + self.active_microscope + ]["img_x_pixels"] ) self.shape_y = int( - self.configuration["experiment"]["CameraParameters"]["img_y_pixels"] + self.configuration["experiment"]["CameraParameters"][ + self.active_microscope + ]["img_y_pixels"] ) if (state["image_mode"] == "z-stack") or (state["image_mode"] == "customized"): self.shape_z = int(state["number_z_steps"]) diff --git a/src/navigate/model/microscope.py b/src/navigate/model/microscope.py index 4c622d17f..f576b81fb 100644 --- a/src/navigate/model/microscope.py +++ b/src/navigate/model/microscope.py @@ -412,28 +412,38 @@ def prepare_acquisition(self): self.camera.close_image_series() # set ROI - img_width = self.configuration["experiment"]["CameraParameters"]["x_pixels"] - img_height = self.configuration["experiment"]["CameraParameters"]["y_pixels"] - center_x = self.configuration["experiment"]["CameraParameters"]["center_x"] - center_y = self.configuration["experiment"]["CameraParameters"]["center_y"] + img_width = self.configuration["experiment"]["CameraParameters"][ + self.microscope_name + ]["x_pixels"] + img_height = self.configuration["experiment"]["CameraParameters"][ + self.microscope_name + ]["y_pixels"] + center_x = self.configuration["experiment"]["CameraParameters"][ + self.microscope_name + ]["center_x"] + center_y = self.configuration["experiment"]["CameraParameters"][ + self.microscope_name + ]["center_y"] self.camera.set_ROI(img_width, img_height, center_x, center_y) # Set Camera Sensor Mode - Must be done before camera is initialized. sensor_mode = self.configuration["experiment"]["CameraParameters"][ - "sensor_mode" - ] + self.microscope_name + ]["sensor_mode"] self.camera.set_sensor_mode(sensor_mode) if sensor_mode == "Light-Sheet": self.camera.set_readout_direction( self.configuration["experiment"]["CameraParameters"][ - "readout_direction" - ] + self.microscope_name + ]["readout_direction"] ) # set binning self.camera.set_binning( - self.configuration["experiment"]["CameraParameters"]["binning"] + self.configuration["experiment"]["CameraParameters"][self.microscope_name][ + "binning" + ] ) # Initialize Image Series - Attaches camera buffer and start imaging self.camera.initialize_image_series(self.data_buffer, self.number_of_frames) @@ -544,18 +554,18 @@ def calculate_exposure_sweep_times(self): readout_time = 0 readout_mode = self.configuration["experiment"]["CameraParameters"][ - "sensor_mode" - ] + self.microscope_name + ]["sensor_mode"] if readout_mode == "Normal": readout_time = self.camera.calculate_readout_time() - elif self.configuration["experiment"]["CameraParameters"][ + elif self.configuration["experiment"]["CameraParameters"][self.microscope_name][ "readout_direction" ] in ["Bidirectional", "Rev. Bidirectional"]: remote_focus_ramp_falling = 0 # set readout out time - self.configuration["experiment"]["CameraParameters"]["readout_time"] = ( - readout_time * 1000 - ) + self.configuration["experiment"]["CameraParameters"][self.microscope_name][ + "readout_time" + ] = (readout_time * 1000) for channel_key in microscope_state["channels"].keys(): channel = microscope_state["channels"][channel_key] @@ -571,8 +581,8 @@ def calculate_exposure_sweep_times(self): exposure_time, int( self.configuration["experiment"]["CameraParameters"][ - "number_of_pixels" - ] + self.microscope_name + ]["number_of_pixels"] ), ) if updated_exposure_time != exposure_time: @@ -668,7 +678,9 @@ def prepare_next_channel(self, update_daq_task_flag=True): # Camera Settings self.current_exposure_time = float(channel["camera_exposure_time"]) / 1000 if ( - self.configuration["experiment"]["CameraParameters"]["sensor_mode"] + self.configuration["experiment"]["CameraParameters"][self.microscope_name][ + "sensor_mode" + ] == "Light-Sheet" ): ( @@ -679,8 +691,8 @@ def prepare_next_channel(self, update_daq_task_flag=True): self.current_exposure_time, int( self.configuration["experiment"]["CameraParameters"][ - "number_of_pixels" - ] + self.microscope_name + ]["number_of_pixels"] ), ) self.camera.set_line_interval(camera_line_interval) diff --git a/src/navigate/model/model.py b/src/navigate/model/model.py index 557bd4d9b..9dd8e8dea 100644 --- a/src/navigate/model/model.py +++ b/src/navigate/model/model.py @@ -412,7 +412,9 @@ def get_data_buffer(self, img_width=512, img_height=512): if ( img_width != self.img_width or img_height != self.img_height - or self.configuration["experiment"]["CameraParameters"]["binning"] + or self.configuration["experiment"]["CameraParameters"][ + self.active_microscope_name + ]["binning"] != self.binning ): self.update_data_buffer(img_width, img_height) @@ -556,21 +558,25 @@ def run_command(self, command, *args, **kwargs): for m in self.virtual_microscopes: image_writer = ( ImageWriter( - self, - self.virtual_microscopes[m].data_buffer, - m, + model=self, + data_buffer=self.virtual_microscopes[m].data_buffer, + microscope_name=m, + sub_dir=m, saving_flags=self.data_buffer_saving_flags, saving_config=saving_config, - ).save_image + ) if self.is_save else None ) + if image_writer: + self.virtual_microscopes[m].image_writer = image_writer + threading.Thread( target=self.simplified_data_process, args=( self.virtual_microscopes[m], getattr(self, f"{m}_show_img_pipe"), - image_writer, + image_writer.save_image if image_writer else None, ), ).start() @@ -822,6 +828,8 @@ def end_acquisition(self): self.active_microscope.end_acquisition() for microscope_name in self.virtual_microscopes: self.virtual_microscopes[microscope_name].end_acquisition() + if hasattr(self.virtual_microscopes[microscope_name], "image_writer"): + self.virtual_microscopes[microscope_name].image_writer.close() plugin_obj = self.plugin_acquisition_modes.get(self.imaging_mode, None) if plugin_obj and hasattr(plugin_obj, "end_acquisition_model"): @@ -835,6 +843,7 @@ def end_acquisition(self): delattr(self, "data_container") if self.image_writer is not None: self.image_writer.close() + #: obj: Add on feature. self.addon_feature = None @@ -1275,10 +1284,17 @@ def launch_virtual_microscope(self, microscope_name, microscope_config): data_buffer : list List of data buffer. """ + img_height = self.configuration["experiment"]["CameraParameters"][ + microscope_name + ]["img_y_pixels"] + img_width = self.configuration["experiment"]["CameraParameters"][ + microscope_name + ]["img_x_pixels"] + # create databuffer data_buffer = [ - SharedNDArray(shape=(self.img_height, self.img_width), dtype="uint16") + SharedNDArray(shape=(img_height, img_width), dtype="uint16") for i in range(self.number_of_frames) ] @@ -1350,10 +1366,7 @@ def launch_virtual_microscope(self, microscope_name, microscope_config): ) # connect virtual microscope with data_buffer - microscope.update_data_buffer( - data_buffer, - self.number_of_frames - ) + microscope.update_data_buffer(data_buffer, self.number_of_frames) # add microscope to self.virtual_microscopes self.virtual_microscopes[microscope_name] = microscope diff --git a/src/navigate/view/popups/camera_setting_popup.py b/src/navigate/view/popups/camera_setting_popup.py new file mode 100644 index 000000000..ca353da14 --- /dev/null +++ b/src/navigate/view/popups/camera_setting_popup.py @@ -0,0 +1,142 @@ +# Copyright (c) 2021-2024 The University of Texas Southwestern Medical Center. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted for academic and research use only +# (subject to the limitations in the disclaimer below) +# provided that the following conditions are met: + +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. + +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# Standard Library Imports +import tkinter as tk + +# Third Party Imports + +# Local Imports +from navigate.view.custom_widgets.popup import PopUp +from navigate.view.main_window_content.camera_tab import CameraSettingsTab + +# p = __name__.split(".")[1] +# logger = logging.getLogger(p) + + +class CameraSettingPopup: + """Popup window for camera setting.""" + + def __init__(self, root, microscope_name, *args, **kwargs): + """Initialize the CameraSettingPopup class. + + Parameters + ---------- + root : tkinter.Tk + Root window of the application. + microscope_name : str + Name of the microscope. + args : list + List of arguments. + kwargs : dict + Dictionary of keyword arguments. + """ + # Creating popup window with this name and size/placement, PopUp is a + # Toplevel window + #: PopUp: Popup window for the camera view. + self.popup = PopUp( + root, + f"{microscope_name} Camera Setting", + "+320+180", + top=False, + transient=False, + ) + self.popup.resizable(tk.TRUE, tk.TRUE) + + # Storing the content frame of the popup, this will be the parent of + # the widgets + content_frame = self.popup.get_frame() + content_frame.columnconfigure(0, pad=5) + content_frame.columnconfigure(1, pad=5) + content_frame.rowconfigure(0, pad=5) + content_frame.rowconfigure(1, pad=5) + content_frame.rowconfigure(2, pad=5) + + # Formatting + tk.Grid.columnconfigure(content_frame, "all", weight=1) + tk.Grid.rowconfigure(content_frame, "all", weight=1) + + #: dict: Dictionary of all the input widgets. + self.inputs = {} + #: dict: Dictionary of all the buttons. + self.buttons = {} + + # Camera setting tab. + self.camera_setting = CameraSettingsTab(content_frame) + self.camera_setting.is_popup = True + self.camera_setting.is_docked = False + self.camera_setting.grid(row=0, column=0, sticky=tk.NSEW) + + # Getters + def get_variables(self): + """Get the variables tied to the widgets. + + This function returns a dictionary of all the variables that are tied to each + widget name. + + The key is the widget name, value is the variable associated. + + Returns + ------- + dict + Dictionary of all the variables that are tied to each widget name. + """ + variables = {} + for key, widget in self.inputs.items(): + variables[key] = widget.get_variable() + return variables + + def get_widgets(self): + """Get the dictionary that holds the input widgets. + + This function returns the dictionary that holds the input widgets. + The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + dict + Dictionary that holds the input widgets. + """ + return self.inputs + + def get_buttons(self): + """Get the dictionary that holds the buttons. + + This function returns the dictionary that holds the buttons. + The key is the button name, value is the button. + + Returns + ------- + dict + Dictionary that holds the buttons. + """ + return self.buttons diff --git a/test/controller/sub_controllers/test_camera_settings.py b/test/controller/sub_controllers/test_camera_settings.py index 39f24833e..f230f1846 100644 --- a/test/controller/sub_controllers/test_camera_settings.py +++ b/test/controller/sub_controllers/test_camera_settings.py @@ -183,20 +183,23 @@ def test_attr(self): assert hasattr(self.camera_settings, attr) def test_populate_experiment_values(self): + microscope_name = self.camera_settings.parent_controller.configuration[ + "experiment" + ]["MicroscopeState"]["microscope_name"] self.camera_settings.parent_controller.configuration["experiment"][ "CameraParameters" - ]["readout_time"] = 0.1 + ][microscope_name]["readout_time"] = 0.1 # Populate widgets with values from experiment file and check self.camera_settings.populate_experiment_values() camera_setting_dict = self.camera_settings.parent_controller.configuration[ "experiment" - ]["CameraParameters"] + ]["CameraParameters"][microscope_name] # Checking values altered are correct assert dict(self.camera_settings.camera_setting_dict) == dict( self.camera_settings.parent_controller.configuration["experiment"][ "CameraParameters" - ] + ][microscope_name] ) assert ( str(self.camera_settings.mode_widgets["Sensor"].get()) @@ -228,14 +231,12 @@ def test_populate_experiment_values(self): == camera_setting_dict["y_pixels"] ) - assert ( - self.camera_settings.roi_widgets["Top_X"].get() - == camera_setting_dict.get("top_x", 0) - ) - assert ( - self.camera_settings.roi_widgets["Top_Y"].get() - == camera_setting_dict.get("top_y", 0) - ) + assert self.camera_settings.roi_widgets[ + "Top_X" + ].get() == camera_setting_dict.get("top_x", 0) + assert self.camera_settings.roi_widgets[ + "Top_Y" + ].get() == camera_setting_dict.get("top_y", 0) if camera_setting_dict.get("is_centered", True): assert ( str(self.camera_settings.roi_widgets["Top_X"].widget["state"]) @@ -253,7 +254,9 @@ def test_populate_experiment_values(self): ) # Exposure Time - channels = self.camera_settings.microscope_state_dict["channels"] + channels = self.camera_settings.parent_controller.configuration["experiment"][ + "MicroscopeState" + ]["channels"] exposure_time = channels[list(channels.keys())[0]]["camera_exposure_time"] assert ( self.camera_settings.framerate_widgets["exposure_time"].get() @@ -268,11 +271,14 @@ def test_populate_experiment_values(self): @pytest.mark.parametrize("mode", ["Normal", "Light-Sheet"]) def test_update_experiment_values(self, mode): + microscope_name = self.camera_settings.parent_controller.configuration[ + "experiment" + ]["MicroscopeState"]["microscope_name"] # Setup basic default experiment self.camera_settings.camera_setting_dict = ( self.camera_settings.parent_controller.configuration["experiment"][ "CameraParameters" - ] + ][microscope_name] ) # Setting up new values in widgets @@ -329,9 +335,12 @@ def test_update_experiment_values(self, mode): @pytest.mark.parametrize("mode", ["Normal", "Light-Sheet"]) def test_update_sensor_mode(self, mode): self.camera_settings.populate_experiment_values() + microscope_name = self.camera_settings.parent_controller.configuration[ + "experiment" + ]["MicroscopeState"]["microscope_name"] camera_setting_dict = self.camera_settings.parent_controller.configuration[ "experiment" - ]["CameraParameters"] + ]["CameraParameters"][microscope_name] # Set mode self.camera_settings.mode_widgets["Sensor"].widget.set(mode) diff --git a/test/controller/test_controller.py b/test/controller/test_controller.py index ecba14072..a4b154567 100644 --- a/test/controller/test_controller.py +++ b/test/controller/test_controller.py @@ -70,8 +70,9 @@ def test_update_buffer(controller): assert controller.model.get_data_buffer.called is False # Change the buffer size - controller.configuration["experiment"]["CameraParameters"]["img_x_pixels"] = 100 - controller.configuration["experiment"]["CameraParameters"]["img_y_pixels"] = 100 + microscope_name = controller.configuration["experiment"]["MicroscopeState"]["microscope_name"] + controller.configuration["experiment"]["CameraParameters"][microscope_name]["img_x_pixels"] = 100 + controller.configuration["experiment"]["CameraParameters"][microscope_name]["img_y_pixels"] = 100 controller.update_buffer() # Make sure that the get_data_buffer method is called. @@ -510,8 +511,9 @@ def get_image_id(): return "stop" return numpy.random.randint(0, 10) - width = controller.configuration["experiment"]["CameraParameters"]["img_x_pixels"] - height = controller.configuration["experiment"]["CameraParameters"]["img_y_pixels"] + microscope_name = controller.configuration["experiment"]["MicroscopeState"]["microscope_name"] + width = controller.configuration["experiment"]["CameraParameters"][microscope_name]["img_x_pixels"] + height = controller.configuration["experiment"]["CameraParameters"][microscope_name]["img_y_pixels"] images = numpy.random.rand(10, width, height) controller.data_buffer = images work_thread = MagicMock() @@ -612,7 +614,8 @@ def test_waveform_template( controller.configuration["experiment"]["MicroscopeState"][ "image_mode" ] = acquisition_mode - controller.configuration["experiment"]["CameraParameters"]["number_of_pixels"] = 10 + microscope_name = controller.configuration["experiment"]["MicroscopeState"]["microscope_name"] + controller.configuration["experiment"]["CameraParameters"][microscope_name]["number_of_pixels"] = 10 controller.populate_experiment_setting(in_initialize=True) controller.camera_setting_controller.mode_widgets["Readout"].set(readout_direction) diff --git a/test/model/data_sources/test_zarr_data_source.py b/test/model/data_sources/test_zarr_data_source.py index b4ae318b5..cb1ad708a 100644 --- a/test/model/data_sources/test_zarr_data_source.py +++ b/test/model/data_sources/test_zarr_data_source.py @@ -8,7 +8,7 @@ from pydantic_ome_ngff.v04.multiscale import Group pydantic = True -except ImportError: +except (ImportError, TypeError): pydantic = False from navigate.tools.file_functions import delete_folder @@ -29,8 +29,9 @@ def zarr_ds(fn, multiposition, per_stack, z_stack, stop_early, size): timepoints = np.random.randint(1, 3) x_size, y_size = size - model.configuration["experiment"]["CameraParameters"]["x_pixels"] = x_size - model.configuration["experiment"]["CameraParameters"]["y_pixels"] = y_size + microscope_name = model.configuration["experiment"]["MicroscopeState"]["microscope_name"] + model.configuration["experiment"]["CameraParameters"][microscope_name]["x_pixels"] = x_size + model.configuration["experiment"]["CameraParameters"][microscope_name]["y_pixels"] = y_size model.img_width = x_size model.img_height = y_size diff --git a/test/model/features/test_common_features.py b/test/model/features/test_common_features.py index 3c62d63a7..cccf4ebe6 100644 --- a/test/model/features/test_common_features.py +++ b/test/model/features/test_common_features.py @@ -40,6 +40,7 @@ class TestZStack: @pytest.fixture(autouse=True) def _prepare_test(self, dummy_model_to_test_features): self.model = dummy_model_to_test_features + self.model.virtual_microscopes = {} self.config = self.model.configuration["experiment"]["MicroscopeState"] self.record_num = 0 self.feature_list = [[{"name": ZStackAcquisition}]] diff --git a/test/model/features/test_restful_features.py b/test/model/features/test_restful_features.py index d970cca35..f046f11f2 100644 --- a/test/model/features/test_restful_features.py +++ b/test/model/features/test_restful_features.py @@ -100,7 +100,9 @@ def setUp(self): "rest_api_config": {"Ilastik": {"url": "http://example.com/ilastik"}}, "experiment": { "MicroscopeState": {"microscope_name": "Nanoscale", "zoom": "1.0"}, - "CameraParameters": {"x_pixels": "2048", "y_pixels": "2048"}, + "CameraParameters": { + "Nanoscale": {"x_pixels": "2048", "y_pixels": "2048"}, + }, "StageParameters": { "x": "100", "y": "100", @@ -125,6 +127,7 @@ def setUp(self): self.mock_model.display_ilastik_segmentation = True self.mock_model.mark_ilastik_position = False self.mock_model.event_queue = MagicMock() + self.mock_model.active_microscope_name = "Nanoscale" self.ilastik_segmentation = IlastikSegmentation(self.mock_model) diff --git a/test/model/features/test_volume_search.py b/test/model/features/test_volume_search.py index 86b67f774..8bc3541bf 100644 --- a/test/model/features/test_volume_search.py +++ b/test/model/features/test_volume_search.py @@ -198,7 +198,12 @@ def test_box_volume_search(self): from navigate.tools.sdf import volume_from_sdf, box M = int(self.config["number_z_steps"]) - self.model.configuration["experiment"]["CameraParameters"]["x_pixels"] = self.N + microscope_name = self.model.configuration["experiment"]["MicroscopeState"][ + "microscope_name" + ] + self.model.configuration["experiment"]["CameraParameters"][microscope_name][ + "x_pixels" + ] = self.N self.lxy = ( np.random.randint(int(0.1 * self.N), int(0.4 * self.N)) * self.curr_pixel_size diff --git a/test/model/metadata_sources/test_metadata.py b/test/model/metadata_sources/test_metadata.py index b5ed232ae..ae85c55c9 100644 --- a/test/model/metadata_sources/test_metadata.py +++ b/test/model/metadata_sources/test_metadata.py @@ -41,8 +41,15 @@ def test_metadata_shape(dummy_model): md.configuration = dummy_model.configuration - txs = dummy_model.configuration["experiment"]["CameraParameters"]["img_x_pixels"] - tys = dummy_model.configuration["experiment"]["CameraParameters"]["img_y_pixels"] + microscope_name = dummy_model.configuration["experiment"]["MicroscopeState"][ + "microscope_name" + ] + txs = dummy_model.configuration["experiment"]["CameraParameters"][microscope_name][ + "img_x_pixels" + ] + tys = dummy_model.configuration["experiment"]["CameraParameters"][microscope_name][ + "img_y_pixels" + ] tzs = dummy_model.configuration["experiment"]["MicroscopeState"]["number_z_steps"] tts = dummy_model.configuration["experiment"]["MicroscopeState"]["timepoints"] tcs = sum(