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/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp index 9e0a2d7917dc9f0237286363bc36947d5888ff7c..0d523b4a07c898f67bd9781d66df6e293fb683fe 100644 --- a/Framework/API/src/MatrixWorkspace.cpp +++ b/Framework/API/src/MatrixWorkspace.cpp @@ -1135,6 +1135,11 @@ MatrixWorkspace::maskedBins(const size_t &workspaceIndex) const { */ void MatrixWorkspace::setMonitorWorkspace( const boost::shared_ptr<MatrixWorkspace> &monitorWS) { + if (monitorWS.get() == this) { + throw std::runtime_error( + "To avoid memory leak, monitor workspace" + " can not be the same workspace as the host workspace"); + } m_monitorWorkspace = monitorWS; } diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/SplineSmoothing.h b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/SplineSmoothing.h index ee17aa3dca0fb55f9ece30aee9b9fb5fc0cc8503..4c35d7f729baca9fd037e16cada539f6911e1873 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/SplineSmoothing.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/SplineSmoothing.h @@ -62,7 +62,7 @@ private: void exec(); /// smooth a single spectrum of the workspace - void smoothSpectrum(int index, int maxBreaks); + void smoothSpectrum(int index); /// calculate derivatives for a single spectrum void calculateSpectrumDerivatives(int index, int order); @@ -82,7 +82,7 @@ private: /// choose points to define a spline and smooth the data void selectSmoothingPoints(API::MatrixWorkspace_const_sptr inputWorkspace, - size_t row, int maxBreaks); + size_t row); /// calculate the spline based on the smoothing points chosen void calculateSmoothing(API::MatrixWorkspace_const_sptr inputWorkspace, diff --git a/Framework/CurveFitting/src/Algorithms/SplineSmoothing.cpp b/Framework/CurveFitting/src/Algorithms/SplineSmoothing.cpp index 87fcfb16e36bfd4af6e18a84ed51b9c459f27e3c..48b809991b7f6eaacaa25569817c71ecb809612a 100644 --- a/Framework/CurveFitting/src/Algorithms/SplineSmoothing.cpp +++ b/Framework/CurveFitting/src/Algorithms/SplineSmoothing.cpp @@ -73,8 +73,8 @@ void SplineSmoothing::init() { auto numOfBreaks = boost::make_shared<BoundedValidator<int>>(); numOfBreaks->setLower(0); - declareProperty("MaxNumberOfBreaks", M_START_SMOOTH_POINTS, numOfBreaks, - "To set the positions of the break-points, default 10 " + declareProperty("MaxNumberOfBreaks", 0, numOfBreaks, + "To set the positions of the break-points, default 0 " "equally spaced real values in interval 0.0 - 1.0"); } @@ -87,9 +87,6 @@ void SplineSmoothing::exec() { int histNo = static_cast<int>(m_inputWorkspace->getNumberHistograms()); int order = static_cast<int>(getProperty("DerivOrder")); - // retrieving number of breaks - int maxBreaks = static_cast<int>(getProperty("MaxNumberOfBreaks")); - m_inputWorkspacePointData = convertBinnedData(m_inputWorkspace); m_outputWorkspace = setupOutputWorkspace(m_inputWorkspacePointData, histNo); @@ -100,7 +97,7 @@ void SplineSmoothing::exec() { Progress pgress(this, 0.0, 1.0, histNo); for (int i = 0; i < histNo; ++i) { - smoothSpectrum(i, maxBreaks); + smoothSpectrum(i); calculateSpectrumDerivatives(i, order); pgress.report(); } @@ -115,14 +112,13 @@ void SplineSmoothing::exec() { /** Smooth a single spectrum of the input workspace * * @param index :: index of the spectrum to smooth - * @param maxBreaks :: the number to set the positions of the break-points */ -void SplineSmoothing::smoothSpectrum(int index, int maxBreaks) { +void SplineSmoothing::smoothSpectrum(int index) { m_cspline = boost::make_shared<BSpline>(); m_cspline->setAttributeValue("Uniform", false); // choose some smoothing points from input workspace - selectSmoothingPoints(m_inputWorkspacePointData, index, maxBreaks); + selectSmoothingPoints(m_inputWorkspacePointData, index); performAdditionalFitting(m_inputWorkspacePointData, index); // compare the data set against our spline @@ -310,25 +306,34 @@ void SplineSmoothing::addSmoothingPoints(const std::set<int> &points, * * @param inputWorkspace :: The input workspace containing noisy data * @param row :: The row of spectra to use - * @param maxBreaks :: the number to set the positions of the break-points */ void SplineSmoothing::selectSmoothingPoints( - MatrixWorkspace_const_sptr inputWorkspace, size_t row, int maxBreaks) { + MatrixWorkspace_const_sptr inputWorkspace, size_t row) { std::set<int> smoothPts; const auto &xs = inputWorkspace->readX(row); const auto &ys = inputWorkspace->readY(row); + // retrieving number of breaks + int maxBreaks = static_cast<int>(getProperty("MaxNumberOfBreaks")); + int xSize = static_cast<int>(xs.size()); - // if retrienved value is default zero - if (maxBreaks == 0) { - setProperty("MaxNumberOfBreaks", xs.size()); - maxBreaks = getProperty("MaxNumberOfBreaks"); - } - // number of points to start with - int numSmoothPts(maxBreaks); // evenly space initial points over data set - int delta = xSize / numSmoothPts; + int delta; + + // if retrieved value is default zero/default + bool incBreaks = false; + + if (maxBreaks != 0) { + // number of points to start with + int numSmoothPts(maxBreaks); + delta = xSize / numSmoothPts; + // include maxBreaks when != 0 + incBreaks = true; + } else { + int numSmoothPts(M_START_SMOOTH_POINTS); + delta = xSize / numSmoothPts; + } for (int i = 0; i < xSize; i += delta) { smoothPts.insert(i); @@ -337,9 +342,17 @@ void SplineSmoothing::selectSmoothingPoints( bool resmooth(true); while (resmooth) { - // if we're using all points then we can't do anything more. - if (smoothPts.size() > (unsigned)(maxBreaks + 2)) - break; + + if (incBreaks) { + if (smoothPts.size() > (unsigned)(maxBreaks + 2)) { + break; + } + + } else if (!incBreaks) { + if (smoothPts.size() >= xs.size() - 1) { + break; + } + } addSmoothingPoints(smoothPts, xs.data(), ys.data()); resmooth = false; diff --git a/Framework/DataHandling/src/LoadFITS.cpp b/Framework/DataHandling/src/LoadFITS.cpp index 533ec1fa764fa4540486146706daf982914b9989..1c76ddf36f3423ed78d5d6dc9a13715b775c1683 100644 --- a/Framework/DataHandling/src/LoadFITS.cpp +++ b/Framework/DataHandling/src/LoadFITS.cpp @@ -8,6 +8,7 @@ #include "MantidKernel/UnitFactory.h" #include <boost/algorithm/string.hpp> +#include <boost/scoped_ptr.hpp> #include <Poco/BinaryReader.h> #include <Poco/FileStream.h> @@ -18,17 +19,28 @@ using namespace Mantid::DataObjects; using namespace Mantid::API; using namespace Mantid::Kernel; +namespace { + +/** + * Reinterpret a byte sequence as InterpretType and cast to double + * @param Pointer to byte src + */ +template <typename InterpretType> double toDouble(uint8_t *src) { + return static_cast<double>(*reinterpret_cast<InterpretType *>(src)); +} +} + namespace Mantid { namespace DataHandling { // Register the algorithm into the AlgorithmFactory DECLARE_FILELOADER_ALGORITHM(LoadFITS) +// Static class constants const std::string LoadFITS::g_BIT_DEPTH_NAME = "BitDepthName"; const std::string LoadFITS::g_ROTATION_NAME = "RotationName"; const std::string LoadFITS::g_AXIS_NAMES_NAME = "AxisNames"; const std::string LoadFITS::g_IMAGE_KEY_NAME = "ImageKeyName"; const std::string LoadFITS::g_HEADER_MAP_NAME = "HeaderMapFile"; - const std::string LoadFITS::g_defaultImgType = "SAMPLE"; /** @@ -781,55 +793,50 @@ void LoadFITS::addAxesInfoAndLogs(Workspace2D_sptr ws, bool loadAsRectImg, void LoadFITS::readDataToWorkspace(const FITSInfo &fileInfo, double cmpp, Workspace2D_sptr ws, std::vector<char> &buffer) { - - size_t bytespp = (fileInfo.bitsPerPixel / 8); - size_t len = m_pixelCount * bytespp; + const size_t bytespp = (fileInfo.bitsPerPixel / 8); + const size_t len = m_pixelCount * bytespp; readInBuffer(fileInfo, buffer, len); - // create pointer of correct data type to void pointer of the buffer: - uint8_t *buffer8 = reinterpret_cast<uint8_t *>(&buffer[0]); + const size_t nrows(fileInfo.axisPixelLengths[1]), + ncols(fileInfo.axisPixelLengths[0]); + // Treat buffer as a series of bytes + uint8_t *buffer8 = reinterpret_cast<uint8_t *>(&buffer.front()); PARALLEL_FOR_NO_WSP_CHECK() - for (int i = 0; i < static_cast<int>(fileInfo.axisPixelLengths[1]); - ++i) { // rows + for (int i = 0; i < static_cast<int>(nrows); ++i) { Mantid::API::ISpectrum *specRow = ws->getSpectrum(i); - double xval = static_cast<double>(i) * cmpp; - std::fill(specRow->dataX().begin(), specRow->dataX().end(), xval); - - for (size_t j = 0; j < fileInfo.axisPixelLengths[0]; ++j) { // columns - - size_t start = - ((i * (bytespp)) * fileInfo.axisPixelLengths[1]) + (j * (bytespp)); - - char tmpbuf; - char *tmp = &tmpbuf; - - // Reverse byte order of current value - std::reverse_copy(buffer8 + start, buffer8 + start + bytespp, tmp); + auto &dataX = specRow->dataX(); + auto &dataY = specRow->dataY(); + auto &dataE = specRow->dataE(); + std::fill(dataX.begin(), dataX.end(), static_cast<double>(i) * cmpp); + + for (size_t j = 0; j < ncols; ++j) { + // Map from 2D->1D index + const size_t start = ((i * (bytespp)) * nrows) + (j * (bytespp)); + uint8_t const *const buffer8Start = buffer8 + start; + // Reverse byte order of current value. Make sure we allocate enough + // enough space to hold the size + boost::scoped_ptr<uint8_t> byteValue(new uint8_t[bytespp]); + std::reverse_copy(buffer8Start, buffer8Start + bytespp, byteValue.get()); double val = 0; - if (fileInfo.bitsPerPixel == 8) - val = static_cast<double>(*reinterpret_cast<uint8_t *>(tmp)); - else if (fileInfo.bitsPerPixel == 16) - val = static_cast<double>(*reinterpret_cast<uint16_t *>(tmp)); - else if (fileInfo.bitsPerPixel == 32 && !fileInfo.isFloat) - val = static_cast<double>(*reinterpret_cast<uint32_t *>(tmp)); - else if (fileInfo.bitsPerPixel == 64 && !fileInfo.isFloat) - val = static_cast<double>(*reinterpret_cast<uint64_t *>(tmp)); - - // cppcheck doesn't realise that these are safe casts - else if (fileInfo.bitsPerPixel == 32 && fileInfo.isFloat) { - // cppcheck-suppress invalidPointerCast - val = static_cast<double>(*reinterpret_cast<float *>(tmp)); + if (fileInfo.bitsPerPixel == 8) { + val = toDouble<uint8_t>(byteValue.get()); + } else if (fileInfo.bitsPerPixel == 16) { + val = toDouble<uint16_t>(byteValue.get()); + } else if (fileInfo.bitsPerPixel == 32 && !fileInfo.isFloat) { + val = toDouble<uint32_t>(byteValue.get()); + } else if (fileInfo.bitsPerPixel == 64 && !fileInfo.isFloat) { + val = toDouble<uint32_t>(byteValue.get()); + } else if (fileInfo.bitsPerPixel == 32 && fileInfo.isFloat) { + val = toDouble<float>(byteValue.get()); } else if (fileInfo.bitsPerPixel == 64 && fileInfo.isFloat) { - // cppcheck-suppress invalidPointerCast - val = *reinterpret_cast<double *>(tmp); + val = toDouble<double>(byteValue.get()); } val = fileInfo.scale * val - fileInfo.offset; - - specRow->dataY()[j] = val; - specRow->dataE()[j] = sqrt(val); + dataY[j] = val; + dataE[j] = sqrt(val); } } } diff --git a/Framework/DataHandling/test/LoadSPETest.h b/Framework/DataHandling/test/LoadSPETest.h index 79f6c98187c7e7208f8ebab8cff45ab29a750679..efea42b7e3f35898aca3a7b033dfd0cb71028738 100644 --- a/Framework/DataHandling/test/LoadSPETest.h +++ b/Framework/DataHandling/test/LoadSPETest.h @@ -61,7 +61,7 @@ public: TS_ASSERT_EQUALS(ws->readX(0)[0], -20.0); TS_ASSERT_EQUALS(ws->readX(22)[86], -2.8); TS_ASSERT_EQUALS(ws->readX(31)[195], 19.0); - + // verify result is NaN TS_ASSERT_DIFFERS(ws->readY(4)[99], ws->readY(4)[99]); TS_ASSERT_EQUALS(ws->readY(5)[0], 0.0); TS_ASSERT_EQUALS(ws->readY(9)[48], -3.911); diff --git a/Framework/DataObjects/inc/MantidDataObjects/MDEventWorkspace.tcc b/Framework/DataObjects/inc/MantidDataObjects/MDEventWorkspace.tcc index 6a0470c99964107a06a5d9f9246503099e180ec9..4050994a565d549210304a7bf774ad56c3282f49 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/MDEventWorkspace.tcc +++ b/Framework/DataObjects/inc/MantidDataObjects/MDEventWorkspace.tcc @@ -9,6 +9,7 @@ #include "MantidKernel/ThreadScheduler.h" #include "MantidKernel/Timer.h" #include "MantidKernel/Utils.h" +#include "MantidKernel/WarningSuppressions.h" #include "MantidDataObjects/MDBoxBase.h" #include "MantidDataObjects/MDBox.h" #include "MantidDataObjects/MDEventWorkspace.h" @@ -22,6 +23,14 @@ #include "MantidKernel/Memory.h" #include "MantidKernel/Exception.h" +// Test for gcc 4.4 +#if __GNUC__ > 4 || \ + (__GNUC__ == 4 && (__GNUC_MINOR__ > 4 || \ + (__GNUC_MINOR__ == 4 && \ + __GNUC_PATCHLEVEL__ > 0))) +GCC_DIAG_OFF(strict-aliasing) +#endif + using namespace Mantid; using namespace Mantid::Kernel; using namespace Mantid::API; diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h index ac82cd7fde4f78c5d9e175e6bdba43a31c9a0df6..84d466869ddae66b62d8b5c17c8ea5038b127c06 100644 --- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h +++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h @@ -40,6 +40,8 @@ public: virtual const std::string category() const; virtual const std::string summary() const; + virtual const std::string alias() const { return "PermuteMD"; } + private: void init(); void exec(); diff --git a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp index fbb754ab25819191b32321b6a3133e9441408f20..2f6cdb9bddca741008f2c17cede38cb09f0985dd 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp @@ -2,6 +2,7 @@ #include "MantidAPI/WorkspaceOpOverloads.h" #include "MantidPythonInterface/api/CloneMatrixWorkspace.h" +#include "MantidPythonInterface/api/ExtractWorkspace.h" #include "MantidPythonInterface/kernel/Converters/WrapWithNumpy.h" #include "MantidPythonInterface/kernel/Policies/RemoveConst.h" #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" @@ -77,6 +78,37 @@ void setSpectrumFromPyObject(MatrixWorkspace &self, data_modifier accessor, } } +/** + * Set a workspace as monitor workspace for current workspace. + * + * @param self :: A reference to the calling object + * @param value :: The python pointer to the workspace to set + */ +void setMonitorWorkspace(MatrixWorkspace &self, + const boost::python::object &value) { + + MatrixWorkspace_sptr monWS = boost::dynamic_pointer_cast<MatrixWorkspace>( + Mantid::PythonInterface::ExtractWorkspace(value)()); + self.setMonitorWorkspace(monWS); +} +/** +* @param self :: A reference to the calling object +* +*@return weak pointer to monitor workspace used by python +*/ +boost::weak_ptr<Workspace> getMonitorWorkspace(MatrixWorkspace &self) { + return boost::weak_ptr<Workspace>(self.monitorWorkspace()); +} +/** + * Clear monitor workspace attached to for current workspace. + * + * @param self :: A reference to the calling object +*/ +void clearMonitorWorkspace(MatrixWorkspace &self) { + MatrixWorkspace_sptr monWS; + self.setMonitorWorkspace(monWS); +} + /** * Set the X values from an python array-style object * @param self :: A reference to the calling object @@ -317,7 +349,19 @@ void export_MatrixWorkspace() { //----------------------------------- .def("equals", &Mantid::API::equals, args("self", "other", "tolerance"), "Performs a comparison operation on two workspaces, using the " - "CheckWorkspacesMatch algorithm"); + "CheckWorkspacesMatch algorithm") + //--------- monitor workspace -------------------------------------- + .def("getMonitorWorkspace", &getMonitorWorkspace, args("self"), + "Return internal monitor workspace bound to current workspace.") + .def("setMonitorWorkspace", &setMonitorWorkspace, + args("self", "MonitorWS"), + "Set specified workspace as monitor workspace for" + "current workspace. " + "Note: The workspace does not have to contain monitors though " + "some subsequent algorithms may expect it to be " + "monitor workspace later.") + .def("clearMonitorWorkspace", &clearMonitorWorkspace, args("self"), + "Forget about monitor workspace, attached to the current workspace"); RegisterWorkspacePtrToPython<MatrixWorkspace>(); } diff --git a/Framework/PythonInterface/plugins/algorithms/LRScalingFactors.py b/Framework/PythonInterface/plugins/algorithms/LRScalingFactors.py index 13a289875724a6f9ab4fd82a735d30dc1e9da5e9..17140b801b2eab7f40764635fd0e051f735d76ef 100644 --- a/Framework/PythonInterface/plugins/algorithms/LRScalingFactors.py +++ b/Framework/PythonInterface/plugins/algorithms/LRScalingFactors.py @@ -58,9 +58,7 @@ class LRScalingFactors(PythonAlgorithm): "Pixel range defining the data peak") self.declareProperty(IntArrayProperty("SignalBackgroundPixelRange", [147, 163]), "Pixel range defining the background") - self.declareProperty(IntArrayProperty("LowResolutionPixelRange", [94, 160], - IntArrayLengthValidator(2), - direction=Direction.Input), + self.declareProperty(IntArrayProperty("LowResolutionPixelRange", [94, 160]), "Pixel range defining the region to use in the low-resolution direction") self.declareProperty("IncidentMedium", "Air", doc="Name of the incident medium") self.declareProperty("FrontSlitName", "S1", doc="Name of the front slit") @@ -96,6 +94,7 @@ class LRScalingFactors(PythonAlgorithm): # Get peak ranges peak_range = self.getProperty("SignalPeakPixelRange").value background_range = self.getProperty("SignalBackgroundPixelRange").value + lowres_range = self.getProperty("LowResolutionPixelRange").value # Supply good values for peak ranges # If we supplied two values, use those boundaries for each run @@ -119,6 +118,16 @@ class LRScalingFactors(PythonAlgorithm): elif len(background_range) < 2: raise RuntimeError("SignalBackgroundPixelRange should have a length of at least 2.") + if len(lowres_range)==2: + x_min = int(lowres_range[0]) + x_max = int(lowres_range[1]) + lowres_range = 2*len(data_runs)*[0] + for i in range(len(data_runs)): + lowres_range[2*i] = x_min + lowres_range[2*i+1] = x_max + elif len(lowres_range) < 2: + raise RuntimeError("LowResolutionPixelRange should have a length of at least 2.") + # Check that the peak range arrays are of the proper length if not (len(peak_range) == 2*len(data_runs) \ and len(background_range) == 2*len(data_runs)): @@ -170,9 +179,11 @@ class LRScalingFactors(PythonAlgorithm): peak = [int(peak_range[2*i]), int(peak_range[2*i+1])] background = [int(background_range[2*i]), int(background_range[2*i+1])] + low_res = [int(lowres_range[2*i]), int(lowres_range[2*i+1])] self.process_data(workspace, peak_range=peak, - background_range=background) + background_range=background, + low_res_range=low_res) # If we don't have the attenuator information and we have the # same slit settings as the previous run, it means we added an @@ -351,12 +362,13 @@ class LRScalingFactors(PythonAlgorithm): fd.write("error_b=%s\n" % item["error_b"]) fd.close() - def process_data(self, workspace, peak_range, background_range): + def process_data(self, workspace, peak_range, background_range, low_res_range): """ Common processing for both sample data and normalization. @param workspace: name of the workspace to work with @param peak_range: range of pixels defining the peak @param background_range: range of pixels defining the background + @param low_res_range: range of pixels in the x-direction """ # Rebin TOF axis tof_range = self.getProperty("TOFRange").value @@ -364,16 +376,11 @@ class LRScalingFactors(PythonAlgorithm): workspace = Rebin(InputWorkspace=workspace, Params=[tof_range[0], tof_step, tof_range[1]], PreserveEvents=False, OutputWorkspace=str(workspace)) - # Integrate over low resolution range - low_res_range = self.getProperty("LowResolutionPixelRange").value - x_min = int(low_res_range[0]) - x_max = int(low_res_range[1]) - # Subtract background workspace = LRSubtractAverageBackground(InputWorkspace=workspace, PeakRange=peak_range, BackgroundRange=background_range, - LowResolutionRange=[x_min, x_max], + LowResolutionRange=low_res_range, OutputWorkspace=str(workspace)) # Normalize by current proton charge diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ApplyPaalmanPingsCorrection.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ApplyPaalmanPingsCorrection.py index 525db109bfc34a1bfc05a30e1cce7d45388c2892..be6133556b7449eef5cbf90cc4d650221c6a8663 100644 --- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ApplyPaalmanPingsCorrection.py +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ApplyPaalmanPingsCorrection.py @@ -247,6 +247,12 @@ class ApplyPaalmanPingsCorrection(PythonAlgorithm): Do a simple container subtraction (when no corrections are given). """ + + logger.information('Rebining container to ensure Minus') + RebinToWorkspace(WorkspaceToRebin=self._can_ws_name, + WorkspaceToMatch=self._sample_ws_name, + OutputWorkspace=self._can_ws_name) + logger.information('Using simple container subtraction') Minus(LHSWorkspace=self._sample_ws_name, diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/QLRun.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/QLRun.py new file mode 100644 index 0000000000000000000000000000000000000000..51a7070d2a21ff1cfee678ac2d7d8c570e6f0dac --- /dev/null +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/QLRun.py @@ -0,0 +1,361 @@ +#pylint: disable=invalid-name,too-many-instance-attributes,too-many-branches,no-init +from IndirectImport import * + +from mantid.api import PythonAlgorithm, AlgorithmFactory, MatrixWorkspaceProperty, PropertyMode, WorkspaceGroupProperty +from mantid.kernel import StringListValidator, Direction +from mantid.simpleapi import * +from mantid import config, logger +import os +import numpy as np + +if is_supported_f2py_platform(): + QLr = import_f2py("QLres") + QLd = import_f2py("QLdata") + Qse = import_f2py("QLse") + +class QLRun(PythonAlgorithm): + + _program = None + _samWS = None + _resWS = None + _resnormWS = None + _e_min = None + _e_max = None + _sam_bins = None + _res_bins = None + _elastic = None + _background = None + _width = None + _res_norm = None + _wfile = None + _loop = None + _save = None + _plot = None + + def category(self): + return "Workflow\\MIDAS;PythonAlgorithms" + + def summary(self): + return "This algorithm runs the Fortran QLines programs which fits a Delta function of"+\ + " amplitude 0 and Lorentzians of amplitude A(j) and HWHM W(j) where j=1,2,3. The"+\ + " whole function is then convoled with the resolution function." + + def version(self): + return 1 + + def PyInit(self): + self.declareProperty(name='Program', defaultValue='QL', + validator=StringListValidator(['QL','QSe']), + doc='The type of program to run (either QL or QSe)') + + self.declareProperty(MatrixWorkspaceProperty('SampleWorkspace', '', direction=Direction.Input), + doc='Name of the Sample input Workspace') + + self.declareProperty(MatrixWorkspaceProperty('ResolutionWorkspace', '', direction=Direction.Input), + doc='Name of the resolution input Workspace') + + self.declareProperty(MatrixWorkspaceProperty('ResNormWorkspace', '', + optional=PropertyMode.Optional, direction=Direction.Input), + doc='Name of the ResNorm input Workspace') + + self.declareProperty(name='MinRange', defaultValue=-0.2, + doc='The start of the fit range. Default=-0.2') + + self.declareProperty(name='MaxRange', defaultValue=0.2, + doc='The end of the fit range. Default=0.2') + + self.declareProperty(name='SampleBins', defaultValue=1, + doc='The number of sample bins') + + self.declareProperty(name='ResolutionBins', defaultValue=1, + doc='The number of resolution bins') + + self.declareProperty(name='Elastic', defaultValue=True, + doc='Fit option for using the elastic peak') + + self.declareProperty(name='Background', defaultValue='Flat', + validator=StringListValidator(['Sloping','Flat','Zero']), + doc='Fit option for the type of background') + + self.declareProperty(name='FixedWidth', defaultValue=True, + doc='Fit option for using FixedWidth') + + self.declareProperty(name='UseResNorm', defaultValue=False, + doc='fit option for using ResNorm') + + self.declareProperty(name='WidthFile', defaultValue='', doc='The name of the fixedWidth file') + + self.declareProperty(name='Loop', defaultValue=True, doc='Switch Sequential fit On/Off') + + self.declareProperty(name='Plot', defaultValue='', doc='Plot options') + + self.declareProperty(name='Save', defaultValue=False, doc='Switch Save result to nxs file Off/On') + + self.declareProperty(WorkspaceGroupProperty('OutputWorkspaceFit', '', direction=Direction.Output), + doc='The name of the fit output workspaces') + + self.declareProperty(MatrixWorkspaceProperty('OutputWorkspaceResult', '', direction=Direction.Output), + doc='The name of the result output workspaces') + + self.declareProperty(MatrixWorkspaceProperty('OutputWorkspaceProb', '', optional=PropertyMode.Optional, + direction=Direction.Output), + doc='The name of the probability output workspaces') + + + def validateInputs(self): + self._get_properties() + issues = dict() + + # Validate fitting range in energy + if self._e_min > self._e_max: + issues['MaxRange'] = 'Must be less than EnergyMin' + + return issues + + + def _get_properties(self): + self._program = self.getPropertyValue('Program') + self._samWS = self.getPropertyValue('SampleWorkspace') + self._resWS = self.getPropertyValue('ResolutionWorkspace') + self._resnormWS = self.getPropertyValue('ResNormWorkspace') + self._e_min = self.getProperty('MinRange').value + self._e_max = self.getProperty('MaxRange').value + self._sam_bins = self.getPropertyValue('SampleBins') + self._res_bins = self.getPropertyValue('ResolutionBins') + self._elastic = self.getProperty('Elastic').value + self._background = self.getPropertyValue('Background') + self._width = self.getProperty('FixedWidth').value + self._res_norm = self.getProperty('UseResNorm').value + self._wfile = self.getPropertyValue('WidthFile') + self._loop = self.getProperty('Loop').value + self._save = self.getProperty('Save').value + self._plot = self.getPropertyValue('Plot') + + #pylint: disable=too-many-locals,too-many-statements + def PyExec(self): + #from IndirectImport import run_f2py_compatibility_test, is_supported_f2py_platform + + run_f2py_compatibility_test() + + from IndirectBayes import (CalcErange, GetXYE, ReadNormFile, + ReadWidthFile, QLAddSampleLogs, C2Fw, + C2Se, QuasiPlot) + from IndirectCommon import (CheckXrange, CheckAnalysers, getEfixed, GetThetaQ, + CheckHistZero, CheckHistSame) + + self.log().information('QLRun input') + + erange = [self._e_min, self._e_max] + nbins = [self._sam_bins, self._res_bins] + + #convert true/false to 1/0 for fortran + o_el = 1 if self._elastic else 0 + o_w1 = 1 if self._width else 0 + o_res = 1 if self._res_norm else 0 + + #fortran code uses background choices defined using the following numbers + if self._background == 'Sloping': + o_bgd = 2 + elif self._background == 'Flat': + o_bgd = 1 + elif self._background == 'Zero': + o_bgd = 0 + + fitOp = [o_el, o_bgd, o_w1, o_res] + + workdir = config['defaultsave.directory'] + if not os.path.isdir(workdir): + workdir = os.getcwd() + logger.information('Default Save directory is not set. Defaulting to current working Directory: ' + workdir) + + array_len = 4096 # length of array in Fortran + CheckXrange(erange,'Energy') + + nbin,nrbin = nbins[0], nbins[1] + + logger.information('Sample is ' + self._samWS) + logger.information('Resolution is ' + self._resWS) + + CheckAnalysers(self._samWS,self._resWS) + efix = getEfixed(self._samWS) + theta, Q = GetThetaQ(self._samWS) + + nsam,ntc = CheckHistZero(self._samWS) + + totalNoSam = nsam + + #check if we're performing a sequential fit + if self._loop != True: + nsam = 1 + + nres = CheckHistZero(self._resWS)[0] + + if self._program == 'QL': + if nres == 1: + prog = 'QLr' # res file + else: + prog = 'QLd' # data file + CheckHistSame(self._samWS,'Sample',self._resWS,'Resolution') + elif self._program == 'QSe': + if nres == 1: + prog = 'QSe' # res file + else: + raise ValueError('Stretched Exp ONLY works with RES file') + + logger.information('Version is ' +prog) + logger.information(' Number of spectra = '+str(nsam)) + logger.information(' Erange : '+str(erange[0])+' to '+str(erange[1])) + + Wy,We = ReadWidthFile(self._width,self._wfile,totalNoSam) + dtn,xsc = ReadNormFile(self._res_norm,self._resnormWS,totalNoSam) + + fname = self._samWS[:-4] + '_'+ prog + probWS = fname + '_Prob' + fitWS = fname + '_Fit' + wrks=os.path.join(workdir, self._samWS[:-4]) + logger.information(' lptfile : '+wrks+'_'+prog+'.lpt') + lwrk=len(wrks) + wrks.ljust(140,' ') + wrkr=self._resWS + wrkr.ljust(140,' ') + + # initialise probability list + if self._program == 'QL': + prob0 = [] + prob1 = [] + prob2 = [] + xQ = np.array([Q[0]]) + for m in range(1,nsam): + xQ = np.append(xQ,Q[m]) + xProb = xQ + xProb = np.append(xProb,xQ) + xProb = np.append(xProb,xQ) + eProb = np.zeros(3*nsam) + + group = '' + for m in range(0,nsam): + logger.information('Group ' +str(m)+ ' at angle '+ str(theta[m])) + nsp = m+1 + nout,bnorm,Xdat,Xv,Yv,Ev = CalcErange(self._samWS,m,erange,nbin) + Ndat = nout[0] + Imin = nout[1] + Imax = nout[2] + if prog == 'QLd': + mm = m + else: + mm = 0 + Nb,Xb,Yb,Eb = GetXYE(self._resWS,mm,array_len) # get resolution data + numb = [nsam, nsp, ntc, Ndat, nbin, Imin, Imax, Nb, nrbin] + rscl = 1.0 + reals = [efix, theta[m], rscl, bnorm] + + if prog == 'QLr': + nd,xout,yout,eout,yfit,yprob=QLr.qlres(numb,Xv,Yv,Ev,reals,fitOp, + Xdat,Xb,Yb,Wy,We,dtn,xsc, + wrks,wrkr,lwrk) + message = ' Log(prob) : '+str(yprob[0])+' '+str(yprob[1])+' '+str(yprob[2])+' '+str(yprob[3]) + logger.information(message) + if prog == 'QLd': + nd,xout,yout,eout,yfit,yprob=QLd.qldata(numb,Xv,Yv,Ev,reals,fitOp, + Xdat,Xb,Yb,Eb,Wy,We, + wrks,wrkr,lwrk) + message = ' Log(prob) : '+str(yprob[0])+' '+str(yprob[1])+' '+str(yprob[2])+' '+str(yprob[3]) + logger.information(message) + if prog == 'QSe': + nd,xout,yout,eout,yfit,yprob=Qse.qlstexp(numb,Xv,Yv,Ev,reals,fitOp,\ + Xdat,Xb,Yb,Wy,We,dtn,xsc,\ + wrks,wrkr,lwrk) + dataX = xout[:nd] + dataX = np.append(dataX,2*xout[nd-1]-xout[nd-2]) + yfit_list = np.split(yfit[:4*nd],4) + dataF1 = yfit_list[1] + if self._program == 'QL': + dataF2 = yfit_list[2] + dataG = np.zeros(nd) + datX = dataX + datY = yout[:nd] + datE = eout[:nd] + datX = np.append(datX,dataX) + datY = np.append(datY,dataF1[:nd]) + datE = np.append(datE,dataG) + res1 = dataF1[:nd] - yout[:nd] + datX = np.append(datX,dataX) + datY = np.append(datY,res1) + datE = np.append(datE,dataG) + nsp = 3 + names = 'data,fit.1,diff.1' + res_plot = [0, 1, 2] + if self._program == 'QL': + datX = np.append(datX,dataX) + datY = np.append(datY,dataF2[:nd]) + datE = np.append(datE,dataG) + res2 = dataF2[:nd] - yout[:nd] + datX = np.append(datX,dataX) + datY = np.append(datY,res2) + datE = np.append(datE,dataG) + nsp += 2 + names += ',fit.2,diff.2' + res_plot.append(4) + prob0.append(yprob[0]) + prob1.append(yprob[1]) + prob2.append(yprob[2]) + + # create result workspace + fitWS = fname+'_Workspaces' + fout = fname+'_Workspace_'+ str(m) + + CreateWorkspace(OutputWorkspace=fout, DataX=datX, DataY=datY, DataE=datE,\ + Nspec=nsp, UnitX='DeltaE', VerticalAxisUnit='Text', VerticalAxisValues=names) + + # append workspace to list of results + group += fout + ',' + + GroupWorkspaces(InputWorkspaces=group,OutputWorkspace=fitWS) + + if self._program == 'QL': + yPr0 = np.array([prob0[0]]) + yPr1 = np.array([prob1[0]]) + yPr2 = np.array([prob2[0]]) + for m in range(1,nsam): + yPr0 = np.append(yPr0,prob0[m]) + yPr1 = np.append(yPr1,prob1[m]) + yPr2 = np.append(yPr2,prob2[m]) + yProb = yPr0 + yProb = np.append(yProb,yPr1) + yProb = np.append(yProb,yPr2) + probWs = CreateWorkspace(OutputWorkspace=probWS, DataX=xProb, DataY=yProb, DataE=eProb,\ + Nspec=3, UnitX='MomentumTransfer') + outWS = C2Fw(self._samWS[:-4],fname) + if self._plot != 'None': + QuasiPlot(fname,self._plot,res_plot,self._loop) + if self._program == 'QSe': + outWS = C2Se(fname) + if self._plot != 'None': + QuasiPlot(fname,self._plot,res_plot,self._loop) + + #Add some sample logs to the output workspaces + CopyLogs(InputWorkspace=self._samWS, OutputWorkspace=outWS) + QLAddSampleLogs(outWS, self._resWS, prog, self._background, self._elastic, erange, + (nbin, nrbin), self._resnormWS, self._wfile) + CopyLogs(InputWorkspace=self._samWS, OutputWorkspace=fitWS) + QLAddSampleLogs(fitWS, self._resWS, prog, self._background, self._elastic, erange, + (nbin, nrbin), self._resnormWS, self._wfile) + + if self._save: + fit_path = os.path.join(workdir,fitWS+'.nxs') + SaveNexusProcessed(InputWorkspace=fitWS, Filename=fit_path) + out_path = os.path.join(workdir, outWS+'.nxs') # path name for nxs file + SaveNexusProcessed(InputWorkspace=outWS, Filename=out_path) + logger.information('Output fit file created : ' + fit_path) + logger.information('Output paramter file created : ' + out_path) + + self.setProperty('OutputWorkspaceFit', fitWS) + self.setProperty('OutputWorkspaceResult', outWS) + + if self._program == 'QL': + self.setProperty('OutputWorkspaceProb', probWS) + + +if is_supported_f2py_platform(): + # Register algorithm with Mantid + AlgorithmFactory.subscribe(QLRun) diff --git a/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py b/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py index 7b27a63bd91e8261f9859a5d8ef894e5f21f4dc5..e11d821084ad9cc76bfaef575fb13c721c4b49ba 100644 --- a/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py +++ b/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py @@ -1,4 +1,4 @@ -import unittest +import unittest import sys import math from testhelpers import create_algorithm, run_algorithm, can_be_instantiated, WorkspaceCreationHelper @@ -344,6 +344,48 @@ class MatrixWorkspaceTest(unittest.TestCase): ws1.setComment(comment) self.assertEquals(comment, ws1.getComment()) AnalysisDataService.remove(ws1.getName()) + + def test_setGetMonitorWS(self): + run_algorithm('CreateWorkspace', OutputWorkspace='ws1',DataX=[1.,2.,3.], DataY=[2.,3.], DataE=[2.,3.],UnitX='TOF') + run_algorithm('CreateWorkspace', OutputWorkspace='ws_mon',DataX=[1.,2.,3.], DataY=[2.,3.], DataE=[2.,3.],UnitX='TOF') + + ws1=AnalysisDataService.retrieve('ws1') + try: + monWs = ws1.getMonitorWorkspace() + GotIt = True + except RuntimeError: + GotIt = False + self.assertFalse(GotIt) + + monWs = AnalysisDataService.retrieve('ws_mon') + ws1.setMonitorWorkspace(monWs) + monWs.setTitle("My Fake Monitor workspace") + + monWs1 = ws1.getMonitorWorkspace(); + self.assertEquals(monWs.getTitle(), monWs1.getTitle()) + + ws1.clearMonitorWorkspace() + try: + monWs1 = ws1.getMonitorWorkspace() + GotIt = True + except RuntimeError: + GotIt = False + self.assertFalse(GotIt) + + # Check weak pointer issues + ws1.setMonitorWorkspace(monWs) + wms=ws1.getMonitorWorkspace() + + allFine = False + try: + ws1.setMonitorWorkspace(wms) + allFine = True + except ValueError: + pass + self.assertTrue(allFine) if __name__ == '__main__': unittest.main() + #Testing particular test from Mantid + #tester=MatrixWorkspaceTest('test_setGetMonitorWS') + #tester.test_setGetMonitorWS() diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/ApplyPaalmanPingsCorrectionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/ApplyPaalmanPingsCorrectionTest.py index 581f3c7e413c935893d8e79bcba6f42cb0f58a52..c3ebe75b86287c76ade0acba92495237588a2080 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/ApplyPaalmanPingsCorrectionTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/ApplyPaalmanPingsCorrectionTest.py @@ -49,7 +49,7 @@ class ApplyPaalmanPingsCorrectionTest(unittest.TestCase): """ DeleteWorkspace(self._sample_ws) - DeleteWorkspace(self._can_ws) + DeleteWorkspace(mtd['can_ws']) DeleteWorkspace(self._corrections_ws) diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt index 952bb617bfa603aaaedee0c7393bf6f8a7ad9af5..caeeaa2c49450ee0c7764664d0e5c337dde9e5e6 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt +++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt @@ -59,6 +59,7 @@ set ( TEST_PY_FILES MSDFitTest.py MuscatSofQWTest.py OSIRISDiffractionReductionTest.py + QLRunTest.py ResNorm2Test.py RetrieveRunInfoTest.py SANSWideAngleCorrectionTest.py diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/QLRunTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/QLRunTest.py new file mode 100644 index 0000000000000000000000000000000000000000..9a12d331d74ef063fa0ee6cedccd9c2237797f39 --- /dev/null +++ b/Framework/PythonInterface/test/python/plugins/algorithms/QLRunTest.py @@ -0,0 +1,185 @@ +import unittest +from mantid.simpleapi import * +from IndirectImport import * +from mantid.api import MatrixWorkspace, WorkspaceGroup + +if is_supported_f2py_platform(): + class QLRunTest(unittest.TestCase): + + _res_ws = None + _sample_ws = None + _num_bins = None + _num_hists = None + + def setUp(self): + self._res_ws = Load(Filename='irs26173_graphite002_res.nxs', + OutputWorkspace='__QLRunTest_Resolution') + self._sample_ws = Load(Filename='irs26176_graphite002_red.nxs', + OutputWorkspace='__QLRunTest_Sample') + self._num_bins = self._sample_ws.blocksize() + self._num_hists = self._sample_ws.getNumberHistograms() + + + def _validate_QLr_shape(self, result, probability, group): + """ + Validates that the output workspaces are of the correct type, units and shape. + + @param result Result workspace from QLRun + @param prob Probability workspace from QLRun + @param group Group workspace of fitted spectra from QLRun + """ + + # Test size/shape of result + self.assertTrue(isinstance(result, MatrixWorkspace)) + self.assertEquals(result.getNumberHistograms(), 21) + self.assertEquals(result.blocksize(), self._num_hists) + self.assertEquals(result.getAxis(0).getUnit().unitID(), 'MomentumTransfer') + + # Test size/shape of probability + self.assertTrue(isinstance(probability, MatrixWorkspace)) + self.assertEquals(probability.getNumberHistograms(), 3) + self.assertEquals(probability.blocksize(), self._num_hists) + self.assertEquals(result.getAxis(0).getUnit().unitID(), 'MomentumTransfer') + + # Test size/shape of group fitting workspaces + self.assertTrue(isinstance(group, WorkspaceGroup)) + self.assertEquals(group.getNumberOfEntries(), self._sample_ws.getNumberHistograms()) + + # Test sub workspaces + for i in range (group.getNumberOfEntries()): + sub_ws = group.getItem(i) + self.assertTrue(isinstance(sub_ws, MatrixWorkspace)) + self.assertEqual(sub_ws.getNumberHistograms(), 5) + self.assertEquals(sub_ws.getAxis(0).getUnit().unitID(), 'DeltaE') + + + def _validate_Qlr_value(self, result, probability, group): + """ + Validates that the output workspaces have expected values + with values from the last known correct version + + @param result Result workspace from QLRun + @param prob Probability workspace from QLRun + @param group Group workspace of fitted spectra from QLRun + """ + + # Test values of result + result_y = result.dataY(0) + self.assertEquals(round(result.dataY(0)[0], 5), 6.06105) + self.assertEquals(round(result.dataY(1)[0], 4), 68.5744) + self.assertEquals(round(result.dataY(2)[0], 7), 0.0589315) + self.assertEquals(round(result.dataY(3)[0], 7), 0.0812087) + + # Test values of probability + prob_y = probability.dataY(0) + self.assertEquals(round(probability.dataY(0)[0], 1), -74176.1) + self.assertEquals(round(probability.dataY(1)[0], 3), -404.884) + self.assertEquals(round(probability.dataY(2)[0], 6), -0.222565) + + # Test values of group + sub_ws = group.getItem(0) + sub_y = sub_ws.dataY(0) + self.assertEquals(round(sub_ws.dataY(0)[0], 5), 0.02540) + self.assertEquals(round(sub_ws.dataY(1)[0], 5), 0.01903) + self.assertEquals(round(sub_ws.dataY(2)[0], 5), -0.00638) + self.assertEquals(round(sub_ws.dataY(3)[0], 5), 0.01614) + self.assertEquals(round(sub_ws.dataY(4)[0], 5), -0.00926) + + + def _validate_QSe_shape(self, result, group): + """ + Validates that the output workspaces are of the correct type, units and shape. + with values from the last known correct version + + @param result Result workspace from QLRun + @param group Group workspace of fitted spectra from QLRun + """ + + # Test size/shape of result + self.assertTrue(isinstance(result, MatrixWorkspace)) + self.assertEquals(result.getNumberHistograms(), 3) + self.assertEquals(result.blocksize(), self._num_hists) + self.assertEquals(result.getAxis(0).getUnit().unitID(), 'MomentumTransfer') + + # Test size/shape of group fitting workspaces + self.assertTrue(isinstance(group, WorkspaceGroup)) + self.assertEquals(group.getNumberOfEntries(), self._sample_ws.getNumberHistograms()) + + # Test sub workspaces + for i in range (group.getNumberOfEntries()): + sub_ws = group.getItem(i) + self.assertTrue(isinstance(sub_ws, MatrixWorkspace)) + self.assertEqual(sub_ws.getNumberHistograms(), 3) + self.assertEquals(sub_ws.getAxis(0).getUnit().unitID(), 'DeltaE') + + + def _validate_QSe_value(self, result, group): + """ + Validates that the output workspaces have expected values + + @param result Result workspace from QLRun + @param prob Probability workspace from QLRun + @param group Group workspace of fitted spectra from QLRun + """ + + # Test values of result + result_y = result.dataY(0) + self.assertEquals(round(result.dataY(0)[0], 5), 81.12644) + self.assertEquals(round(result.dataY(1)[0], 7), 0.0319747) + self.assertEquals(round(result.dataY(2)[0], 5), 0.77168) + + # Test values of group + sub_ws = group.getItem(0) + sub_y = sub_ws.dataY(0) + self.assertEquals(round(sub_ws.dataY(0)[0], 5), 0.02540) + self.assertEquals(round(sub_ws.dataY(1)[0], 5), 0.01632) + self.assertEquals(round(sub_ws.dataY(2)[0], 5), -0.00908) + + + def test_QLr_Run(self): + """ + Test Lorentzian fit for QLRun + """ + fit_group, result, prob= QLRun(Program='QL', + SampleWorkspace=self._sample_ws, + ResolutionWorkspace=self._res_ws, + MinRange=-0.547607, + MaxRange=0.543216, + SampleBins=1, + ResolutionBins=1, + Elastic=False, + Background='Sloping', + FixedWidth=False, + UseResNorm=False, + WidthFile='', + Loop=True, + Save=False, + Plot='None') + self._validate_QLr_shape(result, prob, fit_group) + self._validate_Qlr_value(result, prob, fit_group) + + + def test_QSe_Run(self): + """ + Test Stretched Exponential fit for QLRun + """ + fit_group, result = QLRun(Program='QSe', + SampleWorkspace=self._sample_ws, + ResolutionWorkspace=self._res_ws, + MinRange=-0.547607, + MaxRange=0.543216, + SampleBins=1, + ResolutionBins=1, + Elastic=False, + Background='Sloping', + FixedWidth=False, + UseResNorm=False, + WidthFile='', + Loop=True, + Save=False, + Plot='None') + self._validate_QSe_shape(result, fit_group) + self._validate_QSe_value(result, fit_group) + + if __name__=="__main__": + unittest.main() diff --git a/MantidPlot/src/origin/OPJFile.cpp b/MantidPlot/src/origin/OPJFile.cpp index 7ae87dc19b077632ac9a5d6e755274fe0b9acd77..af88aa5ed1d21f9c46510c0c1126031a38fbe138 100644 --- a/MantidPlot/src/origin/OPJFile.cpp +++ b/MantidPlot/src/origin/OPJFile.cpp @@ -27,22 +27,6 @@ * * ***************************************************************************/ -// Disable various warnings as this is not our code -#if defined(__GNUC__) && !(defined(__INTEL_COMPILER)) -#define GCC_VERSION \ - (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) -#pragma GCC diagnostic ignored "-Wreorder" -#pragma GCC diagnostic ignored "-Wformat" -// This option seems to have disappeared in 4.4.4, but came back in 4.5.x?!?!? -#if GCC_VERSION < 40404 || GCC_VERSION > 40500 -#pragma GCC diagnostic ignored "-Wunused-result" -#endif -#pragma GCC diagnostic ignored "-Wunused" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wtype-limits" -#endif - #ifdef _WIN32 #pragma warning(disable : 4800) #endif @@ -101,10 +85,10 @@ int strcmp_i(const char *s1, { \ size_t retval = fread(ptr, size, nmemb, stream); \ if (static_cast<size_t>(size * nmemb) != retval) { \ - fprintf( \ - debug, \ - " WARNING : could not read %d bytes from file, read: %d bytes\n", \ - size *nmemb); \ + fprintf(debug, " WARNING : could not read %llu bytes from file, read: " \ + "%llu bytes\n", \ + static_cast<unsigned long long>(size) * nmemb, \ + static_cast<unsigned long long>(retval)); \ } \ } @@ -490,7 +474,7 @@ int OPJFile::ParseFormatOld() { stmp = char(i + 0x41); else if (i < 26 * 26) { stmp = char(0x40 + i / 26); - stmp[1] = i % 26 + 0x41; + stmp[1] = char(i % 26 + 0x41); } else { stmp = char(0x40 + i / 26 / 26); stmp[1] = char(i / 26 % 26 + 0x41); @@ -785,7 +769,7 @@ int OPJFile::ParseFormatNew() { int file_size = 0; { CHECKED_FSEEK(debug, f, 0, SEEK_END); - file_size = ftell(f); + file_size = static_cast<int>(ftell(f)); CHECKED_FSEEK(debug, f, 0, SEEK_SET); } @@ -979,7 +963,7 @@ int OPJFile::ParseFormatNew() { fprintf(debug, "%g ", MATRIX.back().data.back()); } break; - case 0x6801: // int + case 0x6801: // int if (data_type_u == 8) // unsigned for (i = 0; i < size; i++) { unsigned int value; @@ -999,7 +983,7 @@ int OPJFile::ParseFormatNew() { fprintf(debug, "%g ", MATRIX.back().data.back()); } break; - case 0x6803: // short + case 0x6803: // short if (data_type_u == 8) // unsigned for (i = 0; i < size; i++) { unsigned short value; @@ -1019,7 +1003,7 @@ int OPJFile::ParseFormatNew() { fprintf(debug, "%g ", MATRIX.back().data.back()); } break; - case 0x6821: // char + case 0x6821: // char if (data_type_u == 8) // unsigned for (i = 0; i < size; i++) { unsigned char value; @@ -3224,7 +3208,7 @@ void OPJFile::readProjectTreeFolder(FILE *f, FILE *debug, int rv = fseek(f, 0, SEEK_END); if (rv < 0) fprintf(debug, "Error: could not move to the end of the file\n"); - file_size = ftell(f); + file_size = static_cast<int>(ftell(f)); rv = fseek(f, POS, SEEK_SET); if (rv < 0) fprintf(debug, "Error: could not move to the beginning of the file\n"); diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionQtGUI.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionQtGUI.ui index 857a5a65389c1db51940fb9d800f6f6b2a84eb6e..07d608c758333a6a562903f9526e48515cfc2eba 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionQtGUI.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionQtGUI.ui @@ -108,7 +108,7 @@ <widget class="QComboBox" name="comboBox_instrument"> <item> <property name="text"> - <string>ENGIN-X</string> + <string>ENGINX</string> </property> </item> </widget> diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h index f813d2b37cb5782b6d39ef2ed7e7ac013d330350..4a0988c8d531c1b432489dbc55bd3bce2f62b51e 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h @@ -198,6 +198,12 @@ private: /// instrument selected (ENGIN-X, etc.) std::string m_currentInst; + /// User select instrument + void userSelectInstrument(const QString &prefix); + + /// setting the instrument prefix ahead of the run number + void setPrefix(std::string prefix); + // plot data representation type selected int static m_currentType; diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/ContainerSubtraction.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/ContainerSubtraction.ui index f940b14a98cff79d60eb0e22dce578a06f62279c..c98b9805596623d125ca25a3e9e1ea2108344948 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/ContainerSubtraction.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/ContainerSubtraction.ui @@ -114,7 +114,7 @@ <item row="0" column="0"> <widget class="QCheckBox" name="ckScaleCan"> <property name="text"> - <string>Scale Can by factor:</string> + <string>Scale Container by factor:</string> </property> <property name="checked"> <bool>false</bool> @@ -134,6 +134,9 @@ <property name="maximum"> <double>999.990000000000009</double> </property> + <property name="minimum"> + <double>-0.000000000000000</double> + </property> <property name="singleStep"> <double>0.100000000000000</double> </property> @@ -142,18 +145,41 @@ </property> </widget> </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="ckShiftCan"> + <property name="text"> + <string>Shift x-values of container by adding:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="loShift"> <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <widget class="QDoubleSpinBox" name="spShift"> + <property name="enabled"> + <bool>false</bool> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <property name="decimals"> + <number>5</number> </property> - </spacer> + <property name="maximum"> + <double>999.990000000000009</double> + </property> + <property name="minimum"> + <double>-999.990000000000009</double> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> + </property> + <property name="value"> + <double>0.000000000000000</double> + </property> + </widget> </item> </layout> </item> @@ -342,9 +368,9 @@ </hints> </connection> <connection> - <sender>ckUseCorrections</sender> + <sender>ckShiftCan</sender> <signal>toggled(bool)</signal> - <receiver>dsCorrections</receiver> + <receiver>spShift</receiver> <slot>setEnabled(bool)</slot> <hints> <hint type="sourcelabel"> diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/Quasi.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/Quasi.h index 08ae4d6a6445c267a962a0dc04e0d3feb873dce9..5aed373ac62fda461893c76adad24146c0678794 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/Quasi.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Indirect/Quasi.h @@ -4,50 +4,50 @@ #include "ui_Quasi.h" #include "IndirectBayesTab.h" -namespace MantidQt -{ - namespace CustomInterfaces - { - class DLLExport Quasi : public IndirectBayesTab - { - Q_OBJECT - - public: - Quasi(QWidget * parent = 0); - - // Inherited methods from IndirectBayesTab - void setup(); - bool validate(); - void run(); - /// Load default settings into the interface - void loadSettings(const QSettings& settings); - - private slots: - /// Slot for when the min range on the range selector changes - void minValueChanged(double min); - /// Slot for when the min range on the range selector changes - void maxValueChanged(double max); - /// Slot to update the guides when the range properties change - void updateProperties(QtProperty* prop, double val); - /// Slot to handle when a new sample file is available - void handleSampleInputReady(const QString& filename); - /// Slot to handle when a new resolution file is available - void handleResolutionInputReady(const QString& wsName); - /// slot to handle when the user changes the program to be used - void handleProgramChange(int index); - /// Slot to handle setting a new preview spectrum - void previewSpecChanged(int value); - /// Handles updating spectra in mini plot - void updateMiniPlot(); - - private: - /// Current preview spectrum - int m_previewSpec; - /// The ui form - Ui::Quasi m_uiForm; - - }; - } // namespace CustomInterfaces +namespace MantidQt { +namespace CustomInterfaces { +class DLLExport Quasi : public IndirectBayesTab { + Q_OBJECT + +public: + Quasi(QWidget *parent = 0); + + // Inherited methods from IndirectBayesTab + void setup(); + bool validate(); + void run(); + /// Load default settings into the interface + void loadSettings(const QSettings &settings); + +private slots: + /// Slot for when the min range on the range selector changes + void minValueChanged(double min); + /// Slot for when the min range on the range selector changes + void maxValueChanged(double max); + /// Slot to update the guides when the range properties change + void updateProperties(QtProperty *prop, double val); + /// Slot to handle when a new sample file is available + void handleSampleInputReady(const QString &filename); + /// Slot to handle when a new resolution file is available + void handleResolutionInputReady(const QString &wsName); + /// slot to handle when the user changes the program to be used + void handleProgramChange(int index); + /// Slot to handle setting a new preview spectrum + void previewSpecChanged(int value); + /// Handles updating spectra in mini plot + void updateMiniPlot(); + /// Handles what happen after the algorithm is run + void algorithmComplete(bool error); + +private: + /// Current preview spectrum + int m_previewSpec; + /// The ui form + Ui::Quasi m_uiForm; + /// alg + Mantid::API::IAlgorithm_sptr m_QuasiAlg; +}; +} // namespace CustomInterfaces } // namespace MantidQt #endif diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h index 75f62457f88b502ca93acc4b23eb366f71232107..91245c83f0e6b1b595c428da017db8928915d5f2 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -309,6 +309,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(); /// Handle a change of the aperture geometry for QResolution void handleQResolutionApertureChange(int aperture); @@ -454,8 +458,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(); /// Gets the QResolution settings and shows them in the GUI void retrieveQResolutionSettings(); /// Gets the QResolution settings for the aperture, decides for the the diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui index 608e11111925a1d2f5371e843055e8982a119633..f257113b7f3f4e3be75364623c5321259958a719 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui @@ -3408,9 +3408,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> @@ -3478,6 +3478,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> @@ -3485,6 +3516,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/EnggDiffraction/EnggDiffractionViewQtGUI.cpp b/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionViewQtGUI.cpp index ccd98974eb014d04e86557d598cd852ebdcbe4a4..99861916153ff2427eb637722c13e352c710f3a1 100644 --- a/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionViewQtGUI.cpp +++ b/MantidQt/CustomInterfaces/src/EnggDiffraction/EnggDiffractionViewQtGUI.cpp @@ -55,7 +55,7 @@ const std::string EnggDiffractionViewQtGUI::m_settingsGroup = * @param parent Parent window (most likely the Mantid main app window). */ EnggDiffractionViewQtGUI::EnggDiffractionViewQtGUI(QWidget *parent) - : UserSubWindow(parent), IEnggDiffractionView(), m_currentInst("ENGIN-X"), + : UserSubWindow(parent), IEnggDiffractionView(), m_currentInst("ENGINX"), m_currentCalibFilename(""), m_presenter(NULL) {} EnggDiffractionViewQtGUI::~EnggDiffractionViewQtGUI() {} @@ -76,6 +76,10 @@ void EnggDiffractionViewQtGUI::initLayout() { m_uiTabSettings.setupUi(wSettings); m_ui.tabMain->addTab(wSettings, QString("Settings")); + QComboBox *inst = m_ui.comboBox_instrument; + m_currentInst = inst->currentText().toStdString(); + + setPrefix(m_currentInst); readSettings(); // basic UI setup, connect signals, etc. @@ -100,9 +104,9 @@ void EnggDiffractionViewQtGUI::doSetupTabCalib() { // CalibrationParameters or similar class/structure const std::string vanadiumRun = "236516"; const std::string ceriaRun = "241391"; - m_uiTabCalib.lineEdit_new_vanadium_num->setText( + m_uiTabCalib.lineEdit_new_vanadium_num->setUserInput( QString::fromStdString(vanadiumRun)); - m_uiTabCalib.lineEdit_new_ceria_num->setText( + m_uiTabCalib.lineEdit_new_ceria_num->setUserInput( QString::fromStdString(ceriaRun)); // push button signals/slots @@ -177,7 +181,6 @@ void EnggDiffractionViewQtGUI::doSetupGeneralWidgets() { // change instrument connect(m_ui.comboBox_instrument, SIGNAL(currentIndexChanged(int)), this, SLOT(instrumentChanged(int))); - connect(m_ui.pushButton_help, SIGNAL(released()), this, SLOT(openHelpWin())); // note connection to the parent window, otherwise an empty frame window // may remain open and visible after this close @@ -212,7 +215,7 @@ void EnggDiffractionViewQtGUI::readSettings() { qs.value("user-params-new-ceria-num", "").toString()); // user params - focusing - m_uiTabFocus.lineEdit_run_num->setText( + m_uiTabFocus.lineEdit_run_num->setUserInput( qs.value("user-params-focus-runno", "").toString()); qs.beginReadArray("user-params-focus-bank_i"); @@ -224,13 +227,13 @@ void EnggDiffractionViewQtGUI::readSettings() { qs.value("value", true).toBool()); qs.endArray(); - m_uiTabFocus.lineEdit_cropped_run_num->setText( + m_uiTabFocus.lineEdit_cropped_run_num->setUserInput( qs.value("user-params-focus-cropped-runno", "").toString()); m_uiTabFocus.lineEdit_cropped_spec_ids->setText( qs.value("user-params-focus-cropped-spectrum-nos", "").toString()); - m_uiTabFocus.lineEdit_texture_run_num->setText( + m_uiTabFocus.lineEdit_texture_run_num->setUserInput( qs.value("user-params-focus-texture-runno", "").toString()); m_uiTabFocus.lineEdit_texture_grouping_file->setText( @@ -735,11 +738,27 @@ void EnggDiffractionViewQtGUI::instrumentChanged(int /*idx*/) { QComboBox *inst = m_ui.comboBox_instrument; if (!inst) return; - m_currentInst = inst->currentText().toStdString(); m_presenter->notify(IEnggDiffractionPresenter::InstrumentChange); } +void EnggDiffractionViewQtGUI::userSelectInstrument(const QString &prefix) { + // Set file browsing to current instrument + setPrefix(prefix.toStdString()); +} + +void EnggDiffractionViewQtGUI::setPrefix(std::string prefix) { + QString prefixInput = QString::fromStdString(prefix); + // focus tab + m_uiTabFocus.lineEdit_run_num->setInstrumentOverride(prefixInput); + m_uiTabFocus.lineEdit_texture_run_num->setInstrumentOverride(prefixInput); + m_uiTabFocus.lineEdit_cropped_run_num->setInstrumentOverride(prefixInput); + + // calibration tab + m_uiTabCalib.lineEdit_new_ceria_num->setInstrumentOverride(prefixInput); + m_uiTabCalib.lineEdit_new_vanadium_num->setInstrumentOverride(prefixInput); +} + void EnggDiffractionViewQtGUI::closeEvent(QCloseEvent *event) { int answer = QMessageBox::AcceptRole; diff --git a/MantidQt/CustomInterfaces/src/Indirect/ContainerSubtraction.cpp b/MantidQt/CustomInterfaces/src/Indirect/ContainerSubtraction.cpp index 0992f1d648ee3dc5ce1a59abc80e75be61dd3ea9..c008d22bafa302038efdbe92ce5fd55943da9ba6 100644 --- a/MantidQt/CustomInterfaces/src/Indirect/ContainerSubtraction.cpp +++ b/MantidQt/CustomInterfaces/src/Indirect/ContainerSubtraction.cpp @@ -50,15 +50,41 @@ void ContainerSubtraction::run() { MatrixWorkspace_sptr canWs = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( canWsName.toStdString()); + QString canCloneName = canWsName + "_Shifted"; + IAlgorithm_sptr clone = AlgorithmManager::Instance().create("CloneWorkspace"); + clone->initialize(); + clone->setProperty("InputWorkspace", canWs); + clone->setProperty("Outputworkspace", canCloneName.toStdString()); + clone->execute(); + MatrixWorkspace_sptr canCloneWs = + AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( + canCloneName.toStdString()); + + if (m_uiForm.ckShiftCan->isChecked()) { + IAlgorithm_sptr scaleX = AlgorithmManager::Instance().create("ScaleX"); + scaleX->initialize(); + scaleX->setProperty("InputWorkspace", canCloneWs); + scaleX->setProperty("OutputWorkspace", canCloneName.toStdString()); + scaleX->setProperty("Factor", m_uiForm.spShift->value()); + scaleX->setProperty("Operation", "Add"); + scaleX->execute(); + IAlgorithm_sptr rebin = + AlgorithmManager::Instance().create("RebinToWorkspace"); + rebin->initialize(); + rebin->setProperty("WorkspaceToRebin", canCloneWs); + rebin->setProperty("WorkspaceToMatch", sampleWs); + rebin->setProperty("OutputWorkspace", canCloneName.toStdString()); + rebin->execute(); + } // If not in wavelength then do conversion - std::string originalCanUnits = canWs->getAxis(0)->unit()->unitID(); + std::string originalCanUnits = canCloneWs->getAxis(0)->unit()->unitID(); if (originalCanUnits != "Wavelength") { g_log.information("Container workspace not in wavelength, need to " "convert to continue."); - absCorProps["CanWorkspace"] = addConvertUnitsStep(canWs, "Wavelength"); + absCorProps["CanWorkspace"] = addConvertUnitsStep(canCloneWs, "Wavelength"); } else { - absCorProps["CanWorkspace"] = canWsName.toStdString(); + absCorProps["CanWorkspace"] = canCloneName.toStdString(); } bool useCanScale = m_uiForm.ckScaleCan->isChecked(); @@ -68,7 +94,7 @@ void ContainerSubtraction::run() { } // Check for same binning across sample and container - if (!checkWorkspaceBinningMatches(sampleWs, canWs)) { + if (!checkWorkspaceBinningMatches(sampleWs, canCloneWs)) { QString text = "Binning on sample and container does not match." "Would you like to rebin the sample to match the container?"; @@ -77,7 +103,7 @@ void ContainerSubtraction::run() { QMessageBox::NoButton); if (result == QMessageBox::Yes) { - addRebinStep(sampleWsName, canWsName); + addRebinStep(canCloneName, sampleWsName); } else { m_batchAlgoRunner->clearQueue(); g_log.error("Cannot apply absorption corrections using a sample and " @@ -205,9 +231,15 @@ void ContainerSubtraction::plotPreview(int specIndex) { Qt::green); // Plot container - m_uiForm.ppPreview->addSpectrum("Container", - m_uiForm.dsContainer->getCurrentDataName(), - specIndex, Qt::red); + if (m_uiForm.ckShiftCan->isChecked()) { + m_uiForm.ppPreview->addSpectrum( + "Container", (m_uiForm.dsContainer->getCurrentDataName() + "_Shifted"), + specIndex, Qt::red); + } else { + m_uiForm.ppPreview->addSpectrum("Container", + m_uiForm.dsContainer->getCurrentDataName(), + specIndex, Qt::red); + } } void ContainerSubtraction::postProcessComplete(bool error) { diff --git a/MantidQt/CustomInterfaces/src/Indirect/Quasi.cpp b/MantidQt/CustomInterfaces/src/Indirect/Quasi.cpp index 5ecec24dba7de73b3a824424b43e05029e2e6e88..ca37a7164ae6acbcd7cc1cbc016eb6a0af4576dc 100644 --- a/MantidQt/CustomInterfaces/src/Indirect/Quasi.cpp +++ b/MantidQt/CustomInterfaces/src/Indirect/Quasi.cpp @@ -124,22 +124,22 @@ void Quasi::run() { // Using 1/0 instead of True/False for compatibility with underlying Fortran // code // in some places - QString save("False"); - QString elasticPeak("False"); - QString sequence("False"); + bool save = false; + bool elasticPeak = false; + bool sequence = false; - QString fixedWidth("False"); - QString fixedWidthFile(""); + bool fixedWidth = false; + std::string fixedWidthFile(""); - QString useResNorm("False"); - QString resNormFile(""); + bool useResNorm = false; + std::string resNormFile(""); - QString pyInput = "from IndirectBayes import QLRun\n"; + std::string sampleName = + m_uiForm.dsSample->getCurrentDataName().toStdString(); + std::string resName = + m_uiForm.dsResolution->getCurrentDataName().toStdString(); - QString sampleName = m_uiForm.dsSample->getCurrentDataName(); - QString resName = m_uiForm.dsResolution->getCurrentDataName(); - - QString program = m_uiForm.cbProgram->currentText(); + std::string program = m_uiForm.cbProgram->currentText().toStdString(); if (program == "Lorentzians") { program = "QL"; @@ -148,59 +148,78 @@ void Quasi::run() { } // Collect input from fit options section - QString background = m_uiForm.cbBackground->currentText(); + std::string background = m_uiForm.cbBackground->currentText().toStdString(); if (m_uiForm.chkElasticPeak->isChecked()) { - elasticPeak = "True"; + elasticPeak = true; } if (m_uiForm.chkSequentialFit->isChecked()) { - sequence = "True"; + sequence = true; } if (m_uiForm.chkFixWidth->isChecked()) { - fixedWidth = "True"; - fixedWidthFile = m_uiForm.mwFixWidthDat->getFirstFilename(); + fixedWidth = true; + fixedWidthFile = m_uiForm.mwFixWidthDat->getFirstFilename().toStdString(); } if (m_uiForm.chkUseResNorm->isChecked()) { - useResNorm = "True"; - resNormFile = m_uiForm.dsResNorm->getCurrentDataName(); + useResNorm = true; + resNormFile = m_uiForm.dsResNorm->getCurrentDataName().toStdString(); } - QString fitOps = "[" + elasticPeak + ", '" + background + "', " + fixedWidth + - ", " + useResNorm + "]"; - // Collect input from the properties browser - QString eMin = m_properties["EMin"]->valueText(); - QString eMax = m_properties["EMax"]->valueText(); - QString eRange = "[" + eMin + "," + eMax + "]"; + double eMin = m_properties["EMin"]->valueText().toDouble(); + double eMax = m_properties["EMax"]->valueText().toDouble(); - QString sampleBins = m_properties["SampleBinning"]->valueText(); - QString resBins = m_properties["ResBinning"]->valueText(); - QString nBins = "[" + sampleBins + "," + resBins + "]"; + long sampleBins = m_properties["SampleBinning"]->valueText().toLong(); + long resBins = m_properties["ResBinning"]->valueText().toLong(); // Output options if (m_uiForm.chkSave->isChecked()) { - save = "True"; + save = true; } - QString plot = m_uiForm.cbPlot->currentText(); - - pyInput += "QLRun('" + program + "','" + sampleName + "','" + resName + - "','" + resNormFile + "'," + eRange + "," - " " + - nBins + "," + fitOps + ",'" + fixedWidthFile + "'," + sequence + - ", " - " Save=" + - save + ", Plot='" + plot + "')\n"; - - runPythonScript(pyInput); - - updateMiniPlot(); + std::string plot = m_uiForm.cbPlot->currentText().toStdString(); + + IAlgorithm_sptr runAlg = AlgorithmManager::Instance().create("QLRun"); + runAlg->initialize(); + runAlg->setProperty("Program", program); + runAlg->setProperty("SampleWorkspace", sampleName); + runAlg->setProperty("ResolutionWorkspace", resName); + runAlg->setProperty("ResNormWorkspace", resNormFile); + runAlg->setProperty("OutputWorkspaceFit", "fit"); + runAlg->setProperty("OutputWorkspaceProb", "prob"); + runAlg->setProperty("OutputWorkspaceResult", "result"); + runAlg->setProperty("MinRange", eMin); + runAlg->setProperty("MaxRange", eMax); + runAlg->setProperty("SampleBins", sampleBins); + runAlg->setProperty("ResolutionBins", resBins); + runAlg->setProperty("Elastic", elasticPeak); + runAlg->setProperty("Background", background); + runAlg->setProperty("FixedWidth", fixedWidth); + runAlg->setProperty("UseResNorm", useResNorm); + runAlg->setProperty("WidthFile", fixedWidthFile); + runAlg->setProperty("Loop", sequence); + runAlg->setProperty("Save", save); + runAlg->setProperty("Plot", plot); + + m_QuasiAlg = runAlg; + m_batchAlgoRunner->addAlgorithm(runAlg); + connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, + SLOT(algorithmComplete(bool))); + m_batchAlgoRunner->executeBatchAsync(); } /** * Updates the data and fit curves on the mini plot. */ +void Quasi::algorithmComplete(bool error) { + + if (error) + return; + else + updateMiniPlot(); +} + void Quasi::updateMiniPlot() { // Update sample plot if (!m_uiForm.dsSample->isValid()) diff --git a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp index c24b322e20f037c5657f4eb58b50b85a68c50597..076ad509c1bc7d18e411ed70d8334c7dff98429b 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(); @@ -425,7 +427,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() { @@ -972,10 +974,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( @@ -1000,7 +1002,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()") @@ -2102,8 +2103,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 @@ -2736,6 +2743,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=" + @@ -2762,9 +2771,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 @@ -4011,6 +4032,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 @@ -4420,6 +4464,64 @@ 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); +} + /** * Retrieves the Q resolution settings and apply them to the GUI diff --git a/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/CatalogSearch.h b/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/CatalogSearch.h index f1fa2041c59c8210ab6a7d18b43de83566119177..e1cc2314bb847e29b2f61d1c52a29ea6b58652f7 100644 --- a/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/CatalogSearch.h +++ b/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/CatalogSearch.h @@ -94,7 +94,7 @@ namespace MantidQt /////////////////////////////////////////////////////////////////////////////// /// Outputs the results of the query into a table. - void populateResultTable(); + void populateResultTable(int sort_section, Qt::SortOrder sort_order); /// Obtain the sessionID for the selected investigation. std::string selectedInvestigationSession(); diff --git a/MantidQt/MantidWidgets/src/CatalogSearch.cpp b/MantidQt/MantidWidgets/src/CatalogSearch.cpp index ebdf521ca508692877b3480946bf484bbd531bb2..828f8481bccb8de01dbaa5f4f87391a6a6791de5 100644 --- a/MantidQt/MantidWidgets/src/CatalogSearch.cpp +++ b/MantidQt/MantidWidgets/src/CatalogSearch.cpp @@ -610,9 +610,15 @@ namespace MantidQt // Update the label to inform the user that searching is in progress. m_icatUiForm.searchResultsLbl->setText("searching investigations..."); + // Get previous sorting parameters + QTableWidget* resultsTable = m_icatUiForm.searchResultsTbl; + int sort_section = resultsTable->horizontalHeader()->sortIndicatorSection(); + Qt::SortOrder sort_order = resultsTable->horizontalHeader()->sortIndicatorOrder(); + // Remove previous search results. std::string searchResults = "searchResults"; - clearSearch(m_icatUiForm.searchResultsTbl, searchResults); + + clearSearch(resultsTable, searchResults); auto sessionIDs = m_catalogSelector->getSelectedCatalogSessions(); // Obtain the number of results for paging. @@ -635,7 +641,7 @@ namespace MantidQt m_icatUiForm.searchResultsLbl->setText(QString::number(numrows) + " investigations found."); // Populate the result table from the searchResult workspace. - populateResultTable(); + populateResultTable(sort_section, sort_order); } /** @@ -710,8 +716,10 @@ namespace MantidQt /** * Outputs the results of the search into the "Search results" table. + * @param sort_section :: An int giving the column number by which to sort the data (0 will sort by StartDate) + * @param sort_order :: A Qt::SortOrder giving the order of sorting */ - void CatalogSearch::populateResultTable() + void CatalogSearch::populateResultTable(int sort_section, Qt::SortOrder sort_order) { // Obtain a pointer to the "searchResults" workspace where the search results are saved if it exists. Mantid::API::ITableWorkspace_sptr workspace; @@ -759,9 +767,14 @@ namespace MantidQt //Resize InvestigationID column to fit contents resultsTable->resizeColumnToContents(headerIndexByName(resultsTable, "InvestigationID")); - // Sort by endDate with the most recent being first. + // Sort by specified column or by descending StartDate if none specified resultsTable->setSortingEnabled(true); - resultsTable->sortByColumn(headerIndexByName(resultsTable, "Start date"),Qt::DescendingOrder); + if (sort_section == 0) { + resultsTable->sortByColumn(headerIndexByName(resultsTable, "Start date"),Qt::DescendingOrder); + } else { + resultsTable->sortByColumn(sort_section, sort_order); + } + } /** diff --git a/docs/source/algorithms/QLRun-v1.rst b/docs/source/algorithms/QLRun-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..fc890f0c469497c854ab4ba76b52ee0468f7649e --- /dev/null +++ b/docs/source/algorithms/QLRun-v1.rst @@ -0,0 +1,45 @@ + +.. algorithm:: + +.. summary:: + +.. alias:: + +.. properties:: + +Description +----------- + +**This algorith can only be run on windows due to f2py support and the underlying fortran code** + +The model that is being fitted is that of a \delta-function (elastic component) of amplitude A(0) and Lorentzians of amplitude A(j) and HWHM W(j) where j=1,2,3. The whole function is then convolved with the resolution function. The -function and Lorentzians are intrinsically normalised to unity so that the amplitudes represent their integrated areas. + +For a Lorentzian, the Fourier transform does the conversion: 1/(x^{2}+\delta^{2}) \Leftrightarrow exp[-2\pi(\delta k)]. If x is identified with energy E and 2\pi k with t/\hbar where t is time then: 1/[E^{2}+(\hbar / \tau)^{2}] \Leftrightarrow exp[-t +/\tau] and \sigma is identified with \hbar / \tau. The program estimates the quasielastic components of each of the groups of spectra and requires the resolution file and optionally the normalisation file created by ResNorm. + +For a Stretched Exponential, the choice of several Lorentzians is replaced with a single function with the shape : \psi\beta(x) \Leftrightarrow +exp[-2\pi(\sigma k)\beta]. This, in the energy to time FT transformation, is \psi\beta(E) \Leftrightarrow exp[-(t/\tau)\beta]. So \sigma is identified with (2\pi)\beta\hbar/\tau . The model that is fitted is that of an elastic component and the stretched exponential and the program gives the best estimate for the \beta parameter and the width for each group of spectra. + +Usage +----- + +**Example - QLRun** + +.. testcode:: QLRunExample + + # Check OS support for F2Py + from IndirectImport import is_supported_f2py_platform + if is_supported_f2py_platform(): + # Load in test data + sampleWs = Load('irs26176_graphite002_red.nxs') + resWs = Load('irs26173_graphite002_red.nxs') + + # Run QLRun algorithm + fit_ws, result_ws, prob_ws = QLRun(Program='QL', SampleWorkspace=sampleWs, ResolutionWorkspace=resWs, + MinRange=-0.547607, MaxRange=0.543216, SampleBins=1, ResolutionBins=1, + Elastic=False, Background='Sloping', FixedWidth=False, UseResNorm=False, + WidthFile='', Loop=True, Save=False, Plot='None') + +.. categories:: + +.. sourcelink:: \ No newline at end of file diff --git a/docs/source/algorithms/SplineSmoothing-v1.rst b/docs/source/algorithms/SplineSmoothing-v1.rst index ab55e85c12b1abac93185d55cbb269c98fca4a7f..491197b2215e547710492afa6470114ade97af51 100644 --- a/docs/source/algorithms/SplineSmoothing-v1.rst +++ b/docs/source/algorithms/SplineSmoothing-v1.rst @@ -18,16 +18,21 @@ derivatives of each of the interpolated points as a side product. Setting the DerivOrder property to zero will force the algorithm to calculate no derivatives. -The algorithm allows user to include number of breaking points which -will enable the algorithm to execute functions with large number of -spikes or noise. With the control of number of breaking points, user -will be able to execute :ref:`SplineSmoothing <algm-SplineSmoothing-v1>` -algorithm in small period of time. The lower breaking points number is -set the faster algorithm will execute the function but it will not -produce high quality smoothing function. By inserting high number of -breaking points, a detailed smoothing function can also be produced. - -Users are also able to successfully apply the algorithm to a workspace +The algorithm provides user with an option of including number of +breaking points which will enable the algorithm to execute functions +with large number of spikes or noise. With the control of number of +breaking points, user will be able to execute +:ref:`SplineSmoothing <algm-SplineSmoothing-v1>` algorithm in small +period of time. The lower breaking points number is set the faster +algorithm will execute the function but it will not produce high +quality smoothing function. By inserting high number of breaking points, +a detailed smoothing function can also be produced. By not providing +MaxNumberOfBreaks, the alogirthm will assume that user would like to +execute the maximum number of breaks possible. This comes with a risk +of alogirthm falling in to a very long loop. + +By providing MaxNumberOfBreaks as a parameter, the users are also will +be able to successfully and efficiently apply the algorithm to a workspace with multiple spectrums, which would generate an output of workspace with multiple spectrums of :ref:`SplineSmoothing <algm-SplineSmoothing-v1>` algorithm applied to it. `BSpline <http://www.mantidproject.org/BSpline>`_ 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/Interface/ui/reflectometer/refl_gui.py b/scripts/Interface/ui/reflectometer/refl_gui.py index 8f1a384733c2e605fd61b22f192b30ccc26b3b90..1a093847e184234563f1876c1d1a32acb7ebd311 100644 --- a/scripts/Interface/ui/reflectometer/refl_gui.py +++ b/scripts/Interface/ui/reflectometer/refl_gui.py @@ -5,7 +5,6 @@ import refl_save import refl_choose_col import refl_options import csv -import string import os import re from operator import itemgetter @@ -672,6 +671,8 @@ class ReflGui(QtGui.QMainWindow, ui_refl_window.Ui_windowRefl): Process has been pressed, check what has been selected then pass the selection (or whole table) to quick """ #--------- If "Process" button pressed, convert raw files to IvsLam and IvsQ and combine if checkbox ticked ------------- + _overallQMin = float("inf") + _overallQMax = float("-inf") try: willProcess = True rows = self.tableMain.selectionModel().selectedRows() @@ -760,7 +761,7 @@ class ReflGui(QtGui.QMainWindow, ui_refl_window.Ui_windowRefl): # Populate runlist first_wq = None - for i in range(len(runno)): + for i in range(0,len(runno)): theta, qmin, qmax, wlam, wq = self._do_run(runno[i], row, i) if not first_wq: first_wq = wq # Cache the first Q workspace @@ -791,32 +792,33 @@ class ReflGui(QtGui.QMainWindow, ui_refl_window.Ui_windowRefl): #Scale each run if self.tableMain.item(row, self.scale_col).text(): Scale(InputWorkspace=wksp[i], OutputWorkspace=wksp[i], Factor=1 / float(self.tableMain.item(row, self.scale_col).text())) - - if self.__checked_row_stiched(row): - if len(runno) == 1: - logger.notice("Nothing to combine for processing row : " + str(row)) - else: - w1 = getWorkspace(wksp[0]) - w2 = getWorkspace(wksp[-1]) - if len(runno) == 2: - outputwksp = runno[0] + '_' + runno[1][3:5] + if self.__checked_row_stiched(row): + if len(runno) == 1: + logger.notice("Nothing to combine for processing row : " + str(row)) else: - outputwksp = runno[0] + '_' + runno[-1][3:5] - begoverlap = w2.readX(0)[0] - # get Qmax - if self.tableMain.item(row, i * 5 + 4).text() == '': - overlapHigh = 0.3 * max(w1.readX(0)) - - Qmin = min(w1.readX(0)) - Qmax = max(w2.readX(0)) - - if len(self.tableMain.item(row, i * 5 + 3).text()) > 0: - Qmin = float(self.tableMain.item(row, i * 5 + 3).text()) - - if len(self.tableMain.item(row, i * 5 + 4).text()) > 0: - Qmax = float(self.tableMain.item(row, i * 5 + 4).text()) - - wcomb = combineDataMulti(wksp, outputwksp, overlapLow, overlapHigh, Qmin, Qmax, -dqq, 1, keep=True) + w1 = getWorkspace(wksp[0]) + w2 = getWorkspace(wksp[-1]) + if len(runno) == 2: + outputwksp = runno[0] + '_' + runno[1][3:] + else: + outputwksp = runno[0] + '_' + runno[-1][3:] + begoverlap = w2.readX(0)[0] + # get Qmax + if self.tableMain.item(row, i * 5 + 4).text() == '': + overlapHigh = 0.3 * max(w1.readX(0)) + + Qmin = min(w1.readX(0)) + Qmax = max(w2.readX(0)) + if len(self.tableMain.item(row, i * 5 + 3).text()) > 0: + Qmin = float(self.tableMain.item(row, i * 5 + 3).text()) + if len(self.tableMain.item(row, i * 5 + 4).text()) > 0: + Qmax = float(self.tableMain.item(row, i * 5 + 4).text()) + if Qmax > _overallQMax: + _overallQMax = Qmax + if Qmin < _overallQMin : + _overallQMin = Qmin + + _wcomb = combineDataMulti(wksp, outputwksp, overlapLow, overlapHigh, _overallQMin, _overallQMax, -dqq, 1, keep=True) # Enable the plot button @@ -904,9 +906,9 @@ class ReflGui(QtGui.QMainWindow, ui_refl_window.Ui_windowRefl): # Create and plot stitched outputs if self.__checked_row_stiched(row): if len(runno) == 2: - outputwksp = runno[0] + '_' + runno[1][3:5] + outputwksp = runno[0] + '_' + runno[1][3:] else: - outputwksp = runno[0] + '_' + runno[2][3:5] + outputwksp = runno[0] + '_' + runno[2][3:] if not getWorkspace(outputwksp, report_error=False): # Stitching has not been done as part of processing, so we need to do it here. wcomb = combineDataMulti(wkspBinned, outputwksp, overlapLow, overlapHigh, Qmin, Qmax, -dqq, 1, keep=True) diff --git a/scripts/SANS/ISISCommandInterface.py b/scripts/SANS/ISISCommandInterface.py index 581120ba42e4afa8d8f8fbe3bc89ee1da856a619..b732d178317507527347f99e1df265bb9037615f 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 @@ -341,7 +341,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) @@ -351,7 +352,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)+')') @@ -693,7 +695,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) @@ -897,9 +900,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) @@ -924,8 +929,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: @@ -1080,7 +1088,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 @@ -1088,20 +1096,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: @@ -1115,43 +1130,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 : @@ -1159,37 +1192,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 #################################################### @@ -1676,6 +1710,19 @@ def are_settings_consistent(): sanslog.error("There was an inconsistency issue with your settings. See details: %s", str(details)) raise RuntimeError("Please fix the following inconsistencies: %s" % str(details)) +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 5440778717c3ee27e9c8f999e3c14ee714b2f1e6..c3f6a6862cdd81fed75c58f6cbec11aed52a10ec 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): @@ -742,7 +772,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): """ @@ -761,6 +792,12 @@ 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 + def _add_parmeters_absent_in_calibration(self, ws_name, calib_name): ''' We load the instrument specific Instrument Parameter File (IPF) and check if @@ -853,7 +890,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 @@ -863,8 +902,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) @@ -1002,44 +1064,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) @@ -1053,9 +1130,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 @@ -1083,8 +1158,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 @@ -1450,28 +1548,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) @@ -1519,11 +1664,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) @@ -1542,5 +1684,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 67284dad3ac915dfdd9839bc5cb39da380278996..ea848550a1331b41d2723989448780a9df7ca012 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 @@ -695,3 +694,14 @@ class ISISReducer(Reducer): self._reduction_steps = None raise RuntimeError(str(details)) + 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 8c20bd16173b00dc620975dc8567c3bc0cf87c78..a02f824ed8867822984f015379f9db12203e8415 100644 --- a/scripts/SANS/isis_reduction_steps.py +++ b/scripts/SANS/isis_reduction_steps.py @@ -1169,6 +1169,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 @@ -2388,6 +2389,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()