Exemple #1
0
    def __init__(self, view, model):
        self._view = view
        self._model = model

        self.load_run_widget = None
        self.load_file_widget = None

        self.view.on_subwidget_loading_started(self.disable_loading)
        self.view.on_subwidget_loading_finished(self.enable_loading)

        self.view.on_file_widget_data_changed(
            self.handle_file_widget_data_changed)
        self.view.on_run_widget_data_changed(
            self.handle_run_widget_data_changed)

        self.view.on_clear_button_clicked(self.clear_data_and_view)

        self._view.on_multiple_load_type_changed(
            self.handle_multiple_runs_option_changed)
        self._use_threading = True

        self.loadNotifier = Observable()

        self.disable_observer = GenericObserver(self.disable_loading)
        self.enable_observer = GenericObserver(self.enable_loading)

        self.update_view_from_model_observer = GenericObserver(self.update_view_from_model)
    def __init__(self,
                 muon_data_context=None,
                 muon_gui_context=None,
                 muon_group_context=None,
                 base_directory='Muon Data',
                 muon_phase_context=None,
                 workspace_suffix=' MA',
                 fitting_context=None,
                 frequency_context=None):

        self._data_context = muon_data_context
        self._gui_context = muon_gui_context
        self._group_pair_context = muon_group_context
        self._phase_context = muon_phase_context
        self.fitting_context = fitting_context
        self.base_directory = base_directory
        self.workspace_suffix = workspace_suffix
        self._frequency_context = frequency_context

        self.ads_observer = MuonContextADSObserver(
            self.remove_workspace_by_name, self.clear_context)

        self.gui_context.update({
            'DeadTimeSource': 'None',
            'LastGoodDataFromFile': True,
            'selected_group_pair': ''
        })

        self.update_view_from_model_notifier = Observable()
Exemple #3
0
    def __init__(self):
        # A dictionary containing the fit histories for different fitting modes. The key should represent a fitting
        # mode, and the value should be a list of FitInformation's indicating all the fits that have been done up to
        # this point
        self._fit_history: dict = {}

        self.new_fit_results_notifier = Observable()
        self.fit_removed_notifier = Observable()
Exemple #4
0
 def __init__(self, fit_list=None):
     self.fit_list = fit_list if fit_list is not None else []
     # Register callbacks with this object to observe when new fits
     # are added
     self.new_fit_notifier = Observable()
     self.plot_guess_notifier = Observable()
     self._number_of_fits = 0
     self._number_of_fits_cache = 0
     self._plot_guess = False
     self._guess = None
    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.current_alg = None
        self._phasequad_obj = None
        self._new_table_name = ""

        self.group_change_observer = GenericObserver(
            self.update_current_groups_list)
        self.run_change_observer = GenericObserver(
            self.update_current_run_list)
        self.instrument_changed_observer = GenericObserver(
            self.update_current_phase_tables)

        self.phase_table_calculation_complete_notifier = Observable()
        self.phase_table_observer = GenericObserver(
            self.update_current_phase_tables)
        self.phasequad_calculation_complete_notifier = Observable()
        self.enable_editing_notifier = Observable()
        self.disable_editing_notifier = Observable()

        self.disable_tab_observer = GenericObserver(self.view.disable_widget)
        self.enable_tab_observer = GenericObserver(self.view.enable_widget)
        self.selected_phasequad_changed_notifier = GenericObservable()
        self.calculation_finished_notifier = GenericObservable()

        self.update_view_from_model_observer = GenericObserver(
            self.update_view_from_model)
        self.update_current_phase_tables()

        self.view.on_first_good_data_changed(
            self.handle_first_good_data_changed)
        self.view.on_last_good_data_changed(self.handle_last_good_data_changed)
        self.view.on_phasequad_table_data_changed(
            self.handle_phasequad_table_data_changed)
    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.current_alg = None

        self.group_change_observer = GenericObserver(self.update_current_groups_list)
        self.run_change_observer = GenericObserver(self.update_current_run_list)
        self.instrument_changed_observer = GenericObserver(self.update_current_phase_tables)

        self.phase_table_calculation_complete_notifier = Observable()
        self.phase_quad_calculation_complete_nofifier = Observable()

        self.update_view_from_model_observer = GenericObserver(self.update_view_from_model)

        self.update_current_phase_tables()
Exemple #7
0
    def __init__(self, muon_data_context=None, muon_gui_context=None, muon_group_context=None, corrections_context=None,
                 base_directory='Muon Data', muon_phase_context=None, workspace_suffix=' MA', fitting_context=None,
                 results_context=None, model_fitting_context=None, plot_panes_context=None, frequency_context=None):
        self._data_context = muon_data_context
        self._gui_context = muon_gui_context
        self._group_pair_context = muon_group_context
        self._corrections_context = corrections_context
        self._phase_context = muon_phase_context
        self.fitting_context = fitting_context
        self.results_context = results_context
        self.model_fitting_context = model_fitting_context
        self.base_directory = base_directory
        self.workspace_suffix = workspace_suffix
        self._plot_panes_context = plot_panes_context
        self.ads_observer = MuonContextADSObserver(
            self.remove_workspace,
            self.clear_context,
            self.workspace_replaced)

        self.gui_context.update(
            {'LastGoodDataFromFile': True,
             'selected_group_pair': ''})

        self.update_view_from_model_notifier = Observable()
        self.update_plots_notifier = Observable()
        self.deleted_plots_notifier = Observable()
    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.thread = None
        self._optional_output_names = {}
        # set data
        self.getWorkspaceNames()
        self.view.set_methods([USEGROUPS, USEDETECTORS])
        # connect
        self.view.maxEntButtonSignal.connect(self.handleMaxEntButton)
        self.view.method_changed_slot(self.update_phase_table_options)
        self.view.method_changed_slot(self.notify_method_changed)
        self.view.period_changed_slot(self.notify_period_changed)
        self.view.cancelSignal.connect(self.cancel)
        self.view.run_changed_slot(self._load_periods)

        self.phase_table_observer = GenericObserver(
            self.update_phase_table_options)
        self.calculation_finished_notifier = GenericObservable()
        self.calculation_started_notifier = GenericObservable()
        self.new_phase_table = GenericObservable()
        self.update_phase_table_options()
        self.method_changed = Observable()
        self.period_changed = Observable()
        self.new_reconstructed_data = Observable()
Exemple #9
0
 def __init__(self, outer):
     Observable.__init__(self)
     self.outer = outer
Exemple #10
0
class MuonContext(object):
    def __init__(self,
                 muon_data_context=None,
                 muon_gui_context=None,
                 muon_group_context=None,
                 base_directory='Muon Data',
                 muon_phase_context=None,
                 workspace_suffix=' MA',
                 fitting_context=None,
                 frequency_context=None):

        self._data_context = muon_data_context
        self._gui_context = muon_gui_context
        self._group_pair_context = muon_group_context
        self._phase_context = muon_phase_context
        self.fitting_context = fitting_context
        self.base_directory = base_directory
        self.workspace_suffix = workspace_suffix
        self._frequency_context = frequency_context

        self.ads_observer = MuonContextADSObserver(
            self.remove_workspace_by_name, self.clear_context)

        self.gui_context.update({
            'DeadTimeSource': 'None',
            'LastGoodDataFromFile': True,
            'selected_group_pair': ''
        })

        self.update_view_from_model_notifier = Observable()

    def __del__(self):
        self.ads_observer.unsubscribe()
        self.ads_observer = None

    @property
    def window_title(self):
        if self._frequency_context:
            return self._frequency_context.window_title
        return "Muon Analysis"

    @property
    def data_context(self):
        return self._data_context

    @property
    def gui_context(self):
        return self._gui_context

    @property
    def group_pair_context(self):
        return self._group_pair_context

    @property
    def phase_context(self):
        return self._phase_context

    def calculate_group(self, group_name, run, rebin=False):
        run_as_string = run_list_to_string(run)
        name = get_group_data_workspace_name(self,
                                             group_name,
                                             run_as_string,
                                             rebin=rebin)
        asym_name = get_group_asymmetry_name(self,
                                             group_name,
                                             run_as_string,
                                             rebin=rebin)
        asym_name_unnorm = get_group_asymmetry_unnorm_name(self,
                                                           group_name,
                                                           run_as_string,
                                                           rebin=rebin)
        group_workspace = calculate_group_data(self, group_name, run, rebin,
                                               name)
        group_asymmetry, group_asymmetry_unnormalised = estimate_group_asymmetry_data(
            self, group_name, run, rebin, asym_name, asym_name_unnorm)

        return group_workspace, group_asymmetry, group_asymmetry_unnormalised

    def calculate_pair(self, pair_name, run, rebin=False):
        run_as_string = run_list_to_string(run)
        name = get_pair_data_workspace_name(self,
                                            pair_name,
                                            run_as_string,
                                            rebin=rebin)
        return calculate_pair_data(self, pair_name, run, rebin, name)

    def show_all_groups(self):
        self.calculate_all_groups()
        for run in self._data_context.current_runs:
            for group_name in self._group_pair_context.group_names:
                run_as_string = run_list_to_string(run)

                directory = get_base_data_directory(self, run_as_string)

                name = get_group_data_workspace_name(self,
                                                     group_name,
                                                     run_as_string,
                                                     rebin=False)
                asym_name = get_group_asymmetry_name(self,
                                                     group_name,
                                                     run_as_string,
                                                     rebin=False)
                asym_name_unnorm = get_group_asymmetry_unnorm_name(
                    self, group_name, run_as_string, rebin=False)

                self.group_pair_context[group_name].show_raw(
                    run, directory + name, directory + asym_name,
                    asym_name_unnorm)

                if self._do_rebin():
                    name = get_group_data_workspace_name(self,
                                                         group_name,
                                                         run_as_string,
                                                         rebin=True)
                    asym_name = get_group_asymmetry_name(self,
                                                         group_name,
                                                         run_as_string,
                                                         rebin=True)
                    asym_name_unnorm = get_group_asymmetry_unnorm_name(
                        self, group_name, run_as_string, rebin=True)

                    self.group_pair_context[group_name].show_rebin(
                        run, directory + name, directory + asym_name,
                        asym_name_unnorm)

    def show_all_pairs(self):
        self.calculate_all_pairs()
        for run in self._data_context.current_runs:
            for pair_name in self._group_pair_context.pair_names:
                run_as_string = run_list_to_string(run)
                name = get_pair_data_workspace_name(self,
                                                    pair_name,
                                                    run_as_string,
                                                    rebin=False)
                directory = get_base_data_directory(self, run_as_string)

                self.group_pair_context[pair_name].show_raw(
                    run, directory + name)

                if self._do_rebin():
                    name = get_pair_data_workspace_name(self,
                                                        pair_name,
                                                        run_as_string,
                                                        rebin=True)
                    self.group_pair_context[pair_name].show_rebin(
                        run, directory + name)

    def calculate_all_pairs(self):
        for run in self._data_context.current_runs:
            for pair_name in self._group_pair_context.pair_names:
                pair_asymmetry_workspace = self.calculate_pair(pair_name, run)
                self.group_pair_context[pair_name].update_asymmetry_workspace(
                    pair_asymmetry_workspace, run)

                if self._do_rebin():
                    pair_asymmetry_workspace = self.calculate_pair(pair_name,
                                                                   run,
                                                                   rebin=True)
                    self.group_pair_context[
                        pair_name].update_asymmetry_workspace(
                            pair_asymmetry_workspace, run, rebin=True)

    def calculate_all_groups(self):
        for run in self._data_context.current_runs:
            for group_name in self._group_pair_context.group_names:

                group_workspace, group_asymmetry, group_asymmetry_unormalised = self.calculate_group(
                    group_name, run)
                self.group_pair_context[group_name].update_workspaces(
                    run,
                    group_workspace,
                    group_asymmetry,
                    group_asymmetry_unormalised,
                    rebin=False)

                if self._do_rebin():
                    group_workspace, group_asymmetry, group_asymmetry_unormalised = self.calculate_group(
                        group_name, run, rebin=True)
                    self.group_pair_context[group_name].update_workspaces(
                        run,
                        group_workspace,
                        group_asymmetry,
                        group_asymmetry_unormalised,
                        rebin=True)

    def update_current_data(self):
        # Update the current data; resetting the groups and pairs to their
        # default values
        if len(self.data_context.current_runs) > 0:
            self.data_context.update_current_data()

            if not self.group_pair_context.groups:
                self.group_pair_context.reset_group_and_pairs_to_default(
                    self.data_context.current_workspace,
                    self.data_context.instrument,
                    self.data_context.main_field_direction)
        else:
            self.data_context.clear()

    def show_raw_data(self):
        self.ads_observer.observeRename(False)
        for run in self.data_context.current_runs:
            run_string = run_list_to_string(run)
            loaded_workspace = \
                self.data_context._loaded_data.get_data(run=run, instrument=self.data_context.instrument)['workspace'][
                    'OutputWorkspace']
            loaded_workspace_deadtime_table = self.data_context._loaded_data.get_data(
                run=run, instrument=self.data_context.instrument
            )['workspace']['DataDeadTimeTable']
            directory = get_base_data_directory(self, run_string)

            deadtime_name = get_deadtime_data_workspace_name(
                self.data_context.instrument,
                str(run[0]),
                workspace_suffix=self.workspace_suffix)
            MuonWorkspaceWrapper(loaded_workspace_deadtime_table).show(
                directory + deadtime_name)
            self.data_context._loaded_data.get_data(
                run=run, instrument=self.data_context.instrument
            )['workspace']['DataDeadTimeTable'] = deadtime_name

            if len(loaded_workspace) > 1:
                # Multi-period data
                for i, single_ws in enumerate(loaded_workspace):
                    name = directory + get_raw_data_workspace_name(
                        self.data_context.instrument,
                        run_string,
                        self.data_context.is_multi_period(),
                        period=str(i + 1),
                        workspace_suffix=self.workspace_suffix)
                    single_ws.show(name)
            else:
                # Single period data
                name = directory + get_raw_data_workspace_name(
                    self.data_context.instrument,
                    run_string,
                    self.data_context.is_multi_period(),
                    workspace_suffix=self.workspace_suffix)
                loaded_workspace[0].show(name)

        self.ads_observer.observeRename(True)

    def _do_rebin(self):
        return (self.gui_context['RebinType'] == 'Fixed' and
                'RebinFixed' in self.gui_context and self.gui_context['RebinFixed']) or \
               (self.gui_context['RebinType'] == 'Variable' and
                'RebinVariable' in self.gui_context and self.gui_context['RebinVariable'])

    def get_workspace_names_for_FFT_analysis(self, use_raw=True):
        workspace_options = self.get_names_of_workspaces_to_fit(
            runs='All',
            group_and_pair='All',
            phasequad=True,
            rebin=not use_raw)
        return workspace_options

    def get_detectors_excluded_from_default_grouping_tables(self):
        groups, _, _ = get_default_grouping(
            self.data_context.current_workspace, self.data_context.instrument,
            self.data_context.main_field_direction)
        detectors_in_group = []
        for group in groups:
            detectors_in_group += group.detectors
        detectors_in_group = set(detectors_in_group)

        return [
            det for det in range(1, self.data_context.num_detectors)
            if det not in detectors_in_group
        ]

    # Get the groups/pairs for active WS
    def getGroupedWorkspaceNames(self):
        run_numbers = self.data_context.current_runs
        runs = [
            wsName.get_raw_data_workspace_name(
                self.data_context.instrument,
                run_list_to_string(run_number),
                self.data_context.is_multi_period(),
                period=str(period + 1),
                workspace_suffix=self.workspace_suffix)
            for run_number in run_numbers
            for period in range(self.data_context.num_periods(run_number))
        ]
        return runs

    def first_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['FirstGoodDataFromFile']:
            return self.data_context.get_loaded_data_for_run(
                run)["FirstGoodData"]
        else:
            if 'FirstGoodData' in self.gui_context:
                return self.gui_context['FirstGoodData']
            else:
                self.gui_context[
                    'FirstGoodData'] = self.data_context.get_loaded_data_for_run(
                        run)["FirstGoodData"]
                return self.gui_context['FirstGoodData']

    def last_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['LastGoodDataFromFile']:
            return round(
                max(
                    self.data_context.get_loaded_data_for_run(run)
                    ["OutputWorkspace"][0].workspace.dataX(0)), 2)
        else:
            if 'LastGoodData' in self.gui_context:
                return self.gui_context['LastGoodData']
            else:
                self.gui_context['LastGoodData'] = round(
                    max(
                        self.data_context.get_loaded_data_for_run(run)
                        ["OutputWorkspace"][0].workspace.dataX(0)), 2)
                return self.gui_context['LastGoodData']

    def dead_time_table(self, run):
        if self.gui_context['DeadTimeSource'] == 'FromADS':
            return self.gui_context['DeadTimeTable']
        elif self.gui_context['DeadTimeSource'] == 'FromFile':
            return self.data_context.get_loaded_data_for_run(
                run)["DataDeadTimeTable"]
        elif self.gui_context['DeadTimeSource'] == 'None':
            return None

    def get_names_of_workspaces_to_fit(self,
                                       runs='',
                                       group_and_pair='',
                                       phasequad=False,
                                       rebin=False,
                                       freq="None"):
        if freq == "None":
            return self.get_names_of_time_domain_workspaces_to_fit(
                runs=runs,
                group_and_pair=group_and_pair,
                phasequad=phasequad,
                rebin=rebin)
        else:
            return self.get_names_of_frequency_domain_workspaces_to_fit(
                runs=runs,
                group_and_pair=group_and_pair,
                phasequad=phasequad,
                frequency_type=freq)

    def get_group_and_pair(self, group_and_pair):
        group = []
        pair = []
        if group_and_pair == 'All':
            group = self.group_pair_context.group_names
            pair = self.group_pair_context.pair_names
        else:
            group_pair_list = group_and_pair.replace(' ', '').split(',')
            group = [
                group for group in group_pair_list
                if group in self.group_pair_context.group_names
            ]
            pair = [
                pair for pair in group_pair_list
                if pair in self.group_pair_context.pair_names
            ]
        return group, pair

    def get_runs(self, runs):
        run_list = []
        if runs == 'All':
            run_list = self.data_context.current_runs
        else:
            run_list = [
                run_string_to_list(item)
                for item in runs.replace(' ', '').split(',')
            ]
            flat_list = []
            for sublist in run_list:
                flat_list += [[run] for run in sublist if len(sublist) > 1]
            run_list += flat_list
            run_list = [
                run for run in run_list
                if run in self.data_context.current_runs
            ]
        return run_list

    def get_names_of_time_domain_workspaces_to_fit(self,
                                                   runs='',
                                                   group_and_pair='',
                                                   phasequad=False,
                                                   rebin=False):
        group, pair = self.get_group_and_pair(group_and_pair)
        run_list = self.get_runs(runs)

        group_names = self.group_pair_context.get_group_workspace_names(
            run_list, group, rebin)
        pair_names = self.group_pair_context.get_pair_workspace_names(
            run_list, pair, rebin)

        phasequad_names = []
        if phasequad:
            for run in run_list:
                run_string = run_list_to_string(run)
                phasequad_names += self.phase_context.get_phase_quad(
                    self.data_context.instrument, run_string)
        return group_names + pair_names + phasequad_names

    def get_names_of_frequency_domain_workspaces_to_fit(
            self,
            runs='',
            group_and_pair='',
            phasequad=False,
            frequency_type="None"):
        if self._frequency_context is None:
            return []
        group, pair = self.get_group_and_pair(group_and_pair)
        run_list = self.get_runs(runs)
        names = self._frequency_context.get_frequency_workspace_names(
            run_list, group, pair, phasequad, frequency_type)
        return names

    def get_list_of_binned_or_unbinned_workspaces_from_equivalents(
            self, input_list):
        equivalent_list = []

        for item in input_list:
            if 'PhaseQuad' in item:
                equivalent_list.append(item)

            equivalent_group_pair = self.group_pair_context.get_equivalent_group_pair(
                item)
            if equivalent_group_pair:
                equivalent_list.append(equivalent_group_pair)

        return equivalent_list

    def remove_workspace_by_name(self, workspace_name):
        self.data_context.remove_workspace_by_name(workspace_name)
        self.group_pair_context.remove_workspace_by_name(workspace_name)
        self.phase_context.remove_workspace_by_name(workspace_name)
        self.fitting_context.remove_workspace_by_name(workspace_name)
        self.gui_context.remove_workspace_by_name(workspace_name)
        self.update_view_from_model_notifier.notify_subscribers(workspace_name)

    def clear_context(self):
        self.data_context.clear()
        self.group_pair_context.clear()
        self.phase_context.clear()
        self.fitting_context.clear()
        self.update_view_from_model_notifier.notify_subscribers()
Exemple #11
0
class MuonContext(object):
    def __init__(self,
                 muon_data_context=None,
                 muon_gui_context=None,
                 muon_group_context=None,
                 base_directory='Muon Data',
                 muon_phase_context=None,
                 workspace_suffix=' MA',
                 fitting_context=None,
                 frequency_context=None):
        self._data_context = muon_data_context
        self._gui_context = muon_gui_context
        self._group_pair_context = muon_group_context
        self._phase_context = muon_phase_context
        self.fitting_context = fitting_context
        self.base_directory = base_directory
        self.workspace_suffix = workspace_suffix

        self.ads_observer = MuonContextADSObserver(self.remove_workspace,
                                                   self.clear_context,
                                                   self.workspace_replaced)

        self.gui_context.update({
            'DeadTimeSource': 'None',
            'LastGoodDataFromFile': True,
            'selected_group_pair': '',
            'PlotMode': PlotMode.Data
        })

        self.update_view_from_model_notifier = Observable()
        self.update_plots_notifier = Observable()
        self.deleted_plots_notifier = Observable()

    @property
    def data_context(self):
        return self._data_context

    @property
    def gui_context(self):
        return self._gui_context

    @property
    def group_pair_context(self):
        return self._group_pair_context

    @property
    def phase_context(self):
        return self._phase_context

    @property
    def default_data_plot_range(self):
        return MUON_ANALYSIS_DEFAULT_X_RANGE

    def num_periods(self, run):
        return self._data_context.num_periods(run)

    @property
    def current_runs(self):
        return self._data_context.current_runs

    def calculate_group(self, group, run, rebin=False):
        run_as_string = run_list_to_string(run)
        periods_as_string = run_list_to_string(group.periods)

        # A user requirement is that processing can continue if a period is missing from some
        # of the runs. This filters out periods which are not in a given run.
        periods = [
            period for period in group.periods
            if period <= self.num_periods(run)
        ]

        # If not periods match return nothing here. The caller then needs to handle this gracefully.
        if not periods:
            return None, None, None

        name = get_group_data_workspace_name(self,
                                             group.name,
                                             run_as_string,
                                             periods_as_string,
                                             rebin=rebin)
        asym_name = get_group_asymmetry_name(self,
                                             group.name,
                                             run_as_string,
                                             periods_as_string,
                                             rebin=rebin)
        asym_name_unnorm = get_group_asymmetry_unnorm_name(self,
                                                           group.name,
                                                           run_as_string,
                                                           periods_as_string,
                                                           rebin=rebin)
        group_workspace = calculate_group_data(self, group, run, rebin, name,
                                               periods)
        group_asymmetry, group_asymmetry_unnormalised = estimate_group_asymmetry_data(
            self, group, run, rebin, asym_name, asym_name_unnorm, periods)

        return group_workspace, group_asymmetry, group_asymmetry_unnormalised

    def calculate_pair(self,
                       pair: MuonPair,
                       run: List[int],
                       rebin: bool = False):
        try:
            forward_group_workspace_name = self._group_pair_context[
                pair.forward_group].get_counts_workspace_for_run(run, rebin)
            backward_group_workspace_name = self._group_pair_context[
                pair.backward_group].get_counts_workspace_for_run(run, rebin)
        except KeyError:
            # A key error here means the requested workspace does not exist so return None
            return None

        run_as_string = run_list_to_string(run)
        output_workspace_name = get_pair_asymmetry_name(self,
                                                        pair.name,
                                                        run_as_string,
                                                        rebin=rebin)
        return calculate_pair_data(pair, forward_group_workspace_name,
                                   backward_group_workspace_name,
                                   output_workspace_name)

    def show_all_groups(self):
        self.calculate_all_groups()
        for run in self._data_context.current_runs:
            with WorkspaceGroupDefinition():
                for group in self._group_pair_context.groups:
                    run_as_string = run_list_to_string(run)
                    group_name = group.name
                    periods = run_list_to_string(group.periods)

                    directory = get_base_data_directory(self, run_as_string)

                    name = get_group_data_workspace_name(self,
                                                         group_name,
                                                         run_as_string,
                                                         periods,
                                                         rebin=False)
                    asym_name = get_group_asymmetry_name(self,
                                                         group_name,
                                                         run_as_string,
                                                         periods,
                                                         rebin=False)
                    asym_name_unnorm = get_group_asymmetry_unnorm_name(
                        self, group_name, run_as_string, periods, rebin=False)

                    self.group_pair_context[group_name].show_raw(
                        run, directory + name, directory + asym_name,
                        asym_name_unnorm)

                    if self._do_rebin():
                        name = get_group_data_workspace_name(self,
                                                             group_name,
                                                             run_as_string,
                                                             periods,
                                                             rebin=True)
                        asym_name = get_group_asymmetry_name(self,
                                                             group_name,
                                                             run_as_string,
                                                             periods,
                                                             rebin=True)
                        asym_name_unnorm = get_group_asymmetry_unnorm_name(
                            self,
                            group_name,
                            run_as_string,
                            periods,
                            rebin=True)

                        self.group_pair_context[group_name].show_rebin(
                            run, directory + name, directory + asym_name,
                            asym_name_unnorm)

    def show_all_pairs(self):
        self.calculate_all_pairs()
        for run in self._data_context.current_runs:
            with WorkspaceGroupDefinition():
                for pair_name in self._group_pair_context.pair_names:
                    run_as_string = run_list_to_string(run)
                    name = get_pair_asymmetry_name(self,
                                                   pair_name,
                                                   run_as_string,
                                                   rebin=False)
                    directory = get_base_data_directory(self, run_as_string)

                    self.group_pair_context[pair_name].show_raw(
                        run, directory + name)

                    if self._do_rebin():
                        name = get_pair_asymmetry_name(self,
                                                       pair_name,
                                                       run_as_string,
                                                       rebin=True)
                        self.group_pair_context[pair_name].show_rebin(
                            run, directory + name)

    def calculate_all_pairs(self):
        self._calculate_pairs(rebin=False)
        if (self._do_rebin()):
            self._calculate_pairs(rebin=True)

    def _calculate_pairs(self, rebin):
        for run in self._data_context.current_runs:
            for pair in self._group_pair_context.pairs:
                pair_asymmetry_workspace = self.calculate_pair(pair,
                                                               run,
                                                               rebin=rebin)
                if not pair_asymmetry_workspace:
                    continue
                pair.update_asymmetry_workspace(pair_asymmetry_workspace,
                                                run,
                                                rebin=rebin)

    def calculate_all_groups(self):
        self._calculate_groups(rebin=False)
        if self._do_rebin():
            self._calculate_groups(rebin=True)

    def _calculate_groups(self, rebin):
        for run in self._data_context.current_runs:
            run_pre_processing(context=self, run=run, rebin=rebin)
            for group in self._group_pair_context.groups:
                group_workspace, group_asymmetry, group_asymmetry_unormalised = \
                     self.calculate_group(group, run, rebin=rebin)

                # If this run contains none of the relevant periods for the group no
                # workspace is created.
                if not group_workspace:
                    continue

                self.group_pair_context[group.name].update_workspaces(
                    run,
                    group_workspace,
                    group_asymmetry,
                    group_asymmetry_unormalised,
                    rebin=rebin)

    def update_current_data(self):
        # Update the current data; resetting the groups and pairs to their
        # default values
        if len(self.data_context.current_runs) > 0:
            self.data_context.update_current_data()

            if not self.group_pair_context.groups:
                maximum_number_of_periods = max(
                    [self.num_periods(run) for run in self.current_runs])
                self.group_pair_context.reset_group_and_pairs_to_default(
                    self.data_context.current_workspace,
                    self.data_context.instrument,
                    self.data_context.main_field_direction,
                    maximum_number_of_periods)
        else:
            self.data_context.clear()

    def show_raw_data(self):
        self.ads_observer.observeRename(False)
        for run in self.data_context.current_runs:
            with WorkspaceGroupDefinition():
                run_string = run_list_to_string(run)
                loaded_workspace = \
                    self.data_context._loaded_data.get_data(run=run, instrument=self.data_context.instrument)['workspace'][
                        'OutputWorkspace']
                loaded_workspace_deadtime_table = self.data_context._loaded_data.get_data(
                    run=run, instrument=self.data_context.instrument
                )['workspace']['DataDeadTimeTable']
                directory = get_base_data_directory(self, run_string)

                deadtime_name = get_deadtime_data_workspace_name(
                    self.data_context.instrument,
                    str(run[0]),
                    workspace_suffix=self.workspace_suffix)
                MuonWorkspaceWrapper(loaded_workspace_deadtime_table).show(
                    directory + deadtime_name)
                self.data_context._loaded_data.get_data(
                    run=run, instrument=self.data_context.instrument
                )['workspace']['DataDeadTimeTable'] = deadtime_name

                if len(loaded_workspace) > 1:
                    # Multi-period data
                    for i, single_ws in enumerate(loaded_workspace):
                        name = directory + get_raw_data_workspace_name(
                            self.data_context.instrument,
                            run_string,
                            multi_period=True,
                            period=str(i + 1),
                            workspace_suffix=self.workspace_suffix)
                        single_ws.show(name)
                else:
                    # Single period data
                    name = directory + get_raw_data_workspace_name(
                        self.data_context.instrument,
                        run_string,
                        multi_period=False,
                        workspace_suffix=self.workspace_suffix)
                    loaded_workspace[0].show(name)

        self.ads_observer.observeRename(True)

    def _do_rebin(self):
        return (self.gui_context['RebinType'] == 'Fixed'
                and 'RebinFixed' in self.gui_context and self.gui_context['RebinFixed']) or \
               (self.gui_context['RebinType'] == 'Variable'
                and 'RebinVariable' in self.gui_context and self.gui_context['RebinVariable'])

    def get_detectors_excluded_from_default_grouping_tables(self):
        groups, _, _ = get_default_grouping(
            self.data_context.current_workspace, self.data_context.instrument,
            self.data_context.main_field_direction)
        detectors_in_group = []
        for group in groups:
            detectors_in_group += group.detectors
        detectors_in_group = set(detectors_in_group)

        return [
            det for det in range(1, self.data_context.num_detectors)
            if det not in detectors_in_group
        ]

    # Get the groups/pairs for active WS
    def getGroupedWorkspaceNames(self):
        run_numbers = self.data_context.current_runs
        runs = [
            wsName.get_raw_data_workspace_name(
                self.data_context.instrument,
                run_list_to_string(run_number),
                self.data_context.is_multi_period(),
                period=str(period + 1),
                workspace_suffix=self.workspace_suffix)
            for run_number in run_numbers
            for period in range(self.data_context.num_periods(run_number))
        ]
        return runs

    def first_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['FirstGoodDataFromFile']:
            return self.data_context.get_loaded_data_for_run(
                run)["FirstGoodData"]
        else:
            if 'FirstGoodData' in self.gui_context:
                return self.gui_context['FirstGoodData']
            else:
                self.gui_context[
                    'FirstGoodData'] = self.data_context.get_loaded_data_for_run(
                        run)["FirstGoodData"]
                return self.gui_context['FirstGoodData']

    def last_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['LastGoodDataFromFile']:
            return round(
                max(
                    self.data_context.get_loaded_data_for_run(run)
                    ["OutputWorkspace"][0].workspace.dataX(0)), 2)
        else:
            if 'LastGoodData' in self.gui_context:
                return self.gui_context['LastGoodData']
            else:
                self.gui_context['LastGoodData'] = round(
                    max(
                        self.data_context.get_loaded_data_for_run(run)
                        ["OutputWorkspace"][0].workspace.dataX(0)), 2)
                return self.gui_context['LastGoodData']

    def dead_time_table(self, run):
        if self.gui_context['DeadTimeSource'] == 'FromADS':
            return self.gui_context['DeadTimeTable']
        elif self.gui_context['DeadTimeSource'] == 'FromFile':
            return self.data_context.get_loaded_data_for_run(
                run)["DataDeadTimeTable"]
        elif self.gui_context['DeadTimeSource'] == 'None':
            return None

    def get_group_and_pair(self, group_and_pair):
        if group_and_pair == 'All':
            group = self.group_pair_context.group_names
            pair = self.group_pair_context.pair_names
        else:
            group_pair_list = group_and_pair.replace(' ', '').split(',')
            group = [
                group for group in group_pair_list
                if group in self.group_pair_context.group_names
            ]
            pair = [
                pair for pair in group_pair_list
                if pair in self.group_pair_context.pair_names
            ]
        return group, pair

    def get_runs(self, runs):
        if runs == 'All':
            run_list = self.data_context.current_runs
        else:
            run_list = [
                run_string_to_list(item)
                for item in runs.replace(' ', '').split(',')
            ]
            flat_list = []
            for sublist in run_list:
                flat_list += [[run] for run in sublist if len(sublist) > 1]
            run_list += flat_list
            run_list = [
                run for run in run_list
                if run in self.data_context.current_runs
            ]
        return run_list

    def get_list_of_binned_or_unbinned_workspaces_from_equivalents(
            self, input_list):
        equivalent_list = []

        for item in input_list:
            if 'PhaseQuad' in item:
                equivalent_list.append(item)

            equivalent_group_pair = self.group_pair_context.get_equivalent_group_pair(
                item)
            if equivalent_group_pair:
                equivalent_list.append(equivalent_group_pair)

        return equivalent_list

    def remove_workspace(self, workspace):
        # required as the renameHandler returns a name instead of a workspace.
        if isinstance(workspace, str):
            workspace_name = workspace
        else:
            workspace_name = workspace.name()

        self.data_context.remove_workspace_by_name(workspace_name)
        self.group_pair_context.remove_workspace_by_name(workspace_name)
        self.phase_context.remove_workspace_by_name(workspace_name)
        self.fitting_context.remove_workspace_by_name(workspace_name)
        self.gui_context.remove_workspace_by_name(workspace_name)
        self.update_view_from_model_notifier.notify_subscribers(workspace_name)
        self.deleted_plots_notifier.notify_subscribers(workspace)

    def clear_context(self):
        self.data_context.clear()
        self.group_pair_context.clear()
        self.phase_context.clear()
        self.fitting_context.clear()
        self.update_view_from_model_notifier.notify_subscribers()

    def workspace_replaced(self, workspace):
        self.update_plots_notifier.notify_subscribers(workspace)
Exemple #12
0
class SansGuiObservable:
    reduction_dim: Observable = Observable()
    save_options: Observable = Observable()
class PhaseTablePresenter(object):
    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.current_alg = None
        self._phasequad_obj = None
        self._new_table_name = ""

        self.group_change_observer = GenericObserver(
            self.update_current_groups_list)
        self.run_change_observer = GenericObserver(
            self.update_current_run_list)
        self.instrument_changed_observer = GenericObserver(
            self.update_current_phase_tables)

        self.phase_table_calculation_complete_notifier = Observable()
        self.phase_table_observer = GenericObserver(
            self.update_current_phase_tables)
        self.phasequad_calculation_complete_notifier = Observable()
        self.enable_editing_notifier = Observable()
        self.disable_editing_notifier = Observable()

        self.disable_tab_observer = GenericObserver(self.view.disable_widget)
        self.enable_tab_observer = GenericObserver(self.view.enable_widget)
        self.selected_phasequad_changed_notifier = GenericObservable()
        self.calculation_finished_notifier = GenericObservable()

        self.update_view_from_model_observer = GenericObserver(
            self.update_view_from_model)
        self.update_current_phase_tables()

        self.view.on_first_good_data_changed(
            self.handle_first_good_data_changed)
        self.view.on_last_good_data_changed(self.handle_last_good_data_changed)
        self.view.on_phasequad_table_data_changed(
            self.handle_phasequad_table_data_changed)

    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.update_current_phase_tables()
        for key, item in self.context.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)

    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:
            self.view.warning_popup(
                "Groups and pairs (including phasequads) must have unique names"
            )
            return False
        if not re.match(valid_name_regex, name):
            self.view.warning_popup(
                "Phasequad names should only contain digits, characters and _")
            return False
        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.update_model_from_view()

        if self.view.input_workspace == "":
            self.view.disable_widget()
            self.view.clear_phase_tables()
            self.view.clear_phasequads()
        else:
            self.view.setEnabled(True)

    def update_current_groups_list(self):
        self.view.set_group_combo_boxes(
            self.context.group_pair_context.group_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()

    def handle_thread_calculation_started(self):
        """Generic handling of starting calculation threads"""
        self.disable_editing_notifier.notify_subscribers()

    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

    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)
        self.current_alg = None

    """=============== Phase table methods ==============="""

    def handle_first_good_data_changed(self):
        self._validate_data_changed(self.view.first_good_time,
                                    "First Good Data")

    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])

        if self.view.first_good_time > self.view.last_good_time:
            self.view.first_good_time = first_good_time
            self.view.last_good_time = last_good_time
            self.view.warning_popup(
                "First Good Data cannot be greater than Last Good Data")
        elif data < first_good_time:
            self.view.first_good_time = first_good_time
            self.view.warning_popup(
                f"{string} cannot be smaller than {first_good_time}")
        elif data > last_good_time:
            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):
        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

    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)
        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()
        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

        self.add_phase_table_to_ADS(detector_table)
        self.add_fitting_info_to_ADS_if_required(parameters['DetectorTable'],
                                                 fitting_information)

        return parameters['DetectorTable']

    def handle_phase_table_calculation_started(self):
        """Specific handling when calculating phase table is started"""
        self.view.enable_phase_table_cancel()
        self.handle_thread_calculation_started()

    def handle_phase_table_calculation_success(self):
        """Specific handling when a phase table calculation succeeds"""
        self.phase_table_calculation_complete_notifier.notify_subscribers()
        self.update_current_phase_tables()
        self.view.disable_phase_table_cancel()
        self._new_table_name = ""
        self.handle_thread_calculation_success()

    def handle_phase_table_calculation_error(self, error):
        """Specific handling when calculate phase table errors"""
        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()

        # Get name of table, an empty string uses a default name so None is used to identify if cancel is pressed
        name = self.view.enter_phase_table_name()
        if name is None:
            self.enable_editing_notifier.notify_subscribers()
            return
        self._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.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']
        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

        # Update the table stored in each phasequad
        self.context.group_pair_context.update_phase_tables(new_table)
        self.context.update_phasequads()  # Updates phasequads
        self.calculation_finished_notifier.notify_subscribers()
        self.view.enable_widget()
        self.enable_editing_notifier.notify_subscribers()

    """=============== Phasequad methods ==============="""

    def calculate_phasequad(self):
        self.context.group_pair_context.add_phasequad(self._phasequad_obj)
        self.context.calculate_phasequads(self._phasequad_obj)

        self.phasequad_calculation_complete_notifier.notify_subscribers(
            self._phasequad_obj.Re.name)
        self.phasequad_calculation_complete_notifier.notify_subscribers(
            self._phasequad_obj.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:
                if phasequad.name == name:
                    self.add_phasequad_to_analysis(False, False, phasequad)
                    self.context.group_pair_context.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:
                if phasequad.name == name:
                    self.add_phasequad_to_analysis(False, False, phasequad)
                    self.context.group_pair_context.remove_phasequad(phasequad)
                    self.calculation_finished_notifier.notify_subscribers()

    def handle_phasequad_calculation_started(self):
        """Specific handling when calculating phasequad is started"""
        self.handle_thread_calculation_started()

    def handle_phasequad_calculation_success(self):
        """Specific handling when a phasequad calculation succeeds"""
        self.add_phasequad_to_analysis(True, True, self._phasequad_obj)
        self.view.disable_updates()
        self.view.add_phasequad_to_table(self._phasequad_obj.name)
        self.view.enable_updates()
        self._phasequad_obj = None
        self.calculation_finished_notifier.notify_subscribers()
        self.handle_thread_calculation_success()

    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:
            self.view.warning_popup("Please generate a phase table first.")
            return

        self.update_model_from_view()

        name = self.view.enter_phasequad_name()
        if name is None:
            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.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()
        if not phasequads:
            self.remove_last_row()
        else:
            self.remove_selected_rows(phasequads)

    def handle_phasequad_table_data_changed(self, row, col):
        """Handles when either Analyse checkbox is changed"""
        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:
            if phasequad.name == name:
                if col == REAL_PART:
                    self.add_part_to_analysis(is_added, phasequad.Re.name)
                elif col == IMAGINARY_PART:
                    self.add_part_to_analysis(is_added, phasequad.Im.name)

    def add_phasequad_to_analysis(self, re_is_added, im_is_added, phasequad):
        """Adds both Real and Imaginary parts to the analysis if their is_added is true, else removes them"""
        self.add_part_to_analysis(re_is_added, phasequad.Re.name, False)
        self.add_part_to_analysis(im_is_added, phasequad.Im.name, False)

    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)
        else:
            self.context.group_pair_context.remove_pair_from_selected_pairs(
                name)

        # Notify a new phasequad is selected to update the plot
        if notify:
            group_info = {'is_added': is_added, 'name': name}
            self.selected_phasequad_changed_notifier.notify_subscribers(
                group_info)
Exemple #14
0
class LoadWidgetPresenterEA(object):

    """
    The load widget is responsible for combining data loaded from its two sub-widgets in a systematic way
    (either keeping a single workspace, or allowing multiple to be loaded at once).

     - It handles any additional load behaviours such as how to handle multiple runs.
     - Handles instrument change.

    Although there is duplication of code (in the models of the subwidgets) this allows them to be
    used standalone without this parent widget.
    """

    def __init__(self, view, model):
        self._view = view
        self._model = model

        self.load_run_widget = None
        self.load_file_widget = None

        self.view.on_subwidget_loading_started(self.disable_loading)
        self.view.on_subwidget_loading_finished(self.enable_loading)

        self.view.on_file_widget_data_changed(
            self.handle_file_widget_data_changed)
        self.view.on_run_widget_data_changed(
            self.handle_run_widget_data_changed)

        self.view.on_clear_button_clicked(self.clear_data_and_view)

        self._view.on_multiple_load_type_changed(
            self.handle_multiple_runs_option_changed)
        self._use_threading = True

        self.loadNotifier = Observable()

        self.disable_observer = GenericObserver(self.disable_loading)
        self.enable_observer = GenericObserver(self.enable_loading)

        self.update_view_from_model_observer = GenericObserver(self.update_view_from_model)

    def set_load_run_widget(self, widget):
        self.load_run_widget = widget
        self.load_run_widget.update_view_from_model([])

    def set_load_file_widget(self, widget):
        self.load_file_widget = widget
        self.load_file_widget.update_view_from_model([])

    def handle_multiple_runs_option_changed(self):
        selection = self._view.get_multiple_loading_state()
        if selection:
            self.load_file_widget.update_multiple_loading_behaviour(CO_ADD)
            self.load_run_widget.update_multiple_loading_behaviour(CO_ADD)
        else:
            self.load_file_widget.update_multiple_loading_behaviour(
                SIMULTANEOUS)
            self.load_run_widget.update_multiple_loading_behaviour(
                SIMULTANEOUS)
        self.load_run_widget.handle_run_changed_by_user()

    def enable_multiple_runs(self, enabled=True):
        self.load_run_widget.enable_multiple_runs(enabled)
        self.load_file_widget.enable_multiple_runs(enabled)

    def handle_file_widget_data_changed(self):
        self.load_run_widget.update_view_from_model(self._model.runs)
        self.loadNotifier.notify_subscribers()

    def handle_run_widget_data_changed(self):
        self._model.update_current_data()
        self.load_run_widget.update_view_from_model(self._model.runs)
        self.loadNotifier.notify_subscribers()

    def disable_loading(self):
        self.load_run_widget.disable_loading()
        self.load_file_widget.disable_loading()
        self._view.disable_loading()

    def enable_loading(self):
        self.load_run_widget.enable_loading()
        self.load_file_widget.enable_loading()
        self._view.enable_loading()

    def show(self):
        self._view.show()

    def clear_data(self):
        self._model.clear_data()

    def clear_data_and_view(self):
        self._model.clear_data()
        self.handle_run_widget_data_changed()
        self.handle_run_widget_data_changed()
        self.load_run_widget.set_current_instrument(
            self._model.instrument)
        self.loadNotifier.notify_subscribers()

    def update_view_from_model(self):
        self.load_run_widget.update_view_from_model(self._model.runs)

    @property
    def view(self):
        return self._view

    def set_current_instrument(self, instrument):
        self._model.instrument = instrument
        self.load_file_widget.set_current_instrument(instrument)
        self.load_run_widget.set_current_instrument(instrument)
Exemple #15
0
class FittingContext(object):
    """Context specific to fitting.

    It holds details are any fits performed including:
       - input workspaces
       - parameters
       - function names
    """
    def __init__(self):
        # A dictionary containing the fit histories for different fitting modes. The key should represent a fitting
        # mode, and the value should be a list of FitInformation's indicating all the fits that have been done up to
        # this point
        self._fit_history: dict = {}

        self.new_fit_results_notifier = Observable()
        self.fit_removed_notifier = Observable()

    def all_latest_fits(self) -> None:
        """Returns the latest fits with unique fit output names for all fitting modes. Override in a child class."""
        raise NotImplementedError(
            "This needs to be overridden by a child class.")

    @property
    def active_fit_history(self) -> None:
        """Returns the fit history for the currently active fitting mode. Override in a child class."""
        raise NotImplementedError(
            "This needs to be overridden by a child class.")

    @active_fit_history.setter
    def active_fit_history(self, fit_history: list) -> None:
        """Sets the fit history for the currently active fitting mode. Override in a child class."""
        raise NotImplementedError(
            "This needs to be overridden by a child class.")

    def clear(self, removed_fits: list = []):
        """Removes all the stored Fits from the context."""
        self.fit_removed_notifier.notify_subscribers(removed_fits)

    def remove_overridden_fits(self) -> None:
        """Removes the fits in the fit history that have been overridden by a newer fit."""
        self.active_fit_history = self._latest_unique_fits_in(
            self.active_fit_history)

    @staticmethod
    def remove_fit_by_name(fits_history: list, workspace_name: str) -> None:
        """Remove a Fit from the history when an ADS delete event happens on one of its output workspaces."""
        for fit in reversed(fits_history):
            if workspace_name in fit.output_workspace_names(
            ) or workspace_name == fit.parameter_workspace_name:
                fits_history.remove(fit)

    def add_fit_from_values(self,
                            input_workspace_names: list,
                            fit_function_name: str,
                            output_workspaces: list,
                            parameter_workspace: StaticWorkspaceWrapper,
                            covariance_workspace: StaticWorkspaceWrapper,
                            global_parameters: list = None,
                            tf_asymmetry_fit: bool = False) -> None:
        """
        Add a new fit information object based on the raw values.
        :param input_workspace_names: A list of input workspace names containing the original data.
        :param fit_function_name: The name of the function used.
        :param output_workspaces: A list of StaticWorkspaceWrapper's containing the output workspaces.
        :param parameter_workspace: A StaticWorkspaceWrapper containing the parameter workspace.
        :param covariance_workspace: A StaticWorkspaceWrapper containing the covariance workspace.
        :param global_parameters: An optional list of global parameters that were tied together during the fit.
        :param tf_asymmetry_fit: An optional flag indicating whether the data is from a TF Asymmetry fit or not.
        """
        self.add_fit(
            FitInformation(input_workspace_names, fit_function_name,
                           output_workspaces, parameter_workspace,
                           covariance_workspace, global_parameters,
                           tf_asymmetry_fit))

    def add_fit(self, fit: FitInformation) -> None:
        """
        Add a new fit to the context. Subscribers are notified of the update.
        :param fit: A new FitInformation object
        """
        self.active_fit_history.append(fit)
        self.new_fit_results_notifier.notify_subscribers(fit)

    def fit_function_names(self) -> list:
        """
        :return: a list of unique function names used in the fit
        """
        return list(
            set([fit.fit_function_name for fit in self.all_latest_fits()]))

    def find_fit_for_input_workspace_list_and_function(self,
                                                       input_workspace_list,
                                                       fit_function_name):
        """
        Find the fit in the list whose input workspace matches the input workspace list
        and the specified fit function name
        :param  input_workspace_list: A list of input workspaces
        :param fit_function_name: Fit function name
        :return: A matching fit
        """
        for fit in self.all_latest_fits():
            if fit.input_workspaces == input_workspace_list and fit.fit_function_name == fit_function_name:
                return fit
        else:
            return None

    def log_names(self, filter_fn=None):
        """
        The names of the logs on the workspaces associated with all of the workspaces.

        :filter_fn: An optional unary function to filter the names out. For more information see
        FitInformation.log_names
        :return: A list of names of logs
        """
        return [
            name for fit in self.all_latest_fits()
            for name in fit.log_names(filter_fn)
        ]

    def undo_previous_fit(self) -> None:
        """
        Undoes the Fit stored at the top of the fit history. If an identical fit exists further back in the history,
        then its workspaces are loaded into the ADS. Otherwise, the Results tab is notified to remove the fit.
        """
        removed_fit = self.active_fit_history.pop()

        fit_found = self._add_next_identical_fit_to_the_ads(removed_fit)
        if not fit_found:
            self.fit_removed_notifier.notify_subscribers([removed_fit])

    def _add_next_identical_fit_to_the_ads(self,
                                           undone_fit: FitInformation) -> bool:
        """
        Adds the next fit in the history that is identical to the undone fit back into the ADS. The history is reversed
        because the end of the list contains the most recent fits.
        :param undone_fit: The Fit that was just undone by the user.
        :returns True if an identical fit was found in the history and added back into the ADS.
        """
        for fit in reversed(self.active_fit_history):
            if fit == undone_fit:
                fit.add_copy_to_ads()
                return True
        return False

    def _latest_unique_fits_in(self, fits_history: list) -> list:
        """Returns a list of fits which all have unique fit output workspaces, and are the most recent of their kind."""
        if len(fits_history) == 0:
            return fits_history

        latest_fits = []
        # Reversed because the fits at the end of the list are the most recently performed fits.
        for fit in reversed(fits_history):
            if fit not in latest_fits:
                latest_fits.append(fit)
        latest_fits.reverse()
        return latest_fits
Exemple #16
0
class MaxEntPresenter(object):

    """
    This class links the MaxEnt model to the GUI
    """

    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.thread = None
        self._optional_output_names = {}
        # set data
        self.getWorkspaceNames()
        self.view.set_methods([USEGROUPS, USEDETECTORS])
        # connect
        self.view.maxEntButtonSignal.connect(self.handleMaxEntButton)
        self.view.method_changed_slot(self.update_phase_table_options)
        self.view.method_changed_slot(self.notify_method_changed)
        self.view.period_changed_slot(self.notify_period_changed)
        self.view.cancelSignal.connect(self.cancel)
        self.view.run_changed_slot(self._load_periods)

        self.phase_table_observer = GenericObserver(self.update_phase_table_options)
        self.calculation_finished_notifier = GenericObservable()
        self.calculation_started_notifier = GenericObservable()
        self.new_phase_table = GenericObservable()
        self.update_phase_table_options()
        self.method_changed = Observable()
        self.period_changed = Observable()
        self.new_reconstructed_data = Observable()

    def notify_method_changed(self):
        self.method_changed.notify_subscribers(self.view.get_method)

    def notify_period_changed(self):
        self.period_changed.notify_subscribers(self.view.get_period)

    @property
    def use_groups(self):
        return self.view.get_method == USEGROUPS

    @property
    def widget(self):
        return self.view

    def _load_periods(self):
        run = run_string_to_list(self.view.get_run)
        periods = []
        if run != []:
            periods = [str(period+1) for period in range(self.context.data_context.num_periods(run))]
        self.view.add_periods(periods)

    @property
    def get_selected_groups(self):
        selected_names = self.context.group_pair_context.selected_groups
        return [ group for group in self.context.group_pair_context.groups if group.name in selected_names]

    @property
    def get_num_groups(self):
        return len(self.context.group_pair_context.selected_groups)

    def runChanged(self):
        self.getWorkspaceNames()

    def clear(self):
        self.view.addRuns([])
        self.view.update_phase_table_combo(['Construct'])

    # functions
    def getWorkspaceNames(self):
        # get runs
        run_list = [run_list_to_string(run) for run in self.context.data_context.current_runs]
        self.view.addRuns(run_list)
        # get periods
        self._load_periods()
        # get min number of points as a power of 2
        start = int(
            math.ceil(math.log(self.context.data_context.num_points) / math.log(2.0)))
        values = [str(2**k) for k in range(start, 21)]
        self.view.addNPoints(values)

    def cancel(self):
        if self.maxent_alg is not None:
            self.maxent_alg.cancel()

    # turn on button
    def activate(self):
        self.view.activateCalculateButton()

    # turn off button
    def deactivate(self):
        self.view.deactivateCalculateButton()

    def createThread(self):
        self.maxent_alg = mantid.AlgorithmManager.create("MuonMaxent")
        self._maxent_output_workspace_name = get_maxent_workspace_name(
            self.get_parameters_for_maxent_calculation()['InputWorkspace'],  self.view.get_method)
        calculation_function = functools.partial(self.calculate_maxent, self.maxent_alg)
        self._maxent_calculation_model = ThreadModelWrapper(calculation_function)
        return thread_model.ThreadModel(self._maxent_calculation_model)

    # constructs the inputs for the MaxEnt algorithms
    # then executes them (see maxent_model to see the order
    # of execution
    def handleMaxEntButton(self):
        # put this on its own thread so not to freeze Mantid
        self.thread = self.createThread()
        self.thread.threadWrapperSetUp(self.deactivate, self.handleFinished, self.handle_error)
        self.calculation_started_notifier.notify_subscribers()
        self.thread.start()

    # kills the thread at end of execution
    def handleFinished(self):
        self.activate()
        self.calculation_finished_notifier.notify_subscribers(self._maxent_output_workspace_name)
        # if phase table is outputed
        if self.view.output_reconstructed_spectra and SPECTRA in self._optional_output_names.keys():
            ws = MuonWorkspaceWrapper(self._optional_output_names[SPECTRA]).workspace
            if self.use_groups:
                table = MuonWorkspaceWrapper(self._optional_output_names[GROUPINGTABLE]).workspace
                self.new_reconstructed_data.notify_subscribers({"ws": ws.name(), "table": table.name()})
            else:
                self.new_reconstructed_data.notify_subscribers({"ws": ws.name(), "table": None})

        if self.view.output_phase_table and PHASETABLE in self._optional_output_names.keys():
            name = self._optional_output_names[PHASETABLE]
            if self.use_groups:
                num_groups = self.get_num_groups
                self.context.frequency_context.add_group_phase_table(MuonWorkspaceWrapper(name), num_groups)
            else:
                self.context.phase_context.add_phase_table(MuonWorkspaceWrapper(name))
                self.new_phase_table.notify_subscribers()
            self.update_phase_table_options()
        # clear optional outputs
        self._optional_output_names = {}

    def handle_error(self, error):
        self.activate()
        self.view.warning_popup(error)

    def calculate_maxent(self, alg):
        maxent_parameters = self.get_parameters_for_maxent_calculation()
        base_name = get_maxent_workspace_name(maxent_parameters['InputWorkspace'],  self.view.get_method)
        if self.use_groups and self.get_num_groups ==0:
            # this is caught as part of the calculation thread
            raise ValueError("Please select groups in the grouping tab")
        else:
            maxent_workspace = run_MuonMaxent(maxent_parameters, alg, base_name)
            self.add_maxent_workspace_to_ADS(maxent_parameters['InputWorkspace'], maxent_workspace, alg)

    def _create_group_table(self):
        tab = create_empty_table(GROUPINGTABLE)
        tab.addColumn('str', 'Group')
        tab.addColumn('str', 'Detectors')
        groups = self.get_selected_groups
        for group in groups:
            detectors = ""
            name = group.name
            for det in group.detectors:
                detectors += str(det) + ","
            tab.addRow([name, detectors[:-1]])
        return tab

    def get_parameters_for_maxent_calculation(self):
        inputs = {}
        run = self.view.get_run
        period = self.view.get_period
        multiperiod = True if self.view.num_periods >1 else False
        name = get_raw_data_workspace_name(self.context.data_context.instrument, run, multiperiod,
                                           period=period, workspace_suffix=self.context.workspace_suffix)
        inputs['InputWorkspace'] = name

        if self.view.phase_table != 'Construct' and self.view.phase_table != 'None':
            inputs['InputPhaseTable'] = self.view.phase_table

        if self.use_groups:
            table = self._create_group_table()
            inputs["GroupTable"] = table

        dead_time_table_name = self.context.corrections_context.current_dead_time_table_name_for_run(
            self.context.data_context.instrument, [run])
        if dead_time_table_name is not None:
            inputs['InputDeadTimeTable'] = dead_time_table_name

        run_float = [float(re.search("[0-9]+", name).group())]
        inputs['FirstGoodTime'] = self.context.first_good_data(run_float)

        inputs['LastGoodTime'] = self.context.last_good_data(run_float)

        inputs['Npts'] = self.view.num_points

        inputs['InnerIterations'] = self.view.inner_iterations

        inputs['OuterIterations'] = self.view.outer_iterations

        inputs['DoublePulse'] = self.view.double_pulse

        inputs['Factor'] = self.view.lagrange_multiplier

        inputs['MaxField'] = self.view.maximum_field

        inputs['DefaultLevel'] = self.view.maximum_entropy_constant

        inputs['FitDeadTime'] = self.view.fit_dead_times

        return inputs

    def update_phase_table_options(self):
        phase_table_list = []
        if self.use_groups:
            num_groups = self.get_num_groups
            phase_table_list = self.context.frequency_context.get_group_phase_tables(num_groups, self.context.data_context.instrument)
            phase_table_list.insert(0, 'None')
        else:
            phase_table_list = self.context.phase_context.get_phase_table_list(self.context.data_context.instrument)
            phase_table_list.insert(0, 'Construct')

        self.view.update_phase_table_combo(phase_table_list)

    def add_maxent_workspace_to_ADS(self, input_workspace, maxent_workspace, alg):
        run = re.search('[0-9]+', input_workspace).group()
        base_name = get_maxent_workspace_name(input_workspace, self.view.get_method)
        directory = get_maxent_workspace_group_name(base_name, self.context.data_context.instrument, self.context.workspace_suffix)

        muon_workspace_wrapper = MuonWorkspaceWrapper(directory + base_name)
        muon_workspace_wrapper.show()

        maxent_output_options = self.get_maxent_output_options()
        self.context.frequency_context.add_maxEnt(run, maxent_workspace)
        self.add_optional_outputs_to_ADS(alg, maxent_output_options, base_name, directory)

        # Storing this on the class so it can be sent as part of the calculation
        # finished signal.
        self._maxent_output_workspace_name = base_name

    def get_maxent_output_options(self):
        output_options = {}

        output_options[PHASETABLE] = self.view.output_phase_table
        output_options[DEADTIMES] = self.view.output_dead_times
        output_options[SPECTRA] = self.view.output_reconstructed_spectra
        output_options[PHASECONVERGENCE] = self.view.output_phase_convergence
        output_options[GROUPINGTABLE] = self.use_groups

        return output_options

    def add_optional_outputs_to_ADS(self, alg, output_options, base_name, directory):
        for key in output_options:
            if key == GROUPINGTABLE and output_options[key]:
                output = GROUPINGTABLE
                self.context.ads_observer.observeRename(False)
                wrapped_workspace = MuonWorkspaceWrapper(output)
                name = directory + base_name + " " + GROUPINGTABLE
                self._optional_output_names[key] = name
                wrapped_workspace.show(name)
                self.context.ads_observer.observeRename(True)
            elif output_options[key]:
                output = alg.getProperty(key).valueAsStr
                self.context.ads_observer.observeRename(False)
                wrapped_workspace = MuonWorkspaceWrapper(output)
                name = directory + base_name + "(" + str(self.get_num_groups)+" groups) "+ optional_output_suffixes[key]
                self._optional_output_names[key] = name
                wrapped_workspace.show(name)
                self.context.ads_observer.observeRename(True)

    def update_view_from_model(self):
        self.getWorkspaceNames()
Exemple #17
0
 def __init__(self, outer):
     Observable.__init__(self)
     self.outer = outer  # handle to containing class
Exemple #18
0
class PhaseTablePresenter(object):
    def __init__(self, view, context):
        self.view = view
        self.context = context
        self.current_alg = None

        self.group_change_observer = GenericObserver(
            self.update_current_groups_list)
        self.run_change_observer = GenericObserver(
            self.update_current_run_list)
        self.instrument_changed_observer = GenericObserver(
            self.update_current_phase_tables)

        self.phase_table_calculation_complete_notifier = Observable()
        self.phase_quad_calculation_complete_nofifier = Observable()
        self.enable_editing_notifier = Observable()
        self.disable_editing_notifier = Observable()

        self.disable_tab_observer = GenericObserver(self.view.disable_widget)
        self.enable_tab_observer = GenericObserver(self.view.enable_widget)

        self.update_view_from_model_observer = GenericObserver(
            self.update_view_from_model)

        self.update_current_phase_tables()

    def update_view_from_model(self):
        self.view.set_input_combo_box(self.context.getGroupedWorkspaceNames())
        self.view.set_group_combo_boxes(
            self.context.group_pair_context.group_names)
        self.update_current_phase_tables()
        for key, item in self.context.phase_context.options_dict.items():
            setattr(self.view, key, item)

    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)

    def cancel(self):
        if self.current_alg is not None:
            self.current_alg.cancel()

    def handle_calulate_phase_table_clicked(self):
        self.update_model_from_view()
        self.disable_editing_notifier.notify_subscribers()
        self.calculation_thread = self.create_calculation_thread()

        self.calculation_thread.threadWrapperSetUp(
            self.handle_phase_table_calculation_started,
            self.handle_calculation_success, self.handle_calculation_error)

        self.calculation_thread.start()

    def create_calculation_thread(self):
        self._calculation_model = ThreadModelWrapper(
            self.calculate_phase_table)
        return thread_model.ThreadModel(self._calculation_model)

    def handle_calculate_phase_quad_button_clicked(self):
        self.update_model_from_view()
        self.phasequad_calculation_thread = self.create_phase_quad_calculation_thread(
        )

        self.phasequad_calculation_thread.threadWrapperSetUp(
            self.handle_calculation_started, self.handle_calculation_success,
            self.handle_calculation_error)

        self.phasequad_calculation_thread.start()

    def create_phase_quad_calculation_thread(self):
        self._phasequad_calculation_model = ThreadModelWrapper(
            self.calculate_phase_quad)
        return thread_model.ThreadModel(self._phasequad_calculation_model)

    def calculate_phase_quad(self):
        parameters = self.get_parameters_for_phase_quad()
        phasequad_workspace_name = get_phase_quad_workspace_name(
            parameters['InputWorkspace'], parameters['PhaseTable'])

        self.current_alg = mantid.AlgorithmManager.create("PhaseQuad")
        phase_quad = run_PhaseQuad(parameters, self.current_alg,
                                   phasequad_workspace_name)
        self.current_alg = None

        self.add_phase_quad_to_ADS(parameters['InputWorkspace'], phase_quad)

    def get_parameters_for_phase_quad(self):
        parameters = {}
        if self.context.phase_context.options_dict[
                'phase_table_for_phase_quad'] == 'Construct':
            parameters['PhaseTable'] = self.calculate_phase_table()
        else:
            parameters['PhaseTable'] = self.context.phase_context.options_dict[
                'phase_table_for_phase_quad']

        parameters['InputWorkspace'] = self.context.phase_context.options_dict[
            'phase_quad_input_workspace']

        return parameters

    def add_phase_quad_to_ADS(self, input_workspace, phasequad_workspace_name):
        run = re.search(
            '^{}([0-9, -]+)[;,_]?'.format(
                self.context.data_context.instrument),
            input_workspace).group(1)

        directory = get_base_data_directory(self.context, run)

        muon_workspace_wrapper = MuonWorkspaceWrapper(directory +
                                                      phasequad_workspace_name)
        muon_workspace_wrapper.show()

        self.context.phase_context.add_phase_quad(muon_workspace_wrapper, run)
        self.phase_quad_calculation_complete_nofifier.notify_subscribers()

    def handle_calculation_started(self):
        self.disable_editing_notifier.notify_subscribers()
        self.view.enable_phasequad_cancel()

    def handle_phase_table_calculation_started(self):
        self.disable_editing_notifier.notify_subscribers()
        self.view.enable_cancel()

    def handle_calculation_error(self, error):
        self.enable_editing_notifier.notify_subscribers()
        self.view.warning_popup(error)
        self.view.disable_cancel()
        self.current_alg = None

    def handle_calculation_success(self):
        self.phase_table_calculation_complete_notifier.notify_subscribers()
        self.enable_editing_notifier.notify_subscribers()
        self.update_current_phase_tables()
        self.view.enable_widget()
        self.view.disable_cancel()
        self.current_alg = None

    def calculate_phase_table(self):
        parameters = self.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

        self.add_phase_table_to_ADS(detector_table)
        self.add_fitting_info_to_ADS_if_required(parameters['DetectorTable'],
                                                 fitting_information)

        return parameters['DetectorTable']

    def add_phase_table_to_ADS(self, base_name):
        run = re.search('[0-9]+', base_name).group()

        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):
        if not self.view.output_fit_information:
            return

        run = re.search('[0-9]+', base_name).group()
        phase_table_group = get_phase_table_workspace_group_name(
            base_name, self.context.data_context.instrument,
            self.context.workspace_suffix)
        directory = get_base_data_directory(self.context,
                                            run) + phase_table_group

        muon_workspace_wrapper = MuonWorkspaceWrapper(directory +
                                                      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)

        return parameters

    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.update_model_from_view()

    def update_current_groups_list(self):
        self.view.set_group_combo_boxes(
            self.context.group_pair_context.group_names)
        self.update_model_from_view()

    def update_current_phase_tables(self):
        phase_table_list = self.context.phase_context.get_phase_table_list(
            self.context.data_context.instrument)
        phase_table_list.append('Construct')

        self.view.set_phase_table_combo_box(phase_table_list)
 def notify_subscribers(self, arg=None):
     Observable.notify_subscribers(self, arg)
Exemple #20
0
class FittingContext(object):
    """Context specific to fitting.

    It holds details are any fits performed including:
       - input workspaces
       - parameters
       - function names
    """
    def __init__(self, fit_list=None):
        self.fit_list = fit_list if fit_list is not None else []
        # Register callbacks with this object to observe when new fits
        # are added
        self.new_fit_results_notifier = Observable()
        self.new_fit_plotting_notifier = Observable()
        self.fit_removed_notifier = Observable()
        self.plot_guess_notifier = Observable()
        self._number_of_fits = 0
        self._number_of_fits_cache = 0
        self._plot_guess = False
        self._guess = None
        self._fit_type = "Single"

    def __len__(self):
        """
        :return: The number of fits in the list
        """
        return len(self.fit_list)

    def add_fit_from_values(self,
                            parameter_workspace,
                            fit_function_name,
                            input_workspace,
                            output_workspace_names,
                            global_parameters=None,
                            plot_fit=True):
        """
        Add a new fit information object based on the raw values.
        See FitInformation constructor for details are arguments.
        """
        self.add_fit(
            FitInformation(parameter_workspace, fit_function_name,
                           input_workspace, output_workspace_names,
                           global_parameters), plot_fit)

    def add_fit(self, fit, plot_fit=True):
        """
        Add a new fit to the context. Subscribers are notified of the update.
        :param fit: A new FitInformation object
        :param plot_fit: Whether the plot the new fit
        """
        if fit not in self.fit_list:
            self.fit_list.append(fit)
            self._number_of_fits += 1
        else:
            self.update_fit(fit)

        self.new_fit_results_notifier.notify_subscribers(fit)

        if plot_fit:
            self.new_fit_plotting_notifier.notify_subscribers(fit)

    def update_fit(self, updated_fit):
        """
        Updates fit parameters of a fit that is currently stored in the context
        :param updated_fit: A FitInformation object
        """
        for fit in self.fit_list:
            if updated_fit == fit:
                fit._fit_parameters = updated_fit._fit_parameters
                return

    def notify_plot_guess_changed(self, plot_guess, guess_ws):
        self.plot_guess = plot_guess
        self.guess_ws = guess_ws
        self.plot_guess_notifier.notify_subscribers()

    def fit_function_names(self):
        """
        :return: a list of unique function names used in the fit
        """
        return list(set([fit.fit_function_name for fit in self.fit_list]))

    def find_output_workspaces_for_input_workspace_name(
            self, input_workspace_name):
        """
        Find the fits in the list whose input workspace matches
        :param input_workspace_name: The name of the input_workspace
        :return: A list of matching fits
        """
        workspace_list = []
        for fit in self.fit_list:
            for index, workspace in enumerate(fit.input_workspaces):
                if workspace == input_workspace_name:
                    workspace_list.append(fit.output_workspace_names[index])

        return workspace_list

    def remove_workspace_by_name(self, workspace_name):
        list_of_fits_to_remove = []
        for fit in self.fit_list:
            if workspace_name in fit.output_workspace_names or workspace_name == fit.parameter_workspace_name:
                self._number_of_fits_cache = 0
                list_of_fits_to_remove.append(fit)

        for fit in list_of_fits_to_remove:
            index = self.fit_list.index(fit)
            if index >= len(self.fit_list) - self._number_of_fits:
                self._number_of_fits -= 1
            self.fit_list.remove(fit)

    def remove_fits_from_stored_fit_list(self, fits):
        removed_fits = []
        for fit in fits:
            if fit in self.fit_list:
                self.fit_list.remove(fit)
                removed_fits += [fit]
                self._number_of_fits -= 1
        self.fit_removed_notifier.notify_subscribers(removed_fits)

    def log_names(self, filter_fn=None):
        """
        The names of the logs on the workspaces associated with all of the workspaces.

        :filter_fn: An optional unary function to filter the names out. For more information see
        FitInformation.log_names
        :return: A list of names of logs
        """
        return [
            name for fit in self.fit_list for name in fit.log_names(filter_fn)
        ]

    def clear(self):
        fits_to_remove = self.fit_list.copy()
        self.remove_fits_from_stored_fit_list(fits_to_remove)

    def remove_latest_fit(self, number_of_fits_to_remove):
        self.fit_list = self.fit_list[:-number_of_fits_to_remove]
        self._number_of_fits = self._number_of_fits_cache

    @property
    def number_of_fits(self):
        return self._number_of_fits

    @number_of_fits.setter
    def number_of_fits(self, value):
        self._number_of_fits_cache = self._number_of_fits
        self._number_of_fits = value

    @property
    def fit_type(self):
        return self._fit_type

    @fit_type.setter
    def fit_type(self, fit_type):
        self._fit_type = fit_type

    @property
    def plot_guess(self):
        return self._plot_guess

    @plot_guess.setter
    def plot_guess(self, value):
        self._plot_guess = value

    @property
    def guess_ws(self):
        return self._guess

    @guess_ws.setter
    def guess_ws(self, value):
        self._guess = value
 def notify_subscribers(self, arg=["", "", ""]):
     Observable.notify_subscribers(self, arg)
Exemple #22
0
 def notify_subscribers(self, *args, **kwargs):
     Observable.notify_subscribers(self, *args, **kwargs)
Exemple #23
0
class MuonContext(object):
    def __init__(self,
                 muon_data_context=None,
                 muon_gui_context=None,
                 muon_group_context=None,
                 corrections_context=None,
                 base_directory='Muon Data',
                 muon_phase_context=None,
                 workspace_suffix=' MA',
                 fitting_context=None,
                 results_context=None,
                 model_fitting_context=None,
                 plot_panes_context=None,
                 frequency_context=None):
        self._data_context = muon_data_context
        self._gui_context = muon_gui_context
        self._group_pair_context = muon_group_context
        self._corrections_context = corrections_context
        self._phase_context = muon_phase_context
        self.fitting_context = fitting_context
        self.results_context = results_context
        self.model_fitting_context = model_fitting_context
        self.base_directory = base_directory
        self.workspace_suffix = workspace_suffix
        self._plot_panes_context = plot_panes_context
        self.ads_observer = MuonContextADSObserver(self.remove_workspace,
                                                   self.clear_context,
                                                   self.workspace_replaced)

        self.gui_context.update({
            'LastGoodDataFromFile': True,
            'selected_group_pair': ''
        })

        self.update_view_from_model_notifier = Observable()
        self.update_plots_notifier = Observable()
        self.deleted_plots_notifier = Observable()

    @property
    def plot_panes_context(self):
        return self._plot_panes_context

    @property
    def data_context(self):
        return self._data_context

    @property
    def gui_context(self):
        return self._gui_context

    @property
    def group_pair_context(self):
        return self._group_pair_context

    @property
    def corrections_context(self):
        return self._corrections_context

    @property
    def phase_context(self):
        return self._phase_context

    def num_periods(self, run):
        return self._data_context.num_periods(run)

    @property
    def current_runs(self):
        return self._data_context.current_runs

    def calculate_counts(self, run, group, rebin=False):
        """Calculates the counts workspace for the given run and group."""
        return self._calculate_counts_or_asymmetry(self._calculate_counts, run,
                                                   group, rebin)

    def _calculate_counts(self, run, group, periods, run_as_string,
                          periods_as_string, rebin):
        """Calculates the counts workspace for the given run and group."""
        output_name = get_group_data_workspace_name(self,
                                                    group.name,
                                                    run_as_string,
                                                    periods_as_string,
                                                    rebin=rebin)
        return calculate_group_data(self, group, run, output_name, periods)

    def calculate_asymmetry(self, run, group, rebin=False):
        """Calculates the asymmetry workspaces for the given run and group."""
        return self._calculate_counts_or_asymmetry(self._calculate_asymmetry,
                                                   run, group, rebin)

    def _calculate_asymmetry(self, run, group, periods, run_as_string,
                             periods_as_string, rebin):
        """Calculates the asymmetry workspaces for the given run and group."""
        output_name = get_group_asymmetry_name(self,
                                               group.name,
                                               run_as_string,
                                               periods_as_string,
                                               rebin=rebin)
        output_name_unnormalised = get_group_asymmetry_unnorm_name(
            self, group.name, run_as_string, periods_as_string, rebin=rebin)
        group_asymmetry, group_asymmetry_unnormalised = estimate_group_asymmetry_data(
            self, group, run, output_name, output_name_unnormalised, periods)
        return group_asymmetry, group_asymmetry_unnormalised

    def _calculate_counts_or_asymmetry(self,
                                       calculation_func,
                                       run,
                                       group,
                                       rebin=False):
        """Calculates the counts or asymmetry workspace depending on the 'calculation_func' that is provided."""
        run_as_string = run_list_to_string(run)
        periods_as_string = run_list_to_string(group.periods)

        # A user requirement is that processing can continue if a period is missing from some
        # of the runs. This filters out periods which are not in a given run.
        periods = [
            period for period in group.periods
            if period <= self.num_periods(run)
        ]

        if not periods:
            return None

        return calculation_func(run, group, periods, run_as_string,
                                periods_as_string, rebin)

    def calculate_diff(self,
                       diff: MuonDiff,
                       run: List[int],
                       rebin: bool = False):
        try:
            positive_workspace_name = self._group_pair_context[
                diff.positive].get_asymmetry_workspace_for_run(run, rebin)
            negative_workspace_name = self._group_pair_context[
                diff.negative].get_asymmetry_workspace_for_run(run, rebin)
        except KeyError:
            # A key error here means the requested workspace does not exist so return None
            return None
        run_as_string = run_list_to_string(run)
        output_workspace_name = get_diff_asymmetry_name(self,
                                                        diff.name,
                                                        run_as_string,
                                                        rebin=rebin)
        return run_minus(positive_workspace_name, negative_workspace_name,
                         output_workspace_name)

    def calculate_pair(self,
                       pair: MuonPair,
                       run: List[int],
                       rebin: bool = False):
        try:
            forward_group_workspace_name = self._group_pair_context[
                pair.forward_group].get_counts_workspace_for_run(run, rebin)
            backward_group_workspace_name = self._group_pair_context[
                pair.backward_group].get_counts_workspace_for_run(run, rebin)
        except KeyError:
            # A key error here means the requested workspace does not exist so return None
            return None

        run_as_string = run_list_to_string(run)
        output_workspace_name = get_pair_asymmetry_name(self,
                                                        pair.name,
                                                        run_as_string,
                                                        rebin=rebin)
        return calculate_pair_data(pair, forward_group_workspace_name,
                                   backward_group_workspace_name,
                                   output_workspace_name)

    def show_group(self, run, group, rebin):
        run_as_string = run_list_to_string(run)
        group_name = group.name
        periods = run_list_to_string(group.periods)

        directory = get_base_data_directory(self, run_as_string)

        name = get_group_data_workspace_name(self,
                                             group_name,
                                             run_as_string,
                                             periods,
                                             rebin=rebin)
        asym_name = get_group_asymmetry_name(self,
                                             group_name,
                                             run_as_string,
                                             periods,
                                             rebin=rebin)
        asym_name_unnorm = get_group_asymmetry_unnorm_name(self,
                                                           group_name,
                                                           run_as_string,
                                                           periods,
                                                           rebin=rebin)

        if not rebin:
            self.group_pair_context[group_name].show_raw(
                run, directory + name, directory + asym_name, asym_name_unnorm)
        else:
            self.group_pair_context[group_name].show_rebin(
                run, directory + name, directory + asym_name, asym_name_unnorm)

    def show_pair(self, run: list, pair: MuonPair):
        pair_name = pair.name
        # Do not want to rename phasequad parts here
        if "_Re_" in pair_name or "_Im_" in pair_name:
            return

        run_as_string = run_list_to_string(run)
        name = get_pair_asymmetry_name(self,
                                       pair_name,
                                       run_as_string,
                                       rebin=False)
        directory = get_base_data_directory(self, run_as_string)

        self.group_pair_context[pair_name].show_raw(run, directory + name)

        if self._do_rebin():
            name = get_pair_asymmetry_name(self,
                                           pair_name,
                                           run_as_string,
                                           rebin=True)
            self.group_pair_context[pair_name].show_rebin(
                run, directory + name)

    def show_diff(self, run: list, diff: MuonDiff):
        diff_name = diff.name

        run_as_string = run_list_to_string(run)
        name = get_diff_asymmetry_name(self,
                                       diff_name,
                                       run_as_string,
                                       rebin=False)
        directory = get_base_data_directory(self, run_as_string)

        self.group_pair_context[diff_name].show_raw(run, directory + name)

        if self._do_rebin():
            name = get_diff_asymmetry_name(self,
                                           diff_name,
                                           run_as_string,
                                           rebin=True)
            self.group_pair_context[diff_name].show_rebin(
                run, directory + name)

    def calculate_all_counts(self):
        self._calculate_all_counts(rebin=False)
        if self._do_rebin():
            self._calculate_all_counts(rebin=True)

    def _calculate_all_counts(self, rebin):
        for run in self._data_context.current_runs:
            run_pre_processing(context=self, run=run, rebin=rebin)
            for group in self._group_pair_context.groups:
                counts_workspace = self.calculate_counts(run, group, rebin)

                if not counts_workspace:
                    continue

                self.group_pair_context[group.name].update_counts_workspace(
                    MuonRun(run), counts_workspace, rebin)

    def calculate_asymmetry_for(self, run, group, rebin):
        asymmetry_workspaces = self.calculate_asymmetry(run, group, rebin)

        if asymmetry_workspaces is not None:
            self.group_pair_context[group.name].update_asymmetry_workspace(
                MuonRun(run), *asymmetry_workspaces, rebin)

    def calculate_pair_for(self, run: List[int], pair: MuonPair):
        self._calculate_pair_for(run, pair, rebin=False)
        if self._do_rebin():
            self._calculate_pair_for(run, pair, rebin=True)

    def _calculate_pair_for(self, run: List[int], pair: MuonPair, rebin: bool):
        pair_asymmetry_workspace = self.calculate_pair(pair, run, rebin=rebin)

        if pair_asymmetry_workspace is not None:
            self.group_pair_context[pair.name].update_asymmetry_workspace(
                pair_asymmetry_workspace, run, rebin=rebin)

    def find_pairs_containing_groups(self, groups: list) -> list:
        """Returns a list of MuonPair's that are formed from one or more groups contained in the provided list."""
        pairs = []
        for pair in self._group_pair_context.pairs:
            if isinstance(pair, MuonPair) and (pair.forward_group in groups or
                                               pair.backward_group in groups):
                pairs.append(pair)
        return pairs

    def calculate_diff_for(self, run: List[int], diff: MuonDiff):
        self._calculate_diff_for(run, diff, rebin=False)
        if self._do_rebin():
            self._calculate_diff_for(run, diff, rebin=True)

    def _calculate_diff_for(self, run: List[int], diff: MuonDiff, rebin: bool):
        diff_asymmetry_workspace = self.calculate_diff(diff, run, rebin=rebin)

        if diff_asymmetry_workspace is not None:
            self.group_pair_context[diff.name].update_asymmetry_workspace(
                diff_asymmetry_workspace, run, rebin=rebin)

    def find_diffs_containing_groups_or_pairs(self,
                                              groups_and_pairs: list) -> list:
        """Returns a list of MuonDiff's that are formed from one or more groups/pairs contained in the provided list."""
        diffs = []
        for diff in self._group_pair_context.diffs:
            if diff.positive in groups_and_pairs or diff.negative in groups_and_pairs:
                diffs.append(diff)
        return diffs

    def update_phasequads(self):
        for phasequad in self.group_pair_context.phasequads:
            self.calculate_phasequads(phasequad)

    def calculate_phasequads(self, phasequad_obj):
        self._calculate_phasequads(phasequad_obj, rebin=False)
        if self._do_rebin():
            self._calculate_phasequads(phasequad_obj, rebin=True)

    def calculate_phasequad(self, phasequad, run, rebin):
        parameters = {}
        parameters['PhaseTable'] = phasequad.phase_table
        run_string = run_list_to_string(run)

        ws_name = get_pair_phasequad_name(self,
                                          add_phasequad_extensions(
                                              phasequad.name),
                                          run_string,
                                          rebin=rebin)

        parameters['InputWorkspace'] = self._run_deadtime(run_string, ws_name)
        runs = self._data_context.current_runs
        if runs:
            parameters['InputWorkspace'] = run_crop_workspace(
                parameters['InputWorkspace'], self.first_good_data(runs[0]),
                self.last_good_data(runs[0]))

        phase_quad = run_PhaseQuad(parameters, ws_name)
        phase_quad = self._run_rebin(phase_quad, rebin)

        workspaces = split_phasequad(phase_quad)
        return workspaces

    def _calculate_phasequads(self, phasequad_obj, rebin):
        for run in self._data_context.current_runs:
            if self._data_context.num_periods(run) > 1:
                raise ValueError("Cannot support multiple periods")

            ws_list = self.calculate_phasequad(phasequad_obj, run, rebin)
            run_string = run_list_to_string(run)
            directory = get_base_data_directory(self, run_string)
            for ws in ws_list:
                muon_workspace_wrapper = MuonWorkspaceWrapper(directory + ws)
                muon_workspace_wrapper.show()

            phasequad_obj.update_asymmetry_workspaces(ws_list,
                                                      run,
                                                      rebin=rebin)

    def _run_deadtime(self, run_string, output):
        name = get_raw_data_workspace_name(
            self.data_context.instrument,
            run_string,
            multi_period=False,
            workspace_suffix=self.workspace_suffix)
        if isinstance(run_string, str):
            run = wsName.get_first_run_from_run_string(run_string)
        dead_time_table = self._corrections_context.current_dead_time_table_name_for_run(
            self.data_context.instrument, [float(run)])
        if dead_time_table:
            return apply_deadtime(name, output, dead_time_table)
        return name

    def _run_rebin(self, name, rebin):
        if rebin:
            params = "1"
            if self.gui_context['RebinType'] == 'Variable' and self.gui_context[
                    "RebinVariable"]:
                params = self.gui_context["RebinVariable"]

            if self.gui_context['RebinType'] == 'Fixed' and self.gui_context[
                    "RebinFixed"]:
                ws = retrieve_ws(name)
                x_data = ws.dataX(0)
                original_step = x_data[1] - x_data[0]
                params = float(self.gui_context["RebinFixed"]) * original_step
            return rebin_ws(name, params)
        else:
            return name

    def update_current_data(self):
        # Update the current data; resetting the groups and pairs to their
        # default values
        if len(self.data_context.current_runs) > 0:
            self.data_context.update_current_data()

            if not self.group_pair_context.groups:
                maximum_number_of_periods = max(
                    [self.num_periods(run) for run in self.current_runs])
                self.group_pair_context.reset_group_and_pairs_to_default(
                    self.data_context.current_workspace,
                    self.data_context.instrument,
                    self.data_context.main_field_direction,
                    maximum_number_of_periods)
        else:
            self.data_context.clear()

    def show_raw_data(self):
        self.ads_observer.observeRename(False)
        for run in self.data_context.current_runs:
            with WorkspaceGroupDefinition():
                run_string = run_list_to_string(run)
                loaded_workspace = self.data_context._loaded_data.get_data(
                    run=run, instrument=self.data_context.instrument
                )['workspace']['OutputWorkspace']
                loaded_workspace_deadtime_table = self.corrections_context.get_default_dead_time_table_name_for_run(
                    self.data_context.instrument, run)
                directory = get_base_data_directory(self, run_string)

                deadtime_name = get_deadtime_data_workspace_name(
                    self.data_context.instrument,
                    str(run[0]),
                    workspace_suffix=self.workspace_suffix)
                MuonWorkspaceWrapper(loaded_workspace_deadtime_table).show(
                    directory + deadtime_name)
                self.corrections_context.set_default_dead_time_table_name_for_run(
                    self.data_context.instrument, run, deadtime_name)

                if len(loaded_workspace) > 1:
                    # Multi-period data
                    for i, single_ws in enumerate(loaded_workspace):
                        name = directory + get_raw_data_workspace_name(
                            self.data_context.instrument,
                            run_string,
                            multi_period=True,
                            period=str(i + 1),
                            workspace_suffix=self.workspace_suffix)
                        single_ws.show(name)
                else:
                    # Single period data
                    name = directory + get_raw_data_workspace_name(
                        self.data_context.instrument,
                        run_string,
                        multi_period=False,
                        workspace_suffix=self.workspace_suffix)
                    loaded_workspace[0].show(name)

        self.ads_observer.observeRename(True)

    def _do_rebin(self):
        return (self.gui_context['RebinType'] == 'Fixed'
                and 'RebinFixed' in self.gui_context and self.gui_context['RebinFixed']) or \
               (self.gui_context['RebinType'] == 'Variable'
                and 'RebinVariable' in self.gui_context and self.gui_context['RebinVariable'])

    def do_double_pulse_fit(self):
        return "DoublePulseEnabled" in self.gui_context and self.gui_context[
            "DoublePulseEnabled"]

    def get_detectors_excluded_from_default_grouping_tables(self):
        groups, _, _ = get_default_grouping(
            self.data_context.current_workspace, self.data_context.instrument,
            self.data_context.main_field_direction)
        detectors_in_group = []
        for group in groups:
            detectors_in_group += group.detectors
        detectors_in_group = set(detectors_in_group)

        return [
            det for det in range(1, self.data_context.num_detectors)
            if det not in detectors_in_group
        ]

    # Get the groups/pairs for active WS
    def getGroupedWorkspaceNames(self):
        run_numbers = self.data_context.current_runs
        runs = [
            wsName.get_raw_data_workspace_name(
                self.data_context.instrument,
                run_list_to_string(run_number),
                self.data_context.is_multi_period(),
                period=str(period + 1),
                workspace_suffix=self.workspace_suffix)
            for run_number in run_numbers
            for period in range(self.data_context.num_periods(run_number))
        ]
        return runs

    def first_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['FirstGoodDataFromFile']:
            return self.data_context.get_loaded_data_for_run(
                run)["FirstGoodData"]
        else:
            if 'FirstGoodData' in self.gui_context:
                return self.gui_context['FirstGoodData']
            else:
                self.gui_context[
                    'FirstGoodData'] = self.data_context.get_loaded_data_for_run(
                        run)["FirstGoodData"]
                return self.gui_context['FirstGoodData']

    def last_good_data(self, run):
        if not self.data_context.get_loaded_data_for_run(run):
            return 0.0

        if self.gui_context['LastGoodDataFromFile']:
            return round(
                max(
                    self.data_context.get_loaded_data_for_run(run)
                    ["OutputWorkspace"][0].workspace.dataX(0)), 2)
        else:
            if 'LastGoodData' in self.gui_context:
                return self.gui_context['LastGoodData']
            else:
                self.gui_context['LastGoodData'] = round(
                    max(
                        self.data_context.get_loaded_data_for_run(run)
                        ["OutputWorkspace"][0].workspace.dataX(0)), 2)
                return self.gui_context['LastGoodData']

    def get_group_and_pair(self, group_and_pair):
        if group_and_pair == 'All':
            group = self.group_pair_context.group_names
            pair = self.group_pair_context.pair_names
            group += self.group_pair_context.get_diffs("group")
            pair += self.group_pair_context.get_diffs("pair")
        else:
            group_pair_list = group_and_pair.replace(' ', '').split(',')
            group = [
                group for group in group_pair_list
                if group in self.group_pair_context.group_names
            ]
            # add group diffs
            diffs = [
                diff.name
                for diff in self.group_pair_context.get_diffs("group")
            ]
            group += [diff for diff in group_pair_list if diff in diffs]
            pair = [
                pair for pair in group_pair_list
                if pair in self.group_pair_context.pair_names
            ]
            diffs = [
                diff.name for diff in self.group_pair_context.get_diffs("pair")
            ]
            pair += [diff for diff in group_pair_list if diff in diffs]
        return group, pair

    def get_runs(self, runs):
        if runs == 'All':
            run_list = self.data_context.current_runs
        else:
            run_list = [
                run_string_to_list(item)
                for item in runs.replace(' ', '').split(',')
            ]
            flat_list = []
            for sublist in run_list:
                flat_list += [[run] for run in sublist if len(sublist) > 1]
            run_list += flat_list
            run_list = [
                run for run in run_list
                if run in self.data_context.current_runs
            ]
        return run_list

    def get_list_of_binned_or_unbinned_workspaces_from_equivalents(
            self, input_list):
        equivalent_list = []

        for item in input_list:
            if 'PhaseQuad' in item:
                equivalent_list.append(item)

            equivalent_group_pair = self.group_pair_context.get_equivalent_group_pair(
                item)
            if equivalent_group_pair:
                equivalent_list.append(equivalent_group_pair)

        return equivalent_list

    def get_workspace_names_for(self, runs: str, groups_and_pairs: list,
                                fit_to_raw: bool) -> list:
        """Returns the workspace names of the loaded data for the provided runs and groups/pairs."""
        workspace_names = []
        for run in self.get_runs(runs):
            for group_and_pair in groups_and_pairs:
                workspace_names += self.get_workspace_names_of_data_with_run(
                    run, group_and_pair, fit_to_raw)

        return workspace_names

    def get_workspace_names_of_data_with_run(self, run: int,
                                             group_and_pair: str,
                                             fit_to_raw: bool):
        """Returns the workspace names of the loaded data with the provided run and group/pair."""
        group, pair = self.get_group_and_pair(group_and_pair)

        group_names = self.group_pair_context.get_group_workspace_names(
            [run], group, not fit_to_raw)
        pair_names = self.group_pair_context.get_pair_workspace_names(
            [run], pair, not fit_to_raw)

        return group_names + pair_names

    def remove_workspace(self, workspace):
        # required as the renameHandler returns a name instead of a workspace.
        if isinstance(workspace, str):
            workspace_name = workspace
        else:
            workspace_name = workspace.name()

        self.data_context.remove_workspace_by_name(workspace_name)
        self.group_pair_context.remove_workspace_by_name(workspace_name)
        self.phase_context.remove_workspace_by_name(workspace_name)
        self.fitting_context.remove_workspace_by_name(workspace_name)
        self.results_context.remove_workspace_by_name(workspace_name)
        self.update_view_from_model_notifier.notify_subscribers(workspace_name)
        self.deleted_plots_notifier.notify_subscribers(workspace)

    def clear_context(self):
        self.data_context.clear()
        self.group_pair_context.clear()
        self.phase_context.clear()
        self.fitting_context.clear()
        self.update_view_from_model_notifier.notify_subscribers()

    def workspace_replaced(self, workspace):
        self.update_plots_notifier.notify_subscribers(workspace)