diff --git a/Code/Mantid/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/TOSCABankCorrection.py b/Code/Mantid/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/TOSCABankCorrection.py new file mode 100644 index 0000000000000000000000000000000000000000..498867d9255d05bbc559622ab1771f212f60acb0 --- /dev/null +++ b/Code/Mantid/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/TOSCABankCorrection.py @@ -0,0 +1,238 @@ +#pylint: disable=no-init +from mantid.kernel import * +from mantid.api import * +from mantid.simpleapi import * + + +class TOSCABankCorrection(DataProcessorAlgorithm): + + _input_ws = None + _output_ws = None + _search_range = None + _peak_tolerance = None + _peak_position = None + _peak_function = None + + + def category(self): + return 'PythonAlgorithms;Inelastic;CorrectionFunctions' + + + def summary(self): + return 'Corrects TOSCA reductions where the peaks across banks are not in alignment.' + + + def PyInit(self): + self.declareProperty(WorkspaceProperty(name='InputWorkspace', defaultValue='', + direction=Direction.Input), + doc='Input reduced workspace') + + self.declareProperty(FloatArrayProperty(name='SearchRange', + values=[200, 2000]), + doc='Range over which to find peaks') + + self.declareProperty(name='PeakPosition', defaultValue='', + doc='Specify a particular peak to use') + + self.declareProperty(name='ClosePeakTolerance', defaultValue=20.0, + doc='Tolerance under which peaks are considered to be the same') + + self.declareProperty(name='PeakFunction', defaultValue='Lorentzian', + validator=StringListValidator(['Lorentzian', 'Gaussian']), + doc='Type of peak to search for') + + self.declareProperty(MatrixWorkspaceProperty(name='OutputWorkspace', defaultValue='', + direction=Direction.Output), + doc='Output corrected workspace') + + self.declareProperty(name='TargetPeakCentre', defaultValue=0.0, + direction=Direction.Output, + doc='X position between the centres of the two ' \ + 'selected peaks') + + self.declareProperty(name='ScaleFactor1', defaultValue=1.0, + direction=Direction.Output, + doc='Scale factor for the first bank (histogram 0)') + + self.declareProperty(name='ScaleFactor2', defaultValue=1.0, + direction=Direction.Output, + doc='Scale factor for the second bank (histogram 1)') + + + def validateInputs(self): + self._get_properties() + issues = dict() + + # Validate search range + if len(self._search_range) != 2: + issues['SearchRange'] = 'Search range must have two values' + elif self._search_range[0] > self._search_range[1]: + issues['SearchRange'] = 'Search range must be in format "low,high"' + + # Ensure manual peak position is inside search range + if 'SearchRange' not in issues and self._peak_position is not None: + if self._peak_position < self._search_range[0] or self._peak_position > self._search_range[1]: + issues['PeakPosition'] = 'Peak position must be inside SearchRange' + + return issues + + + def PyExec(self): + self._get_properties() + + # Crop the sample workspace to the search range + CropWorkspace(InputWorkspace=self._input_ws, + OutputWorkspace='__search_ws', + XMin=self._search_range[0], + XMax=self._search_range[1]) + + peak = self._get_peak('__search_ws') + DeleteWorkspace('__search_ws') + + # Ensure there is at least one peak found + if peak is None: + raise RuntimeError('Could not find any peaks. Try increasing \ + width of SearchRange and/or ClosePeakTolerance') + + target_centre = (peak[0] + peak[1]) / 2.0 + bank_1_scale_factor = target_centre / peak[0] + bank_2_scale_factor = target_centre / peak[1] + + logger.information('Bank 1 scale factor: %f' % bank_1_scale_factor) + logger.information('Bank 2 scale factor: %f' % bank_2_scale_factor) + + self._apply_correction(bank_1_scale_factor, bank_2_scale_factor) + + self.setPropertyValue('OutputWorkspace', self._output_ws) + + self.setProperty('TargetPeakCentre', target_centre) + + self.setProperty('ScaleFactor1', bank_1_scale_factor) + self.setProperty('ScaleFactor2', bank_2_scale_factor) + + + def _get_properties(self): + self._input_ws = self.getPropertyValue('InputWorkspace') + self._output_ws = self.getPropertyValue('OutputWorkspace') + + self._search_range = self.getProperty('SearchRange').value + self._peak_tolerance = self.getProperty('ClosePeakTolerance').value + + self._peak_function = self.getPropertyValue('PeakFunction') + + try: + self._peak_position = float(self.getPropertyValue('PeakPosition')) + except ValueError: + self._peak_position = None + + + def _get_peak(self, search_ws): + """ + Finds a matching peak over the two banks. + + @param search_ws Workspace to search + @return Peak centres for matching peak over both banks + """ + + find_peak_args = dict() + if self._peak_position is not None: + find_peak_args['PeakPositions'] = [self._peak_position] + + # Find the peaks in each bank + FindPeaks(InputWorkspace=search_ws, + PeaksList='__bank_1_peaks', + WorkspaceIndex=0, + PeakFunction=self._peak_function, + **find_peak_args) + + FindPeaks(InputWorkspace=search_ws, + PeaksList='__bank_2_peaks', + WorkspaceIndex=1, + PeakFunction=self._peak_function, + **find_peak_args) + + # Sort peaks by height, prefer to match tall peaks + SortTableWorkspace(InputWorkspace='__bank_1_peaks', + OutputWorkspace='__bank_1_peaks', + Columns='centre', + Ascending=False) + + bank_1_ws = mtd['__bank_1_peaks'] + bank_2_ws = mtd['__bank_2_peaks'] + + matching_peaks = list() + + # Find the centres of two peaks that are close to each other on both banks + for peak_idx in range(0, bank_1_ws.rowCount()): + bank_1_centre = bank_1_ws.cell('centre', peak_idx) + + for other_peak_idx in range(0, bank_2_ws.rowCount()): + bank_2_centre = bank_2_ws.cell('centre', other_peak_idx) + + if abs(bank_1_centre - bank_2_centre) < self._peak_tolerance: + matching_peaks.append((bank_1_centre, bank_2_centre)) + + # Remove temporary workspaces + DeleteWorkspace('__bank_1_peaks') + DeleteWorkspace('__bank_2_peaks') + + if len(matching_peaks) > 0: + selected_peak = matching_peaks[0] + logger.debug('Found matching peak: %s' % (str(selected_peak))) + else: + selected_peak = None + logger.warning('No peak found') + + return selected_peak + + + def _apply_correction(self, bank_1_sf, bank_2_sf): + """ + Applies correction to a copy of the input workspace. + + @param bank_1_sf Bank 1 scale factor + @param bank_2_sf Bank 2 scale factor + """ + + # Create a copy of the workspace with just bank spectra + CropWorkspace(InputWorkspace=self._input_ws, + OutputWorkspace=self._output_ws, + StartWorkspaceIndex=0, + EndWorkspaceIndex=1) + + # Scale bank 1 X axis + ScaleX(InputWorkspace=self._output_ws, + OutputWorkspace=self._output_ws, + Operation='Multiply', + Factor=bank_1_sf, + IndexMin=0, + IndexMax=0) + + # Scale bank 2 X axis + ScaleX(InputWorkspace=self._output_ws, + OutputWorkspace=self._output_ws, + Operation='Multiply', + Factor=bank_2_sf, + IndexMin=1, + IndexMax=1) + + # Rebin the corrected workspace to match the input + RebinToWorkspace(WorkspaceToRebin=self._output_ws, + WorkspaceToMatch=self._input_ws, + OutputWorkspace=self._output_ws) + + # Recalculate the sum spectra from corrected banks + GroupDetectors(InputWorkspace=self._output_ws, + OutputWorkspace='__sum', + SpectraList=[0,1], + Behaviour='Average') + + # Add the new sum spectra to the output workspace + AppendSpectra(InputWorkspace1=self._output_ws, + InputWorkspace2='__sum', + OutputWorkspace=self._output_ws) + + DeleteWorkspace('__sum') + + +AlgorithmFactory.subscribe(TOSCABankCorrection) diff --git a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt index a9cc2c323b8e7e70ba314442152bb99cea6ad05e..64c61d26428868f5d92f66d5429a8fec00236de9 100644 --- a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt +++ b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt @@ -74,6 +74,7 @@ set ( TEST_PY_FILES UpdatePeakParameterTableValueTest.py SANSSubtractTest.py TimeSliceTest.py + TOSCABankCorrectionTest.py TransformToIqtTest.py ExportSampleLogsToCSVFileTest.py ExportExperimentLogTest.py diff --git a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/TOSCABankCorrectionTest.py b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/TOSCABankCorrectionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..32330e62e1d274d5f9cc57c726fb71d7fcbf9ad3 --- /dev/null +++ b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/TOSCABankCorrectionTest.py @@ -0,0 +1,118 @@ +import unittest +from mantid.simpleapi import * +from mantid.api import * + + +class TOSCABankCorrectionTest(unittest.TestCase): + + def setUp(self): + """ + Loads sample workspace. + """ + + self._original = '__TOSCABankCorrectionTest_original' + + Load(Filename='TSC14007_graphite002_red.nxs', + OutputWorkspace=self._original) + + + def tearDown(self): + """ + Removes workspaces. + """ + + DeleteWorkspace(self._original) + + + def test_automatic_peak_selection(self): + """ + Tests automatically finding a peak in the default range. + """ + + corrected_reduction, peak_position, scale_factor_1, scale_factor_2 = \ + TOSCABankCorrection(InputWorkspace=self._original) + + self.assertAlmostEqual(peak_position, 1077.47222328) + self.assertAlmostEqual(scale_factor_1, 1.0059271) + self.assertAlmostEqual(scale_factor_2, 0.9941423) + + + def test_automatic_peak_in_range(self): + """ + Tests automatically finding a peak in a given range. + """ + + corrected_reduction, peak_position, scale_factor_1, scale_factor_2 = \ + TOSCABankCorrection(InputWorkspace=self._original, + SearchRange=[200, 800]) + + self.assertAlmostEqual(peak_position, 713.20080359) + self.assertAlmostEqual(scale_factor_1, 1.006076146) + self.assertAlmostEqual(scale_factor_2, 0.993996806) + + + def test_manual_peak_selection(self): + """ + Tests using a peak provided by the user. + """ + + corrected_reduction, peak_position, scale_factor_1, scale_factor_2 = \ + TOSCABankCorrection(InputWorkspace=self._original, + PeakPosition='715') + + self.assertAlmostEqual(peak_position, 713.4430671) + self.assertAlmostEqual(scale_factor_1, 1.00611439) + self.assertAlmostEqual(scale_factor_2, 0.99395947) + + + def test_manual_peak_not_found(self): + """ + Tests error handling when a peak cannot be found using a manual peak position. + """ + + self.assertRaises(RuntimeError, + TOSCABankCorrection, + InputWorkspace=self._original, + OutputWorkspace='__TOSCABankCorrectionTest_output', + PeakPosition='900') + + + def test_validation_search_range_order(self): + """ + Tests validation to ensure low and high values are entered in correct order. + """ + + self.assertRaises(RuntimeError, + TOSCABankCorrection, + InputWorkspace=self._original, + OutputWorkspace='__TOSCABankCorrectionTest_output', + SearchRange=[500, 50]) + + + def test_validation_search_range_count(self): + """ + Tests validation to ensure two values exist values are entered in correct order. + """ + + self.assertRaises(RuntimeError, + TOSCABankCorrection, + InputWorkspace=self._original, + OutputWorkspace='__TOSCABankCorrectionTest_output', + SearchRange=[500]) + + + def test_validation_peak_position_in_search_range(self): + """ + Tests validation to ensure that the PeakPosition falls inside SearchRange. + """ + + self.assertRaises(RuntimeError, + TOSCABankCorrection, + InputWorkspace=self._original, + OutputWorkspace='__TOSCABankCorrectionTest_output', + SearchRange=[200, 2000], + PeakPosition='2500') + + +if __name__ == '__main__': + unittest.main() diff --git a/Code/Mantid/Testing/Data/DocTest/TSC14007_graphite002_red.nxs.md5 b/Code/Mantid/Testing/Data/DocTest/TSC14007_graphite002_red.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..4d60a5e8378b6418b6fec52a5f50e2c26c34bb05 --- /dev/null +++ b/Code/Mantid/Testing/Data/DocTest/TSC14007_graphite002_red.nxs.md5 @@ -0,0 +1 @@ +360b5639a909d42b099693b8d7a99b45 diff --git a/Code/Mantid/Testing/Data/UnitTest/TSC14007_graphite002_red.nxs.md5 b/Code/Mantid/Testing/Data/UnitTest/TSC14007_graphite002_red.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..4d60a5e8378b6418b6fec52a5f50e2c26c34bb05 --- /dev/null +++ b/Code/Mantid/Testing/Data/UnitTest/TSC14007_graphite002_red.nxs.md5 @@ -0,0 +1 @@ +360b5639a909d42b099693b8d7a99b45 diff --git a/Code/Mantid/docs/source/algorithms/IndirectResolution-v1.rst b/Code/Mantid/docs/source/algorithms/IndirectResolution-v1.rst index 4f72b1b0dc528b8ef64d3765d86292f8ead4f851..f6d387ef38e0ab9adb299707a9aa6ccd99512593 100644 --- a/Code/Mantid/docs/source/algorithms/IndirectResolution-v1.rst +++ b/Code/Mantid/docs/source/algorithms/IndirectResolution-v1.rst @@ -23,6 +23,8 @@ Workflow Usage ----- +.. include:: ../usagedata-note.txt + **Example - Running IndirectResolution.** .. testcode:: ExIndirectResolutionSimple diff --git a/Code/Mantid/docs/source/algorithms/IndirectTransmissionMonitor-v1.rst b/Code/Mantid/docs/source/algorithms/IndirectTransmissionMonitor-v1.rst index a081de39a7f061cbfe56b9da9906d86909c5d0da..c88003c746fe792422609bc2c60491f4790c883c 100644 --- a/Code/Mantid/docs/source/algorithms/IndirectTransmissionMonitor-v1.rst +++ b/Code/Mantid/docs/source/algorithms/IndirectTransmissionMonitor-v1.rst @@ -27,6 +27,8 @@ Workflow Usage ----- +.. include:: ../usagedata-note.txt + **Example - Create mapping file for IRIS** .. testcode:: exIRISTransmission diff --git a/Code/Mantid/docs/source/algorithms/TOSCABankCorrection-v1.rst b/Code/Mantid/docs/source/algorithms/TOSCABankCorrection-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..c345c4fbd11859e3a56fc2ce3f83e14f705d81ba --- /dev/null +++ b/Code/Mantid/docs/source/algorithms/TOSCABankCorrection-v1.rst @@ -0,0 +1,94 @@ +.. algorithm:: + +.. summary:: + +.. alias:: + +.. properties:: + +Description +----------- + +This algorithm attempts to automatically correct TOSCA data in which the +position of the sample has been moved and has affected the alignment of features +on the spectra from forward and backscattering detector banks. + +The input workspace should be an energy transfer reduction, for the default +values of SearchRange and ClosePeakTolerance the X axis is assumed to be in +cm-1, however the X axis is not restricted to this unit. + +The algorithm works by finding peaks of a given shape (using the :ref:`FindPeaks +<algm-FindPeaks>`) on both the forward and backscattering banks, either +selecting a peak in a given position or selecting the peak with the highest X +value and attempting to match them to what is believed to be the same feature on +the other bank. + +A scale factor is then calculated for each bank that will align at least the +selected peak and in doing so will also align the majority of misaligned peaks +across the two banks. + +The sacling factor is calculated as follows: + +.. math:: + + X_{centre} = \frac{X_{forward peak} + X_{back peak}}{2} + + SF_{forward} = \frac{X_{centre}}{X_{forward peak}} + + SF_{back} = \frac{X_{centre}}{X_{back peak}} + +The corrected spectra are then rebinned to the input workspace (using +:ref:`RebinToWorkspace <algm-RebinToWorkspace>`) to preserve the X range and to +maintain bin alignment. + +The sum spectra (containing both forward and back scattering detectors) is then +recalculated by averaging the intensities of the two corrected spectra, this +compensates for the broader peaks seen on the original sum spectra due to the +misalignment of the peaks. + +.. note:: + This algorithm is only intended to provide an approximation of what the + measured spectra would look like if the sample was in the expected sample + position. + +Usage +----- + +.. include:: ../usagedata-note.txt + +**Example - Automatic peak selection.** + +.. testcode:: ExTOSCABankCorrectionAutomatic + + original_reduction = Load('TSC14007_graphite002_red.nxs') + + corrected_reduction, peak_position, scale_factor_1, scale_factor_2 = \ + TOSCABankCorrection(InputWorkspace=original_reduction) + + print 'Target peak centre: %.f' % peak_position + +Output: + +.. testoutput:: ExTOSCABankCorrectionAutomatic + + Target peak centre: 1077 + +**Example - Manual peak selection.** + +.. testcode:: ExTOSCABankCorrectionManual + + original_reduction = Load('TSC14007_graphite002_red.nxs') + + corrected_reduction, peak_position, scale_factor_1, scale_factor_2 = \ + TOSCABankCorrection(InputWorkspace=original_reduction, + PeakPosition='715') + + print 'Target peak centre: %.f' % peak_position + +Output: + +.. testoutput:: ExTOSCABankCorrectionManual + + Target peak centre: 713 + +.. categories::