class BeamCentrePresenter(object): class ConcreteBeamCentreListener(BeamCentre.BeamCentreListener): def __init__(self, presenter): self._presenter = presenter def on_run_clicked(self): self._presenter.on_run_clicked() class CentreFinderListener(WorkHandler.WorkListener): def __init__(self, presenter): super(BeamCentrePresenter.CentreFinderListener, self).__init__() self._presenter = presenter def on_processing_finished(self, result): self._presenter.on_processing_finished_centre_finder(result) def on_processing_error(self, error): self._presenter.on_processing_error_centre_finder(error) def __init__(self, parent_presenter, SANSCentreFinder, work_handler=None, beam_centre_model=None): self._view = None self._parent_presenter = parent_presenter self._work_handler = WorkHandler( ) if not work_handler else work_handler self._logger = Logger("SANS") self._beam_centre_model = BeamCentreModel( SANSCentreFinder) if not beam_centre_model else beam_centre_model def set_view(self, view): if view: self._view = view # Set up run listener listener = BeamCentrePresenter.ConcreteBeamCentreListener(self) self._view.add_listener(listener) # Set the default gui self._view.set_options(self._beam_centre_model) # Connect view signals self.connect_signals() def connect_signals(self): self._view.r_min_line_edit.textChanged.connect( self._validate_radius_values) self._view.r_max_line_edit.textChanged.connect( self._validate_radius_values) def on_update_instrument(self, instrument): self._beam_centre_model.set_scaling(instrument) self._view.on_update_instrument(instrument) def on_update_rows(self): self._beam_centre_model.reset_inst_defaults( self._parent_presenter.instrument) def on_processing_finished_centre_finder(self, result): # Enable button self._view.set_run_button_to_normal() # Update Centre Positions in model and GUI if self._beam_centre_model.update_lab: self._beam_centre_model.lab_pos_1 = result['pos1'] self._beam_centre_model.lab_pos_2 = result['pos2'] self._view.lab_pos_1 = self._beam_centre_model.lab_pos_1 * self._beam_centre_model.scale_1 self._view.lab_pos_2 = self._beam_centre_model.lab_pos_2 * self._beam_centre_model.scale_2 if self._beam_centre_model.update_hab: self._beam_centre_model.hab_pos_1 = result['pos1'] self._beam_centre_model.hab_pos_2 = result['pos2'] self._view.hab_pos_1 = self._beam_centre_model.hab_pos_1 * self._beam_centre_model.scale_1 self._view.hab_pos_2 = self._beam_centre_model.hab_pos_2 * self._beam_centre_model.scale_2 def on_processing_error_centre_finder(self, error): self._logger.warning( "There has been an error. See more: {}".format(error)) self._view.set_run_button_to_normal() def on_processing_error(self, error): self._view.set_run_button_to_normal() def on_run_clicked(self): self._work_handler.wait_for_done() # Get the state information for the first row. state = self._parent_presenter.get_state_for_row(0) if not state: self._logger.information( "You can only calculate the beam centre if a user file has been loaded and there" "valid sample scatter entry has been provided in the selected row." ) return # Disable the button self._view.set_run_button_to_processing() #Update model self._update_beam_model_from_view() # Run the task listener = BeamCentrePresenter.CentreFinderListener(self) state_copy = copy.copy(state) self._work_handler.process(listener, self._beam_centre_model.find_beam_centre, 0, state_copy) def _update_beam_model_from_view(self): self._beam_centre_model.r_min = self._view.r_min self._beam_centre_model.r_max = self._view.r_max self._beam_centre_model.max_iterations = self._view.max_iterations self._beam_centre_model.tolerance = self._view.tolerance self._beam_centre_model.left_right = self._view.left_right self._beam_centre_model.verbose = self._view.verbose self._beam_centre_model.COM = self._view.COM self._beam_centre_model.up_down = self._view.up_down self._beam_centre_model.lab_pos_1 = self._view.lab_pos_1 / self._beam_centre_model.scale_1 self._beam_centre_model.lab_pos_2 = self._view.lab_pos_2 / self._beam_centre_model.scale_2 self._beam_centre_model.hab_pos_1 = self._view.hab_pos_1 / self._beam_centre_model.scale_1 self._beam_centre_model.hab_pos_2 = self._view.hab_pos_2 / self._beam_centre_model.scale_2 self._beam_centre_model.q_min = self._view.q_min self._beam_centre_model.q_max = self._view.q_max self._beam_centre_model.component = self._view.component self._beam_centre_model.update_hab = self._view.update_hab self._beam_centre_model.update_lab = self._view.update_lab def update_centre_positions(self, state_model): lab_pos_1 = getattr(state_model, 'lab_pos_1') lab_pos_2 = getattr(state_model, 'lab_pos_2') hab_pos_1 = getattr(state_model, 'hab_pos_1') if getattr( state_model, 'hab_pos_1') else lab_pos_1 hab_pos_2 = getattr(state_model, 'hab_pos_2') if getattr( state_model, 'hab_pos_2') else lab_pos_2 self._view.lab_pos_1 = lab_pos_1 self._view.lab_pos_2 = lab_pos_2 self._view.hab_pos_1 = hab_pos_1 self._view.hab_pos_2 = hab_pos_2 def update_hab_selected(self): self._beam_centre_model.update_hab = True self._beam_centre_model.update_lab = False # HAB is selected, so ensure update HAB is enabled and checked self._view.enable_update_hab(True) # Disable and deselect update LAB self._view.enable_update_lab(False) def update_lab_selected(self): self._beam_centre_model.update_hab = False self._beam_centre_model.update_lab = True # LAB is selected, so ensure update LAB is enabled and checked self._view.enable_update_lab(True) # Disable and deselect update HAB self._view.enable_update_hab(False) def update_all_selected(self): self._beam_centre_model.update_hab = True self._beam_centre_model.update_lab = True self._view.enable_update_hab(True) self._view.enable_update_lab(True) 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 set_on_view(self, attribute_name, state_model): attribute = getattr(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 _validate_radius_values(self): min_value = getattr(self._view, "r_min_line_edit").text() max_value = getattr(self._view, "r_max_line_edit").text() try: min_value = float(min_value) max_value = float(max_value) except ValueError: # one of the values is empty pass else: if min_value == max_value == 0: self._view.run_button.setEnabled(False) return if min_value >= max_value: if self._view.run_button.isEnabled(): # Only post to logger once per disabling self._logger.notice( "Minimum radius is larger than maximum radius. " "Cannot find beam centre with current settings.") self._view.run_button.setEnabled(False) else: self._view.run_button.setEnabled(True)
class BeamCentrePresenter(object): class ConcreteBeamCentreListener(BeamCentre.BeamCentreListener): def __init__(self, presenter): self._presenter = presenter def on_run_clicked(self): self._presenter.on_run_clicked() def __init__(self, parent_presenter, beam_centre_model=None): self._view = None self._parent_presenter = parent_presenter self._logger = Logger("SANS") self._beam_centre_model = BeamCentreModel( ) if not beam_centre_model else beam_centre_model self._worker = BeamCentreAsync(parent_presenter=self) def set_view(self, view): if view: self._view = view # Set up run listener listener = BeamCentrePresenter.ConcreteBeamCentreListener(self) self._view.add_listener(listener) # Set the default gui self._view.set_options(self._beam_centre_model) # Connect view signals self.connect_signals() def connect_signals(self): self._view.r_min_line_edit.textChanged.connect( self._validate_radius_values) self._view.r_max_line_edit.textChanged.connect( self._validate_radius_values) def on_update_instrument(self, instrument): self._view.on_update_instrument(instrument) def on_update_rows(self): self._beam_centre_model.reset_inst_defaults( self._parent_presenter.instrument) self.update_centre_positions() def on_update_centre_values(self, new_vals: Dict): self._beam_centre_model.update_centre_positions(new_vals) def on_processing_finished_centre_finder(self): # Enable button self._view.set_run_button_to_normal() # Update Centre Positions in model and GUI self._view.rear_pos_1 = self._round(self._beam_centre_model.rear_pos_1) self._view.rear_pos_2 = self._round(self._beam_centre_model.rear_pos_2) self._view.front_pos_1 = self._round( self._beam_centre_model.front_pos_1) self._view.front_pos_2 = self._round( self._beam_centre_model.front_pos_2) def on_processing_error_centre_finder(self, error): self._logger.warning( "There has been an error. See more: {}".format(error)) self._view.set_run_button_to_normal() def on_processing_finished(self): # Signal from run tab presenter self._view.set_run_button_to_normal() def on_processing_error(self, error): # Signal from run tab presenter self._view.set_run_button_to_normal() def on_run_clicked(self): UsageService.registerFeatureUsage( FeatureType.Feature, ["ISIS SANS", "Beam Centre Finder - Run"], False) # Get the state information for the first row. state = self._parent_presenter.get_state_for_row(0) if not state: self._logger.information( "You can only calculate the beam centre if a user file has been loaded and there" "valid sample scatter entry has been provided in the selected row." ) return # Disable the button self._view.set_run_button_to_processing() self._update_beam_model_from_view() # Run the task state_copy = copy.copy(state) self._worker.find_beam_centre( state_copy, self._beam_centre_model.pack_beam_centre_settings()) def _update_beam_model_from_view(self): self._beam_centre_model.r_min = self._view.r_min self._beam_centre_model.r_max = self._view.r_max self._beam_centre_model.max_iterations = self._view.max_iterations self._beam_centre_model.tolerance = self._view.tolerance self._beam_centre_model.left_right = self._view.left_right self._beam_centre_model.verbose = self._view.verbose self._beam_centre_model.COM = self._view.COM self._beam_centre_model.up_down = self._view.up_down self._beam_centre_model.rear_pos_1 = self._view.rear_pos_1 self._beam_centre_model.rear_pos_2 = self._view.rear_pos_2 self._beam_centre_model.front_pos_1 = self._view.front_pos_1 self._beam_centre_model.front_pos_2 = self._view.front_pos_2 self._beam_centre_model.q_min = self._view.q_min self._beam_centre_model.q_max = self._view.q_max self._beam_centre_model.component = self._view.component self._beam_centre_model.update_front = self._view.update_front self._beam_centre_model.update_rear = self._view.update_rear def copy_centre_positions(self, state_model): """ Copies rear / front positions from an external model """ rear_pos_1 = getattr(state_model, 'rear_pos_1') rear_pos_2 = getattr(state_model, 'rear_pos_2') self._beam_centre_model.rear_pos_1 = rear_pos_1 self._beam_centre_model.rear_pos_2 = rear_pos_2 self._beam_centre_model.front_pos_1 = \ getattr(state_model, 'front_pos_1') if getattr(state_model, 'front_pos_1') else rear_pos_1 self._beam_centre_model.front_pos_2 = \ getattr(state_model, 'front_pos_2') if getattr(state_model, 'front_pos_2') else rear_pos_2 def update_centre_positions(self): rear_pos_1 = self._beam_centre_model.rear_pos_1 rear_pos_2 = self._beam_centre_model.rear_pos_2 front_pos_1 = self._beam_centre_model.front_pos_1 if self._beam_centre_model.front_pos_1 == '' else rear_pos_1 front_pos_2 = self._beam_centre_model.front_pos_2 if self._beam_centre_model.front_pos_2 == '' else rear_pos_2 self._view.rear_pos_1 = self._round(rear_pos_1) self._view.rear_pos_2 = self._round(rear_pos_2) self._view.front_pos_1 = self._round(front_pos_1) self._view.front_pos_2 = self._round(front_pos_2) def update_front_selected(self): self._beam_centre_model.update_front = True self._beam_centre_model.update_rear = False # front is selected, so ensure update front is enabled and checked self._view.enable_update_front(True) # Disable and deselect update rear self._view.enable_update_rear(False) def update_rear_selected(self): self._beam_centre_model.update_front = False self._beam_centre_model.update_rear = True # rear is selected, so ensure update rear is enabled and checked self._view.enable_update_rear(True) # Disable and deselect update front self._view.enable_update_front(False) def update_all_selected(self): self._beam_centre_model.update_front = True self._beam_centre_model.update_rear = True self._view.enable_update_front(True) self._view.enable_update_rear(True) 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 set_on_view(self, attribute_name, state_model): attribute = getattr(state_model, attribute_name) # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. if attribute or isinstance(attribute, bool): setattr(self._view, attribute_name, attribute) def _round(self, val): DECIMAL_PLACES_CENTRE_POS = 3 try: val = float(val) except ValueError: return val return round(val, DECIMAL_PLACES_CENTRE_POS) def _validate_radius_values(self): min_value = getattr(self._view, "r_min_line_edit").text() max_value = getattr(self._view, "r_max_line_edit").text() try: min_value = float(min_value) max_value = float(max_value) except ValueError: # one of the values is empty pass else: if min_value == max_value == 0: self._view.run_button.setEnabled(False) return if min_value >= max_value: if self._view.run_button.isEnabled(): # Only post to logger once per disabling self._logger.notice( "Minimum radius is larger than maximum radius. " "Cannot find beam centre with current settings.") self._view.run_button.setEnabled(False) else: self._view.run_button.setEnabled(True)
class BeamCentreModelTest(unittest.TestCase): def setUp(self): self.result = {'pos1': 300, 'pos2': -300} self.centre_finder_instance = mock.MagicMock(return_value=self.result) self.SANSCentreFinder = mock.MagicMock( return_value=self.centre_finder_instance) self.beam_centre_model = BeamCentreModel(self.SANSCentreFinder) def test_that_model_initialises_with_correct_values(self): self.assertEqual(self.beam_centre_model.max_iterations, 10) self.assertEqual(self.beam_centre_model.r_min, 60) self.assertEqual(self.beam_centre_model.r_max, 280) self.assertEqual(self.beam_centre_model.left_right, True) self.assertEqual(self.beam_centre_model.up_down, True) self.assertEqual(self.beam_centre_model.tolerance, 0.0001251) self.assertEqual(self.beam_centre_model.lab_pos_1, '') self.assertEqual(self.beam_centre_model.lab_pos_2, '') self.assertEqual(self.beam_centre_model.hab_pos_2, '') self.assertEqual(self.beam_centre_model.hab_pos_1, '') self.assertEqual(self.beam_centre_model.scale_1, 1000) self.assertEqual(self.beam_centre_model.scale_2, 1000) self.assertEqual(self.beam_centre_model.COM, False) self.assertEqual(self.beam_centre_model.verbose, False) self.assertEqual(self.beam_centre_model.q_min, 0.01) self.assertEqual(self.beam_centre_model.q_max, 0.1) self.assertEqual(self.beam_centre_model.component, DetectorType.LAB) self.assertTrue(self.beam_centre_model.update_lab) self.assertTrue(self.beam_centre_model.update_hab) def test_all_other_hardcoded_inst_values_taken(self): for inst in { SANSInstrument.NO_INSTRUMENT, SANSInstrument.SANS2D, SANSInstrument.ZOOM }: self.beam_centre_model.reset_inst_defaults(instrument=inst) self.assertEqual(60, self.beam_centre_model.r_min) self.assertEqual(280, self.beam_centre_model.r_max) self.assertEqual(1000, self.beam_centre_model.scale_1) def test_loq_values_updated(self): self.beam_centre_model.reset_inst_defaults(SANSInstrument.LOQ) self.assertEqual(96, self.beam_centre_model.r_min) self.assertEqual(216, self.beam_centre_model.r_max) def test_that_can_update_model_values(self): self.beam_centre_model.scale_2 = 1.0 self.assertEqual(self.beam_centre_model.scale_2, 1.0) def test_that_correct_values_are_set_for_LARMOR(self): self.beam_centre_model.reset_inst_defaults( instrument=SANSInstrument.LARMOR) self.assertEqual(self.beam_centre_model.scale_1, 1.0) def test_that_find_beam_centre_calls_centre_finder_once_when_COM_is_False( self): state = mock.MagicMock() self.beam_centre_model.find_beam_centre(state) self.SANSCentreFinder.return_value.assert_called_once_with( state, r_min=self.beam_centre_model.r_min, r_max=self.beam_centre_model.r_max, max_iter=self.beam_centre_model.max_iterations, x_start=self.beam_centre_model.lab_pos_1, y_start=self.beam_centre_model.lab_pos_2, tolerance=self.beam_centre_model.tolerance, find_direction=FindDirectionEnum.ALL, reduction_method=True, verbose=False, component=DetectorType.LAB) def test_that_find_beam_centre_calls_centre_finder_twice_when_COM_is_TRUE( self): state = mock.MagicMock() self.beam_centre_model.COM = True self.beam_centre_model.find_beam_centre(state) self.assertEqual(self.SANSCentreFinder.return_value.call_count, 2) self.SANSCentreFinder.return_value.assert_called_with( state, r_min=self.beam_centre_model.r_min, r_max=self.beam_centre_model.r_max, max_iter=self.beam_centre_model.max_iterations, x_start=self.result['pos1'], y_start=self.result['pos2'], tolerance=self.beam_centre_model.tolerance, find_direction=FindDirectionEnum.ALL, reduction_method=True, verbose=False, component=DetectorType.LAB) self.SANSCentreFinder.return_value.assert_any_call( state, r_min=self.beam_centre_model.r_min, r_max=self.beam_centre_model.r_max, max_iter=self.beam_centre_model.max_iterations, x_start=self.beam_centre_model.lab_pos_1, y_start=self.beam_centre_model.lab_pos_2, tolerance=self.beam_centre_model.tolerance, find_direction=FindDirectionEnum.ALL, reduction_method=False, component=DetectorType.LAB)
class BeamCentreModelTest(unittest.TestCase): def setUp(self): self.result = {'pos1': 300, 'pos2': -300} self.centre_finder_instance = mock.MagicMock(return_value=self.result) self.SANSCentreFinder = mock.MagicMock( return_value=self.centre_finder_instance) self.beam_centre_model = BeamCentreModel() def test_that_model_initialises_with_correct_values(self): self.assertEqual(self.beam_centre_model.max_iterations, 10) self.assertEqual(self.beam_centre_model.r_min, 60) self.assertEqual(self.beam_centre_model.r_max, 280) self.assertEqual(self.beam_centre_model.left_right, True) self.assertEqual(self.beam_centre_model.up_down, True) self.assertEqual(self.beam_centre_model.tolerance, 0.0001251) self.assertEqual(self.beam_centre_model.rear_pos_1, '') self.assertEqual(self.beam_centre_model.rear_pos_2, '') self.assertEqual(self.beam_centre_model.front_pos_2, '') self.assertEqual(self.beam_centre_model.front_pos_1, '') self.assertEqual(self.beam_centre_model.COM, False) self.assertEqual(self.beam_centre_model.verbose, False) self.assertEqual(self.beam_centre_model.q_min, 0.01) self.assertEqual(self.beam_centre_model.q_max, 0.1) self.assertEqual(self.beam_centre_model.component, DetectorType.LAB) self.assertTrue(self.beam_centre_model.update_rear) self.assertTrue(self.beam_centre_model.update_front) def test_all_other_hardcoded_inst_values_taken(self): for inst in { SANSInstrument.NO_INSTRUMENT, SANSInstrument.SANS2D, SANSInstrument.ZOOM }: self.beam_centre_model.reset_inst_defaults(instrument=inst) self.assertEqual(60, self.beam_centre_model.r_min) self.assertEqual(280, self.beam_centre_model.r_max) def test_update_centre_positions_front_mode(self): expected_vals = {"pos1": 101.123, "pos2": 202.234} rear_vals_before = (self.beam_centre_model.rear_pos_1, self.beam_centre_model.rear_pos_2) self.beam_centre_model.component = DetectorType.HAB # Where HAB == front self.beam_centre_model.update_centre_positions(expected_vals) # mm -> m scaling self.assertEqual(expected_vals["pos1"] * 1000, self.beam_centre_model.front_pos_1) self.assertEqual(expected_vals["pos2"] * 1000, self.beam_centre_model.front_pos_2) self.assertEqual(rear_vals_before[0], self.beam_centre_model.rear_pos_1) self.assertEqual(rear_vals_before[1], self.beam_centre_model.rear_pos_2) def test_update_centre_positions_rear_mode(self): expected_vals = {"pos1": 303.345, "pos2": 404.456} front_vals_before = (self.beam_centre_model.front_pos_1, self.beam_centre_model.front_pos_2) self.beam_centre_model.component = DetectorType.LAB # Where LAB == rear self.beam_centre_model.update_centre_positions(expected_vals) # mm -> m scaling self.assertEqual(expected_vals["pos1"] * 1000, self.beam_centre_model.rear_pos_1) self.assertEqual(expected_vals["pos2"] * 1000, self.beam_centre_model.rear_pos_2) self.assertEqual(front_vals_before[0], self.beam_centre_model.front_pos_1) self.assertEqual(front_vals_before[1], self.beam_centre_model.front_pos_2) def test_loq_values_updated(self): self.beam_centre_model.reset_inst_defaults(SANSInstrument.LOQ) self.assertEqual(96, self.beam_centre_model.r_min) self.assertEqual(216, self.beam_centre_model.r_max) def test_that_can_update_model_values(self): self.beam_centre_model.r_max = 1.0 self.assertEqual(self.beam_centre_model.r_max, 1.0) def test_beam_centre_scales_to_mills(self): self.assertIsNot(SANSInstrument.LARMOR, self.beam_centre_model.instrument) value_in_mm = 1200 self.beam_centre_model.rear_pos_1 = value_in_mm self.beam_centre_model.front_pos_2 = value_in_mm * 2 self.assertEqual(value_in_mm, self.beam_centre_model.rear_pos_1) self.assertEqual((value_in_mm * 2), self.beam_centre_model.front_pos_2) # Should internally be in m self.assertEqual((value_in_mm / 1000), self.beam_centre_model._rear_pos_1) self.assertEqual((value_in_mm * 2 / 1000), self.beam_centre_model._front_pos_2) def test_beam_centre_does_not_scale(self): self.beam_centre_model.instrument = SANSInstrument.LARMOR value_in_m = 1.2 self.beam_centre_model.rear_pos_1 = value_in_m self.beam_centre_model.front_pos_1 = value_in_m * 2 self.assertEqual(value_in_m, self.beam_centre_model.rear_pos_1) self.assertEqual(value_in_m, self.beam_centre_model._rear_pos_1) self.assertEqual((value_in_m * 2), self.beam_centre_model.front_pos_1) self.assertEqual((value_in_m * 2), self.beam_centre_model._front_pos_1) def test_instrument_is_set(self): for inst in SANSInstrument: self.beam_centre_model.reset_inst_defaults(inst) self.assertEqual(inst, self.beam_centre_model.instrument) def test_scaling_does_not_affect_non_rear_front_values(self): value_in_mm = 100 # 0.1 m for inst in [SANSInstrument.LARMOR, SANSInstrument.LOQ]: self.beam_centre_model.instrument = inst self.beam_centre_model.tolerance = value_in_mm self.assertEqual((value_in_mm / 1000), self.beam_centre_model._tolerance) self.assertEqual(self.beam_centre_model.tolerance, value_in_mm) def test_scaling_can_handle_non_float_types(self): self.beam_centre_model.instrument = SANSInstrument.NO_INSTRUMENT # When in doubt it should just forward the value as is self.beam_centre_model.rear_pos_1 = 'a' self.assertEqual(self.beam_centre_model.rear_pos_1, 'a') def test_scaling_ignores_zero_vals(self): self.beam_centre_model.lab_pos_1 = 0.0 self.beam_centre_model.lab_pos_2 = 0.0 self.beam_centre_model.hab_pos_1 = 0.0 self.beam_centre_model.hab_pos_2 = 0.0 self.assertEqual(0.0, self.beam_centre_model.lab_pos_1) self.assertEqual(0.0, self.beam_centre_model.lab_pos_2) self.assertEqual(0.0, self.beam_centre_model.hab_pos_1) self.assertEqual(0.0, self.beam_centre_model.hab_pos_2)