Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Framework/DataHandling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ set(SRC_FILES
src/StartAndEndTimeFromNexusFileExtractor.cpp
src/UpdateInstrumentFromFile.cpp
src/XmlHandler.cpp
src/RotateSampleShape.cpp
)

set(INC_FILES
Expand Down Expand Up @@ -444,6 +445,7 @@ set(INC_FILES
src/LoadRaw/isisraw2.h
src/LoadRaw/item_struct.h
src/LoadRaw/vms_convert.h
inc/MantidDataHandling/RotateSampleShape.h
)

set(TEST_FILES
Expand Down Expand Up @@ -642,6 +644,7 @@ set(TEST_FILES
StartAndEndTimeFromNexusFileExtractorTest.h
UpdateInstrumentFromFileTest.h
XMLInstrumentParameterTest.h
RotateSampleShapeTest.h
)

if(COVERAGE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace DataHandling {
*/
class MANTID_DATAHANDLING_DLL CreateSampleShape final : public API::Algorithm {
public:
static void setSampleShape(API::ExperimentInfo &expt, const std::string &shapeXML);
static void setSampleShape(API::ExperimentInfo &expt, const std::string &shapeXML, bool addTypeTag = true);

public:
const std::string name() const override { return "CreateSampleShape"; }
Expand Down
53 changes: 53 additions & 0 deletions Framework/DataHandling/inc/MantidDataHandling/RotateSampleShape.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#pragma once

#include "MantidAPI/Algorithm.h"
#include "MantidAPI/MultipleExperimentInfos.h"
#include "MantidDataHandling/DllConfig.h"

namespace Mantid {
namespace Geometry {
class Goniometer;
}
} // namespace Mantid

namespace Mantid {
namespace DataHandling {

using Mantid::Geometry::Goniometer;

/** Define the initial orientation of the sample with respect to the beam and instrument by giving the axes and
*directions of rotations.
*/
class MANTID_DATAHANDLING_DLL RotateSampleShape final : public API::Algorithm {
public:
/// Algorithm's name for identification
const std::string name() const override { return "RotateSampleShape"; };
/// Summary of algorithms purpose
const std::string summary() const override {
return "Define the initial orientation of the sample with respect to the beam and instrument "
"by giving the axes, angle and directions of rotations.";
}

/// Algorithm's version for identification
int version() const override { return 1; };
const std::vector<std::string> seeAlso() const override { return {"SetGoniometer"}; }
/// Algorithm's category for identification
const std::string category() const override { return "DataHandling\\Instrument"; }

private:
/// Initialise the properties
void init() override;
/// Run the algorithm
void exec() override;
void prepareGoniometerAxes(Goniometer &gon);
bool checkIsValidShape(const API::ExperimentInfo_sptr &ei, std::string &shapeXML, bool &isMeshShape);
};

} // namespace DataHandling
} // namespace Mantid
5 changes: 3 additions & 2 deletions Framework/DataHandling/src/CreateSampleShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ using namespace Mantid::API;
* @brief Set the shape via an XML string on the given experiment
* @param expt A reference to the experiment holding the sample object
* @param shapeXML XML defining the object's shape
* @param addTypeTag true to wrap a \<type\> tag around the XML supplied(default)
*/
void CreateSampleShape::setSampleShape(API::ExperimentInfo &expt, const std::string &shapeXML) {
void CreateSampleShape::setSampleShape(API::ExperimentInfo &expt, const std::string &shapeXML, bool addTypeTag) {
Geometry::ShapeFactory sFactory;
// Create the object
auto shape = sFactory.createShape(shapeXML);
auto shape = sFactory.createShape(shapeXML, addTypeTag);
// Check it's valid and attach it to the workspace sample but preserve any
// material
if (shape->hasValidShape()) {
Expand Down
164 changes: 164 additions & 0 deletions Framework/DataHandling/src/RotateSampleShape.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright &copy; 2024 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidDataHandling/RotateSampleShape.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/Sample.h"
#include "MantidDataHandling/CreateSampleShape.h"
#include "MantidGeometry/Instrument/Goniometer.h"
#include "MantidGeometry/Objects/MeshObject.h"
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>

namespace Mantid::DataHandling {

// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(RotateSampleShape)

using namespace Mantid::Geometry;
using namespace Mantid::Kernel;
using namespace Mantid::API;

/// How many axes (max) to define
const size_t NUM_AXES = 6;

/** Initialize the algorithm's properties.
*/
void RotateSampleShape::init() {
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>("Workspace", "", Direction::InOut),
"The workspace containing the sample whose orientation is to be rotated");

std::string axisHelp = ": degrees,x,y,z,1/-1 (1 for ccw, -1 for cw rotation).";
for (size_t i = 0; i < NUM_AXES; i++) {
std::ostringstream propName;
propName << "Axis" << i;
declareProperty(std::make_unique<PropertyWithValue<std::string>>(propName.str(), "", Direction::Input),
propName.str() + axisHelp);
}
}

/** Execute the algorithm.
*/
void RotateSampleShape::exec() {
Workspace_sptr ws = getProperty("Workspace");
auto ei = std::dynamic_pointer_cast<ExperimentInfo>(ws);

if (!ei) {
// We're dealing with an MD workspace which has multiple experiment infos
auto infos = std::dynamic_pointer_cast<MultipleExperimentInfos>(ws);
if (!infos) {
throw std::invalid_argument("Input workspace does not support RotateSampleShape");
}
if (infos->getNumExperimentInfo() < 1) {
ExperimentInfo_sptr info(new ExperimentInfo());
infos->addExperimentInfo(info);
}
ei = infos->getExperimentInfo(0);
}

std::string shapeXML;
bool isMeshShape = false;
if (!checkIsValidShape(ei, shapeXML, isMeshShape)) {
throw std::runtime_error("Input sample does not have a valid shape!");
}

// Create a goniometer with provided rotations
Goniometer gon;
prepareGoniometerAxes(gon);
if (gon.getNumberAxes() == 0)
g_log.warning() << "Empty goniometer created; will always return an "
"identity rotation matrix.\n";

const auto sampleShapeRotation = gon.getR();
if (sampleShapeRotation == Kernel::Matrix<double>(3, 3, true)) {
// If the resulting rotationMatrix is Identity, ignore the calculatrion
g_log.warning("Rotation matrix set via RotateSampleShape is an Identity matrix. Ignored rotating sample shape");
return;
}

const auto oldRotation = ei->run().getGoniometer().getR();
auto newSampleShapeRot = sampleShapeRotation * oldRotation;
if (isMeshShape) {
auto meshShape = std::dynamic_pointer_cast<MeshObject>(ei->sample().getShapePtr());
meshShape->rotate(newSampleShapeRot);
} else {
shapeXML = Geometry::ShapeFactory().addGoniometerTag(newSampleShapeRot, shapeXML);
Mantid::DataHandling::CreateSampleShape::setSampleShape(*ei, shapeXML, false);
}
}

bool RotateSampleShape::checkIsValidShape(const API::ExperimentInfo_sptr &ei, std::string &shapeXML,
bool &isMeshShape) {
if (ei->sample().hasShape()) {
const auto csgShape = std::dynamic_pointer_cast<CSGObject>(ei->sample().getShapePtr());
if (csgShape && csgShape->hasValidShape()) {
shapeXML = csgShape->getShapeXML();
if (!shapeXML.empty()) {
return true;
}
} else {
const auto meshShape = std::dynamic_pointer_cast<MeshObject>(ei->sample().getShapePtr());
if (meshShape && meshShape->hasValidShape()) {
isMeshShape = true;
return true;
}
}
}
return false;
}

void RotateSampleShape::prepareGoniometerAxes(Goniometer &gon) {
for (size_t i = 0; i < NUM_AXES; i++) {
std::ostringstream propName;
propName << "Axis" << i;
std::string axisDesc = getPropertyValue(propName.str());
if (!axisDesc.empty()) {
std::vector<std::string> tokens;
boost::split(tokens, axisDesc, boost::algorithm::detail::is_any_ofF<char>(","));
if (tokens.size() != 5)
throw std::invalid_argument("Wrong number of arguments to parameter " + propName.str() +
". Expected 5 comma-separated arguments.");

std::transform(tokens.begin(), tokens.end(), tokens.begin(), [](std::string str) { return Strings::strip(str); });
if (!std::all_of(tokens.begin(), tokens.end(), [](std::string tokenStr) { return !tokenStr.empty(); })) {
throw std::invalid_argument("Empty axis parameters found!");
}

double angle = 0;
if (!Strings::convert(tokens[0], angle)) {
throw std::invalid_argument("Error converting angle string '" + tokens[0] + "' to a number.");
}

std::string axisName = "RotateSampleShapeAxis" + Strings::toString(i) + "_FixedValue";
double x = 0, y = 0, z = 0;
if (!Strings::convert(tokens[1], x))
throw std::invalid_argument("Error converting x string '" + tokens[1] + "' to a number.");
if (!Strings::convert(tokens[2], y))
throw std::invalid_argument("Error converting y string '" + tokens[2] + "' to a number.");
if (!Strings::convert(tokens[3], z))
throw std::invalid_argument("Error converting z string '" + tokens[3] + "' to a number.");
V3D vec(x, y, z);
if (vec.norm() < 1e-4)
throw std::invalid_argument("Rotation axis vector should be non-zero!");

int ccw = 0;
if (!Strings::convert(tokens[4], ccw)) {
throw std::invalid_argument("Error converting sense of roation '" + tokens[4] + "' to a number.");
}
if (ccw != 1 && ccw != -1) {
throw std::invalid_argument("The sense of rotation parameter must only be 1 (ccw) or -1 (cw)");
}
// Default to degrees
gon.pushAxis(axisName, x, y, z, angle, ccw);
}
}
}

} // namespace Mantid::DataHandling
Loading
Loading