Mantid
Loading...
Searching...
No Matches
Rasterize.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 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 +
11#include <array>
12
13namespace Mantid::Geometry {
14
15using Geometry::CSGObject;
16using Kernel::V3D;
17
18void Raster::reserve(size_t numVolumeElements) {
19 l1.reserve(numVolumeElements);
20 volume.reserve(numVolumeElements);
21 position.reserve(numVolumeElements);
22}
23
24namespace { // anonymous
25
26constexpr static V3D X_AXIS{1, 0, 0};
27constexpr static V3D Y_AXIS{0, 1, 0};
28constexpr static V3D Z_AXIS{0, 0, 1};
29
30// since cylinders are symmetric around the main axis, choose a random
31// perpendicular to have as the second axis
32V3D createPerpendicular(const V3D &symmetryAxis) {
33 const std::array<double, 3> scalars = {{fabs(symmetryAxis.scalar_prod(X_AXIS)),
34 fabs(symmetryAxis.scalar_prod(Y_AXIS)),
35 fabs(symmetryAxis.scalar_prod(Z_AXIS))}};
36 // check against the cardinal axes
37 if (scalars[0] == 0.)
38 return symmetryAxis.cross_prod(X_AXIS);
39 else if (scalars[1] == 0.)
40 return symmetryAxis.cross_prod(Y_AXIS);
41 else if (scalars[2] == 0.)
42 return symmetryAxis.cross_prod(Z_AXIS);
43
44 // see which one is smallest
45 if (scalars[0] < scalars[1] && scalars[0] < scalars[2])
46 return symmetryAxis.cross_prod(X_AXIS);
47 else if (scalars[1] < scalars[0] && scalars[1] < scalars[2])
48 return symmetryAxis.cross_prod(Y_AXIS);
49 else
50 return symmetryAxis.cross_prod(Z_AXIS);
51}
52
53const V3D CalculatePosInCylinder(const double phi, const double R, const double z, const std::array<V3D, 3> coords) {
54 const auto &x_prime = coords[0];
55 const auto &y_prime = coords[1];
56 const auto &z_prime = coords[2];
57
58 double rSinPhi = -R * sin(phi);
59 const double rCosPhi = -R * cos(phi);
60
61 // Calculate the current position in the shape in Cartesian coordinates
62 const double xcomp = rCosPhi * x_prime[0] + rSinPhi * y_prime[0] + z * z_prime[0];
63 const double ycomp = rCosPhi * x_prime[1] + rSinPhi * y_prime[1] + z * z_prime[1];
64 const double zcomp = rCosPhi * x_prime[2] + rSinPhi * y_prime[2] + z * z_prime[2];
65 return V3D(xcomp, ycomp, zcomp);
66}
67
68// This must be updated when new primitives become available
69bool hasCustomizedRaster(detail::ShapeInfo::GeometryShape shape) {
71 return true;
73 return true;
74 // nothing else has been customized
75 return false;
76}
77
78Raster calculateGeneric(const V3D &beamDirection, const IObject &integShape, const IObject &sampleShape,
79 const double cubeSizeInMetre) {
80 if (cubeSizeInMetre <= 0.)
81 throw std::runtime_error("Tried to section shape into zero size elements");
82
83 const auto integBbox = integShape.getBoundingBox();
84 assert(integBbox.xMax() > integBbox.xMin());
85 assert(integBbox.yMax() > integBbox.yMin());
86 assert(integBbox.zMax() > integBbox.zMin());
87
88 const double xLength = integBbox.xMax() - integBbox.xMin();
89 const double yLength = integBbox.yMax() - integBbox.yMin();
90 const double zLength = integBbox.zMax() - integBbox.zMin();
91
92 const auto numXSlices = static_cast<size_t>(xLength / cubeSizeInMetre);
93 const auto numYSlices = static_cast<size_t>(yLength / cubeSizeInMetre);
94 const auto numZSlices = static_cast<size_t>(zLength / cubeSizeInMetre);
95 const double XSliceThickness = xLength / static_cast<double>(numXSlices);
96 const double YSliceThickness = yLength / static_cast<double>(numYSlices);
97 const double ZSliceThickness = zLength / static_cast<double>(numZSlices);
98 const double elementVolume = XSliceThickness * YSliceThickness * ZSliceThickness;
99
100 const size_t numVolumeElements = numXSlices * numYSlices * numZSlices;
101
102 // Also define the shape bounding box for quicker checking if gauge vol is outside shape
103 const auto sampleBbox = sampleShape.getBoundingBox();
104 assert(sampleBbox.xMax() > sampleBbox.xMin());
105 assert(sampleBbox.yMax() > sampleBbox.yMin());
106 assert(sampleBbox.zMax() > sampleBbox.zMin());
107
108 Raster result;
109 try {
110 result.reserve(numVolumeElements);
111 } catch (...) {
112 // Typically get here if the number of volume elements is too large
113 // Provide a bit more information
114 throw std::logic_error("Too many volume elements requested - try increasing the value "
115 "of the ElementSize property.");
116 }
117
118 // go through the bounding box generating cubes and seeing if they are
119 // inside the shape
120 for (size_t i = 0; i < numZSlices; ++i) {
121 const double z = (static_cast<double>(i) + 0.5) * ZSliceThickness + integBbox.zMin();
122
123 for (size_t j = 0; j < numYSlices; ++j) {
124 const double y = (static_cast<double>(j) + 0.5) * YSliceThickness + integBbox.yMin();
125
126 for (size_t k = 0; k < numXSlices; ++k) {
127 const double x = (static_cast<double>(k) + 0.5) * XSliceThickness + integBbox.xMin();
128 // Set the current position in the sample in Cartesian coordinates.
129 const Kernel::V3D currentPosition = V3D(x, y, z);
130 // Check if the current point is within the object. If not, skip.
131 if (sampleShape.isValid(currentPosition)) {
132 // Create track for distance in sample before scattering point
133 Track incoming(currentPosition, -beamDirection);
134 // We have an issue where occasionally, even though a point is
135 // within the object a track segment to the surface isn't correctly
136 // created. In the context of this algorithm I think it's safe to
137 // just chuck away the element in this case. This will also throw
138 // away points that are inside a gauge volume but outside the sample
139 if (sampleShape.interceptSurface(incoming) > 0) {
140 result.l1.emplace_back(incoming.totalDistInsideObject());
141 result.position.emplace_back(currentPosition);
142 result.volume.emplace_back(elementVolume);
143 }
144 }
145 }
146 }
147 }
148
149 // Record the number of elements we ended up with
150 result.totalvolume = static_cast<double>(result.l1.size()) * elementVolume;
151
152 return result;
153}
154
155} // namespace
156
157namespace Rasterize {
158
159// -------------------
160// collection of calculations that convert to CSGObjects and pass the work on
161
162Raster calculate(const V3D &beamDirection, const IObject &integShape, const IObject &sampleShape,
163 const double cubeSizeInMetre) {
164 const auto primitive = integShape.shape();
165 if (hasCustomizedRaster(primitive)) {
166 // convert to the underlying primitive type - this assumes that there are
167 // only customizations for CSGObjects
168 const auto &shapeInfo = integShape.shapeInfo();
170 const auto params = shapeInfo.cylinderGeometry();
171 const size_t numSlice = std::max<size_t>(1, static_cast<size_t>(params.height / cubeSizeInMetre));
172 const size_t numAnnuli = std::max<size_t>(1, static_cast<size_t>(params.radius / cubeSizeInMetre));
173 return calculateCylinder(beamDirection, integShape, sampleShape, numSlice, numAnnuli);
175 const auto params = shapeInfo.hollowCylinderGeometry();
176 const size_t numSlice = std::max<size_t>(1, static_cast<size_t>(params.height / cubeSizeInMetre));
177 const size_t numAnnuli =
178 std::max<size_t>(1, static_cast<size_t>((params.radius - params.innerRadius) / cubeSizeInMetre));
179 return calculateHollowCylinder(beamDirection, integShape, sampleShape, numSlice, numAnnuli);
180 } else {
181 throw std::runtime_error("Rasterize::calculate should never get to this point");
182 }
183 } else {
184 return calculateGeneric(beamDirection, integShape, sampleShape, cubeSizeInMetre);
185 }
186}
187
188Raster calculateCylinder(const V3D &beamDirection, const IObject &integShape, const IObject &sampleShape,
189 const size_t numSlices, const size_t numAnnuli) {
191 throw std::invalid_argument("Given shape is not a cylinder.");
192
193 if (numSlices == 0)
194 throw std::runtime_error("Tried to section cylinder into zero slices");
195 if (numAnnuli == 0)
196 throw std::runtime_error("Tried to section cylinder into zero annuli");
197
198 // convert to the underlying primitive type
199 const auto &shapeInfo = integShape.shapeInfo();
200
201 // get the geometry for the volume elements
202 const auto params = shapeInfo.cylinderGeometry();
203 const V3D center = (params.axis * .5 * params.height) + params.centreOfBottomBase;
204
205 const double sliceThickness{params.height / static_cast<double>(numSlices)};
206 const double deltaR{params.radius / static_cast<double>(numAnnuli)};
207
208 /* The number of volume elements is
209 * numslices*(1+2+3+.....+numAnnuli)*6
210 * Since the first annulus is separated in 6 segments, the next one in 12 and
211 * so on.....
212 */
213 const size_t numVolumeElements = numSlices * numAnnuli * (numAnnuli + 1) * 3;
214
215 Raster result;
216 result.reserve(numVolumeElements);
217 result.totalvolume = params.height * M_PI * params.radius * params.radius;
218
219 // Assume that z' = axis. Then select whatever has the smallest dot product
220 // with axis to be the x' direction
221 const V3D z_prime = normalize(params.axis);
222 const V3D x_prime = createPerpendicular(z_prime);
223 const V3D y_prime = z_prime.cross_prod(x_prime);
224 const auto coords = std::array<V3D, 3>{{x_prime, y_prime, z_prime}};
225
226 // loop over the elements of the integShape and create everything
227 // loop over slices
228 for (size_t i = 0; i < numSlices; ++i) {
229 const double z = (static_cast<double>(i) + 0.5) * sliceThickness - 0.5 * params.height;
230
231 // Number of elements in 1st annulus
232 size_t Ni = 0;
233 // loop over annuli
234 for (size_t j = 0; j < numAnnuli; ++j) {
235 Ni += 6;
236 const double R = (static_cast<double>(j) * params.radius / static_cast<double>(numAnnuli)) + (0.5 * deltaR);
237
238 // all the volume elements in the ring/slice are the same
239 const double outerR = R + (deltaR / 2.0);
240 const double innerR = outerR - deltaR;
241 const double elementVolume =
242 M_PI * (outerR * outerR - innerR * innerR) * sliceThickness / static_cast<double>(Ni);
243
244 // loop over elements in current annulus
245 for (size_t k = 0; k < Ni; ++k) {
246 const double phi = 2. * M_PI * static_cast<double>(k) / static_cast<double>(Ni);
247 const auto position = center + CalculatePosInCylinder(phi, R, z, coords);
248 // check element is within the sample shape
249 if (sampleShape.isValid(position)) {
250 Track incoming(position, -beamDirection);
251 // check there is a valid path from the source to this point within the sample
252 if (sampleShape.interceptSurface(incoming) > 0) {
253 result.l1.emplace_back(incoming.totalDistInsideObject());
254 result.position.emplace_back(position);
255 result.volume.emplace_back(elementVolume);
256 }
257 }
258 } // loop over k
259 } // loop over j
260 } // loop over i
261
262 return result;
263}
264
265Raster calculateHollowCylinder(const V3D &beamDirection, const IObject &integShape, const IObject &sampleShape,
266 const size_t numSlices, const size_t numAnnuli) {
268 throw std::invalid_argument("Given shape is not a hollow cylinder.");
269
270 if (numSlices == 0)
271 throw std::runtime_error("Tried to section cylinder into zero slices");
272 if (numAnnuli == 0)
273 throw std::runtime_error("Tried to section cylinder into zero annuli");
274
275 // convert to the underlying primitive type
276 const auto &shapeInfo = integShape.shapeInfo();
277
278 // get the geometry for the volume elements
279 const auto params = shapeInfo.hollowCylinderGeometry();
280 const V3D center = (params.axis * .5 * params.height) + params.centreOfBottomBase;
281
282 const double sliceThickness{params.height / static_cast<double>(numSlices)};
283 const double deltaR{(params.radius - params.innerRadius) / static_cast<double>(numAnnuli)};
284
285 /* The number of volume elements is
286 * numslices*(1+2+3+.....+numAnnuli)*6
287 * Since the first annulus is separated in 6 segments, the next one in 12 and
288 * so on.....
289 */
290 const size_t numVolumeElements = numSlices * numAnnuli * (numAnnuli + 1) * 3;
291
292 Raster result;
293 result.reserve(numVolumeElements);
294 result.totalvolume = params.height * M_PI * (params.radius * params.radius - params.innerRadius * params.innerRadius);
295
296 // Assume that z' = axis. Then select whatever has the smallest dot product
297 // with axis to be the x' direction
298 V3D z_prime = params.axis;
299 z_prime.normalize();
300 const V3D x_prime = createPerpendicular(z_prime);
301 const V3D y_prime = z_prime.cross_prod(x_prime);
302 const auto coords = std::array<V3D, 3>{{x_prime, y_prime, z_prime}};
303
304 // loop over the elements of the shape and create everything
305 // loop over slices
306 for (size_t i = 0; i < numSlices; ++i) {
307 const double z = (static_cast<double>(i) + 0.5) * sliceThickness - 0.5 * params.height;
308
309 // Number of elements in 1st annulus
310 // NOTE:
311 // For example, if the hollow cylinder consist of an inner cylinder surface with
312 // two annulus, and two annulus for the hollow ring (i.e. total four annulus for
313 // the outter cylinder surface). We have
314 // Ni = [6, 12, 18, 24]
315 // ^
316 // inner outter
317 const auto nSteps = params.innerRadius / deltaR;
318 size_t Ni = static_cast<size_t>(nSteps) * 6;
319 // loop over annuli
320 for (size_t j = 0; j < numAnnuli; ++j) {
321 Ni += 6;
322 const double R =
323 params.innerRadius +
324 (static_cast<double>(j) * (params.radius - params.innerRadius) / static_cast<double>(numAnnuli)) +
325 (0.5 * deltaR);
326
327 // all the volume elements in the ring/slice are the same
328 const double outerR = R + (deltaR / 2.0);
329 const double innerR = outerR - deltaR;
330 const double elementVolume =
331 M_PI * (outerR * outerR - innerR * innerR) * sliceThickness / static_cast<double>(Ni);
332
333 // loop over elements in current annulus
334 for (size_t k = 0; k < Ni; ++k) {
335 const double phi = 2. * M_PI * static_cast<double>(k) / static_cast<double>(Ni);
336
337 const auto position = center + CalculatePosInCylinder(phi, R, z, coords);
338
339 // check element is within the sample shape
340 if (sampleShape.isValid(position)) {
341 Track incoming(position, -beamDirection);
342 // check there is a valid path from the source to this point within the sample
343 if (sampleShape.interceptSurface(incoming) > 0) {
344 result.l1.emplace_back(incoming.totalDistInsideObject());
345 result.position.emplace_back(position);
346 result.volume.emplace_back(elementVolume);
347 }
348 }
349 } // loop over k
350 } // loop over j
351 } // loop over i
352
353 return result;
354}
355
356} // namespace Rasterize
357} // namespace Mantid::Geometry
double position
Definition GetAllEi.cpp:154
#define fabs(x)
Definition Matrix.cpp:22
IObject : Interface for geometry objects.
Definition IObject.h:42
virtual int interceptSurface(Geometry::Track &) const =0
virtual const detail::ShapeInfo & shapeInfo() const =0
virtual detail::ShapeInfo::GeometryShape shape() const =0
virtual bool isValid(const Kernel::V3D &) const =0
Defines a track as a start point and a direction.
Definition Track.h:165
double totalDistInsideObject() const
Returns the sum of all the links distInsideObject in the track.
Definition Track.cpp:254
CylinderGeometry cylinderGeometry() const
Definition ShapeInfo.cpp:53
HollowCylinderGeometry hollowCylinderGeometry() const
Definition ShapeInfo.cpp:58
Class for 3D vectors.
Definition V3D.h:34
double normalize()
Make a normalized vector (return norm value)
Definition V3D.cpp:129
constexpr V3D cross_prod(const V3D &v) const noexcept
Cross product (this * argument)
Definition V3D.h:284
MANTID_GEOMETRY_DLL Raster calculateHollowCylinder(const Kernel::V3D &beamDirection, const IObject &integShape, const IObject &sampleShape, const size_t numSlices, const size_t numAnnuli)
MANTID_GEOMETRY_DLL Raster calculate(const Kernel::V3D &beamDirection, const IObject &integShape, const IObject &sampleShape, const double cubeSizeInMetre)
MANTID_GEOMETRY_DLL Raster calculateCylinder(const Kernel::V3D &beamDirection, const IObject &integShape, const IObject &sampleShape, const size_t numSlices, const size_t numAnnuli)
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:352
Holds the information used for doing numerical integrations of object in the beam.
Definition Rasterize.h:21
std::vector< double > volume
Cached element volumes.
Definition Rasterize.h:26
std::vector< double > l1
Cached L1 distances.
Definition Rasterize.h:25
void reserve(size_t numVolumeElements)
Definition Rasterize.cpp:18
std::vector< Kernel::V3D > position
Cached element positions.
Definition Rasterize.h:27