Mantid
Loading...
Searching...
No Matches
FindSXPeaksHelper.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 +
12#include "MantidKernel/Logger.h"
14#include "MantidKernel/Unit.h"
16
17#include "MantidTypes/SpectrumDefinition.h"
18
19#define BOOST_ALLOW_DEPRECATED_HEADERS
20#include <boost/graph/adjacency_list.hpp>
21#undef BOOST_ALLOW_DEPRECATED_HEADERS
22
23#include <boost/graph/connected_components.hpp>
24#include <cmath>
25
26namespace {
27
28const double TWO_PI = 2 * M_PI;
29
30bool isDifferenceLargerThanTolerance(const double angle1, const double angle2, const double tolerance) {
31 auto difference = std::abs(angle1 - angle2);
32
33 // If we have more than 360 degree angle difference then we need to wrap it
34 // back to 360
35 if (difference > TWO_PI) {
36 difference = std::fmod(difference, TWO_PI);
37 }
38
39 // If we have more than 180 degrees then we must take the smaller angle
40 if (difference > M_PI) {
41 difference = TWO_PI - difference;
42 }
43
44 return difference > tolerance;
45}
46} // namespace
47
48using namespace boost;
50
52
53Mantid::Kernel::Logger g_log("FindSXPeaksHelper");
54
55/* ------------------------------------------------------------------------------------------
56 * Single Crystal peak representation
57 * ------------------------------------------------------------------------------------------
58 */
59
69SXPeak::SXPeak(double t, double phi, double intensity, const std::vector<int> &spectral, const size_t wsIndex,
70 const API::SpectrumInfo &spectrumInfo)
71 : m_tof(t), m_phi(phi), m_intensity(intensity), m_spectra(spectral), m_wsIndex(wsIndex) {
72 // Sanity checks
73 if (intensity < 0) {
74 throw std::invalid_argument("SXPeak: Cannot have an intensity < 0");
75 }
76 if (spectral.empty()) {
77 throw std::invalid_argument("SXPeak: Cannot have zero sized spectral list");
78 }
79 if (!spectrumInfo.hasDetectors(m_wsIndex)) {
80 throw std::invalid_argument("SXPeak: Spectrum at ws index " + std::to_string(wsIndex) + " doesn't have detectors");
81 }
82
83 const auto l1 = spectrumInfo.l1();
84 const auto l2 = spectrumInfo.l2(m_wsIndex);
85
86 m_twoTheta = spectrumInfo.twoTheta(m_wsIndex);
87 m_LTotal = l1 + l2;
88 if (m_LTotal < 0) {
89 throw std::invalid_argument("SXPeak: Cannot have detector distance < 0");
90 }
91 m_detId = spectrumInfo.detector(m_wsIndex).getID();
92 m_nPixels = 1;
93
95 const auto unit = Mantid::Kernel::UnitFactory::Instance().create("dSpacing");
97 spectrumInfo.getDetectorValues(tof, *unit, Kernel::DeltaEMode::Elastic, false, m_wsIndex, pmap);
98 unit->initialize(l1, 0, pmap);
99 try {
100 m_dSpacing = unit->singleFromTOF(m_tof);
101 } catch (std::exception &) {
102 m_dSpacing = 0;
103 }
104
105 const auto samplePos = spectrumInfo.samplePosition();
106 const auto sourcePos = spectrumInfo.sourcePosition();
107 const auto detPos = spectrumInfo.position(m_wsIndex);
108 // Normalized beam direction
109 const auto beamDir = normalize(samplePos - sourcePos);
110 // Normalized detector direction
111 const auto detDir = normalize(detPos - samplePos);
112 m_unitWaveVector = beamDir - detDir;
113 m_qConvention = Kernel::ConfigService::Instance().getString("Q.convention");
114}
115
121bool SXPeak::compare(const SXPeak &rhs, double tolerance) const {
123 return false;
125 return false;
126 if (!Kernel::withinRelativeDifference(m_twoTheta / m_nPixels, rhs.m_twoTheta / rhs.m_nPixels, tolerance))
127 return false;
128 return true;
129}
130
131bool SXPeak::compare(const SXPeak &rhs, const double xTolerance, const double phiTolerance, const double thetaTolerance,
132 const XAxisUnit units) const {
133
134 const auto x_1 = (units == XAxisUnit::TOF) ? m_tof : m_dSpacing;
135 const auto x_2 = (units == XAxisUnit::TOF) ? rhs.m_tof : rhs.m_dSpacing;
136
137 if (std::abs(x_1 - x_2) > xTolerance) {
138 return false;
139 }
140
141 if (isDifferenceLargerThanTolerance(m_phi, rhs.m_phi, phiTolerance)) {
142 return false;
143 }
144 if (isDifferenceLargerThanTolerance(m_twoTheta, rhs.m_twoTheta, thetaTolerance)) {
145 return false;
146 }
147 return true;
148}
149
155 double qSign = 1.0;
156 if (m_qConvention == "Crystallography") {
157 qSign = -1.0;
158 }
159 double vi = m_LTotal / (m_tof * 1e-6);
160 // wavenumber = h_bar / mv
162 // in angstroms
163 wi *= 1e10;
164 // wavevector=1/wavenumber = 2pi/wavelength
165 double wvi = 1.0 / wi;
166 // Now calculate the wavevector of the scattered neutron
167 return m_unitWaveVector * wvi * qSign;
168}
169
175 m_tof += rhs.m_tof;
176 m_phi += rhs.m_phi;
177 m_twoTheta += rhs.m_twoTheta;
178 m_intensity += rhs.m_intensity;
179 m_LTotal += rhs.m_LTotal;
180 m_nPixels += 1;
181 m_spectra.insert(m_spectra.end(), rhs.m_spectra.cbegin(), rhs.m_spectra.cend());
182 return *this;
183}
184
187 m_tof /= m_nPixels;
188 m_phi /= m_nPixels;
192 m_nPixels = 1;
193}
194
198const double &SXPeak::getIntensity() const { return m_intensity; }
199
204
208const std::vector<int> &SXPeak::getPeakSpectras() const { return m_spectra; }
209
210PeakContainer::PeakContainer(const HistogramData::HistogramY &y)
211 : m_y(y), m_startIndex(0), m_stopIndex(m_y.size() - 1), m_maxIndex(0) {}
212
214 m_startIndex = std::distance(m_y.begin(), item);
216 m_maxSignal = *item;
217}
218
220 if (*item > m_maxSignal) {
221 m_maxIndex = std::distance(m_y.begin(), item);
222 m_maxSignal = *item;
223 }
224}
225
227 if (*item > m_maxSignal) {
228 m_maxIndex = std::distance(m_y.begin(), item);
229 m_maxSignal = *item;
230 }
231 m_stopIndex = std::distance(m_y.begin(), item);
232
233 // Peak end is one back though
234 --m_stopIndex;
235}
236
238 // If we didn't record anything then the start iterator is at the end
239 if (m_startIndex >= m_y.size()) {
240 return 0;
241 }
242 if (m_stopIndex >= m_startIndex) {
243 return m_stopIndex - m_startIndex + 1;
244 }
245 return 0;
246}
247
248yIt PeakContainer::getMaxIterator() const { return m_y.begin() + m_maxIndex; }
249
251
252/* ------------------------------------------------------------------------------------------
253 * Background
254 * ------------------------------------------------------------------------------------------
255 */
256AbsoluteBackgroundStrategy::AbsoluteBackgroundStrategy(const double background) : m_background(background) {}
257
259 const HistogramData::HistogramY & /*y*/) const {
260 return intensity < m_background;
261}
262
264 : m_backgroundMultiplier(backgroundMultiplier) {}
265
267 const HistogramData::HistogramY &y) const {
268 auto background = 0.5 * (1.0 + y.front() + y.back());
269 background *= m_backgroundMultiplier;
270 return intensity < background;
271}
272
273/* ------------------------------------------------------------------------------------------
274 * Peak Finding Strategy
275 * ------------------------------------------------------------------------------------------
276 */
277
279 const API::SpectrumInfo &spectrumInfo, const double minValue,
280 const double maxValue, const XAxisUnit units)
281 : m_backgroundStrategy(backgroundStrategy), m_minValue(minValue), m_maxValue(maxValue),
282 m_spectrumInfo(spectrumInfo), m_units(units) {}
283
284PeakList PeakFindingStrategy::findSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
285 const HistogramData::HistogramE &e, const int workspaceIndex) const {
286 // ---------------------------------------
287 // Get the lower and upper bound iterators
288 // ---------------------------------------
289 auto boundsIterator = getBounds(x);
290 auto lowit = boundsIterator.first;
291 auto highit = boundsIterator.second;
292
293 // If range specified doesn't overlap with this spectrum then bail out
294 if (lowit == x.end() || highit == x.begin()) {
295 return PeakList();
296 }
297
298 // Upper limit is the bin before, i.e. the last value smaller than MaxRange
299 --highit;
300
301 // ---------------------------------------
302 // Perform the search of the peaks
303 // ---------------------------------------
304 return dofindSXPeaks(x, y, e, lowit, highit, workspaceIndex);
305}
306
307void PeakFindingStrategy::setMinNBinsPerPeak(int minNBinsPerPeak) { m_minNBinsPerPeak = minNBinsPerPeak; }
308
309void PeakFindingStrategy::filterPeaksForMinBins(std::vector<std::unique_ptr<PeakContainer>> &inputPeakList) const {
310 if (m_minNBinsPerPeak == EMPTY_INT() || inputPeakList.empty()) {
311 return;
312 }
313
314 for (auto inputIt = inputPeakList.begin(); inputIt != inputPeakList.end();) {
315 if (static_cast<int>((*inputIt)->getNumberOfPointsInPeak()) < m_minNBinsPerPeak) {
316 inputIt = inputPeakList.erase(inputIt);
317 } else {
318 ++inputIt;
319 }
320 }
321}
322
323BoundsIterator PeakFindingStrategy::getBounds(const HistogramData::HistogramX &x) const {
324 // Find the range [min,max]
325 auto lowit = (m_minValue == EMPTY_DBL()) ? x.begin() : std::lower_bound(x.begin(), x.end(), m_minValue);
326 using std::placeholders::_1;
327 auto highit = (m_maxValue == EMPTY_DBL())
328 ? x.end()
329 : std::find_if(lowit, x.end(), std::bind(std::greater<double>(), _1, m_maxValue));
330
331 return std::make_pair(lowit, highit);
332}
333
341double PeakFindingStrategy::calculatePhi(size_t workspaceIndex) const {
342 double phi;
343
344 // Get the detectors for the workspace index
345 const auto &spectrumDefinition = m_spectrumInfo.spectrumDefinition(workspaceIndex);
346 const auto numberOfDetectors = spectrumDefinition.size();
347 const auto &det = m_spectrumInfo.detector(workspaceIndex);
348 if (numberOfDetectors == 1) {
349 phi = det.getPhi();
350 } else {
351 // Have to average the value for phi
352 auto detectorGroup = dynamic_cast<const Mantid::Geometry::DetectorGroup *>(&det);
353 if (!detectorGroup) {
354 throw std::runtime_error("Could not cast to detector group");
355 }
356 phi = detectorGroup->getPhi();
357 }
358
359 if (phi < 0) {
360 phi += 2.0 * M_PI;
361 }
362 return phi;
363}
364
365double PeakFindingStrategy::getXValue(const HistogramData::HistogramX &x, const size_t peakLocation) const {
366 auto leftBinPosition = x.begin() + peakLocation;
367 const double leftBinEdge = *leftBinPosition;
368 const double rightBinEdge = *std::next(leftBinPosition);
369 return 0.5 * (leftBinEdge + rightBinEdge);
370}
371
372double PeakFindingStrategy::convertToTOF(const double xValue, const size_t workspaceIndex) const {
373 if (m_units == XAxisUnit::TOF) {
374 // we're already using TOF units
375 return xValue;
376 } else {
377 const auto unit = UnitFactory::Instance().create("dSpacing");
380 m_spectrumInfo.getDetectorValues(*unit, tof, Kernel::DeltaEMode::Elastic, false, workspaceIndex, pmap);
381 // we're using d-spacing, convert the point to TOF
382 unit->initialize(m_spectrumInfo.l1(), 0, pmap);
383 return unit->singleToTOF(xValue);
384 }
385}
386
387PeakList PeakFindingStrategy::convertToSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
388 const std::vector<std::unique_ptr<PeakContainer>> &foundPeaks,
389 const int workspaceIndex) const {
390 PeakList peaks;
391
392 if (foundPeaks.empty()) {
393 return peaks;
394 }
395
396 // Add a vector to the std::optional
397 peaks = std::vector<FindSXPeaksHelper::SXPeak>();
398
399 for (const auto &peak : foundPeaks) {
400 // Get the index of the bin
401 auto maxY = peak->getMaxIterator();
402 const auto distance = std::distance(y.begin(), maxY);
403 const auto xValue = getXValue(x, distance);
404 const auto tof = convertToTOF(xValue, workspaceIndex);
405 const double phi = calculatePhi(workspaceIndex);
406
407 std::vector<int> specs(1, workspaceIndex);
408 (*peaks).emplace_back(tof, phi, *maxY, specs, workspaceIndex, m_spectrumInfo);
409 }
410
411 return peaks;
412}
413
415 const API::SpectrumInfo &spectrumInfo, const double minValue,
416 const double maxValue, const XAxisUnit units)
417 : PeakFindingStrategy(backgroundStrategy, spectrumInfo, minValue, maxValue, units) {}
418
419PeakList StrongestPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
420 [[maybe_unused]] const HistogramData::HistogramE &e, Bound low,
421 Bound high, const int workspaceIndex) const {
422 auto distmin = std::distance(x.begin(), low);
423 auto distmax = std::distance(x.begin(), high);
424
425 // Find the max element
426 auto maxY = (y.size() > 1) ? std::max_element(y.begin() + distmin, y.begin() + distmax) : y.begin();
427
428 // Perform a check against the background
429 double intensity = (*maxY);
430 if (m_backgroundStrategy->isBelowBackground(intensity, y)) {
431 return PeakList();
432 }
433
434 // Create the SXPeak information
435 const auto distance = std::distance(y.begin(), maxY);
436 const auto xValue = getXValue(x, distance);
437 const auto tof = convertToTOF(xValue, workspaceIndex);
438 const double phi = calculatePhi(workspaceIndex);
439
440 std::vector<int> specs(1, workspaceIndex);
441 std::vector<SXPeak> peaks;
442 peaks.emplace_back(tof, phi, *maxY, specs, workspaceIndex, m_spectrumInfo);
443 return peaks;
444}
445
446AllPeaksStrategy::AllPeaksStrategy(const BackgroundStrategy *backgroundStrategy, const API::SpectrumInfo &spectrumInfo,
447 const double minValue, const double maxValue, const XAxisUnit units)
448 : PeakFindingStrategy(backgroundStrategy, spectrumInfo, minValue, maxValue, units) {
449 // We only allow the AbsoluteBackgroundStrategy for now
450 if (!dynamic_cast<const AbsoluteBackgroundStrategy *>(m_backgroundStrategy)) {
451 throw std::invalid_argument("The AllPeaksStrategy has to be initialized "
452 "with the AbsoluteBackgroundStrategy.");
453 }
454}
455
456PeakList AllPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
457 [[maybe_unused]] const HistogramData::HistogramE &e, Bound low, Bound high,
458 const int workspaceIndex) const {
459 // Get all peaks from the container
460 auto foundPeaks = getAllPeaks(x, y, low, high, m_backgroundStrategy);
461
462 // Filter the found peaks having the mininum number of bins
463 filterPeaksForMinBins(foundPeaks);
464
465 // Convert the found peaks to SXPeaks
466 auto peaks = convertToSXPeaks(x, y, foundPeaks, workspaceIndex);
467
468 return peaks;
469}
470
471std::vector<std::unique_ptr<PeakContainer>>
472AllPeaksStrategy::getAllPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, Bound low,
473 Bound high,
474 const Mantid::Crystal::FindSXPeaksHelper::BackgroundStrategy *backgroundStrategy) const {
475 // We iterate over the data and only consider data which is above the
476 // threshold.
477 // Once data starts to be above the threshold we start to record it and add it
478 // to a peak. Once it falls below, it concludes recording of that particular
479 // peak
480 bool isRecording = false;
481
482 std::unique_ptr<PeakContainer> currentPeak = nullptr;
483 std::vector<std::unique_ptr<PeakContainer>> peaks;
484
485 // We want to the upper boundary to be inclusive hence we need to increment it
486 // by one
487 if (high != x.end()) {
488 ++high;
489 }
490 auto distanceMin = std::distance(x.begin(), low);
491 auto distanceMax = std::distance(x.begin(), high);
492
493 const auto lowY = y.begin() + distanceMin;
494 auto highY = distanceMax < static_cast<int>(y.size()) ? y.begin() + distanceMax : y.end();
495
496 for (auto it = lowY; it != highY; ++it) {
497 const auto signal = *it;
498 const auto isAboveThreshold = !backgroundStrategy->isBelowBackground(signal, y);
499
500 // There are four scenarios:
501 // 1. Not recording + below threshold => continue
502 // 2. Not recording + above treshold => start recording
503 // 3. Recording + below threshold => stop recording
504 // 4. Recording + above threshold => continue recording
505 if (!isRecording && (!std::isfinite(signal) || !isAboveThreshold)) {
506 continue;
507 } else if (!isRecording && isAboveThreshold && std::isfinite(signal)) {
508 // only start recording if is finite as NaN values will be found to be above threshold
509 currentPeak = std::make_unique<PeakContainer>(y);
510 currentPeak->startRecord(it);
511 isRecording = true;
512 } else if (isRecording && !isAboveThreshold) {
513 currentPeak->stopRecord(it);
514 peaks.emplace_back(std::move(currentPeak));
515 currentPeak = nullptr;
516 isRecording = false;
517 } else {
518 // this will continue to record NaN if previous point was above the background
519 currentPeak->record(it);
520 }
521 }
522
523 // Handle a peak on the edge if it exists
524 if (isRecording) {
525 if (highY == y.end()) {
526 --highY;
527 }
528 currentPeak->stopRecord(highY);
529 peaks.emplace_back(std::move(currentPeak));
530 currentPeak = nullptr;
531 }
532
533 return peaks;
534}
535
536NSigmaPeaksStrategy::NSigmaPeaksStrategy(const API::SpectrumInfo &spectrumInfo, const double nsigma,
537 const double minValue, const double maxValue, const XAxisUnit units)
538 : PeakFindingStrategy(nullptr, spectrumInfo, minValue, maxValue, units), m_nsigma(nsigma) {}
539
540PeakList NSigmaPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
541 const HistogramData::HistogramE &e, Bound low, Bound high,
542 const int workspaceIndex) const {
543 auto nsigmaPeaks = getAllNSigmaPeaks(x, y, e, low, high);
544
545 // Filter the found peaks having the mininum number of bins
546 filterPeaksForMinBins(nsigmaPeaks);
547
548 auto sxPeaks = convertToSXPeaks(x, y, nsigmaPeaks, workspaceIndex);
549 return sxPeaks;
550}
551
552std::vector<std::unique_ptr<PeakContainer>> NSigmaPeaksStrategy::getAllNSigmaPeaks(const HistogramData::HistogramX &x,
553 const HistogramData::HistogramY &y,
554 const HistogramData::HistogramE &e,
555 Bound low, Bound high) const {
556 /*
557 Credits to the author of SXD2001 for the idea of using NSigma as a threshold for peak finding:
558 Gutmann, M. J. (2005). SXD2001. ISIS Facility, Rutherford Appleton Laboratory, Oxfordshire, England.
559 */
560
561 bool isRecording = false;
562 std::unique_ptr<PeakContainer> currentPeak = nullptr;
563 std::vector<std::unique_ptr<PeakContainer>> peaks;
564
565 // We want to the upper boundary to be inclusive hence we need to increment by one
566 if (high != x.end()) {
567 ++high;
568 }
569 auto distanceMin = std::distance(x.begin(), low);
570 auto distanceMax = std::distance(x.begin(), high);
571
572 const auto lowY = y.begin() + distanceMin;
573 auto highY = distanceMax < static_cast<int>(y.size()) ? y.begin() + distanceMax : y.end();
574
575 const auto lowE = e.begin() + distanceMin;
576 const auto highE = distanceMax < static_cast<int>(e.size()) ? e.begin() + distanceMax : e.begin();
577
578 auto yIt = lowY + 1;
579 auto eIt = lowE + 1;
580
581 for (; yIt != highY && eIt != highE; ++yIt, ++eIt) {
582 const auto signalDiff = *yIt - *(yIt - 1);
583 const auto isStartPeak = signalDiff > (m_nsigma * (*eIt)) + NSIGMA_COMPARISON_THRESHOLD;
584 const auto isSigDropSignificant = (signalDiff * (-1.)) > (m_nsigma * (*eIt)) + NSIGMA_COMPARISON_THRESHOLD;
585 const auto isEndPeak = (currentPeak != nullptr ? (isSigDropSignificant || currentPeak->getStartingSignal() > *yIt)
586 : isSigDropSignificant);
587
588 /*Possible scenarios
589 1. isRecording is False and isStartPeak True(== isEndPeak is False) => start recording
590 2. isRecording is True and isEndPeak False - continue recording
591 3. isRecording is True and isEndpeak is True(== isStartpeak is False) - end peak
592 4. else - continue
593 */
594 if (!isRecording && isStartPeak) {
595 currentPeak = std::make_unique<PeakContainer>(y);
596 currentPeak->startRecord(yIt);
597 isRecording = true;
598 } else if (isRecording && !isEndPeak) {
599 currentPeak->record(yIt);
600 } else if (isRecording && isEndPeak) {
601 currentPeak->stopRecord(yIt);
602 peaks.emplace_back(std::move(currentPeak));
603 currentPeak = nullptr;
604 isRecording = false;
605 } else {
606 continue;
607 }
608 }
609
610 // Handle a peak on the edge if it exists
611 if (isRecording) {
612 if (highY == y.end()) {
613 --highY;
614 }
615 currentPeak->stopRecord(highY);
616 peaks.emplace_back(std::move(currentPeak));
617 currentPeak = nullptr;
618 }
619
620 return peaks;
621}
622/* ------------------------------------------------------------------------------------------
623 * PeakList Reduction Strategy
624 * ------------------------------------------------------------------------------------------
625 */
626
628 : m_compareStrategy(compareStrategy) {}
629
631 m_minNSpectraPerPeak = minNSpectraPerPeak;
632}
633
635 m_maxNSpectraPerPeak = maxSpectrasForPeak;
636}
637
640
641std::vector<SXPeak> SimpleReduceStrategy::reduce(const std::vector<SXPeak> &peaks,
642 Mantid::Kernel::ProgressBase & /*progress*/) const {
643 // If the peaks are empty then do nothing
644 if (peaks.empty()) {
645 return peaks;
646 }
647
648 std::vector<SXPeak> finalPeaks;
649 for (const auto &currentPeak : peaks) {
650 auto pos = std::find_if(finalPeaks.begin(), finalPeaks.end(), [&currentPeak, this](SXPeak &peak) {
651 auto result = this->m_compareStrategy->compare(currentPeak, peak);
652 // bool result = currentPeak.compare(peak,
653 // resolution);
654 if (result)
655 peak += currentPeak;
656 return result;
657 });
658 if (pos == finalPeaks.end()) {
659 finalPeaks.emplace_back(currentPeak);
660 }
661 }
662
664
665 return finalPeaks;
666}
667
668void SimpleReduceStrategy::reducePeaksFromNumberOfSpectras(std::vector<SXPeak> &inputPeaks) const {
670 return;
671 }
672
673 for (auto peakIt = inputPeaks.begin(); peakIt != inputPeaks.end();) {
674 if (((m_minNSpectraPerPeak != EMPTY_INT()) &&
675 (static_cast<int>((*peakIt).getPeakSpectras().size()) < m_minNSpectraPerPeak)) ||
677 (static_cast<int>((*peakIt).getPeakSpectras().size()) > m_maxNSpectraPerPeak))) {
678 peakIt = inputPeaks.erase(peakIt);
679 } else {
680 ++peakIt;
681 }
682 }
683}
684
687
688std::vector<SXPeak> FindMaxReduceStrategy::reduce(const std::vector<SXPeak> &peaks,
689 Mantid::Kernel::ProgressBase &progress) const {
690 // If the peaks are empty then do nothing
691 if (peaks.empty()) {
692 return peaks;
693 }
694
695 // Groups the peaks into elements which are considered alike
696 auto peakGroups = getPeakGroups(peaks, progress);
697
698 // Now reduce the peaks groups
699 return getFinalPeaks(peakGroups);
700}
701
702// Define some graph elements
703using PeakGraph = adjacency_list<vecS, vecS, undirectedS, SXPeak *>;
704using Vertex = boost::graph_traits<PeakGraph>::vertex_descriptor;
705using Edge = boost::graph_traits<PeakGraph>::edge_descriptor;
706
707std::vector<std::vector<SXPeak *>> FindMaxReduceStrategy::getPeakGroups(const std::vector<SXPeak> &peakList,
708 Mantid::Kernel::ProgressBase &progress) const {
709
710 // Create a vector of addresses. Note that the peaks live on the stack. This
711 // here only works, because the peaks are always in a stack frame below.
712 std::vector<SXPeak *> peaks;
713 peaks.reserve(peakList.size());
714 std::transform(peakList.cbegin(), peakList.cend(), std::back_inserter(peaks),
715 [](const auto &peak) { return &const_cast<SXPeak &>(peak); });
716
717 // Add the peaks to a graph
718 Edge edge;
719 PeakGraph graph;
720 PeakGraph::vertex_iterator vertexIt, vertexEnd;
721
722 // Provide a warning if there are more than 500 peaks found.
723 const size_t numberOfPeaksFound = peaks.size();
724 if (numberOfPeaksFound > 500) {
725 std::string warningMessage = std::string("There are ") + std::to_string(numberOfPeaksFound) +
726 std::string(" peaks being processed. This might take a long time. "
727 "Please check that the cutoff of the background that "
728 "you have selected is high enough, else the algorithm will"
729 " mistake background noise for peaks. The instrument view "
730 "allows you to easily inspect the typical background level.");
731 g_log.warning(warningMessage);
732 }
733
734 std::string message = std::string("There are ") + std::to_string(numberOfPeaksFound) +
735 std::string(" peaks. Investigating peak number ");
736 int peakCounter = 0;
737
738 for (auto peak : peaks) {
739 ++peakCounter;
740
741 // 1. Add the vertex
742 auto vertex = add_vertex(peak, graph);
743
744 // 2. Iterate over all elements already in the graph and check if they need
745 // to edstablish an edge between them.
746 std::tie(vertexIt, vertexEnd) = vertices(graph);
747
748 // Provide a progress report such that users can escape the graph generation
749 if (peakCounter > 50) {
750 progress.doReport(message + std::to_string(peakCounter));
751 }
752
753 for (; vertexIt != vertexEnd; ++vertexIt) {
754 // 2.1 Check if we are looking at the new vertex itself. We don't want
755 // self-loops
756 if (vertex == *vertexIt) {
757 continue;
758 }
759
760 // 2.2 Check if the edge exists already
761 if (boost::edge(vertex, *vertexIt, graph).second) {
762 continue;
763 }
764
765 // 2.3 Check if the two vertices should have an edge
766 const auto toCheck = graph[*vertexIt];
767 if (m_compareStrategy->compare(*peak, *toCheck)) {
768 // We need to create an edge
769 add_edge(vertex, *vertexIt, graph);
770 }
771 }
772 }
773
774 // Create disjoined graphs from graph above
775 std::vector<int> components(boost::num_vertices(graph));
776 const int numberOfPeaks = connected_components(graph, &components[0]);
777
778 std::vector<std::vector<SXPeak *>> peakGroups(numberOfPeaks);
779
780 for (auto i = 0u; i < components.size(); ++i) {
781 auto index = components[i];
782 peakGroups[index].emplace_back(graph[i]);
783 }
784
785 return peakGroups;
786}
787
788std::vector<SXPeak> FindMaxReduceStrategy::getFinalPeaks(const std::vector<std::vector<SXPeak *>> &peakGroups) const {
789 std::vector<SXPeak> peaks;
790 // For each peak groupf find one peak
791 // Currently we select the peak with the largest signal (this strategy could
792 // be changed to something like a weighted mean or similar)
793 for (const auto &group : peakGroups) {
794 // When MinNSpectraPerPeak or maxNSpectraPerPeak parameters are provided,
795 // a group will be ignored if it does not satisfy the minimum or the maximum number of spectrums
796 // required to identify as a peak.
797 if ((m_minNSpectraPerPeak != EMPTY_INT() && static_cast<int>(group.size()) < m_minNSpectraPerPeak) ||
798 (m_maxNSpectraPerPeak != EMPTY_INT() && static_cast<int>(group.size()) > m_maxNSpectraPerPeak)) {
799 continue;
800 }
801
802 SXPeak *maxPeak = nullptr;
803 double maxIntensity = std::numeric_limits<double>::min();
804
805 for (auto *element : group) {
806 if (element->getIntensity() > maxIntensity) {
807 maxIntensity = element->getIntensity();
808 maxPeak = element;
809 }
810 }
811
812 // Add the max peak if valid
813 if (maxPeak) {
814 // check not null as is case when intensity is NaN (though this shouldn't occur now)
815 peaks.emplace_back(*maxPeak);
816 }
817 }
818
819 return peaks;
820}
821
822/* ------------------------------------------------------------------------------------------
823 * Comparison Strategy
824 * ------------------------------------------------------------------------------------------
825 */
826RelativeCompareStrategy::RelativeCompareStrategy(const double resolution) : m_resolution(resolution) {}
827
828bool RelativeCompareStrategy::compare(const SXPeak &lhs, const SXPeak &rhs) const {
829 return lhs.compare(rhs, m_resolution);
830}
831
832AbsoluteCompareStrategy::AbsoluteCompareStrategy(const double xUnitResolution, const double phiResolution,
833 const double twoThetaResolution, const XAxisUnit units)
834 : m_xUnitResolution(xUnitResolution), m_phiResolution(phiResolution), m_twoThetaResolution(twoThetaResolution),
835 m_units(units) {
836 // Convert the input from degree to radians
837 constexpr double rad2deg = M_PI / 180.;
840}
841
845
846} // namespace Mantid::Crystal::FindSXPeaksHelper
std::vector< float > m_tof
sum of all time-of-flight within the bin
const std::vector< double > & rhs
#define NSIGMA_COMPARISON_THRESHOLD
const double m_phi
Definition LoadEMU.cpp:223
double tolerance
API::SpectrumInfo is an intermediate step towards a SpectrumInfo that is part of Instrument-2....
Kernel::V3D sourcePosition() const
Returns the source position.
Kernel::V3D samplePosition() const
Returns the sample position.
bool hasDetectors(const size_t index) const
Returns true if the spectrum is associated with detectors in the instrument.
double twoTheta(const size_t index) const
Returns the scattering angle 2 theta in radians (angle w.r.t.
Kernel::V3D position(const size_t index) const
Returns the position of the spectrum with given index.
double l2(const size_t index) const
Returns L2 (distance from sample to spectrum).
const SpectrumDefinition & spectrumDefinition(const size_t index) const
Returns a const reference to the SpectrumDefinition of the spectrum.
const Geometry::IDetector & detector(const size_t index) const
Return a const reference to the detector or detector group of the spectrum with given index.
double l1() const
Returns L1 (distance from source to sample).
void getDetectorValues(const Kernel::Unit &inputUnit, const Kernel::Unit &outputUnit, const Kernel::DeltaEMode::Type emode, const bool signedTheta, int64_t wsIndex, Kernel::UnitParametersMap &pmap) const
Get the detector values relevant to unit conversion for a workspace index.
AbsoluteCompareStrategy(const double tofResolution, const double phiResolution, const double twoThetaResolution, const XAxisUnit units=XAxisUnit::TOF)
bool compare(const SXPeak &lhs, const SXPeak &rhs) const override
PeakList dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, Bound low, Bound high, const int workspaceIndex) const override
std::vector< std::unique_ptr< PeakContainer > > getAllPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, Bound low, Bound high, const Mantid::Crystal::FindSXPeaksHelper::BackgroundStrategy *backgroundStrategy) const
AllPeaksStrategy(const BackgroundStrategy *backgroundStrategy, const API::SpectrumInfo &spectrumInfo, const double minValue=EMPTY_DBL(), const double maxValue=EMPTY_DBL(), const XAxisUnit units=XAxisUnit::TOF)
virtual bool compare(const SXPeak &lhs, const SXPeak &rhs) const =0
FindMaxReduceStrategy(const CompareStrategy *compareStrategy)
std::vector< SXPeak > reduce(const std::vector< SXPeak > &peaks, Mantid::Kernel::ProgressBase &progress) const override
std::vector< std::vector< SXPeak * > > getPeakGroups(const std::vector< SXPeak > &peakList, Mantid::Kernel::ProgressBase &progress) const
std::vector< SXPeak > getFinalPeaks(const std::vector< std::vector< SXPeak * > > &peakGroups) const
PeakList dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, Bound low, Bound high, const int workspaceIndex) const override
NSigmaPeaksStrategy(const API::SpectrumInfo &spectrumInfo, const double nsigma=EMPTY_DBL(), const double minValue=EMPTY_DBL(), const double maxValue=EMPTY_DBL(), const XAxisUnit units=XAxisUnit::TOF)
std::vector< std::unique_ptr< PeakContainer > > getAllNSigmaPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, Bound low, Bound high) const
PeakContainer(const HistogramData::HistogramY &y)
virtual PeakList dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, Bound low, Bound high, const int workspaceIndex) const =0
double calculatePhi(size_t workspaceIndex) const
Calculates the average phi value if the workspace contains multiple detectors per spectrum,...
double getXValue(const HistogramData::HistogramX &x, const size_t peakLocation) const
void filterPeaksForMinBins(std::vector< std::unique_ptr< PeakContainer > > &inputPeakList) const
PeakList findSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, const int workspaceIndex) const
PeakFindingStrategy(const BackgroundStrategy *backgroundStrategy, const API::SpectrumInfo &spectrumInfo, const double minValue=EMPTY_DBL(), const double maxValue=EMPTY_DBL(), const XAxisUnit units=XAxisUnit::TOF)
PeakList convertToSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const std::vector< std::unique_ptr< PeakContainer > > &peaks, const int workspaceIndex) const
BoundsIterator getBounds(const HistogramData::HistogramX &x) const
double convertToTOF(const double xValue, const size_t workspaceIndex) const
ReducePeakListStrategy(const CompareStrategy *compareStrategy)
bool compare(const SXPeak &lhs, const SXPeak &rhs) const override
void reducePeaksFromNumberOfSpectras(std::vector< SXPeak > &inputPeaks) const
std::vector< SXPeak > reduce(const std::vector< SXPeak > &peaks, Mantid::Kernel::ProgressBase &progress) const override
SimpleReduceStrategy(const CompareStrategy *compareStrategy)
PeakList dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, const HistogramData::HistogramE &e, Bound low, Bound high, const int workspaceIndex) const override
StrongestPeaksStrategy(const BackgroundStrategy *backgroundStrategy, const API::SpectrumInfo &spectrumInfo, const double minValue=EMPTY_DBL(), const double maxValue=EMPTY_DBL(), const XAxisUnit units=XAxisUnit::TOF)
Holds a collection of detectors.
virtual double getPhi() const =0
Gives the phi of this detector object in radians.
virtual detid_t getID() const =0
Get the detector ID.
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
virtual void doReport(const std::string &msg="")=0
Pure virtual method that does the progress reporting, to be overridden.
Time of flight in microseconds.
Definition Unit.h:248
Class for 3D vectors.
Definition V3D.h:34
Mantid::HistogramData::HistogramY::const_iterator yIt
boost::graph_traits< PeakGraph >::vertex_descriptor Vertex
adjacency_list< vecS, vecS, undirectedS, SXPeak * > PeakGraph
Mantid::Kernel::Logger g_log("FindSXPeaksHelper")
boost::graph_traits< PeakGraph >::edge_descriptor Edge
HistogramData::HistogramX::const_iterator Bound
XAxisUnit
enum to determine the units of the workspaces X axis we are searching in
std::optional< std::vector< SXPeak > > PeakList
std::pair< Bound, Bound > BoundsIterator
MANTID_KERNEL_DLL bool withinRelativeDifference(T const x, T const y, S const tolerance)
Test whether x, y are within relative tolerance tol.
std::unordered_map< UnitParams, double > UnitParametersMap
Definition Unit.h:30
SingletonHolder< UnitFactoryImpl > UnitFactory
Definition UnitFactory.h:79
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:352
static constexpr double NeutronMass
Mass of the neutron in kg.
static constexpr double h_bar
Planck constant in J*s, divided by 2 PI.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
int32_t detid_t
Typedef for a detector ID.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
Generate a tableworkspace to store the calibration results.
std::string to_string(const wide_integer< Bits, Signed > &n)
bool isBelowBackground(const double intensity, const HistogramData::HistogramY &) const override
virtual bool isBelowBackground(const double intensity, const HistogramData::HistogramY &y) const =0
bool isBelowBackground(const double intensity, const HistogramData::HistogramY &y) const override
int m_nPixels
Number of contributing pixels.
detid_t getDetectorId() const
Getter for the detector Id.
double m_phi
Phi angle for the centre detector of the peak.
std::vector< int > m_spectra
Contributing spectra to this peak.
Kernel::V3D m_unitWaveVector
Unit vector in the direction of the wavevector.
double m_LTotal
Detector-sample distance.
const std::vector< int > & getPeakSpectras() const
Getter for spectrum indexes of a peak.
SXPeak(double t, double phi, double intensity, const std::vector< int > &spectral, const size_t wsIndex, const Mantid::API::SpectrumInfo &spectrumInfo)
Constructor.
bool compare(const SXPeak &rhs, double tolerance) const
Object comparison.
double m_intensity
Measured intensity of centre of the peak.
double m_dSpacing
d-spacing at the peak centre
void reduce()
Normalise by number of pixels.
SXPeak & operator+=(const SXPeak &rhs)
Operator addition overload.
Mantid::Kernel::V3D getQ() const
Getter for LabQ.
const double & getIntensity() const
Getter for the intensity.
double m_twoTheta
2 * theta angle for then centre detector of the peak
size_t m_wsIndex
Detector workspace index.
Peak indexing algorithm, which works by assigning multiple possible HKL values to each peak and then ...