diff --git a/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_model.py b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_model.py new file mode 100644 index 000000000000..4f34630b1387 --- /dev/null +++ b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_model.py @@ -0,0 +1,152 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source, +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + +from mantidqtinterfaces.Muon.GUI.Common.ADSHandler.workspace_naming import ( + get_run_numbers_as_string_from_workspace_name, + get_base_data_directory, + get_phase_table_workspace_name, +) +from mantidqtinterfaces.Muon.GUI.Common.ADSHandler.muon_workspace_wrapper import MuonWorkspaceWrapper +from mantidqtinterfaces.Muon.GUI.Common.utilities.algorithm_utils import run_CalMuonDetectorPhases +from mantid import AlgorithmManager +from mantidqtinterfaces.Muon.GUI.Common.thread_model_wrapper import ThreadModelWrapper +from mantidqtinterfaces.Muon.GUI.Common import thread_model +from mantidqtinterfaces.Muon.GUI.Common.muon_phasequad import MuonPhasequad + + +class PhaseTableModel: + def __init__(self, context): + self._context = context + self._current_alg = None + self._phasequad = None + self._new_table_name = "" + self._calculation_thread = None + self._phasequad_calculation_thread = None + + @property + def context(self): + return self._context + + @property + def phase_context(self): + return self._context.phase_context + + @property + def group_pair_context(self): + return self._context.group_pair_context + + @property + def phasequad(self): + return self._phasequad + + @phasequad.setter + def phasequad(self, value): + self._phasequad = value + + @property + def new_table_name(self): + return self._new_table_name + + @new_table_name.setter + def new_table_name(self, value): + self._new_table_name = value + + @property + def calculation_thread(self): + return self._calculation_thread + + @property + def group_pair_names(self): + return self._context.group_pair_context.group_names + + @property + def group_pairs(self): + return self._context.group_pair_context.pairs + + @property + def group_phasequads(self): + return self._context.group_pair_context.phasequads + + @property + def instrument(self): + return self._context.data_context.instrument + + @instrument.setter + def instrument(self, value): + self._context.data_context.instrument = value + + def cancel_current_alg(self): + if self._current_alg is not None: + self._current_alg.cancel() + + def clear_current_alg(self): + self._current_alg = None + + def get_grouped_workspace_names(self): + return self._context.getGroupedWorkspaceNames() + + def add_phase_table_to_ads(self, base_name): + run = get_run_numbers_as_string_from_workspace_name(base_name, self.instrument) + directory = get_base_data_directory(self._context, run) + muon_workspace_wrapper = MuonWorkspaceWrapper(directory + base_name) + muon_workspace_wrapper.show() + self.phase_context.add_phase_table(muon_workspace_wrapper) + + @staticmethod + def add_fitting_info_to_ads(fit_workspace_name): + muon_workspace_wrapper = MuonWorkspaceWrapper(fit_workspace_name) + muon_workspace_wrapper.show() + + def create_parameters_for_cal_muon_phase_algorithm(self): + parameters = dict() + parameters["FirstGoodData"] = self.phase_context.options_dict["first_good_time"] + parameters["LastGoodData"] = self.phase_context.options_dict["last_good_time"] + parameters["InputWorkspace"] = self.phase_context.options_dict["input_workspace"] + forward_group = self.phase_context.options_dict["forward_group"] + parameters["ForwardSpectra"] = self.group_pair_context[forward_group].detectors + backward_group = self.phase_context.options_dict["backward_group"] + parameters["BackwardSpectra"] = self.group_pair_context[backward_group].detectors + parameters["DetectorTable"] = get_phase_table_workspace_name( + parameters["InputWorkspace"], forward_group, backward_group, new_name=self.new_table_name + ) + return parameters + + def CalMuonDetectorPhases_wrapper(self, parameters, fitting_workspace_name): + self.current_alg = AlgorithmManager.create("CalMuonDetectorPhases") + detector_table, fitting_information = run_CalMuonDetectorPhases(parameters, self.current_alg, fitting_workspace_name) + self.current_alg = None + + return detector_table, fitting_information + + def remove_phasequad(self, phasequad): + self.group_pair_context.remove_phasequad(phasequad) + + def calculate_phase_table_on_thread(self, calculation_func, started_callback, success_callback, error_callback): + # Calculate the new table in a separate thread + self._calculation_thread = self._create_calculation_thread(calculation_func) + self._calculation_thread.threadWrapperSetUp( + started_callback, + success_callback, + error_callback, + ) + self._calculation_thread.start() + + def calculate_phasequad_on_thread(self, name, calculation_func, started_callback, success_callback, error_callback): + # Calculate the phasequad in a separate thread + table = self._context.phase_context.options_dict["phase_table_for_phase_quad"] + self._phasequad = MuonPhasequad(str(name), table) + self._phasequad_calculation_thread = self._create_calculation_thread(calculation_func) + self._phasequad_calculation_thread.threadWrapperSetUp( + started_callback, + success_callback, + error_callback, + ) + self._phasequad_calculation_thread.start() + + @staticmethod + def _create_calculation_thread(calculation_func): + calculation_model = ThreadModelWrapper(calculation_func) + return thread_model.ThreadModel(calculation_model) diff --git a/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_presenter.py b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_presenter.py index d59303a8b2f6..cc42ff61caf5 100644 --- a/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_presenter.py +++ b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_presenter.py @@ -4,32 +4,17 @@ # NScD Oak Ridge National Laboratory, European Spallation Source, # Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS # SPDX - License - Identifier: GPL - 3.0 + -from mantidqtinterfaces.Muon.GUI.Common.thread_model_wrapper import ThreadModelWrapper -from mantidqtinterfaces.Muon.GUI.Common import thread_model -from mantidqtinterfaces.Muon.GUI.Common.utilities.algorithm_utils import run_CalMuonDetectorPhases -from mantidqtinterfaces.Muon.GUI.Common.muon_phasequad import MuonPhasequad from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_view import REAL_PART, IMAGINARY_PART from mantidqt.utils.observer_pattern import Observable, GenericObserver, GenericObservable import re -from mantidqtinterfaces.Muon.GUI.Common.ADSHandler.workspace_naming import ( - get_phase_table_workspace_name, - get_fitting_workspace_name, - get_base_data_directory, - get_run_number_from_workspace_name, - get_run_numbers_as_string_from_workspace_name, -) -from mantidqtinterfaces.Muon.GUI.Common.ADSHandler.muon_workspace_wrapper import MuonWorkspaceWrapper +from mantidqtinterfaces.Muon.GUI.Common.ADSHandler.workspace_naming import get_fitting_workspace_name, get_run_number_from_workspace_name from mantidqtinterfaces.Muon.GUI.Common.utilities.run_string_utils import valid_name_regex -import mantid class PhaseTablePresenter(object): - def __init__(self, view, context): + def __init__(self, view, model): self.view = view - self.context = context - self.current_alg = None - self._phasequad_obj = None - self._new_table_name = "" + self.model = model self.group_change_observer = GenericObserver(self.update_current_groups_list) self.run_change_observer = GenericObserver(self.update_current_run_list) @@ -55,20 +40,20 @@ def __init__(self, view, context): def update_view_from_model(self): self.view.disable_updates() - self.view.set_input_combo_box(self.context.getGroupedWorkspaceNames()) - self.view.set_group_combo_boxes(self.context.group_pair_context.group_names) + self.view.set_input_combo_box(self.model.get_grouped_workspace_names()) + self.view.set_group_combo_boxes(self.model.group_pair_names) self.update_current_phase_tables() - for key, item in self.context.phase_context.options_dict.items(): + for key, item in self.model.phase_context.options_dict.items(): setattr(self.view, key, item) self.view.enable_updates() def update_model_from_view(self): - for key in self.context.phase_context.options_dict: - self.context.phase_context.options_dict[key] = getattr(self.view, key, None) + for key in self.model.phase_context.options_dict: + self.model.phase_context.options_dict[key] = getattr(self.view, key, None) def validate_phasequad_name(self, name): """Checks if name is in use by another pair of group, and that it is in a valid format""" - if name in self.context.group_pair_context.pairs: + if name in self.model.group_pairs: self.view.warning_popup("Groups and pairs (including phasequads) must have unique names") return False if not re.match(valid_name_regex, name): @@ -77,8 +62,8 @@ def validate_phasequad_name(self, name): return True def update_current_run_list(self): - self.view.set_input_combo_box(self.context.getGroupedWorkspaceNames()) - self.view.set_group_combo_boxes(self.context.group_pair_context.group_names) + self.view.set_input_combo_box(self.model.get_grouped_workspace_names()) + self.view.set_group_combo_boxes(self.model.group_pair_names) self.update_model_from_view() if self.view.input_workspace == "": @@ -89,13 +74,12 @@ def update_current_run_list(self): self.view.setEnabled(True) def update_current_groups_list(self): - self.view.set_group_combo_boxes(self.context.group_pair_context.group_names) + self.view.set_group_combo_boxes(self.model.group_pair_names) self.update_model_from_view() def cancel_current_alg(self): """Cancels the current algorithm if executing""" - if self.current_alg is not None: - self.current_alg.cancel() + self.model.cancel_current_alg() def handle_thread_calculation_started(self): """Generic handling of starting calculation threads""" @@ -105,13 +89,13 @@ def handle_thread_calculation_success(self): """Generic handling of success from calculation threads""" self.enable_editing_notifier.notify_subscribers() self.view.enable_widget() - self.current_alg = None + self.model.clear_current_alg() def handle_thread_calculation_error(self, error): """Generic handling of error from calculation threads""" self.enable_editing_notifier.notify_subscribers() self.view.warning_popup(error.exc_value) - self.current_alg = None + self.model.clear_current_alg() """=============== Phase table methods ===============""" @@ -122,9 +106,9 @@ def handle_last_good_data_changed(self): self._validate_data_changed(self.view.last_good_time, "Last Good Data") def _validate_data_changed(self, data, string): - run = float(get_run_number_from_workspace_name(self.view.input_workspace, self.context.data_context.instrument)) - last_good_time = self.context.last_good_data([run]) - first_good_time = self.context.first_good_data([run]) + run = float(get_run_number_from_workspace_name(self.view.input_workspace, self.model.instrument)) + last_good_time = self.model.context.last_good_data([run]) + first_good_time = self.model.context.first_good_data([run]) if self.view.first_good_time > self.view.last_good_time: self.view.first_good_time = first_good_time @@ -137,52 +121,28 @@ def _validate_data_changed(self, data, string): self.view.last_good_time = last_good_time self.view.warning_popup(f"{string} cannot be greater than {last_good_time}") - def add_phase_table_to_ADS(self, base_name): - run = get_run_numbers_as_string_from_workspace_name(base_name, self.context.data_context.instrument) - directory = get_base_data_directory(self.context, run) - muon_workspace_wrapper = MuonWorkspaceWrapper(directory + base_name) - muon_workspace_wrapper.show() - self.context.phase_context.add_phase_table(muon_workspace_wrapper) - - def add_fitting_info_to_ADS_if_required(self, base_name, fit_workspace_name): + def add_fitting_info_to_ADS_if_required(self, fit_workspace_name): if not self.view.output_fit_information: return - muon_workspace_wrapper = MuonWorkspaceWrapper(fit_workspace_name) - muon_workspace_wrapper.show() - - def create_parameters_for_cal_muon_phase_algorithm(self): - parameters = {} - parameters["FirstGoodData"] = self.context.phase_context.options_dict["first_good_time"] - parameters["LastGoodData"] = self.context.phase_context.options_dict["last_good_time"] - parameters["InputWorkspace"] = self.context.phase_context.options_dict["input_workspace"] - forward_group = self.context.phase_context.options_dict["forward_group"] - parameters["ForwardSpectra"] = self.context.group_pair_context[forward_group].detectors - backward_group = self.context.phase_context.options_dict["backward_group"] - parameters["BackwardSpectra"] = self.context.group_pair_context[backward_group].detectors - parameters["DetectorTable"] = get_phase_table_workspace_name( - parameters["InputWorkspace"], forward_group, backward_group, new_name=self._new_table_name - ) - return parameters + self.model.add_fitting_info_to_ads(fit_workspace_name) def update_current_phase_tables(self): """Retrieves up-to-date list of phase tables from context and adds to view""" - phase_table_list = self.context.phase_context.get_phase_table_list(self.context.data_context.instrument) + phase_table_list = self.model.phase_context.get_phase_table_list(self.model.instrument) self.view.set_phase_table_combo_box(phase_table_list) def calculate_phase_table(self): """Runs the algorithm to create a phase table""" - parameters = self.create_parameters_for_cal_muon_phase_algorithm() + parameters = self.model.create_parameters_for_cal_muon_phase_algorithm() fitting_workspace_name = ( get_fitting_workspace_name(parameters["DetectorTable"]) if self.view.output_fit_information else "__NotUsed" ) - self.current_alg = mantid.AlgorithmManager.create("CalMuonDetectorPhases") - detector_table, fitting_information = run_CalMuonDetectorPhases(parameters, self.current_alg, fitting_workspace_name) - self.current_alg = None + detector_table, fitting_information = self.model.CalMuonDetectorPhases_wrapper(parameters, fitting_workspace_name) - self.add_phase_table_to_ADS(detector_table) - self.add_fitting_info_to_ADS_if_required(parameters["DetectorTable"], fitting_information) + self.model.add_phase_table_to_ads(detector_table) + self.add_fitting_info_to_ADS_if_required(fitting_information) return parameters["DetectorTable"] @@ -196,7 +156,7 @@ def handle_phase_table_calculation_success(self): self.phase_table_calculation_complete_notifier.notify_subscribers() self.update_current_phase_tables() self.view.disable_phase_table_cancel() - self._new_table_name = "" + self.model.new_table_name = "" self.handle_thread_calculation_success() def handle_phase_table_calculation_error(self, error): @@ -204,10 +164,6 @@ def handle_phase_table_calculation_error(self, error): self.view.disable_phase_table_cancel() self.handle_thread_calculation_error(error) - def create_phase_table_calculation_thread(self): - self._calculation_model = ThreadModelWrapper(self.calculate_phase_table) - return thread_model.ThreadModel(self._calculation_model) - def handle_calculate_phase_table_clicked(self): self.update_model_from_view() self.disable_editing_notifier.notify_subscribers() @@ -217,30 +173,28 @@ def handle_calculate_phase_table_clicked(self): if name is None: self.enable_editing_notifier.notify_subscribers() return - self._new_table_name = name + self.model.new_table_name = name - # Calculate the new table in a separate thread - self.calculation_thread = self.create_phase_table_calculation_thread() - self.calculation_thread.threadWrapperSetUp( + self.model.calculate_phase_table_on_thread( + self.calculate_phase_table, self.handle_phase_table_calculation_started, self.handle_phase_table_calculation_success, self.handle_phase_table_calculation_error, ) - self.calculation_thread.start() def handle_phase_table_changed(self): """Handles when phase table is changed, recalculates any existing phasequads""" self.disable_editing_notifier.notify_subscribers() self.view.disable_widget() - current_table = self.context.phase_context.options_dict["phase_table_for_phase_quad"] + current_table = self.model.phase_context.options_dict["phase_table_for_phase_quad"] new_table = self.view.get_phase_table() if new_table == current_table: return - self.context.phase_context.options_dict["phase_table_for_phase_quad"] = new_table + self.model.phase_context.options_dict["phase_table_for_phase_quad"] = new_table # Update the table stored in each phasequad - self.context.group_pair_context.update_phase_tables(new_table) - self.context.update_phasequads() # Updates phasequads + self.model.group_pair_context.update_phase_tables(new_table) + self.model.context.update_phasequads() # Updates phasequads self.calculation_finished_notifier.notify_subscribers() self.view.enable_widget() self.enable_editing_notifier.notify_subscribers() @@ -248,29 +202,29 @@ def handle_phase_table_changed(self): """=============== Phasequad methods ===============""" def calculate_phasequad(self): - self.context.group_pair_context.add_phasequad(self._phasequad_obj) - self.context.calculate_phasequads(self._phasequad_obj) + self.model.group_pair_context.add_phasequad(self.model.phasequad) + self.model.context.calculate_phasequads(self.model.phasequad) - self.phasequad_calculation_complete_notifier.notify_subscribers(self._phasequad_obj.Re.name) - self.phasequad_calculation_complete_notifier.notify_subscribers(self._phasequad_obj.Im.name) + self.phasequad_calculation_complete_notifier.notify_subscribers(self.model.phasequad.Re.name) + self.phasequad_calculation_complete_notifier.notify_subscribers(self.model.phasequad.Im.name) def remove_last_row(self): if self.view.num_rows() > 0: name = self.view.get_table_contents()[-1] self.view.remove_last_row() - for phasequad in self.context.group_pair_context.phasequads: + for phasequad in self.model.group_phasequads: if phasequad.name == name: self.add_phasequad_to_analysis(False, False, phasequad) - self.context.group_pair_context.remove_phasequad(phasequad) + self.model.remove_phasequad(phasequad) self.calculation_finished_notifier.notify_subscribers() def remove_selected_rows(self, phasequad_names): for name, index in reversed(phasequad_names): self.view.remove_phasequad_by_index(index) - for phasequad in self.context.group_pair_context.phasequads: + for phasequad in self.model.group_phasequads: if phasequad.name == name: self.add_phasequad_to_analysis(False, False, phasequad) - self.context.group_pair_context.remove_phasequad(phasequad) + self.model.remove_phasequad(phasequad) self.calculation_finished_notifier.notify_subscribers() def handle_phasequad_calculation_started(self): @@ -279,11 +233,11 @@ def handle_phasequad_calculation_started(self): def handle_phasequad_calculation_success(self): """Specific handling when a phasequad calculation succeeds""" - self.add_phasequad_to_analysis(True, True, self._phasequad_obj) + self.add_phasequad_to_analysis(True, True, self.model.phasequad) self.view.disable_updates() - self.view.add_phasequad_to_table(self._phasequad_obj.name) + self.view.add_phasequad_to_table(self.model.phasequad.name) self.view.enable_updates() - self._phasequad_obj = None + self.model.phasequad = None self.calculation_finished_notifier.notify_subscribers() self.handle_thread_calculation_success() @@ -291,10 +245,6 @@ def handle_phasequad_calculation_error(self, error): """Specific handling when calculate phase table errors""" self.handle_thread_calculation_error(error) - def create_phasequad_calculation_thread(self): - self._phasequad_calculation_model = ThreadModelWrapper(self.calculate_phasequad) - return thread_model.ThreadModel(self._phasequad_calculation_model) - def handle_add_phasequad_button_clicked(self): """When the + button is pressed, calculate a new phasequad from the currently selected table""" if self.view.number_of_phase_tables < 1: @@ -308,15 +258,13 @@ def handle_add_phasequad_button_clicked(self): return elif self.validate_phasequad_name(name): - table = self.context.phase_context.options_dict["phase_table_for_phase_quad"] - self._phasequad_obj = MuonPhasequad(str(name), table) - self.phasequad_calculation_thread = self.create_phasequad_calculation_thread() - self.phasequad_calculation_thread.threadWrapperSetUp( + self.model.calculate_phasequad_on_thread( + name, + self.calculate_phasequad, self.handle_phasequad_calculation_started, self.handle_phasequad_calculation_success, self.handle_phasequad_calculation_error, ) - self.phasequad_calculation_thread.start() def handle_remove_phasequad_button_clicked(self): phasequads = self.view.get_selected_phasequad_names_and_indexes() @@ -330,7 +278,7 @@ def handle_phasequad_table_data_changed(self, row, col): item = self.view.get_table_item(row, col) name = self.view.get_table_item_text(row, 0) is_added = True if item.checkState() == 2 else False - for phasequad in self.context.group_pair_context.phasequads: + for phasequad in self.model.group_phasequads: if phasequad.name == name: if col == REAL_PART: self.add_part_to_analysis(is_added, phasequad.Re.name) @@ -345,9 +293,9 @@ def add_phasequad_to_analysis(self, re_is_added, im_is_added, phasequad): def add_part_to_analysis(self, is_added, name, notify=True): """Adds the Real (Re) or Imaginary (Im) part to the analysis if is_added is True, else removes it""" if is_added: - self.context.group_pair_context.add_pair_to_selected_pairs(name) + self.model.group_pair_context.add_pair_to_selected_pairs(name) else: - self.context.group_pair_context.remove_pair_from_selected_pairs(name) + self.model.group_pair_context.remove_pair_from_selected_pairs(name) # Notify a new phasequad is selected to update the plot if notify: diff --git a/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_widget.py b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_widget.py index 26789ce42e2c..cae016f62b88 100644 --- a/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_widget.py +++ b/qt/python/mantidqtinterfaces/mantidqtinterfaces/Muon/GUI/Common/phase_table_widget/phase_table_widget.py @@ -5,13 +5,15 @@ # Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS # SPDX - License - Identifier: GPL - 3.0 + from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_view import PhaseTableView +from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model import PhaseTableModel from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter import PhaseTablePresenter class PhaseTabWidget(object): def __init__(self, context, parent): self.phase_table_view = PhaseTableView(parent) - self.phase_table_presenter = PhaseTablePresenter(self.phase_table_view, context) + self.phase_table_model = PhaseTableModel(context) + self.phase_table_presenter = PhaseTablePresenter(self.phase_table_view, self.phase_table_model) # Phase table actions self.phase_table_view.set_calculate_phase_table_action(self.phase_table_presenter.handle_calculate_phase_table_clicked) diff --git a/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_model_test.py b/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_model_test.py new file mode 100644 index 000000000000..94840307a2d0 --- /dev/null +++ b/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_model_test.py @@ -0,0 +1,51 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source, +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + +import unittest +from unittest import mock + +from mantidqtinterfaces.Muon.GUI.Common.test_helpers.context_setup import setup_context +from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model import PhaseTableModel +from mantidqtinterfaces.Muon.GUI.Common.muon_group import MuonGroup + + +class PhaseTableModelTest(unittest.TestCase): + def setUp(self): + context = setup_context() + self.model = PhaseTableModel(context) + + forward_group = MuonGroup(group_name="fwd", detector_ids=[1, 3, 5, 7, 9]) + backward_group = MuonGroup(group_name="bwd", detector_ids=[2, 4, 6, 8, 10]) + self.model.group_pair_context.add_group(forward_group) + self.model.group_pair_context.add_group(backward_group) + + def test_create_parameters_for_cal_muon_phase_returns_correct_parameter_dict(self): + workspace_name = "input_workspace_name_raw_data" + self.model.phase_context.options_dict["input_workspace"] = workspace_name + + result = self.model.create_parameters_for_cal_muon_phase_algorithm() + + self.assertEqual( + result, + { + "BackwardSpectra": [2, 4, 6, 8, 10], + "FirstGoodData": 0.1, + "ForwardSpectra": [1, 3, 5, 7, 9], + "InputWorkspace": workspace_name, + "LastGoodData": 15, + "DetectorTable": "input_workspace_name; PhaseTable; fwd; bwd", + }, + ) + + @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model.MuonWorkspaceWrapper") + def test_that_phase_table_added_to_ADS_with_correct_name_and_group(self, mock_workspace_wrapper): + workspace_wrapper = mock.MagicMock() + mock_workspace_wrapper.return_value = workspace_wrapper + + self.model.add_phase_table_to_ads("MUSR22222_period_1; PhaseTable") + + mock_workspace_wrapper.assert_called_once_with("MUSR22222 MA/MUSR22222_period_1; PhaseTable") + workspace_wrapper.show.assert_called_once_with() diff --git a/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_presenter_test.py b/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_presenter_test.py index fbf53631c999..13567c23a8a6 100644 --- a/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_presenter_test.py +++ b/qt/python/mantidqtinterfaces/test/Muon/phase_table_widget/phase_table_presenter_test.py @@ -14,6 +14,7 @@ from qtpy import QtCore from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter import PhaseTablePresenter from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_view import PhaseTableView +from mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model import PhaseTableModel from mantidqtinterfaces.Muon.GUI.Common.muon_group import MuonGroup from mantidqtinterfaces.Muon.GUI.Common.muon_phasequad import MuonPhasequad from mantidqtinterfaces.Muon.GUI.Common.test_helpers.context_setup import setup_context @@ -25,7 +26,8 @@ def phase_table_name_side_effect(): @start_qapplication class PhaseTablePresenterTest(unittest.TestCase): - def wait_for_thread(self, thread_model): + @staticmethod + def wait_for_thread(thread_model): if thread_model and thread_model.worker: while thread_model.worker.is_alive(): time.sleep(0.1) @@ -33,16 +35,17 @@ def wait_for_thread(self, thread_model): def setUp(self): self.view = PhaseTableView() - self.context = setup_context() - self.context.data_context.instrument = "MUSR" + context = setup_context() + context.data_context.instrument = "MUSR" + self.model = PhaseTableModel(context) - self.presenter = PhaseTablePresenter(self.view, self.context) + self.presenter = PhaseTablePresenter(self.view, self.model) forward_group = MuonGroup(group_name="fwd", detector_ids=[1, 3, 5, 7, 9]) backward_group = MuonGroup(group_name="bwd", detector_ids=[2, 4, 6, 8, 10]) - self.context.group_pair_context.add_group(forward_group) - self.context.group_pair_context.add_group(backward_group) + self.model.group_pair_context.add_group(forward_group) + self.model.group_pair_context.add_group(backward_group) self.presenter.update_current_groups_list() self.view.warning_popup = mock.MagicMock() @@ -52,7 +55,7 @@ def setUp(self): def test_update_view_from_model_updates_view_to_have_correct_values(self): self.presenter.update_view_from_model() - for key, item in self.context.phase_context.options_dict.items(): + for key, item in self.model.phase_context.options_dict.items(): self.assertEqual(getattr(self.view, key), item) def test_update_model_from_view_updates_model_to_have_correct_values_if_view_changed(self): @@ -62,29 +65,11 @@ def test_update_model_from_view_updates_model_to_have_correct_values_if_view_cha self.presenter.update_model_from_view() - self.assertEqual(self.context.phase_context.options_dict["input_workspace"], workspace_name) - - def test_create_parameters_for_cal_muon_phase_returns_correct_parameter_dict(self): - workspace_name = "input_workspace_name_raw_data" - self.context.phase_context.options_dict["input_workspace"] = workspace_name - - result = self.presenter.create_parameters_for_cal_muon_phase_algorithm() - - self.assertEqual( - result, - { - "BackwardSpectra": [2, 4, 6, 8, 10], - "FirstGoodData": 0.1, - "ForwardSpectra": [1, 3, 5, 7, 9], - "InputWorkspace": workspace_name, - "LastGoodData": 15, - "DetectorTable": "input_workspace_name; PhaseTable; fwd; bwd", - }, - ) + self.assertEqual(self.model.phase_context.options_dict["input_workspace"], workspace_name) def test_correctly_retrieves_workspace_names_associsated_to_current_runs(self): self.view.set_input_combo_box = mock.MagicMock() - self.context.getGroupedWorkspaceNames = mock.MagicMock(return_value=["MUSR22222", "MUSR44444"]) + self.model.get_grouped_workspace_names = mock.MagicMock(return_value=["MUSR22222", "MUSR44444"]) self.presenter.update_current_run_list() @@ -97,46 +82,36 @@ def test_correctly_retrieves_names_of_current_groups(self): self.view.set_group_combo_boxes.assert_called_once_with(["fwd", "bwd"]) - @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter.MuonWorkspaceWrapper") - def test_that_phase_table_added_to_ADS_with_correct_name_and_group(self, mock_workspace_wrapper): - workspace_wrapper = mock.MagicMock() - mock_workspace_wrapper.return_value = workspace_wrapper - - self.presenter.add_phase_table_to_ADS("MUSR22222_period_1; PhaseTable") - - mock_workspace_wrapper.assert_called_once_with("MUSR22222 MA/MUSR22222_period_1; PhaseTable") - workspace_wrapper.show.assert_called_once_with() - - @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter.run_CalMuonDetectorPhases") + @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model.run_CalMuonDetectorPhases") def test_handle_calculate_phase_table_clicked_behaves_correctly_for_succesful_calculation(self, run_algorithm_mock): detector_table_mock = mock.MagicMock() self.view.set_input_combo_box(["MUSR22222_raw_data_period_1"]) - self.context.getGroupedWorkspaceNames = mock.MagicMock(return_value=["MUSR22222_raw_data_period_1"]) - self.context.phase_context.options_dict["input_workspace"] = "MUSR22222_raw_data_period_1" + self.model.get_grouped_workspace_names = mock.MagicMock(return_value=["MUSR22222_raw_data_period_1"]) + self.model.phase_context.options_dict["input_workspace"] = "MUSR22222_raw_data_period_1" self.presenter.update_view_from_model() run_algorithm_mock.return_value = (detector_table_mock, mock.MagicMock()) - self.presenter.add_phase_table_to_ADS = mock.MagicMock() + self.model.add_phase_table_to_ads = mock.MagicMock() self.presenter.update_current_run_list() self.presenter.handle_calculate_phase_table_clicked() - self.wait_for_thread(self.presenter.calculation_thread) + self.wait_for_thread(self.model.calculation_thread) - self.presenter.add_phase_table_to_ADS.assert_called_once_with(detector_table_mock) + self.model.add_phase_table_to_ads.assert_called_once_with(detector_table_mock) self.assertTrue(self.view.isEnabled()) - @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter.run_CalMuonDetectorPhases") + @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model.run_CalMuonDetectorPhases") def test_handle_calculate_phase_table_clicked_behaves_correctly_for_error_in_calculation(self, run_algorithm_mock): - self.context.getGroupedWorkspaceNames = mock.MagicMock(return_value=["MUSR22222_raw_data_period_1"]) - self.context.phase_context.options_dict["input_workspace"] = "MUSR22222_raw_data_period_1" + self.model.get_grouped_workspace_names = mock.MagicMock(return_value=["MUSR22222_raw_data_period_1"]) + self.model.phase_context.options_dict["input_workspace"] = "MUSR22222_raw_data_period_1" self.presenter.update_view_from_model() runtime_error = RuntimeError("CalMuonDetectorPhases has failed") run_algorithm_mock.side_effect = runtime_error - self.presenter.add_phase_table_to_ADS = mock.MagicMock() + self.model.add_phase_table_to_ads = mock.MagicMock() self.presenter.calculate_base_name_and_group = mock.MagicMock(return_value=("MUSR22222_raw_data_period_1", "MUSR22222 PhaseTable")) self.presenter.update_current_run_list() self.presenter.handle_calculate_phase_table_clicked() - self.wait_for_thread(self.presenter.calculation_thread) + self.wait_for_thread(self.model.calculation_thread) self.assertTrue(self.view.isEnabled()) self.view.warning_popup.assert_called_once_with(runtime_error) @@ -145,7 +120,7 @@ def test_update_current_phase_table_list_retrieves_all_correct_tables(self): self.view.set_phase_table_combo_box = mock.MagicMock() workspace_wrapper = mock.MagicMock() workspace_wrapper.workspace_name = "MUSR22222_phase_table" - self.context.phase_context.add_phase_table(workspace_wrapper) + self.model.phase_context.add_phase_table(workspace_wrapper) self.presenter.update_current_phase_tables() @@ -158,24 +133,24 @@ def test_handle_calculation_started_and_handle_calculation_ended_called_correctl self.presenter.calculate_phase_table = mock.MagicMock() self.presenter.handle_calculate_phase_table_clicked() - self.wait_for_thread(self.presenter.calculation_thread) + self.wait_for_thread(self.model.calculation_thread) self.presenter.handle_phase_table_calculation_started.assert_called_once_with() self.presenter.handle_phase_table_calculation_success.assert_called_once_with() self.presenter.handle_phase_table_calculation_error.assert_not_called() self.presenter.calculate_phase_table.assert_called_once_with() - @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter.MuonWorkspaceWrapper") + @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model.MuonWorkspaceWrapper") def test_add_fitting_info_to_ADS_does_nothing_if_output_fitting_info_is_false(self, workspace_wrapper_mock): - self.presenter.add_fitting_info_to_ADS_if_required("MUSR22222_PhaseTable", mock.MagicMock()) + self.presenter.add_fitting_info_to_ADS_if_required(mock.MagicMock()) workspace_wrapper_mock.assert_not_called() - @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_presenter.MuonWorkspaceWrapper") + @mock.patch("mantidqtinterfaces.Muon.GUI.Common.phase_table_widget.phase_table_model.MuonWorkspaceWrapper") def test_add_fitting_info_to_ADS_adds_fitting_info_to_ADS_if_option_selected(self, workspace_wrapper_mock): self.view.output_fit_info_box.setCheckState(QtCore.Qt.Checked) - self.presenter.add_fitting_info_to_ADS_if_required("MUSR22222_PhaseTable", "MUSR22222_PhaseTable; fit_information") + self.presenter.add_fitting_info_to_ADS_if_required("MUSR22222_PhaseTable; fit_information") workspace_wrapper_mock.assert_called_once_with("MUSR22222_PhaseTable; fit_information") workspace_wrapper_mock.return_value.show.assert_called_once_with() @@ -232,69 +207,69 @@ def test_handle_add_phasequad_button(self): self.view.set_phase_table_combo_box(["Table"]) self.presenter.validate_phasequad_name = mock.Mock(return_value=True) self.view.enter_phasequad_name = mock.Mock(return_value="test") - self.presenter.create_phasequad_calculation_thread = mock.MagicMock() + self.model.calculate_phasequad_on_thread = mock.MagicMock() self.presenter.handle_add_phasequad_button_clicked() - self.assertEqual(self.presenter.create_phasequad_calculation_thread.call_count, 1) + self.assertEqual(self.model.calculate_phasequad_on_thread.call_count, 1) def test_phasequad_success(self): - self.context.group_pair_context.add_pair_to_selected_pairs = mock.Mock() + self.model.group_pair_context.add_pair_to_selected_pairs = mock.Mock() self.presenter.selected_phasequad_changed_notifier = mock.Mock() self.presenter.calculation_finished_notifier = mock.Mock() self.presenter.handle_thread_calculation_success = mock.Mock() - self.context.group_pair_context.add_pair_to_selected_pairs = mock.Mock() + self.model.group_pair_context.add_pair_to_selected_pairs = mock.Mock() self.presenter.selected_phasequad_changed_notifier.notify_subscribers = mock.Mock() - self.presenter._phasequad_obj = MuonPhasequad("test", "table") + self.model.phasequad = MuonPhasequad("test", "table") self.presenter.handle_phasequad_calculation_success() - self.context.group_pair_context.add_pair_to_selected_pairs.assert_any_call("test_Re_") - self.context.group_pair_context.add_pair_to_selected_pairs.assert_any_call("test_Im_") + self.model.group_pair_context.add_pair_to_selected_pairs.assert_any_call("test_Re_") + self.model.group_pair_context.add_pair_to_selected_pairs.assert_any_call("test_Im_") self.presenter.selected_phasequad_changed_notifier.notify_subscribers.assert_not_called() self.presenter.calculation_finished_notifier.notify_subscribers.assert_called_once_with() self.presenter.handle_thread_calculation_success.assert_called_once_with() def test_handle_first_good_data_too_small(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.first_good_time = 0.0 - self.context.first_good_data = mock.Mock(return_value=0.102) + self.model.context.first_good_data = mock.Mock(return_value=0.102) self.presenter.handle_first_good_data_changed() self.view.warning_popup.assert_called_once_with("First Good Data cannot be smaller than 0.102") def test_handle_first_good_data_too_big(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.first_good_time = 40.0 self.view.last_good_time = 41.0 - self.context.last_good_data = mock.Mock(return_value=32.29) + self.model.context.last_good_data = mock.Mock(return_value=32.29) self.presenter.handle_first_good_data_changed() self.view.warning_popup.assert_called_once_with("First Good Data cannot be greater than 32.29") def test_handle_last_good_data_too_small(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.first_good_time = -1.0 self.view.last_good_time = 0.0 - self.context.first_good_data = mock.Mock(return_value=0.102) + self.model.context.first_good_data = mock.Mock(return_value=0.102) self.presenter.handle_last_good_data_changed() self.view.warning_popup.assert_called_once_with("Last Good Data cannot be smaller than 0.102") def test_handle_last_good_data_too_big(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.last_good_time = 41.0 - self.context.last_good_data = mock.Mock(return_value=32.29) + self.model.context.last_good_data = mock.Mock(return_value=32.29) self.presenter.handle_last_good_data_changed() self.view.warning_popup.assert_called_once_with("Last Good Data cannot be greater than 32.29") def test_handle_first_good_greater_than_last_good(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.first_good_time = 20.0 self.view.last_good_time = 10.0 self.presenter.handle_first_good_data_changed() @@ -303,7 +278,7 @@ def test_handle_first_good_greater_than_last_good(self): def test_handle_last_good_less_than_first_good(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" + self.model.instrument = "MUSR" self.view.first_good_time = 20.0 self.view.last_good_time = 10.0 self.presenter.handle_last_good_data_changed() @@ -312,9 +287,9 @@ def test_handle_last_good_less_than_first_good(self): def test_handle_first_good_and_last_good_pass_validation(self): self.view.input_workspace_combo_box.currentText = mock.Mock(return_value="MUSR62260_raw_data MA") - self.context.data_context.instrument = "MUSR" - self.context.first_good_data = mock.Mock(return_value=0.102) - self.context.last_good_data = mock.Mock(return_value=32.29) + self.model.instrument = "MUSR" + self.model.context.first_good_data = mock.Mock(return_value=0.102) + self.model.context.last_good_data = mock.Mock(return_value=32.29) self.view.first_good_time = 10.0 self.view.last_good_time = 20.0 self.presenter.handle_first_good_data_changed() @@ -331,10 +306,10 @@ def test_remove_phasequad_button_last_row(self): self.view.remove_last_row = mock.Mock() self.presenter.add_phasequad_to_analysis = mock.Mock() self.presenter.calculation_finished_notifier = mock.Mock() - self.presenter.context.group_pair_context._phasequad = [phasequad] + self.presenter.model.group_pair_context._phasequad = [phasequad] self.presenter.handle_remove_phasequad_button_clicked() - self.assertEqual(0, len(self.presenter.context.group_pair_context.phasequads)) + self.assertEqual(0, len(self.presenter.model.group_phasequads)) self.presenter.add_phasequad_to_analysis.assert_called_once_with(False, False, phasequad) self.presenter.calculation_finished_notifier.notify_subscribers.assert_called_once_with() @@ -348,10 +323,10 @@ def test_remove_phasequad_button_selected_rows(self): self.view.remove_phasequad_by_index = mock.Mock() self.presenter.add_phasequad_to_analysis = mock.Mock() self.presenter.calculation_finished_notifier = mock.Mock() - self.presenter.context.group_pair_context._phasequad = [phasequad_1, phasequad_2, phasequad_3] + self.presenter.model.group_pair_context._phasequad = [phasequad_1, phasequad_2, phasequad_3] self.presenter.handle_remove_phasequad_button_clicked() - self.assertEqual(1, len(self.presenter.context.group_pair_context.phasequads)) + self.assertEqual(1, len(self.presenter.model.group_phasequads)) self.view.remove_phasequad_by_index.assert_any_call(0) self.view.remove_phasequad_by_index.assert_any_call(1) self.presenter.add_phasequad_to_analysis.assert_any_call(False, False, phasequad_1) @@ -361,29 +336,29 @@ def test_remove_phasequad_button_selected_rows(self): def test_calculate_phasequad(self): self.presenter.phasequad_calculation_complete_notifier = mock.Mock() self.presenter.phasequad_calculation_complete_notifier.notify_subscribers = mock.Mock() - self.context.group_pair_context.add_phasequad = mock.Mock() - self.context.calculate_phasequads = mock.Mock() + self.model.group_pair_context.add_phasequad = mock.Mock() + self.model.context.calculate_phasequads = mock.Mock() phasequad = MuonPhasequad("test", "table") - self.presenter._phasequad_obj = phasequad + self.model.phasequad = phasequad self.presenter.calculate_phasequad() - self.context.group_pair_context.add_phasequad.assert_called_once_with(phasequad) - self.context.calculate_phasequads.assert_called_once_with(phasequad) + self.model.group_pair_context.add_phasequad.assert_called_once_with(phasequad) + self.model.context.calculate_phasequads.assert_called_once_with(phasequad) self.presenter.phasequad_calculation_complete_notifier.notify_subscribers.assert_any_call(phasequad.Re.name) self.presenter.phasequad_calculation_complete_notifier.notify_subscribers.assert_any_call(phasequad.Im.name) self.assertEqual(self.presenter.phasequad_calculation_complete_notifier.notify_subscribers.call_count, 2) def test_handle_phase_table_changed_to_new_table(self): - self.context.phase_context.options_dict["phase_table_for_phase_quad"] = "MUSR22222_raw_data_period_1" + self.model.phase_context.options_dict["phase_table_for_phase_quad"] = "MUSR22222_raw_data_period_1" self.view.get_phase_table = mock.Mock(return_value="MUSR33333_raw_data_period_2") - self.context.group_pair_context.update_phase_tables = mock.Mock() - self.context.update_phasequads = mock.Mock() + self.model.group_pair_context.update_phase_tables = mock.Mock() + self.model.context.update_phasequads = mock.Mock() self.presenter.handle_phase_table_changed() - self.assertEqual(self.context.phase_context.options_dict["phase_table_for_phase_quad"], "MUSR33333_raw_data_period_2") - self.context.group_pair_context.update_phase_tables.assert_called_once_with("MUSR33333_raw_data_period_2") - self.assertEqual(1, self.context.update_phasequads.call_count) + self.assertEqual(self.model.phase_context.options_dict["phase_table_for_phase_quad"], "MUSR33333_raw_data_period_2") + self.model.group_pair_context.update_phase_tables.assert_called_once_with("MUSR33333_raw_data_period_2") + self.assertEqual(1, self.model.context.update_phasequads.call_count) if __name__ == "__main__":