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 */
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());
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
340double PeakFindingStrategy::calculatePhi(size_t workspaceIndex) const {
341 double phi = m_spectrumInfo.phi(workspaceIndex);
342
343 if (phi < 0) {
344 phi += 2.0 * M_PI;
345 }
346 return phi;
347}
348
349double PeakFindingStrategy::getXValue(const HistogramData::HistogramX &x, const size_t peakLocation) const {
350 auto leftBinPosition = x.begin() + peakLocation;
351 const double leftBinEdge = *leftBinPosition;
352 const double rightBinEdge = *std::next(leftBinPosition);
353 return 0.5 * (leftBinEdge + rightBinEdge);
354}
355
356double PeakFindingStrategy::convertToTOF(const double xValue, const size_t workspaceIndex) const {
357 if (m_units == XAxisUnit::TOF) {
358 // we're already using TOF units
359 return xValue;
360 } else {
361 const auto unit = UnitFactory::Instance().create("dSpacing");
364 m_spectrumInfo.getDetectorValues(*unit, tof, Kernel::DeltaEMode::Elastic, false, workspaceIndex, pmap);
365 // we're using d-spacing, convert the point to TOF
366 unit->initialize(m_spectrumInfo.l1(), 0, pmap);
367 return unit->singleToTOF(xValue);
368 }
369}
370
371PeakList PeakFindingStrategy::convertToSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
372 const std::vector<std::unique_ptr<PeakContainer>> &foundPeaks,
373 const int workspaceIndex) const {
374 PeakList peaks;
375
376 if (foundPeaks.empty()) {
377 return peaks;
378 }
379
380 // Add a vector to the std::optional
381 peaks = std::vector<FindSXPeaksHelper::SXPeak>();
382
383 for (const auto &peak : foundPeaks) {
384 // Get the index of the bin
385 auto maxY = peak->getMaxIterator();
386 const auto distance = std::distance(y.begin(), maxY);
387 const auto xValue = getXValue(x, distance);
388 const auto tof = convertToTOF(xValue, workspaceIndex);
389 const double phi = calculatePhi(workspaceIndex);
390
391 std::vector<int> specs(1, workspaceIndex);
392 (*peaks).emplace_back(tof, phi, *maxY, specs, workspaceIndex, m_spectrumInfo);
393 }
394
395 return peaks;
396}
397
399 const API::SpectrumInfo &spectrumInfo, const double minValue,
400 const double maxValue, const XAxisUnit units)
401 : PeakFindingStrategy(backgroundStrategy, spectrumInfo, minValue, maxValue, units) {}
402
403PeakList StrongestPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
404 [[maybe_unused]] const HistogramData::HistogramE &e, Bound low,
405 Bound high, const int workspaceIndex) const {
406 auto distmin = std::distance(x.begin(), low);
407 auto distmax = std::distance(x.begin(), high);
408
409 // Find the max element
410 auto maxY = (y.size() > 1) ? std::max_element(y.begin() + distmin, y.begin() + distmax) : y.begin();
411
412 // Perform a check against the background
413 double intensity = (*maxY);
415 return PeakList();
416 }
417
418 // Create the SXPeak information
419 const auto distance = std::distance(y.begin(), maxY);
420 const auto xValue = getXValue(x, distance);
421 const auto tof = convertToTOF(xValue, workspaceIndex);
422 const double phi = calculatePhi(workspaceIndex);
423
424 std::vector<int> specs(1, workspaceIndex);
425 std::vector<SXPeak> peaks;
426 peaks.emplace_back(tof, phi, *maxY, specs, workspaceIndex, m_spectrumInfo);
427 return peaks;
428}
429
430AllPeaksStrategy::AllPeaksStrategy(const BackgroundStrategy *backgroundStrategy, const API::SpectrumInfo &spectrumInfo,
431 const double minValue, const double maxValue, const XAxisUnit units)
432 : PeakFindingStrategy(backgroundStrategy, spectrumInfo, minValue, maxValue, units) {
433 // We only allow the AbsoluteBackgroundStrategy for now
434 if (!dynamic_cast<const AbsoluteBackgroundStrategy *>(m_backgroundStrategy)) {
435 throw std::invalid_argument("The AllPeaksStrategy has to be initialized "
436 "with the AbsoluteBackgroundStrategy.");
437 }
438}
439
440PeakList AllPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
441 [[maybe_unused]] const HistogramData::HistogramE &e, Bound low, Bound high,
442 const int workspaceIndex) const {
443 // Get all peaks from the container
444 auto foundPeaks = getAllPeaks(x, y, low, high, m_backgroundStrategy);
445
446 // Filter the found peaks having the mininum number of bins
447 filterPeaksForMinBins(foundPeaks);
448
449 // Convert the found peaks to SXPeaks
450 auto peaks = convertToSXPeaks(x, y, foundPeaks, workspaceIndex);
451
452 return peaks;
453}
454
455std::vector<std::unique_ptr<PeakContainer>>
456AllPeaksStrategy::getAllPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y, Bound low,
457 Bound high,
458 const Mantid::Crystal::FindSXPeaksHelper::BackgroundStrategy *backgroundStrategy) const {
459 // We iterate over the data and only consider data which is above the
460 // threshold.
461 // Once data starts to be above the threshold we start to record it and add it
462 // to a peak. Once it falls below, it concludes recording of that particular
463 // peak
464 bool isRecording = false;
465
466 std::unique_ptr<PeakContainer> currentPeak = nullptr;
467 std::vector<std::unique_ptr<PeakContainer>> peaks;
468
469 // We want to the upper boundary to be inclusive hence we need to increment it
470 // by one
471 if (high != x.end()) {
472 ++high;
473 }
474 auto distanceMin = std::distance(x.begin(), low);
475 auto distanceMax = std::distance(x.begin(), high);
476
477 const auto lowY = y.begin() + distanceMin;
478 auto highY = distanceMax < static_cast<int>(y.size()) ? y.begin() + distanceMax : y.end();
479
480 for (auto it = lowY; it != highY; ++it) {
481 const auto signal = *it;
482 const auto isAboveThreshold = !backgroundStrategy->isBelowBackground(signal, y);
483
484 // There are four scenarios:
485 // 1. Not recording + below threshold => continue
486 // 2. Not recording + above treshold => start recording
487 // 3. Recording + below threshold => stop recording
488 // 4. Recording + above threshold => continue recording
489 if (!isRecording && (!std::isfinite(signal) || !isAboveThreshold)) {
490 continue;
491 } else if (!isRecording && isAboveThreshold && std::isfinite(signal)) {
492 // only start recording if is finite as NaN values will be found to be above threshold
493 currentPeak = std::make_unique<PeakContainer>(y);
494 currentPeak->startRecord(it);
495 isRecording = true;
496 } else if (isRecording && !isAboveThreshold) {
497 currentPeak->stopRecord(it);
498 peaks.emplace_back(std::move(currentPeak));
499 currentPeak = nullptr;
500 isRecording = false;
501 } else {
502 // this will continue to record NaN if previous point was above the background
503 currentPeak->record(it);
504 }
505 }
506
507 // Handle a peak on the edge if it exists
508 if (isRecording) {
509 if (highY == y.end()) {
510 --highY;
511 }
512 currentPeak->stopRecord(highY);
513 peaks.emplace_back(std::move(currentPeak));
514 currentPeak = nullptr;
515 }
516
517 return peaks;
518}
519
520NSigmaPeaksStrategy::NSigmaPeaksStrategy(const API::SpectrumInfo &spectrumInfo, const double nsigma,
521 const double minValue, const double maxValue, const XAxisUnit units)
522 : PeakFindingStrategy(nullptr, spectrumInfo, minValue, maxValue, units), m_nsigma(nsigma) {}
523
524PeakList NSigmaPeaksStrategy::dofindSXPeaks(const HistogramData::HistogramX &x, const HistogramData::HistogramY &y,
525 const HistogramData::HistogramE &e, Bound low, Bound high,
526 const int workspaceIndex) const {
527 auto nsigmaPeaks = getAllNSigmaPeaks(x, y, e, low, high);
528
529 // Filter the found peaks having the mininum number of bins
530 filterPeaksForMinBins(nsigmaPeaks);
531
532 auto sxPeaks = convertToSXPeaks(x, y, nsigmaPeaks, workspaceIndex);
533 return sxPeaks;
534}
535
536std::vector<std::unique_ptr<PeakContainer>> NSigmaPeaksStrategy::getAllNSigmaPeaks(const HistogramData::HistogramX &x,
537 const HistogramData::HistogramY &y,
538 const HistogramData::HistogramE &e,
539 Bound low, Bound high) const {
540 /*
541 Credits to the author of SXD2001 for the idea of using NSigma as a threshold for peak finding:
542 Gutmann, M. J. (2005). SXD2001. ISIS Facility, Rutherford Appleton Laboratory, Oxfordshire, England.
543 */
544
545 bool isRecording = false;
546 std::unique_ptr<PeakContainer> currentPeak = nullptr;
547 std::vector<std::unique_ptr<PeakContainer>> peaks;
548
549 // We want to the upper boundary to be inclusive hence we need to increment by one
550 if (high != x.end()) {
551 ++high;
552 }
553 auto distanceMin = std::distance(x.begin(), low);
554 auto distanceMax = std::distance(x.begin(), high);
555
556 const auto lowY = y.begin() + distanceMin;
557 auto highY = distanceMax < static_cast<int>(y.size()) ? y.begin() + distanceMax : y.end();
558
559 const auto lowE = e.begin() + distanceMin;
560 const auto highE = distanceMax < static_cast<int>(e.size()) ? e.begin() + distanceMax : e.begin();
561
562 auto yIt = lowY + 1;
563 auto eIt = lowE + 1;
564
565 for (; yIt != highY && eIt != highE; ++yIt, ++eIt) {
566 const auto signalDiff = *yIt - *(yIt - 1);
567 const auto isStartPeak = signalDiff > (m_nsigma * (*eIt)) + NSIGMA_COMPARISON_THRESHOLD;
568 const auto isSigDropSignificant = (signalDiff * (-1.)) > (m_nsigma * (*eIt)) + NSIGMA_COMPARISON_THRESHOLD;
569 const auto isEndPeak = (currentPeak != nullptr ? (isSigDropSignificant || currentPeak->getStartingSignal() > *yIt)
570 : isSigDropSignificant);
571
572 /*Possible scenarios
573 1. isRecording is False and isStartPeak True(== isEndPeak is False) => start recording
574 2. isRecording is True and isEndPeak False - continue recording
575 3. isRecording is True and isEndpeak is True(== isStartpeak is False) - end peak
576 4. else - continue
577 */
578 if (!isRecording && isStartPeak) {
579 currentPeak = std::make_unique<PeakContainer>(y);
580 currentPeak->startRecord(yIt);
581 isRecording = true;
582 } else if (isRecording && !isEndPeak) {
583 currentPeak->record(yIt);
584 } else if (isRecording && isEndPeak) {
585 currentPeak->stopRecord(yIt);
586 peaks.emplace_back(std::move(currentPeak));
587 currentPeak = nullptr;
588 isRecording = false;
589 } else {
590 continue;
591 }
592 }
593
594 // Handle a peak on the edge if it exists
595 if (isRecording) {
596 if (highY == y.end()) {
597 --highY;
598 }
599 currentPeak->stopRecord(highY);
600 peaks.emplace_back(std::move(currentPeak));
601 currentPeak = nullptr;
602 }
603
604 return peaks;
605}
606/* ------------------------------------------------------------------------------------------
607 * PeakList Reduction Strategy
608 * ------------------------------------------------------------------------------------------
609 */
610
612 : m_compareStrategy(compareStrategy) {}
613
615 m_minNSpectraPerPeak = minNSpectraPerPeak;
616}
617
619 m_maxNSpectraPerPeak = maxSpectrasForPeak;
620}
621
624
625std::vector<SXPeak> SimpleReduceStrategy::reduce(const std::vector<SXPeak> &peaks,
626 Mantid::Kernel::ProgressBase & /*progress*/) const {
627 // If the peaks are empty then do nothing
628 if (peaks.empty()) {
629 return peaks;
630 }
631
632 std::vector<SXPeak> finalPeaks;
633 for (const auto &currentPeak : peaks) {
634 auto pos = std::find_if(finalPeaks.begin(), finalPeaks.end(), [&currentPeak, this](SXPeak &peak) {
635 auto result = this->m_compareStrategy->compare(currentPeak, peak);
636 // bool result = currentPeak.compare(peak,
637 // resolution);
638 if (result)
639 peak += currentPeak;
640 return result;
641 });
642 if (pos == finalPeaks.end()) {
643 finalPeaks.emplace_back(currentPeak);
644 }
645 }
646
648
649 return finalPeaks;
650}
651
652void SimpleReduceStrategy::reducePeaksFromNumberOfSpectras(std::vector<SXPeak> &inputPeaks) const {
654 return;
655 }
656
657 for (auto peakIt = inputPeaks.begin(); peakIt != inputPeaks.end();) {
658 if (((m_minNSpectraPerPeak != EMPTY_INT()) &&
659 (static_cast<int>((*peakIt).getPeakSpectras().size()) < m_minNSpectraPerPeak)) ||
661 (static_cast<int>((*peakIt).getPeakSpectras().size()) > m_maxNSpectraPerPeak))) {
662 peakIt = inputPeaks.erase(peakIt);
663 } else {
664 ++peakIt;
665 }
666 }
667}
668
671
672std::vector<SXPeak> FindMaxReduceStrategy::reduce(const std::vector<SXPeak> &peaks,
673 Mantid::Kernel::ProgressBase &progress) const {
674 // If the peaks are empty then do nothing
675 if (peaks.empty()) {
676 return peaks;
677 }
678
679 // Groups the peaks into elements which are considered alike
680 auto peakGroups = getPeakGroups(peaks, progress);
681
682 // Now reduce the peaks groups
683 return getFinalPeaks(peakGroups);
684}
685
686// Define some graph elements
687using PeakGraph = adjacency_list<vecS, vecS, undirectedS, SXPeak *>;
688using Vertex = boost::graph_traits<PeakGraph>::vertex_descriptor;
689using Edge = boost::graph_traits<PeakGraph>::edge_descriptor;
690
691std::vector<std::vector<SXPeak *>> FindMaxReduceStrategy::getPeakGroups(const std::vector<SXPeak> &peakList,
692 Mantid::Kernel::ProgressBase &progress) const {
693
694 // Create a vector of addresses. Note that the peaks live on the stack. This
695 // here only works, because the peaks are always in a stack frame below.
696 std::vector<SXPeak *> peaks;
697 peaks.reserve(peakList.size());
698 std::transform(peakList.cbegin(), peakList.cend(), std::back_inserter(peaks),
699 [](const auto &peak) { return &const_cast<SXPeak &>(peak); });
700
701 // Add the peaks to a graph
702 Edge edge;
703 PeakGraph graph;
704 PeakGraph::vertex_iterator vertexIt, vertexEnd;
705
706 // Provide a warning if there are more than 500 peaks found.
707 const size_t numberOfPeaksFound = peaks.size();
708 if (numberOfPeaksFound > 500) {
709 std::string warningMessage = std::string("There are ") + std::to_string(numberOfPeaksFound) +
710 std::string(" peaks being processed. This might take a long time. "
711 "Please check that the cutoff of the background that "
712 "you have selected is high enough, else the algorithm will"
713 " mistake background noise for peaks. The instrument view "
714 "allows you to easily inspect the typical background level.");
715 g_log.warning(warningMessage);
716 }
717
718 std::string message = std::string("There are ") + std::to_string(numberOfPeaksFound) +
719 std::string(" peaks. Investigating peak number ");
720 int peakCounter = 0;
721
722 for (auto peak : peaks) {
723 ++peakCounter;
724
725 // 1. Add the vertex
726 auto vertex = add_vertex(peak, graph);
727
728 // 2. Iterate over all elements already in the graph and check if they need
729 // to edstablish an edge between them.
730 std::tie(vertexIt, vertexEnd) = vertices(graph);
731
732 // Provide a progress report such that users can escape the graph generation
733 if (peakCounter > 50) {
734 progress.doReport(message + std::to_string(peakCounter));
735 }
736
737 for (; vertexIt != vertexEnd; ++vertexIt) {
738 // 2.1 Check if we are looking at the new vertex itself. We don't want
739 // self-loops
740 if (vertex == *vertexIt) {
741 continue;
742 }
743
744 // 2.2 Check if the edge exists already
745 if (boost::edge(vertex, *vertexIt, graph).second) {
746 continue;
747 }
748
749 // 2.3 Check if the two vertices should have an edge
750 const auto toCheck = graph[*vertexIt];
751 if (m_compareStrategy->compare(*peak, *toCheck)) {
752 // We need to create an edge
753 add_edge(vertex, *vertexIt, graph);
754 }
755 }
756 }
757
758 // Create disjoined graphs from graph above
759 std::vector<int> components(boost::num_vertices(graph));
760 const int numberOfPeaks = connected_components(graph, &components[0]);
761
762 std::vector<std::vector<SXPeak *>> peakGroups(numberOfPeaks);
763
764 for (auto i = 0u; i < components.size(); ++i) {
765 auto index = components[i];
766 peakGroups[index].emplace_back(graph[i]);
767 }
768
769 return peakGroups;
770}
771
772std::vector<SXPeak> FindMaxReduceStrategy::getFinalPeaks(const std::vector<std::vector<SXPeak *>> &peakGroups) const {
773 std::vector<SXPeak> peaks;
774 // For each peak groupf find one peak
775 // Currently we select the peak with the largest signal (this strategy could
776 // be changed to something like a weighted mean or similar)
777 for (const auto &group : peakGroups) {
778 // When MinNSpectraPerPeak or maxNSpectraPerPeak parameters are provided,
779 // a group will be ignored if it does not satisfy the minimum or the maximum number of spectrums
780 // required to identify as a peak.
781 if ((m_minNSpectraPerPeak != EMPTY_INT() && static_cast<int>(group.size()) < m_minNSpectraPerPeak) ||
782 (m_maxNSpectraPerPeak != EMPTY_INT() && static_cast<int>(group.size()) > m_maxNSpectraPerPeak)) {
783 continue;
784 }
785
786 SXPeak *maxPeak = nullptr;
787 double maxIntensity = std::numeric_limits<double>::min();
788
789 for (auto *element : group) {
790 if (element->getIntensity() > maxIntensity) {
791 maxIntensity = element->getIntensity();
792 maxPeak = element;
793 }
794 }
795
796 // Add the max peak if valid
797 if (maxPeak) {
798 // check not null as is case when intensity is NaN (though this shouldn't occur now)
799 peaks.emplace_back(*maxPeak);
800 }
801 }
802
803 return peaks;
804}
805
806/* ------------------------------------------------------------------------------------------
807 * Comparison Strategy
808 * ------------------------------------------------------------------------------------------
809 */
810RelativeCompareStrategy::RelativeCompareStrategy(const double resolution) : m_resolution(resolution) {}
811
812bool RelativeCompareStrategy::compare(const SXPeak &lhs, const SXPeak &rhs) const {
813 return lhs.compare(rhs, m_resolution);
814}
815
816AbsoluteCompareStrategy::AbsoluteCompareStrategy(const double xUnitResolution, const double phiResolution,
817 const double twoThetaResolution, const XAxisUnit units)
818 : m_xUnitResolution(xUnitResolution), m_phiResolution(phiResolution), m_twoThetaResolution(twoThetaResolution),
819 m_units(units) {
820 // Convert the input from degree to radians
821 constexpr double rad2deg = M_PI / 180.;
824}
825
829
830} // namespace Mantid::Crystal::FindSXPeaksHelper
std::vector< float > m_tof
sum of all time-of-flight within the bin
const std::vector< double > & rhs
double intensity
double background
#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 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 phi(const size_t index) const
Returns the angle of the spectrum position about the lab-frame z axis, atan2(y, x),...
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 phi for the workspace index.
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)
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:286
Class for 3D vectors.
Definition V3D.h:35
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:29
SingletonHolder< UnitFactoryImpl > UnitFactory
Definition UnitFactory.h:79
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:353
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 ...