diff --git a/src/navigate/config/config.py b/src/navigate/config/config.py index 5ab637b7c..d1fb2e597 100644 --- a/src/navigate/config/config.py +++ b/src/navigate/config/config.py @@ -873,11 +873,16 @@ def verify_waveform_constants(manager, configuration): # other_constants waveform_dict = configuration["waveform_constants"] + microscope_name = configuration["configuration"]["microscopes"].keys()[0] other_constants_dict = { "remote_focus_settle_duration": "0", "percent_smoothing": "0", "remote_focus_delay": "0", "remote_focus_ramp_falling": "5", + "camera_settle_duration": "0", + "camera_delay": configuration["configuration"]["microscopes"][microscope_name][ + "camera" + ]["delay"], } if ( "other_constants" not in waveform_dict.keys() @@ -893,7 +898,7 @@ def verify_waveform_constants(manager, configuration): try: float(waveform_dict["other_constants"][k]) except (ValueError, KeyError): - waveform_dict["other_constants"][k] = "0" + waveform_dict["other_constants"][k] = other_constants_dict[k] def verify_configuration(manager, configuration): diff --git a/src/navigate/controller/sub_controllers/waveform_popup.py b/src/navigate/controller/sub_controllers/waveform_popup.py index cf240d623..1388998d9 100644 --- a/src/navigate/controller/sub_controllers/waveform_popup.py +++ b/src/navigate/controller/sub_controllers/waveform_popup.py @@ -39,6 +39,9 @@ from navigate.controller.sub_controllers.gui import GUIController from navigate.tools.file_functions import save_yaml_file from navigate.tools.common_functions import combine_funcs +from navigate.view.popups.waveform_parameter_popup_window import ( + AdvancedWaveformParameterPopupWindow, +) # Logger Setup p = __name__.split(".")[1] @@ -176,6 +179,12 @@ def __init__(self, view, parent_controller, waveform_constants_path): self.variables["Ramp_falling"].trace_add( "write", self.update_waveform_parameters ) + self.variables["camera_delay"].trace_add( + "write", self.update_waveform_parameters + ) + self.variables["camera_settle_duration"].trace_add( + "write", self.update_waveform_parameters + ) # Save waveform constants self.view.get_buttons()["Save"].configure(command=self.save_waveform_constants) @@ -185,6 +194,10 @@ def __init__(self, view, parent_controller, waveform_constants_path): command=self.toggle_waveform_state ) + self.view.get_buttons()["advanced_galvo_setting"].configure( + command=self.display_advanced_setting_window + ) + # Save waveform constants upon closing the popup window self.view.popup.protocol( "WM_DELETE_WINDOW", @@ -196,6 +209,11 @@ def __init__(self, view, parent_controller, waveform_constants_path): ), ) + # All channels use the same galvo parameters + self.widgets["all_channels"].widget.configure( + command=self.set_galvo_to_all_channels + ) + # Populate widgets self.widgets["Mode"].widget["values"] = list( self.resolution_info["remote_focus_constants"].keys() @@ -231,23 +249,23 @@ def configure_widget_range(self): "precision", 0 ) ) - increment = int( + self.increment = int( self.configuration_controller.remote_focus_dict["hardware"].get("step", 0) ) if precision == 0: precision = -4 if self.laser_max < 1 else -3 elif precision > 0: precision = -precision - if increment == 0: - increment = 0.0001 if self.laser_max < 1 else 0.001 - elif increment < 0: - increment = -increment + if self.increment == 0: + self.increment = 0.0001 if self.laser_max < 1 else 0.001 + elif self.increment < 0: + self.increment = -self.increment # set ranges of value for those lasers for laser in self.lasers: self.widgets[laser + " Amp"].widget.configure(from_=self.laser_min) self.widgets[laser + " Amp"].widget.configure(to=self.laser_max) - self.widgets[laser + " Amp"].widget.configure(increment=increment) + self.widgets[laser + " Amp"].widget.configure(increment=self.increment) self.widgets[laser + " Amp"].widget.set_precision(precision) self.widgets[laser + " Amp"].widget.trigger_focusout_validation() # TODO: The offset bounds should adjust based on the amplitude bounds, @@ -255,7 +273,7 @@ def configure_widget_range(self): # in update_remote_focus_settings() self.widgets[laser + " Off"].widget.configure(from_=self.laser_min) self.widgets[laser + " Off"].widget.configure(to=self.laser_max) - self.widgets[laser + " Off"].widget.configure(increment=increment) + self.widgets[laser + " Off"].widget.configure(increment=self.increment) self.widgets[laser + " Off"].widget.set_precision(precision) self.widgets[laser + " Off"].widget.trigger_focusout_validation() @@ -264,7 +282,7 @@ def configure_widget_range(self): galvo_max = d["hardware"]["max"] self.widgets[galvo + " Amp"].widget.configure(from_=galvo_min) self.widgets[galvo + " Amp"].widget.configure(to=galvo_max) - self.widgets[galvo + " Amp"].widget.configure(increment=increment) + self.widgets[galvo + " Amp"].widget.configure(increment=self.increment) self.widgets[galvo + " Amp"].widget.set_precision(precision) self.widgets[galvo + " Amp"].widget["state"] = "normal" self.widgets[galvo + " Amp"].widget.trigger_focusout_validation() @@ -273,13 +291,13 @@ def configure_widget_range(self): # in update_remote_focus_settings() self.widgets[galvo + " Off"].widget.configure(from_=galvo_min) self.widgets[galvo + " Off"].widget.configure(to=galvo_max) - self.widgets[galvo + " Off"].widget.configure(increment=increment) + self.widgets[galvo + " Off"].widget.configure(increment=self.increment) self.widgets[galvo + " Off"].widget.set_precision(precision) self.widgets[galvo + " Off"].widget["state"] = "normal" self.widgets[galvo + " Off"].widget.trigger_focusout_validation() self.widgets[galvo + " Freq"].widget.configure(from_=0) - self.widgets[galvo + " Freq"].widget.configure(increment=increment) + self.widgets[galvo + " Freq"].widget.configure(increment=self.increment) self.widgets[galvo + " Freq"].widget.set_precision(precision) self.widgets[galvo + " Freq"].widget["state"] = "normal" self.widgets[galvo + " Freq"].widget.trigger_focusout_validation() @@ -418,6 +436,15 @@ def show_laser_info(self, *args): self.resolution_info["other_constants"]["remote_focus_ramp_falling"] ) self.widgets["Ramp_falling"].widget.trigger_focusout_validation() + self.widgets["camera_delay"].set( + self.resolution_info["other_constants"].get("camera_delay", 2) + ) + self.widgets["camera_delay"].widget.trigger_focusout_validation() + self.widgets["camera_settle_duration"].set( + self.resolution_info["other_constants"]["camera_settle_duration"] + ) + self.widgets["camera_settle_duration"].widget.trigger_focusout_validation() + self.update_waveform_parameters_flag = True # update resolution value in central controller (menu) @@ -427,6 +454,13 @@ def show_laser_info(self, *args): # reconfigure widgets self.configure_widget_range() + # update advanced setting + if hasattr(self, "advanced_setting_popup"): + self.display_galvo_advanced_setting() + galvo_factor = self.resolution_info["other_constants"].get( + "galvo_factor", "none" + ) + self.set_galvo_factor(galvo_factor) def update_remote_focus_settings(self, name, laser, remote_focus_name): """Update remote focus settings in memory. @@ -510,6 +544,10 @@ def update_waveform_parameters(self, *args, **wargs): duty_cycle = float(self.widgets["Duty"].widget.get()) smoothing = float(self.widgets["Smoothing"].widget.get()) ramp_falling = float(self.widgets["Ramp_falling"].widget.get()) + camera_delay = float(self.widgets["camera_delay"].widget.get()) + camera_settle_duration = float( + self.widgets["camera_settle_duration"].widget.get() + ) except ValueError: return @@ -523,6 +561,10 @@ def update_waveform_parameters(self, *args, **wargs): ] = ramp_falling self.resolution_info["other_constants"]["remote_focus_delay"] = delay self.resolution_info["other_constants"]["percent_smoothing"] = smoothing + self.resolution_info["other_constants"]["camera_delay"] = camera_delay + self.resolution_info["other_constants"][ + "camera_settle_duration" + ] = camera_settle_duration # Pass the values to the parent controller. self.event_id = self.view.popup.after( @@ -748,3 +790,227 @@ def restore_amplitude(self): ] = self.amplitude_dict[galvo] self.widgets[galvo + " Amp"].widget.config(state="normal") self.amplitude_dict = None + + def display_advanced_setting_window(self): + """Display advanced galvo setting window""" + if hasattr(self, "advanced_setting_popup"): + galvo_factor = self.resolution_info["other_constants"].get( + "galvo_factor", "none" + ) + self.advanced_setting_popup.variables["galvo_factor"].set(galvo_factor) + self.advanced_setting_popup.popup.deiconify() + self.advanced_setting_popup.popup.attributes("-topmost", 1) + else: + self.advanced_setting_popup = AdvancedWaveformParameterPopupWindow( + self.view + ) + # close the window + self.advanced_setting_popup.popup.protocol( + "WM_DELETE_WINDOW", + combine_funcs( + # save parameters + self.advanced_setting_popup.popup.dismiss, + lambda: delattr(self, "advanced_setting_popup"), + ), + ) + # register functioons + self.advanced_setting_popup.variables["galvo_factor"].trace_add( + "write", self.display_galvo_advanced_setting + ) + galvo_factor = self.resolution_info["other_constants"].get( + "galvo_factor", "none" + ) + self.advanced_setting_popup.variables["galvo_factor"].set(galvo_factor) + + def display_galvo_advanced_setting(self, *args, **kwargs): + """Generate dynamic galvo advanced setting widgets""" + galvo_factor = self.advanced_setting_popup.variables["galvo_factor"].get() + if galvo_factor == "none": + factors = ["All"] + galvos = [] + for i in range(self.configuration_controller.galvo_num): + galvo_name = f"Galvo {i}" + if self.resolution not in self.galvo_setting[galvo_name].keys(): + continue + galvos.append( + [ + ( + self.galvo_setting[galvo_name][self.resolution][self.mag][ + "amplitude" + ], + self.galvo_setting[galvo_name][self.resolution][self.mag][ + "offset" + ], + ) + ] + ) + else: + if galvo_factor == "channel": + channel_num = self.configuration_controller.number_of_channels + factors = [f"Channel {i+1}" for i in range(channel_num)] + else: # laser + factors = list( + self.resolution_info["remote_focus_constants"][self.resolution][ + self.mag + ].keys() + ) + galvos = [] + for i in range(self.configuration_controller.galvo_num): + galvo_name = f"Galvo {i}" + temp = [] + if self.resolution not in self.galvo_setting[galvo_name].keys(): + continue + galvo_setting = self.galvo_setting[galvo_name][self.resolution][ + self.mag + ] + for fid in factors: + if fid not in galvo_setting.keys(): + galvo_setting[fid] = { + "amplitude": galvo_setting["amplitude"], + "offset": galvo_setting["offset"], + } + temp.append( + (galvo_setting[fid]["amplitude"], galvo_setting[fid]["offset"]) + ) + galvos.append(temp) + + self.advanced_setting_popup.generate_parameter_frame(factors, galvos) + # set values + for i in range(len(factors)): + for j in range(len(galvos)): + self.advanced_setting_popup.parameters[f"galvo_{i}_{j}_amp"].set( + galvos[j][i][0] + ) + self.advanced_setting_popup.parameters[f"galvo_{i}_{j}_off"].set( + galvos[j][i][1] + ) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_amp" + ].widget.configure(from_=self.galvo_dict[j]["hardware"]["min"]) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_amp" + ].widget.configure(to=self.galvo_dict[j]["hardware"]["max"]) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_amp" + ].widget.configure(increment=self.increment) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_off" + ].widget.configure(from_=self.galvo_dict[j]["hardware"]["min"]) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_off" + ].widget.configure(to=self.galvo_dict[j]["hardware"]["max"]) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_off" + ].widget.configure(increment=self.increment) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_amp" + ].get_variable().trace_add( + "write", self.update_galvo_advanced_setting(i, j, factors[i], "amp") + ) + self.advanced_setting_popup.parameters[ + f"galvo_{i}_{j}_off" + ].get_variable().trace_add( + "write", self.update_galvo_advanced_setting(i, j, factors[i], "off") + ) + + # update galvo factor + if galvo_factor != self.resolution_info["other_constants"].get( + "galvo_factor", "none" + ): + self.set_galvo_factor(galvo_factor) + + def update_galvo_advanced_setting( + self, factor_id, galvo_id, factor_name, amp_or_off + ): + """Update galvo setting parameters + + Parameters + ---------- + factor_id : int + The index of the galvo factor + galvo_id : int + The index of the galvo device + factor_name : str + The name of galvo associated factor + amp_or_off : str + Amplitude or Offset: the value should be "amp" or "off + + Returns + ------- + func : Function + A widget function + """ + + def func(*args, **kwargs): + try: + value = float( + self.advanced_setting_popup.parameters[ + f"galvo_{factor_id}_{galvo_id}_{amp_or_off}" + ].get() + ) + except ValueError: + return + + if self.event_id: + self.view.popup.after_cancel(self.event_id) + + parameter_name = "amplitude" if amp_or_off == "amp" else "offset" + galvo_name = f"Galvo {galvo_id}" + if factor_name == "All": + self.galvo_setting[galvo_name][self.resolution][self.mag][ + parameter_name + ] = value + else: + self.galvo_setting[galvo_name][self.resolution][self.mag][factor_name][ + parameter_name + ] = value + + self.event_id = self.view.popup.after( + 500, + lambda: self.parent_controller.execute("update_setting", "galvo"), + ) + + return func + + def set_galvo_to_all_channels(self): + """Set galvo factor to 'none' and use galvo parameters in all channels""" + self.set_galvo_factor("none") + self.resolution_info["other_constants"]["galvo_factor"] = "none" + if hasattr(self, "advanced_setting_popup"): + self.advanced_setting_popup.variables["galvo_factor"].set("none") + + def set_galvo_factor(self, galvo_factor="none"): + """Set galvo factor and display information + + Parameters + ---------- + galvo_factor : str + The name of galvo factor: 'laser', 'channel', or 'none' + """ + if galvo_factor != "none": + self.view.inputs["galvo_info"].widget.configure( + text=f"Associate with {galvo_factor}" + ) + self.view.inputs["all_channels"].widget.configure(state="normal") + self.view.inputs["all_channels"].set(False) + # disable galvo widgets in the main popup window + for i in range(len(self.galvos)): + galvo_name = f"Galvo {i}" + self.widgets[galvo_name + " Amp"].widget["state"] = "disabled" + self.widgets[galvo_name + " Off"].widget["state"] = "disabled" + else: + self.view.inputs["galvo_info"].widget.configure(text="") + self.view.inputs["all_channels"].widget.configure(state="disabled") + self.view.inputs["all_channels"].set(True) + # enable galvo widgets in the main popup window + for i in range(len(self.galvos)): + galvo_name = f"Galvo {i}" + self.widgets[galvo_name + " Amp"].widget["state"] = "normal" + self.widgets[galvo_name + " Off"].widget["state"] = "normal" + self.resolution_info["other_constants"]["galvo_factor"] = galvo_factor + if self.event_id: + self.view.popup.after_cancel(self.event_id) + self.event_id = self.view.popup.after( + 500, + lambda: self.parent_controller.execute("update_setting", "galvo"), + ) diff --git a/src/navigate/model/devices/daq/base.py b/src/navigate/model/devices/daq/base.py index c2ec1e24f..fc9a7dd16 100644 --- a/src/navigate/model/devices/daq/base.py +++ b/src/navigate/model/devices/daq/base.py @@ -84,9 +84,7 @@ def __init__(self, configuration): # Camera Parameters #: float: Camera delay percentage self.camera_delay = ( - self.configuration["configuration"]["microscopes"][self.microscope_name][ - "camera" - ]["delay"] + float(self.waveform_constants["other_constants"].get("camera_delay", 5)) / 1000 ) @@ -157,9 +155,7 @@ def enable_microscope(self, microscope_name): self.microscope_name = microscope_name self.camera_delay = ( - self.configuration["configuration"]["microscopes"][microscope_name][ - "camera" - ]["delay"] + float(self.waveform_constants["other_constants"].get("camera_delay", 5)) / 1000 ) self.sample_rate = self.configuration["configuration"]["microscopes"][ diff --git a/src/navigate/model/devices/daq/ni.py b/src/navigate/model/devices/daq/ni.py index b5c06fcf3..e389b60c0 100644 --- a/src/navigate/model/devices/daq/ni.py +++ b/src/navigate/model/devices/daq/ni.py @@ -498,9 +498,7 @@ def enable_microscope(self, microscope_name): self.analog_output_tasks = {} self.camera_delay = ( - self.configuration["configuration"]["microscopes"][microscope_name][ - "camera" - ]["delay"] + float(self.waveform_constants["other_constants"].get("camera_delay", 5)) / 1000 ) self.sample_rate = self.configuration["configuration"]["microscopes"][ diff --git a/src/navigate/model/devices/galvo/base.py b/src/navigate/model/devices/galvo/base.py index e0de6d628..40eee80b3 100644 --- a/src/navigate/model/devices/galvo/base.py +++ b/src/navigate/model/devices/galvo/base.py @@ -129,6 +129,9 @@ def adjust(self, exposure_times, sweep_times): microscope_state = self.configuration["experiment"]["MicroscopeState"] microscope_name = microscope_state["microscope_name"] zoom_value = microscope_state["zoom"] + galvo_factor = self.configuration["waveform_constants"]["other_constants"].get( + "galvo_factor", "none" + ) galvo_parameters = self.configuration["waveform_constants"]["galvo_constants"][ self.galvo_name ][microscope_name][zoom_value] @@ -155,6 +158,21 @@ def adjust(self, exposure_times, sweep_times): galvo_frequency = ( float(galvo_parameters.get("frequency", 0)) / exposure_time ) + factor_name = None + if galvo_factor == "channel": + factor_name = ( + f"Channel {channel_key[channel_key.index('_')+1:]}" + ) + elif galvo_factor == "laser": + factor_name = channel["laser"] + if factor_name and factor_name in galvo_parameters.keys(): + galvo_amplitude = float( + galvo_parameters[factor_name].get("amplitude", 0) + ) + galvo_offset = float( + galvo_parameters[factor_name].get("offset", 0) + ) + except ValueError as e: logger.error( f"{e} waveform constants.yml doesn't have parameter " diff --git a/src/navigate/view/popups/waveform_parameter_popup_window.py b/src/navigate/view/popups/waveform_parameter_popup_window.py index 2a2a679c4..2658b5e16 100644 --- a/src/navigate/view/popups/waveform_parameter_popup_window.py +++ b/src/navigate/view/popups/waveform_parameter_popup_window.py @@ -30,13 +30,19 @@ # 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 from tkinter import ttk +import logging + +# Third-party imports + +# Local application imports from navigate.view.custom_widgets.popup import PopUp from navigate.view.custom_widgets.LabelInputWidgetFactory import LabelInput from navigate.view.custom_widgets.validation import ValidatedSpinbox -import logging +# Logging p = __name__.split(".")[1] logger = logging.getLogger(p) @@ -70,45 +76,70 @@ def __init__(self, root, configuration_controller, *args, **kwargs): #: dict: Dictionary for all the variables self.inputs = {} + #: dict: Dictionary for all the buttons self.buttons = {} - # Frames for widgets #: ttk.Frame: Frame for mode and magnification self.mode_mag_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) + #: ttk.Frame: Frame for saving waveform parameters self.save_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) - #: ttk.Frame: Frame for laser parameters - self.laser_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) - #: ttk.Frame: Frame for high/low resolution - self.high_low_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) + + #: ttk.LabelFrame: Frame for remote focus parameters + self.remote_focus_frame = ttk.LabelFrame( + content_frame, text="Remote Focus Settings", padding=(0, 0, 0, 0) + ) + + #: ttk.LabelFrame: Frame for galvo parameters + self.galvo_frame = ttk.LabelFrame( + content_frame, text="Galvo Settings", padding=(0, 0, 0, 0) + ) + + #: ttk.LabelFrame: Frame for waveform + self.waveform_frame = ttk.LabelFrame( + content_frame, text="Waveform Parameters", padding=(0, 0, 0, 0) + ) # Griding Frames - self.mode_mag_frame.grid(row=0, column=0, sticky=tk.NSEW) - self.save_frame.grid(row=0, column=1, sticky=tk.NSEW) - self.laser_frame.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW) - self.high_low_frame.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW) + self.mode_mag_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=(10, 0)) + self.save_frame.grid(row=0, column=3, sticky=tk.NE, padx=10, pady=(10, 0)) + self.remote_focus_frame.grid( + row=2, column=0, columnspan=4, sticky=tk.NSEW, padx=10, pady=(10, 0) + ) + self.galvo_frame.grid( + row=3, column=0, columnspan=4, sticky=tk.NSEW, padx=10, pady=(10, 0) + ) + self.waveform_frame.grid( + row=4, column=0, columnspan=4, sticky=tk.NSEW, padx=10, pady=10 + ) # Filling Frames with widgets - # Mode/Mag Frame + max_length = max(len("Microscope"), len("Magnification")) + padded_text = "Microscope" + padded_text = padded_text.ljust(max_length) self.inputs["Mode"] = LabelInput( parent=self.mode_mag_frame, - label="Mode", + label=padded_text, input_class=ttk.Combobox, input_var=tk.StringVar(), - label_args={"padding": (2, 5, 48, 0)}, + label_args={"padding": (2, 5, 5, 0)}, ) - self.inputs["Mode"].grid(row=0, column=0) + self.inputs["Mode"].grid(row=0, column=0, padx=10) + self.inputs["Mode"].pad_input(50, 5, 0, 0) self.inputs["Mode"].state(["readonly"]) + padded_text = "Magnification" + padded_text = padded_text.ljust(max_length) self.inputs["Mag"] = LabelInput( parent=self.mode_mag_frame, - label="Magnification", + label=padded_text, input_class=ttk.Combobox, input_var=tk.StringVar(), label_args={"padding": (2, 5, 5, 0)}, ) - self.inputs["Mag"].grid(row=1, column=0) + self.inputs["Mag"].grid(row=1, column=0, padx=10) + self.inputs["Mag"].pad_input(50, 5, 0, 0) self.inputs["Mag"].state(["readonly"]) # Save Waveform Parameters Frame @@ -130,22 +161,22 @@ def __init__(self, root, configuration_controller, *args, **kwargs): laser_labels = self.configuration_controller.lasers_info title_labels = ["Laser", "Amplitude", "Offset"] # Loop for widgets - for i in range(3): + for i in range(len(title_labels)): # Title labels title = ttk.Label( - self.laser_frame, text=title_labels[i], padding=(2, 5, 0, 0) + self.remote_focus_frame, text=title_labels[i], padding=(2, 5, 60, 0) ) title.grid(row=0, column=i, sticky=tk.NSEW, padx=(0, 5)) for i, label in enumerate(laser_labels): # Laser labels laser = ttk.Label( - self.laser_frame, text=laser_labels[i], padding=(2, 5, 0, 0) + self.remote_focus_frame, text=laser_labels[i], padding=(2, 5, 0, 0) ) laser.grid(row=i + 1, column=0, sticky=tk.NSEW) # Entry Widgets self.inputs[laser_labels[i] + " Amp"] = LabelInput( - parent=self.laser_frame, + parent=self.remote_focus_frame, input_class=ValidatedSpinbox, input_var=tk.StringVar(), ) @@ -155,7 +186,7 @@ def __init__(self, root, configuration_controller, *args, **kwargs): ) self.inputs[laser_labels[i] + " Off"] = LabelInput( - parent=self.laser_frame, + parent=self.remote_focus_frame, input_class=ValidatedSpinbox, input_var=tk.StringVar(), ) @@ -168,87 +199,161 @@ def __init__(self, root, configuration_controller, *args, **kwargs): map(lambda i: f"Galvo {i}", range(self.configuration_controller.galvo_num)) ) - prev = len(laser_labels) + title_labels = ["Galvo", "Amplitude", "Offset", "Frequency"] + # Loop for widgets + for i in range(len(title_labels)): + # Title labels + title = ttk.Label( + self.galvo_frame, text=title_labels[i], padding=(2, 5, 53, 0) + ) + title.grid(row=0, column=i, sticky=tk.NSEW, padx=(0, 5)) for i, label in enumerate(galvo_labels): galvo = ttk.Label( - self.laser_frame, text=galvo_labels[i], padding=(2, 5, 0, 0) + self.galvo_frame, text=galvo_labels[i], padding=(2, 5, 53, 0) ) - galvo.grid(row=prev + 1, column=0, sticky=tk.NSEW) + galvo.grid(row=i + 1, column=0, sticky=tk.NSEW) self.inputs[galvo_labels[i] + " Amp"] = LabelInput( - parent=self.laser_frame, + parent=self.galvo_frame, input_class=ValidatedSpinbox, input_var=tk.StringVar(), ) self.inputs[galvo_labels[i] + " Amp"].grid( - row=prev + 1, column=1, sticky=tk.NSEW, pady=(20, 0), padx=(0, 5) + row=i + 1, column=1, sticky=tk.NSEW, pady=(10, 0), padx=(0, 5) ) self.inputs[galvo_labels[i] + " Off"] = LabelInput( - parent=self.laser_frame, + parent=self.galvo_frame, input_class=ValidatedSpinbox, input_var=tk.StringVar(), ) self.inputs[galvo_labels[i] + " Off"].grid( - row=prev + 1, column=2, sticky=tk.NSEW, pady=(20, 0) + row=i + 1, column=2, sticky=tk.NSEW, pady=(10, 0), padx=(0, 5) ) - galvo_freq = ttk.Label( - self.laser_frame, - text=galvo_labels[i] + " Freq (Hz)", - padding=(2, 5, 0, 0), - ) - - galvo_freq.grid(row=prev + 2, column=0, sticky=tk.NSEW) - self.inputs[galvo_labels[i] + " Freq"] = LabelInput( - parent=self.laser_frame, + parent=self.galvo_frame, input_class=ValidatedSpinbox, input_var=tk.StringVar(), input_args={"from_": 0, "to": 1000, "increment": 0.1}, ) self.inputs[galvo_labels[i] + " Freq"].grid( - row=prev + 2, column=1, sticky=tk.NSEW, pady=(20, 0) + row=i + 1, column=3, sticky=tk.NSEW, pady=(10, 0), padx=(0, 5) ) # Button for automatic estimate of galvo frequency self.buttons[galvo_labels[i] + " Freq"] = ttk.Button( - self.laser_frame, - text="Estimate Frequency", + self.galvo_frame, text="Estimate Frequency", padding=(2, 0, 2, 0) ) self.buttons[galvo_labels[i] + " Freq"].grid( - row=prev + 2, column=2, sticky=tk.NSEW, pady=(20, 0) + row=i + 1, column=4, sticky=tk.NE, pady=(10, 0) ) - prev = prev + 2 + self.inputs["galvo_info"] = LabelInput( + parent=self.galvo_frame, + input_class=ttk.Label, + input_var=None, + input_args={"text": "", "state": "disabled"}, + ) + self.inputs["galvo_info"].grid( + row=len(galvo_labels) + 1, column=1, sticky=tk.NW, pady=(10, 0) + ) + + self.inputs["all_channels"] = LabelInput( + parent=self.galvo_frame, + input_class=ttk.Checkbutton, + input_var=tk.BooleanVar(), + label="Apply To All Channels", + ) + self.inputs["all_channels"].grid( + row=len(galvo_labels) + 1, + column=2, + columnspan=2, + sticky=tk.NE, + pady=(10, 0), + padx=(0, 20), + ) + self.inputs["all_channels"].widget.config(state="disabled") + + self.buttons["advanced_galvo_setting"] = ttk.Button( + self.galvo_frame, text="Advanced Settings", padding=(5, 0, 5, 0) + ) + self.buttons["advanced_galvo_setting"].grid( + row=len(galvo_labels) + 1, + column=4, + sticky=tk.NSEW, + pady=(10, 0), + ) - # High/Low Resolution - hi_lo_labels = ["Delay (ms)", "Fly Back Time (ms)", "Settle Duration (ms)", "Percent Smoothing"] - dict_labels = ["Delay", "Ramp_falling" , "Duty", "Smoothing"] + # other remote focus waveform setting + label = ttk.Label(self.waveform_frame, text="Remote Focus Settings") + label.grid(row=0, columnspan=4, padx=5, sticky=tk.NW, pady=(10, 0)) + separator = ttk.Separator(self.waveform_frame) + separator.grid(row=1, columnspan=4, sticky=tk.NSEW) + rf_waveform_labels = [ + "Delay (ms)", + "Fly Back Time (ms)", + "Settle Duration (ms)", + "Percent Smoothing", + ] + dict_labels = ["Delay", "Ramp_falling", "Duty", "Smoothing"] for i in range(len(dict_labels)): + label = ttk.Label( + self.waveform_frame, text=rf_waveform_labels[i], padding=(2, 5, 5, 0) + ) + label.grid( + row=i // 2 + 2, + column=(i % 2) * 2, + padx=(2 + (i % 2) * 40, 20), + sticky=tk.NSEW, + ) self.inputs[dict_labels[i]] = LabelInput( - parent=self.high_low_frame, + parent=self.waveform_frame, input_class=ValidatedSpinbox, - label=hi_lo_labels[i], + # label=rf_waveform_labels[i], input_var=tk.StringVar(), - label_args={"padding": (2, 5, 5, 0)}, + # label_args={"padding": (2, 5, 5, 0)}, input_args={"from_": 0, "to": 100, "increment": 0.1}, ) self.inputs[dict_labels[i]].grid( - row=i, column=0, sticky=tk.NSEW, padx=(2, 5) + row=i // 2 + 2, column=(i % 2) * 2 + 1, sticky=tk.NSEW + ) + row_id = 2 + len(rf_waveform_labels) // 2 + label = ttk.Label(self.waveform_frame, text="Camera Settings") + label.grid(row=row_id, column=0, padx=5, pady=(10, 0), sticky=tk.NW) + separator = ttk.Separator(self.waveform_frame) + separator.grid(row=row_id + 1, columnspan=4, sticky=tk.NSEW) + camera_waveform_labels = ["Delay (ms)", "Settle Duration (ms)"] + dict_labels = ["camera_delay", "camera_settle_duration"] + for i in range(len(camera_waveform_labels)): + label = ttk.Label( + self.waveform_frame, + text=camera_waveform_labels[i], + padding=(2, 5, 5, 0), + ) + label.grid( + row=i // 2 + row_id + 2, + column=(i % 2) * 2, + padx=(2 + (i % 2) * 40, 20), + sticky=tk.NSEW, + ) + self.inputs[dict_labels[i]] = LabelInput( + parent=self.waveform_frame, + input_class=ValidatedSpinbox, + # label=rf_waveform_labels[i], + input_var=tk.StringVar(), + # label_args={"padding": (2, 5, 5, 0)}, + input_args={"from_": 0, "to": 100, "increment": 0.1}, + ) + self.inputs[dict_labels[i]].grid( + row=i // 2 + row_id + 2, column=(i % 2) * 2 + 1, sticky=tk.NSEW ) - - # Padding Entry Widgets - self.inputs["Delay"].pad_input(75, 0, 0, 0) - self.inputs["Ramp_falling"].pad_input(30, 0, 0, 0) - self.inputs["Smoothing"].pad_input(30, 0, 0, 0) - self.inputs["Duty"].pad_input(25, 0, 0, 0) # Getters def get_variables(self): @@ -262,10 +367,6 @@ def get_variables(self): ------- dict Dictionary of all the variables tied to each widget name - - Examples - -------- - >>> self.get_variables() """ variables = {} for key, widget in self.inputs.items(): @@ -282,10 +383,6 @@ def get_widgets(self): ------- dict Dictionary of all the widgets - - Examples - -------- - >>> self.get_widgets() """ return self.inputs @@ -299,9 +396,184 @@ def get_buttons(self): ------- dict Dictionary of all the buttons - - Examples - -------- - >>> self.get_buttons() """ return self.buttons + + +class AdvancedWaveformParameterPopupWindow: + """Popup window with advanced waveform parameters for galvos, remote focusing, + etc.""" + + def __init__(self, root, *args, **kwargs): + """Initialize the AdvancedWaveformParameterPopupWindow + + Parameters + ---------- + root : tk.Tk + The root window + """ + + # Creating popup window with this name and size/placement, PopUp is a + # Toplevel window + + #: PopUp: The popup window + self.popup = PopUp( + root, "Advanced Galvo Setting", "+320+180", top=False, transient=False + ) + + content_frame = self.popup.get_frame() + # Formatting + tk.Grid.columnconfigure(content_frame, "all", weight=1) + tk.Grid.rowconfigure(content_frame, "all", weight=1) + + #: dict: Dictionary for all of the inputs + self.inputs = {} + + #: dict: Dictionary for all the buttons + self.buttons = {} + + #: dict: Dictionary for all the variables + self.variables = {} + + #: dict: Dictionary for all the parameters + self.parameters = {} + + #: ttk.Frame: Frame for top + self.top_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) + + #: ttk.Frame: Frame for parameters + self.parameter_frame = ttk.Frame(content_frame, padding=(0, 0, 0, 0)) + + self.top_frame.grid(row=0, column=0, sticky=tk.NSEW, padx=10, pady=(10, 0)) + self.parameter_frame.grid( + row=1, column=0, sticky=tk.NSEW, padx=10, pady=(10, 0) + ) + + label = ttk.Label(self.top_frame, text="Galvo Parameters Associate with:") + label.grid(row=0, column=0, columnspan=4, sticky=tk.NW) + self.variables["galvo_factor"] = tk.StringVar() + + laser = ttk.Radiobutton( + master=self.top_frame, + text="Laser Wavelength", + value="laser", + variable=self.variables["galvo_factor"], + ) + channel = ttk.Radiobutton( + master=self.top_frame, + text="Channel", + value="channel", + variable=self.variables["galvo_factor"], + ) + none_factor = ttk.Radiobutton( + master=self.top_frame, + text="None", + value="none", + variable=self.variables["galvo_factor"], + ) + self.top_frame.grid_columnconfigure(0, minsize=50) + laser.grid(row=1, column=1, sticky=tk.NSEW, padx=5, pady=(10, 0)) + channel.grid(row=1, column=2, sticky=tk.NSEW, padx=5, pady=(10, 0)) + none_factor.grid(row=1, column=3, sticky=tk.NSEW, padx=5, pady=(10, 0)) + + def generate_parameter_frame(self, factors=["All"], galvos=[]): + """Generate galvo widgets + + Parameters + ---------- + factors : list + A list of galvo factor names + galvos : list + A list of galvo amplitude and offset values + """ + # remove all widgets + for child in self.parameter_frame.winfo_children(): + child.destroy() + + self.parameters = {} + + if len(galvos) < 3: + self.parameter_frame.grid_columnconfigure(0, minsize=50) + for i in range(len(galvos)): + label = ttk.Label(self.parameter_frame, text=f"Galvo {i}") + label.grid(row=0, column=i * 2 + 1, columnspan=2, padx=20) + + label = ttk.Label(self.parameter_frame, text="Amplitude") + label.grid( + row=1, column=i * 2 + 1, sticky=tk.NSEW, padx=10, pady=(5, 0) + ) + label = ttk.Label(self.parameter_frame, text="Offset") + label.grid( + row=1, column=i * 2 + 2, sticky=tk.NSEW, padx=10, pady=(5, 0) + ) + + for i in range(len(factors)): + label = ttk.Label(self.parameter_frame, text=factors[i]) + label.grid(row=i + 2, column=0, sticky=tk.NSEW, padx=20, pady=5) + + for j in range(len(galvos)): + self.parameters[f"galvo_{i}_{j}_amp"] = LabelInput( + parent=self.parameter_frame, + input_class=ValidatedSpinbox, + input_var=tk.StringVar(), + ) + # self.parameters[f"galvo_{i}_{j}_amp"].set(galvos[j][i][0]) + self.parameters[f"galvo_{i}_{j}_amp"].grid( + row=i + 2, + column=j * 2 + 1, + sticky=tk.NSEW, + padx=(0, 5), + pady=(0, 5), + ) + + self.parameters[f"galvo_{i}_{j}_off"] = LabelInput( + parent=self.parameter_frame, + input_class=ValidatedSpinbox, + input_var=tk.StringVar(), + ) + # self.parameters[f"galvo_{i}_{j}_off"].set(galvos[j][i][1]) + self.parameters[f"galvo_{i}_{j}_off"].grid( + row=i + 2, + column=j * 2 + 2, + sticky=tk.NSEW, + padx=(0, 5), + pady=(0, 5), + ) + else: + # using tabs + notebook = ttk.Notebook(self.parameter_frame) + notebook.grid(row=0, column=0, padx=10, pady=10) + + for i in range(len(factors)): + tab = tk.Frame(notebook, width=200) + notebook.add(tab, text=factors[i]) + tab.grid_columnconfigure(0, minsize=50) + label = ttk.Label(tab, text="Amplitude") + label.grid(row=0, column=1, sticky=tk.NSEW, padx=10, pady=5) + label = ttk.Label(tab, text="Offset") + label.grid(row=0, column=2, sticky=tk.NSEW, padx=10, pady=5) + + for j in range(len(galvos)): + label = ttk.Label(tab, text=f"Galvo {j}") + label.grid( + row=j + 1, column=0, sticky=tk.NSEW, padx=10, pady=(0, 5) + ) + + self.parameters[f"galvo_{i}_{j}_amp"] = LabelInput( + parent=tab, + input_class=ValidatedSpinbox, + input_var=tk.StringVar(), + ) + # self.parameters[f"galvo_{i}_{j}_amp"].set(galvos[j][i][0]) + self.parameters[f"galvo_{i}_{j}_amp"].grid( + row=j + 1, column=1, sticky=tk.NSEW, padx=10, pady=(0, 5) + ) + self.parameters[f"galvo_{i}_{j}_off"] = LabelInput( + parent=tab, + input_class=ValidatedSpinbox, + input_var=tk.StringVar(), + ) + # self.parameters[f"galvo_{i}_{j}_off"].set(galvos[j][i][1]) + self.parameters[f"galvo_{i}_{j}_off"].grid( + row=j + 1, column=2, sticky=tk.NSEW, padx=10, pady=(0, 5) + )