"""
This module contains the interfaces to the different tabs and dialogs in the ClearMap GUI.
"""
import os
from PyQt5.QtWidgets import QWhatsThis
from ClearMap.Utils.exceptions import MissingRequirementException
from ClearMap.gui.gui_utils import create_clearmap_widget
[docs]
class GenericUi:
"""
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_window
self.name = name
self.ui_file_name = ui_file_name
self.widget_class_name = widget_class_name
self.ui = None
self.params = None
self.progress_watcher = self.main_window.progress_watcher
[docs]
def init_ui(self):
self.ui = create_clearmap_widget(f'{self.ui_file_name}.ui', patch_parent_class=self.widget_class_name)
self.patch_button_boxes()
[docs]
def set_params(self, *args):
"""
Set the params object which links the UI and the configuration file
Parameters
----------
args
Returns
-------
"""
raise NotImplementedError()
[docs]
def load_config_to_gui(self):
"""
Set every control on the UI to the value in the params
Returns
-------
"""
self.params.cfg_to_ui()
[docs]
def set_progress_watcher(self, watcher):
pass
[docs]
class GenericDialog(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]
def init_ui(self):
super().init_ui()
self.ui.setWindowTitle(self.name.title())
[docs]
def set_params(self, *args):
raise NotImplementedError()
[docs]
class GenericTab(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 = None
self.tab_idx = tab_idx
self.minimum_width = 200 # REFACTOR:
self.advanced_control_names = []
[docs]
def init_ui(self):
super().init_ui()
self.ui.setMinimumWidth(self.minimum_width)
self.main_window.tabWidget.removeTab(self.tab_idx)
self.main_window.tabWidget.insertTab(self.tab_idx, self.ui, self.name.title())
if hasattr(self.ui, 'advancedCheckBox'):
for ctrl_name in self.advanced_control_names:
ctrl = getattr(self.ui, ctrl_name)
ctrl.setVisible(False)
self.ui.advancedCheckBox.stateChanged.connect(self.handle_advanced_checked)
[docs]
def handle_advanced_checked(self):
"""
Activate the *advanced* mode which will display more controls
Returns
-------
"""
checked = self.ui.advancedCheckBox.isChecked()
for ctrl_name in self.advanced_control_names:
ctrl = getattr(self.ui, ctrl_name)
ctrl.setVisible(checked)
[docs]
def set_params(self, *args):
"""
Set the params object which links the UI and the configuration file
Parameters
----------
args
Returns
-------
"""
raise NotImplementedError()
[docs]
def read_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]
def fix_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]
def disable(self):
"""
Disable this tab (UI element)
Returns
-------
"""
self.ui.setEnabled(False)
[docs]
def step_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
-------
"""
if isinstance(file_list, str):
file_list = [file_list]
for f_path in file_list:
if not os.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.')
return False
return True
[docs]
def setup_workers(self):
"""
Setup the optional workers (which handle the computations) associated with this tab
Returns
-------
"""
pass
[docs]
def display_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]
def connect_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]
def wrap_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
-------
"""
if step_args is None:
step_args = []
if step_kw_args is None:
step_kw_args = {}
if save_cfg:
self.params.ui_to_cfg()
if task_name:
if not nested:
n_steps = 0
self.main_window.make_progress_dialog(task_name, n_steps=n_steps, abort=abort_func)
try:
if main_thread:
func(*step_args, **step_kw_args)
else:
self.main_window.wrap_in_thread(func, *step_args, **step_kw_args)
except MissingRequirementException as ex:
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:
if self.preprocessor is not None and self.preprocessor.workspace is not None: # WARNING: hacky
self.preprocessor.workspace.executor = None
if close_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]
class PostProcessingTab(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 = None
self.processing_type = 'post'
[docs]
def set_params(self, sample_params, alignment_params):
"""
Set the params object which links the UI and the configuration file
Parameters
----------
args
Returns
-------
"""
raise NotImplementedError()
[docs]
def setup_preproc(self, pre_processor):
"""
Set the PreProcessor object associated with the sample
Parameters
----------
pre_processor : PreProcessor
Returns
-------
"""
self.preprocessor = pre_processor
[docs]
def plot_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) for v in vals] for ax, vals in zip('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 setup
for axis, ax_max in zip('XYZ', self.preprocessor.raw_stitched_shape): # FIXME: not always raw stitched
getattr(tab, f'{slicer_prefix}{axis}RangeMax').setMaximum(ax_max)