# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file."""Action handler==============The :mod:`cdl.core.gui.actionhandler` module handles all application actions(menus, toolbars, context menu). These actions point to DataLab panels, processors,objecthandler, ...Utility classes---------------.. autoclass:: SelectCond :members:.. autoclass:: ActionCategory :members:Handler classes---------------.. autoclass:: SignalActionHandler :members: :inherited-members:.. autoclass:: ImageActionHandler :members: :inherited-members:"""# pylint: disable=invalid-name # Allows short reference names like x, y, ...from__future__importannotationsimportabcimportenumfromcollections.abcimportCallable,GeneratorfromcontextlibimportcontextmanagerfromtypingimportTYPE_CHECKINGfromguidata.configtoolsimportget_iconfromguidata.qthelpersimportadd_actions,create_actionfromqtpyimportQtCoreasQCfromqtpyimportQtGuiasQGfromqtpyimportQtWidgetsasQWfromcdl.configimportConf,_fromcdl.widgetsimportfitdialogifTYPE_CHECKING:fromcdl.core.gui.objectmodelimportObjectGroupfromcdl.core.gui.panel.imageimportImagePanelfromcdl.core.gui.panel.signalimportSignalPanelfromcdl.core.model.imageimportImageObjfromcdl.core.model.signalimportSignalObj
[docs]classSelectCond:"""Signal or image select conditions"""@staticmethoddef__compat_groups(selected_groups:list[ObjectGroup],min_len:int=1)->bool:"""Check if groups are compatible"""return(len(selected_groups)>=min_lenandall(len(group)==len(selected_groups[0])forgroupinselected_groups)andall(len(group)>0forgroupinselected_groups))
[docs]@staticmethoddefexactly_one(selected_groups:list[ObjectGroup],selected_objects:list[SignalObj|ImageObj],)->bool:"""Exactly one signal or image is selected"""returnlen(selected_groups)==0andlen(selected_objects)==1
[docs]@staticmethod# pylint: disable=unused-argumentdefexactly_one_group(selected_groups:list[ObjectGroup],selected_objects:list[SignalObj|ImageObj],)->bool:"""Exactly one group is selected"""returnlen(selected_groups)==1
[docs]@staticmethod# pylint: disable=unused-argumentdefat_least_one_group_or_one_object(sel_groups:list[ObjectGroup],sel_objects:list[SignalObj|ImageObj],)->bool:"""At least one group or one signal or image is selected"""returnlen(sel_objects)>=1orlen(sel_groups)>=1
[docs]@staticmethod# pylint: disable=unused-argumentdefat_least_one(sel_groups:list[ObjectGroup],sel_objects:list[SignalObj|ImageObj],)->bool:"""At least one signal or image is selected"""returnlen(sel_objects)>=1orSelectCond.__compat_groups(sel_groups,1)
[docs]@staticmethoddefat_least_two(sel_groups:list[ObjectGroup],sel_objects:list[SignalObj|ImageObj],)->bool:"""At least two signals or images are selected"""returnlen(sel_objects)>=2orSelectCond.__compat_groups(sel_groups,2)
[docs]@staticmethod# pylint: disable=unused-argumentdefwith_roi(selected_groups:list[ObjectGroup],selected_objects:list[SignalObj|ImageObj],)->bool:"""At least one signal or image has a ROI"""returnany(obj.roiisnotNoneforobjinselected_objects)
[docs]classActionCategory(enum.Enum):"""Action categories"""FILE=enum.auto()EDIT=enum.auto()VIEW=enum.auto()OPERATION=enum.auto()PROCESSING=enum.auto()ANALYSIS=enum.auto()CONTEXT_MENU=enum.auto()PANEL_TOOLBAR=enum.auto()VIEW_TOOLBAR=enum.auto()SUBMENU=enum.auto()# temporaryPLUGINS=enum.auto()# for plugins actions
classBaseActionHandler(metaclass=abc.ABCMeta):"""Object handling panel GUI interactions: actions, menus, ... Args: panel: Panel to handle panel_toolbar: Panel toolbar (actions related to the panel objects management) view_toolbar: View toolbar (actions related to the panel view, i.e. plot) """OBJECT_STR=""# e.g. "signal"def__init__(self,panel:SignalPanel|ImagePanel,panel_toolbar:QW.QToolBar,view_toolbar:QW.QToolBar,):self.panel=panelself.panel_toolbar=panel_toolbarself.view_toolbar=view_toolbarself.feature_actions={}self.operation_end_actions=Noneself.__category_in_progress:ActionCategory=Noneself.__submenu_in_progress=Falseself.__actions:dict[Callable,list[QW.QAction]]={}self.__submenus:dict[str,QW.QMenu]={}@contextmanagerdefnew_category(self,category:ActionCategory)->Generator[None,None,None]:"""Context manager for creating a new menu. Args: category: Action category Yields: None """self.__category_in_progress=categorytry:yieldfinally:self.__category_in_progress=None@contextmanagerdefnew_menu(self,title:str,icon_name:str|None=None)->Generator[None,None,None]:"""Context manager for creating a new menu. Args: title: Menu title icon_name: Menu icon name. Defaults to None. Yields: None """key=self.__category_in_progress.name+"/"+titleis_new=keynotinself.__submenusifis_new:self.__submenus[key]=menu=QW.QMenu(title)ificon_name:menu.setIcon(get_icon(icon_name))else:menu=self.__submenus[key]self.__submenu_in_progress=Truetry:yieldfinally:self.__submenu_in_progress=Falseadd_actions(menu,self.feature_actions.pop(ActionCategory.SUBMENU))ifis_new:self.add_to_action_list(menu)defnew_action(self,title:str,position:int|None=None,separator:bool=False,triggered:Callable|None=None,toggled:Callable|None=None,shortcut:QW.QShortcut|None=None,icon_name:str|None=None,tip:str|None=None,select_condition:Callable|str|None=None,context_menu_pos:int|None=None,context_menu_sep:bool=False,toolbar_pos:int|None=None,toolbar_sep:bool=False,toolbar_category:ActionCategory|None=None,)->QW.QAction:"""Create new action and add it to list of actions. Args: title: action title position: add action to menu at this position. Defaults to None. separator: add separator before action in menu (or after if pos is positive). Defaults to False. triggered: triggered callback. Defaults to None. toggled: toggled callback. Defaults to None. shortcut: shortcut. Defaults to None. icon_name: icon name. Defaults to None. tip: tooltip. Defaults to None. select_condition: selection condition. Defaults to None. If str, must be the name of a method of SelectCond, i.e. one of "always", "exactly_one", "exactly_one_group", "at_least_one_group_or_one_object", "at_least_one", "at_least_two", "with_roi". context_menu_pos: add action to context menu at this position. Defaults to None. context_menu_sep: add separator before action in context menu (or after if context_menu_pos is positive). Defaults to False. toolbar_pos: add action to toolbar at this position. Defaults to None. toolbar_sep: add separator before action in toolbar (or after if toolbar_pos is positive). Defaults to False. toolbar_category: toolbar category. Defaults to None. If toolbar_pos is not None, this specifies the category of the toolbar. If None, defaults to ActionCategory.VIEW_TOOLBAR if the current category is ActionCategory.VIEW, else to ActionCategory.PANEL_TOOLBAR. Returns: New action """ifisinstance(select_condition,str):assertselect_conditioninSelectCond.__dict__select_condition=getattr(SelectCond,select_condition)action=create_action(parent=self.panel,title=title,triggered=triggered,toggled=toggled,shortcut=shortcut,icon=get_icon(icon_name)ificon_nameelseNone,tip=tip,context=QC.Qt.WidgetWithChildrenShortcut,# [1])self.panel.addAction(action)# [1]# [1] This is needed to make actions work with shortcuts for active panel,# because some of the shortcuts are using the same keybindings for both panels.# (Fixes #10)self.add_action(action,select_condition)self.add_to_action_list(action,None,position,separator)ifcontext_menu_posisnotNone:self.add_to_action_list(action,ActionCategory.CONTEXT_MENU,context_menu_pos,context_menu_sep)iftoolbar_posisnotNone:iftoolbar_categoryisNone:ifself.__category_in_progressisActionCategory.VIEW:toolbar_category=ActionCategory.VIEW_TOOLBARelse:toolbar_category=ActionCategory.PANEL_TOOLBARself.add_to_action_list(action,toolbar_category,toolbar_pos,toolbar_sep)returnactiondefadd_to_action_list(self,action:QW.QAction,category:ActionCategory|None=None,pos:int|None=None,sep:bool=False,)->None:"""Add action to list of actions. Args: action: action to add category: action category. Defaults to None. If None, action is added to the current category. pos: add action to menu at this position. Defaults to None. If None, action is added at the end of the list. sep: add separator before action in menu (or after if pos is positive). Defaults to False. """ifcategoryisNone:ifself.__submenu_in_progress:category=ActionCategory.SUBMENUelifself.__category_in_progressisnotNone:category=self.__category_in_progresselse:raiseValueError("No category specified")ifposisNone:pos=-1actionlist=self.feature_actions.setdefault(category,[])add_separator_after=pos>=0ifpos<0:pos=len(actionlist)+pos+1actionlist.insert(pos,action)ifsep:ifadd_separator_after:pos+=1actionlist.insert(pos,None)defadd_action(self,action:QW.QAction,select_condition:Callable|None=None)->None:"""Add action to list of actions. Args: action: action to add select_condition: condition to enable action. Defaults to None. If None, action is enabled if at least one object is selected. """ifselect_conditionisNone:select_condition=SelectCond.at_least_oneself.__actions.setdefault(select_condition,[]).append(action)defselected_objects_changed(self,selected_groups:list[ObjectGroup],selected_objects:list[SignalObj|ImageObj],)->None:"""Update actions based on selected objects. Args: selected_groups: selected groups selected_objects: selected objects """forcond,actlistinself.__actions.items():ifcondisnotNone:foractinactlist:act.setEnabled(cond(selected_groups,selected_objects))defcreate_all_actions(self):"""Create all actions"""self.create_first_actions()self.create_last_actions()add_actions(self.panel_toolbar,self.feature_actions.pop(ActionCategory.PANEL_TOOLBAR))# For the view toolbar, we add the actions to the beginning of the toolbar:before=self.view_toolbar.actions()[0]foractioninself.feature_actions.pop(ActionCategory.VIEW_TOOLBAR):ifactionisNone:self.view_toolbar.insertSeparator(before)else:self.view_toolbar.insertAction(before,action)self.view_toolbar.insertSeparator(before)defcreate_first_actions(self):"""Create actions that are added to the menus in the first place"""withself.new_category(ActionCategory.FILE):self.new_action(_("New %s...")%self.OBJECT_STR,icon_name=f"new_{self.OBJECT_STR}.svg",tip=_("Create new %s")%self.OBJECT_STR,triggered=self.panel.new_object,shortcut=QG.QKeySequence(QG.QKeySequence.New),select_condition=SelectCond.always,toolbar_pos=-1,)self.new_action(_("Open %s...")%self.OBJECT_STR,# icon: fileopen_signal.svg or fileopen_image.svgicon_name=f"fileopen_{self.__class__.__name__[:3].lower()}.svg",tip=_("Open %s")%self.OBJECT_STR,triggered=self.panel.load_from_files,shortcut=QG.QKeySequence(QG.QKeySequence.Open),select_condition=SelectCond.always,toolbar_pos=-1,)self.new_action(_("Save %s...")%self.OBJECT_STR,# icon: filesave_signal.svg or filesave_image.svgicon_name=f"filesave_{self.__class__.__name__[:3].lower()}.svg",tip=_("Save selected %s")%self.OBJECT_STR,triggered=self.panel.save_to_files,shortcut=QG.QKeySequence(QG.QKeySequence.Save),select_condition=SelectCond.at_least_one,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Import text file..."),icon_name="import_text.svg",triggered=self.panel.exec_import_wizard,select_condition=SelectCond.always,)withself.new_category(ActionCategory.EDIT):self.new_action(_("New group..."),icon_name="new_group.svg",tip=_("Create a new group"),triggered=self.panel.new_group,select_condition=SelectCond.always,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Rename group..."),icon_name="rename_group.svg",tip=_("Rename selected group"),triggered=self.panel.rename_group,select_condition=SelectCond.exactly_one_group,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Move up"),icon_name="move_up.svg",tip=_("Move up selection (groups or objects)"),triggered=self.panel.objview.move_up,select_condition=SelectCond.at_least_one_group_or_one_object,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Move down"),icon_name="move_down.svg",tip=_("Move down selection (groups or objects)"),triggered=self.panel.objview.move_down,select_condition=SelectCond.at_least_one_group_or_one_object,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Duplicate"),icon_name="duplicate.svg",tip=_("Duplicate selected %s")%self.OBJECT_STR,separator=True,triggered=self.panel.duplicate_object,shortcut=QG.QKeySequence(QG.QKeySequence.Copy),select_condition=SelectCond.at_least_one_group_or_one_object,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Remove"),icon_name="delete.svg",tip=_("Remove selected %s")%self.OBJECT_STR,triggered=self.panel.remove_object,shortcut=QG.QKeySequence(QG.QKeySequence.Delete),select_condition=SelectCond.at_least_one_group_or_one_object,context_menu_pos=-1,toolbar_pos=-1,)self.new_action(_("Delete all"),select_condition=SelectCond.always,shortcut="Shift+Ctrl+Suppr",tip=_("Delete all groups and objects"),icon_name="delete_all.svg",triggered=self.panel.delete_all_objects,toolbar_pos=-1,)self.new_action(_("Copy metadata"),separator=True,icon_name="metadata_copy.svg",tip=_("Copy metadata from selected %s")%self.OBJECT_STR,triggered=self.panel.copy_metadata,select_condition=SelectCond.exactly_one,toolbar_pos=-1,)self.new_action(_("Paste metadata"),icon_name="metadata_paste.svg",tip=_("Paste metadata into selected %s")%self.OBJECT_STR,triggered=self.panel.paste_metadata,toolbar_pos=-1,)self.new_action(_("Import metadata")+"...",icon_name="metadata_import.svg",tip=_("Import metadata into %s")%self.OBJECT_STR,triggered=self.panel.import_metadata_from_file,select_condition=SelectCond.exactly_one,toolbar_pos=-1,)self.new_action(_("Export metadata")+"...",icon_name="metadata_export.svg",tip=_("Export selected %s metadata")%self.OBJECT_STR,triggered=self.panel.export_metadata_from_file,select_condition=SelectCond.exactly_one,toolbar_pos=-1,)self.new_action(_("Delete object metadata"),icon_name="metadata_delete.svg",tip=_("Delete all that is contained in object metadata"),triggered=self.panel.delete_metadata,toolbar_pos=-1,)self.new_action(_("Add object title to plot"),separator=True,triggered=self.panel.add_label_with_title,tip=_("Add object title as a label to the plot"),)self.new_action(_("Copy titles to clipboard"),icon_name="copy_titles.svg",tip=_("Copy titles of selected objects to clipboard"),triggered=self.panel.copy_titles_to_clipboard,)self.new_action(_("Edit regions of interest..."),separator=True,triggered=self.panel.processor.edit_regions_of_interest,icon_name="roi.svg",select_condition=SelectCond.exactly_one,context_menu_pos=-1,context_menu_sep=True,toolbar_pos=-1,toolbar_category=ActionCategory.VIEW_TOOLBAR,)self.new_action(_("Remove regions of interest"),triggered=self.panel.processor.delete_regions_of_interest,icon_name="roi_delete.svg",select_condition=SelectCond.with_roi,context_menu_pos=-1,)withself.new_category(ActionCategory.VIEW):self.new_action(_("View in a new window")+"...",icon_name="new_window.svg",tip=_("View selected %s in a new window")%self.OBJECT_STR,triggered=self.panel.open_separate_view,context_menu_pos=0,context_menu_sep=True,toolbar_pos=0,)self.new_action(_("Edit annotations")+"...",icon_name="annotations.svg",tip=_("Edit annotations of selected %s")%self.OBJECT_STR,triggered=lambda:self.panel.open_separate_view(edit_annotations=True),context_menu_pos=1,toolbar_pos=-1,)main=self.panel.mainwindowforcatin(ActionCategory.VIEW,ActionCategory.VIEW_TOOLBAR):foractin(main.autorefresh_action,main.showfirstonly_action):self.add_to_action_list(act,cat,-1)self.new_action(_("Refresh manually"),icon_name="refresh-manual.svg",tip=_("Refresh plot, even if auto-refresh is enabled"),shortcut=QG.QKeySequence(QG.QKeySequence.Refresh),triggered=self.panel.manual_refresh,select_condition=SelectCond.always,toolbar_pos=-1,)forcatin(ActionCategory.VIEW,ActionCategory.VIEW_TOOLBAR):self.add_to_action_list(main.showlabel_action,cat,-1)withself.new_category(ActionCategory.OPERATION):self.new_action(_("Sum"),triggered=self.panel.processor.compute_sum,select_condition=SelectCond.at_least_two,icon_name="sum.svg",)self.new_action(_("Average"),triggered=self.panel.processor.compute_average,select_condition=SelectCond.at_least_two,icon_name="average.svg",)self.new_action(_("Difference"),triggered=self.panel.processor.compute_difference,select_condition=SelectCond.at_least_one,icon_name="difference.svg",)self.new_action(_("Quadratic difference"),triggered=self.panel.processor.compute_quadratic_difference,select_condition=SelectCond.at_least_one,icon_name="quadratic_difference.svg",)self.new_action(_("Product"),triggered=self.panel.processor.compute_product,select_condition=SelectCond.at_least_two,icon_name="product.svg",)self.new_action(_("Division"),triggered=self.panel.processor.compute_division,select_condition=SelectCond.at_least_one,icon_name="division.svg",)self.new_action(_("Arithmetic operation")+"...",triggered=self.panel.processor.compute_arithmetic,select_condition=SelectCond.at_least_one,icon_name="arithmetic.svg",)withself.new_menu(_("Constant Operations"),icon_name="constant.svg"):self.new_action(_("Add constant"),triggered=self.panel.processor.compute_addition_constant,select_condition=SelectCond.at_least_one,icon_name="constant_add.svg",)self.new_action(_("Substract constant"),triggered=self.panel.processor.compute_difference_constant,select_condition=SelectCond.at_least_one,icon_name="constant_substract.svg",)self.new_action(_("Multiply by constant"),triggered=self.panel.processor.compute_product_constant,select_condition=SelectCond.at_least_one,icon_name="constant_multiply.svg",)self.new_action(_("Divide by constant"),triggered=self.panel.processor.compute_division_constant,select_condition=SelectCond.at_least_one,icon_name="constant_divide.svg",)self.new_action(_("Absolute value"),triggered=self.panel.processor.compute_abs,separator=True,icon_name="abs.svg",)self.new_action(_("Real part"),triggered=self.panel.processor.compute_re,icon_name="re.svg",)self.new_action(_("Imaginary part"),triggered=self.panel.processor.compute_im,icon_name="im.svg",)self.new_action(_("Convert data type"),triggered=self.panel.processor.compute_astype,separator=True,icon_name="convert_dtype.svg",)self.new_action(_("Exponential"),triggered=self.panel.processor.compute_exp,separator=True,icon_name="exp.svg",)self.new_action(_("Logarithm (base 10)"),triggered=self.panel.processor.compute_log10,separator=False,icon_name="log10.svg",)withself.new_category(ActionCategory.PROCESSING):withself.new_menu(_("Axis transformation"),icon_name="axis_transform.svg"):self.new_action(_("Linear calibration"),triggered=self.panel.processor.compute_calibration,)self.new_action(_("Swap X/Y axes"),triggered=self.panel.processor.compute_swap_axes,icon_name="swap_x_y.svg",)withself.new_menu(_("Level adjustment"),icon_name="level_adjustment.svg"):self.new_action(_("Normalize"),triggered=self.panel.processor.compute_normalize,icon_name="normalize.svg",)self.new_action(_("Clipping"),triggered=self.panel.processor.compute_clip,icon_name="clip.svg",)self.new_action(_("Offset correction"),triggered=self.panel.processor.compute_offset_correction,icon_name="offset_correction.svg",tip=_("Evaluate and subtract the offset value from the data"),)withself.new_menu(_("Noise reduction"),icon_name="noise_reduction.svg"):self.new_action(_("Gaussian filter"),triggered=self.panel.processor.compute_gaussian_filter,)self.new_action(_("Moving average"),triggered=self.panel.processor.compute_moving_average,)self.new_action(_("Moving median"),triggered=self.panel.processor.compute_moving_median,)self.new_action(_("Wiener filter"),triggered=self.panel.processor.compute_wiener,)withself.new_menu(_("Fourier analysis"),icon_name="fourier.svg"):self.new_action(_("FFT"),triggered=self.panel.processor.compute_fft,tip=_("Warning: only real part is plotted"),)self.new_action(_("Inverse FFT"),triggered=self.panel.processor.compute_ifft,tip=_("Warning: only real part is plotted"),)self.new_action(_("Magnitude spectrum"),triggered=self.panel.processor.compute_magnitude_spectrum,)self.new_action(_("Phase spectrum"),triggered=self.panel.processor.compute_phase_spectrum,)self.new_action(_("Power spectral density"),triggered=self.panel.processor.compute_psd,)withself.new_category(ActionCategory.ANALYSIS):self.new_action(_("Statistics")+"...",triggered=self.panel.processor.compute_stats,icon_name="stats.svg",context_menu_pos=-1,context_menu_sep=True,)self.new_action(_("Histogram")+"...",triggered=self.panel.processor.compute_histogram,icon_name="histogram.svg",context_menu_pos=-1,)defcreate_last_actions(self):"""Create actions that are added to the menus in the end"""withself.new_category(ActionCategory.PROCESSING):self.new_action(_("ROI extraction"),triggered=self.panel.processor.compute_roi_extraction,# Icon name is 'signal_roi.svg' or 'image_roi.svg':icon_name=f"{self.OBJECT_STR}_roi.svg",separator=True,)withself.new_category(ActionCategory.ANALYSIS):self.new_action(_("Show results")+"...",triggered=self.panel.show_results,icon_name="show_results.svg",separator=True,select_condition=SelectCond.at_least_one_group_or_one_object,)self.new_action(_("Plot results")+"...",triggered=self.panel.plot_results,icon_name="plot_results.svg",select_condition=SelectCond.at_least_one_group_or_one_object,)self.new_action(_("Delete results")+"...",triggered=self.panel.delete_results,icon_name="delete_results.svg",select_condition=SelectCond.at_least_one_group_or_one_object,)
[docs]classSignalActionHandler(BaseActionHandler):"""Object handling signal panel GUI interactions: actions, menus, ..."""OBJECT_STR=_("signal")
[docs]defcreate_first_actions(self):"""Create actions that are added to the menus in the first place"""super().create_first_actions()withself.new_category(ActionCategory.OPERATION):self.new_action(_("Power"),triggered=self.panel.processor.compute_power,separator=True,icon_name="power.svg",)self.new_action(_("Square root"),triggered=self.panel.processor.compute_sqrt,separator=False,icon_name="sqrt.svg",)self.new_action(_("Derivative"),triggered=self.panel.processor.compute_derivative,separator=True,icon_name="derivative.svg",)self.new_action(_("Integral"),triggered=self.panel.processor.compute_integral,icon_name="integral.svg",)defcra_fit(title,fitdlgfunc,iconname,tip:str|None=None):"""Create curve fitting action"""returnself.new_action(title,triggered=lambda:self.panel.processor.compute_fit(title,fitdlgfunc),icon_name=iconname,tip=tip,)withself.new_category(ActionCategory.PROCESSING):withself.new_menu(_("Axis transformation")):self.new_action(_("Reverse X-axis"),triggered=self.panel.processor.compute_reverse_x,icon_name="reverse_signal_x.svg",)withself.new_menu(_("Frequency filters"),icon_name="highpass.svg"):self.new_action(_("Low-pass filter"),triggered=self.panel.processor.compute_lowpass,icon_name="lowpass.svg",)self.new_action(_("High-pass filter"),triggered=self.panel.processor.compute_highpass,icon_name="highpass.svg",)self.new_action(_("Band-pass filter"),triggered=self.panel.processor.compute_bandpass,icon_name="bandpass.svg",)self.new_action(_("Band-stop filter"),triggered=self.panel.processor.compute_bandstop,icon_name="bandstop.svg",)withself.new_menu(_("Fitting"),icon_name="expfit.svg"):cra_fit(_("Linear fit"),fitdialog.linearfit,"linearfit.svg")self.new_action(_("Polynomial fit"),triggered=self.panel.processor.compute_polyfit,icon_name="polyfit.svg",)cra_fit(_("Gaussian fit"),fitdialog.gaussianfit,"gaussfit.svg")cra_fit(_("Lorentzian fit"),fitdialog.lorentzianfit,"lorentzfit.svg")cra_fit(_("Voigt fit"),fitdialog.voigtfit,"voigtfit.svg")self.new_action(_("Multi-Gaussian fit"),triggered=self.panel.processor.compute_multigaussianfit,icon_name="multigaussfit.svg",)cra_fit(_("Exponential fit"),fitdialog.exponentialfit,"expfit.svg")cra_fit(_("Sinusoidal fit"),fitdialog.sinusoidalfit,"sinfit.svg")cra_fit(_("CDF fit"),fitdialog.cdffit,"cdffit.svg",tip=_("Cumulative distribution function fit, ""related to Error function (erf)"),)self.new_action(_("Windowing"),triggered=self.panel.processor.compute_windowing,icon_name="windowing.svg",tip=_("Apply a window function (or apodization): Hanning, Hamming, ..."),)self.new_action(_("Detrending"),triggered=self.panel.processor.compute_detrending,icon_name="detrending.svg",)self.new_action(_("Interpolation"),triggered=self.panel.processor.compute_interpolation,icon_name="interpolation.svg",)self.new_action(_("Resampling"),triggered=self.panel.processor.compute_resampling,icon_name="resampling.svg",)withself.new_category(ActionCategory.ANALYSIS):self.new_action(_("Full width at half-maximum"),triggered=self.panel.processor.compute_fwhm,separator=True,tip=_("Compute Full Width at Half-Maximum (FWHM)"),icon_name="fwhm.svg",)self.new_action(_("Full width at")+" 1/e²",triggered=self.panel.processor.compute_fw1e2,tip=_("Compute Full Width at Maximum")+"/e²",icon_name="fw1e2.svg",)self.new_action(_("X values at min/max")+"...",triggered=self.panel.processor.compute_x_at_minmax,tip=_("Compute X values at signal minimum and maximum"),)self.new_action(_("Peak detection"),separator=True,triggered=self.panel.processor.compute_peak_detection,icon_name="peak_detect.svg",)self.new_action(_("Sampling rate and period")+"...",separator=True,triggered=self.panel.processor.compute_sampling_rate_period,tip=_("Compute sampling rate and period for a constant sampling signal"),)self.new_action(_("Dynamic parameters")+"...",triggered=self.panel.processor.compute_dynamic_parameters,context_menu_pos=-1,tip=_("Compute dynamic parameters: ENOB, SNR, SINAD, THD, ..."),)self.new_action(_("Bandwidth at -3dB")+"...",triggered=self.panel.processor.compute_bandwidth_3db,context_menu_pos=-1,tip=_("Compute bandwidth at -3dB assuming a low-pass filter ""already expressed in dB"),)self.new_action(_("Contrast"),triggered=self.panel.processor.compute_contrast,tip=_("Compute contrast of a signal, i.e. (max-min)/(max+min), ""e.g. for an image profile"),)withself.new_category(ActionCategory.VIEW):antialiasing_action=self.new_action(_("Curve anti-aliasing"),icon_name="curve_antialiasing.svg",toggled=self.panel.toggle_anti_aliasing,tip=_("Toggle curve anti-aliasing on/off (may slow down plotting)"),toolbar_pos=-1,)antialiasing_action.setChecked(Conf.view.sig_antialiasing.get(True))self.new_action(_("Reset curve styles"),select_condition=SelectCond.always,icon_name="reset_curve_styles.svg",triggered=self.panel.reset_curve_styles,tip=_("Curve styles are looped over a list of predefined styles.\n""This action resets the list to its initial state."),toolbar_pos=-1,)
[docs]defcreate_last_actions(self):"""Create actions that are added to the menus in the end"""withself.new_category(ActionCategory.OPERATION):self.new_action(_("Convolution"),triggered=self.panel.processor.compute_convolution,separator=True,icon_name="convolution.svg",)super().create_last_actions()
[docs]defcreate_first_actions(self):"""Create actions that are added to the menus in the first place"""super().create_first_actions()withself.new_category(ActionCategory.VIEW):showcontrast_action=self.new_action(_("Show contrast panel"),icon_name="contrast.png",tip=_("Show or hide contrast adjustment panel"),select_condition=SelectCond.always,toggled=self.panel.toggle_show_contrast,toolbar_pos=-1,)showcontrast_action.setChecked(Conf.view.show_contrast.get(True))withself.new_category(ActionCategory.OPERATION):self.new_action("Log10(z+n)",triggered=self.panel.processor.compute_logp1,)self.new_action(_("Flat-field correction"),separator=True,triggered=self.panel.processor.compute_flatfield,select_condition=SelectCond.at_least_one,)withself.new_menu(_("Rotation"),icon_name="rotate_right.svg"):self.new_action(_("Flip horizontally"),triggered=self.panel.processor.compute_fliph,icon_name="flip_horizontally.svg",context_menu_pos=-1,context_menu_sep=True,)self.new_action(_("Flip vertically"),triggered=self.panel.processor.compute_flipv,icon_name="flip_vertically.svg",context_menu_pos=-1,)self.new_action(_("Rotate %s right")%"90°",# pylint: disable=consider-using-f-stringtriggered=self.panel.processor.compute_rotate270,icon_name="rotate_right.svg",context_menu_pos=-1,)self.new_action(_("Rotate %s left")%"90°",# pylint: disable=consider-using-f-stringtriggered=self.panel.processor.compute_rotate90,icon_name="rotate_left.svg",context_menu_pos=-1,)self.new_action(_("Rotate arbitrarily..."),triggered=self.panel.processor.compute_rotate,)withself.new_menu(_("Intensity profiles"),icon_name="profile.svg"):self.new_action(_("Line profile..."),triggered=self.panel.processor.compute_line_profile,icon_name="profile.svg",tip=_("Extract horizontal or vertical profile"),context_menu_pos=-1,context_menu_sep=True,)self.new_action(_("Segment profile..."),triggered=self.panel.processor.compute_segment_profile,icon_name="profile_segment.svg",tip=_("Extract profile along a segment"),context_menu_pos=-1,)self.new_action(_("Average profile..."),triggered=self.panel.processor.compute_average_profile,icon_name="profile_average.svg",tip=_("Extract average horizontal or vertical profile"),context_menu_pos=-1,)self.new_action(_("Radial profile extraction..."),triggered=self.panel.processor.compute_radial_profile,icon_name="profile_radial.svg",tip=_("Radial profile extraction around image centroid"),)self.new_action(_("Distribute on a grid..."),triggered=self.panel.processor.distribute_on_grid,icon_name="distribute_on_grid.svg",select_condition=SelectCond.at_least_two,)self.new_action(_("Reset image positions"),triggered=self.panel.processor.reset_positions,icon_name="reset_positions.svg",select_condition=SelectCond.at_least_two,)withself.new_category(ActionCategory.PROCESSING):withself.new_menu(_("Thresholding"),icon_name="thresholding.svg"):self.new_action(_("Parametric thresholding"),triggered=self.panel.processor.compute_threshold,)self.new_action(_("ISODATA thresholding"),triggered=self.panel.processor.compute_threshold_isodata,)self.new_action(_("Li thresholding"),triggered=self.panel.processor.compute_threshold_li,)self.new_action(_("Mean thresholding"),triggered=self.panel.processor.compute_threshold_mean,)self.new_action(_("Minimum thresholding"),triggered=self.panel.processor.compute_threshold_minimum,)self.new_action(_("Otsu thresholding"),triggered=self.panel.processor.compute_threshold_otsu,)self.new_action(_("Triangle thresholding"),triggered=self.panel.processor.compute_threshold_triangle,)self.new_action(_("Yen thresholding"),triggered=self.panel.processor.compute_threshold_yen,)self.new_action(_("All thresholding methods")+"...",triggered=self.panel.processor.compute_all_threshold,separator=True,tip=_("Apply all thresholding methods"),)withself.new_menu(_("Exposure"),icon_name="exposure.svg"):self.new_action(_("Gamma correction"),triggered=self.panel.processor.compute_adjust_gamma,)self.new_action(_("Logarithmic correction"),triggered=self.panel.processor.compute_adjust_log,)self.new_action(_("Sigmoid correction"),triggered=self.panel.processor.compute_adjust_sigmoid,)self.new_action(_("Histogram equalization"),triggered=self.panel.processor.compute_equalize_hist,)self.new_action(_("Adaptive histogram equalization"),triggered=self.panel.processor.compute_equalize_adapthist,)self.new_action(_("Intensity rescaling"),triggered=self.panel.processor.compute_rescale_intensity,)withself.new_menu(_("Restoration"),icon_name="noise_reduction.svg"):self.new_action(_("Total variation denoising"),triggered=self.panel.processor.compute_denoise_tv,)self.new_action(_("Bilateral filter denoising"),triggered=self.panel.processor.compute_denoise_bilateral,)self.new_action(_("Wavelet denoising"),triggered=self.panel.processor.compute_denoise_wavelet,)self.new_action(_("White Top-Hat denoising"),triggered=self.panel.processor.compute_denoise_tophat,)self.new_action(_("All denoising methods")+"...",triggered=self.panel.processor.compute_all_denoise,separator=True,tip=_("Apply all denoising methods"),)withself.new_menu(_("Morphology"),icon_name="morphology.svg"):self.new_action(_("White Top-Hat (disk)"),triggered=self.panel.processor.compute_white_tophat,)self.new_action(_("Black Top-Hat (disk)"),triggered=self.panel.processor.compute_black_tophat,)self.new_action(_("Erosion (disk)"),triggered=self.panel.processor.compute_erosion,)self.new_action(_("Dilation (disk)"),triggered=self.panel.processor.compute_dilation,)self.new_action(_("Opening (disk)"),triggered=self.panel.processor.compute_opening,)self.new_action(_("Closing (disk)"),triggered=self.panel.processor.compute_closing,)self.new_action(_("All morphological operations")+"...",triggered=self.panel.processor.compute_all_morphology,separator=True,tip=_("Apply all morphological operations"),)withself.new_menu(_("Edges"),icon_name="edges.svg"):self.new_action(_("Roberts filter"),triggered=self.panel.processor.compute_roberts)self.new_action(_("Prewitt filter"),triggered=self.panel.processor.compute_prewitt,separator=True,)self.new_action(_("Prewitt filter (horizontal)"),triggered=self.panel.processor.compute_prewitt_h,)self.new_action(_("Prewitt filter (vertical)"),triggered=self.panel.processor.compute_prewitt_v,)self.new_action(_("Sobel filter"),triggered=self.panel.processor.compute_sobel,separator=True,)self.new_action(_("Sobel filter (horizontal)"),triggered=self.panel.processor.compute_sobel_h,)self.new_action(_("Sobel filter (vertical)"),triggered=self.panel.processor.compute_sobel_v,)self.new_action(_("Scharr filter"),triggered=self.panel.processor.compute_scharr,separator=True,)self.new_action(_("Scharr filter (horizontal)"),triggered=self.panel.processor.compute_scharr_h,)self.new_action(_("Scharr filter (vertical)"),triggered=self.panel.processor.compute_scharr_v,)self.new_action(_("Farid filter"),triggered=self.panel.processor.compute_farid,separator=True,)self.new_action(_("Farid filter (horizontal)"),triggered=self.panel.processor.compute_farid_h,)self.new_action(_("Farid filter (vertical)"),triggered=self.panel.processor.compute_farid_v,)self.new_action(_("Laplace filter"),triggered=self.panel.processor.compute_laplace,separator=True,)self.new_action(_("All edges filters")+"...",triggered=self.panel.processor.compute_all_edges,separator=True,tip=_("Compute all edges filters"),)self.new_action(_("Canny filter"),triggered=self.panel.processor.compute_canny)self.new_action(_("Butterworth filter"),triggered=self.panel.processor.compute_butterworth,)withself.new_category(ActionCategory.ANALYSIS):# TODO: [P3] Add "Create ROI grid..." action to create a regular grid# or ROIs (maybe reuse/derive from `core.gui.processor.image.GridParam`)self.new_action(_("Centroid"),separator=True,triggered=self.panel.processor.compute_centroid,tip=_("Compute image centroid"),)self.new_action(_("Minimum enclosing circle center"),triggered=self.panel.processor.compute_enclosing_circle,tip=_("Compute smallest enclosing circle center"),)self.new_action(_("2D peak detection"),separator=True,triggered=self.panel.processor.compute_peak_detection,tip=_("Compute automatic 2D peak detection"),)self.new_action(_("Contour detection"),triggered=self.panel.processor.compute_contour_shape,tip=_("Compute contour shape fit"),)self.new_action(_("Circle Hough transform"),triggered=self.panel.processor.compute_hough_circle_peaks,tip=_("Detect circular shapes using circle Hough transform"),)withself.new_menu(_("Blob detection")):self.new_action(_("Blob detection (DOG)"),triggered=self.panel.processor.compute_blob_dog,tip=_("Detect blobs using Difference of Gaussian (DOG) method"),)self.new_action(_("Blob detection (DOH)"),triggered=self.panel.processor.compute_blob_doh,tip=_("Detect blobs using Determinant of Hessian (DOH) method"),)self.new_action(_("Blob detection (LOG)"),triggered=self.panel.processor.compute_blob_log,tip=_("Detect blobs using Laplacian of Gaussian (LOG) method"),)self.new_action(_("Blob detection (OpenCV)"),triggered=self.panel.processor.compute_blob_opencv,tip=_("Detect blobs using OpenCV SimpleBlobDetector"),)
[docs]defcreate_last_actions(self):"""Create actions that are added to the menus in the end"""withself.new_category(ActionCategory.PROCESSING):self.new_action(_("Resize"),triggered=self.panel.processor.compute_resize,icon_name="resize.svg",separator=True,)self.new_action(_("Pixel binning"),triggered=self.panel.processor.compute_binning,icon_name="binning.svg",)super().create_last_actions()