diff --git a/Code/Mantid/Framework/MDAlgorithms/CMakeLists.txt b/Code/Mantid/Framework/MDAlgorithms/CMakeLists.txt index b64ab8c3316d23e7cfa74ce1d5cf4b99708c84c9..12b1561a2dda2012e92d813f457e3f6b0520eb21 100644 --- a/Code/Mantid/Framework/MDAlgorithms/CMakeLists.txt +++ b/Code/Mantid/Framework/MDAlgorithms/CMakeLists.txt @@ -109,6 +109,7 @@ set ( SRC_FILES src/SmoothMD.cpp src/ThresholdMD.cpp src/TransformMD.cpp + src/TransposeMD.cpp src/UnaryOperationMD.cpp src/UnitsConversionHelper.cpp src/UserFunctionMD.cpp @@ -227,6 +228,7 @@ set ( INC_FILES inc/MantidMDAlgorithms/SmoothMD.h inc/MantidMDAlgorithms/ThresholdMD.h inc/MantidMDAlgorithms/TransformMD.h + inc/MantidMDAlgorithms/TransposeMD.h inc/MantidMDAlgorithms/UnaryOperationMD.h inc/MantidMDAlgorithms/UnitsConversionHelper.h inc/MantidMDAlgorithms/Vector3DParameter.h @@ -334,6 +336,7 @@ set ( TEST_FILES TobyFitResolutionModelTest.h TobyFitYVectorTest.h TransformMDTest.h + TransposeMDTest.h UnaryOperationMDTest.h UnitsConversionHelperTest.h WeightedMeanMDTest.h diff --git a/Code/Mantid/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h b/Code/Mantid/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h new file mode 100644 index 0000000000000000000000000000000000000000..a218102a80ebe1bf030fb1eed9e769258355213a --- /dev/null +++ b/Code/Mantid/Framework/MDAlgorithms/inc/MantidMDAlgorithms/TransposeMD.h @@ -0,0 +1,50 @@ +#ifndef MANTID_MDALGORITHMS_TRANSPOSEMD_H_ +#define MANTID_MDALGORITHMS_TRANSPOSEMD_H_ + +#include "MantidMDAlgorithms/DllConfig.h" +#include "MantidAPI/Algorithm.h" +namespace Mantid { +namespace MDAlgorithms { + +/** TransposeMD : Transpose an MDWorkspace. Allows dimensions to be collapsed down. + + Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge + National Laboratory & European Spallation Source + + This file is part of Mantid. + + Mantid is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + Mantid is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + File change history is stored at: <https://github.com/mantidproject/mantid> + Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class MANTID_MDALGORITHMS_DLL TransposeMD : public API::Algorithm { +public: + TransposeMD(); + virtual ~TransposeMD(); + + virtual const std::string name() const; + virtual int version() const; + virtual const std::string category() const; + virtual const std::string summary() const; + +private: + void init(); + void exec(); +}; + +} // namespace MDAlgorithms +} // namespace Mantid + +#endif /* MANTID_MDALGORITHMS_TRANSPOSEMD_H_ */ diff --git a/Code/Mantid/Framework/MDAlgorithms/src/TransposeMD.cpp b/Code/Mantid/Framework/MDAlgorithms/src/TransposeMD.cpp new file mode 100644 index 0000000000000000000000000000000000000000..55c9619e4fdf79d8ca11fea6e521563f12eb93f3 --- /dev/null +++ b/Code/Mantid/Framework/MDAlgorithms/src/TransposeMD.cpp @@ -0,0 +1,148 @@ +#include "MantidMDAlgorithms/TransposeMD.h" +#include "MantidKernel/ArrayProperty.h" +#include "MantidKernel/ArrayBoundedValidator.h" +#include "MantidAPI/IMDHistoWorkspace.h" +#include "MantidAPI/IMDIterator.h" +#include "MantidDataObjects/CoordTransformAligned.h" +#include "MantidDataObjects/MDHistoWorkspace.h" +#include "MantidGeometry/MDGeometry/IMDDimension.h" +#include "MantidGeometry/MDGeometry/MDHistoDimension.h" + +#include <vector> +#include <algorithm> +#include <numeric> +#include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> + +namespace Mantid { +namespace MDAlgorithms { + +using namespace Mantid::Kernel; +using namespace Mantid::API; +using namespace Mantid::DataObjects; + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(TransposeMD) + +//---------------------------------------------------------------------------------------------- +/** Constructor + */ +TransposeMD::TransposeMD() {} + +//---------------------------------------------------------------------------------------------- +/** Destructor + */ +TransposeMD::~TransposeMD() {} + +//---------------------------------------------------------------------------------------------- + +/// Algorithms name for identification. @see Algorithm::name +const std::string TransposeMD::name() const { return "TransposeMD"; } + +/// Algorithm's version for identification. @see Algorithm::version +int TransposeMD::version() const { return 1; } + +/// Algorithm's category for identification. @see Algorithm::category +const std::string TransposeMD::category() const { return "MDAlgorithms"; } + +/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary +const std::string TransposeMD::summary() const { + return "Transpose the dimensions of a MDWorkspace to create a new output " + "MDWorkspace"; +} + +//---------------------------------------------------------------------------------------------- +/** Initialize the algorithm's properties. + */ +void TransposeMD::init() { + declareProperty(new WorkspaceProperty<IMDHistoWorkspace>("InputWorkspace", "", + Direction::Input), + "An input workspace."); + + auto axisValidator = boost::make_shared<ArrayBoundedValidator<int>>(); + axisValidator->clearUpper(); + axisValidator->setLower(0); + + declareProperty(new ArrayProperty<int>("Axes", std::vector<int>(0), + axisValidator, Direction::Input), + "Permutes the axes according to the indexes given. Zero " + "based indexing. Defaults to no transpose."); + + declareProperty(new WorkspaceProperty<IMDHistoWorkspace>( + "OutputWorkspace", "", Direction::Output), + "An output workspace."); +} + +//---------------------------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void TransposeMD::exec() { + IMDHistoWorkspace_sptr inWSProp = getProperty("InputWorkspace"); + auto inWS = boost::dynamic_pointer_cast<MDHistoWorkspace>(inWSProp); + if (!inWS) { + throw std::invalid_argument( + "Expect the InputWorkspace to be a MDHistoWorkspace"); + } + + size_t nDimsInput = inWS->getNumDims(); + size_t nDimsOutput = inWS->getNumDims(); // The assumed default. + std::vector<int> axesInts = this->getProperty("Axes"); + std::vector<size_t> axes(axesInts.begin(), axesInts.end()); + Property *axesProperty = this->getProperty("Axes"); + if (!axesProperty->isDefault()) { + if (axes.size() > nDimsInput) { + throw std::invalid_argument( + "More axis specified than dimensions are avaiable in the input"); + } + auto it = std::max_element(axes.begin(), axes.end()); + if (*it > nDimsInput) { + throw std::invalid_argument("One of the axis indexes specified indexes a " + "dimension outside the real dimension range"); + } + nDimsOutput = axes.size(); + } else { + axes = std::vector<size_t>(nDimsOutput); + std::iota(axes.begin(), axes.end(), 0); + } + + + std::vector<coord_t> origin; + std::vector<Geometry::IMDDimension_sptr> targetGeometry; + for (size_t i = 0; i < nDimsOutput; ++i) { + // Clone the dimension corresponding to the axis requested. + auto cloneDim = Geometry::IMDDimension_sptr( + new Geometry::MDHistoDimension(inWS->getDimension(axes[i]).get())); + targetGeometry.push_back(cloneDim); + // Set the same origin as we have on the input workspace + origin.push_back(coord_t(cloneDim->getMinimum())); + } + + // Make the output workspace in the right shape. + auto outWS = MDHistoWorkspace_sptr(new MDHistoWorkspace(targetGeometry)); + + // Configure the coordinate transform. + std::vector<coord_t> scaling(nDimsOutput, 1); // No scaling + CoordTransformAligned coordTransform(nDimsInput, nDimsOutput, axes, origin, + scaling); + + IMDIterator * inIterator = inWS->createIterator(); + do{ + auto center = inIterator->getCenter(); + const coord_t* incoords = center.getBareArray(); + std::vector<coord_t> outcoords(nDimsOutput); + coordTransform.apply(incoords, &outcoords[0]); + + size_t index = outWS->getLinearIndexAtCoord(&outcoords[0]); + outWS->setSignalAt(index, inIterator->getSignal()); + outWS->setErrorSquaredAt(index, inIterator->getError()*inIterator->getError()); + // TODO more otherwise + + }while(inIterator->next()); + + + this->setProperty("OutputWorkspace", + outWS); +} + +} // namespace MDAlgorithms +} // namespace Mantid diff --git a/Code/Mantid/Framework/MDAlgorithms/test/TransposeMDTest.h b/Code/Mantid/Framework/MDAlgorithms/test/TransposeMDTest.h new file mode 100644 index 0000000000000000000000000000000000000000..7f008da51b4f51b3858582cfa4ee112e58e9b633 --- /dev/null +++ b/Code/Mantid/Framework/MDAlgorithms/test/TransposeMDTest.h @@ -0,0 +1,174 @@ +#ifndef MANTID_MDALGORITHMS_TRANSPOSEMDTEST_H_ +#define MANTID_MDALGORITHMS_TRANSPOSEMDTEST_H_ + +#include <cxxtest/TestSuite.h> +#include <vector> + +#include "MantidMDAlgorithms/TransposeMD.h" +#include "MantidTestHelpers/MDEventsTestHelper.h" +#include "MantidGeometry/MDGeometry/MDTypes.h" + +using Mantid::MDAlgorithms::TransposeMD; +using namespace Mantid::DataObjects; +using namespace Mantid::API; + +class TransposeMDTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static TransposeMDTest *createSuite() { return new TransposeMDTest(); } + static void destroySuite(TransposeMDTest *suite) { delete suite; } + + void test_Init() { + TransposeMD alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + void test_valid_axes_lower_limit_throws() { + TransposeMD transposeMD; + transposeMD.initialize(); + std::vector<int> axes; + axes.push_back(1); // should be fine. + TS_ASSERT_THROWS_NOTHING(transposeMD.setProperty("Axes", axes)); + axes.push_back(-1); // Not a valid axis + TS_ASSERT_THROWS(transposeMD.setProperty("Axes", axes), + std::invalid_argument &); + } + + void test_too_many_dimension_indexes_throws() { + auto inputWS = MDEventsTestHelper::makeFakeMDHistoWorkspace( + 1 /*signal*/, 2 /*numDims*/, 3 /*numBins in each dimension*/); + + TransposeMD transposeMD; + transposeMD.setChild(true); + transposeMD.initialize(); + transposeMD.setPropertyValue("OutputWorkspace", "dummy"); + transposeMD.setProperty( + "Axes", + std::vector<int>(4, 1)); // 4-axis entries, but only 3 dimensions + transposeMD.setProperty("InputWorkspace", inputWS); + TS_ASSERT_THROWS(transposeMD.execute(), std::invalid_argument &); + } + + void test_indexes_that_dont_exist_throws() { + auto inputWS = MDEventsTestHelper::makeFakeMDHistoWorkspace( + 1 /*signal*/, 2 /*numDims*/, 3 /*numBins in each dimension*/); + + TransposeMD transposeMD; + transposeMD.setChild(true); + transposeMD.initialize(); + transposeMD.setPropertyValue("OutputWorkspace", "dummy"); + transposeMD.setProperty("Axes", + std::vector<int>(1, 3)); // Invalid index of 3! + transposeMD.setProperty("InputWorkspace", inputWS); + TSM_ASSERT_THROWS( + "Axis values can only be 0-2 for this ws. 3 is not valid.", + transposeMD.execute(), std::invalid_argument &); + } + + void test_no_transpose() { + auto inputWS = MDEventsTestHelper::makeFakeMDHistoWorkspace( + 1 /*signal*/, 2 /*numDims*/, 3 /*numBins in each dimension*/); + + // Set some values. If transposed then these should end up elsewhere. + inputWS->setSignalAt(0, 2); + inputWS->setSignalAt(1, 2); + + TransposeMD transposeMD; + transposeMD.setChild(true); + transposeMD.initialize(); + transposeMD.setPropertyValue("OutputWorkspace", "dummy"); + transposeMD.setProperty("InputWorkspace", inputWS); + transposeMD.execute(); + IMDHistoWorkspace_sptr outputWS = + transposeMD.getProperty("OutputWorkspace"); + + // Lets check that the workspaces are essentially the same. + TS_ASSERT_EQUALS(inputWS->getNumDims(), outputWS->getNumDims()); + TS_ASSERT_EQUALS(inputWS->getDimension(0)->getName(), + outputWS->getDimension(0)->getName()); + TS_ASSERT_EQUALS(inputWS->getDimension(1)->getName(), + outputWS->getDimension(1)->getName()); + + // Data should be the same too. + TS_ASSERT_EQUALS(inputWS->getSignalAt(0), outputWS->getSignalAt(0)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(1), outputWS->getSignalAt(1)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(2), outputWS->getSignalAt(2)); + } + + void test_transpose_all() { + auto inputWS = MDEventsTestHelper::makeFakeMDHistoWorkspace( + 1 /*signal*/, 2 /*numDims*/, 3 /*numBins in each dimension*/); + + // Set some values. If transposed then these should end up elsewhere. + inputWS->setSignalAt(0, 2); + inputWS->setSignalAt(1, 2); + + TransposeMD transposeMD; + transposeMD.setChild(true); + transposeMD.initialize(); + transposeMD.setPropertyValue("OutputWorkspace", "dummy"); + transposeMD.setProperty("InputWorkspace", inputWS); + std::vector<int> axes; + axes.push_back(1); + axes.push_back(0); + transposeMD.setProperty("Axes", axes); + transposeMD.execute(); + IMDHistoWorkspace_sptr outputWS = + transposeMD.getProperty("OutputWorkspace"); + + // Lets check the output workspace + TS_ASSERT_EQUALS(inputWS->getNumDims(), outputWS->getNumDims()); + TS_ASSERT_EQUALS(inputWS->getDimension(0)->getName(), + outputWS->getDimension(axes[0])->getName()); + TS_ASSERT_EQUALS(inputWS->getDimension(1)->getName(), + outputWS->getDimension(axes[1])->getName()); + + // Data should be transposed. + TS_ASSERT_EQUALS(inputWS->getSignalAt(0), outputWS->getSignalAt(0)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(1), outputWS->getSignalAt(1 * 3)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(2), outputWS->getSignalAt(2)); + } + + void test_collapse() { + + size_t nbins[3] = {3, 3, 1}; // last dimension integrated out + Mantid::coord_t min[3] = {0, 0, 0}; + Mantid::coord_t max[3] = {10, 10, 5}; + auto inputWS = MDEventsTestHelper::makeFakeMDHistoWorkspaceGeneral(3 /*ndims*/, 1 /*signal*/, + 1 /*errorSquared*/, + nbins /*numBins*/, min, max); + // Set some values. If transposed then these should end up elsewhere. + inputWS->setSignalAt(0, 2); + inputWS->setSignalAt(1, 2); + + TransposeMD transposeMD; + transposeMD.setChild(true); + transposeMD.initialize(); + transposeMD.setPropertyValue("OutputWorkspace", "dummy"); + transposeMD.setProperty("InputWorkspace", inputWS); + std::vector<int> axes; + axes.push_back(0); + axes.push_back(1); + transposeMD.setProperty("Axes", axes); // 0 and 1, but 2 not specified! + transposeMD.execute(); + IMDHistoWorkspace_sptr outputWS = + transposeMD.getProperty("OutputWorkspace"); + + // Lets check that output workspace + TS_ASSERT_EQUALS(inputWS->getNumDims(), outputWS->getNumDims() + 1); + TS_ASSERT_EQUALS(inputWS->getDimension(0)->getName(), + outputWS->getDimension(axes[0])->getName()); + TS_ASSERT_EQUALS(inputWS->getDimension(1)->getName(), + outputWS->getDimension(axes[1])->getName()); + + // Otherwise the data should be the same. We simply clipped off the + // integrated dimension. + TS_ASSERT_EQUALS(inputWS->getSignalAt(0), outputWS->getSignalAt(0)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(1), outputWS->getSignalAt(1)); + TS_ASSERT_EQUALS(inputWS->getSignalAt(2), outputWS->getSignalAt(2)); + } +}; + +#endif /* MANTID_MDALGORITHMS_TRANSPOSEMDTEST_H_ */