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