def start_calibration_worker(self, sample_path, plot_output, rb_num, bank=None, calfile=None, spectrum_numbers=None): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param sample_path: Path to sample data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. :param bank: Optional parameter to crop by bank. :param calfile: Custom calibration file the user can supply for the calibration region of interest. :param spectrum_numbers: Optional parameter to crop by spectrum number. """ self.worker = AsyncTask(self.model.create_new_calibration, (sample_path,), { "plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num, "bank": bank, "calfile": calfile, "spectrum_numbers": spectrum_numbers }, error_cb=self._on_error, success_cb=self._on_success) self.pending_calibration.set_calibration(sample_path, self.instrument) self.pending_calibration.set_roi_info(bank, calfile, spectrum_numbers) self.set_calibrate_controls_enabled(False) self.worker.start()
def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num, bank=None, spectrum_numbers=None): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param vanadium_path: Path to vanadium data file. :param sample_path: Path to sample data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. :param bank: Optional parameter to crop by bank. :param spectrum_numbers: Optional parameter to crop by spectrum number. """ self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path), { "plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num, "bank": bank, "spectrum_numbers": spectrum_numbers }, error_cb=self._on_error, success_cb=self._on_success) self.pending_calibration.set_calibration(vanadium_path, sample_path, self.instrument) self.set_calibrate_controls_enabled(False) self.worker.start()
def execute_async(self, code_str, line_offset, filename=None, blocking=False): """ Execute the given code string on a separate thread. This function returns as soon as the new thread starts :param code_str: A string containing code to execute :param line_offset: See PythonCodeExecution.execute() :param filename: See PythonCodeExecution.execute() :param blocking: If True the call will block until the task is finished :returns: The created async task, only returns task if the blocking is False """ # Stack is chopped on error to avoid the AsyncTask.run->self.execute calls appearing # as these are not useful for the user in this context if not blocking: task = AsyncTask(self.execute, args=(code_str, filename, line_offset), success_cb=self._on_success, error_cb=self._on_error) task.start() self._task = task return task else: self._task = BlockingAsyncTaskWithCallback( self.execute, args=(code_str, filename, line_offset), success_cb=self._on_success, error_cb=self._on_error, blocking_cb=QApplication.processEvents) return self._task.start()
def add_simpleapi_to_completions_if_required(self): """ If the simpleapi functions haven't been added to the completions, start a separate thread to load them in. """ if not self.simpleapi_in_completions and "from mantid.simpleapi import *" in self.editor.text(): self.simpleapi_in_completions = True self.worker = AsyncTask(self._add_simpleapi_to_completions_if_required) self.worker.start()
def start_calibration_worker(self, plot_output): """ Calibrate the data in a separate thread so as to not freeze the GUI. """ self.worker = AsyncTask(self.model.create_new_calibration, (self.current_calibration, self.rb_num, plot_output), error_cb=self._on_error, success_cb=self._on_success) self.set_calibrate_controls_enabled(False) self.worker.start()
def _start_load_worker(self, filenames): """ Load one to many files into mantid that are tracked by the interface. :param filenames: Comma separated list of filenames to load """ self.worker = AsyncTask(self.model.load_files, (filenames, ), error_cb=self._on_worker_error, finished_cb=self._emit_enable_button_signal, success_cb=self._on_worker_success) self.worker.start()
def start_calibration_worker(self, vanadium_path, calib_path, plot_output, rb_num): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param vanadium_path: Path to vanadium data file. :param calib_path: Path to calibration data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. """ self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, calib_path), {"plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num}, error_cb=self._on_error, finished_cb=self.enable_calibrate_buttons) self.disable_calibrate_buttons() self.worker.start()
def test_successful_no_arg_operation_calls_success_and_finished_callback(self): def foo(): return 42 recv = AsyncTaskTest.Receiver() t = AsyncTask(foo, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertTrue(recv.success_cb_called) self.assertFalse(recv.error_cb_called) self.assertEqual(42, recv.task_output)
def start_focus_worker(self, focus_paths: list, van_path: str, plot_output: bool, rb_num: str, regions_dict: dict) -> None: """ Focus data in a separate thread to stop the main GUI from hanging. :param focus_paths: List of paths to the files containing the data to focus. :param plot_output: True if the output should be plotted. :param rb_num: The RB Number from the main window (often an experiment id) :param regions_dict: Dictionary containing the regions to focus over, mapping region_name -> grouping_ws_name """ self.worker = AsyncTask(self.model.focus_run, (focus_paths, van_path, plot_output, self.instrument, rb_num, regions_dict), error_cb=self._on_worker_error, finished_cb=self._on_worker_success) self.set_focus_controls_enabled(False) self.worker.start()
def start_focus_worker(self, focus_paths, banks, plot_output, rb_num, spectrum_numbers=None): """ Focus data in a separate thread to stop the main GUI from hanging. :param focus_paths: List of paths to the files containing the data to focus. :param banks: A list of banks that are to be focused. :param plot_output: True if the output should be plotted. :param rb_num: The RB Number from the main window (often an experiment id) :param spectrum_numbers: Optional parameter to crop to a specific list of spectrum numbers. """ self.worker = AsyncTask(self.model.focus_run, (focus_paths, banks, plot_output, self.instrument, rb_num, spectrum_numbers), error_cb=self._on_worker_error, finished_cb=self.emit_enable_button_signal) self.set_focus_controls_enabled(False) self.worker.start()
def test_unsuccessful_args_and_kwargs_operation_calls_error_and_finished_callback(self): def foo(scale, shift): raise RuntimeError("Bad operation") recv = AsyncTaskTest.Receiver() scale, shift = 2, 4 t = AsyncTask(foo, args = (scale,), kwargs={'shift': shift}, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertFalse(recv.success_cb_called) self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError))
def test_successful_args_and_kwargs_operation_calls_success_and_finished_callback(self): def foo(scale, shift): return scale*42 + shift recv = AsyncTaskTest.Receiver() scale, shift = 2, 4 t = AsyncTask(foo, args = (scale,), kwargs={'shift': shift}, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertTrue(recv.success_cb_called) self.assertFalse(recv.error_cb_called) self.assertEqual(scale*42 + shift, recv.task_output)
def execute_async(self, code_str, filename=''): """ Execute the given code string on a separate thread. This function returns as soon as the new thread starts :param code_str: A string containing code to execute :param filename: See PythonCodeExecution.execute() :returns: The created async task """ # Stack is chopped on error to avoid the AsyncTask.run->self.execute calls appearing # as these are not useful for the user in this context task = AsyncTask(self.execute, args=(code_str, filename), success_cb=self._on_success, error_cb=self._on_error) task.start() self._task = task return task
def test_unsuccessful_operation_with_error_cb(self): def foo(scale, shift): def bar(): raise RuntimeError("Bad operation") bar() recv = AsyncTaskTest.Receiver() scale, shift = 2, 4 t = AsyncTask(foo, args = (scale,), kwargs={'shift': shift}, error_cb=recv.on_error) t.start() t.join() self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError)) self.assertEqual(3, len(recv.task_exc_stack)) self.assertEqual(135, recv.task_exc_stack[1][1]) self.assertEqual(134, recv.task_exc_stack[2][1])
def execute_async(self, code_str, filename=None): """ Execute the given code string on a separate thread. This function returns as soon as the new thread starts :param code_str: A string containing code to execute :param filename: See PythonCodeExecution.execute() :returns: The created async task """ # Stack is chopped on error to avoid the AsyncTask.run->self.execute calls appearing # as these are not useful for the user in this context task = AsyncTask(self.execute, args=(code_str, filename), success_cb=self._on_success, error_cb=self._on_error) task.start() self._task = task return task
def __init__(self, configurations): self.jobs = [] for config in configurations: print("CONFIG:", config) self.jobs.append( AsyncTask(TotalScatteringReduction, args=(config, ), success_cb=self.on_success, error_cb=self.on_error, finished_cb=self.on_finished))
def test_unsuccessful_no_arg_operation_calls_error_and_finished_callback( self): def foo(): # this is a bad operation # that should appear in the stack trace raise RuntimeError("Bad operation") recv = AsyncTaskTest.Receiver() t = AsyncTask(foo, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertFalse(recv.success_cb_called) self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError), msg="Expected RuntimeError, found " + recv.task_exc.__class__.__name__) self.assertEqual(2, len(recv.task_exc_stack)) # line number of self.target in asynchronous.py self.assertEqual(91, recv.task_exc_stack[0][1]) # line number of raise statement above self.assertEqual(95, recv.task_exc_stack[1][1])
def test_unsuccessful_no_arg_operation_calls_error_and_finished_callback(self): def foo(): # this is a bad operation # that should appear in the stack trace raise RuntimeError("Bad operation") recv = AsyncTaskTest.Receiver() t = AsyncTask(foo, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertFalse(recv.success_cb_called) self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError), msg="Expected RuntimeError, found " + recv.task_exc.__class__.__name__) self.assertEqual(2, len(recv.task_exc_stack)) # line number of self.target in asynchronous.py self.assertEqual(91, recv.task_exc_stack[0][1]) # line number of raise statement above self.assertEqual(95, recv.task_exc_stack[1][1])
def execute_async(self, code_str, filename=None, blocking=False): """ Execute the given code string on a separate thread. This function returns as soon as the new thread starts :param code_str: A string containing code to execute :param filename: See PythonCodeExecution.execute() :param blocking: If True the call will block until the task is finished :returns: The created async task, only returns task if the blocking is False """ # Stack is chopped on error to avoid the AsyncTask.run->self.execute calls appearing # as these are not useful for the user in this context if not blocking: task = AsyncTask(self.execute, args=(code_str, filename), success_cb=self._on_success, error_cb=self._on_error) task.start() self._task = task return task else: self._task = BlockingAsyncTaskWithCallback(self.execute, args=(code_str, filename), success_cb=self._on_success, error_cb=self._on_error, blocking_cb=QApplication.processEvents) return self._task.start()
def test_correct_exception_is_raised_when_called_on_other_thread(self): self.exc = None self.exit_code = None def collect_error(e): self.exc = e thread = AsyncTask(self.task, error_cb=collect_error) thread.start() while thread.is_alive(): QApplication.processEvents() thread.join(0.5) self.assertTrue(isinstance(self.exc.exc_value, CustomException)) self.assertEqual(TaskExitCode.ERROR, thread.exit_code)
def test_successful_no_arg_operation_calls_success_and_finished_callback( self): def foo(): return 42 recv = AsyncTaskTest.Receiver() t = AsyncTask(foo, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertTrue(recv.success_cb_called) self.assertFalse(recv.error_cb_called) self.assertEqual(42, recv.task_output)
def test_successful_positional_args_operation_calls_success_and_finished_callback( self): def foo(shift): return 42 + shift recv = AsyncTaskTest.Receiver() shift = 2 t = AsyncTask(foo, args=(shift, ), success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertTrue(recv.success_cb_called) self.assertFalse(recv.error_cb_called) self.assertEqual(42 + shift, recv.task_output)
def test_unsuccessful_args_and_kwargs_operation_calls_error_and_finished_callback( self): def foo(scale, shift): raise RuntimeError("Bad operation") recv = AsyncTaskTest.Receiver() scale, shift = 2, 4 t = AsyncTask(foo, args=(scale, ), kwargs={'shift': shift}, success_cb=recv.on_success, error_cb=recv.on_error, finished_cb=recv.on_finished) t.start() t.join() self.assertTrue(recv.finished_cb_called) self.assertFalse(recv.success_cb_called) self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError))
def test_unsuccessful_operation_with_error_cb(self): def foo(scale, shift): def bar(): raise RuntimeError("Bad operation") bar() recv = AsyncTaskTest.Receiver() scale, shift = 2, 4 t = AsyncTask(foo, args=(scale, ), kwargs={'shift': shift}, error_cb=recv.on_error) t.start() t.join() self.assertTrue(recv.error_cb_called) self.assertTrue(isinstance(recv.task_exc, RuntimeError)) self.assertEqual(3, len(recv.task_exc_stack)) self.assertEqual(135, recv.task_exc_stack[1][1]) self.assertEqual(134, recv.task_exc_stack[2][1])
class FocusPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.calibration_observer = CalibrationObserver(self) # Observable Setup self.focus_run_notifier = GenericObservable() # Connect view signals to local methods. self.view.set_on_focus_clicked(self.on_focus_clicked) self.view.set_enable_controls_connection( self.set_focus_controls_enabled) # Variables from other GUI tabs. self.current_calibration = CalibrationInfo() self.instrument = "ENGINX" self.rb_num = None last_van_path = get_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_vanadium_run") if last_van_path: self.view.set_van_file_text_with_search(last_van_path) def add_focus_subscriber(self, obs): self.focus_run_notifier.add_subscriber(obs) def on_focus_clicked(self): if not self._validate(): return regions_dict = self.current_calibration.create_focus_roi_dictionary() focus_paths = self.view.get_focus_filenames() van_path = self.view.get_vanadium_filename() if self._number_of_files_warning(focus_paths): self.start_focus_worker(focus_paths, van_path, self.view.get_plot_output(), self.rb_num, regions_dict) van_run = self.view.get_vanadium_run() set_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_vanadium_run", van_run) def start_focus_worker(self, focus_paths: list, van_path: str, plot_output: bool, rb_num: str, regions_dict: dict) -> None: """ Focus data in a separate thread to stop the main GUI from hanging. :param focus_paths: List of paths to the files containing the data to focus. :param plot_output: True if the output should be plotted. :param rb_num: The RB Number from the main window (often an experiment id) :param regions_dict: Dictionary containing the regions to focus over, mapping region_name -> grouping_ws_name """ self.worker = AsyncTask(self.model.focus_run, (focus_paths, van_path, plot_output, self.instrument, rb_num, regions_dict), error_cb=self._on_worker_error, finished_cb=self._on_worker_success) self.set_focus_controls_enabled(False) self.worker.start() def _on_worker_success(self): self.emit_enable_button_signal() self.focus_run_notifier.notify_subscribers( self.model.get_last_focused_files()) def set_instrument_override(self, instrument): instrument = INSTRUMENT_DICT[instrument] self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_num(self, rb_num): self.rb_num = rb_num def _validate(self): """ Ensure that the worker is ready to be started. :return: True if the worker can be started safely. """ if self.view.is_searching(): create_error_message( self.view, "Mantid is searching for data files. Please wait.") return False if not self.view.get_focus_valid(): create_error_message(self.view, "Check run numbers/path is valid.") return False if not self.view.get_vanadium_valid(): create_error_message(self.view, "Check vanadium run number/path is valid.") return False if not self.current_calibration.is_valid(): create_error_message( self.view, "Create or Load a calibration via the Calibration tab before focusing." ) return False if self.current_calibration.get_instrument() != self.instrument: create_error_message( self.view, "Please make sure the selected instrument matches instrument for the current calibration.\n" "The instrument for the current calibration is: " + self.current_calibration.get_instrument()) return False return True def _number_of_files_warning(self, paths): if len( paths ) > 10: # Just a guess on the warning for now. May change in future. response = QMessageBox.warning( self.view, 'Engineering Diffraction - Warning', 'You are attempting to focus {} workspaces. This may take some time.\n\n Would you like to continue?' .format(len(paths)), QMessageBox.Ok | QMessageBox.Cancel) return response == QMessageBox.Ok else: return True def _on_worker_error(self, error_info): logger.error(str(error_info)) self.emit_enable_button_signal() def set_focus_controls_enabled(self, enabled): self.view.set_focus_button_enabled(enabled) self.view.set_plot_output_enabled(enabled) def emit_enable_button_signal(self): self.view.sig_enable_controls.emit(True) def update_calibration(self, calibration): """ Update the current calibration following an call from a CalibrationNotifier :param calibration: The new current calibration. """ self.current_calibration = calibration region_text = calibration.get_roi_text() self.view.set_region_display_text(region_text)
class FittingDataPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.row_numbers = TwoWayRowDict( ) # {ws_name: table_row} and {table_row: ws_name} self.plotted = set() # List of plotted workspace names # Connect view signals to local methods self.view.set_on_load_clicked(self.on_load_clicked) self.view.set_enable_button_connection(self._enable_load_button) self.view.set_on_remove_selected_clicked( self._remove_selected_tracked_workspaces) self.view.set_on_remove_all_clicked( self._remove_all_tracked_workspaces) self.view.set_on_table_cell_changed(self._handle_table_cell_changed) # Observable Setup self.plot_added_notifier = GenericObservable() self.plot_removed_notifier = GenericObservable() self.all_plots_removed_notifier = GenericObservable() def on_load_clicked(self): if self._validate(): filenames = self._get_filenames() self._start_load_worker(filenames) def remove_workspace(self, ws_name): if ws_name in self.get_loaded_workspaces(): removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self._repopulate_table() def rename_workspace(self, old_name, new_name): loaded_workspaces = self.get_loaded_workspaces() if old_name in loaded_workspaces: ws = loaded_workspaces.pop(old_name) loaded_workspaces[new_name] = ws if old_name in self.plotted: self.plotted.remove(old_name) self.plotted.add(new_name) self._repopulate_table() def clear_workspaces(self): self.get_loaded_workspaces().clear() self.plotted.clear() self.row_numbers.clear() self._repopulate_table() def replace_workspace(self, name, workspace): if name in self.get_loaded_workspaces(): self.get_loaded_workspaces()[name] = workspace if name in self.plotted: self.all_plots_removed_notifier.notify_subscribers() self._repopulate_table() def get_loaded_workspaces(self): return self.model.get_loaded_workspaces() def _start_load_worker(self, filenames): """ Load one to many files into mantid that are tracked by the interface. :param filenames: Comma separated list of filenames to load """ self.worker = AsyncTask(self.model.load_files, (filenames, ), error_cb=self._on_worker_error, finished_cb=self._emit_enable_button_signal, success_cb=self._on_worker_success) self.worker.start() def _on_worker_error(self, _): logger.error("Error occurred when loading files.") self._emit_enable_button_signal() def _on_worker_success(self, _): if self.view.get_add_to_plot(): self.plotted.update(self.model.get_last_added()) self._repopulate_table() def _repopulate_table(self): """ Populate the table with the information from the loaded workspaces. Will also handle any workspaces that need to be plotted. """ self._remove_all_table_rows() self.row_numbers.clear() self.all_plots_removed_notifier.notify_subscribers() workspaces = self.get_loaded_workspaces() for i, name in enumerate(workspaces): try: run_no = self.model.get_sample_log_from_ws(name, "run_number") bank = self.model.get_sample_log_from_ws(name, "bankid") if bank == 0: bank = "cropped" checked = name in self.plotted self._add_row_to_table(name, i, run_no, bank, checked) except RuntimeError: self._add_row_to_table(name, i) self._handle_table_cell_changed(i, 2) def _remove_selected_tracked_workspaces(self): row_numbers = self._remove_selected_table_rows() for row_no in row_numbers: ws_name = self.row_numbers.pop(row_no) removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self._repopulate_table() def _remove_all_tracked_workspaces(self): self.clear_workspaces() self._remove_all_table_rows() def _handle_table_cell_changed(self, row, col): if col == 2 and row in self.row_numbers: # Is from the plot check column ws = self.model.get_loaded_workspaces()[self.row_numbers[row]] ws_name = self.row_numbers[row] if self.view.get_item_checked(row, col): # Plot Box is checked self.plot_added_notifier.notify_subscribers(ws) self.plotted.add(ws_name) else: # Plot box is unchecked self.plot_removed_notifier.notify_subscribers(ws) self.plotted.discard(ws_name) def _enable_load_button(self, enabled): self.view.set_load_button_enabled(enabled) def _emit_enable_button_signal(self): self.view.sig_enable_load_button.emit(True) def _get_filenames(self): return self.view.get_filenames_to_load() def _is_searching(self): return self.view.is_searching() def _files_are_valid(self): return self.view.get_filenames_valid() def _validate(self): if self._is_searching(): create_error_message( self.view, "Mantid is searching for files. Please wait.") return False elif not self._files_are_valid(): create_error_message(self.view, "Entered files are not valid.") return False return True def _add_row_to_table(self, ws_name, row, run_no=None, bank=None, checked=False): words = ws_name.split("_") if run_no is not None and bank is not None: self.view.add_table_row(run_no, bank, checked) self.row_numbers[ws_name] = row elif len(words) == 4 and words[2] == "bank": logger.notice( "No sample logs present, determining information from workspace name." ) self.view.add_table_row(words[1], words[3], checked) self.row_numbers[ws_name] = row else: logger.warning( "The workspace '{}' was not in the correct naming format. Files should be named in the following way: " "INSTRUMENT_RUNNUMBER_bank_BANK. Using workspace name as identifier." .format(ws_name)) self.view.add_table_row(ws_name, "N/A", checked) self.row_numbers[ws_name] = row def _remove_table_row(self, row_no): self.view.remove_table_row(row_no) def _remove_selected_table_rows(self): return self.view.remove_selected() def _remove_all_table_rows(self): self.view.remove_all()
class FittingDataPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.row_numbers = TwoWayRowDict( ) # {ws_name: table_row} and {table_row: ws_name} self.plotted = set() # List of plotted workspace names # Connect view signals to local methods self.view.set_on_load_clicked(self.on_load_clicked) self.view.set_enable_load_button_connection(self._enable_load_button) self.view.set_enable_inspect_bg_button_connection( self._enable_inspect_bg_button) self.view.set_on_remove_selected_clicked( self._remove_selected_tracked_workspaces) self.view.set_on_remove_all_clicked( self._remove_all_tracked_workspaces) self.view.set_on_plotBG_clicked(self._plotBG) self.view.set_on_table_cell_changed(self._handle_table_cell_changed) self.view.set_on_xunit_changed(self._log_xunit_change) self.view.set_table_selection_changed(self._handle_selection_changed) # Observable Setup self.plot_added_notifier = GenericObservable() self.plot_removed_notifier = GenericObservable() self.all_plots_removed_notifier = GenericObservable() def _log_xunit_change(self, xunit): logger.notice( "Subsequent files will be loaded with the x-axis unit:\t{}".format( xunit)) def on_load_clicked(self, xunit): if self._validate(): filenames = self._get_filenames() self._start_load_worker(filenames, xunit) def remove_workspace(self, ws_name): if ws_name in self.get_loaded_workspaces(): removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self._repopulate_table() self.model.repopulate_logs() # so matches new table elif ws_name in self.model.get_log_workspaces_name(): logger.warning( 'Deleting the log workspace may cause unexpected errors.') def rename_workspace(self, old_name, new_name): if old_name in self.get_loaded_workspaces(): self.model.update_workspace_name(old_name, new_name) if old_name in self.plotted: self.plotted.remove(old_name) self.plotted.add(new_name) self._repopulate_table() self.model.repopulate_logs() # so matches new table def clear_workspaces(self): self.get_loaded_workspaces().clear() self.plotted.clear() self.row_numbers.clear() self._repopulate_table() def replace_workspace(self, name, workspace): if name in self.get_loaded_workspaces(): self.get_loaded_workspaces()[name] = workspace if name in self.plotted: self.all_plots_removed_notifier.notify_subscribers() self._repopulate_table() def get_loaded_workspaces(self): return self.model.get_loaded_workspaces() def _start_load_worker(self, filenames, xunit): """ Load one to many files into mantid that are tracked by the interface. :param filenames: Comma separated list of filenames to load """ self.worker = AsyncTask( self.model.load_files, (filenames, xunit), error_cb=self._on_worker_error, finished_cb=self._emit_enable_load_button_signal, success_cb=self._on_worker_success) self.worker.start() def _on_worker_error(self, _): logger.error("Error occurred when loading files.") self._emit_enable_load_button_signal() def _on_worker_success(self, _): if self.view.get_add_to_plot(): self.plotted.update(self.model.get_last_added()) self._repopulate_table() def _repopulate_table(self): """ Populate the table with the information from the loaded workspaces. Will also handle any workspaces that need to be plotted. """ self._remove_all_table_rows() self.row_numbers.clear() self.all_plots_removed_notifier.notify_subscribers() workspaces = self.get_loaded_workspaces() for i, name in enumerate(workspaces): try: run_no = self.model.get_sample_log_from_ws(name, "run_number") bank = self.model.get_sample_log_from_ws(name, "bankid") if bank == 0: bank = "cropped" checked = name in self.plotted if name in self.model.get_bg_params(): self._add_row_to_table(name, i, run_no, bank, checked, *self.model.get_bg_params()[name]) else: self._add_row_to_table(name, i, run_no, bank, checked) except RuntimeError: self._add_row_to_table(name, i) self._handle_table_cell_changed(i, 2) def _remove_selected_tracked_workspaces(self): row_numbers = self._remove_selected_table_rows() self.model.remove_log_rows(row_numbers) for row_no in row_numbers: ws_name = self.row_numbers.pop(row_no) removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self._repopulate_table() self.model.repopulate_logs() def _remove_all_tracked_workspaces(self): self.clear_workspaces() self.model.clear_logs() self._remove_all_table_rows() def _plotBG(self): # make external figure row_numbers = self.view.get_selected_rows() for row in row_numbers: if self.view.get_item_checked(row, 3): # background has been subtracted from workspace ws_name = self.row_numbers[row] self.model.plot_background_figure(ws_name) def _handle_table_cell_changed(self, row, col): if row in self.row_numbers: ws_name = self.row_numbers[row] if col == 2: # Plot check box ws = self.model.get_loaded_workspaces()[ws_name] if self.view.get_item_checked(row, col): # Plot Box is checked self.plot_added_notifier.notify_subscribers(ws) self.plotted.add(ws_name) else: # Plot box is unchecked self.plot_removed_notifier.notify_subscribers(ws) self.plotted.discard(ws_name) elif col == 3: # subtract bg if self.view.get_item_checked(row, col): # subtract bg box checked bg_params = self.view.read_bg_params_from_table(row) self.model.do_background_subtraction(ws_name, bg_params) elif self.model.get_background_workspaces()[ws_name]: # box unchecked and bg exists: self.model.undo_background_subtraction(ws_name) elif col > 3: if self.view.get_item_checked(row, 3): # bg params changed - revaluate background bg_params = self.view.read_bg_params_from_table(row) self.model.do_background_subtraction(ws_name, bg_params) def _handle_selection_changed(self): rows = self.view.get_selected_rows() enabled = False for row in rows: if self.view.get_item_checked(row, 3): enabled = True self._enable_inspect_bg_button(enabled) def _enable_load_button(self, enabled): self.view.set_load_button_enabled(enabled) def _emit_enable_load_button_signal(self): self.view.sig_enable_load_button.emit(True) def _enable_inspect_bg_button(self, enabled): self.view.set_inspect_bg_button_enabled(enabled) def _get_filenames(self): return self.view.get_filenames_to_load() def _is_searching(self): return self.view.is_searching() def _files_are_valid(self): return self.view.get_filenames_valid() def _validate(self): if self._is_searching(): create_error_message( self.view, "Mantid is searching for files. Please wait.") return False elif not self._files_are_valid(): create_error_message(self.view, "Entered files are not valid.") return False return True def _add_row_to_table(self, ws_name, row, run_no=None, bank=None, checked=False, bgsub=False, niter=100, xwindow=None, SG=True): words = ws_name.split("_") # find xwindow from ws xunit if not specified if not xwindow: ws = self.model.get_loaded_workspaces()[ws_name] if ws.getAxis(0).getUnit().unitID() == "TOF": xwindow = 1000 else: xwindow = 0.05 if run_no is not None and bank is not None: self.view.add_table_row(run_no, bank, checked, bgsub, niter, xwindow, SG) self.row_numbers[ws_name] = row elif len(words) == 4 and words[2] == "bank": logger.notice( "No sample logs present, determining information from workspace name." ) self.view.add_table_row(words[1], words[3], checked, bgsub, niter, xwindow, SG) self.row_numbers[ws_name] = row else: logger.warning( "The workspace '{}' was not in the correct naming format. Files should be named in the following way: " "INSTRUMENT_RUNNUMBER_bank_BANK. Using workspace name as identifier." .format(ws_name)) self.view.add_table_row(ws_name, "N/A", checked, bgsub, niter, xwindow, SG) self.row_numbers[ws_name] = row def _remove_table_row(self, row_no): self.view.remove_table_row(row_no) def _remove_selected_table_rows(self): return self.view.remove_selected() def _remove_all_table_rows(self): self.view.remove_all()
class CalibrationPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.calibration_notifier = self.CalibrationNotifier(self) self.current_calibration = CalibrationInfo() self.pending_calibration = CalibrationInfo() self.connect_view_signals() # Main Window State Variables self.instrument = "ENGINX" self.rb_num = None # Cropping Options self.cropping_widget = CroppingPresenter(parent=self.view, view=self.view.get_cropping_widget()) self.show_cropping(False) def connect_view_signals(self): self.view.set_on_calibrate_clicked(self.on_calibrate_clicked) self.view.set_enable_controls_connection(self.set_calibrate_controls_enabled) self.view.set_update_field_connection(self.set_field_value) self.view.set_on_radio_new_toggled(self.set_create_new_enabled) self.view.set_on_radio_existing_toggled(self.set_load_existing_enabled) self.view.set_on_check_cropping_state_changed(self.show_cropping) def on_calibrate_clicked(self): plot_output = self.view.get_plot_output() if self.view.get_new_checked() and self._validate(): sample_file = self.view.get_sample_filename() if self.view.get_crop_checked(): self.start_cropped_calibration_worker(sample_file, plot_output, self.rb_num) else: self.start_calibration_worker(sample_file,plot_output, self.rb_num) elif self.view.get_load_checked(): if not self.validate_path(): logger.notice("Invalid calibration path") return filename = self.view.get_path_filename() try: instrument, sample_file, grp_ws_name, roi_text, banks = \ self.model.load_existing_calibration_files(filename) except: return self.pending_calibration.set_calibration(sample_file, instrument) self.pending_calibration.set_roi_info_load(banks, grp_ws_name, roi_text) self.set_current_calibration() set_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_calibration_path", filename) def start_calibration_worker(self, sample_path, plot_output, rb_num, bank=None, calfile=None, spectrum_numbers=None): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param sample_path: Path to sample data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. :param bank: Optional parameter to crop by bank. :param calfile: Custom calibration file the user can supply for the calibration region of interest. :param spectrum_numbers: Optional parameter to crop by spectrum number. """ self.worker = AsyncTask(self.model.create_new_calibration, (sample_path,), { "plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num, "bank": bank, "calfile": calfile, "spectrum_numbers": spectrum_numbers }, error_cb=self._on_error, success_cb=self._on_success) self.pending_calibration.set_calibration(sample_path, self.instrument) self.pending_calibration.set_roi_info(bank, calfile, spectrum_numbers) self.set_calibrate_controls_enabled(False) self.worker.start() def start_cropped_calibration_worker(self, sample_path, plot_output, rb_num): if self.cropping_widget.get_custom_calfile_enabled(): calfile = self.cropping_widget.get_custom_calfile() self.start_calibration_worker(sample_path, plot_output, rb_num, calfile=calfile) elif self.cropping_widget.get_custom_spectra_enabled(): spec_nums = self.cropping_widget.get_custom_spectra() self.start_calibration_worker(sample_path, plot_output, rb_num, spectrum_numbers=spec_nums) else: bank = str(self.cropping_widget.get_bank()) self.start_calibration_worker(sample_path, plot_output, rb_num, bank=bank) def set_current_calibration(self, success_info=None): if success_info: logger.information("Thread executed in " + str(success_info.elapsed_time) + " seconds.") self.current_calibration = deepcopy(self.pending_calibration) self.calibration_notifier.notify_subscribers(self.current_calibration) self.pending_calibration.clear() def load_last_calibration(self) -> None: """ Loads the most recently created or loaded calibration into the interface instance. To be used on interface startup. """ last_cal_path = get_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_calibration_path") if last_cal_path: self.view.set_load_checked(True) self.view.set_file_text_with_search(last_cal_path) def set_field_value(self): self.view.set_sample_text(self.current_calibration.get_sample()) def set_instrument_override(self, instrument): instrument = INSTRUMENT_DICT[instrument] self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_num(self, rb_num): self.rb_num = rb_num def _validate(self): # Do nothing if run numbers are invalid or view is searching. if self.view.is_searching(): create_error_message(self.view, "Mantid is searching for data files. Please wait.") return False if not self.view.get_sample_valid(): create_error_message(self.view, "Check run numbers/path is valid.") return False if self.view.get_crop_checked(): if self.cropping_widget.get_custom_calfile_enabled() and not self.cropping_widget.is_calfile_valid(): create_error_message(self.view, "Check custom calfile path is valid.") return False if self.cropping_widget.get_custom_spectra_enabled() and not self.cropping_widget.is_spectra_valid(): create_error_message(self.view, "Check custom spectra are valid.") return False return True def validate_path(self): return self.view.get_path_valid() def emit_enable_button_signal(self): self.view.sig_enable_controls.emit(True) def set_calibrate_controls_enabled(self, enabled): self.view.set_calibrate_button_enabled(enabled) def _on_error(self, error_info): logger.error(str(error_info)) self.emit_enable_button_signal() def _on_success(self, success_info): self.set_current_calibration(success_info) self.emit_enable_button_signal() def set_create_new_enabled(self, enabled): self.view.set_sample_enabled(enabled) if enabled: self.set_calibrate_button_text("Calibrate") self.view.set_check_plot_output_enabled(True) self.view.set_check_cropping_enabled(True) self.find_files() def set_load_existing_enabled(self, enabled): self.view.set_path_enabled(enabled) if enabled: self.set_calibrate_button_text("Load") self.view.set_check_plot_output_enabled(False) self.view.set_check_cropping_enabled(False) self.view.set_check_cropping_checked(False) def set_calibrate_button_text(self, text): self.view.set_calibrate_button_text(text) def find_files(self): self.view.find_sample_files() def show_cropping(self, show): self.view.set_cropping_widget_visibility(show) # ----------------------- # Observers / Observables # ----------------------- class CalibrationNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer def notify_subscribers(self, *args, **kwargs): Observable.notify_subscribers(self, *args)
class CodeCompleter(object): """ This class generates autocompletions for Workbench's script editor. It generates autocompletions from environment globals. These completions are updated on every successful script execution. """ def __init__(self, editor, env_globals=None): self.simpleapi_in_completions = False self.editor = editor self.env_globals = env_globals self.worker = None # A dict gives O(1) lookups and ensures we have no duplicates self._completions_dict = dict() if re.search("^#{0}import .*numpy( |,|$)", self.editor.text(), re.MULTILINE): self._add_to_completions(self._get_module_call_tips('numpy')) if re.search("^#{0}import .*pyplot( |,|$)", self.editor.text(), re.MULTILINE): with _ignore_matplotlib_deprecation_warnings(): self._add_to_completions(self._get_module_call_tips('matplotlib.pyplot')) self._add_to_completions(python_keywords) self.editor.enableAutoCompletion(CodeEditor.AcsAPIs) self.editor.updateCompletionAPI(self.completions) @property def completions(self): return list(self._completions_dict.keys()) def _get_completions_from_globals(self): return generate_call_tips(self.env_globals, prepend_module_name=True) def _add_to_completions(self, completions): for completion in completions: self._completions_dict[completion] = True def update_completion_api(self): with _ignore_matplotlib_deprecation_warnings(): self._add_to_completions(self._get_completions_from_globals()) self.editor.updateCompletionAPI(self.completions) def add_simpleapi_to_completions_if_required(self): """ If the simpleapi functions haven't been added to the completions, start a separate thread to load them in. """ if not self.simpleapi_in_completions and "from mantid.simpleapi import *" in self.editor.text(): self.simpleapi_in_completions = True self.worker = AsyncTask(self._add_simpleapi_to_completions_if_required) self.worker.start() def _add_simpleapi_to_completions_if_required(self): self._add_to_completions(self._get_module_call_tips('mantid.simpleapi')) self.update_completion_api() def _get_module_call_tips(self, module): """ Get the call tips for a given module. If the module cannot be found in sys.modules return an empty list :param str module: The name of the module :return list: A list of call tips for the module """ try: module = sys.modules[module] except KeyError: return [] module_name = get_module_import_alias(module.__name__, self.editor.text()) return generate_call_tips(module.__dict__, module_name)
class FittingDataPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.iplot = [] self.row_numbers = TwoWayRowDict( ) # {ws_name: table_row} and {table_row: ws_name} self.plotted = set() # List of plotted workspace names # Connect view signals to local methods self.view.set_on_load_clicked(self.on_load_clicked) self.view.set_enable_load_button_connection(self._enable_load_button) self.view.set_enable_inspect_bg_button_connection( self._enable_inspect_bg_button) self.view.set_on_remove_selected_clicked( self._remove_selected_tracked_workspaces) self.view.set_on_remove_all_clicked( self._remove_all_tracked_workspaces) self.view.set_on_plotBG_clicked(self._plotBG) self.view.set_on_seq_fit_clicked(self._start_seq_fit) self.view.set_on_serial_fit_clicked(self._start_serial_fit) self.view.set_on_table_cell_changed(self._handle_table_cell_changed) self.view.set_on_bank_changed(self._update_file_filter) self.view.set_on_xunit_changed(self._update_file_filter) self.view.set_table_selection_changed(self._handle_selection_changed) # Observable Setup self.plot_added_notifier = GenericObservable() self.plot_removed_notifier = GenericObservable() self.all_plots_removed_notifier = GenericObservable() self.fit_all_started_notifier = GenericObservable() # Observers self.fit_observer = GenericObserverWithArgPassing(self.fit_completed) # self.fit_enabled_observer = GenericObserverWithArgPassing( self.set_fit_enabled) self.fit_all_done_observer = GenericObserverWithArgPassing( self.fit_completed) self.focus_run_observer = GenericObserverWithArgPassing( self.view.set_default_files) def set_fit_enabled(self, fit_enabled): self.view.set_fit_buttons_enabled(fit_enabled) def fit_completed(self, fit_props): self.model.update_fit(fit_props) def _start_seq_fit(self): ws_list = self.model.get_ws_sorted_by_primary_log() self.fit_all_started_notifier.notify_subscribers(ws_list, do_sequential=True) def _start_serial_fit(self): ws_list = self.model.get_ws_list() self.fit_all_started_notifier.notify_subscribers(ws_list, do_sequential=False) def _update_file_filter(self, bank, xunit): self.view.update_file_filter(bank, xunit) def on_load_clicked(self): if self._validate(): filenames = self._get_filenames() self._start_load_worker(filenames) def remove_workspace(self, ws_name): if ws_name in self.get_loaded_workspaces(): removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self.model.remove_log_rows([self.row_numbers[ws_name]]) self.model.update_log_workspace_group() self._repopulate_table() elif ws_name in self.model.get_log_workspaces_name(): self.model.update_log_workspace_group() def rename_workspace(self, old_name, new_name): if old_name in self.get_loaded_workspaces(): self.model.update_workspace_name(old_name, new_name) if old_name in self.plotted: self.plotted.remove(old_name) self.plotted.add(new_name) self._repopulate_table() self.model.update_log_workspace_group() # so matches new table def clear_workspaces(self): self.get_loaded_workspaces().clear() self.get_bgsub_workspaces().clear() self.get_bg_params().clear() self.model.set_log_workspaces_none() self.plotted.clear() self.row_numbers.clear() self._repopulate_table() def replace_workspace(self, name, workspace): if name in self.get_loaded_workspaces(): self.get_loaded_workspaces()[name] = workspace if name in self.plotted: self.all_plots_removed_notifier.notify_subscribers() self._repopulate_table() def get_loaded_workspaces(self): return self.model.get_loaded_workspaces() def get_bgsub_workspaces(self): return self.model.get_bgsub_workspaces() def get_bg_params(self): return self.model.get_bg_params() def restore_table( self ): # used when the interface is being restored from a save or crash self._repopulate_table() def _start_load_worker(self, filenames): """ Load one to many files into mantid that are tracked by the interface. :param filenames: Comma separated list of filenames to load """ self.worker = AsyncTask( self.model.load_files, (filenames, ), error_cb=self._on_worker_error, finished_cb=self._emit_enable_load_button_signal, success_cb=self._on_worker_success) self.worker.start() def _on_worker_error(self, _): logger.error("Error occurred when loading files.") self._emit_enable_load_button_signal() def _on_worker_success(self, _): wsnames = self.model.get_last_added() if self.view.get_add_to_plot(): self.plotted.update(wsnames) self._repopulate_table() # subtract background - has to be done post repopulation, can't change default in _add_row_to_table [ self.view.set_item_checkstate(self.row_numbers[wsname], 3, True) for wsname in wsnames ] def _repopulate_table(self): """ Populate the table with the information from the loaded workspaces. Will also handle any workspaces that need to be plotted. """ self._remove_all_table_rows() self.row_numbers.clear() self.all_plots_removed_notifier.notify_subscribers() workspaces = self.get_loaded_workspaces() for i, name in enumerate(workspaces): try: run_no = self.model.get_sample_log_from_ws(name, "run_number") bank = self.model.get_sample_log_from_ws(name, "bankid") if bank == 0: bank = "cropped" checked = name in self.plotted or name + "_bgsub" in self.plotted if name in self.model.get_bg_params(): self._add_row_to_table(name, i, run_no, bank, checked, *self.model.get_bg_params()[name]) else: self._add_row_to_table(name, i, run_no, bank, checked) except RuntimeError: self._add_row_to_table(name, i) self._handle_table_cell_changed(i, 2) def _remove_selected_tracked_workspaces(self): row_numbers = self._remove_selected_table_rows() self.model.remove_log_rows(row_numbers) for row_no in row_numbers: ws_name = self.row_numbers.pop(row_no) removed = self.get_loaded_workspaces().pop(ws_name) self.plot_removed_notifier.notify_subscribers(removed) self.plotted.discard(ws_name) self._repopulate_table() def _remove_all_tracked_workspaces(self): self.clear_workspaces() self.model.clear_logs() self._remove_all_table_rows() def _plotBG(self): # make external figure row_numbers = self.view.get_selected_rows() for row in row_numbers: ws_name = self.row_numbers[row] self.model.plot_background_figure(ws_name) def _handle_table_cell_changed(self, row, col): if row in self.row_numbers: ws_name = self.row_numbers[row] is_plotted = self.view.get_item_checked(row, 2) is_sub = self.view.get_item_checked(row, 3) if col == 2: # Plot check box if is_sub: ws = self.model.get_bgsub_workspaces()[ws_name] ws_name += "_bgsub" else: ws = self.model.get_loaded_workspaces()[ws_name] if self.view.get_item_checked(row, col): # Plot Box is checked self.plot_added_notifier.notify_subscribers(ws) self.plotted.add(ws_name) else: # Plot box is unchecked self.plot_removed_notifier.notify_subscribers(ws) self.plotted.discard(ws_name) elif col == 3: # subtract bg col self.model.update_bgsub_status(ws_name, is_sub) if is_sub or is_plotted: # this ensures the sub ws isn't made on load bg_params = self.view.read_bg_params_from_table(row) self.model.create_or_update_bgsub_ws(ws_name, bg_params) self._update_plotted_ws_with_sub_state(ws_name, is_sub) elif col > 3: if is_sub: # bg params changed - revaluate background bg_params = self.view.read_bg_params_from_table(row) self.model.create_or_update_bgsub_ws(ws_name, bg_params) def _update_plotted_ws_with_sub_state(self, ws_name, is_sub): ws = self.model.get_loaded_workspaces()[ws_name] ws_bgsub = self.model.get_bgsub_workspaces()[ws_name] if ws_name in self.plotted and is_sub: self.plot_removed_notifier.notify_subscribers(ws) self.plotted.discard(ws_name) self.plot_added_notifier.notify_subscribers(ws_bgsub) self.plotted.add(ws_name + "_bgsub") elif ws_name + "_bgsub" in self.plotted and not is_sub: self.plot_removed_notifier.notify_subscribers(ws_bgsub) self.plotted.discard(ws_name + "_bgsub") self.plot_added_notifier.notify_subscribers(ws) self.plotted.add(ws_name) def _handle_selection_changed(self): enable = True if not self.view.get_selected_rows(): enable = False self._enable_inspect_bg_button(enable) def _enable_load_button(self, enabled): self.view.set_load_button_enabled(enabled) def _emit_enable_load_button_signal(self): self.view.sig_enable_load_button.emit(True) def _enable_inspect_bg_button(self, enabled): self.view.set_inspect_bg_button_enabled(enabled) def _get_filenames(self): return self.view.get_filenames_to_load() def _is_searching(self): return self.view.is_searching() def _files_are_valid(self): return self.view.get_filenames_valid() def _validate(self): if self._is_searching(): create_error_message( self.view, "Mantid is searching for files. Please wait.") return False elif not self._files_are_valid(): create_error_message(self.view, "Entered files are not valid.") return False return True def _add_row_to_table(self, ws_name, row, run_no=None, bank=None, checked=False, bgsub=False, niter=100, xwindow=None, SG=True): words = ws_name.split("_") # find xwindow from ws xunit if not specified if not xwindow: ws = self.model.get_loaded_workspaces()[ws_name] if ws.getAxis(0).getUnit().unitID() == "TOF": xwindow = 1000 else: xwindow = 0.05 if run_no is not None and bank is not None: self.view.add_table_row(run_no, bank, checked, bgsub, niter, xwindow, SG) elif len(words) == 4 and words[2] == "bank": logger.notice( "No sample logs present, determining information from workspace name." ) self.view.add_table_row(words[1], words[3], checked, bgsub, niter, xwindow, SG) else: logger.warning( "The workspace '{}' was not in the correct naming format. Files should be named in the following way: " "INSTRUMENT_RUNNUMBER_bank_BANK. Using workspace name as identifier." .format(ws_name)) self.view.add_table_row(ws_name, "N/A", checked, bgsub, niter, xwindow, SG) self.row_numbers[ws_name] = row def _remove_table_row(self, row_no): self.view.remove_table_row(row_no) def _remove_selected_table_rows(self): return self.view.remove_selected() def _remove_all_table_rows(self): self.view.remove_all()
class CalibrationPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.view.set_on_calibrate_clicked(self.on_calibrate_clicked) self.worker = None self.instrument = "ENGINX" self.rb_num = None def on_calibrate_clicked(self): if not self.validate_run_numbers(): return if self.view.is_searching(): return vanadium_no = self.view.get_vanadium_filename() calib_no = self.view.get_calib_filename() plot_output = self.view.get_plot_output() self.start_calibration_worker(vanadium_no, calib_no, plot_output, self.rb_num) def start_calibration_worker(self, vanadium_path, calib_path, plot_output, rb_num): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param vanadium_path: Path to vanadium data file. :param calib_path: Path to calibration data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. """ self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, calib_path), {"plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num}, error_cb=self._on_error, finished_cb=self.enable_calibrate_buttons) self.disable_calibrate_buttons() self.worker.start() def set_instrument_override(self, instrument): if instrument == 0: instrument = "ENGINX" elif instrument == 1: instrument = "IMAT" else: raise ValueError("Invalid instrument index") self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_number(self, rb_number): self.rb_num = rb_number def validate_run_numbers(self): if self.view.get_calib_valid() and self.view.get_vanadium_valid(): return True else: return False def disable_calibrate_buttons(self): self.view.set_calibrate_button_enabled(False) self.view.set_check_plot_output_enabled(False) def enable_calibrate_buttons(self): self.view.set_calibrate_button_enabled(True) self.view.set_check_plot_output_enabled(True) def _on_error(self, failure_info): logger.warning(str(failure_info)) self.enable_calibrate_buttons()
class CalibrationPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.calibration_notifier = self.CalibrationNotifier(self) self.current_calibration = CalibrationInfo() self.pending_calibration = CalibrationInfo() self.connect_view_signals() # Main Window State Variables self.instrument = "ENGINX" self.rb_num = None # Cropping Options self.cropping_widget = CroppingWidget( self.view, view=self.view.get_cropping_widget()) self.show_cropping(False) def connect_view_signals(self): self.view.set_on_calibrate_clicked(self.on_calibrate_clicked) self.view.set_enable_controls_connection( self.set_calibrate_controls_enabled) self.view.set_update_fields_connection(self.set_field_values) self.view.set_on_radio_new_toggled(self.set_create_new_enabled) self.view.set_on_radio_existing_toggled(self.set_load_existing_enabled) self.view.set_on_check_cropping_state_changed(self.show_cropping) def on_calibrate_clicked(self): plot_output = self.view.get_plot_output() if self.view.get_new_checked() and self._validate(): vanadium_file = self.view.get_vanadium_filename() sample_file = self.view.get_sample_filename() if self.view.get_crop_checked(): self.start_cropped_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num) else: self.start_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num) elif self.view.get_load_checked(): if not self.validate_path(): return filename = self.view.get_path_filename() instrument, vanadium_file, sample_file = self.model.load_existing_gsas_parameters( filename) self.pending_calibration.set_calibration(vanadium_file, sample_file, instrument) self.set_current_calibration() def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num, bank=None, spectrum_numbers=None): """ Calibrate the data in a separate thread so as to not freeze the GUI. :param vanadium_path: Path to vanadium data file. :param sample_path: Path to sample data file. :param plot_output: Whether to plot the output. :param rb_num: The current RB number set in the GUI. :param bank: Optional parameter to crop by bank. :param spectrum_numbers: Optional parameter to crop by spectrum number. """ self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path), { "plot_output": plot_output, "instrument": self.instrument, "rb_num": rb_num, "bank": bank, "spectrum_numbers": spectrum_numbers }, error_cb=self._on_error, success_cb=self._on_success) self.pending_calibration.set_calibration(vanadium_path, sample_path, self.instrument) self.set_calibrate_controls_enabled(False) self.worker.start() def start_cropped_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num): if self.cropping_widget.is_custom(): spec_nums = self.cropping_widget.get_custom_spectra() self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, spectrum_numbers=spec_nums) else: bank = self.cropping_widget.get_bank() self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, bank=bank) def set_current_calibration(self, success_info=None): if success_info: logger.information("Thread executed in " + str(success_info.elapsed_time) + " seconds.") self.current_calibration = deepcopy(self.pending_calibration) self.calibration_notifier.notify_subscribers(self.current_calibration) self.emit_update_fields_signal() self.pending_calibration.clear() def set_field_values(self): self.view.set_sample_text(self.current_calibration.get_sample()) self.view.set_vanadium_text(self.current_calibration.get_vanadium()) def set_instrument_override(self, instrument): instrument = INSTRUMENT_DICT[instrument] self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_num(self, rb_num): self.rb_num = rb_num def _validate(self): # Do nothing if run numbers are invalid or view is searching. if self.view.is_searching(): create_error_message( self.view, "Mantid is searching for data files. Please wait.") return False if not self.validate_run_numbers(): create_error_message(self.view, "Check run numbers/path is valid.") return False if self.view.get_crop_checked( ) and not self.cropping_widget.is_valid(): create_error_message(self.view, "Check cropping values are valid.") return False return True def validate_run_numbers(self): return self.view.get_sample_valid() and self.view.get_vanadium_valid() def validate_path(self): return self.view.get_path_valid() def emit_enable_button_signal(self): self.view.sig_enable_controls.emit(True) def emit_update_fields_signal(self): self.view.sig_update_fields.emit() def set_calibrate_controls_enabled(self, enabled): self.view.set_calibrate_button_enabled(enabled) self.view.set_check_plot_output_enabled(enabled) def _on_error(self, _): self.emit_enable_button_signal() def _on_success(self, success_info): self.set_current_calibration(success_info) self.emit_enable_button_signal() def set_create_new_enabled(self, enabled): self.view.set_vanadium_enabled(enabled) self.view.set_sample_enabled(enabled) if enabled: self.set_calibrate_button_text("Calibrate") self.view.set_check_plot_output_enabled(True) self.view.set_check_cropping_enabled(True) self.find_files() def set_load_existing_enabled(self, enabled): self.view.set_path_enabled(enabled) if enabled: self.set_calibrate_button_text("Load") self.view.set_check_plot_output_enabled(False) self.view.set_check_cropping_enabled(False) self.view.set_check_cropping_checked(False) def set_calibrate_button_text(self, text): self.view.set_calibrate_button_text(text) def find_files(self): self.view.find_sample_files() self.view.find_vanadium_files() def show_cropping(self, show): self.view.set_cropping_widget_visibility(show) # ----------------------- # Observers / Observables # ----------------------- class CalibrationNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer def notify_subscribers(self, *args, **kwargs): Observable.notify_subscribers(self, *args)
class FocusPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.calibration_observer = self.CalibrationObserver(self) # Connect view signals to local methods. self.view.set_on_focus_clicked(self.on_focus_clicked) self.view.set_enable_controls_connection(self.set_focus_controls_enabled) self.view.set_on_check_cropping_state_changed(self.show_cropping) # Variables from other GUI tabs. self.current_calibration = CalibrationInfo() self.instrument = "ENGINX" self.rb_num = None # Cropping Options self.cropping_widget = CroppingWidget(self.view, view=self.view.get_cropping_widget()) self.show_cropping(False) def on_focus_clicked(self): if not self._validate(): return banks, spectrum_numbers = self._get_banks() focus_paths = self.view.get_focus_filenames() if self._number_of_files_warning(focus_paths): self.start_focus_worker(focus_paths, banks, self.view.get_plot_output(), self.rb_num, spectrum_numbers) def start_focus_worker(self, focus_paths, banks, plot_output, rb_num, spectrum_numbers=None): """ Focus data in a separate thread to stop the main GUI from hanging. :param focus_paths: List of paths to the files containing the data to focus. :param banks: A list of banks that are to be focused. :param plot_output: True if the output should be plotted. :param rb_num: The RB Number from the main window (often an experiment id) :param spectrum_numbers: Optional parameter to crop to a specific list of spectrum numbers. """ self.worker = AsyncTask(self.model.focus_run, (focus_paths, banks, plot_output, self.instrument, rb_num, spectrum_numbers), error_cb=self._on_worker_error, finished_cb=self.emit_enable_button_signal) self.set_focus_controls_enabled(False) self.worker.start() def set_instrument_override(self, instrument): instrument = INSTRUMENT_DICT[instrument] self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_num(self, rb_num): self.rb_num = rb_num def _validate(self): """ Ensure that the worker is ready to be started. :return: True if the worker can be started safely. """ if self.view.is_searching(): create_error_message(self.view, "Mantid is searching for data files. Please wait.") return False if not self.view.get_focus_valid(): create_error_message(self.view, "Check run numbers/path is valid.") return False if not check_workspaces_exist() or not self.current_calibration.is_valid(): create_error_message( self.view, "Create or Load a calibration via the Calibration tab before focusing.") return False if self.current_calibration.get_instrument() != self.instrument: create_error_message( self.view, "Please make sure the selected instrument matches instrument for the current calibration.\n" "The instrument for the current calibration is: " + self.current_calibration.get_instrument()) return False if self.view.get_crop_checked() and not self.cropping_widget.is_valid(): create_error_message(self.view, "Check cropping values are valid.") return False return True def _number_of_files_warning(self, paths): if len(paths) > 10: # Just a guess on the warning for now. May change in future. response = QMessageBox.warning( self.view, 'Engineering Diffraction - Warning', 'You are attempting to focus {} workspaces. This may take some time.\n\n Would you like to continue?' .format(len(paths)), QMessageBox.Ok | QMessageBox.Cancel) return response == QMessageBox.Ok else: return True def _on_worker_error(self, _): self.emit_enable_button_signal() def set_focus_controls_enabled(self, enabled): self.view.set_focus_button_enabled(enabled) self.view.set_plot_output_enabled(enabled) def _get_banks(self): if self.view.get_crop_checked(): if self.cropping_widget.is_custom(): return None, self.cropping_widget.get_custom_spectra() else: return [self.cropping_widget.get_bank()], None else: return ["1", "2"], None def emit_enable_button_signal(self): self.view.sig_enable_controls.emit(True) def update_calibration(self, calibration): """ Update the current calibration following an call from a CalibrationNotifier :param calibration: The new current calibration. """ self.current_calibration = calibration def show_cropping(self, visible): self.view.set_cropping_widget_visibility(visible) # ----------------------- # Observers / Observables # ----------------------- class CalibrationObserver(Observer): def __init__(self, outer): Observer.__init__(self) self.outer = outer def update(self, observable, calibration): self.outer.update_calibration(calibration)
class CalibrationPresenter(object): def __init__(self, model, view): self.model = model self.view = view self.worker = None self.calibration_notifier = self.CalibrationNotifier(self) self.current_calibration = CalibrationInfo() self.connect_view_signals() # Main Window State Variables self.instrument = "ENGINX" self.rb_num = None # Cropping Options self.cropping_widget = CroppingPresenter(parent=self.view, view=self.view.get_cropping_widget()) self.show_cropping(False) def connect_view_signals(self): self.view.set_on_calibrate_clicked(self.on_calibrate_clicked) self.view.set_enable_controls_connection(self.set_calibrate_controls_enabled) self.view.set_update_field_connection(self.set_field_value) self.view.set_on_radio_new_toggled(self.set_create_new_enabled) self.view.set_on_radio_existing_toggled(self.set_load_existing_enabled) self.view.set_on_check_cropping_state_changed(self.show_cropping) def update_calibration_from_view(self): self.current_calibration.clear() if self.view.get_load_checked(): # loading calibration from path to .prm self.current_calibration.set_calibration_from_prm_fname(self.view.get_path_filename()) else: # make a new calibration sample_file = self.view.get_sample_filename() self.current_calibration.set_calibration_paths(self.instrument, sample_file) # set group and any additional parameters needed if self.view.get_crop_checked(): self.current_calibration.set_group(self.cropping_widget.get_group()) if self.current_calibration.group == GROUP.CUSTOM: self.current_calibration.set_cal_file(self.cropping_widget.get_custom_calfile()) elif self.current_calibration.group == GROUP.CROPPED: self.current_calibration.set_spectra_list(self.cropping_widget.get_custom_spectra()) else: # default if no cropping self.current_calibration.set_group(GROUP.BOTH) def on_calibrate_clicked(self): if self.view.get_new_checked() and self._validate(): self.update_calibration_from_view() self.start_calibration_worker(self.view.get_plot_output()) elif self.view.get_load_checked() and self.validate_path(): self.update_calibration_from_view() self.model.load_existing_calibration_files(self.current_calibration) self._notify_updated_calibration() def start_calibration_worker(self, plot_output): """ Calibrate the data in a separate thread so as to not freeze the GUI. """ self.worker = AsyncTask(self.model.create_new_calibration, (self.current_calibration, self.rb_num, plot_output), error_cb=self._on_error, success_cb=self._on_success) self.set_calibrate_controls_enabled(False) self.worker.start() def _on_error(self, error_info): logger.error(str(error_info)) self.emit_enable_button_signal() def _on_success(self, success_info): self._notify_updated_calibration() self.emit_enable_button_signal() def _notify_updated_calibration(self): self.calibration_notifier.notify_subscribers(self.current_calibration) set_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_calibration_path", self.current_calibration.get_prm_filepath()) def set_field_value(self): self.view.set_sample_text(self.current_calibration.get_sample()) def load_last_calibration(self) -> None: """ Loads the most recently created or loaded calibration into the interface instance. To be used on interface startup. """ last_cal_path = get_setting(output_settings.INTERFACES_SETTINGS_GROUP, output_settings.ENGINEERING_PREFIX, "last_calibration_path") if last_cal_path: self.view.set_load_checked(True) self.view.set_file_text_with_search(last_cal_path) def set_instrument_override(self, instrument): instrument = INSTRUMENT_DICT[instrument] self.view.set_instrument_override(instrument) self.instrument = instrument def set_rb_num(self, rb_num): self.rb_num = rb_num def _validate(self): # Do nothing if run numbers are invalid or view is searching. if self.view.is_searching(): create_error_message(self.view, "Mantid is searching for data files. Please wait.") return False if not self.view.get_sample_valid(): create_error_message(self.view, "Check run numbers/path is valid.") return False if self.view.get_crop_checked(): if self.cropping_widget.get_custom_calfile_enabled() and not self.cropping_widget.is_calfile_valid(): create_error_message(self.view, "Check custom calfile path is valid.") return False if self.cropping_widget.get_custom_spectra_enabled() and not self.cropping_widget.is_spectra_valid(): create_error_message(self.view, "Check custom spectra are valid.") return False return True def validate_path(self): return self.view.get_path_valid() def emit_enable_button_signal(self): self.view.sig_enable_controls.emit(True) def set_calibrate_controls_enabled(self, enabled): self.view.set_calibrate_button_enabled(enabled) def set_create_new_enabled(self, enabled): self.view.set_sample_enabled(enabled) if enabled: self.set_calibrate_button_text("Calibrate") self.view.set_check_plot_output_enabled(True) self.view.set_check_cropping_enabled(True) self.find_files() def set_load_existing_enabled(self, enabled): self.view.set_path_enabled(enabled) if enabled: self.set_calibrate_button_text("Load") self.view.set_check_plot_output_enabled(False) self.view.set_check_cropping_enabled(False) self.view.set_check_cropping_checked(False) def set_calibrate_button_text(self, text): self.view.set_calibrate_button_text(text) def find_files(self): self.view.find_sample_files() def show_cropping(self, show): self.view.set_cropping_widget_visibility(show) # ----------------------- # Observers / Observables # ----------------------- class CalibrationNotifier(Observable): def __init__(self, outer): Observable.__init__(self) self.outer = outer def notify_subscribers(self, *args, **kwargs): Observable.notify_subscribers(self, *args)