Mantid
Loading...
Searching...
No Matches
PanelsSurfaceCalculator.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 +
7
14
15#include <cmath>
16#include <numeric>
17
18using namespace Mantid::Geometry;
19using Mantid::Beamline::ComponentType;
21
22namespace Mantid::API {
33 Mantid::Kernel::V3D &yaxis) const {
34 double R, theta, phi;
35 zaxis.getSpherical(R, theta, phi);
36 if (theta <= 45.0) {
37 xaxis = Mantid::Kernel::V3D(1, 0, 0);
38 } else if (phi <= 45.0) {
39 xaxis = Mantid::Kernel::V3D(0, 1, 0);
40 } else {
41 xaxis = Mantid::Kernel::V3D(0, 0, 1);
42 }
43 yaxis = zaxis.cross_prod(xaxis);
44 yaxis.normalize();
45 xaxis = yaxis.cross_prod(zaxis);
46}
47
55std::vector<Mantid::Kernel::V3D>
57 const size_t rootIndex) const {
58 auto panel = componentInfo.quadrilateralComponent(rootIndex);
59 auto child = rootIndex;
60
61 // Find shapeInfo for one of the children
62 do {
63 const auto &children = componentInfo.children(child);
64 child = children[0];
65 } while (!componentInfo.isDetector(child));
66
67 const auto &shape = componentInfo.shape(child);
68 const auto &shapeInfo = shape.getGeometryHandler()->shapeInfo();
69 const auto &points = shapeInfo.points();
70 double xoff = 0.0;
71 double yoff = 0.0;
72
73 // Find x and y widths of detectors and treat as offsets;
74 for (size_t i = 0; i < points.size() - 1; ++i) {
75 double xdiff = std::abs(points[i + 1].X() - points[i].X());
76 double ydiff = std::abs(points[i + 1].Y() - points[i].Y());
77 if (xdiff != 0)
78 xoff = xdiff * 0.5;
79 if (ydiff != 0)
80 yoff = ydiff * 0.5;
81 }
82
83 std::vector<Mantid::Kernel::V3D> corners{
84 componentInfo.position(panel.bottomLeft), componentInfo.position(panel.bottomRight),
85 componentInfo.position(panel.topRight), componentInfo.position(panel.topLeft)};
86
87 // Find xmin, xmax, ymin and ymax
88 double xmin = corners[0].X();
89 double xmax = corners[0].X();
90 double ymin = corners[0].Y();
91 double ymax = corners[0].Y();
92
93 for (const auto &corner : corners) {
94 xmin = corner.X() < xmin ? corner.X() : xmin;
95 xmax = corner.X() > xmax ? corner.X() : xmax;
96 ymin = corner.Y() < ymin ? corner.Y() : ymin;
97 ymax = corner.Y() > ymax ? corner.Y() : ymax;
98 }
99
100 // apply offsets
101 for (auto &corner : corners) {
102 auto x = corner.X();
103 auto y = corner.Y();
104 corner.setX(x == xmin ? x - xoff : x + xoff);
105 corner.setY(y == ymin ? y - yoff : y + yoff);
106 }
107
108 return corners;
109}
110
118PanelsSurfaceCalculator::calculatePanelNormal(const std::vector<Mantid::Kernel::V3D> &panelCorners) const {
119 // find the normal
120 auto xaxis = panelCorners[1] - panelCorners[0];
121 auto yaxis = panelCorners[3] - panelCorners[0];
122 const auto normal = normalize(xaxis.cross_prod(yaxis));
123 return normal;
124}
125
135bool PanelsSurfaceCalculator::isBankFlat(const ComponentInfo &componentInfo, size_t bankIndex,
136 const std::vector<size_t> &tubes, const Mantid::Kernel::V3D &normal) {
137 for (auto tube : tubes) {
138 const auto &children = componentInfo.children(tube);
139 const auto vector = normalize(componentInfo.position(children[0]) - componentInfo.position(children[1]));
140 if (std::abs(vector.scalar_prod(normal)) > Mantid::Kernel::Tolerance) {
141 this->g_log.warning() << "Assembly " << componentInfo.name(bankIndex) << " isn't flat.\n";
142 return false;
143 }
144 }
145 return true;
146}
147
156 const std::vector<size_t> &tubes) {
157 // calculate normal from first two tubes in bank as before
158 const auto &tube0 = componentInfo.children(tubes[0]);
159 const auto &tube1 = componentInfo.children(tubes[1]);
160 auto pos = componentInfo.position(tube0[0]);
161 auto x = componentInfo.position(tube0[1]) - pos;
162 x.normalize();
163
164 auto y = componentInfo.position(tube1[0]) - pos;
165 y.normalize();
166 auto normal = x.cross_prod(y);
167
168 if (normal.nullVector()) {
169 y = componentInfo.position(tube1[1]) - componentInfo.position(tube1[0]);
170 y.normalize();
171 normal = x.cross_prod(y);
172 }
173
174 normal.normalize();
175
176 if (normal.nullVector())
177 this->g_log.warning() << "Colinear Assembly.\n";
178
179 return normal;
180}
181
189void PanelsSurfaceCalculator::setBankVisited(const ComponentInfo &componentInfo, size_t bankIndex,
190 std::vector<bool> &visitedComponents) const {
191 const auto &children = componentInfo.children(bankIndex);
192 visitedComponents[bankIndex] = true;
193 for (auto child : children) {
194 const auto &subChildren = componentInfo.children(child);
195 if (subChildren.size() > 0)
196 setBankVisited(componentInfo, child, visitedComponents);
197 else
198 visitedComponents[child] = true;
199 }
200}
201
210 const std::vector<size_t> &components) const {
211 return std::accumulate(
212 components.cbegin(), components.cend(), std::size_t{0u},
213 [&componentInfo](size_t lhs, const auto &comp) { return componentInfo.isDetector(comp) ? lhs + 1u : lhs; });
214}
215
228 const V3D &yAxis, const V3D &samplePosition) const {
229 V3D directionToViewer = zAxis;
230 V3D bankToOrigin = samplePosition - detPos;
231 // signed shortest distance from the bank's plane to the origin (m_pos)
232 double a = normal.scalar_prod(bankToOrigin);
233 // if a is negative the origin is on the "back" side of the plane
234 // (the "front" side is facing in the direction of the normal)
235 if (a < 0.0) {
236 // we need to flip the normal to make the side looking at the origin to be
237 // the front one
238 normal *= -1;
239 }
240 double b = zAxis.scalar_prod(bankToOrigin);
241 if (b < 0.0) {
242 // if the bank is at positive z then we need to rotate the normal to point in negative z direction
243 directionToViewer *= -1;
244 }
245 if (directionToViewer == -normal) {
246 return Mantid::Kernel::Quat(0, yAxis.X(), yAxis.Y(), yAxis.Z()); // 180 degree rotation about y axis
247 } else if (normal.cross_prod(directionToViewer).nullVector()) {
248 return Mantid::Kernel::Quat();
249 }
250
251 Mantid::Kernel::Quat requiredRotation;
252 if (normal.cross_prod(yAxis).nullVector()) {
253 requiredRotation = Mantid::Kernel::Quat(normal, directionToViewer);
254 } else {
255 V3D normalInXZPlane = {normal.X(), 0., normal.Z()};
256 normalInXZPlane.normalize();
257 auto rotationLocalX = Mantid::Kernel::Quat(normal, normalInXZPlane);
258 auto rotAboutY180 = Mantid::Kernel::Quat(0, yAxis.X(), yAxis.Y(), yAxis.Z());
259 auto rotationLocalY =
260 normalInXZPlane == -directionToViewer ? rotAboutY180 : Mantid::Kernel::Quat(normalInXZPlane, directionToViewer);
261 requiredRotation = rotationLocalY * rotationLocalX;
262 }
263 return requiredRotation;
264}
265
277std::vector<Mantid::Kernel::V2D>
279 const V3D &refPos, const Mantid::Kernel::Quat &rotation,
280 const V3D &xaxis, const V3D &yaxis) const {
281 auto bb = componentInfo.boundingBox(detectorIndex);
282 auto bbMinPoint = bb.minPoint() - refPos;
283 auto bbMaxPoint = bb.maxPoint() - refPos;
284 rotation.rotate(bbMinPoint);
285 rotation.rotate(bbMaxPoint);
286 bbMinPoint += refPos;
287 bbMaxPoint += refPos;
288 Mantid::Kernel::V2D bb0(xaxis.scalar_prod(bbMinPoint), yaxis.scalar_prod(bbMinPoint));
289 Mantid::Kernel::V2D bb1(xaxis.scalar_prod(bbMaxPoint), yaxis.scalar_prod(bbMaxPoint));
290 return {bb0, bb1};
291}
292
300std::vector<std::vector<size_t>> PanelsSurfaceCalculator::examineAllComponents(
301 const ComponentInfo &componentInfo,
302 std::function<std::vector<size_t>(const ComponentInfo &, size_t, std::vector<bool> &)> operation) {
303 std::vector<bool> visited(componentInfo.size(), false);
304 std::vector<std::vector<size_t>> detectorIDs;
305
306 for (int64_t i = static_cast<int64_t>(componentInfo.root() - 1); i > 0; --i) {
307 auto children = componentInfo.children(i);
308
309 if (children.size() > 0 && !visited[i]) {
310 visited[i] = true;
311 detectorIDs.push_back(operation(componentInfo, i, visited));
312 } else if (children.size() == 0 && componentInfo.parent(i) == componentInfo.root()) {
313 visited[i] = true;
314 }
315 }
316 return detectorIDs;
317}
318
327std::vector<size_t> PanelsSurfaceCalculator::tubeDetectorParentIDs(const ComponentInfo &componentInfo, size_t rootIndex,
328 std::vector<bool> &visited) {
329 const auto componentType = componentInfo.componentType(rootIndex);
330 if (componentType != ComponentType::OutlineComposite)
331 return {};
332
333 const auto bankIndex0 = componentInfo.parent(rootIndex);
334 auto tubes = std::vector<size_t>();
335 bool foundFlatBank = false;
336 V3D normal;
337 size_t bankIndex;
338 auto addTubes = [&componentInfo](size_t parentIndex, std::vector<size_t> &tubes) {
339 const auto &children = componentInfo.children(parentIndex);
340 for (auto child : children) {
341 if (componentInfo.componentType(child) == ComponentType::OutlineComposite)
342 // tube must have more than one detector to enable normal to be calculated
343 if (componentInfo.children(child).size() > 1)
344 tubes.emplace_back(child);
345 }
346 };
347
348 // The main use case for this method has an assembly containing a set of
349 // individual assemblies each of which has a single tube but together
350 // these tubes make a flat structure.
351 // Try grandparent of the tube supplied tube initially
352 if (componentInfo.hasParent(bankIndex0)) {
353 bankIndex = componentInfo.parent(bankIndex0);
354 const auto &bankChildren = componentInfo.children(bankIndex);
355
356 // Go down the tree to find all the tubes.
357 for (const auto index : bankChildren)
358 addTubes(index, tubes);
359 if (tubes.empty()) {
360 this->setBankVisited(componentInfo, bankIndex, visited);
361 return tubes;
362 }
363 // Now we found all the tubes that may form a flat struture.
364 // Use two of the tubes to calculate the normal to the plain of that structure
365 normal = tubes.size() > 1 ? this->calculateBankNormal(componentInfo, tubes) : V3D();
366 // If some of the tubes are not perpendicular to the normal the structure
367 // isn't flat
368 if (!normal.nullVector() && this->isBankFlat(componentInfo, bankIndex, tubes, normal))
369 foundFlatBank = true;
370 }
371
372 if (!foundFlatBank) {
373 // Try the next level down - parent of tube supplied
374 tubes.clear();
375 bankIndex = bankIndex0;
376 addTubes(bankIndex, tubes);
377 normal = tubes.size() > 1 ? this->calculateBankNormal(componentInfo, tubes) : V3D();
378 if (normal.nullVector() || !this->isBankFlat(componentInfo, bankIndex, tubes, normal))
379 this->setBankVisited(componentInfo, componentInfo.parent(rootIndex), visited);
380 }
381
382 this->setBankVisited(componentInfo, bankIndex, visited);
383 return tubes;
384}
385
393std::optional<Kernel::V2D> PanelsSurfaceCalculator::getSideBySideViewPos(const ComponentInfo &componentInfo,
394 const size_t componentIndex) const {
395 const auto pos = componentInfo.sideBySideViewPosition(componentIndex);
396 if (pos.X() == EMPTY_DBL())
397 return std::nullopt;
398 return pos;
399}
400
401} // namespace Mantid::API
std::map< DeltaEMode::Type, std::string > index
IntArray detectorIndex
Mantid::Kernel::Quat(ComponentInfo::* rotation)(const size_t) const
bool isBankFlat(const ComponentInfo &componentInfo, size_t bankIndex, const std::vector< size_t > &tubes, const V3D &normal)
Do all the detectors lie in a plane?
std::vector< V3D > retrievePanelCorners(const ComponentInfo &componentInfo, const size_t rootIndex) const
Returns the four corners of the specified panel.
V3D calculateBankNormal(const ComponentInfo &componentInfo, const std::vector< size_t > &tubes)
Calculate the normal vector of a bank of detectors.
void setupBasisAxes(const V3D &zaxis, V3D &xaxis, V3D &yaxis) const
Given the z axis, define the x and y ones.
std::vector< std::vector< size_t > > examineAllComponents(const ComponentInfo &componentInfo, std::function< std::vector< size_t >(const ComponentInfo &, size_t, std::vector< bool > &)> operation)
Perform a specified operation on all the components.
std::optional< Kernel::V2D > getSideBySideViewPos(const ComponentInfo &componentInfo, const size_t componentIndex) const
Gives the specified side-by-side view position from the IDF.
size_t findNumDetectors(const ComponentInfo &componentInfo, const std::vector< size_t > &components) const
How many detectors are there in the given list of component indices?
Mantid::Kernel::Quat calcBankRotation(const V3D &detPos, V3D normal, const V3D &zAxis, const V3D &yAxis, const V3D &samplePosition) const
Calculate the rotation needed around the bank's local x and y axes to place a bank on the projection ...
V3D calculatePanelNormal(const std::vector< V3D > &panelCorners) const
Calculate the normal vector to a panel.
void setBankVisited(const ComponentInfo &componentInfo, size_t bankIndex, std::vector< bool > &visitedComponents) const
Recursively set all detectors and subcomponents of a bank as visited.
std::vector< size_t > tubeDetectorParentIDs(const ComponentInfo &componentInfo, size_t rootIndex, std::vector< bool > &visited)
Parent indices of tubes.
std::vector< Mantid::Kernel::V2D > transformedBoundingBoxPoints(const ComponentInfo &componentInfo, size_t detectorIndex, const V3D &refPos, const Mantid::Kernel::Quat &rotation, const V3D &xaxis, const V3D &yaxis) const
Transforms bounding box of a detector.
const Kernel::V3D & minPoint() const
Returns the min point of the box.
Definition BoundingBox.h:89
ComponentInfo : Provides a component centric view on to the instrument.
bool hasParent(const size_t componentIndex) const
size_t parent(const size_t componentIndex) const
BoundingBox boundingBox(const size_t componentIndex, const BoundingBox *reference=nullptr, const bool excludeMonitors=false) const
Compute the bounding box for the component with componentIndex taking into account all sub components...
Kernel::V3D position(const size_t componentIndex) const
const std::vector< size_t > & children(size_t componentIndex) const
Kernel::V2D sideBySideViewPosition(const size_t componentIndex) const
bool isDetector(const size_t componentIndex) const
const std::string & name(const size_t componentIndex) const
const Geometry::IObject & shape(const size_t componentIndex) const
QuadrilateralComponent quadrilateralComponent(const size_t componentIndex) const
Beamline::ComponentType componentType(const size_t componentIndex) const
virtual std::shared_ptr< GeometryHandler > getGeometryHandler() const =0
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
Class for quaternions.
Definition Quat.h:39
void rotate(V3D &) const
Rotate a vector.
Definition Quat.cpp:397
Implements a 2-dimensional vector embedded in a 3D space, i.e.
Definition V2D.h:29
Class for 3D vectors.
Definition V3D.h:34
std::size_t size() const noexcept
Number of components in V3D.
Definition V3D.h:49
constexpr double scalar_prod(const V3D &v) const noexcept
Calculates the cross product.
Definition V3D.h:280
constexpr double X() const noexcept
Get x.
Definition V3D.h:238
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
constexpr double Y() const noexcept
Get y.
Definition V3D.h:239
void getSpherical(double &R, double &theta, double &phi) const noexcept
Return the vector's position in spherical coordinates.
Definition V3D.cpp:116
constexpr double Z() const noexcept
Get z.
Definition V3D.h:240
bool nullVector(const double tolerance=1e-3) const noexcept
Determine if the point is null.
Definition V3D.cpp:238
constexpr double Tolerance
Standard tolerance value.
Definition Tolerance.h:12
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:352
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42