diff --git a/Code/Mantid/scripts/test/SANSCentreFinderTest.py b/Code/Mantid/scripts/test/SANSCentreFinderTest.py new file mode 100644 index 0000000000000000000000000000000000000000..955269e543e76db6e80caa6662490bc25ef45188 --- /dev/null +++ b/Code/Mantid/scripts/test/SANSCentreFinderTest.py @@ -0,0 +1,270 @@ +import unittest +import mantid +from mantid.simpleapi import * +import centre_finder as cf +import ISISCommandInterface as command_iface +from reducer_singleton import ReductionSingleton +import isis_reduction_steps as reduction_steps + + +class SANSBeamCentrePositionUpdater(unittest.TestCase): + def test_that_find_ALL_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.ALL) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.1 + y_expected = 2.2 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + + def test_that_find_LEFTRIGHT_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.LEFT_RIGHT) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.1 + y_expected = 2.0 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + + def test_that_find_UPPDOWN_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.UP_DOWN) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.0 + y_expected = 2.2 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + +class TestPositionProvider(unittest.TestCase): + workspace_name = 'dummy_ws' + + class MockSample(object): + ''' + Mocking out the sample + ''' + def __init__(self, ws_name): + super(TestPositionProvider.MockSample,self).__init__() + self.wksp_name = ws_name + def get_wksp_name(self): + return self.wksp_name + + def _provide_reducer(self, is_larmor, is_new = True): + ''' + Provide a reducer with either Larmor or non-Larmor. If we have Larmor, + then we want to be able to set the run number as well + ''' + command_iface.Clean() + if is_larmor and is_new: + command_iface.LARMOR() + CreateSampleWorkspace(OutputWorkspace=self.workspace_name) + AddSampleLog(Workspace=self.workspace_name,LogName='run_number', LogText='3000', LogType='Number') + sample = self.MockSample(self.workspace_name) + ReductionSingleton()._sample_run = sample + return ReductionSingleton() + + elif is_larmor and not is_new: + command_iface.LARMOR() + CreateSampleWorkspace(OutputWorkspace=self.workspace_name) + AddSampleLog(Workspace=self.workspace_name,LogName='run_number', LogText='1000', LogType='Number') + sample = self.MockSample(self.workspace_name) + ReductionSingleton()._sample_run = sample + return ReductionSingleton() + else: + command_iface.LOQ() + return ReductionSingleton() + + def _clean_up(self, workspace_name): + if workspace_name in mtd.getObjectNames(): + mtd.remove(workspace_name) + + def test_that_XY_increment_provider_is_created_for_non_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = False + reducer = self._provide_reducer(is_larmor) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderXY), "Should create a XY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_YAngle_increment_provider_is_created_for_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = True + is_new = True + reducer = self._provide_reducer(is_larmor, is_new) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderAngleY), "Should create a AngleY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_XY_increment_provider_is_created_for_old_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = True + is_new = False + reducer = self._provide_reducer(is_larmor, is_new) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderXY), "Should create a XY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_XY_increment_provider_halves_the_step(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act and Assert + self.assertTrue(increment_coord1 == provider.get_increment_coord1()) + self.assertTrue(increment_coord2 == provider.get_increment_coord2()) + + provider.half_and_reverse_increment_coord1() + provider.half_and_reverse_increment_coord2() + + self.assertTrue(-increment_coord1/2.0 == provider.get_increment_coord1()) + self.assertTrue(-increment_coord2/2.0 == provider.get_increment_coord2()) + + def test_that_AngleY_increment_provider_halves_the_step(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + tolerance_angle = 33 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act and Assert + self.assertTrue(increment_coord1 == provider.get_increment_coord1()) + self.assertTrue(increment_coord2 == provider.get_increment_coord2()) + + provider.half_and_reverse_increment_coord1() + provider.half_and_reverse_increment_coord2() + + self.assertTrue(-increment_coord1/2.0 == provider.get_increment_coord1()) + self.assertTrue(-increment_coord2/2.0 == provider.get_increment_coord2()) + + def test_that_XY_increment_is_smaller_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertTrue(is_smaller_coord1) + self.assertTrue(is_smaller_coord2) + + def test_that_XY_increment_is_larger_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 0.2 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertFalse(is_smaller_coord1) + self.assertFalse(is_smaller_coord2) + + def test_that_AngleY_increment_is_smaller_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 100 + tolerance_angle = 233 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertTrue(is_smaller_coord1) + self.assertTrue(is_smaller_coord2) + + def test_that_AngleY_increment_is_larger_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 0.2 + tolerance_angle = 0.1 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertFalse(is_smaller_coord1) + self.assertFalse(is_smaller_coord2) + + +if __name__ == "__main__": + unittest.main() diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h index 432717a89f94aab0aa22890cd4e5e4bb8958ae20..0dc60514e8c56492f6a263d1a0f58d40ac7aeb7c 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -306,6 +306,10 @@ private slots: void onTransmissionRadiusCheckboxChanged(); /// Transmission setting for ROI files void onTransmissionROIFilesCheckboxChanged(); + /// React to change in Left/Right checkbox + void onLeftRightCheckboxChanged(); + /// React to change in Up/Down checkbox + void onUpDownCheckboxChanged(); private: /// used to specify the range of validation to do @@ -449,8 +453,12 @@ private: void checkWaveLengthAndQValues(bool &isValid, QString &message, QLineEdit *min, QLineEdit *max, QComboBox *selection, QString type); + /// Update the beam center fields + void updateBeamCenterCoordinates(); /// LOQ specific settings void applyLOQSettings(bool isNowLOQ); + /// Set the beam finder details + void setBeamFinderDetails(); UserSubWindow *slicingWindow; }; diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui index 8aabf49ddde2a81d1f1236a03cfc878865273fcb..984a9246bc607f29b47f881614a2fd3cec300f03 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui @@ -3201,9 +3201,9 @@ p, li { white-space: pre-wrap; } <property name="checkable"> <bool>false</bool> </property> - <layout class="QVBoxLayout" name="verticalLayout_12" stretch="1,1,2"> + <layout class="QVBoxLayout" name="verticalLayout_12" stretch="0,1,2"> <item> - <widget class="QGroupBox" name="groupBox_12"> + <widget class="QGroupBox" name="beam_centre_finder_groupbox"> <property name="title"> <string>Current ( x , y ) [mm]</string> </property> @@ -3271,6 +3271,37 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="1" column="3"> + <layout class="QHBoxLayout" name="horizontalLayout_24"> + <item> + <widget class="QCheckBox" name="left_right_checkbox"> + <property name="text"> + <string>Left/Right</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="up_down_checkbox"> + <property name="text"> + <string>Up/Down</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Find Beam Centre for:</string> + </property> + </widget> + </item> </layout> <zorder>rear_beam_y</zorder> <zorder>rear_radio</zorder> @@ -3278,6 +3309,7 @@ p, li { white-space: pre-wrap; } <zorder>front_beam_x</zorder> <zorder>front_radio</zorder> <zorder>rear_beam_x</zorder> + <zorder>label_16</zorder> </widget> </item> <item> diff --git a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp index 34f19ca07dfc7dcb706b601a631f14e895888ee7..fdb9b58f4dec0cb0c1b326bdd302e3c0b6d53e45 100644 --- a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp +++ b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp @@ -244,6 +244,8 @@ void SANSRunWindow::initLayout() { m_uiForm.centre_logging->attachLoggingChannel(); connect(m_uiForm.clear_centre_log, SIGNAL(clicked()), m_uiForm.centre_logging, SLOT(clear())); + connect(m_uiForm.up_down_checkbox, SIGNAL(stateChanged(int)), this, SLOT(onUpDownCheckboxChanged())); + connect(m_uiForm.left_right_checkbox, SIGNAL(stateChanged(int)), this, SLOT(onLeftRightCheckboxChanged())); // Create the widget hash maps initWidgetMaps(); @@ -422,7 +424,7 @@ void SANSRunWindow::setupSaveBox() { SLOT(enableOrDisableDefaultSave())); } } -/** Raises a saveWorkspaces dialog which allows people to save any workspace or +/** Raises a saveWorkspaces dialog which allows people to save any workspace * workspaces the user chooses */ void SANSRunWindow::saveWorkspacesDialog() { @@ -969,10 +971,10 @@ bool SANSRunWindow::loadUserFile() { m_uiForm.smpl_offset->setText(QString::number(dbl_param * unit_conv)); // Centre coordinates - // from the ticket #5942 both detectors have center coordinates - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[0]").toDouble(); + // Update the beam centre coordinates + updateBeamCenterCoordinates(); + // Set the beam finder specific settings + setBeamFinderDetails(); // get the scale factor1 for the beam centre to scale it correctly double dbl_paramsf = runReduceScriptFunction( @@ -997,7 +999,6 @@ bool SANSRunWindow::loadUserFile() { "print i.ReductionSingleton().get_beam_center('front')[1]") .toDouble(); m_uiForm.front_beam_y->setText(QString::number(dbl_param * 1000.0)); - // Gravity switch QString param = runReduceScriptFunction("print i.ReductionSingleton().to_Q.get_gravity()") @@ -2096,8 +2097,14 @@ bool SANSRunWindow::handleLoadButtonClick() { m_sample_file = sample; setProcessingState(Ready); m_uiForm.load_dataBtn->setText("Load Data"); + + // Update the beam center position + updateBeamCenterCoordinates(); + // Set the beam finder specific settings + setBeamFinderDetails(); return true; } + /** Queries the number of periods from the Python object whose name was passed * @param RunStep name of the RunStep Python object * @param output where the number will be displayed @@ -2721,6 +2728,8 @@ void SANSRunWindow::handleRunFindCentre() { if (m_uiForm.front_radio->isChecked()) py_code += "i.SetDetectorFloodFile('')\n"; + // We need to load the FinDirectionEnum class + py_code += "from centre_finder import FindDirectionEnum as FindDirectionEnum \n"; // Find centre function py_code += "i.FindBeamCentre(rlow=" + m_uiForm.beam_rmin->text() + ",rupp=" + m_uiForm.beam_rmax->text() + ",MaxIter=" + @@ -2747,9 +2756,21 @@ void SANSRunWindow::handleRunFindCentre() { setProcessingState(Ready); return; } - py_code += ", tolerance=" + QString::number(tolerance) + ")"; + py_code += ", tolerance=" + QString::number(tolerance); + + // Set which part of the beam centre finder should be used + auto updownIsRequired = m_uiForm.up_down_checkbox->isChecked(); + auto leftRightIsRequired = m_uiForm.left_right_checkbox->isChecked(); + if (updownIsRequired && leftRightIsRequired) { + py_code += ", find_direction=FindDirectionEnum.ALL"; + } else if (updownIsRequired) { + py_code += ", find_direction=FindDirectionEnum.UP_DOWN"; + } else if (leftRightIsRequired) { + py_code += ", find_direction=FindDirectionEnum.LEFT_RIGHT"; + } + py_code += ")"; - g_centreFinderLog.notice("Iteration 1\n"); + g_centreFinderLog.notice("Beam Centre Finder Start\n"); m_uiForm.beamstart_box->setFocus(); // Execute the code @@ -3996,6 +4017,29 @@ void SANSRunWindow::setM3M4Logic(TransSettings setting, bool isNowChecked) { this->m_uiForm.trans_roi_files_checkbox->setChecked(false); } +/** + * React to changes of the Up/Down checkbox + */ +void SANSRunWindow::onUpDownCheckboxChanged() { + auto checked = m_uiForm.up_down_checkbox->isChecked(); + if (m_uiForm.rear_radio->isChecked()) { + m_uiForm.rear_beam_y->setEnabled(checked); + } else { + m_uiForm.front_beam_y->setEnabled(checked); + } +} + +/** + * React to changes of the Left/Right checkbox + */ +void SANSRunWindow::onLeftRightCheckboxChanged() { + auto checked = m_uiForm.left_right_checkbox->isChecked(); + if (m_uiForm.rear_radio->isChecked()) { + m_uiForm.rear_beam_x->setEnabled(checked); + } else { + m_uiForm.front_beam_x->setEnabled(checked); + } +} /** * Set beam stop logic for Radius, ROI and Mask * @param setting :: the checked item @@ -4405,5 +4449,63 @@ void SANSRunWindow::checkWaveLengthAndQValues(bool &isValid, QString &message, } } +/** + * Update the beam centre coordinates + */ +void SANSRunWindow::updateBeamCenterCoordinates() { + // Centre coordinates + // from the ticket #5942 both detectors have center coordinates + double dbl_param = + runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[0]") + .toDouble(); + // get the scale factor1 for the beam centre to scale it correctly + double dbl_paramsf = + runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center_scale_factor1()") + .toDouble(); + m_uiForm.rear_beam_x->setText(QString::number(dbl_param * dbl_paramsf)); + // get scale factor2 for the beam centre to scale it correctly + dbl_paramsf = + runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center_scale_factor2()") + .toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[1]") + .toDouble(); + m_uiForm.rear_beam_y->setText(QString::number(dbl_param * dbl_paramsf)); + // front + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('front')[0]") + .toDouble(); + m_uiForm.front_beam_x->setText(QString::number(dbl_param * 1000.0)); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('front')[1]") + .toDouble(); + m_uiForm.front_beam_y->setText(QString::number(dbl_param * 1000.0)); +} + +/** + * Set the beam finder details + */ +void SANSRunWindow::setBeamFinderDetails() { + // The instrument name + auto instrumentName = m_uiForm.inst_opt->currentText(); + + // Set the labels according to the instrument + auto requiresAngle = runReduceScriptFunction( + "print i.is_current_workspace_an_angle_workspace()") + .simplified(); + QString labelPosition; + if (requiresAngle == m_constants.getPythonTrueKeyword()) { + labelPosition = "Current ( " + QString(QChar(0x03B2)) + " , y ) ["; + labelPosition.append(QChar(0xb0)); + labelPosition += ",mm]"; + } else { + labelPosition = "Current ( x , y ) [mm,mm]"; + } + m_uiForm.beam_centre_finder_groupbox->setTitle(labelPosition); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 7cd45300f74869139a60cf613343c6613fe322d6..ed23cdbc4dca37bcb9b589337e3e278249f466e8 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -31,6 +31,7 @@ set ( TEST_PY_FILES test/RunDescriptorTest.py test/SansIsisGuiSettings.py test/SANSBatchModeTest.py + test/SANSCentreFinderTest.py test/SANSCommandInterfaceTest.py test/SANSUtilityTest.py test/SANSIsisInstrumentTest.py diff --git a/scripts/SANS/ISISCommandInterface.py b/scripts/SANS/ISISCommandInterface.py index 3f06b3229bf67e2a871806a1a62459c5db70707b..41ab1de5f8bb008e1a871655be6858323e7b6f33 100644 --- a/scripts/SANS/ISISCommandInterface.py +++ b/scripts/SANS/ISISCommandInterface.py @@ -11,7 +11,7 @@ sanslog = Logger("SANS") import isis_reduction_steps import isis_reducer -from centre_finder import CentreFinder as CentreFinder +from centre_finder import * #import SANSReduction from mantid.simpleapi import * from mantid.api import WorkspaceGroup @@ -340,7 +340,8 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su @param wav_start: the first wavelength to be in the output data @param wav_end: the last wavelength in the output data - @param full_trans_wav: if to use a wide wavelength range, the instrument's default wavelength range, for the transmission correction, false by default + @param full_trans_wav: if to use a wide wavelength range, the instrument's default wavelength range, + for the transmission correction, false by default @param name_suffix: append the created output workspace with this @param combineDet: combineDet can be one of the following: 'rear' (run one reduction for the 'rear' detector data) @@ -350,7 +351,8 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su None (run one reduction for whatever detector has been set as the current detector before running this method. If front apply rescale+shift) @param resetSetup: if true reset setup at the end - @param out_fit_settings: An output parameter. It is used, specially when resetSetup is True, in order to remember the 'scale and fit' of the fitting algorithm. + @param out_fit_settings: An output parameter. It is used, specially when resetSetup is True, in order to remember the + 'scale and fit' of the fitting algorithm. @return Name of one of the workspaces created """ _printMessage('WavRangeReduction(' + str(wav_start) + ', ' + str(wav_end) + ', '+str(full_trans_wav)+')') @@ -674,7 +676,8 @@ def _WavRangeReduction(name_suffix=None): def _common_substring(val1, val2): l = [] for i in range(len(val1)): - if val1[i]==val2[i]: l.append(val1[i]) + if val1[i]==val2[i]: + l.append(val1[i]) else: return ''.join(l) @@ -878,9 +881,11 @@ def SetDetectorOffsets(bank, x, y, z, rot, radius, side, xtilt=0.0, ytilt=0.0 ): detector.y_tilt = ytilt def SetCorrectionFile(bank, filename): - # 10/03/15 RKH, create a new routine that allows change of "direct beam file" = correction file, for a given - # detector, this simplify the iterative process used to adjust it. Will still have to keep changing the name of the file - # for each iteratiom to avoid Mantid using a cached version, but can then use only a single user (=mask) file for each set of iterations. + # 10/03/15 RKH, create a new routine that allows change of "direct beam file" = correction file, + # for a given detector, this simplify the iterative process used to adjust it. + # Will still have to keep changing the name of the file + # for each iteratiom to avoid Mantid using a cached version, but can then use + # only a single user (=mask) file for each set of iterations. # Modelled this on SetDetectorOffsets above ... """ @param bank: Must be either 'front' or 'rear' (not case sensitive) @@ -905,8 +910,11 @@ def LimitsR(rmin, rmax, quiet=False, reducer=None): def LimitsWav(lmin, lmax, step, bin_type): _printMessage('LimitsWav(' + str(lmin) + ', ' + str(lmax) + ', ' + str(step) + ', ' + bin_type + ')') - if bin_type.upper().strip() == 'LINEAR': bin_type = 'LIN' - if bin_type.upper().strip() == 'LOGARITHMIC': bin_type = 'LOG' + if bin_type.upper().strip() == 'LINEAR': + bin_type = 'LIN' + if bin_type.upper().strip() == 'LOGARITHMIC': + bin_type = 'LOG' + if bin_type == 'LOG': bin_sym = '-' else: @@ -1061,7 +1069,7 @@ def createColetteScript(inputdata, format, reduced, centreit , plotresults, csvf return script -def FindBeamCentre(rlow, rupp, MaxIter = 10, xstart = None, ystart = None, tolerance=1.251e-4): +def FindBeamCentre(rlow, rupp, MaxIter = 10, xstart = None, ystart = None, tolerance=1.251e-4, find_direction = FindDirectionEnum.ALL): """ Estimates the location of the effective beam centre given a good initial estimate. For more information go to this page @@ -1069,20 +1077,27 @@ def FindBeamCentre(rlow, rupp, MaxIter = 10, xstart = None, ystart = None, toler @param rlow: mask around the (estimated) centre to this radius (in millimetres) @param rupp: don't include further out than this distance (mm) from the centre point @param MaxInter: don't calculate more than this number of iterations (default = 10) - @param xstart: initial guess for the horizontal distance of the beam centre from the - detector centre in meters (default the values in the mask file) - @param ystart: initial guess for the distance of the beam centre from the detector - centre vertically in metres (default the values in the mask file) - @param tolerance: define the precision of the search. If the step is smaller than the tolerance, - it will be considered stop searching the centre (default=1.251e-4 or 1.251um) + @param xstart: initial guess for the horizontal distance of the beam centre + from the detector centre in meters (default the values in the mask file), or in the + case of rotated instruments a rotation about the y axis. The unit is degree/XSF + @param ystart: initial guess for the distance of the beam centre from the detector centre + vertically in metres (default the values in the mask file) + @param tolerance: define the precision of the search. If the step is smaller than the + tolerance, it will be considered stop searching the centre (default=1.251e-4 or 1.251um) + @param find_only: if only Up/Down or only Left/Right is + required then variable is set to @return: the best guess for the beam centre point """ - XSTEP = ReductionSingleton().inst.cen_find_step - YSTEP = ReductionSingleton().inst.cen_find_step2 + COORD1STEP = ReductionSingleton().inst.cen_find_step + COORD2STEP = ReductionSingleton().inst.cen_find_step2 XSF = ReductionSingleton().inst.beam_centre_scale_factor1 YSF = ReductionSingleton().inst.beam_centre_scale_factor2 + coord1_scale_factor = XSF + coord2_scale_factor = YSF + # Here we have to be careful as the original position can be either in [m, m] or [degree, m], we need to make sure + # that we are consistent to not mix with [degree/XSF, m] original = ReductionSingleton().get_instrument().cur_detector_position(ReductionSingleton().get_sample().get_wksp_name()) if ReductionSingleton().instrument.lowAngDetSet: @@ -1096,43 +1111,61 @@ def FindBeamCentre(rlow, rupp, MaxIter = 10, xstart = None, ystart = None, toler float(xstart), float(ystart)),det_bank) beamcoords = ReductionSingleton().get_beam_center() - XNEW = beamcoords[0] - YNEW = beamcoords[1] - xstart = beamcoords[0] - ystart = beamcoords[1] - #remove this if we know running the Reducer() doesn't change i.e. all execute() methods are const centre_reduction = copy.deepcopy(ReductionSingleton().reference()) LimitsR(str(float(rlow)), str(float(rupp)), quiet=True, reducer=centre_reduction) - centre = CentreFinder(original) - centre.logger.notice("xstart,ystart="+str(XNEW*1000.)+" "+str(YNEW*1000.)) - centre.logger.notice("Starting centre finding routine ...") - #this function moves the detector to the beam center positions defined above and + # Create an object which handles the positions and increments + centre_positioner = CentrePositioner(reducer = centre_reduction, + position_type = find_direction, + coord1_start = beamcoords[0], + coord2_start = beamcoords[1], + coord1_step = COORD1STEP, + coord2_step = COORD2STEP, + tolerance = tolerance) + + # Produce the initial position + COORD1NEW, COORD2NEW = centre_positioner.produce_initial_position() + + # Set the CentreFinder + sign_policy = centre_positioner.produce_sign_policy() + centre = CentreFinder(original, sign_policy, find_direction) + + # Produce a logger for this the Beam Centre Finder + beam_center_logger = BeamCenterLogger(centre_reduction, + coord1_scale_factor, + coord2_scale_factor) + + # If we have 0 iterations then we should return here + if MaxIter <= 0: + zero_iterations_msg = ("You have selected 0 iterations. The beam centre" + + "will be positioned at (" + str(xstart) + ", " + str(ystart) +")") + beam_center_logger.report(zero_iterations_msg) + return xstart, ystart + + beam_center_logger.report_init(COORD1NEW, COORD2NEW) + + # this function moves the detector to the beam center positions defined above and # returns an estimate of where the beam center is relative to the new center - resX_old, resY_old = centre.SeekCentre(centre_reduction, [XNEW, YNEW]) + resCoord1_old, resCoord2_old = centre.SeekCentre(centre_reduction, [COORD1NEW, COORD2NEW]) centre_reduction = copy.deepcopy(ReductionSingleton().reference()) LimitsR(str(float(rlow)), str(float(rupp)), quiet=True, reducer=centre_reduction) - - logger.notice(centre.status_str(0, resX_old, resY_old)) - + beam_center_logger.report_status(0, original[0], original[1], resCoord1_old, resCoord2_old) # take first trial step - XNEW = xstart + XSTEP - YNEW = ystart + YSTEP + COORD1NEW, COORD2NEW = centre_positioner.increment_position(COORD1NEW, COORD2NEW) graph_handle = None it = 0 for i in range(1, MaxIter+1): it = i - centre_reduction.set_beam_finder( - isis_reduction_steps.BaseBeamFinder(XNEW, YNEW), det_bank) + isis_reduction_steps.BaseBeamFinder(COORD1NEW, COORD2NEW), det_bank) + resCoord1, resCoord2 = centre.SeekCentre(centre_reduction, [COORD1NEW, COORD2NEW]) - resX, resY = centre.SeekCentre(centre_reduction, [XNEW, YNEW]) centre_reduction = copy.deepcopy(ReductionSingleton().reference()) LimitsR(str(float(rlow)), str(float(rupp)), quiet=True, reducer=centre_reduction) - centre.logger.notice(centre.status_str(it, resX, resY)) + beam_center_logger.report_status(it, COORD1NEW, COORD2NEW, resCoord1, resCoord2) if mantidplot: try : @@ -1140,37 +1173,38 @@ def FindBeamCentre(rlow, rupp, MaxIter = 10, xstart = None, ystart = None, toler #once we have a plot it will be updated automatically when the workspaces are updated graph_handle = mantidplot.plotSpectrum(centre.QUADS, 0) graph_handle.activeLayer().setTitle(\ - centre.status_str(it, resX, resY)) + beam_center_logger.get_status_message(it, COORD1NEW, COORD2NEW, resCoord1, resCoord2)) except : #if plotting is not available it probably means we are running outside a GUI, in which case do everything but don't plot pass - #have we stepped across the y-axis that goes through the beam center? - if resX > resX_old: + if resCoord1 > resCoord1_old: # yes with stepped across the middle, reverse direction and half the step size - XSTEP = -XSTEP/2. - if resY > resY_old: - YSTEP = -YSTEP/2. - if abs(XSTEP) < tolerance and abs(YSTEP) < tolerance : + centre_positioner.set_new_increment_coord1() + if resCoord2 > resCoord2_old: + centre_positioner.set_new_increment_coord2() + if (centre_positioner.is_increment_coord1_smaller_than_tolerance() and + centre_positioner.is_increment_coord2_smaller_than_tolerance()): # this is the success criteria, we've close enough to the center - centre.logger.notice("Converged - check if stuck in local minimum!") + beam_center_logger.report("Converged - check if stuck in local minimum!") break - resX_old = resX - resY_old = resY - XNEW += XSTEP - YNEW += YSTEP + resCoord1_old = resCoord1 + resCoord2_old = resCoord2 - if it == MaxIter: - centre.logger.notice("Out of iterations, new coordinates may not be the best!") - XNEW -= XSTEP - YNEW -= YSTEP + if it != MaxIter: + COORD1NEW, COORD2NEW = centre_positioner.increment_position(COORD1NEW, COORD2NEW) + else: + beam_center_logger.report("Out of iterations, new coordinates may not be the best!") + + # Create the appropriate return values + coord1_centre, coord2_centre = centre_positioner.produce_final_position(COORD1NEW, COORD2NEW) ReductionSingleton().set_beam_finder( - isis_reduction_steps.BaseBeamFinder(XNEW, YNEW), det_bank) - centre.logger.notice("Centre coordinates updated: [" + str(XNEW*XSF) + ", " + str(YNEW*YSF) + ']') + isis_reduction_steps.BaseBeamFinder(coord1_centre, coord2_centre), det_bank) + beam_center_logger.report_final(coord1_centre, coord2_centre) - return XNEW, YNEW + return coord1_centre, coord2_centre ###################### Utility functions #################################################### @@ -1408,6 +1442,19 @@ def AddRuns(runs, instrument ='sans2d', saveAsEvent=False, binning = "Monitors", isOverlay = isOverlay, time_shifts = time_shifts) +def is_current_workspace_an_angle_workspace(): + ''' + Queries if the current workspace, stored in the reducer is a workspace + which uses [angle, pos] to denote its location + @returns true if it is an angle workspace else false + ''' + is_angle = False + # pylint: disable=bare-except + try: + is_angle = is_workspace_which_requires_angle(reducer = ReductionSingleton()) + except: + is_angle = False + return is_angle ############################################################################### ######################### Start of Deprecated Code ############################ ############################################################################### diff --git a/scripts/SANS/centre_finder.py b/scripts/SANS/centre_finder.py index b179ee8328d451445d135384b4818756b3e47ab3..db86f5be5c480e821de4adb636a74f7908b855b4 100644 --- a/scripts/SANS/centre_finder.py +++ b/scripts/SANS/centre_finder.py @@ -1,10 +1,53 @@ #pylint: disable=invalid-name -import isis_reducer from isis_reduction_steps import StripEndNans +from isis_instrument import LARMOR from mantid.simpleapi import * from mantid.kernel import Logger import SANSUtility +class FindDirectionEnum(object): #pylint: disable=R0903 + ALL = 0 + UP_DOWN = 1 + LEFT_RIGHT=2 + + def __init__(self): + super(FindDirectionEnum,self).__init__() + + +def is_workspace_which_requires_angle(reducer): + ''' + Check if the sample worksapce requires the first + coordinate to be an angle + @param reducer: the reducer object + @returns true if the workspace requires an angle otherwise false + ''' + instrument_name = reducer.instrument.name() + if instrument_name != LARMOR._NAME: + return False + workspace_name = reducer.get_sample().wksp_name + if workspace_name: + ws_ref = mtd[workspace_name] + return LARMOR.is_run_new_style_run(ws_ref) + return False + + + +def get_bench_rotation(reducer): + ''' + Extract the bench rotation from the instrument + of the reducer + @param reducer: the reducer object + @returns the bench rotation in degrees + ''' + bench_rotation = 0.0 + # pylint: disable=bare-except + try: + ws = mtd[str(reducer.get_sample().get_wksp_name())] + bench_rotation = reducer.instrument.getDetValues(ws)[0] + except: + bench_rotation = 0.0 + return bench_rotation + class CentreFinder(object): """ Aids estimating the effective centre of the particle beam by calculating Q in four @@ -12,17 +55,26 @@ class CentreFinder(object): better estimate for the beam centre position can hence be calculated iteratively """ QUADS = ['Left', 'Right', 'Up', 'Down'] - def __init__(self, guess_centre): + def __init__(self, guess_centre, sign_policy, find_direction = FindDirectionEnum.ALL): """ Takes a loaded reducer (sample information etc.) and the initial guess of the centre position that are required for all later iterations @param guess_centre: the starting position that the trial x and y are relative to + @param sign_policy: sets the sign for the move operation. + @param find_direction: Find beam centre for directions, ie if all or only up/down + or only left right """ self.logger = Logger("CentreFinder") self._last_pos = guess_centre self.detector = None - self.XSF = 1.0 - self.YSF = 1.0 + self.coord1_scale_factor = 1.0 + self.coord2_scale_factor = 1.0 + self.find_direction = find_direction + self.sign_coord1 = -1. + self.sign_coord2 = -1. + if sign_policy is not None and len(sign_policy) == 2: + self.sign_coord1 = sign_policy[0] + self.sign_coord2 = sign_policy[1] def SeekCentre(self, setup, trial): """ @@ -32,13 +84,13 @@ class CentreFinder(object): @param trial: the coordinates of the location to test as a list in the form [x, y] @return: the asymmetry in the calculated Q in the x and y directions """ - self.detector = setup.instrument.cur_detector().name() # populate the x and y scale factor values at this point for the text box - self.XSF = setup.instrument.beam_centre_scale_factor1 - self.YSF = setup.instrument.beam_centre_scale_factor2 + self.coord1_scale_factor = setup.instrument.beam_centre_scale_factor1 + self.coord2_scale_factor = setup.instrument.beam_centre_scale_factor2 + # We are looking only at the differnce between the old position and the trial. self.move(setup, trial[0]-self._last_pos[0], trial[1]-self._last_pos[1]) #phi masking will remove areas of the detector that we need @@ -50,13 +102,13 @@ class CentreFinder(object): steps = steps[0:len(steps)-1] setup._reduce(init=False, post=False, steps=steps) - self._group_into_quadrants(setup, 'centre', trial[0], trial[1], suffix='_tmp') + self._group_into_quadrants(setup, 'centre', suffix='_tmp') if setup.get_can(): #reduce the can here setup.reduce_can('centre_can', run_Q=False) - self._group_into_quadrants(setup, 'centre_can', trial[0], trial[1], suffix='_can') + self._group_into_quadrants(setup, 'centre_can', suffix='_can') Minus(LHSWorkspace='Left_tmp',RHSWorkspace= 'Left_can',OutputWorkspace= 'Left_tmp') Minus(LHSWorkspace='Right_tmp',RHSWorkspace= 'Right_can',OutputWorkspace= 'Right_tmp') Minus(LHSWorkspace='Up_tmp',RHSWorkspace= 'Up_can',OutputWorkspace= 'Up_tmp') @@ -70,7 +122,8 @@ class CentreFinder(object): DeleteWorkspace(Workspace='centre') self._last_pos = trial - #prepare the workspaces for "publication", after they have their standard names calculations will be done on them and they will be plotted + # prepare the workspaces for "publication", after they have their + # standard names calculations will be done on them and they will be plotted for out_wksp in self.QUADS: in_wksp = out_wksp+'_tmp' ReplaceSpecialValues(InputWorkspace=in_wksp,OutputWorkspace=in_wksp,NaNValue=0,InfinityValue=0) @@ -81,21 +134,6 @@ class CentreFinder(object): return self._calculate_residue() - def status_str(self, iter, x_res, y_res): - """ - Creates a human readble string from the numbers passed to it - @param iter: iteration number - @param x_res: asymmetry in the x direction - @param y_res: asymmetry in y - @return: a human readable string - """ - - x_str = str(self._last_pos[0] * self.XSF).ljust(10)[0:9] - y_str = str(self._last_pos[1] * self.YSF).ljust(10)[0:9] - x_res = ' SX='+str(x_res).ljust(7)[0:6] - y_res = ' SY='+str(y_res).ljust(7)[0:6] - return 'Itr '+str(iter)+': ('+x_str+', '+y_str+')'+x_res+y_res - def move(self, setup, x, y): """ Move the selected detector in both the can and sample workspaces, remembering the @@ -104,19 +142,34 @@ class CentreFinder(object): @param x: the distance to move in the x (-x) direction in metres @param y: the distance to move in the y (-y) direction in metres """ - x = -x - y = -y - MoveInstrumentComponent(Workspace=setup.get_sample().wksp_name,\ - ComponentName=self.detector, X=x, Y=y, RelativePosition=True) + # Displacing the beam by +5 is equivalent to displacing the isntrument by -5. Hence we change + # the sign here. LARMOR does this correction in the instrument itself, while for the others + # we don't + x = self.sign_coord1*x + y = self.sign_coord2*y + + setup.instrument.elementary_displacement_of_single_component(workspace=setup.get_sample().wksp_name, + component_name=self.detector, + coord1 = x, + coord2 = y, + coord1_scale_factor = 1., + coord2_scale_factor = 1., + relative_displacement = True) if setup.get_can(): - MoveInstrumentComponent(Workspace=setup.get_can().wksp_name,\ - ComponentName=self.detector, X=x, Y=y, RelativePosition=True) + setup.instrument.elementary_displacement_of_single_component(workspace=setup.get_can().wksp_name, + component_name=self.detector, + coord1 = x, + coord2 = y, + coord1_scale_factor = 1., + coord2_scale_factor = 1., + relative_displacement = True) # Create a workspace with a quadrant value in it - def _create_quadrant(self, setup, reduced_ws, quadrant, xcentre, ycentre, r_min, r_max, suffix): + def _create_quadrant(self, setup, reduced_ws, quadrant, r_min, r_max, suffix): out_ws = quadrant+suffix # Need to create a copy because we're going to mask 3/4 out and that's a one-way trip CloneWorkspace(InputWorkspace=reduced_ws,OutputWorkspace= out_ws) + objxml = SANSUtility.QuadrantXML([0, 0, 0.0], r_min, r_max, quadrant) # Mask out everything outside the quadrant of interest MaskDetectorsInShape(Workspace=out_ws,ShapeXML= objxml) @@ -125,12 +178,12 @@ class CentreFinder(object): #Q1D(output,rawcount_ws,output,q_bins,AccountForGravity=GRAVITY) # Create 4 quadrants for the centre finding algorithm and return their names - def _group_into_quadrants(self, setup, input, xcentre, ycentre, suffix=''): + def _group_into_quadrants(self, setup, input_value, suffix=''): r_min = setup.CENT_FIND_RMIN r_max = setup.CENT_FIND_RMAX for q in self.QUADS: - self._create_quadrant(setup, input, q, xcentre, ycentre, r_min, r_max, suffix) + self._create_quadrant(setup, input_value, q, r_min, r_max, suffix) def _calculate_residue(self): """ @@ -138,46 +191,679 @@ class CentreFinder(object): and Down. This assumes that a workspace with one spectrum for each of the quadrants @return: difference left to right, difference up down """ - yvalsA = mtd['Left'].readY(0) - yvalsB = mtd['Right'].readY(0) - qvalsA = mtd['Left'].readX(0) - qvalsB = mtd['Right'].readX(0) - qrange = [len(yvalsA), len(yvalsB)] - nvals = min(qrange) residueX = 0 - indexB = 0 - for indexA in range(0, nvals): - if qvalsA[indexA] < qvalsB[indexB]: - self.logger.notice("LR1 "+str(indexA)+" "+str(indexB)) - continue - elif qvalsA[indexA] > qvalsB[indexB]: - while qvalsA[indexA] > qvalsB[indexB]: - self.logger.notice("LR2 "+str(indexA)+" "+str(indexB)) - indexB += 1 - if indexA > nvals - 1 or indexB > nvals - 1: - break - residueX += pow(yvalsA[indexA] - yvalsB[indexB], 2) - indexB += 1 + if self.find_direction == FindDirectionEnum.ALL or self.find_direction == FindDirectionEnum.LEFT_RIGHT: + yvalsAX = mtd['Left'].readY(0) + yvalsBX = mtd['Right'].readY(0) + qvalsAX = mtd['Left'].readX(0) + qvalsBX = mtd['Right'].readX(0) + qrangeX = [len(yvalsAX), len(yvalsBX)] + nvalsX = min(qrangeX) + id1X = "LR1" + id2X = "LR2" + residueX = self._residual_calculation_for_single_direction(yvalsA = yvalsAX, + yvalsB = yvalsBX, + qvalsA = qvalsAX, + qvalsB = qvalsBX, + qrange = qrangeX, + nvals = nvalsX, + id1 = id1X, + id2 = id2X) - yvalsA = mtd['Up'].readY(0) - yvalsB = mtd['Down'].readY(0) - qvalsA = mtd['Up'].readX(0) - qvalsB = mtd['Down'].readX(0) - qrange = [len(yvalsA), len(yvalsB)] - nvals = min(qrange) residueY = 0 + if self.find_direction == FindDirectionEnum.ALL or self.find_direction == FindDirectionEnum.UP_DOWN: + yvalsAY = mtd['Up'].readY(0) + yvalsBY = mtd['Down'].readY(0) + qvalsAY = mtd['Up'].readX(0) + qvalsBY = mtd['Down'].readX(0) + qrangeY = [len(yvalsAY), len(yvalsBY)] + nvalsY = min(qrangeY) + id1Y = "UD1" + id2Y = "UD2" + residueY = self._residual_calculation_for_single_direction(yvalsA = yvalsAY, + yvalsB = yvalsBY, + qvalsA = qvalsAY, + qvalsB = qvalsBY, + qrange = qrangeY, + nvals = nvalsY, + id1 = id1Y, + id2 = id2Y) + return residueX, residueY + + def _residual_calculation_for_single_direction(self, yvalsA, yvalsB, qvalsA, qvalsB, qrange, nvals, id1, id2): + dummy_1 = qrange + residue = 0 indexB = 0 for indexA in range(0, nvals): if qvalsA[indexA] < qvalsB[indexB]: - self.logger.notice("UD1 "+str(indexA)+" "+str(indexB)) + self.logger.notice(id1 + " " +str(indexA)+" "+str(indexB)) continue elif qvalsA[indexA] > qvalsB[indexB]: while qvalsA[indexA] > qvalsB[indexB]: - self.logger("UD2 "+str(indexA)+" "+str(indexB)) + self.logger(id2 + " " +str(indexA)+" "+str(indexB)) indexB += 1 if indexA > nvals - 1 or indexB > nvals - 1: break - residueY += pow(yvalsA[indexA] - yvalsB[indexB], 2) + residue += pow(yvalsA[indexA] - yvalsB[indexB], 2) indexB += 1 + return residue - return residueX, residueY + def _get_cylinder_direction(self, workspace): + ''' + Get the direction that the masking clyinder needs to point at. This should be the normal + of the tilted detector bench. The original normal is along the beam axis as defined in + the instrument definition file. + @param workspace: the workspace with the tilted detector bench + @returns the required direction of the cylinder axis + ''' + ws = mtd[workspace] + instrument = ws.getInstrument() + quat = instrument.getComponentByName(self.detector).getRotation() + cylinder_direction = instrument.getReferenceFrame().vecPointingAlongBeam() + quat.rotate(cylinder_direction) + return cylinder_direction.X(), cylinder_direction.Y(), cylinder_direction.Z() + + + +class CentrePositioner(object): + ''' + Handles the positions and increments for beam finding. + ''' + def __init__(self, reducer, position_type, coord1_start, coord2_start,coord1_step,coord2_step, tolerance): #pylint: disable=too-many-arguments + ''' + Set the CentrePositioner. It requires: + @param reducer:: The reducer + @param position_type: If do a full search or only UP/DOWN or LEFT/RIGHT + @param coord1_start: The initial value for the first coordinate + @param coord2_start: The initial value for the second coordinate + @param coord1_step: The initial step size for the first coordinate + @param coord2_step: The initial step size for the second coordinate + @param tolerance: The tolerance + @param scale_factor_coord1: the scale factor for the first coordinate + @param scale_factor_coord2: the scale factor for the second coordinate + ''' + super(CentrePositioner,self).__init__() + # Create the appropriate position updater + pos_factory = BeamCentrePositionUpdaterFactory() + self.position_updater = pos_factory.create_beam_centre_position_updater(position_type) + + # Create the appropriate increment converter + position_provider_factory = PositionProviderFactory(coord1_step,coord2_step, tolerance, position_type) + self.position_provider = position_provider_factory.create_position_provider(reducer) + + # set the correct units starting coordinate. such that we are dealing with units of + # either [m,m] or [degree, m] + self.coord1_start = self.position_provider.get_coord1_for_input_with_correct_scaling(coord1_start) + self.coord2_start = coord2_start + + self.current_coord1 = self.coord1_start + self.current_coord2 = self.coord2_start + + + def increment_position(self, coord1_old, coord2_old): + ''' + Increment the coordinates + @param coord1_old: the first old coordinate + @param coord2_old: the seocond old coordinate + @returns the incremented coordinates + ''' + coord1_increment = self.position_provider.get_increment_coord1() + coord2_increment = self.position_provider.get_increment_coord2() + return self.position_updater.increment_position(coord1_old, coord2_old, coord1_increment, coord2_increment) + + def set_new_increment_coord1(self): + ''' + Set the new increment for the first coordinate. + ''' + self.position_provider.half_and_reverse_increment_coord1() + + def set_new_increment_coord2(self): + ''' + Set the new increment for the second coordinate. + ''' + self.position_provider.half_and_reverse_increment_coord2() + + def produce_final_position(self, coord1_new, coord2_new): + ''' + Produce the final coordinates + @param coord1_new: the newest version of coordinate 1 + @param coord2_new: the newest version of coordinate 2 + @returns the final coordinates + ''' + # We need to make sure that the first coordinate is returned with the correct scaling for the BaseBeamFinder, + # ie either [m, m] or [degree/1000, m] also if it might have a bench rotation applied to it + coord1 = self.position_provider.get_coord1_for_output_with_correct_scaling_and_offset(coord1_new) + return coord1, coord2_new + + def produce_initial_position(self): + ''' + Produce the initial position. This is important especially for the LARMOR + case where we can have an additional BENCH Rotation. + @returns the initital position for coord1 and coord2 + ''' + return self.position_provider.produce_initial_position(self.coord1_start, self.coord2_start) + + def is_increment_coord1_smaller_than_tolerance(self): + ''' + Check if the increment for the first coordinate is smaller than the tolerance + @returns True if the increment of the first coordinate is smaller than tolerance else False + ''' + return self.position_provider.is_coord1_increment_smaller_than_tolerance() + + def is_increment_coord2_smaller_than_tolerance(self): + ''' + Check if the increment for the second coordinate is smaller than the tolerance + @returns True if the increment of the second coordinate is smaller than tolerance else False + ''' + return self.position_provider.is_coord2_increment_smaller_than_tolerance() + + def produce_sign_policy(self): + ''' + Gets a tuple of sign policies for the translation of the instrument. + ''' + return self.position_provider.provide_sign_policy() + +# Thes classes make sure that only the relevant directions are updated +# They are not instrument dependent, they should only dependt on the user's choice. +class BeamCentrePositionUpdaterFactory(object): #pylint: disable=R0903 + ''' + Creates the required beam centre position updater. + ''' + def __init__(self): + super(BeamCentrePositionUpdaterFactory, self).__init__() + + def create_beam_centre_position_updater(self, beam_centre_position_udpater_type): + ''' + Factory method to create the appropriate Beam Centre Position Updater + @param beam_centre_position_udpater_type: the type of updater + ''' + if beam_centre_position_udpater_type == FindDirectionEnum.LEFT_RIGHT: + return BeamCentrePositionUpdaterLeftRight() + elif beam_centre_position_udpater_type == FindDirectionEnum.UP_DOWN: + return BeamCentrePositionUpdaterUpDown() + elif beam_centre_position_udpater_type == FindDirectionEnum.ALL: + return BeamCentrePositionUpdaterAll() + else: + RuntimeError("Error in BeamCentrePositionUpdaterFactory: You need to provide a position update" + "policy, ie up/down, left/right or all") + +class BeamCentrePositionUpdater(object): #pylint: disable=R0903 + ''' + Handles the position updates, ie if we are only intereseted in left/right or up/down or all + ''' + def __init__(self): + super(BeamCentrePositionUpdater, self).__init__() + + def increment_position(self, coord1_old, coord2_old, coord1_increment, coord2_increment): + dummy_1 = coord1_old + dummy_2 = coord2_old + dummy_3 = coord1_increment + dummy_4 = coord2_increment + raise RuntimeError("BeamCentrePositionUpdater is not implemented") + + +class BeamCentrePositionUpdaterAll(BeamCentrePositionUpdater): + ''' + Handles the position updates when all directions are being selected + ''' + def __init__(self): + super(BeamCentrePositionUpdaterAll, self).__init__() + + def increment_position(self, coord1_old, coord2_old, coord1_increment, coord2_increment): + ''' + Increment the coordinates + @param coord1_old: the old first coordinate + @param coord2_old: the old second coordinate + @param coord1_increment: the increment for the first coordinate + @param coord2_increment: the increment for the second coordinate + ''' + return coord1_old + coord1_increment, coord2_old + coord2_increment + + +class BeamCentrePositionUpdaterUpDown(BeamCentrePositionUpdater): + ''' + Handles the position updates when only up/down is selected + ''' + def __init__(self): + super(BeamCentrePositionUpdaterUpDown, self).__init__() + + def increment_position(self, coord1_old, coord2_old, coord1_increment, coord2_increment): + ''' + Increment the coordinates. + @param coord1_old: the old first coordinate + @param coord2_old: the old second coordinate + @param coord1_increment: the increment for the first coordinate + @param coord2_increment: the increment for the second coordinate + @returns the incremented position + ''' + dummy_1 = coord1_increment + return coord1_old, coord2_old + coord2_increment + + +class BeamCentrePositionUpdaterLeftRight(BeamCentrePositionUpdater): + ''' + Handles the position updates when only right/left is selected + ''' + def __init__(self): + super(BeamCentrePositionUpdaterLeftRight, self).__init__() + + def increment_position(self, coord1_old, coord2_old, coord1_increment, coord2_increment): + ''' + Increment the coordinates. + @param coord1_old: the old first coordinate + @param coord2_old: the old second coordinate + @param coord1_increment: the increment for the first coordinate + @param coord2_increment: the increment for the second coordinate + @returns the incremented position + ''' + dummy_1 = coord2_increment + return coord1_old + coord1_increment, coord2_old + + def produce_final_position(self, x_new, x_initial, y_new, y_initial): + ''' + Produce the final position. + @param coord1_new: the new first coordinate + @param coord1_initial: the first initial coordinate + @param coord2_new: the new second coordinate + @param coord2_initial: the second initial coordinate + @returns the final position + ''' + dummy_1 = y_new + dummy_2 = x_initial + return x_new, y_initial + + + +# Provides the positions and increments with the correct scaling +# These set of classes are instrument dependent. +class PositionProviderFactory(object): + ''' + Creates the required increment provider. The increments for the two coordinates + depend on the instrument, eg Larmor's first coordinate is an angle for certain + run numbers. + ''' + def __init__(self, increment_coord1, increment_coord2, tolerance, position_type): + ''' + Initialize the PositionProviderFactory + @param increment_coord1: The increment for the first coordinate + @param increment_coord2: The increment for the second coordinate + @param tolerance: The tolerance setting + @param position_type: The type of the psoitio, ie left/right, up/down or all + ''' + super(PositionProviderFactory,self).__init__() + self.increment_coord1 = increment_coord1 + self.increment_coord2 = increment_coord2 + self.tolerance = tolerance + self.position_type = position_type + + def create_position_provider(self, reducer): + ''' + Factory method for the PositionProvider + @param reducer: The reducer object + @returns The correct increment provider + ''' + if is_workspace_which_requires_angle(reducer): + # The angle increment is currently not specified in the instrument parameters file, + # hence we set a value here. This is also true for the tolerance + #increment_coord1_angle = self.get_increment_coord1_angle(reducer, self.tolerance) + #tolerance_angle = self.get_tolerance_for_angle(self.tolerance, self.increment_coord1, increment_coord1_angle) + increment_coord1_angle = self.increment_coord1 /1000. # The tolerance needs to be specified in angles + tolerance_angle = self.tolerance + + # Find the bench rotation. Only supply the bench rotation if it is really needed. If we supply an offset + # through a bench rotation we need to take into account that the directionality of the angles is not + # the same as in Mantid. We need to reverse the sign of the bench rotation + coord1_offset = -1*get_bench_rotation(reducer) + + # Get the scale factor for the first coordinate + coord1_scale_factor = reducer.get_beam_center_scale_factor1() + + return PositionProviderAngleY(increment_coord1 = increment_coord1_angle, + increment_coord2 = self.increment_coord2, + tolerance = self.tolerance, + tolerance_angle = tolerance_angle, + coord1_offset = coord1_offset, + coord1_scale_factor = coord1_scale_factor) + else: + return PositionProviderXY(increment_coord1 = self.increment_coord1, + increment_coord2 = self.increment_coord2, + tolerance = self.tolerance) + + def get_increment_coord1_angle(self, reducer, tolerance): + ''' + Estimate an increment for the angle based on the specified coord1 increment for linear + displacements and the distance from the sample to the detector. + For a distance D and an increment dx, we estimate dAlpha to be tan(dAlpha)=dx/D. + Since D >> dx, we can use Taylor expansion to set dAlpha = dx/D + @param reducer: the reducer object + @param tolerance: the tolerance + ''' + workspace_name = reducer.get_sample().wksp_name + workspace = mtd[workspace_name] + + instrument = workspace.getInstrument() + detector_name = reducer.instrument.cur_detector().name() + + sample = instrument.getSample() + component = instrument.getComponentByName(detector_name) + + # We use here the first detector entry. Do we need to have this smarter in the future? + detector_bench = component[0] + distance = detector_bench.getDistance(sample) + + return tolerance/distance + + + def get_tolerance_for_angle(self, tolerance_linear, increment_linear, increment_angle): + ''' + The tolerance associated with a linear disaplacement is translated into + a tolerance for an angle. Tol_Angle = Increment_Angle *(Tol_Linear/Increment_Linear) + @param tolerance_linear: the tolerance for the linear displacement + @param increment_lienar: the increment of the linear displacement + @param increment_angle: the increment of the rotation + ''' + return (increment_angle/increment_linear)*tolerance_linear + +class PositionProvider(object): + def __init__(self, increment_coord1, increment_coord2, tolerance): + super(PositionProvider,self).__init__() + dummy_1 = increment_coord1 + dummy_2 = increment_coord2 + dummy_3 = tolerance + + def get_coord1_for_input_with_correct_scaling(self, coord1): + dummy_coord1 = coord1 + RuntimeError("The PositionProvider interface is not implemented") + + def get_coord1_for_output_with_correct_scaling_and_offset(self, coord1): + dummy_coord1 = coord1 + RuntimeError("The PositionProvider interface is not implemented") + + def produce_initial_position(self, coord1, coord2): + dummy_coord1 = coord1 + dummy_coord2 = coord2 + RuntimeError("The PositionProvider interface is not implemented") + + def half_and_reverse_increment_coord1(self): + RuntimeError("The PositionProvider interface is not implemented") + + def half_and_reverse_increment_coord2(self): + RuntimeError("The PositionProvider interface is not implemented") + + def is_coord1_increment_smaller_than_tolerance(self): + RuntimeError("The PositionProvider interface is not implemented") + + def is_coord2_increment_smaller_than_tolerance(self): + RuntimeError("The PositionProvider interface is not implemented") + + def get_increment_coord1(self): + RuntimeError("The PositionProvider interface is not implemented") + + def get_increment_coord2(self): + RuntimeError("The PositionProvider interface is not implemented") + + def check_is_smaller_than_tolerance(self,to_check,tolerance): + if abs(to_check) < tolerance: + return True + else: + return False + + def provide_sign_policy(self): + RuntimeError("The PositionProvider interface is not implemented") + +class PositionProviderXY(PositionProvider): + ''' + Handles the increments for the case when both coordinates are cartesian + ''' + def __init__(self, increment_coord1, increment_coord2, tolerance): + super(PositionProviderXY,self).__init__(increment_coord1, increment_coord2, tolerance) + self.increment_x = increment_coord1 + self.increment_y = increment_coord2 + self.tolerance = tolerance + # The sign policy + self.sign_policy_x = -1. + self.sign_policy_y = -1. + + def get_coord1_for_input_with_correct_scaling(self, coord1): + ''' + Get the coordinate with the correct scaling; + in this case it is already correct as we have [m,m] + @param coord1: first coordinate in m + @returns the first coordinate in m + ''' + return coord1 + + def get_coord1_for_output_with_correct_scaling_and_offset(self, coord1): + ''' + Get the coordinate with the correct scaling + @param coord1: first coordinate in m + @returns the first coordinate in m + ''' + return coord1 + + def produce_initial_position(self, coord1, coord2): + return coord1, coord2 + + def half_and_reverse_increment_coord1(self): + ''' + Halves the step size and reverses the step direction + ''' + self.increment_x = -self.increment_x/2.0 + + def half_and_reverse_increment_coord2(self): + ''' + Halves the step size and reverses the step direction + ''' + self.increment_y = -self.increment_y/2.0 + + def is_coord1_increment_smaller_than_tolerance(self): + ''' + Check if the increment for the first coordinate is smaller than the tolerance + @returns True if the increment is smaller than the tolerance, otherwise false + ''' + return self.check_is_smaller_than_tolerance(self.increment_x, self.tolerance) + + def is_coord2_increment_smaller_than_tolerance(self): + ''' + Check if the increment for the second coordinate is smaller than the tolerance + @returns True if the increment is smaller than the tolerance, otherwise false + ''' + return self.check_is_smaller_than_tolerance(self.increment_y, self.tolerance) + + def get_increment_coord1(self): + return self.increment_x + + def get_increment_coord2(self): + return self.increment_y + + def provide_sign_policy(self): + ''' + Get the sign policy for the x, y translations. Displacing the beam by 5mm + is equivalent to displacing the instrument by -5mm, hence we need the + minus sign here. + ''' + return self.sign_policy_x, self.sign_policy_y + +class PositionProviderAngleY(PositionProvider): + ''' + Handles the increments for the case when the first coordinate is an angle + and the second is a cartesian coordinate + ''' + def __init__(self, increment_coord1, increment_coord2, tolerance, tolerance_angle, coord1_offset, coord1_scale_factor): #pylint: disable=too-many-arguments + super(PositionProviderAngleY,self).__init__(increment_coord1, increment_coord2, tolerance) + self.increment_angle = increment_coord1 + self.increment_y = increment_coord2 + self.tolerance = tolerance + self.tolerance_angle = tolerance_angle + self.coord1_offset = coord1_offset + self.coord1_scale_factor = coord1_scale_factor + # The sign policy + self.sign_policy_angle = 1. + self.sign_policy_y = -1. + + + def get_coord1_for_input_with_correct_scaling(self, coord1): + ''' + Get the coordinate with the correct scaling + @param coord1: first coordinate in degree/COORD1SF + @returns the first coordinate in degree + ''' + return coord1*self.coord1_scale_factor + + def get_coord1_for_output_with_correct_scaling_and_offset(self, coord1): + ''' + Get the coordinate with the correct scaling + @param coord1: first coordinate in degree + @returns the first coordinate in degree/COORD1SF + ''' + # At this point we want to take out the offset, hence we need to substract it. + return (coord1 - self.coord1_offset)/self.coord1_scale_factor + + def produce_initial_position(self, coord1, coord2): + ''' + The initial position might require a correction for the bench rotation. + ''' + return coord1 + self.coord1_offset, coord2 + + def half_and_reverse_increment_coord1(self): + ''' + Halves the step size and reverses the step direction + ''' + self.increment_angle = -self.increment_angle/2.0 + + def half_and_reverse_increment_coord2(self): + ''' + Halves the step size and reverses the step direction + ''' + self.increment_y = -self.increment_y/2.0 + + def is_coord1_increment_smaller_than_tolerance(self): + ''' + Check if the increment for the first coordinate is smaller than the tolerance + @returns True if the increment is smaller than the tolerance, otherwise false + ''' + return self.check_is_smaller_than_tolerance(self.increment_angle,self.tolerance_angle) + + def is_coord2_increment_smaller_than_tolerance(self): + ''' + Check if the increment for the second coordinate is smaller than the tolerance + @returns True if the increment is smaller than the tolerance, otherwise false + ''' + return self.check_is_smaller_than_tolerance(self.increment_y,self.tolerance) + + def get_increment_coord1(self): + return self.increment_angle*self.coord1_scale_factor + + def get_increment_coord2(self): + return self.increment_y + + def provide_sign_policy(self): + ''' + Get the sign policy for the angle, y translations. Displacing the beam by 5mm + is equivalent to displacing the instrument by -5mm. The angle displacement in + LARMOR does the sign switch already. Hence we have a positve sign policy for + the angle direction + ''' + return self.sign_policy_angle, self.sign_policy_y + + +# The classes below provide a logging facility for the Beam Centre Finder mechanism +class BeamCenterLogger(object): + ''' + Logger during the beam centre operation. The logging will + depend partially on the type of the first coordinate, ie [m, m] or [degree, m]. + It will also perform a correction for potential offsets like bench rotations. + ''' + def __init__(self, reducer, coord1_scale_factor, coord2_scale_factor): + super(BeamCenterLogger, self).__init__() + self.logger = Logger("CentreFinder") + self.using_angle = False + if is_workspace_which_requires_angle(reducer): + self.coord1_scale_factor = 1. + self.using_angle = True + # Find the bench rotation. Only supply the bench rotation if it is really needed. If we supply an offset + # through a bench rotation we need to take into account that the directionality of the angles is not + # the same as in Mantid. We need to reverse the sign of the bench rotation to get the correct rotation. + self.offset_coord1 = -1*get_bench_rotation(reducer) + else: + self.coord1_scale_factor = coord1_scale_factor + self.offset_coord1 = 0.0 + + self.coord2_scale_factor = coord2_scale_factor + self.offset_coord2 = 0.0 + + def report_init(self, coord1, coord2): + ''' + Report the initial setup + @param coord1: the first coordinate + @param coord2: the second coordinate + ''' + if self.using_angle: + initial_msg = "beta_start" + else: + initial_msg = "x_start" + # We need to substract the offset from the coordinate, since we do not want to display the offset + # which is on the data + val1 = (coord1 - self.offset_coord1)*self.coord1_scale_factor + val2 = (coord2 - self.offset_coord2)*self.coord2_scale_factor + + msg = initial_msg + ",ystart= %s %s" % (str(val1), str(val2)) + self.logger.notice(msg) + self.logger.notice("Starting centre finding routine ...") + + def report_status(self, iteration, coord1, coord2, resid1, resid2): #pylint: disable=too-many-arguments + ''' + Report the status of a beam finder iteration + @param iteration: the number of the iteration + @param coord1: the first coordinate + @param coord2: the second coordinate + @param resid1: the residual of the first coordinate + @param resid2: the residual of the second coordinate + ''' + msg = self.get_status_message(iteration, coord1, coord2, resid1, resid2) + self.logger.notice(msg) + + def get_status_message(self, iteration, coord1, coord2, resid1, resid2): #pylint: disable=too-many-arguments + ''' + Report the status of a beam finder iteration + @param iteration: the number of the iteration + @param coord1: the first coordinate + @param coord2: the second coordinate + @param resid1: the residual of the first coordinate + @param resid2: the residual of the second coordinate + ''' + # We need to substract the offset from the coordinate, since we do not want to display the offset + # which is on the data + val1 = (coord1 - self.offset_coord1)* self.coord1_scale_factor + val2 = (coord2 - self.offset_coord2)* self.coord2_scale_factor + coord1str = str(val1).ljust(10)[0:9] + coord2str = str(val2).ljust(10)[0:9] + res1str = str(resid1).ljust(7)[0:6] + res2str = str(resid2).ljust(7)[0:6] + msg = "Itr %i: (%s, %s) SX=%s SY=%s" %(iteration, coord1str, coord2str, res1str, res2str) + return msg + + def report(self, msg): + ''' + Report a general message + @param msg: the message to report + ''' + self.logger.notice(msg) + + def report_final(self, coord1, coord2): + ''' + Report the final coordinates which are set in the reducer + @param coord1: the first coordinate + @param coord2: the second coordinate + ''' + # We shouldn't need an offset correction at this point as a possible. + # Also we need to multiply both entries with the same (1000) scaling, + # Because the first coordinate should have been corrected before + # being passed into this method. For reporting purposes we revert this + # correction. Also we don't need to remove the offset, since it the input + # already has it removed. + general_scale = self.coord2_scale_factor + val1 = (coord1)*general_scale + val2 = (coord2)*general_scale + msg = "Centre coordinates updated: [ %f, %f ]" %(val1, val2) + self.logger.notice(msg) diff --git a/scripts/SANS/isis_instrument.py b/scripts/SANS/isis_instrument.py index 821b956195403a29bfb2b308d0a83d29286235c9..7cbaff03481cbfafcb4b570dd99e3c7286527f70 100644 --- a/scripts/SANS/isis_instrument.py +++ b/scripts/SANS/isis_instrument.py @@ -507,6 +507,9 @@ class ISISInstrument(BaseInstrument): self.monitor_zs = {} # Used when new calibration required. self._newCalibrationWS = None + # Centre of beam after a move has been applied, + self.beam_centre_pos1_after_move = 0.0 + self.beam_centre_pos2_after_move = 0.0 def get_incident_mon(self): """ @@ -712,8 +715,35 @@ class ISISInstrument(BaseInstrument): """Define how to move the bank to position beamX and beamY must be implemented""" raise RuntimeError("Not Implemented") + def elementary_displacement_of_single_component(self, workspace, component_name, coord1, coord2, + coord1_scale_factor = 1., coord2_scale_factor = 1.,relative_displacement = True): + """ + A simple elementary displacement of a single component. + This provides the adequate displacement for finding the beam centre. + @param workspace: the workspace which needs to have the move applied to it + @param component_name: the name of the component which being displaced + @param coord1: the first coordinate, which is x here + @param coord2: the second coordinate, which is y here + @param coord1_scale_factor: scale factor for the first coordinate + @param coord2_scale_factor: scale factor for the second coordinate + @param relative_displacement: If the the displacement is to be relative (it normally should be) + """ + dummy_1 = workspace + dummy_2 = component_name + dummy_3 = coord1 + dummy_3 = coord2 + dummy_4 = relative_displacement + dummy_5 = coord1_scale_factor + dummy_6 = coord2_scale_factor + raise RuntimeError("Not Implemented") + def cur_detector_position(self, ws_name): - """Return the position of the center of the detector bank""" + ''' + Return the position of the center of the detector bank + @param ws_name: the input workspace name + @raise RuntimeError: Not implemented + ''' + dummy_1 = ws_name raise RuntimeError("Not Implemented") def on_load_sample(self, ws_name, beamcentre, isSample): @@ -736,7 +766,8 @@ class ISISInstrument(BaseInstrument): self.changeCalibration(ws_name) # centralize the bank to the centre - self.move_components(ws_name, beamcentre[0], beamcentre[1]) + dummy_centre, centre_shift = self.move_components(ws_name, beamcentre[0], beamcentre[1]) + return centre_shift def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): """ @@ -755,6 +786,11 @@ class ISISInstrument(BaseInstrument): # this forces us to have 'copyable' objects. self._newCalibrationWS = str(ws_reference) + def get_updated_beam_centre_after_move(self): + ''' + @returns the beam centre position after the instrument has moved + ''' + return self.beam_centre_pos1_after_move, self.beam_centre_pos2_after_move class LOQ(ISISInstrument): @@ -790,7 +826,9 @@ class LOQ(ISISInstrument): xshift = (317.5/1000.) - xbeam yshift = (317.5/1000.) - ybeam - MoveInstrumentComponent(Workspace=ws,ComponentName= self.cur_detector().name(), X = xshift, Y = yshift, RelativePosition="1") + MoveInstrumentComponent(Workspace=ws, + ComponentName= self.cur_detector().name(), + X = xshift, Y = yshift, RelativePosition="1") # Have a separate move for x_corr, y_coor and z_coor just to make it more obvious in the # history, and to expert users what is going on @@ -800,8 +838,31 @@ class LOQ(ISISInstrument): xshift = xshift + det.x_corr/1000.0 yshift = yshift + det.y_corr/1000.0 + # Set the beam centre position afte the move, leave as they were + self.beam_centre_pos1_after_move = xbeam + self.beam_centre_pos2_after_move = ybeam + return [xshift, yshift], [xshift, yshift] + def elementary_displacement_of_single_component(self, workspace, component_name, coord1, coord2, + coord1_scale_factor = 1., coord2_scale_factor = 1.,relative_displacement = True): + """ + A simple elementary displacement of a single component. + This provides the adequate displacement for finding the beam centre. + @param workspace: the workspace which needs to have the move applied to it + @param component_name: the name of the component which being displaced + @param coord1: the first coordinate, which is x here + @param coord2: the second coordinate, which is y here + @param coord1_scale_factor: scale factor for the first coordinate + @param coord2_scale_factor: scale factor for the second coordinate + @param relative_displacement: If the the displacement is to be relative (it normally should be) + """ + MoveInstrumentComponent(Workspace=workspace, + ComponentName=component_name, + X=coord1, + Y=coord2, + RelativePosition=relative_displacement) + def get_marked_dets(self): raise NotImplementedError('The marked detector list isn\'t stored for instrument '+self._NAME) @@ -939,44 +1000,59 @@ class SANS2D(ISISInstrument): # Deal with front detector # 10/03/15 RKH need to add tilt of detector, in degrees, with respect to the horizontal or vertical of the detector plane - # this time we can rotate about the detector's own axis so can use RotateInstrumentComponent, ytilt rotates about x axis, xtilt rotates about z axis + # this time we can rotate about the detector's own axis so can use RotateInstrumentComponent, + # ytilt rotates about x axis, xtilt rotates about z axis # if frontDet.y_tilt != 0.0: - RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = "1.", Y = "0.", Z = "0.", Angle = frontDet.y_tilt) + RotateInstrumentComponent(Workspace=ws, + ComponentName= self.getDetector('front').name(), + X = "1.", + Y = "0.", + Z = "0.", + Angle = frontDet.y_tilt) if frontDet.x_tilt != 0.0: - RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = "0.", Y = "0.", Z = "1.", Angle = frontDet.x_tilt) + RotateInstrumentComponent(Workspace=ws, + ComponentName= self.getDetector('front').name(), + X = "0.", + Y = "0.", + Z = "1.", + Angle = frontDet.x_tilt) # # 9/1/12 this all dates to Richard Heenan & Russell Taylor's original python development for SANS2d - # the rotation axis on the SANS2d front detector is actually set front_det_radius = 306mm behind the detector. - # Since RotateInstrumentComponent will only rotate about the centre of the detector, we have to to the rest here. + # the rotation axis on the SANS2d front detector is actually set front_det_radius = 306mm behind the detector. + # Since RotateInstrumentComponent will only rotate about the centre of the detector, we have to to the rest here. # rotate front detector according to value in log file and correction value provided in user file rotateDet = (-FRONT_DET_ROT - frontDet.rot_corr) RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X="0.", Y="1.0", Z="0.", Angle=rotateDet) RotRadians = math.pi*(FRONT_DET_ROT + frontDet.rot_corr)/180. # The rear detector is translated to the beam position using the beam centre coordinates in the user file. - # (Note that the X encoder values in NOT used for the rear detector.) - # The front detector is translated using the difference in X encoder values, with a correction from the user file. - # 21/3/12 RKH [In reality only the DIFFERENCE in X encoders is used, having separate X corrections for both detectors is unnecessary, - # but we will continue with this as it makes the mask file smore logical and avoids a retrospective change.] - # 21/3/12 RKH add .side_corr allows rotation axis of the front detector being offset from the detector X=0 - # this inserts *(1.0-math.cos(RotRadians)) into xshift, and - # - frontDet.side_corr*math.sin(RotRadians) into zshift. - # (Note we may yet need to introduce further corrections for parallax errors in the detectors, which may be wavelength dependent!) + # (Note that the X encoder values in NOT used for the rear detector.) + # The front detector is translated using the difference in X encoder values, with a correction from the user file. + # 21/3/12 RKH [In reality only the DIFFERENCE in X encoders is used, having separate X corrections for + # both detectors is unnecessary, + # but we will continue with this as it makes the mask file smore logical and avoids a retrospective change.] + # 21/3/12 RKH add .side_corr allows rotation axis of the front detector being offset from the detector X=0 + # this inserts *(1.0-math.cos(RotRadians)) into xshift, and + # - frontDet.side_corr*math.sin(RotRadians) into zshift. + # (Note we may yet need to introduce further corrections for parallax errors in the detectors, which may be wavelength dependent!) xshift = (REAR_DET_X + rearDet.x_corr -frontDet.x_corr - FRONT_DET_X -frontDet.side_corr*(1-math.cos(RotRadians)) + (self.FRONT_DET_RADIUS +frontDet.radius_corr)*math.sin(RotRadians) )/1000. - self.FRONT_DET_DEFAULT_X_M - xbeam yshift = (frontDet.y_corr/1000. - ybeam) - # Note don't understand the comment below (9/1/12 these are comments from the original python code, you may remove them if you like!) + # Note don't understand the comment below (9/1/12 these are comments from the original python code, + # you may remove them if you like!) # default in instrument description is 23.281m - 4.000m from sample at 19,281m ! # need to add ~58mm to det1 to get to centre of detector, before it is rotated. - # 21/3/12 RKH add .radius_corr + # 21/3/12 RKH add .radius_corr zshift = (FRONT_DET_Z + frontDet.z_corr + (self.FRONT_DET_RADIUS +frontDet.radius_corr)*(1 - math.cos(RotRadians)) - frontDet.side_corr*math.sin(RotRadians))/1000. zshift -= self.FRONT_DET_DEFAULT_SD_M - MoveInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = xshift, Y = yshift, Z = zshift, RelativePosition="1") - + MoveInstrumentComponent(Workspace=ws, + ComponentName= self.getDetector('front').name(), + X = xshift, Y = yshift, Z = zshift, RelativePosition="1") # deal with rear detector # 10/03/15 RKH need to add tilt of detector, in degrees, with respect to the horizontal or vertical of the detector plane - # Best to do the tilts first, while the detector is still centred on the z axis, ytilt rotates about x axis, xtilt rotates about z axis + # Best to do the tilts first, while the detector is still centred on the z axis, + # ytilt rotates about x axis, xtilt rotates about z axis # NOTE the beam centre coordinates may change if rearDet.y_tilt != 0.0: RotateInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = "1.", Y = "0.", Z = "0.", Angle = rearDet.y_tilt) @@ -990,9 +1066,7 @@ class SANS2D(ISISInstrument): sanslog.notice("Setup move "+str(xshift*1000.)+" "+str(yshift*1000.)+" "+str(zshift*1000.)) MoveInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = xshift, Y = yshift, Z = zshift, RelativePosition="1") - self.move_all_components(ws) - #this implements the TRANS/TRANSPEC=4/SHIFT=... line, this overrides any other monitor move if self.monitor_4_offset: #get the current location of the monitor @@ -1020,8 +1094,31 @@ class SANS2D(ISISInstrument): beam_cen = [0.0,0.0] det_cen = [-xbeam, -ybeam] + # Set the beam centre position afte the move, leave as they were + self.beam_centre_pos1_after_move = xbeam + self.beam_centre_pos2_after_move = ybeam + return beam_cen, det_cen + def elementary_displacement_of_single_component(self, workspace, component_name, coord1, coord2, + coord1_scale_factor = 1., coord2_scale_factor = 1.,relative_displacement = True): + """ + A simple elementary displacement of a single component. + This provides the adequate displacement for finding the beam centre. + @param workspace: the workspace which needs to have the move applied to it + @param component_name: the name of the component which being displaced + @param coord1: the first coordinate, which is x here + @param coord2: the second coordinate, which is y here + @param coord1_scale_factor: scale factor for the first coordinate + @param coord2_scale_factor: scale factor for the second coordinate + @param relative_displacement: If the the displacement is to be relative (it normally should be) + """ + MoveInstrumentComponent(Workspace=workspace, + ComponentName=component_name, + X=coord1, + Y=coord2, + RelativePosition=relative_displacement) + def get_detector_log(self, wksp): """ Reads information about the state of the instrument on the information @@ -1387,28 +1484,75 @@ class LARMOR(ISISInstrument): zshift = 0 sanslog.notice("Setup move " + str(xshift*XSF) + " " + str(yshift*YSF) + " " + str(zshift*1000)) MoveInstrumentComponent(ws, ComponentName=detBench.name(), X=xshift, Y=yshift, Z=zshift) + + # Deal with the angle value + total_x_shift = self._rotate_around_y_axis(workspace = ws, + component_name = detBench.name(), + x_beam = xbeam, + x_scale_factor = XSF, + bench_rotation = BENCH_ROT) + + # Set the beam centre position afte the move + self.beam_centre_pos1_after_move = xbeam # Need to provide the angle in 1000th of a degree + self.beam_centre_pos2_after_move = ybeam + + # beam centre, translation, new beam position + return [0.0, 0.0], [-xbeam, -ybeam] + + def elementary_displacement_of_single_component(self, workspace, component_name, coord1, coord2, + coord1_scale_factor = 1., coord2_scale_factor = 1.,relative_displacement = True): + """ + A simple elementary displacement of a single component. + This provides the adequate displacement for finding the beam centre. + @param workspace: the workspace which needs to have the move applied to it + @param component_name: the name of the component which being displaced + @param coord1: the first coordinate, which is x here + @param coord2: the second coordinate, which is y here + @param coord1_scale_factor: scale factor for the first coordinate + @param coord2_scale_factor: scale factor for the second coordinate + @param relative_displacement: If the the displacement is to be relative (it normally should be) + """ + dummy_coord2_scale_factor = coord2_scale_factor + # Shift the component in the y direction + MoveInstrumentComponent(Workspace=workspace, + ComponentName=component_name, + Y=coord2, + RelativePosition=relative_displacement) + + # Rotate around the y-axis. + self._rotate_around_y_axis(workspace = workspace, + component_name = component_name, + x_beam = coord1, + x_scale_factor = coord1_scale_factor, + bench_rotation = 0.) + + def _rotate_around_y_axis(self,workspace, component_name, x_beam, x_scale_factor, bench_rotation): + ''' + Rotates the component of the workspace around the y axis or shift along x, depending on the run number + @param workspace: a workspace name + @param component_name: the component to rotate + @param x_beam: either a shift in mm or a angle in degree + @param x_scale_factor: + ''' # in order to avoid rewriting old mask files from initial commisioning during 2014. - ws_ref=mtd[ws] - try: - run_num = ws_ref.getRun().getLogData('run_number').value - except: - run_num = int(re.findall(r'\d+',str(ws))[0]) + ws_ref=mtd[workspace] # The angle value # Note that the x position gets converted from mm to m when read from the user file so we need to reverse this if X is now an angle - if int(run_num) < 2217: + if not LARMOR.is_run_new_style_run(ws_ref): # Initial commisioning before run 2217 did not pay much attention to making sure the bench_rot value was meaningful - xshift = -xbeam - sanslog.notice("Setup move " + str(xshift*XSF) + " " + str(0.0) + " " + str(0.0)) - MoveInstrumentComponent(ws, ComponentName=detBench.name(), X=xshift, Y=0.0, Z=0.0) + xshift = -x_beam + sanslog.notice("Setup move " + str(xshift*x_scale_factor) + " " + str(0.0) + " " + str(0.0)) + MoveInstrumentComponent(workspace, ComponentName=component_name, X=xshift, Y=0.0, Z=0.0) else: - xshift = BENCH_ROT-xbeam*XSF - sanslog.notice("Setup move " + str(xshift*XSF) + " " + str(0.0) + " " + str(0.0)) - RotateInstrumentComponent(ws, ComponentName=detBench.name(), X=0, Y=1, Z=0, Angle=xshift) - #logger.warning("Back from RotateInstrumentComponent") - - # beam centre, translation - return [0.0, 0.0], [-xbeam, -ybeam] + # The x shift is in degree + # IMPORTANT NOTE: It seems that the definition of positive and negative angles is different + # between Mantid and the beam scientists. This explains the different signs for x_beam and + # bench_rotation. + xshift = bench_rotation -x_beam*x_scale_factor + sanslog.notice("Setup rotate " + str(xshift*x_scale_factor) + " " + str(0.0) + " " + str(0.0)) + RotateInstrumentComponent(workspace, ComponentName=component_name, X=0, Y=1, Z=0, Angle=xshift) + return xshift def append_marked(self, detNames): self._marked_dets.append(detNames) @@ -1456,11 +1600,8 @@ class LARMOR(ISISInstrument): ws_ref = mtd[str(ws_name)] # in order to avoid problems with files from initial commisioning during 2014. # these didn't have the required log entries for the detector position - try: - run_num = ws_ref.getRun().getLogData('run_number').value - except: - run_num = int(re.findall(r'\d+',str(ws_name))[0]) - if int(run_num) >= 2217: + + if LARMOR.is_run_new_style_run(ws_ref): try: #logger.warning("Trying get_detector_log") log = self.get_detector_log(ws_ref) @@ -1479,5 +1620,23 @@ class LARMOR(ISISInstrument): ISISInstrument.on_load_sample(self, ws_name, beamcentre, isSample) + @staticmethod + def is_run_new_style_run(workspace_ref): + ''' + Checks if the run assiated with the workspace is pre or post 2217 + Original comment: + In order to avoid problems with files from initial commisioning during 2014. + these didn't have the required log entries for the detector position + @param workspace_ref:: A handle to the workspace + ''' + try: + run_num = workspace_ref.getRun().getLogData('run_number').value + except: + run_num = int(re.findall(r'\d+',str(ws_name))[-1]) + if int(run_num) >= 2217: + return True + else: + return False + if __name__ == '__main__': pass diff --git a/scripts/SANS/isis_reducer.py b/scripts/SANS/isis_reducer.py index 8b9195ee640a7dce0f32a1119cbd9efa08d64533..ad08212b01d0dff48e45ffae70d7deed7c818c28 100644 --- a/scripts/SANS/isis_reducer.py +++ b/scripts/SANS/isis_reducer.py @@ -271,7 +271,6 @@ class ISISReducer(Reducer): center = self.instrument.get_default_beam_center() self._beam_finder = isis_reduction_steps.BaseBeamFinder(center[0], center[1]) - def set_sample(self, run, reload, period): """ Assigns and load the run that this reduction chain will analysis @@ -671,3 +670,14 @@ class ISISReducer(Reducer): #if the workspace can't be deleted this function does nothing pass + def update_beam_center(self): + """ + Gets a possible new beam center position from the instrument after translation + or rotation. Previously this was not necessary as the reducer told the instrument + how to position, but now the instrument can get further positioning information + from the Instrument Parameter File. + """ + centre_pos1, centre_pos2 = self.instrument.get_updated_beam_centre_after_move() + # Update the beam centre finder for the rear + self._beam_finder.update_beam_center(centre_pos1, centre_pos2) + diff --git a/scripts/SANS/isis_reduction_steps.py b/scripts/SANS/isis_reduction_steps.py index f98175cf90062335d14d3cf0894e08d7e117e840..fb7bd90f899a96141a3d26895628cecd71e70462 100644 --- a/scripts/SANS/isis_reduction_steps.py +++ b/scripts/SANS/isis_reduction_steps.py @@ -1138,6 +1138,7 @@ class LoadSample(LoadRun): num = 0 while True: reducer.instrument.on_load_sample(self.wksp_name, reducer.get_beam_center(), isSample) + reducer.update_beam_center() num += 1 if num == self.periods_in_file: break @@ -2104,6 +2105,15 @@ class BaseBeamFinder(ReductionStep): def execute(self, reducer, workspace=None): return "Beam Center set at: %s %s" % (str(self._beam_center_x), str(self._beam_center_y)) + def update_beam_center(self, beam_center_x, beam_center_y): + ''' + Update the beam center position of the BeamBaseFinder + @param beam_center_x: The first position + @param beam_center_y: The second position + ''' + self._beam_center_x = beam_center_x + self._beam_center_y = beam_center_y + class UserFile(ReductionStep): """ diff --git a/scripts/test/SANSCentreFinderTest.py b/scripts/test/SANSCentreFinderTest.py new file mode 100644 index 0000000000000000000000000000000000000000..955269e543e76db6e80caa6662490bc25ef45188 --- /dev/null +++ b/scripts/test/SANSCentreFinderTest.py @@ -0,0 +1,270 @@ +import unittest +import mantid +from mantid.simpleapi import * +import centre_finder as cf +import ISISCommandInterface as command_iface +from reducer_singleton import ReductionSingleton +import isis_reduction_steps as reduction_steps + + +class SANSBeamCentrePositionUpdater(unittest.TestCase): + def test_that_find_ALL_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.ALL) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.1 + y_expected = 2.2 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + + def test_that_find_LEFTRIGHT_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.LEFT_RIGHT) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.1 + y_expected = 2.0 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + + def test_that_find_UPPDOWN_produces_correct_increment(self): + # Arrange + fac = cf.BeamCentrePositionUpdaterFactory() + position_updater = fac.create_beam_centre_position_updater(cf.FindDirectionEnum.UP_DOWN) + x = 1.0 + y = 2.0 + x_step = 0.1 + y_step = 0.2 + + # Act + x_new, y_new = position_updater.increment_position(x, y, x_step, y_step) + + # Assert + x_expected = 1.0 + y_expected = 2.2 + self.assertEqual(x_expected, x_new, "The x value should have been incremented.") + self.assertEqual(y_expected, y_new, "The y value should have been incremented.") + +class TestPositionProvider(unittest.TestCase): + workspace_name = 'dummy_ws' + + class MockSample(object): + ''' + Mocking out the sample + ''' + def __init__(self, ws_name): + super(TestPositionProvider.MockSample,self).__init__() + self.wksp_name = ws_name + def get_wksp_name(self): + return self.wksp_name + + def _provide_reducer(self, is_larmor, is_new = True): + ''' + Provide a reducer with either Larmor or non-Larmor. If we have Larmor, + then we want to be able to set the run number as well + ''' + command_iface.Clean() + if is_larmor and is_new: + command_iface.LARMOR() + CreateSampleWorkspace(OutputWorkspace=self.workspace_name) + AddSampleLog(Workspace=self.workspace_name,LogName='run_number', LogText='3000', LogType='Number') + sample = self.MockSample(self.workspace_name) + ReductionSingleton()._sample_run = sample + return ReductionSingleton() + + elif is_larmor and not is_new: + command_iface.LARMOR() + CreateSampleWorkspace(OutputWorkspace=self.workspace_name) + AddSampleLog(Workspace=self.workspace_name,LogName='run_number', LogText='1000', LogType='Number') + sample = self.MockSample(self.workspace_name) + ReductionSingleton()._sample_run = sample + return ReductionSingleton() + else: + command_iface.LOQ() + return ReductionSingleton() + + def _clean_up(self, workspace_name): + if workspace_name in mtd.getObjectNames(): + mtd.remove(workspace_name) + + def test_that_XY_increment_provider_is_created_for_non_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = False + reducer = self._provide_reducer(is_larmor) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderXY), "Should create a XY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_YAngle_increment_provider_is_created_for_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = True + is_new = True + reducer = self._provide_reducer(is_larmor, is_new) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderAngleY), "Should create a AngleY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_XY_increment_provider_is_created_for_old_larmor(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + is_larmor = True + is_new = False + reducer = self._provide_reducer(is_larmor, is_new) + # Act + factory = cf.PositionProviderFactory(increment_coord1 = increment_coord1, + increment_coord2 = increment_coord2, + tolerance = tolerance, + position_type = cf.FindDirectionEnum.ALL) + provider = factory.create_position_provider(reducer = reducer) + + # Asssert + self.assertTrue(isinstance(provider, cf.PositionProviderXY), "Should create a XY increment provider") + # Clean up + self._clean_up(self.workspace_name) + + def test_that_XY_increment_provider_halves_the_step(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act and Assert + self.assertTrue(increment_coord1 == provider.get_increment_coord1()) + self.assertTrue(increment_coord2 == provider.get_increment_coord2()) + + provider.half_and_reverse_increment_coord1() + provider.half_and_reverse_increment_coord2() + + self.assertTrue(-increment_coord1/2.0 == provider.get_increment_coord1()) + self.assertTrue(-increment_coord2/2.0 == provider.get_increment_coord2()) + + def test_that_AngleY_increment_provider_halves_the_step(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + tolerance_angle = 33 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act and Assert + self.assertTrue(increment_coord1 == provider.get_increment_coord1()) + self.assertTrue(increment_coord2 == provider.get_increment_coord2()) + + provider.half_and_reverse_increment_coord1() + provider.half_and_reverse_increment_coord2() + + self.assertTrue(-increment_coord1/2.0 == provider.get_increment_coord1()) + self.assertTrue(-increment_coord2/2.0 == provider.get_increment_coord2()) + + def test_that_XY_increment_is_smaller_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 200 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertTrue(is_smaller_coord1) + self.assertTrue(is_smaller_coord2) + + def test_that_XY_increment_is_larger_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 0.2 + provider = cf.PositionProviderXY(increment_coord1,increment_coord2,tolerance) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertFalse(is_smaller_coord1) + self.assertFalse(is_smaller_coord2) + + def test_that_AngleY_increment_is_smaller_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 100 + tolerance_angle = 233 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertTrue(is_smaller_coord1) + self.assertTrue(is_smaller_coord2) + + def test_that_AngleY_increment_is_larger_than_tolerance(self): + # Arrange + increment_coord1 = 1 + increment_coord2 = 2 + tolerance = 0.2 + tolerance_angle = 0.1 + bench_rotation = 1 + coord1_scale_factor = 1 + provider = cf.PositionProviderAngleY(increment_coord1,increment_coord2,tolerance,tolerance_angle, bench_rotation, coord1_scale_factor) + + # Act + is_smaller_coord1 = provider.is_coord1_increment_smaller_than_tolerance() + is_smaller_coord2 = provider.is_coord2_increment_smaller_than_tolerance() + + # Assert + self.assertFalse(is_smaller_coord1) + self.assertFalse(is_smaller_coord2) + + +if __name__ == "__main__": + unittest.main()