def __init__(self, facility, view=None): super(RunTabPresenter, self).__init__() self._facility = facility # Logger self.sans_logger = Logger("SANS") # Name of grpah to output to self.output_graph = 'SANS-Latest' self.progress = 0 # Models that are being used by the presenter self._state_model = None self._table_model = TableModel() # Presenter needs to have a handle on the view since it delegates it self._view = None self.set_view(view) self._processing = False self.work_handler = WorkHandler() self.batch_process_runner = BatchProcessRunner(self.notify_progress, self.on_processing_finished, self.on_processing_error) # File information for the first input self._file_information = None self._clipboard = [] # Settings diagnostic tab presenter self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter(self) # Masking table presenter self._masking_table_presenter = MaskingTablePresenter(self) # Beam centre presenter self._beam_centre_presenter = BeamCentrePresenter(self, WorkHandler, BeamCentreModel, SANSCentreFinder) # Workspace Diagnostic page presenter self._workspace_diagnostic_presenter = DiagnosticsPagePresenter(self, WorkHandler, run_integral, create_state, self._facility)
def __init__(self, facility, view=None): super(RunTabPresenter, self).__init__() self._facility = facility # Logger self.sans_logger = Logger("SANS") # Presenter needs to have a handle on the view since it delegates it self._view = None self.set_view(view) # Models that are being used by the presenter self._state_model = None self._table_model = None # Due to the nature of the DataProcessorWidget we need to provide an algorithm with at least one input # workspace and at least one output workspace. Our SANS state approach is not compatible with this. Hence # we provide a dummy workspace which is not used. We keep it invisible on the ADS and delete it when the # main_presenter is deleted. # This is not a nice solution but in line with the SANS dummy algorithm approach that we have provided # for the self._create_dummy_input_workspace() # File information for the first input self._file_information = None # Settings diagnostic tab presenter self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter( self) # Masking table presenter self._masking_table_presenter = MaskingTablePresenter(self) # Beam centre presenter self._beam_centre_presenter = BeamCentrePresenter(self)
def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(self): self.parent_presenter._file_information = None self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_not_called()
def test_that_set_scaling_is_not_called_when_file_information_does_not_exist( self, beam_centre_model_mock): self.parent_presenter._file_information = None #mock.MagicMock(return_value = None) self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_not_called()
def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists( self, beam_centre_model_mock): self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_called_once_with( SANSInstrument.LARMOR)
def setUp(self): self.parent_presenter = create_run_tab_presenter_mock( use_fake_state=False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock( return_value=SANSInstrument.LARMOR) self.presenter = BeamCentrePresenter(self.parent_presenter) self.view = create_mock_beam_centre_tab()
def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.SANSCentreFinder, work_handler=self.WorkHandler, beam_centre_model=self.BeamCentreModel) self.presenter.connect_signals = mock.Mock() self.presenter.set_view(self.view)
def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder) self.presenter.connect_signals = mock.Mock()
def test_that_on_run_clicked_calls_find_beam_centre( self, work_handler_mock): self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertTrue(work_handler_mock.return_value.process.call_count == 1) self.assertTrue(work_handler_mock.return_value.process.call_args[0][1] == find_beam_centre) self.assertTrue(work_handler_mock.return_value.process.call_args[0][3] == self.presenter._beam_centre_model)
def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder)
class BeamCentrePresenterTest(unittest.TestCase): def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.SANSCentreFinder, work_handler=self.WorkHandler, beam_centre_model=self.BeamCentreModel) self.presenter.connect_signals = mock.Mock() self.presenter.set_view(self.view) def test_that_on_run_clicked_calls_find_beam_centre(self): self.presenter.on_run_clicked() self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertEqual(self.presenter._work_handler.process.call_args[0][1], self.presenter._beam_centre_model.find_beam_centre) def test_that_on_run_clicked_updates_model_from_view(self): self.view.left_right = False self.view.lab_pos_1 = 100 self.view.lab_pos_2 = -100 self.presenter.set_view(self.view) self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter.on_run_clicked() self.assertFalse(self.presenter._beam_centre_model.left_right) self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1) self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1) def test_on_update_rows_skips_no_rows(self): self.parent_presenter.num_rows.return_value = 0 self.assertFalse(self.WorkHandler.process.called) self.presenter.on_update_rows() def test_reset_inst_defaults_called_on_update_rows(self): self.parent_presenter.num_rows.return_value = 1 self.parent_presenter.instrument = SANSInstrument.SANS2D self.presenter.on_update_rows() self.BeamCentreModel.reset_inst_defaults.assert_called_with(SANSInstrument.SANS2D) def test_that_set_scaling_is_called_on_update_instrument(self): self.presenter.set_view(self.view) self.presenter.on_update_instrument(SANSInstrument.LARMOR) self.presenter._beam_centre_model.set_scaling.assert_called_once_with(SANSInstrument.LARMOR) def test_that_view_on_update_instrument_is_called_on_update_instrument(self): self.presenter.set_view(self.view) self.presenter.on_update_instrument(SANSInstrument.LARMOR) self.view.on_update_instrument.assert_called_once_with(SANSInstrument.LARMOR) def test_that_on_processing_finished_updates_view(self): self.presenter.set_view(self.view) self.BeamCentreModel.lab_pos_1 = 1 self.BeamCentreModel.lab_pos_2 = 2 self.BeamCentreModel.hab_pos_1 = 3 self.BeamCentreModel.hab_pos_2 = 4 scale_1 = 1000 scale_2 = 2000 self.BeamCentreModel.scale_1 = scale_1 self.BeamCentreModel.scale_2 = scale_2 self.presenter.on_processing_finished_centre_finder() self.assertEqual(self.BeamCentreModel.lab_pos_1 * scale_1, self.view.lab_pos_1) self.assertEqual(self.BeamCentreModel.lab_pos_2 * scale_2, self.view.lab_pos_2) self.assertEqual(self.BeamCentreModel.hab_pos_1 * scale_1, self.view.hab_pos_1) self.assertEqual(self.BeamCentreModel.hab_pos_2 * scale_2, self.view.hab_pos_2) self.view.set_run_button_to_normal.assert_called_once_with() def test_that_update_hab_selected_enabled_hab_and_disabled_lab(self): self.presenter.set_view(self.view) self.presenter.update_hab_selected() # Check that the model has been updated self.assertTrue(self.presenter._beam_centre_model.update_hab) self.assertFalse(self.presenter._beam_centre_model.update_lab) # Check that we called a method to enable the HAB and a method to disable the LAB self.presenter._view.enable_update_hab.assert_called_once_with(True) self.presenter._view.enable_update_lab.assert_called_once_with(False) def test_that_update_lab_selected_enabled_lab_and_disabled_hab(self): self.presenter.set_view(self.view) self.presenter.update_lab_selected() # Check that the model has been updated self.assertFalse(self.presenter._beam_centre_model.update_hab) self.assertTrue(self.presenter._beam_centre_model.update_lab) # Check that we called a method to disable the HAB and a method to enable the LAB self.presenter._view.enable_update_hab.assert_called_once_with(False) self.presenter._view.enable_update_lab.assert_called_once_with(True) def test_that_update_all_selected_enabled_hab_and_lab(self): self.presenter.set_view(self.view) self.presenter.update_all_selected() # Check that the model has been updated self.assertTrue(self.presenter._beam_centre_model.update_hab) self.assertTrue(self.presenter._beam_centre_model.update_lab) # Check that we called a method to enable the HAB and a method to enable the LAB self.presenter._view.enable_update_hab.assert_called_once_with(True) self.presenter._view.enable_update_lab.assert_called_once_with(True)
class BeamCentrePresenterTest(unittest.TestCase): def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock(return_value = SANSInstrument.LARMOR) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder) def test_that_on_run_clicked_calls_find_beam_centre(self): self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertTrue(self.presenter._work_handler.process.call_args[0][1] == self.presenter._beam_centre_model.find_beam_centre) def test_that_on_run_clicked_updates_model_from_view(self): self.view.left_right = False self.view.lab_pos_1 = 100 self.view.lab_pos_2 = -100 self.presenter.set_view(self.view) self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter.on_run_clicked() self.assertFalse(self.presenter._beam_centre_model.left_right) self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1) self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1) def test_that_set_options_is_called_on_update_rows(self): self.presenter.set_view(self.view) self.view.set_options.assert_called_once_with(self.presenter._beam_centre_model) self.presenter.on_update_rows() self.assertTrue(self.view.set_options.call_count == 2) def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists(self): self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_called_once_with(SANSInstrument.LARMOR) def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(self): self.parent_presenter._file_information = None self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_not_called() def test_that_on_processing_finished_updates_view_and_model(self): self.presenter.set_view(self.view) result = {'pos1': 0.1, 'pos2': -0.1} self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter.on_processing_finished_centre_finder(result) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.lab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.lab_pos_2) self.assertEqual(self.view.lab_pos_1, self.presenter._beam_centre_model.scale_1*result['pos1']) self.assertEqual(self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2*result['pos2']) self.view.set_run_button_to_normal.assert_called_once_with()
class RunTabPresenter(object): class ConcreteRunTabListener(SANSDataProcessorGui.RunTabListener): def __init__(self, presenter): super(RunTabPresenter.ConcreteRunTabListener, self).__init__() self._presenter = presenter def on_user_file_load(self): self._presenter.on_user_file_load() def on_mask_file_add(self): self._presenter.on_mask_file_add() def on_batch_file_load(self): self._presenter.on_batch_file_load() def on_processed_clicked(self): self._presenter.on_processed_clicked() def on_processing_finished(self): self._presenter.on_processing_finished() def on_data_changed(self): self._presenter.on_data_changed() def on_manage_directories(self): self._presenter.on_manage_directories() def __init__(self, facility, view=None): super(RunTabPresenter, self).__init__() self._facility = facility # Logger self.sans_logger = Logger("SANS") # Presenter needs to have a handle on the view since it delegates it self._view = None self.set_view(view) # Models that are being used by the presenter self._state_model = None self._table_model = None # Due to the nature of the DataProcessorWidget we need to provide an algorithm with at least one input # workspace and at least one output workspace. Our SANS state approach is not compatible with this. Hence # we provide a dummy workspace which is not used. We keep it invisible on the ADS and delete it when the # main_presenter is deleted. # This is not a nice solution but in line with the SANS dummy algorithm approach that we have provided # for the self._create_dummy_input_workspace() # File information for the first input self._file_information = None # Settings diagnostic tab presenter self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter( self) # Masking table presenter self._masking_table_presenter = MaskingTablePresenter(self) # Beam centre presenter self._beam_centre_presenter = BeamCentrePresenter(self) def __del__(self): self._delete_dummy_input_workspace() def _default_gui_setup(self): """ Provides a default setup of the GUI. This is important for the initial start up, when the view is being set. """ # Set the possible reduction modes reduction_mode_list = get_reduction_mode_strings_for_gui() self._view.set_reduction_modes(reduction_mode_list) # Set the step type options for wavelength range_step_types = [ RangeStepType.to_string(RangeStepType.Lin), RangeStepType.to_string(RangeStepType.Log) ] self._view.wavelength_step_type = range_step_types # Set the geometry options. This needs to include the option to read the sample shape from file. sample_shape = [ "Read from file", SampleShape.to_string(SampleShape.CylinderAxisUp), SampleShape.to_string(SampleShape.Cuboid), SampleShape.to_string(SampleShape.CylinderAxisAlong) ] self._view.sample_shape = sample_shape # Set the q range self._view.q_1d_step_type = range_step_types self._view.q_xy_step_type = range_step_types # Set the fit options fit_types = [ FitType.to_string(FitType.Linear), FitType.to_string(FitType.Logarithmic), FitType.to_string(FitType.Polynomial) ] self._view.transmission_sample_fit_type = fit_types self._view.transmission_can_fit_type = fit_types # ------------------------------------------------------------------------------------------------------------------ # Table + Actions # ------------------------------------------------------------------------------------------------------------------ def set_view(self, view): """ Sets the view :param view: the view is the SANSDataProcessorGui. The presenter needs to access some of the API """ if view is not None: self._view = view # Add a listener to the view listener = RunTabPresenter.ConcreteRunTabListener(self) self._view.add_listener(listener) # Default gui setup self._default_gui_setup() # Set appropriate view for the state diagnostic tab presenter self._settings_diagnostic_tab_presenter.set_view( self._view.settings_diagnostic_tab) # Set appropriate view for the masking table presenter self._masking_table_presenter.set_view(self._view.masking_table) # Set the appropriate view for the beam centre presenter self._beam_centre_presenter.set_view(self._view.beam_centre) def on_user_file_load(self): """ Loads the user file. Populates the models and the view. """ try: # 1. Get the user file path from the view user_file_path = self._view.get_user_file_path() if not user_file_path: return # 2. Get the full file path user_file_path = FileFinder.getFullPath(user_file_path) if not os.path.exists(user_file_path): raise RuntimeError( "The user path {} does not exist. Make sure a valid user file path" " has been specified.".format(user_file_path)) # Clear out the current view self._view.reset_all_fields_to_default() # 3. Read and parse the user file user_file_reader = UserFileReader(user_file_path) user_file_items = user_file_reader.read_user_file() # 4. Populate the model self._state_model = StateGuiModel(user_file_items) # 5. Update the views. self._update_view_from_state_model() # 6. Perform calls on child presenters self._masking_table_presenter.on_update_rows() self._settings_diagnostic_tab_presenter.on_update_rows() self._beam_centre_presenter.on_update_rows() except Exception as e: self.sans_logger.error( "Loading of the user file failed. Ensure that the path to your files has been added " "to the Mantid search directories! See here for more details: {}" .format(str(e))) def on_batch_file_load(self): """ Loads a batch file and populates the batch table based on that. """ try: # 1. Get the batch file from the view batch_file_path = self._view.get_batch_file_path() if not batch_file_path: return if not os.path.exists(batch_file_path): raise RuntimeError( "The batch file path {} does not exist. Make sure a valid batch file path" " has been specified.".format(batch_file_path)) # 2. Read the batch file batch_file_parser = BatchCsvParser(batch_file_path) parsed_rows = batch_file_parser.parse_batch_file() # 3. Clear the table self._view.clear_table() # 4. Populate the table for row in parsed_rows: self._populate_row_in_table(row) # 5. Populate the selected instrument and the correct detector selection self._setup_instrument_specific_settings() # 6. Perform calls on child presenters self._masking_table_presenter.on_update_rows() self._settings_diagnostic_tab_presenter.on_update_rows() self._beam_centre_presenter.on_update_rows() except RuntimeError as e: self.sans_logger.error( "Loading of the batch file failed. Ensure that the path to your files has been added" " to the Mantid search directories! See here for more details: {}" .format(str(e))) def on_data_changed(self): # 1. Populate the selected instrument and the correct detector selection self._setup_instrument_specific_settings() # 2. Perform calls on child presenters self._masking_table_presenter.on_update_rows() self._settings_diagnostic_tab_presenter.on_update_rows() self._beam_centre_presenter.on_update_rows() def on_processed_clicked(self): """ Prepares the batch reduction. 0. Validate rows and create dummy workspace if it does not exist 1. Sets up the states 2. Adds a dummy input workspace 3. Adds row index information """ try: self.sans_logger.information("Starting processing of batch table.") # 0. Validate rows self._create_dummy_input_workspace() self._validate_rows() # 1. Set up the states and convert them into property managers states = self.get_states() if not states: raise RuntimeError( "There seems to have been an issue with setting the states. Make sure that a user file" " has been loaded") property_manager_service = PropertyManagerService() property_manager_service.add_states_to_pmds(states) # 2. Add dummy input workspace to Options column self._remove_dummy_workspaces_and_row_index() self._set_dummy_workspace() # 3. Add dummy row index to Options column self._set_indices() except Exception as e: self._view.halt_process_flag() self.sans_logger.error("Process halted due to: {}".format(str(e))) def on_processing_finished(self): self._remove_dummy_workspaces_and_row_index() def on_manage_directories(self): self._view.show_directory_manager() def on_mask_file_add(self): """ We get the added mask file name and add it to the list of masks """ new_mask_file = self._view.get_mask_file() if not new_mask_file: return new_mask_file_full_path = FileFinder.getFullPath(new_mask_file) if not new_mask_file_full_path: return # Add the new mask file to state model mask_files = self._state_model.mask_files mask_files.append(new_mask_file) self._state_model.mask_files = mask_files # Make sure that the sub-presenters are up to date with this change self._masking_table_presenter.on_update_rows() self._settings_diagnostic_tab_presenter.on_update_rows() self._beam_centre_presenter.on_update_rows() def _add_to_hidden_options(self, row, property_name, property_value): """ Adds a new property to the Hidden Options column @param row: The row where the Options column is being altered @param property_name: The property name on the GUI algorithm. @param property_value: The value which is being set for the property. """ entry = property_name + OPTIONS_EQUAL + str(property_value) options = self._get_hidden_options(row) if options: options += OPTIONS_SEPARATOR + entry else: options = entry self._set_hidden_options(options, row) def _set_hidden_options(self, value, row): self._view.set_cell(value, row, HIDDEN_OPTIONS_INDEX) def _get_options(self, row): return self._view.get_cell(row, OPTIONS_INDEX, convert_to=str) def _get_hidden_options(self, row): return self._view.get_cell(row, HIDDEN_OPTIONS_INDEX, convert_to=str) def is_empty_row(self, row): """ Checks if a row has no entries. These rows will be ignored. :param row: the row index :return: True if the row is empty. """ indices = range(OPTIONS_INDEX + 1) for index in indices: cell_value = self._view.get_cell(row, index, convert_to=str) if cell_value: return False return True def _remove_from_hidden_options(self, row, property_name): """ Remove the entries in the hidden options column :param row: the row index :param property_name: the property name which is to be removed """ options = self._get_hidden_options(row) # Remove the property entry and the value individual_options = options.split(",") clean_options = [] for individual_option in individual_options: if property_name not in individual_option: clean_options.append(individual_option) clean_options = ",".join(clean_options) self._set_hidden_options(clean_options, row) def _validate_rows(self): """ Validation of the rows. A minimal setup requires that ScatterSample is set. """ # If SampleScatter is empty, then don't run the reduction. # We allow empty rows for now, since we cannot remove them from Python. number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): if not self.is_empty_row(row): sample_scatter = self._view.get_cell(row, 0) if not sample_scatter: raise RuntimeError( "Row {} has not SampleScatter specified. Please correct this." .format(row)) def get_processing_options(self): """ Creates a processing string for the data processor widget :return: A processing string for the data processor widget """ global_options = "" # Check if optimizations should be used optimization_selection = "UseOptimizations=1" if self._view.use_optimizations else "UseOptimizations=0" global_options += optimization_selection # Get the output mode output_mode = self._view.output_mode output_mode_selection = "OutputMode=" + OutputMode.to_string( output_mode) global_options += "," global_options += output_mode_selection return global_options # ------------------------------------------------------------------------------------------------------------------ # Controls # ------------------------------------------------------------------------------------------------------------------ def disable_controls(self): """ Disable all input fields and buttons during the execution of the reduction. """ # TODO: think about enabling and disable some controls during reduction pass def enable_controls(self): """ Enable all input fields and buttons after the execution has completed. """ # TODO: think about enabling and disable some controls during reduction pass # ------------------------------------------------------------------------------------------------------------------ # Table Model and state population # ------------------------------------------------------------------------------------------------------------------ def get_states(self, row_index=None): """ Gathers the state information for all rows. :param row_index: if a single row is selected, then only this row is returned, else all the state for all rows is returned :return: a list of states """ start_time_state_generation = time.time() # 1. Update the state model state_model_with_view_update = self._get_state_model_with_view_update() # 2. Update the table model table_model = self._get_table_model() # 3. Go through each row and construct a state object if table_model and state_model_with_view_update: states = self._create_states(state_model_with_view_update, table_model, row_index) else: states = None stop_time_state_generation = time.time() time_taken = stop_time_state_generation - start_time_state_generation self.sans_logger.information( "The generation of all states took {}s".format(time_taken)) return states def get_row_indices(self): """ Gets the indices of row which are not empty. :return: a list of row indices. """ row_indices_which_are_not_empty = [] number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): if not self.is_empty_row(row): row_indices_which_are_not_empty.append(row) return row_indices_which_are_not_empty def get_state_for_row(self, row_index): """ Creates the state for a particular row. :param row_index: the row index :return: a state if the index is valid and there is a state else None """ states = self.get_states(row_index=row_index) if states is None: self.sans_logger.warning( "There does not seem to be data for a row {}.".format( row_index)) return None if row_index in list(states.keys()): if states: return states[row_index] return None def _update_view_from_state_model(self): # Front tab view self._set_on_view("zero_error_free") self._set_on_view("save_types") self._set_on_view("compatibility_mode") self._set_on_view("merge_scale") self._set_on_view("merge_shift") self._set_on_view("merge_scale_fit") self._set_on_view("merge_shift_fit") self._set_on_view("merge_q_range_start") self._set_on_view("merge_q_range_stop") # Settings tab view self._set_on_view("reduction_dimensionality") self._set_on_view("reduction_mode") self._set_on_view("event_slices") self._set_on_view("event_binning") self._set_on_view("merge_mask") self._set_on_view("wavelength_step_type") self._set_on_view("wavelength_min") self._set_on_view("wavelength_max") self._set_on_view("wavelength_step") self._set_on_view("absolute_scale") self._set_on_view("sample_shape") self._set_on_view("sample_height") self._set_on_view("sample_width") self._set_on_view("sample_thickness") self._set_on_view("z_offset") # Adjustment tab self._set_on_view("normalization_incident_monitor") self._set_on_view("normalization_interpolate") self._set_on_view("transmission_incident_monitor") self._set_on_view("transmission_interpolate") self._set_on_view("transmission_roi_files") self._set_on_view("transmission_mask_files") self._set_on_view("transmission_radius") self._set_on_view("transmission_monitor") self._set_on_view("transmission_mn_shift") self._set_on_view("show_transmission") self._set_on_view_transmission_fit() self._set_on_view("pixel_adjustment_det_1") self._set_on_view("pixel_adjustment_det_2") self._set_on_view("wavelength_adjustment_det_1") self._set_on_view("wavelength_adjustment_det_2") # Q tab self._set_on_view_q_rebin_string() self._set_on_view("q_xy_max") self._set_on_view("q_xy_step") self._set_on_view("q_xy_step_type") self._set_on_view("gravity_on_off") self._set_on_view("gravity_extra_length") self._set_on_view("use_q_resolution") self._set_on_view_q_resolution_aperture() self._set_on_view("q_resolution_delta_r") self._set_on_view("q_resolution_collimation_length") self._set_on_view("q_resolution_moderator_file") # Mask self._set_on_view("phi_limit_min") self._set_on_view("phi_limit_max") self._set_on_view("phi_limit_use_mirror") self._set_on_view("radius_limit_min") self._set_on_view("radius_limit_max") # Beam Centre self._beam_centre_presenter.set_on_view('lab_pos_1', self._state_model) self._beam_centre_presenter.set_on_view('lab_pos_2', self._state_model) self._beam_centre_presenter.set_on_view('hab_pos_1', self._state_model) self._beam_centre_presenter.set_on_view('hab_pos_2', self._state_model) def _set_on_view_transmission_fit_sample_settings(self): # Set transmission_sample_use_fit fit_type = self._state_model.transmission_sample_fit_type use_fit = fit_type is not FitType.NoFit self._view.transmission_sample_use_fit = use_fit # Set the polynomial order for sample polynomial_order = self._state_model.transmission_sample_polynomial_order if fit_type is FitType.Polynomial else 2 # noqa self._view.transmission_sample_polynomial_order = polynomial_order # Set the fit type for the sample fit_type = fit_type if fit_type is not FitType.NoFit else FitType.Linear self._view.transmission_sample_fit_type = fit_type # Set the wavelength wavelength_min = self._state_model.transmission_sample_wavelength_min wavelength_max = self._state_model.transmission_sample_wavelength_max if wavelength_min and wavelength_max: self._view.transmission_sample_use_wavelength = True self._view.transmission_sample_wavelength_min = wavelength_min self._view.transmission_sample_wavelength_max = wavelength_max def _set_on_view_transmission_fit(self): # Steps for adding the transmission fit to the view # 1. Check if individual settings exist. If so then set the view to separate, else set them to both # 2. Apply the settings separate_settings = self._state_model.has_transmission_fit_got_separate_settings_for_sample_and_can( ) self._view.set_fit_selection(use_separate=separate_settings) if separate_settings: self._set_on_view_transmission_fit_sample_settings() # Set transmission_sample_can_fit fit_type_can = self._state_model.transmission_can_fit_type() use_can_fit = fit_type_can is FitType.NoFit self._view.transmission_can_use_fit = use_can_fit # Set the polynomial order for can polynomial_order_can = self._state_model.transmission_can_polynomial_order if fit_type_can is FitType.Polynomial else 2 # noqa self._view.transmission_can_polynomial_order = polynomial_order_can # Set the fit type for the can fit_type_can = fit_type_can if fit_type_can is not FitType.NoFit else FitType.Linear self.transmission_can_fit_type = fit_type_can # Set the wavelength wavelength_min = self._state_model.transmission_can_wavelength_min wavelength_max = self._state_model.transmission_can_wavelength_max if wavelength_min and wavelength_max: self._view.transmission_can_use_wavelength = True self._view.transmission_can_wavelength_min = wavelength_min self._view.transmission_can_wavelength_max = wavelength_max else: self._set_on_view_transmission_fit_sample_settings() def _set_on_view_q_resolution_aperture(self): self._set_on_view("q_resolution_source_a") self._set_on_view("q_resolution_sample_a") self._set_on_view("q_resolution_source_h") self._set_on_view("q_resolution_sample_h") self._set_on_view("q_resolution_source_w") self._set_on_view("q_resolution_sample_w") # If we have h1, h2, w1, and w2 selected then we want to select the rectangular aperture. is_rectangular = self._state_model.q_resolution_source_h and self._state_model.q_resolution_sample_h and \ self._state_model.q_resolution_source_w and self._state_model.q_resolution_sample_w # noqa self._view.set_q_resolution_shape_to_rectangular(is_rectangular) def _set_on_view_q_rebin_string(self): """ Maps the q_1d_rebin_string of the model to the q_1d_step and q_1d_step_type property of the view. """ rebin_string = self._state_model.q_1d_rebin_string # Extract the min, max and step and step type from the rebin string elements = rebin_string.split(",") # If we have three elements then we want to set only the if len(elements) == 3: step_element = float(elements[1]) step = abs(step_element) step_type = RangeStepType.Lin if step_element >= 0 else RangeStepType.Log # Set on the view self._view.q_1d_min_or_rebin_string = float(elements[0]) self._view.q_1d_max = float(elements[2]) self._view.q_1d_step = step self._view.q_1d_step_type = step_type else: # Set the rebin string self._view.q_1d_min_or_rebin_string = rebin_string self._view.q_1d_step_type = self._view.VARIABLE def _set_on_view(self, attribute_name): attribute = getattr(self._state_model, attribute_name) if attribute or isinstance( attribute, bool ): # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa setattr(self._view, attribute_name, attribute) def _set_on_view_with_view(self, attribute_name, view): attribute = getattr(self._state_model, attribute_name) if attribute or isinstance( attribute, bool ): # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa setattr(view, attribute_name, attribute) def _get_state_model_with_view_update(self): """ Goes through all sub presenters and update the state model based on the views. Note that at the moment we have set up the view and the model such that the name of a property must be the same in the view and the model. This can be easily changed, but it also provides a good cohesion. """ state_model = copy.deepcopy(self._state_model) # If we don't have a state model then return None if state_model is None: return state_model # Run tab view self._set_on_state_model("zero_error_free", state_model) self._set_on_state_model("save_types", state_model) self._set_on_state_model("compatibility_mode", state_model) self._set_on_state_model("merge_scale", state_model) self._set_on_state_model("merge_shift", state_model) self._set_on_state_model("merge_scale_fit", state_model) self._set_on_state_model("merge_shift_fit", state_model) self._set_on_state_model("merge_q_range_start", state_model) self._set_on_state_model("merge_q_range_stop", state_model) self._set_on_state_model("merge_mask", state_model) self._set_on_state_model("merge_max", state_model) self._set_on_state_model("merge_min", state_model) # Settings tab self._set_on_state_model("reduction_dimensionality", state_model) self._set_on_state_model("reduction_mode", state_model) self._set_on_state_model("event_slices", state_model) self._set_on_state_model("event_binning", state_model) self._set_on_state_model("wavelength_step_type", state_model) self._set_on_state_model("wavelength_min", state_model) self._set_on_state_model("wavelength_max", state_model) self._set_on_state_model("wavelength_step", state_model) self._set_on_state_model("absolute_scale", state_model) self._set_on_state_model("sample_shape", state_model) self._set_on_state_model("sample_height", state_model) self._set_on_state_model("sample_width", state_model) self._set_on_state_model("sample_thickness", state_model) self._set_on_state_model("z_offset", state_model) # Adjustment tab self._set_on_state_model("normalization_incident_monitor", state_model) self._set_on_state_model("normalization_interpolate", state_model) self._set_on_state_model("transmission_incident_monitor", state_model) self._set_on_state_model("transmission_interpolate", state_model) self._set_on_state_model("transmission_roi_files", state_model) self._set_on_state_model("transmission_mask_files", state_model) self._set_on_state_model("transmission_radius", state_model) self._set_on_state_model("transmission_monitor", state_model) self._set_on_state_model("transmission_mn_shift", state_model) self._set_on_state_model("show_transmission", state_model) self._set_on_state_model_transmission_fit(state_model) self._set_on_state_model("pixel_adjustment_det_1", state_model) self._set_on_state_model("pixel_adjustment_det_2", state_model) self._set_on_state_model("wavelength_adjustment_det_1", state_model) self._set_on_state_model("wavelength_adjustment_det_2", state_model) # Q tab self._set_on_state_model_q_1d_rebin_string(state_model) self._set_on_state_model("q_xy_max", state_model) self._set_on_state_model("q_xy_step", state_model) self._set_on_state_model("q_xy_step_type", state_model) self._set_on_state_model("gravity_on_off", state_model) self._set_on_state_model("gravity_extra_length", state_model) self._set_on_state_model("use_q_resolution", state_model) self._set_on_state_model("q_resolution_source_a", state_model) self._set_on_state_model("q_resolution_sample_a", state_model) self._set_on_state_model("q_resolution_source_h", state_model) self._set_on_state_model("q_resolution_sample_h", state_model) self._set_on_state_model("q_resolution_source_w", state_model) self._set_on_state_model("q_resolution_sample_w", state_model) self._set_on_state_model("q_resolution_delta_r", state_model) self._set_on_state_model("q_resolution_collimation_length", state_model) self._set_on_state_model("q_resolution_moderator_file", state_model) # Mask self._set_on_state_model("phi_limit_min", state_model) self._set_on_state_model("phi_limit_max", state_model) self._set_on_state_model("phi_limit_use_mirror", state_model) self._set_on_state_model("radius_limit_min", state_model) self._set_on_state_model("radius_limit_max", state_model) # Beam Centre self._beam_centre_presenter.set_on_state_model("lab_pos_1", state_model) self._beam_centre_presenter.set_on_state_model("lab_pos_2", state_model) return state_model def _set_on_state_model_transmission_fit(self, state_model): # Behaviour depends on the selection of the fit if self._view.use_same_transmission_fit_setting_for_sample_and_can(): use_fit = self._view.transmission_sample_use_fit fit_type = self._view.transmission_sample_fit_type polynomial_order = self._view.transmission_sample_polynomial_order state_model.transmission_sample_fit_type = fit_type if use_fit else FitType.NoFit state_model.transmission_can_fit_type = fit_type if use_fit else FitType.NoFit state_model.transmission_sample_polynomial_order = polynomial_order state_model.transmission_can_polynomial_order = polynomial_order # Wavelength settings if self._view.transmission_sample_use_wavelength: wavelength_min = self._view.transmission_sample_wavelength_min wavelength_max = self._view.transmission_sample_wavelength_max state_model.transmission_sample_wavelength_min = wavelength_min state_model.transmission_sample_wavelength_max = wavelength_max state_model.transmission_can_wavelength_min = wavelength_min state_model.transmission_can_wavelength_max = wavelength_max else: # Sample use_fit_sample = self._view.transmission_sample_use_fit fit_type_sample = self._view.transmission_sample_fit_type polynomial_order_sample = self._view.transmission_sample_polynomial_order state_model.transmission_sample_fit_type = fit_type_sample if use_fit_sample else FitType.NoFit state_model.transmission_sample_polynomial_order = polynomial_order_sample # Wavelength settings if self._view.transmission_sample_use_wavelength: wavelength_min = self._view.transmission_sample_wavelength_min wavelength_max = self._view.transmission_sample_wavelength_max state_model.transmission_sample_wavelength_min = wavelength_min state_model.transmission_sample_wavelength_max = wavelength_max # Can use_fit_can = self._view.transmission_can_use_fit fit_type_can = self._view.transmission_can_fit_type polynomial_order_can = self._view.transmission_can_polynomial_order state_model.transmission_can_fit_type = fit_type_can if use_fit_can else FitType.NoFit state_model.transmission_can_polynomial_order = polynomial_order_can # Wavelength settings if self._view.transmission_can_use_wavelength: wavelength_min = self._view.transmission_can_wavelength_min wavelength_max = self._view.transmission_can_wavelength_max state_model.transmission_can_wavelength_min = wavelength_min state_model.transmission_can_wavelength_max = wavelength_max def _set_on_state_model_q_1d_rebin_string(self, state_model): q_1d_step_type = self._view.q_1d_step_type # If we are dealing with a simple rebin string then the step type is None if self._view.q_1d_step_type is None: state_model.q_1d_rebin_string = self._view.q_1d_min_or_rebin_string else: q_1d_min = self._view.q_1d_min_or_rebin_string q_1d_max = self._view.q_1d_max q_1d_step = self._view.q_1d_step if q_1d_min and q_1d_max and q_1d_step and q_1d_step_type: q_1d_rebin_string = str(q_1d_min) + "," q_1d_step_type_factor = -1. if q_1d_step_type is RangeStepType.Log else 1. q_1d_rebin_string += str( q_1d_step_type_factor * q_1d_step) + "," q_1d_rebin_string += str(q_1d_max) state_model.q_1d_rebin_string = q_1d_rebin_string def _set_on_state_model(self, attribute_name, state_model): attribute = getattr(self._view, attribute_name) if attribute or isinstance(attribute, bool): setattr(state_model, attribute_name, attribute) def _get_table_model(self): # 1. Create a new table model user_file = self._view.get_user_file_path() batch_file = self._view.get_batch_file_path() table_model = TableModel() table_model.user_file = user_file self.batch_file = batch_file # 2. Iterate over each row, create a table row model and insert it number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): sample_scatter = self._view.get_cell(row=row, column=SAMPLE_SCATTER_INDEX, convert_to=str) sample_scatter_period = self._view.get_cell( row=row, column=SAMPLE_SCATTER_PERIOD_INDEX, convert_to=str) sample_transmission = self._view.get_cell( row=row, column=SAMPLE_TRANSMISSION_INDEX, convert_to=str) sample_transmission_period = self._view.get_cell( row=row, column=SAMPLE_TRANSMISSION_PERIOD_INDEX, convert_to=str) # noqa sample_direct = self._view.get_cell(row=row, column=SAMPLE_DIRECT_INDEX, convert_to=str) sample_direct_period = self._view.get_cell( row=row, column=SAMPLE_DIRECT_PERIOD_INDEX, convert_to=str) can_scatter = self._view.get_cell(row=row, column=CAN_SCATTER_INDEX, convert_to=str) can_scatter_period = self._view.get_cell( row=row, column=CAN_SCATTER_PERIOD_INDEX, convert_to=str) can_transmission = self._view.get_cell( row=row, column=CAN_TRANSMISSION_INDEX, convert_to=str) can_transmission_period = self._view.get_cell( row=row, column=CAN_TRANSMISSION_PERIOD_INDEX, convert_to=str) can_direct = self._view.get_cell(row=row, column=CAN_DIRECT_INDEX, convert_to=str) can_direct_period = self._view.get_cell( row=row, column=CAN_DIRECT_PERIOD_INDEX, convert_to=str) output_name = self._view.get_cell(row=row, column=OUTPUT_NAME_INDEX, convert_to=str) user_file = self._view.get_cell(row=row, column=USER_FILE_INDEX, convert_to=str) # Get the options string # We don't have to add the hidden column here, since it only contains information for the SANS # workflow to operate properly. It however does not contain information for the options_string = self._get_options(row) table_index_model = TableIndexModel( index=row, sample_scatter=sample_scatter, sample_scatter_period=sample_scatter_period, sample_transmission=sample_transmission, sample_transmission_period=sample_transmission_period, sample_direct=sample_direct, sample_direct_period=sample_direct_period, can_scatter=can_scatter, can_scatter_period=can_scatter_period, can_transmission=can_transmission, can_transmission_period=can_transmission_period, can_direct=can_direct, can_direct_period=can_direct_period, output_name=output_name, user_file=user_file, options_column_string=options_string) table_model.add_table_entry(row, table_index_model) return table_model def _create_states(self, state_model, table_model, row_index=None): """ Here we create the states based on the settings in the models :param state_model: the state model object :param table_model: the table model object :param row_index: the selected row, if None then all rows are generated """ number_of_rows = self._view.get_number_of_rows() if row_index is not None: # Check if the selected index is valid if row_index >= number_of_rows: return None rows = [row_index] else: rows = range(number_of_rows) states = {} gui_state_director = GuiStateDirector(table_model, state_model, self._facility) for row in rows: self.sans_logger.information( "Generating state for row {}".format(row)) if not self.is_empty_row(row): row_user_file = table_model.get_row_user_file(row) if row_user_file: user_file_path = FileFinder.getFullPath(row_user_file) if not os.path.exists(user_file_path): raise RuntimeError( "The user path {} does not exist. Make sure a valid user file path" " has been specified.".format(user_file_path)) user_file_reader = UserFileReader(user_file_path) user_file_items = user_file_reader.read_user_file() row_state_model = StateGuiModel(user_file_items) row_gui_state_director = GuiStateDirector( table_model, row_state_model, self._facility) self._create_row_state(row_gui_state_director, states, row) else: self._create_row_state(gui_state_director, states, row) return states def _create_row_state(self, director, states, row): try: state = director.create_state(row) states.update({row: state}) except (ValueError, RuntimeError) as e: raise RuntimeError( "There was a bad entry for row {}. Ensure that the path to your files has " "been added to the Mantid search directories! See here for more " "details: {}".format(row, str(e))) def _populate_row_in_table(self, row): """ Adds a row to the table """ def get_string_entry(_tag, _row): _element = "" if _tag in _row: _element = _row[_tag] return _element def get_string_period(_tag): return "" if _tag == ALL_PERIODS else str(_tag) # 1. Pull out the entries sample_scatter = get_string_entry(BatchReductionEntry.SampleScatter, row) sample_scatter_period = get_string_entry( BatchReductionEntry.SampleScatterPeriod, row) sample_transmission = get_string_entry( BatchReductionEntry.SampleTransmission, row) sample_transmission_period = get_string_entry( BatchReductionEntry.SampleTransmissionPeriod, row) sample_direct = get_string_entry(BatchReductionEntry.SampleDirect, row) sample_direct_period = get_string_entry( BatchReductionEntry.SampleDirectPeriod, row) can_scatter = get_string_entry(BatchReductionEntry.CanScatter, row) can_scatter_period = get_string_entry( BatchReductionEntry.CanScatterPeriod, row) can_transmission = get_string_entry( BatchReductionEntry.CanTransmission, row) can_transmission_period = get_string_entry( BatchReductionEntry.CanScatterPeriod, row) can_direct = get_string_entry(BatchReductionEntry.CanDirect, row) can_direct_period = get_string_entry( BatchReductionEntry.CanDirectPeriod, row) output_name = get_string_entry(BatchReductionEntry.Output, row) # 2. Create entry that can be understood by table row_entry = "SampleScatter:{},ssp:{},SampleTrans:{},stp:{},SampleDirect:{},sdp:{}," \ "CanScatter:{},csp:{},CanTrans:{},ctp:{}," \ "CanDirect:{},cdp:{},OutputName:{}".format(sample_scatter, get_string_period(sample_scatter_period), sample_transmission, get_string_period(sample_transmission_period), sample_direct, get_string_period(sample_direct_period), can_scatter, get_string_period(can_scatter_period), can_transmission, get_string_period(can_transmission_period), can_direct, get_string_period(can_direct_period), output_name) self._view.add_row(row_entry) # ------------------------------------------------------------------------------------------------------------------ # Settings # ------------------------------------------------------------------------------------------------------------------ def _setup_instrument_specific_settings(self): # Get the first run number of the scatter data for the first table sample_scatter = self._view.get_cell(row=0, column=0, convert_to=str) # Check if it exists at all if not sample_scatter: return # Get the file information from file_information_factory = SANSFileInformationFactory() try: self._file_information = file_information_factory.create_sans_file_information( sample_scatter) except NotImplementedError: self.sans_logger.warning( "Could not get file information from {}.".format( sample_scatter)) self._file_information = None # Provide the instrument specific settings if self._file_information: # Set the instrument on the table instrument = self._file_information.get_instrument() self._view.set_instrument_settings(instrument) # Set the reduction mode reduction_mode_list = get_reduction_mode_strings_for_gui( instrument=instrument) self._view.set_reduction_modes(reduction_mode_list) else: self._view.set_instrument_settings(SANSInstrument.NoInstrument) reduction_mode_list = get_reduction_mode_strings_for_gui() self._view.set_reduction_modes(reduction_mode_list) # ------------------------------------------------------------------------------------------------------------------ # Setting workaround for state in DataProcessorWidget # ------------------------------------------------------------------------------------------------------------------ def _remove_dummy_workspaces_and_row_index(self): number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): self._remove_from_hidden_options(row, "InputWorkspace") self._remove_from_hidden_options(row, "RowIndex") def _set_indices(self): number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): to_set = Property.EMPTY_INT if self.is_empty_row(row) else row self._add_to_hidden_options(row, "RowIndex", to_set) def _set_dummy_workspace(self): number_of_rows = self._view.get_number_of_rows() for row in range(number_of_rows): self._add_to_hidden_options( row, "InputWorkspace", SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME) @staticmethod def _create_dummy_input_workspace(): if not AnalysisDataService.doesExist( SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME): workspace = WorkspaceFactory.create("Workspace2D", 1, 1, 1) AnalysisDataService.addOrReplace( SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME, workspace) @staticmethod def _delete_dummy_input_workspace(): if AnalysisDataService.doesExist( SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME): AnalysisDataService.remove( SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME)
class BeamCentrePresenterTest(unittest.TestCase): def setUp(self): self.parent_presenter = create_run_tab_presenter_mock( use_fake_state=False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock( return_value=SANSInstrument.LARMOR) self.presenter = BeamCentrePresenter(self.parent_presenter) self.view = create_mock_beam_centre_tab() @mock.patch('sans.gui_logic.presenter.beam_centre_presenter.WorkHandler') def test_that_on_run_clicked_calls_find_beam_centre( self, work_handler_mock): self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertTrue(work_handler_mock.return_value.process.call_count == 1) self.assertTrue(work_handler_mock.return_value.process.call_args[0][1] == find_beam_centre) self.assertTrue(work_handler_mock.return_value.process.call_args[0][3] == self.presenter._beam_centre_model) def test_that_on_run_clicked_updates_model_from_view(self): self.view.left_right = False self.view.lab_pos_1 = 100 self.view.lab_pos_2 = -100 self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertFalse(self.presenter._beam_centre_model.left_right) self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1) self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1) def test_that_set_options_is_called_on_update_rows(self): self.presenter.set_view(self.view) self.view.set_options.assert_called_once_with( self.presenter._beam_centre_model) self.presenter.on_update_rows() self.assertTrue(self.view.set_options.call_count == 2) @mock.patch( 'sans.gui_logic.presenter.beam_centre_presenter.BeamCentreModel') def test_that_set_scaling_is_called_on_update_rows_when_file_information_exists( self, beam_centre_model_mock): self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_called_once_with( SANSInstrument.LARMOR) @mock.patch( 'sans.gui_logic.presenter.beam_centre_presenter.BeamCentreModel') def test_that_set_scaling_is_not_called_when_file_information_does_not_exist( self, beam_centre_model_mock): self.parent_presenter._file_information = None #mock.MagicMock(return_value = None) self.presenter = BeamCentrePresenter(self.parent_presenter) self.presenter.set_view(self.view) self.presenter.on_update_rows() self.presenter._beam_centre_model.set_scaling.assert_not_called() def test_that_on_processing_finished_updates_view_and_model(self): self.presenter.set_view(self.view) result = {'pos1': 0.1, 'pos2': -0.1} self.presenter.on_processing_finished_centre_finder(result) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.lab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.lab_pos_2) self.assertEqual( self.view.lab_pos_1, self.presenter._beam_centre_model.scale_1 * result['pos1']) self.assertEqual( self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2 * result['pos2']) self.view.set_run_button_to_normal.assert_called_once_with() @mock.patch( 'sans.gui_logic.presenter.beam_centre_presenter.SANSCentreFinder') def test_that_find_beam_centre_initiates_centre_finder_with_correct_parameters( self, centre_finder_mock): state = mock.MagicMock() beam_centre_model = BeamCentreModel() beam_centre_model.lab_pos_1 = 0.1 beam_centre_model.lab_pos_2 = -0.1 find_direction = FindDirectionEnum.All find_beam_centre(state, beam_centre_model) centre_finder_mock.return_value.called_once_with( state, r_min=beam_centre_model.r_min, r_max=beam_centre_model.r_max, max_iter=beam_centre_model.max_iterations, x_start=beam_centre_model.lab_pos_1, y_start=beam_centre_model.lab_pos_2, tolerance=beam_centre_model.tolerance, find_direction=find_direction, reduction_method=True)
class RunTabPresenter(object): class ConcreteRunTabListener(SANSDataProcessorGui.RunTabListener): def __init__(self, presenter): super(RunTabPresenter.ConcreteRunTabListener, self).__init__() self._presenter = presenter def on_user_file_load(self): self._presenter.on_user_file_load() def on_mask_file_add(self): self._presenter.on_mask_file_add() def on_batch_file_load(self): self._presenter.on_batch_file_load() def on_processed_clicked(self): self._presenter.on_processed_clicked() def on_multi_period_selection(self, show_periods): self._presenter.on_multiperiod_changed(show_periods) def on_data_changed(self, row, column, new_value, old_value): self._presenter.on_data_changed(row, column, new_value, old_value) def on_manage_directories(self): self._presenter.on_manage_directories() def on_instrument_changed(self): self._presenter.on_instrument_changed() def on_row_inserted(self, index, row): self._presenter.on_row_inserted(index, row) def on_rows_removed(self, rows): self._presenter.on_rows_removed(rows) def on_copy_rows_requested(self): self._presenter.on_copy_rows_requested() def on_paste_rows_requested(self): self._presenter.on_paste_rows_requested() def on_insert_row(self): self._presenter.on_insert_row() def on_erase_rows(self): self._presenter.on_erase_rows() def on_cut_rows(self): self._presenter.on_cut_rows_requested() class ProcessListener(WorkHandler.WorkListener): def __init__(self, presenter): super(RunTabPresenter.ProcessListener, self).__init__() self._presenter = presenter def on_processing_finished(self, result): self._presenter.on_processing_finished(result) def on_processing_error(self, error): self._presenter.on_processing_error(error) def __init__(self, facility, view=None): super(RunTabPresenter, self).__init__() self._facility = facility # Logger self.sans_logger = Logger("SANS") # Name of grpah to output to self.output_graph = 'SANS-Latest' self.progress = 0 # Models that are being used by the presenter self._state_model = None self._table_model = TableModel() # Presenter needs to have a handle on the view since it delegates it self._view = None self.set_view(view) self._processing = False self.work_handler = WorkHandler() self.batch_process_runner = BatchProcessRunner( self.notify_progress, self.on_processing_finished, self.on_processing_error) # File information for the first input self._file_information = None self._clipboard = [] # Settings diagnostic tab presenter self._settings_diagnostic_tab_presenter = SettingsDiagnosticPresenter( self) # Masking table presenter self._masking_table_presenter = MaskingTablePresenter(self) # Beam centre presenter self._beam_centre_presenter = BeamCentrePresenter( self, WorkHandler, BeamCentreModel, SANSCentreFinder) # Workspace Diagnostic page presenter self._workspace_diagnostic_presenter = DiagnosticsPagePresenter( self, WorkHandler, run_integral, create_state, self._facility) def _default_gui_setup(self): """ Provides a default setup of the GUI. This is important for the initial start up, when the view is being set. """ # Set the possible reduction modes reduction_mode_list = get_reduction_mode_strings_for_gui() self._view.set_reduction_modes(reduction_mode_list) # Set the possible instruments instrument_list = get_instrument_strings_for_gui() self._view.set_instruments(instrument_list) # Set the step type options for wavelength range_step_types = [ RangeStepType.to_string(RangeStepType.Lin), RangeStepType.to_string(RangeStepType.Log), RangeStepType.to_string(RangeStepType.RangeLog), RangeStepType.to_string(RangeStepType.RangeLin) ] self._view.wavelength_step_type = range_step_types # Set the geometry options. This needs to include the option to read the sample shape from file. sample_shape = [ "Read from file", SampleShape.Cylinder, SampleShape.FlatPlate, SampleShape.Disc ] self._view.sample_shape = sample_shape # Set the q range self._view.q_1d_step_type = [ RangeStepType.to_string(RangeStepType.Lin), RangeStepType.to_string(RangeStepType.Log) ] self._view.q_xy_step_type = [ RangeStepType.to_string(RangeStepType.Lin) ] # Set the fit options fit_types = [ FitType.to_string(FitType.Linear), FitType.to_string(FitType.Logarithmic), FitType.to_string(FitType.Polynomial) ] self._view.transmission_sample_fit_type = fit_types self._view.transmission_can_fit_type = fit_types # ------------------------------------------------------------------------------------------------------------------ # Table + Actions # ------------------------------------------------------------------------------------------------------------------ def set_view(self, view): """ Sets the view :param view: the view is the SANSDataProcessorGui. The presenter needs to access some of the API """ if view is not None: self._view = view # Add a listener to the view listener = RunTabPresenter.ConcreteRunTabListener(self) self._view.add_listener(listener) # Default gui setup self._default_gui_setup() # Set appropriate view for the state diagnostic tab presenter self._settings_diagnostic_tab_presenter.set_view( self._view.settings_diagnostic_tab) # Set appropriate view for the masking table presenter self._masking_table_presenter.set_view(self._view.masking_table) # Set the appropriate view for the beam centre presenter self._beam_centre_presenter.set_view(self._view.beam_centre) # Set the appropriate view for the diagnostic page self._workspace_diagnostic_presenter.set_view( self._view.diagnostic_page, self._view.instrument) self._view.setup_layout() self._view.set_hinting_line_edit_for_column( 15, self._table_model.get_options_hint_strategy()) def on_user_file_load(self): """ Loads the user file. Populates the models and the view. """ try: # 1. Get the user file path from the view user_file_path = self._view.get_user_file_path() if not user_file_path: return # 2. Get the full file path user_file_path = FileFinder.getFullPath(user_file_path) if not os.path.exists(user_file_path): raise RuntimeError( "The user path {} does not exist. Make sure a valid user file path" " has been specified.".format(user_file_path)) self._table_model.user_file = user_file_path # Clear out the current view self._view.reset_all_fields_to_default() # 3. Read and parse the user file user_file_reader = UserFileReader(user_file_path) user_file_items = user_file_reader.read_user_file() # 4. Populate the model self._state_model = StateGuiModel(user_file_items) # 5. Update the views. self._update_view_from_state_model() self._beam_centre_presenter.update_centre_positions( self._state_model) self._beam_centre_presenter.on_update_rows() self._masking_table_presenter.on_update_rows() self._workspace_diagnostic_presenter.on_user_file_load( user_file_path) except Exception as e: self.sans_logger.error( "Loading of the user file failed. {}".format(str(e))) self.display_warning_box('Warning', 'Loading of the user file failed.', str(e)) def on_batch_file_load(self): """ Loads a batch file and populates the batch table based on that. """ try: # 1. Get the batch file from the view batch_file_path = self._view.get_batch_file_path() if not batch_file_path: return if not os.path.exists(batch_file_path): raise RuntimeError( "The batch file path {} does not exist. Make sure a valid batch file path" " has been specified.".format(batch_file_path)) self._table_model.batch_file = batch_file_path # 2. Read the batch file batch_file_parser = BatchCsvParser(batch_file_path) parsed_rows = batch_file_parser.parse_batch_file() # 3. Populate the table self._table_model.clear_table_entries() for index, row in enumerate(parsed_rows): self._add_row_to_table_model(row, index) self._table_model.remove_table_entries([len(parsed_rows)]) self.update_view_from_table_model() self._beam_centre_presenter.on_update_rows() self._masking_table_presenter.on_update_rows() except RuntimeError as e: self.sans_logger.error( "Loading of the batch file failed. {}".format(str(e))) self.display_warning_box('Warning', 'Loading of the batch file failed', str(e)) def _add_row_to_table_model(self, row, index): """ Adds a row to the table """ def get_string_entry(_tag, _row): _element = "" if _tag in _row: _element = _row[_tag] return _element def get_string_period(_tag): return "" if _tag == ALL_PERIODS else str(_tag) # 1. Pull out the entries sample_scatter = get_string_entry(BatchReductionEntry.SampleScatter, row) sample_scatter_period = get_string_period( get_string_entry(BatchReductionEntry.SampleScatterPeriod, row)) sample_transmission = get_string_entry( BatchReductionEntry.SampleTransmission, row) sample_transmission_period = \ get_string_period(get_string_entry(BatchReductionEntry.SampleTransmissionPeriod, row)) sample_direct = get_string_entry(BatchReductionEntry.SampleDirect, row) sample_direct_period = get_string_period( get_string_entry(BatchReductionEntry.SampleDirectPeriod, row)) can_scatter = get_string_entry(BatchReductionEntry.CanScatter, row) can_scatter_period = get_string_period( get_string_entry(BatchReductionEntry.CanScatterPeriod, row)) can_transmission = get_string_entry( BatchReductionEntry.CanTransmission, row) can_transmission_period = get_string_period( get_string_entry(BatchReductionEntry.CanScatterPeriod, row)) can_direct = get_string_entry(BatchReductionEntry.CanDirect, row) can_direct_period = get_string_period( get_string_entry(BatchReductionEntry.CanDirectPeriod, row)) output_name = get_string_entry(BatchReductionEntry.Output, row) file_information_factory = SANSFileInformationFactory() file_information = file_information_factory.create_sans_file_information( sample_scatter) sample_thickness = file_information._thickness user_file = get_string_entry(BatchReductionEntry.UserFile, row) row_entry = [ sample_scatter, sample_scatter_period, sample_transmission, sample_transmission_period, sample_direct, sample_direct_period, can_scatter, can_scatter_period, can_transmission, can_transmission_period, can_direct, can_direct_period, output_name, user_file, sample_thickness, '' ] table_index_model = TableIndexModel(*row_entry) self._table_model.add_table_entry(index, table_index_model) def update_view_from_table_model(self): self._view.clear_table() self._view.hide_period_columns() for row_index, row in enumerate(self._table_model._table_entries): row_entry = [str(x) for x in row.to_list()] self._view.add_row(row_entry) self._view.change_row_color( row_state_to_colour_mapping[row.row_state], row_index + 1) self._view.set_row_tooltip(row.tool_tip, row_index + 1) if row.isMultiPeriod(): self._view.show_period_columns() self._view.remove_rows([0]) self._view.clear_selection() def on_data_changed(self, row, column, new_value, old_value): self._table_model.update_table_entry(row, column, new_value) self._view.change_row_color( row_state_to_colour_mapping[RowState.Unprocessed], row) self._view.set_row_tooltip('', row) self._beam_centre_presenter.on_update_rows() self._masking_table_presenter.on_update_rows() def on_instrument_changed(self): self._setup_instrument_specific_settings() def on_processed_clicked(self): """ Prepares the batch reduction. 0. Validate rows and create dummy workspace if it does not exist 1. Sets up the states 2. Adds a dummy input workspace 3. Adds row index information """ try: self._view.disable_buttons() self._processing = True self.sans_logger.information("Starting processing of batch table.") # 1. Set up the states and convert them into property managers selected_rows = self._view.get_selected_rows() selected_rows = selected_rows if selected_rows else range( self._table_model.get_number_of_rows()) for row in selected_rows: self._table_model.reset_row_state(row) self.update_view_from_table_model() states, errors = self.get_states(row_index=selected_rows) for row, error in errors.items(): self.on_processing_error(row, error) if not states: self.on_processing_finished(None) return # 4. Create the graph if continuous output is specified if mantidplot: if self._view.plot_results and not mantidplot.graph( self.output_graph): mantidplot.newGraph(self.output_graph) # Check if optimizations should be used use_optimizations = self._view.use_optimizations # Get the output mode output_mode = self._view.output_mode # Check if results should be plotted plot_results = self._view.plot_results # Get the name of the graph to output to output_graph = self.output_graph self.progress = 0 setattr(self._view, 'progress_bar_value', self.progress) setattr(self._view, 'progress_bar_maximum', len(states)) self.batch_process_runner.process_states(states, use_optimizations, output_mode, plot_results, output_graph) except Exception as e: self._view.enable_buttons() self.sans_logger.error("Process halted due to: {}".format(str(e))) self.display_warning_box('Warning', 'Process halted', str(e)) def on_multiperiod_changed(self, show_periods): if show_periods: self._view.show_period_columns() else: self._view.hide_period_columns() def display_warning_box(self, title, text, detailed_text): self._view.display_message_box(title, text, detailed_text) def notify_progress(self, row): self.increment_progress() message = '' self._table_model.set_row_to_processed(row, message) self.update_view_from_table_model() def on_processing_finished(self, result): self._view.enable_buttons() self._processing = False def on_processing_error(self, row, error_msg): self.increment_progress() self._table_model.set_row_to_error(row, error_msg) self.update_view_from_table_model() def increment_progress(self): self.progress = self.progress + 1 setattr(self._view, 'progress_bar_value', self.progress) def on_row_inserted(self, index, row): row_table_index = TableIndexModel(*row) self._table_model.add_table_entry(index, row_table_index) def on_insert_row(self): selected_rows = self._view.get_selected_rows() selected_row = selected_rows[ 0] + 1 if selected_rows else self._table_model.get_number_of_rows( ) table_entry_row = self._table_model.create_empty_row() self._table_model.add_table_entry(selected_row, table_entry_row) self.update_view_from_table_model() def on_erase_rows(self): selected_rows = self._view.get_selected_rows() empty_row = TableModel.create_empty_row() for row in selected_rows: self._table_model.replace_table_entries([row], [empty_row]) self.update_view_from_table_model() def on_rows_removed(self, rows): self._table_model.remove_table_entries(rows) self.update_view_from_table_model() def on_copy_rows_requested(self): selected_rows = self._view.get_selected_rows() self._clipboard = [] for row in selected_rows: data_from_table_model = self._table_model.get_table_entry( row).to_list() self._clipboard.append(data_from_table_model) def on_cut_rows_requested(self): self.on_copy_rows_requested() rows = self._view.get_selected_rows() self.on_rows_removed(rows) def on_paste_rows_requested(self): if self._clipboard: selected_rows = self._view.get_selected_rows() selected_rows = selected_rows if selected_rows else [ self._table_model.get_number_of_rows() ] replacement_table_index_models = [ TableIndexModel(*x) for x in self._clipboard ] self._table_model.replace_table_entries( selected_rows, replacement_table_index_models) self.update_view_from_table_model() def on_manage_directories(self): self._view.show_directory_manager() def get_row_indices(self): """ Gets the indices of row which are not empty. :return: a list of row indices. """ row_indices_which_are_not_empty = [] number_of_rows = self._table_model.get_number_of_rows() for row in range(number_of_rows): if not self.is_empty_row(row): row_indices_which_are_not_empty.append(row) return row_indices_which_are_not_empty def on_mask_file_add(self): """ We get the added mask file name and add it to the list of masks """ new_mask_file = self._view.get_mask_file() if not new_mask_file: return new_mask_file_full_path = FileFinder.getFullPath(new_mask_file) if not new_mask_file_full_path: return # Add the new mask file to state model mask_files = self._state_model.mask_files mask_files.append(new_mask_file) self._state_model.mask_files = mask_files # Make sure that the sub-presenters are up to date with this change self._masking_table_presenter.on_update_rows() self._settings_diagnostic_tab_presenter.on_update_rows() self._beam_centre_presenter.on_update_rows() def is_empty_row(self, row): """ Checks if a row has no entries. These rows will be ignored. :param row: the row index :return: True if the row is empty. """ return self._table_model.is_empty_row(row) # def _validate_rows(self): # """ # Validation of the rows. A minimal setup requires that ScatterSample is set. # """ # # If SampleScatter is empty, then don't run the reduction. # # We allow empty rows for now, since we cannot remove them from Python. # number_of_rows = self._table_model.get_number_of_rows() # for row in range(number_of_rows): # if not self.is_empty_row(row): # sample_scatter = self._view.get_cell(row, 0) # if not sample_scatter: # raise RuntimeError("Row {} has not SampleScatter specified. Please correct this.".format(row)) # ------------------------------------------------------------------------------------------------------------------ # Controls # ------------------------------------------------------------------------------------------------------------------ def disable_controls(self): """ Disable all input fields and buttons during the execution of the reduction. """ # TODO: think about enabling and disable some controls during reduction pass def enable_controls(self): """ Enable all input fields and buttons after the execution has completed. """ # TODO: think about enabling and disable some controls during reduction pass # ------------------------------------------------------------------------------------------------------------------ # Table Model and state population # ------------------------------------------------------------------------------------------------------------------ def get_states(self, row_index=None, file_lookup=True): """ Gathers the state information for all rows. :param row_index: if a single row is selected, then only this row is returned, else all the state for all rows is returned :return: a list of states """ start_time_state_generation = time.time() # 1. Update the state model state_model_with_view_update = self._get_state_model_with_view_update() # 2. Update the table model table_model = self._table_model # 3. Go through each row and construct a state object if table_model and state_model_with_view_update: states, errors = create_states(state_model_with_view_update, table_model, self._view.instrument, self._facility, row_index=row_index, file_lookup=file_lookup) else: states = None errors = None stop_time_state_generation = time.time() time_taken = stop_time_state_generation - start_time_state_generation self.sans_logger.information( "The generation of all states took {}s".format(time_taken)) return states, errors def get_state_for_row(self, row_index, file_lookup=True): """ Creates the state for a particular row. :param row_index: the row index :return: a state if the index is valid and there is a state else None """ states, errors = self.get_states(row_index=[row_index], file_lookup=file_lookup) if states is None: self.sans_logger.warning( "There does not seem to be data for a row {}.".format( row_index)) return None if row_index in list(states.keys()): if states: return states[row_index] return None def _update_view_from_state_model(self): # Front tab view self._set_on_view("zero_error_free") self._set_on_view("save_types") self._set_on_view("compatibility_mode") self._set_on_view("merge_scale") self._set_on_view("merge_shift") self._set_on_view("merge_scale_fit") self._set_on_view("merge_shift_fit") self._set_on_view("merge_q_range_start") self._set_on_view("merge_q_range_stop") self._set_on_view("merge_max") self._set_on_view("merge_min") # Settings tab view self._set_on_view("reduction_dimensionality") self._set_on_view("reduction_mode") self._set_on_view("event_slices") self._set_on_view("event_binning") self._set_on_view("merge_mask") self._set_on_view("wavelength_step_type") self._set_on_view("wavelength_min") self._set_on_view("wavelength_max") self._set_on_view("wavelength_step") self._set_on_view("absolute_scale") self._set_on_view("sample_shape") self._set_on_view("sample_height") self._set_on_view("sample_width") self._set_on_view("sample_thickness") self._set_on_view("z_offset") # Adjustment tab self._set_on_view("normalization_incident_monitor") self._set_on_view("normalization_interpolate") self._set_on_view("transmission_incident_monitor") self._set_on_view("transmission_interpolate") self._set_on_view("transmission_roi_files") self._set_on_view("transmission_mask_files") self._set_on_view("transmission_radius") self._set_on_view("transmission_monitor") self._set_on_view("transmission_mn_shift") self._set_on_view("show_transmission") self._set_on_view_transmission_fit() self._set_on_view("pixel_adjustment_det_1") self._set_on_view("pixel_adjustment_det_2") self._set_on_view("wavelength_adjustment_det_1") self._set_on_view("wavelength_adjustment_det_2") # Q tab self._set_on_view_q_rebin_string() self._set_on_view("q_xy_max") self._set_on_view("q_xy_step") self._set_on_view("q_xy_step_type") self._set_on_view("gravity_on_off") self._set_on_view("gravity_extra_length") self._set_on_view("use_q_resolution") self._set_on_view_q_resolution_aperture() self._set_on_view("q_resolution_delta_r") self._set_on_view("q_resolution_collimation_length") self._set_on_view("q_resolution_moderator_file") self._set_on_view("r_cut") self._set_on_view("w_cut") # Mask self._set_on_view("phi_limit_min") self._set_on_view("phi_limit_max") self._set_on_view("phi_limit_use_mirror") self._set_on_view("radius_limit_min") self._set_on_view("radius_limit_max") def _set_on_view_transmission_fit_sample_settings(self): # Set transmission_sample_use_fit fit_type = self._state_model.transmission_sample_fit_type use_fit = fit_type is not FitType.NoFit self._view.transmission_sample_use_fit = use_fit # Set the polynomial order for sample polynomial_order = self._state_model.transmission_sample_polynomial_order if fit_type is FitType.Polynomial else 2 # noqa self._view.transmission_sample_polynomial_order = polynomial_order # Set the fit type for the sample fit_type = fit_type if fit_type is not FitType.NoFit else FitType.Linear self._view.transmission_sample_fit_type = fit_type # Set the wavelength wavelength_min = self._state_model.transmission_sample_wavelength_min wavelength_max = self._state_model.transmission_sample_wavelength_max if wavelength_min and wavelength_max: self._view.transmission_sample_use_wavelength = True self._view.transmission_sample_wavelength_min = wavelength_min self._view.transmission_sample_wavelength_max = wavelength_max def _set_on_view_transmission_fit(self): # Steps for adding the transmission fit to the view # 1. Check if individual settings exist. If so then set the view to separate, else set them to both # 2. Apply the settings separate_settings = self._state_model.has_transmission_fit_got_separate_settings_for_sample_and_can( ) self._view.set_fit_selection(use_separate=separate_settings) if separate_settings: self._set_on_view_transmission_fit_sample_settings() # Set transmission_sample_can_fit fit_type_can = self._state_model.transmission_can_fit_type() use_can_fit = fit_type_can is FitType.NoFit self._view.transmission_can_use_fit = use_can_fit # Set the polynomial order for can polynomial_order_can = self._state_model.transmission_can_polynomial_order if fit_type_can is FitType.Polynomial else 2 # noqa self._view.transmission_can_polynomial_order = polynomial_order_can # Set the fit type for the can fit_type_can = fit_type_can if fit_type_can is not FitType.NoFit else FitType.Linear self.transmission_can_fit_type = fit_type_can # Set the wavelength wavelength_min = self._state_model.transmission_can_wavelength_min wavelength_max = self._state_model.transmission_can_wavelength_max if wavelength_min and wavelength_max: self._view.transmission_can_use_wavelength = True self._view.transmission_can_wavelength_min = wavelength_min self._view.transmission_can_wavelength_max = wavelength_max else: self._set_on_view_transmission_fit_sample_settings() def _set_on_view_q_resolution_aperture(self): self._set_on_view("q_resolution_source_a") self._set_on_view("q_resolution_sample_a") self._set_on_view("q_resolution_source_h") self._set_on_view("q_resolution_sample_h") self._set_on_view("q_resolution_source_w") self._set_on_view("q_resolution_sample_w") # If we have h1, h2, w1, and w2 selected then we want to select the rectangular aperture. is_rectangular = self._state_model.q_resolution_source_h and self._state_model.q_resolution_sample_h and \ self._state_model.q_resolution_source_w and self._state_model.q_resolution_sample_w # noqa self._view.set_q_resolution_shape_to_rectangular(is_rectangular) def _set_on_view_q_rebin_string(self): """ Maps the q_1d_rebin_string of the model to the q_1d_step and q_1d_step_type property of the view. """ rebin_string = self._state_model.q_1d_rebin_string # Extract the min, max and step and step type from the rebin string elements = rebin_string.split(",") # If we have three elements then we want to set only the if len(elements) == 3: step_element = float(elements[1]) step = abs(step_element) step_type = RangeStepType.Lin if step_element >= 0 else RangeStepType.Log # Set on the view self._view.q_1d_min_or_rebin_string = float(elements[0]) self._view.q_1d_max = float(elements[2]) self._view.q_1d_step = step self._view.q_1d_step_type = step_type else: # Set the rebin string self._view.q_1d_min_or_rebin_string = rebin_string self._view.q_1d_step_type = self._view.VARIABLE def _set_on_view(self, attribute_name): attribute = getattr(self._state_model, attribute_name) if attribute or isinstance( attribute, bool ): # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa setattr(self._view, attribute_name, attribute) def _set_on_view_with_view(self, attribute_name, view): attribute = getattr(self._state_model, attribute_name) if attribute or isinstance( attribute, bool ): # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa setattr(view, attribute_name, attribute) def _get_state_model_with_view_update(self): """ Goes through all sub presenters and update the state model based on the views. Note that at the moment we have set up the view and the model such that the name of a property must be the same in the view and the model. This can be easily changed, but it also provides a good cohesion. """ state_model = copy.deepcopy(self._state_model) # If we don't have a state model then return None if state_model is None: return state_model # Run tab view self._set_on_state_model("zero_error_free", state_model) self._set_on_state_model("save_types", state_model) self._set_on_state_model("compatibility_mode", state_model) self._set_on_state_model("merge_scale", state_model) self._set_on_state_model("merge_shift", state_model) self._set_on_state_model("merge_scale_fit", state_model) self._set_on_state_model("merge_shift_fit", state_model) self._set_on_state_model("merge_q_range_start", state_model) self._set_on_state_model("merge_q_range_stop", state_model) self._set_on_state_model("merge_mask", state_model) self._set_on_state_model("merge_max", state_model) self._set_on_state_model("merge_min", state_model) # Settings tab self._set_on_state_model("reduction_dimensionality", state_model) self._set_on_state_model("reduction_mode", state_model) self._set_on_state_model("event_slices", state_model) self._set_on_state_model("event_binning", state_model) self._set_on_state_model("wavelength_step_type", state_model) self._set_on_state_model("wavelength_min", state_model) self._set_on_state_model("wavelength_max", state_model) self._set_on_state_model("wavelength_step", state_model) self._set_on_state_model("wavelength_range", state_model) self._set_on_state_model("absolute_scale", state_model) self._set_on_state_model("sample_shape", state_model) self._set_on_state_model("sample_height", state_model) self._set_on_state_model("sample_width", state_model) self._set_on_state_model("sample_thickness", state_model) self._set_on_state_model("z_offset", state_model) # Adjustment tab self._set_on_state_model("normalization_incident_monitor", state_model) self._set_on_state_model("normalization_interpolate", state_model) self._set_on_state_model("transmission_incident_monitor", state_model) self._set_on_state_model("transmission_interpolate", state_model) self._set_on_state_model("transmission_roi_files", state_model) self._set_on_state_model("transmission_mask_files", state_model) self._set_on_state_model("transmission_radius", state_model) self._set_on_state_model("transmission_monitor", state_model) self._set_on_state_model("transmission_mn_shift", state_model) self._set_on_state_model("show_transmission", state_model) self._set_on_state_model_transmission_fit(state_model) self._set_on_state_model("pixel_adjustment_det_1", state_model) self._set_on_state_model("pixel_adjustment_det_2", state_model) self._set_on_state_model("wavelength_adjustment_det_1", state_model) self._set_on_state_model("wavelength_adjustment_det_2", state_model) # Q tab self._set_on_state_model_q_1d_rebin_string(state_model) self._set_on_state_model("q_xy_max", state_model) self._set_on_state_model("q_xy_step", state_model) self._set_on_state_model("q_xy_step_type", state_model) self._set_on_state_model("gravity_on_off", state_model) self._set_on_state_model("gravity_extra_length", state_model) self._set_on_state_model("use_q_resolution", state_model) self._set_on_state_model("q_resolution_source_a", state_model) self._set_on_state_model("q_resolution_sample_a", state_model) self._set_on_state_model("q_resolution_source_h", state_model) self._set_on_state_model("q_resolution_sample_h", state_model) self._set_on_state_model("q_resolution_source_w", state_model) self._set_on_state_model("q_resolution_sample_w", state_model) self._set_on_state_model("q_resolution_delta_r", state_model) self._set_on_state_model("q_resolution_collimation_length", state_model) self._set_on_state_model("q_resolution_moderator_file", state_model) self._set_on_state_model("r_cut", state_model) self._set_on_state_model("w_cut", state_model) # Mask self._set_on_state_model("phi_limit_min", state_model) self._set_on_state_model("phi_limit_max", state_model) self._set_on_state_model("phi_limit_use_mirror", state_model) self._set_on_state_model("radius_limit_min", state_model) self._set_on_state_model("radius_limit_max", state_model) # Beam Centre self._beam_centre_presenter.set_on_state_model("lab_pos_1", state_model) self._beam_centre_presenter.set_on_state_model("lab_pos_2", state_model) return state_model def _set_on_state_model_transmission_fit(self, state_model): # Behaviour depends on the selection of the fit if self._view.use_same_transmission_fit_setting_for_sample_and_can(): use_fit = self._view.transmission_sample_use_fit fit_type = self._view.transmission_sample_fit_type polynomial_order = self._view.transmission_sample_polynomial_order state_model.transmission_sample_fit_type = fit_type if use_fit else FitType.NoFit state_model.transmission_can_fit_type = fit_type if use_fit else FitType.NoFit state_model.transmission_sample_polynomial_order = polynomial_order state_model.transmission_can_polynomial_order = polynomial_order # Wavelength settings if self._view.transmission_sample_use_wavelength: wavelength_min = self._view.transmission_sample_wavelength_min wavelength_max = self._view.transmission_sample_wavelength_max state_model.transmission_sample_wavelength_min = wavelength_min state_model.transmission_sample_wavelength_max = wavelength_max state_model.transmission_can_wavelength_min = wavelength_min state_model.transmission_can_wavelength_max = wavelength_max else: # Sample use_fit_sample = self._view.transmission_sample_use_fit fit_type_sample = self._view.transmission_sample_fit_type polynomial_order_sample = self._view.transmission_sample_polynomial_order state_model.transmission_sample_fit_type = fit_type_sample if use_fit_sample else FitType.NoFit state_model.transmission_sample_polynomial_order = polynomial_order_sample # Wavelength settings if self._view.transmission_sample_use_wavelength: wavelength_min = self._view.transmission_sample_wavelength_min wavelength_max = self._view.transmission_sample_wavelength_max state_model.transmission_sample_wavelength_min = wavelength_min state_model.transmission_sample_wavelength_max = wavelength_max # Can use_fit_can = self._view.transmission_can_use_fit fit_type_can = self._view.transmission_can_fit_type polynomial_order_can = self._view.transmission_can_polynomial_order state_model.transmission_can_fit_type = fit_type_can if use_fit_can else FitType.NoFit state_model.transmission_can_polynomial_order = polynomial_order_can # Wavelength settings if self._view.transmission_can_use_wavelength: wavelength_min = self._view.transmission_can_wavelength_min wavelength_max = self._view.transmission_can_wavelength_max state_model.transmission_can_wavelength_min = wavelength_min state_model.transmission_can_wavelength_max = wavelength_max def _set_on_state_model_q_1d_rebin_string(self, state_model): q_1d_step_type = self._view.q_1d_step_type # If we are dealing with a simple rebin string then the step type is None if self._view.q_1d_step_type is None: state_model.q_1d_rebin_string = self._view.q_1d_min_or_rebin_string else: q_1d_min = self._view.q_1d_min_or_rebin_string q_1d_max = self._view.q_1d_max q_1d_step = self._view.q_1d_step if q_1d_min and q_1d_max and q_1d_step and q_1d_step_type: q_1d_rebin_string = str(q_1d_min) + "," q_1d_step_type_factor = -1. if q_1d_step_type is RangeStepType.Log else 1. q_1d_rebin_string += str( q_1d_step_type_factor * q_1d_step) + "," q_1d_rebin_string += str(q_1d_max) state_model.q_1d_rebin_string = q_1d_rebin_string def _set_on_state_model(self, attribute_name, state_model): attribute = getattr(self._view, attribute_name) if attribute is not None and attribute != '': setattr(state_model, attribute_name, attribute) def get_cell_value(self, row, column): return self._view.get_cell(row=row, column=self.table_index[column], convert_to=str) # ------------------------------------------------------------------------------------------------------------------ # Settings # ------------------------------------------------------------------------------------------------------------------ def _setup_instrument_specific_settings(self, instrument=None): if not instrument: instrument = self._view.instrument self._view.set_instrument_settings(instrument) self._beam_centre_presenter.on_update_instrument(instrument) self._workspace_diagnostic_presenter.set_instrument_settings( instrument)
class BeamCentrePresenterTest(unittest.TestCase): def setUp(self): self.parent_presenter = create_run_tab_presenter_mock( use_fake_state=False) self.parent_presenter._file_information = mock.MagicMock() self.parent_presenter._file_information.get_instrument = mock.MagicMock( return_value=SANSInstrument.LARMOR) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel, self.SANSCentreFinder) def test_that_on_run_clicked_calls_find_beam_centre(self): self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertTrue(self.presenter._work_handler.process.call_args[0][1] == self.presenter._beam_centre_model.find_beam_centre) def test_that_on_run_clicked_updates_model_from_view(self): self.view.left_right = False self.view.lab_pos_1 = 100 self.view.lab_pos_2 = -100 self.presenter.set_view(self.view) self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter.on_run_clicked() self.assertFalse(self.presenter._beam_centre_model.left_right) self.assertEqual(self.presenter._beam_centre_model.lab_pos_1, 0.1) self.assertEqual(self.presenter._beam_centre_model.lab_pos_2, -0.1) def test_that_set_options_is_called_on_update_rows(self): self.presenter.set_view(self.view) self.view.set_options.assert_called_once_with( self.presenter._beam_centre_model) self.presenter.on_update_rows() self.assertTrue(self.view.set_options.call_count == 2) def test_that_model_reset_to_defaults_for_instrument_is_called_on_update_rows( self): self.presenter.set_view(self.view) self.presenter.on_update_rows() self.assertEqual( self.presenter._beam_centre_model.reset_to_defaults_for_instrument. call_count, 1) def test_that_set_scaling_is_called_on_update_instrument(self): self.presenter.set_view(self.view) self.presenter.on_update_instrument(SANSInstrument.LARMOR) self.presenter._beam_centre_model.set_scaling.assert_called_once_with( SANSInstrument.LARMOR) def test_that_view_on_update_instrument_is_called_on_update_instrument( self): self.presenter.set_view(self.view) self.presenter.on_update_instrument(SANSInstrument.LARMOR) self.view.on_update_instrument.assert_called_once_with( SANSInstrument.LARMOR) def test_that_on_processing_finished_updates_view_and_model(self): self.presenter.set_view(self.view) result = {'pos1': 0.1, 'pos2': -0.1} self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter._beam_centre_model.update_lab = True self.presenter._beam_centre_model.update_hab = True self.presenter.on_processing_finished_centre_finder(result) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.lab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.lab_pos_2) self.assertEqual( self.view.lab_pos_1, self.presenter._beam_centre_model.scale_1 * result['pos1']) self.assertEqual( self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2 * result['pos2']) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.hab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.hab_pos_2) self.assertEqual( self.view.hab_pos_1, self.presenter._beam_centre_model.scale_1 * result['pos1']) self.assertEqual( self.view.hab_pos_2, self.presenter._beam_centre_model.scale_2 * result['pos2']) self.view.set_run_button_to_normal.assert_called_once_with() def test_that_on_processing_finished_updates_does_not_update_view_and_model_when_update_disabled( self): self.presenter.set_view(self.view) result = {'pos1': 0.1, 'pos2': -0.1} result_1 = {'pos1': 0.2, 'pos2': -0.2} self.presenter._beam_centre_model.scale_1 = 1000 self.presenter._beam_centre_model.scale_2 = 1000 self.presenter._beam_centre_model.update_lab = True self.presenter._beam_centre_model.update_hab = True self.presenter.on_processing_finished_centre_finder(result) self.presenter._beam_centre_model.update_lab = False self.presenter._beam_centre_model.update_hab = False self.presenter.on_processing_finished_centre_finder(result_1) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.lab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.lab_pos_2) self.assertEqual( self.view.lab_pos_1, self.presenter._beam_centre_model.scale_1 * result['pos1']) self.assertEqual( self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2 * result['pos2']) self.assertEqual(result['pos1'], self.presenter._beam_centre_model.hab_pos_1) self.assertEqual(result['pos2'], self.presenter._beam_centre_model.hab_pos_2) self.assertEqual( self.view.hab_pos_1, self.presenter._beam_centre_model.scale_1 * result['pos1']) self.assertEqual( self.view.hab_pos_2, self.presenter._beam_centre_model.scale_2 * result['pos2']) self.assertEqual(self.view.set_run_button_to_normal.call_count, 2)
class BeamCentrePresenterTest(unittest.TestCase): def setUp(self): self.parent_presenter = create_run_tab_presenter_mock(use_fake_state = False) self.view = create_mock_beam_centre_tab() self.WorkHandler = mock.MagicMock() self.BeamCentreModel = mock.MagicMock() self.SANSCentreFinder = mock.MagicMock() self.presenter = BeamCentrePresenter(self.parent_presenter, beam_centre_model=self.BeamCentreModel) self.presenter.connect_signals = mock.Mock() self.presenter.set_view(self.view) self.presenter._worker = mock.create_autospec(self.presenter._worker) def test_that_on_run_clicked_calls_find_beam_centre(self): self.presenter.on_run_clicked() self.presenter._worker.find_beam_centre.assert_called_once_with( mock.ANY, self.BeamCentreModel.pack_beam_centre_settings.return_value) def test_beam_centre_finder_update_handles_str(self): # Since the view might give us mixed types check we can handle str model = self.BeamCentreModel model.rear_pos_1 = "1" model.rear_pos_2 = 1 model.front_pos_1 = 2 model.front_pos_2 = "2" self.presenter.on_processing_finished_centre_finder() for i in [self.view.rear_pos_1, self.view.rear_pos_2]: self.assertEquals(1, i) for i in [self.view.front_pos_1, self.view.front_pos_2]: self.assertEquals(2, i) def test_that_on_run_clicked_updates_model_from_view(self): self.view.left_right = False self.view.rear_pos_1 = 100 self.view.rear_pos_2 = -100 self.presenter.set_view(self.view) self.presenter.on_run_clicked() self.assertFalse(self.presenter._beam_centre_model.left_right) self.assertEqual(self.presenter._beam_centre_model.rear_pos_1, 100) self.assertEqual(self.presenter._beam_centre_model.rear_pos_2, -100) def test_on_update_rows_skips_no_rows(self): self.parent_presenter.num_rows.return_value = 0 self.assertFalse(self.WorkHandler.process.called) self.presenter.on_update_rows() def test_reset_inst_defaults_called_on_update_rows(self): self.parent_presenter.num_rows.return_value = 1 self.parent_presenter.instrument = SANSInstrument.SANS2D self.presenter.on_update_rows() self.BeamCentreModel.reset_inst_defaults.assert_called_with(SANSInstrument.SANS2D) def test_that_on_processing_finished_updates_view(self): self.presenter.set_view(self.view) self.BeamCentreModel.rear_pos_1 = 1 self.BeamCentreModel.rear_pos_2 = 2 self.BeamCentreModel.front_pos_1 = 3 self.BeamCentreModel.front_pos_2 = 4 self.presenter.on_processing_finished_centre_finder() self.assertEqual(self.BeamCentreModel.rear_pos_1, self.view.rear_pos_1) self.assertEqual(self.BeamCentreModel.rear_pos_2, self.view.rear_pos_2) self.assertEqual(self.BeamCentreModel.front_pos_1, self.view.front_pos_1) self.assertEqual(self.BeamCentreModel.front_pos_2, self.view.front_pos_2) self.view.set_run_button_to_normal.assert_called_once_with() def test_on_update_positions_handles_strings(self): self.BeamCentreModel.rear_pos_1 = "foo" self.BeamCentreModel.rear_pos_2 = "" self.presenter.update_centre_positions() self.assertEqual("foo", self.view.rear_pos_1) self.assertEqual("", self.view.rear_pos_2) def test_on_success_forwards_new_vals(self): expected_vals = mock.NonCallableMock() self.presenter.on_update_centre_values(expected_vals) self.BeamCentreModel.update_centre_positions.assert_called_once_with(expected_vals) def test_that_update_front_selected_enabled_front_and_disabled_rear(self): self.presenter.set_view(self.view) self.presenter.update_front_selected() # Check that the model has been updated self.assertTrue(self.presenter._beam_centre_model.update_front) self.assertFalse(self.presenter._beam_centre_model.update_rear) # Check that we called a method to enable the front and a method to disable the rear self.presenter._view.enable_update_front.assert_called_once_with(True) self.presenter._view.enable_update_rear.assert_called_once_with(False) def test_that_update_rear_selected_enabled_rear_and_disabled_front(self): self.presenter.set_view(self.view) self.presenter.update_rear_selected() # Check that the model has been updated self.assertFalse(self.presenter._beam_centre_model.update_front) self.assertTrue(self.presenter._beam_centre_model.update_rear) # Check that we called a method to disable the front and a method to enable the rear self.presenter._view.enable_update_front.assert_called_once_with(False) self.presenter._view.enable_update_rear.assert_called_once_with(True) def test_that_update_all_selected_enabled_front_and_rear(self): self.presenter.set_view(self.view) self.presenter.update_all_selected() # Check that the model has been updated self.assertTrue(self.presenter._beam_centre_model.update_front) self.assertTrue(self.presenter._beam_centre_model.update_rear) # Check that we called a method to enable the front and a method to enable the rear self.presenter._view.enable_update_front.assert_called_once_with(True) self.presenter._view.enable_update_rear.assert_called_once_with(True) def test_copies_into_internal_model(self): mocked_external_model = mock.NonCallableMock() attr_list = ["rear_pos_1", "rear_pos_2", "front_pos_1", "front_pos_2"] for attr in attr_list: setattr(mocked_external_model, attr, random.randint(0, 1000)/100) # Simulate a random FP value self.presenter.copy_centre_positions(mocked_external_model) for attr in attr_list: self.assertEqual(getattr(mocked_external_model, attr), getattr(self.presenter._beam_centre_model, attr)) def test_copies_without_front_into_internal_model(self): mocked_external_model = mock.NonCallableMock() rear_attr = ["rear_pos_1", "rear_pos_2"] front_list = ["front_pos_1", "front_pos_2"] for attr in front_list: # On instruments with no front this can be a blank string setattr(mocked_external_model, attr, '') for attr in rear_attr: setattr(mocked_external_model, attr, random.randint(0, 1000)/100) # Simulate a random FP value self.presenter.copy_centre_positions(mocked_external_model) for attr in rear_attr: self.assertEqual(getattr(mocked_external_model, attr), getattr(self.presenter._beam_centre_model, attr)) # When front isn't present we should take rear values for front_attr, rear_attr in zip(front_list, rear_attr): self.assertEqual(getattr(mocked_external_model, rear_attr), getattr(self.presenter._beam_centre_model, front_attr)) def test_on_update_rows_updates_centres(self): # As the rear centres can update when the rows change (due to different run number groups) # we should always update from the model self.presenter.update_centre_positions = mock.Mock() self.presenter.on_update_rows() self.presenter.update_centre_positions.assert_called_once() def test_update_center_positions_with_valid_front(self): self.BeamCentreModel.rear_pos_1 = 1.1 self.BeamCentreModel.rear_pos_2 = 1.2 self.BeamCentreModel.front_pos_1 = 2.1 self.BeamCentreModel.front_pos_2 = 2.2 self.presenter.update_centre_positions() self.assertEqual(1.1, self.view.rear_pos_1) self.assertEqual(1.2, self.view.rear_pos_2) self.assertEqual(2.1, self.view.front_pos_1) self.assertEqual(2.2, self.view.front_pos_2) def test_update_center_positions_with_empty_front(self): self.BeamCentreModel.rear_pos_1 = 1.1 self.BeamCentreModel.rear_pos_2 = 1.2 self.BeamCentreModel.front_pos_1 = "" self.BeamCentreModel.front_pos_2 = "" self.presenter.update_centre_positions() self.assertEqual(1.1, self.view.rear_pos_1) self.assertEqual(1.2, self.view.rear_pos_2) self.assertEqual(self.BeamCentreModel.rear_pos_1, self.view.front_pos_1) self.assertEqual(self.BeamCentreModel.rear_pos_2, self.view.front_pos_2) def test_update_center_positions_with_zero_front(self): self.BeamCentreModel.rear_pos_1 = 1.1 self.BeamCentreModel.rear_pos_2 = 1.2 # Zero is a valid value, so should be respected self.BeamCentreModel.front_pos_1 = 0. self.BeamCentreModel.front_pos_2 = 0. self.presenter.update_centre_positions() self.assertEqual(1.1, self.view.rear_pos_1) self.assertEqual(1.2, self.view.rear_pos_2) self.assertEqual(0., self.view.front_pos_1) self.assertEqual(0., self.view.front_pos_2)