Mantid
Loading...
Searching...
No Matches
EstimateScatteringVolumeCentreOfMass.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
10#include "MantidAPI/Run.h"
11#include "MantidAPI/Sample.h"
19#include "MantidKernel/Matrix.h"
20#include "MantidKernel/V3D.h"
21#include <unordered_map>
22
23namespace Mantid::Algorithms {
24
25using namespace API;
26using namespace Geometry;
27using namespace Kernel;
28
29namespace {
30const std::string UNIT_M = "m";
31const std::string UNIT_CM = "cm";
32const std::string UNIT_MM = "mm";
33static const std::unordered_map<std::string, double> unitToMeters{{UNIT_M, 1.0}, {UNIT_CM, 0.01}, {UNIT_MM, 0.001}};
34} // namespace
35
36// Register the algorithm into the AlgorithmFactory
37DECLARE_ALGORITHM(EstimateScatteringVolumeCentreOfMass)
38
41
43
44 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input,
45 std::make_shared<InstrumentValidator>()),
46 "Input Workspace");
47 declareProperty(std::make_unique<PropertyWithValue<std::vector<double>>>("CentreOfMass", V3D(), Direction::Output),
48 "Estimated centre of mass of illuminated sample volume");
49
50 auto moreThanZero = std::make_shared<BoundedValidator<double>>();
51 moreThanZero->setLower(1e-6);
52 declareProperty("ElementSize", 1.0, moreThanZero,
53 "The size of one side of an integration element cube in {ElementUnits}");
54
55 declareProperty("ElementUnits", UNIT_MM,
56 std::make_shared<StringListValidator>(std::vector<std::string>{UNIT_M, UNIT_CM, UNIT_MM}),
57 "The units which ElementSize has been provided in");
58}
59
61 // Retrieve the input workspace
62 m_inputWS = getProperty("InputWorkspace");
63 // Cache the beam direction
64 const V3D beamDirection = m_inputWS->getInstrument()->getBeamDirection();
65 // Calculate the element size
66 m_cubeSide = getProperty("ElementSize"); // in units
67 const std::string elementUnits = getProperty("ElementUnits");
68
69 auto it = unitToMeters.find(elementUnits);
70 if (it == unitToMeters.end()) {
71 throw std::invalid_argument("Supported units for ElementUnits are (m, cm, mm), not: " + elementUnits);
72 }
73 m_cubeSide *= it->second; // now in m
74
75 // The sample shape on the workspace already has any initial rotation baked into its definition,
76 // so it is expressed in the sample shape's own frame. The workspace's goniometer R describes
77 // the additional rotation from that frame into the lab frame. The gauge volume (if any) is
78 // defined in the lab frame.
79 //
80 // When a gauge volume is present we rasterise it in the lab frame and transform each candidate
81 // voxel into the sample shape's frame via R.inv() to test inclusion against the sample. Doing
82 // the intersection this way - rather than rotating the gauge into the sample frame - keeps the
83 // gauge's axis-aligned bounding box tight even for non-axis-aligned rotations; rotating the
84 // gauge would inflate its bbox and silently admit voxels outside the actual gauge volume.
85 //
86 // With no gauge volume the illumination volume equals the sample, so we rasterise the sample
87 // in its own frame (where the rasterise loop only ever accepts points inside the sample anyway)
88 // and rotate the resulting mean position into the lab frame.
89 const Geometry::IObject_sptr sampleObject = extractValidSampleObject(m_inputWS->mutableSample());
90 const Kernel::Matrix<double> gonioR = m_inputWS->run().getGoniometer().getR();
91
92 V3D averagePosInLabFrame;
93 if (m_inputWS->run().hasProperty("GaugeVolume")) {
94 averagePosInLabFrame = rasterizeLabGaugeAndCalculateMeanElementPosition(*sampleObject, gonioR);
95 } else {
96 const V3D averagePosInShapeFrame =
97 rasterizeGaugeVolumeAndCalculateMeanElementPosition(beamDirection, sampleObject, sampleObject);
98 averagePosInLabFrame = gonioR * averagePosInShapeFrame;
99 }
100 setProperty("CentreOfMass", std::vector<double>(averagePosInLabFrame));
101}
102
106 const V3D beamDirection, const Geometry::IObject_sptr integrationVolume,
107 const Geometry::IObject_sptr sampleObject) {
108 const auto raster = Geometry::Rasterize::calculate(beamDirection, *integrationVolume, *sampleObject, m_cubeSide);
109 if (raster.l1.size() == 0) {
110 // most errors should be caught by the rasterise function, but just in case
111 const std::string mess("Failed to find any points in the rasterized illumination volume within the sample shape - "
112 "Check sample shape and gauge volume are defined correctly or try reducing the ElementSize");
113 g_log.error(mess);
114 throw std::runtime_error(mess);
115 }
116 // calculate mean element position
117 const V3D meanPos = calcAveragePosition(raster.position);
118 return meanPos;
119}
120
123 // Check there is one, and fail if not
124 if (!sample.getShape().hasValidShape()) {
125 const std::string mess("No shape has been defined for the sample in the input workspace");
126 g_log.error(mess);
127 throw std::invalid_argument(mess);
128 } else {
129 g_log.information("Successfully extracted the sample object");
130 return sample.getShapePtr();
131 }
132}
133
135 const Geometry::IObject &sampleObject, const Kernel::Matrix<double> &gonioR) {
136 g_log.information("Calculating scattering within the gauge volume defined on the input workspace");
137 const std::string xml = m_inputWS->run().getProperty("GaugeVolume")->value();
139
140 Kernel::Matrix<double> gonioRInv(gonioR);
141 gonioRInv.Invert();
142 const bool gonioIsIdentity = (gonioR == Kernel::Matrix<double>(3, 3, true));
143
144 const auto bbox = gauge->getBoundingBox();
145 const double xLength = bbox.xMax() - bbox.xMin();
146 const double yLength = bbox.yMax() - bbox.yMin();
147 const double zLength = bbox.zMax() - bbox.zMin();
148 const auto numXSlices = static_cast<size_t>(xLength / m_cubeSide);
149 const auto numYSlices = static_cast<size_t>(yLength / m_cubeSide);
150 const auto numZSlices = static_cast<size_t>(zLength / m_cubeSide);
151 if (numXSlices == 0 || numYSlices == 0 || numZSlices == 0) {
152 const std::string mess("Gauge volume is too small for the chosen ElementSize - "
153 "try reducing the ElementSize");
154 g_log.error(mess);
155 throw std::runtime_error(mess);
156 }
157 const double dx = xLength / static_cast<double>(numXSlices);
158 const double dy = yLength / static_cast<double>(numYSlices);
159 const double dz = zLength / static_cast<double>(numZSlices);
160
161 V3D sum(0.0, 0.0, 0.0);
162 size_t count = 0;
163 for (size_t i = 0; i < numZSlices; ++i) {
164 const double z = (static_cast<double>(i) + 0.5) * dz + bbox.zMin();
165 for (size_t j = 0; j < numYSlices; ++j) {
166 const double y = (static_cast<double>(j) + 0.5) * dy + bbox.yMin();
167 for (size_t k = 0; k < numXSlices; ++k) {
168 const double x = (static_cast<double>(k) + 0.5) * dx + bbox.xMin();
169 const V3D pLab(x, y, z);
170 // Reject voxels outside the actual (lab-frame) gauge volume. For an axis-aligned gauge
171 // the bbox is tight and this is a no-op, but for any non-axis-aligned authored gauge
172 // it correctly clips the iteration to the gauge interior.
173 if (!gauge->isValid(pLab)) {
174 continue;
175 }
176 // Test inclusion against the sample shape in its own frame.
177 const V3D pShape = gonioIsIdentity ? pLab : gonioRInv * pLab;
178 if (!sampleObject.isValid(pShape)) {
179 continue;
180 }
181 sum += pLab;
182 ++count;
183 }
184 }
185 }
186 if (count == 0) {
187 const std::string mess("Failed to find any voxels inside both the gauge volume and the sample "
188 "shape - check sample shape and gauge volume are defined correctly or "
189 "try reducing the ElementSize");
190 g_log.error(mess);
191 throw std::runtime_error(mess);
192 }
193 sum /= static_cast<double>(count);
194 return sum;
195}
196
198 if (!pos.empty()) {
199 V3D sum = std::accumulate(pos.begin(), pos.end(), V3D(0.0, 0.0, 0.0));
200 sum /= static_cast<double>(pos.size());
201 return sum;
202 } else {
203 // shouldn't be able to reach this point anyway
204 const std::string mess("No intersection points found between illumination volume and sample shape - "
205 "Check sample shape and gauge volume are defined correctly or try reducing the ElementSize");
206 g_log.error(mess);
207 throw std::runtime_error(mess);
208 }
209}
210
211} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
int count
counter
Definition Matrix.cpp:37
Base class from which all concrete algorithm classes should be derived.
Definition Algorithm.h:76
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:423
This class stores information about the sample used in particular run.
Definition Sample.h:33
const Geometry::IObject & getShape() const
Return the sample shape.
Definition Sample.cpp:102
const Geometry::IObject_sptr getShapePtr() const
Return a pointer to the sample shape.
Definition Sample.cpp:110
A property class for workspaces.
const Kernel::V3D rasterizeLabGaugeAndCalculateMeanElementPosition(const Geometry::IObject &sampleObject, const Kernel::Matrix< double > &gonioR)
Rasterise the workspace's lab-frame GaugeVolume directly, transforming each candidate voxel into the ...
const Geometry::IObject_sptr extractValidSampleObject(const API::Sample &sample)
Create the sample object using the Geometry classes, or use the existing one.
const Kernel::V3D rasterizeGaugeVolumeAndCalculateMeanElementPosition(const Kernel::V3D beamDirection, const Geometry::IObject_sptr integrationVolume, const Geometry::IObject_sptr sampleObject)
Calculate as raster of the illumination volume, evaluating which points are within the sample geometr...
const Kernel::V3D calcAveragePosition(const std::vector< Kernel::V3D > &pos)
API::MatrixWorkspace_sptr m_inputWS
A pointer to the input workspace.
IObject : Interface for geometry objects.
Definition IObject.h:42
virtual bool isValid(const Kernel::V3D &) const =0
virtual bool hasValidShape() const =0
Class originally intended to be used with the DataHandling 'LoadInstrument' algorithm.
std::shared_ptr< CSGObject > createShape(Poco::XML::Element *pElem)
Creates a geometric object from a DOM-element-node pointing to an element whose child nodes contain t...
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
Numerical Matrix class.
Definition Matrix.h:42
T Invert()
LU inversion routine.
Definition Matrix.cpp:924
The concrete, templated class for properties.
Class for 3D vectors.
Definition V3D.h:34
MANTID_GEOMETRY_DLL Raster calculate(const Kernel::V3D &beamDirection, const IObject &integShape, const IObject &sampleShape, const double cubeSizeInMetre)
std::shared_ptr< IObject > IObject_sptr
Typdef for a shared pointer.
Definition IObject.h:93
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54