def notify_subscribers(self, *args, **kwargs): Observable.notify_subscribers(self, *args, **kwargs)
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()
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
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
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)
def notify_subscribers(self, arg=["", "", ""]): Observable.notify_subscribers(self, arg)
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)
def notify_subscribers(self, arg=None): Observable.notify_subscribers(self, arg)
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)
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)
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)
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()