class MirrorSettingsController(SettingsBarController): """ Controller, which provides the user with the option to select among different configurations regarding the mirror position. For example, the user can select the configuration with the flipped mirror which is under the sample and placed upside-down. """ def __init__(self, tab_panel, tab_data): super(MirrorSettingsController, self).__init__(tab_data) self.panel = tab_panel mirror_lens = tab_data.main.lens self.panel_center = SettingsPanel(self.panel.pnl_mode_btns) self.panel_center.SetBackgroundColour(odemis.gui.BG_COLOUR_PANEL) self.panel.pnl_mode_btns.GetSizer().Add(self.panel_center, 1, border=5, flag=wx.LEFT | wx.RIGHT | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) entry_mirrorPosition = create_setting_entry(self.panel_center, "Mirror type", mirror_lens.configuration, mirror_lens, conf={"control_type": odemis.gui.CONTROL_COMBO, "label": "Mirror type", "tooltip": "Change the type of the mirror"}) entry_mirrorPosition.value_ctrl.SetBackgroundColour(odemis.gui.BG_COLOUR_PANEL) # remove border self.panel_center.GetSizer().GetItem(0).SetBorder(0) self.panel_center.Layout() @call_in_wx_main def on_preparation(self, is_preparing): # Don't change enable based on the preparation pass def enable(self, enabled): self.panel_center.Enable(enabled)
class StreakCamAlignSettingsController(SettingsBarController): """ Controller, which creates the streak panel in the alignment tab and provides the necessary settings to align and calibrate a streak camera. """ def __init__(self, tab_panel, tab_data): super(StreakCamAlignSettingsController, self).__init__(tab_data) self.panel = tab_panel main_data = tab_data.main self.streak_ccd = main_data.streak_ccd self.streak_delay = main_data.streak_delay self.streak_unit = main_data.streak_unit self.streak_lens = main_data.streak_lens self._calib_path = get_picture_folder( ) # path to the trigger delay calibration folder self.panel_streak = SettingsPanel(self.panel.pnl_streak) self.panel_streak.SetBackgroundColour(odemis.gui.BG_COLOUR_PANEL) self.panel.pnl_streak.GetSizer().Add(self.panel_streak, 1, border=5, flag=wx.BOTTOM | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) entry_timeRange = create_setting_entry( self.panel_streak, "Time range", self.streak_unit.timeRange, self.streak_unit, conf={ "control_type": odemis.gui.CONTROL_COMBO, "label": "Time range", "tooltip": "Time needed by the streak unit for one sweep " "from top to bottom of the readout camera chip." }) entry_timeRange.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.ctrl_timeRange = entry_timeRange.value_ctrl entry_triggerDelay = create_setting_entry( self.panel_streak, "Trigger delay", self.streak_delay.triggerDelay, self.streak_delay, conf={ "control_type": odemis.gui.CONTROL_FLT, "label": "Trigger delay", "tooltip": "Change the trigger delay value to " "center the image." }, change_callback=self._onUpdateTriggerDelayMD) entry_triggerDelay.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.ctrl_triggerDelay = entry_triggerDelay.value_ctrl entry_magnification = create_setting_entry( self.panel_streak, "Magnification", self.streak_lens.magnification, self.streak_lens, conf={ "control_type": odemis.gui.CONTROL_COMBO, "label": "Magnification", "tooltip": "Change the magnification of the input" "optics for the streak camera system. \n" "Values < 1: De-magnifying \n" "Values > 1: Magnifying" }) entry_magnification.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.combo_magnification = entry_magnification.value_ctrl # remove border self.panel_streak.GetSizer().GetItem(0).SetBorder(0) self.panel_streak.Layout() self.panel.btn_open_streak_calib_file.Bind(wx.EVT_BUTTON, self._onOpenCalibFile) self.panel.btn_save_streak_calib_file.Bind(wx.EVT_BUTTON, self._onSaveCalibFile) def _onUpdateTriggerDelayMD(self, evt): """ Callback method for trigger delay ctrl GUI element. Overwrites the triggerDelay value in the MD after a new value was requested via the GUI. """ evt.Skip() cur_timeRange = self.streak_unit.timeRange.value requested_triggerDelay = self.ctrl_triggerDelay.GetValue() # get a copy of MD trigger2delay_MD = self.streak_delay.getMetadata()[ model.MD_TIME_RANGE_TO_DELAY] # check if key already exists (prevent creating new key due to floating point issues) key = util.find_closest(cur_timeRange, trigger2delay_MD.keys()) if util.almost_equal(key, cur_timeRange): # Replace the current delay value with the requested for an already existing timeRange in the dict. # This avoid duplication of keys, which are only different because of floating point issues. trigger2delay_MD[key] = requested_triggerDelay else: trigger2delay_MD[cur_timeRange] = requested_triggerDelay logging.warning( "A new entry %s was added to MD_TIME_RANGE_TO_DELAY, " "which is not in the device .timeRange choices.", cur_timeRange) # check the number of keys in the dict is same as choices for VA if len(trigger2delay_MD.keys()) != len( self.streak_unit.timeRange.choices): logging.warning( "MD_TIME_RANGE_TO_DELAY has %d entries, while the device .timeRange has %d choices.", len(trigger2delay_MD.keys()), len(self.streak_unit.timeRange.choices)) self.streak_delay.updateMetadata( {model.MD_TIME_RANGE_TO_DELAY: trigger2delay_MD}) # Note: updateMetadata should here never raise an exception as the UnitFloatCtrl already # catches errors regarding type and out-of-range inputs # update txt displayed in GUI self._onUpdateTriggerDelayGUI("Calibration not saved yet", odemis.gui.FG_COLOUR_WARNING) def _onUpdateTriggerDelayGUI(self, text, colour=odemis.gui.FG_COLOUR_EDIT): """ Updates the GUI elements regarding the new trigger delay value. :parameter text (str): the text to show :parameter colour (wx.Colour): the colour to use """ self.panel.txt_StreakCalibFilename.Value = text self.panel.txt_StreakCalibFilename.SetForegroundColour(colour) def _onOpenCalibFile(self, event): """ Loads a calibration file (*csv) containing the time range and the corresponding trigger delay for streak camera calibration. """ logging.debug( "Open trigger delay calibration file for temporal acquisition.") dialog = wx.FileDialog(self.panel, message="Choose a calibration file to load", defaultDir=self._calib_path, defaultFile="", style=wx.FD_OPEN, wildcard="csv files (*.csv)|*.csv") # Show the dialog and check whether is was accepted or cancelled if dialog.ShowModal() != wx.ID_OK: return # get selected path + filename and update default directory self._calib_path = dialog.GetDirectory() path = dialog.GetPath() filename = dialog.GetFilename() # read file try: tr2d = calibration.read_trigger_delay_csv( path, self.streak_unit.timeRange.choices, self.streak_delay.triggerDelay.range) except ValueError as error: self._onUpdateTriggerDelayGUI("Error while loading file!", odemis.gui.FG_COLOUR_HIGHLIGHT) logging.error("Failed loading %s: %s", filename, error) return # update the MD: overwrite the complete dict self.streak_delay.updateMetadata({model.MD_TIME_RANGE_TO_DELAY: tr2d}) # update triggerDelay shown in GUI cur_timeRange = self.streak_unit.timeRange.value # find the corresponding trigger delay key = util.find_closest(cur_timeRange, tr2d.keys()) # Note: no need to check almost_equal again as we do that already when loading the file self.streak_delay.triggerDelay.value = tr2d[key] # set the new value self._onUpdateTriggerDelayGUI(filename) # update txt displayed in GUI def _onSaveCalibFile(self, event): """ Saves a calibration file (*csv) containing the time range and the corresponding trigger delay for streak camera calibration. """ logging.debug( "Save trigger delay calibration file for temporal acquisition.") dialog = wx.FileDialog( self.panel, message= "Choose a filename and destination to save the calibration file. " "It is advisory to include the SEM voltage into the filename.", defaultDir=self._calib_path, defaultFile="", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, wildcard="csv files (*.csv)|*.csv") # Show the dialog and check whether is was accepted or cancelled if dialog.ShowModal() != wx.ID_OK: return # get selected path + filename and update default directory self._calib_path = dialog.GetDirectory() path = dialog.GetPath() filename = dialog.GetFilename() # check if filename is provided with the correct extension if os.path.splitext(filename)[1] != ".csv": filename += ".csv" path += ".csv" # get a copy of the triggerDelay dict from MD tr2d = self.streak_delay.getMetadata()[model.MD_TIME_RANGE_TO_DELAY] calibration.write_trigger_delay_csv(path, tr2d) # update txt displayed in GUI self._onUpdateTriggerDelayGUI(filename)
class SettingsController(with_metaclass(ABCMeta, object)): """ Settings base class which describes an indirect wrapper for FoldPanelItems :param fold_panel_item: (FoldPanelItem) Parent window :param default_msg: (str) Text message which will be shown if the SettingPanel does not contain any child windows. :param highlight_change: (bool) If set to True, the values will be highlighted when they match the cached values. """ def __init__(self, fold_panel_item, default_msg, highlight_change=False, tab_data=None): self.panel = SettingsPanel(fold_panel_item, default_msg=default_msg) fold_panel_item.add_item(self.panel) self.highlight_change = highlight_change self.tab_data = tab_data self.num_entries = 0 self.entries = [] # list of SettingEntry self._disabled_entries = set() # set of SettingEntry objects self._subscriptions = [] def hide_panel(self): self.show_panel(False) def show_panel(self, show=True): self.panel.Show(show) def pause(self): """ Pause SettingEntry related control updates """ for entry in self.entries: entry.pause() if entry.value_ctrl and entry.value_ctrl.IsEnabled(): entry.value_ctrl.Enable(False) self._disabled_entries.add(entry) def resume(self): """ Pause SettingEntry related control updates """ for entry in self.entries: entry.resume() if entry in self._disabled_entries: entry.value_ctrl.Enable(True) self._disabled_entries.remove(entry) def enable(self, enabled): """ Enable or disable all SettingEntries """ for entry in self.entries: if entry.value_ctrl: entry.value_ctrl.Enable(enabled) def add_file_button(self, label, value=None, tooltip=None, clearlabel=None, dialog_style=wx.FD_OPEN, wildcard=None): config = guiconf.get_acqui_conf() lbl_ctrl, value_ctrl = self.panel.add_file_button( label, value or config.last_path, clearlabel, dialog_style, wildcard) if tooltip is not None: lbl_ctrl.SetToolTip(tooltip) value_ctrl.SetToolTip(tooltip) # Add the corresponding setting entry ne = SettingEntry(name=label, lbl_ctrl=lbl_ctrl, value_ctrl=value_ctrl) self.entries.append(ne) return ne def add_setting_entry(self, name, va, hw_comp, conf=None): """ Add a name/value pair to the settings panel. :param name: (string): name of the value :param va: (VigilantAttribute) :param hw_comp: (Component): the component that contains this VigilantAttribute :param conf: ({}): Configuration items that may override default settings :return SettingEntry or None: the entry created, or None, if no entry was created (eg, because the conf indicates CONTROL_NONE). """ assert isinstance(va, VigilantAttributeBase) # Remove any 'empty panel' warning self.panel.clear_default_message() ne = create_setting_entry(self.panel, name, va, hw_comp, conf, self.on_setting_changed) if ne is None: return None self.entries.append(ne) if self.highlight_change: bind_setting_context_menu(ne) self.panel.Parent.Parent.Layout() return ne def add_axis(self, name, comp, conf=None): """ Add a widget to the setting panel to control an axis :param name: (string): name of the axis :param comp: (Component): the component that contains this axis :param conf: ({}): Configuration items that may override default settings """ ne = create_axis_entry(self.panel, name, comp, conf) self.entries.append(ne) # TODO: uncomment this once bind_setting_context_meny supports AxisSettingEntry # if self.highlight_change: # bind_setting_context_menu(ne) self.panel.Parent.Parent.Layout() def add_widgets(self, *wdg): """ Adds a widget at the end of the panel, on the whole width :param wdg: (wxWindow) the widgets to add (max 2) """ # if only one widget: span over all the panel width if len(wdg) == 1: span = (1, 2) else: span = wx.DefaultSpan for i, w in enumerate(wdg): self.panel.gb_sizer.Add(w, (self.panel.num_rows, i), span=span, flag=wx.ALL | wx.EXPAND, border=5) self.panel.num_rows += 1 def add_metadata(self, key, value): """ Adds an entry representing specific metadata According to the metadata key, the right representation is used for the value. :param key: (model.MD_*) the metadata key :param value: (depends on the metadata) the value to display """ # By default the key is a nice user-readable string label = str(key) # Convert value to a nice string according to the metadata type try: if key == model.MD_ACQ_DATE: # convert to a date using the user's preferences nice_str = time.strftime("%c", time.localtime(value)) # In Python 2, we still need to convert it to unicode if isinstance(nice_str, bytes): nice_str = nice_str.decode(locale.getpreferredencoding()) else: # Still try to beautify a bit if it's a number if (isinstance(value, (int, long, float)) or (isinstance(value, collections.Iterable) and len(value) > 0 and isinstance(value[0], (int, long, float)))): nice_str = readable_str(value, sig=3) else: nice_str = str(value) except Exception: logging.exception("Trying to convert metadata %s", key) nice_str = "N/A" self.panel.add_readonly_field(label, nice_str) def add_bc_control(self, detector): """ Add Hw brightness/contrast control """ self.panel.add_divider() # Create extra gird bag sizer gb_sizer = wx.GridBagSizer() gb_sizer.SetEmptyCellSize((0, 0)) # Create the widgets btn_autoadjust = ImageTextToggleButton( self.panel, height=24, label="Auto adjust", icon=img.getBitmap("icon/ico_contrast.png")) btn_autoadjust.SetToolTip("Adjust detector brightness/contrast") gb_sizer.Add(btn_autoadjust, (0, 0), (2, 1), border=10, flag=wx.ALIGN_CENTRE_VERTICAL | wx.RIGHT) sld_conf = { "accuracy": 2, "event": wx.EVT_SCROLL_CHANGED, "control_type": odemis.gui.CONTROL_SLIDER, "type": "slider", } num_rows = 0 if model.hasVA(detector, "brightness"): brightness_entry = self.add_setting_entry("brightness", detector.brightness, detector, sld_conf) # TODO: 'Ugly' detaching somewhat nullifies the cleanliness created by using # 'add_setting_entry'. 'add_setting_entry' Needs some more refactoring anyway. self.panel.gb_sizer.Detach(brightness_entry.value_ctrl) self.panel.gb_sizer.Detach(brightness_entry.lbl_ctrl) gb_sizer.Add(brightness_entry.lbl_ctrl, (num_rows, 1)) gb_sizer.Add(brightness_entry.value_ctrl, (num_rows, 2), flag=wx.EXPAND) num_rows += 1 if model.hasVA(detector, "contrast"): contrast_entry = self.add_setting_entry("contrast", detector.contrast, detector, sld_conf) self.panel.gb_sizer.Detach(contrast_entry.value_ctrl) self.panel.gb_sizer.Detach(contrast_entry.lbl_ctrl) gb_sizer.Add(contrast_entry.lbl_ctrl, (num_rows, 1)) gb_sizer.Add(contrast_entry.value_ctrl, (num_rows, 2), flag=wx.EXPAND) num_rows += 1 if num_rows: gb_sizer.AddGrowableCol(2) # Add the extra sizer to the main sizer self.panel.gb_sizer.Add(gb_sizer, (self.panel.num_rows, 0), span=(1, 2), border=5, flag=wx.ALL | wx.EXPAND) self.panel.num_rows += 1 # Connect various events to the auto adjust button def on_chamber_state(state, btn=btn_autoadjust): wx.CallAfter(btn.Enable, state in (CHAMBER_UNKNOWN, CHAMBER_VACUUM)) # We keep a reference to keep the subscription active. self._subscriptions.append(on_chamber_state) self.tab_data.main.chamberState.subscribe(on_chamber_state, init=True) @call_in_wx_main def adjust_done(_): """ Callback that enables and untoggles the 'auto adjust' contrast button """ btn_autoadjust.SetToggle(False) btn_autoadjust.SetLabel("Auto adjust") btn_autoadjust.Enable() brightness_entry.value_ctrl.Enable() contrast_entry.value_ctrl.Enable() def auto_adjust(_): """ Call the auto contrast method on the detector if it's not already running """ if not btn_autoadjust.up: f = detector.applyAutoContrast() btn_autoadjust.SetLabel("Adjusting...") btn_autoadjust.Disable() brightness_entry.value_ctrl.Disable() contrast_entry.value_ctrl.Disable() f.add_done_callback(adjust_done) btn_autoadjust.Bind(wx.EVT_BUTTON, auto_adjust) def on_setting_changed(self, evt): logging.debug("Setting has changed") evt.Skip() def Refresh(self): """ TODO: check if this is still necessary after the foldpanel update """ self.panel.Layout() p = self.panel.Parent while p: if isinstance(p, wx.ScrolledWindow): p.FitInside() p = None else: p = p.Parent
class StreakCamAlignSettingsController(SettingsBarController): """ Controller, which creates the streak panel in the alignment tab and provides the necessary settings to align and calibrate a streak camera. """ def __init__(self, tab_panel, tab_data): super(StreakCamAlignSettingsController, self).__init__(tab_data) self.panel = tab_panel main_data = tab_data.main self.streak_ccd = main_data.streak_ccd self.streak_delay = main_data.streak_delay self.streak_unit = main_data.streak_unit self.streak_lens = main_data.streak_lens self._calib_path = get_picture_folder( ) # path to the trigger delay calibration folder self.panel_streak = SettingsPanel(self.panel.pnl_streak) self.panel_streak.SetBackgroundColour(odemis.gui.BG_COLOUR_PANEL) self.panel.pnl_streak.GetSizer().Add(self.panel_streak, 1, border=5, flag=wx.BOTTOM | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) entry_timeRange = create_setting_entry( self.panel_streak, "Time range", self.streak_unit.timeRange, self.streak_unit, conf={ "control_type": odemis.gui.CONTROL_COMBO, "label": "Time range", "tooltip": "Time needed by the streak unit for one sweep " "from top to bottom of the readout camera chip." }) entry_timeRange.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.ctrl_timeRange = entry_timeRange.value_ctrl entry_triggerDelay = create_setting_entry( self.panel_streak, "Trigger delay", self.streak_delay.triggerDelay, self.streak_delay, conf={ "control_type": odemis.gui.CONTROL_FLT, "label": "Trigger delay", "tooltip": "Change the trigger delay value to " "center the image." }, change_callback=self._onUpdateTriggerDelayMD) entry_triggerDelay.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.ctrl_triggerDelay = entry_triggerDelay.value_ctrl entry_magnification = create_setting_entry( self.panel_streak, "Magnification", self.streak_lens.magnification, self.streak_lens, conf={ "control_type": odemis.gui.CONTROL_COMBO, "label": "Magnification", "tooltip": "Change the magnification of the input" "optics for the streak camera system. \n" "Values < 1: De-magnifying \n" "Values > 1: Magnifying" }) entry_magnification.value_ctrl.SetBackgroundColour( odemis.gui.BG_COLOUR_PANEL) self.combo_magnification = entry_magnification.value_ctrl # remove border self.panel_streak.GetSizer().GetItem(0).SetBorder(0) self.panel_streak.Layout() self.panel.btn_open_streak_calib_file.Bind(wx.EVT_BUTTON, self._onOpenCalibFile) self.panel.btn_save_streak_calib_file.Bind(wx.EVT_BUTTON, self._onSaveCalibFile) def _onUpdateTriggerDelayMD(self, evt): """ Callback method for trigger delay ctrl GUI element. Overwrites the triggerDelay value in the MD after a new value was requested via the GUI. """ evt.Skip() cur_timeRange = self.streak_unit.timeRange.value requested_triggerDelay = self.ctrl_triggerDelay.GetValue() # get a copy of MD trigger2delay_MD = self.streak_delay.getMetadata()[ model.MD_TIME_RANGE_TO_DELAY] trigger2delay_MD[cur_timeRange] = requested_triggerDelay self.streak_delay.updateMetadata( {model.MD_TIME_RANGE_TO_DELAY: trigger2delay_MD}) # Note: updateMetadata should here never raise an exception as the UnitFloatCtrl already # catches errors regarding type and out-of-range inputs # update txt displayed in GUI self._onUpdateTriggerDelayGUI(STATUS, None) def _onUpdateTriggerDelayGUI(self, mode, filename): """ Updates the GUI elements regarding the new trigger delay value. :parameter mode: (constant) SAVE, LOAD, ERROR or STATUS for display :parameter filename: (str) filename of the loaded/saved file """ if mode in (LOAD, SAVE): self.panel.txt_StreakCalibFilename.Value = "%s" % filename self.panel.txt_StreakCalibFilename.SetForegroundColour( odemis.gui.FG_COLOUR_EDIT) elif mode == ERROR: self.panel.txt_StreakCalibFilename.Value = "Error while loading file!" self.panel.txt_StreakCalibFilename.SetForegroundColour( odemis.gui.FG_COLOUR_HIGHLIGHT) elif mode == STATUS: self.panel.txt_StreakCalibFilename.Value = "Calibration not saved yet!" self.panel.txt_StreakCalibFilename.SetForegroundColour( odemis.gui.FG_COLOUR_WARNING) else: raise ValueError( "Mode %s for display of the current status of the trigger delay " "calibration file is unknown.", mode) def _onOpenCalibFile(self, event): """ Loads a calibration file (*csv) containing the time range and the corresponding trigger delay for streak camera calibration. """ logging.debug( "Open trigger delay calibration file for temporal acquisition.") dialog = wx.FileDialog(self.panel, message="Choose a calibration file to load", defaultDir=self._calib_path, defaultFile="", style=wx.FD_OPEN, wildcard="csv files (*.csv)|*.csv") # Show the dialog and check whether is was accepted or cancelled if dialog.ShowModal() != wx.ID_OK: return # get selected path + filename and update default directory self._calib_path = dialog.GetDirectory() path = dialog.GetPath() filename = dialog.GetFilename() # read file with open(path, 'rb') as csvfile: calibFile = csv.reader(csvfile, delimiter=':') try: tr2d_dict = calibration.get_time_range_to_trigger_delay( calibFile, self.streak_unit.timeRange.choices, self.streak_delay.triggerDelay.range) except ValueError as error: self._onUpdateTriggerDelayGUI( ERROR, filename) # update txt displayed in GUI logging.error(error) return # update the MD self.streak_delay.updateMetadata( {model.MD_TIME_RANGE_TO_DELAY: tr2d_dict}) # update triggerDelay shown in GUI cur_timeRange = self.streak_unit.timeRange.value # find the corresponding trigger delay key = odemis.util.find_closest(cur_timeRange, tr2d_dict.keys()) # Note: no need to check almost_equal again as we do that already when loading the file self.streak_delay.triggerDelay.value = tr2d_dict[ key] # set the new value self._onUpdateTriggerDelayGUI(LOAD, filename) # update txt displayed in GUI def _onSaveCalibFile(self, event): """ Saves a calibration file (*csv) containing the time range and the corresponding trigger delay for streak camera calibration. """ logging.debug( "Save trigger delay calibration file for temporal acquisition.") dialog = wx.FileDialog( self.panel, message= "Choose a filename and destination to save the calibration file. " "It is advisory to include the SEM voltage into the filename.", defaultDir=self._calib_path, defaultFile="", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, wildcard="csv files (*.csv)|*.csv") # Show the dialog and check whether is was accepted or cancelled if dialog.ShowModal() != wx.ID_OK: return # get selected path + filename and update default directory self._calib_path = dialog.GetDirectory() path = dialog.GetPath() filename = dialog.GetFilename() # check if filename is provided with the correct extension if os.path.splitext(filename)[1] != ".csv": filename += ".csv" path += ".csv" # get a copy of the triggerDelay dict from MD triggerDelay_dict = self.streak_delay.getMetadata()[ model.MD_TIME_RANGE_TO_DELAY] with open(path, 'wb') as csvfile: calibFile = csv.writer(csvfile, delimiter=':') for key in triggerDelay_dict.keys(): calibFile.writerow([key, triggerDelay_dict[key]]) # update txt displayed in GUI self._onUpdateTriggerDelayGUI(SAVE, filename)