"""This module contains the interfaces to the different tabs and dialogs in the ClearMap GUI."""importosfromPyQt5.QtWidgetsimportQWhatsThisfromClearMap.Utils.exceptionsimportMissingRequirementExceptionfromClearMap.gui.gui_utilsimportcreate_clearmap_widget
[docs]classGenericUi:""" The first layer of interface. This is not implemented directly but is the base class of GenericTab and GenericDialog, themselves interfaces """def__init__(self,main_window,name,ui_file_name,widget_class_name):""" Parameters ---------- main_window: ClearMapGui name: str ui_file_name: str widget_class_name: str """self.main_window=main_windowself.name=nameself.ui_file_name=ui_file_nameself.widget_class_name=widget_class_nameself.ui=Noneself.params=Noneself.progress_watcher=self.main_window.progress_watcher
[docs]defset_params(self,*args):""" Set the params object which links the UI and the configuration file Parameters ---------- args Returns ------- """raiseNotImplementedError()
[docs]defload_config_to_gui(self):""" Set every control on the UI to the value in the params Returns ------- """self.params.cfg_to_ui()
[docs]defpatch_button_boxes(self):""" Patch the button boxes in the ui so that the text corresponds to that defined in QtCreqtor Returns ------- """self.main_window.patch_button_boxes(self.ui)
[docs]classGenericDialog(GenericUi):""" Interface to any dialog associated with parameters """def__init__(self,main_window,name,file_name):super().__init__(main_window,name,file_name,'QDialog')
[docs]classGenericTab(GenericUi):""" The interface to all tab managers. A tab manager includes a tab widget, the associated parameters and potentially a processor object which handles the computations. """def__init__(self,main_window,name,tab_idx,ui_file_name):""" Parameters ---------- main_window: ClearMapGui name: str tab_idx: int ui_file_name: str """super().__init__(main_window,name,ui_file_name,'QTabWidget')self.processing_type=Noneself.tab_idx=tab_idxself.minimum_width=200# REFACTOR:self.advanced_control_names=[]
[docs]defhandle_advanced_checked(self):""" Activate the *advanced* mode which will display more controls Returns ------- """checked=self.ui.advancedCheckBox.isChecked()forctrl_nameinself.advanced_control_names:ctrl=getattr(self.ui,ctrl_name)ctrl.setVisible(checked)
[docs]defset_params(self,*args):""" Set the params object which links the UI and the configuration file Parameters ---------- args Returns ------- """raiseNotImplementedError()
[docs]defread_configs(self,cfg_path):# FIXME: REFACTOR: parse_configs""" Read the configuration file associated with the params from the filesystem Parameters ---------- cfg_path Returns ------- """self.params.read_configs(cfg_path)
[docs]deffix_config(self):# TODO: check if could make part of self.params may not be possible since not set""" Amend the config for the tabs that required live patching the config Returns ------- """self.params.fix_cfg_file(self.params.config_path)
[docs]defdisable(self):""" Disable this tab (UI element) Returns ------- """self.ui.setEnabled(False)
[docs]defstep_exists(self,step_name,file_list):""" Check that prerequisite step step_name has been run and produced the outputs in file_list Parameters ---------- step_name file_list Returns ------- """ifisinstance(file_list,str):file_list=[file_list]forf_pathinfile_list:ifnotos.path.exists(f_path):self.main_window.print_error_msg(f'Missing {step_name} file {f_path}. 'f'Please ensure {step_name} is run first.')returnFalsereturnTrue
[docs]defsetup_workers(self):""" Setup the optional workers (which handle the computations) associated with this tab Returns ------- """pass
[docs]defdisplay_whats_this(self,widget):""" Utility function to display the detailed *whatsThis* message associated with the control Parameters ---------- widget Returns ------- """QWhatsThis.showText(widget.pos(),widget.whatsThis(),widget)
[docs]defconnect_whats_this(self,info_btn,whats_this_ctrl):""" Utility function to bind the info button to the display of the detailed *whatsThis* message associated with the control Parameters ---------- widget Returns ------- """info_btn.clicked.connect(lambda:self.display_whats_this(whats_this_ctrl))
[docs]defwrap_step(self,task_name,func,step_args=None,step_kw_args=None,n_steps=1,abort_func=None,save_cfg=True,nested=True,close_when_done=True,main_thread=False):# FIXME: saving config should be default""" This function wraps the computations of the tab. It should start a new thread to ensure that the UI remains responsive (unless main_thread is set to True). It will also start a progress dialog Parameters ---------- task_name : str The name of the task to be displayed func : function The function to run step_args : list The positional arguments to func step_kw_args : dict The keyword arguments to func n_steps : int The number of top level steps in the computation. This will be disabled if nested is False. abort_func : function The function to trigger to abort the execution of the computation (bound to the abort button) save_cfg : bool Whether to save the configuration to disk before running the computation. This is usually the right choice to ensure that the config reloaded by func is up to date. nested : bool Whether the computation has 2 levels of progress close_when_done : bool Close the progress dialog when func has finished executing main_thread : bool Whether to run in the main thread. Default is False to ensure that the UI thread remains responsive, a new thread will be spawned. Returns ------- """ifstep_argsisNone:step_args=[]ifstep_kw_argsisNone:step_kw_args={}ifsave_cfg:self.params.ui_to_cfg()iftask_name:ifnotnested:n_steps=0self.main_window.make_progress_dialog(task_name,n_steps=n_steps,abort=abort_func)try:ifmain_thread:func(*step_args,**step_kw_args)else:self.main_window.wrap_in_thread(func,*step_args,**step_kw_args)exceptMissingRequirementExceptionasex:self.main_window.print_error_msg(ex)self.main_window.popup(str(ex),base_msg=f'Could not run operation {func.__name__}',print_warning=False)finally:ifself.preprocessorisnotNoneandself.preprocessor.workspaceisnotNone:# WARNING: hackyself.preprocessor.workspace.executor=Noneifclose_when_done:self.progress_watcher.finish()else:msg=f'{self.progress_watcher.main_step_name} finished'self.main_window.print_status_msg(msg)self.main_window.log_progress(f' : {msg}')
[docs]classPostProcessingTab(GenericTab):""" Interface to all the tab managers in charge of post processing the data (e.e. typically detecting relevant info in the data). One particularity of a post processing tab manager is that it includes the corresponding pre processor. A tab manager includes a tab widget, the associated parameters and potentially a processor object which handles the computations. """def__init__(self,main_window,name,tab_idx,ui_file_name):super().__init__(main_window,name,tab_idx,ui_file_name)self.preprocessor=Noneself.processing_type='post'
[docs]defset_params(self,sample_params,alignment_params):""" Set the params object which links the UI and the configuration file Parameters ---------- args Returns ------- """raiseNotImplementedError()
[docs]defsetup_preproc(self,pre_processor):""" Set the PreProcessor object associated with the sample Parameters ---------- pre_processor : PreProcessor Returns ------- """self.preprocessor=pre_processor
[docs]defplot_slicer(self,slicer_prefix,tab,params):""" Display the orthoslicer to pick a subset of 3D data. This is typically used to create a small dataset to test parameters for long running operations before analysing the whole sample Parameters ---------- slicer_prefix tab params Returns ------- """self.main_window.clear_plots()# if self.preprocessor.was_registered:# img = mhd_read(self.preprocessor.annotation_file_path) # FIXME: does not work (probably compressed format)# else:img=self.preprocessor.workspace.source('resampled')self.main_window.ortho_viewer.setup(img,params,parent=self.main_window)dvs=self.main_window.ortho_viewer.plot_orthogonal_views()ranges=[[params.reverse_scale_axis(v,ax)forvinvals]forax,valsinzip('xyz',params.slice_tuples)]self.main_window.ortho_viewer.update_ranges(ranges)self.main_window.setup_plots(dvs,['x','y','z'])# WARNING: needs to be done after setupforaxis,ax_maxinzip('XYZ',self.preprocessor.raw_stitched_shape):# FIXME: not always raw stitchedgetattr(tab,f'{slicer_prefix}{axis}RangeMax').setMaximum(ax_max)