Mantid
Loading...
Searching...
No Matches
FindPeaksMD.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 +
8#include "MantidAPI/Run.h"
18#include "MantidKernel/VMD.h"
19
20#include <map>
21#include <vector>
22
23using namespace Mantid::Kernel;
24using namespace Mantid::API;
25using namespace Mantid::DataObjects;
26using namespace Mantid::DataObjects;
27
28namespace Mantid::MDAlgorithms {
29namespace {
30// ---------- Template deduction of the event type
31// --------------------------------
32// See boost::type_traits documentation
33
35template <typename MDE, size_t nd> struct IsFullEvent : boost::false_type {};
36
38template <size_t nd> struct IsFullEvent<MDEvent<nd>, nd> : boost::true_type {};
39
44template <typename MDE, size_t nd> bool isFullMDEvent(const boost::true_type & /*unused*/) { return true; }
45
50template <typename MDE, size_t nd> bool isFullMDEvent(const boost::false_type & /*unused*/) { return false; }
51
55template <typename MDE, size_t nd> bool isFullMDEvent() { return isFullMDEvent<MDE, nd>(IsFullEvent<MDE, nd>()); }
56
62template <typename MDE, size_t nd>
63void addDetectors(DataObjects::Peak &peak, MDBoxBase<MDE, nd> &box, const boost::true_type & /*unused*/) {
64 if (box.getNumChildren() > 0) {
65 std::cerr << "Box has children\n";
66 addDetectors(peak, box, boost::true_type());
67 }
68 auto *mdBox = dynamic_cast<MDBox<MDE, nd> *>(&box);
69 if (!mdBox) {
70 throw std::invalid_argument("FindPeaksMD::addDetectors - Unexpected Box "
71 "type, cannot retrieve events");
72 }
73 const auto &events = mdBox->getConstEvents();
74 auto itend = events.end();
75 for (auto it = events.begin(); it != itend; ++it) {
76 peak.addContributingDetID(it->getDetectorID());
77 }
78}
79
82template <typename MDE, size_t nd>
83void addDetectors(DataObjects::Peak & /*unused*/, MDBoxBase<MDE, nd> & /*unused*/,
84 const boost::false_type & /*unused*/) {
85 throw std::runtime_error("FindPeaksMD - Workspace contains lean events, "
86 "cannot include detector information");
87}
88
94template <typename MDE, size_t nd> void addDetectors(DataObjects::Peak &peak, MDBoxBase<MDE, nd> &box) {
95 // Compile time deduction of the correct function call
96 addDetectors(peak, box, IsFullEvent<MDE, nd>());
97}
98} // namespace
99
100// Register the algorithm into the AlgorithmFactory
101DECLARE_ALGORITHM(FindPeaksMD)
102
103const std::string FindPeaksMD::volumeNormalization = "VolumeNormalization";
104const std::string FindPeaksMD::numberOfEventsNormalization = "NumberOfEventsNormalization";
105
106//----------------------------------------------------------------------------------------------
110 : peakWS(), peakRadiusSquared(), DensityThresholdFactor(0.0), m_maxPeaks(0), m_addDetectors(true),
111 m_densityScaleFactor(1e-6), prog(nullptr), m_inst(), m_runNumber(-1), dimType(), m_goniometer() {}
112
113//----------------------------------------------------------------------------------------------
117 declareProperty(std::make_unique<WorkspaceProperty<IMDWorkspace>>("InputWorkspace", "", Direction::Input),
118 "An input MDEventWorkspace or MDHistoWorkspace with at least "
119 "3 dimensions.");
120
121 declareProperty(std::make_unique<PropertyWithValue<double>>("PeakDistanceThreshold", 0.1, Direction::Input),
122 "Threshold distance for rejecting peaks that are found to be too close "
123 "from each other.\n"
124 "This should be some multiple of the radius of a peak. Default: 0.1.");
125
126 declareProperty(std::make_unique<PropertyWithValue<int64_t>>("MaxPeaks", 500, Direction::Input),
127 "Maximum number of peaks to find. Default: 500.");
128
129 std::vector<std::string> strategy = {volumeNormalization, numberOfEventsNormalization};
130 declareProperty("PeakFindingStrategy", volumeNormalization, std::make_shared<StringListValidator>(strategy),
131 "Strategy for finding peaks in an MD workspace."
132 "1. VolumeNormalization: This is the default strategy. It will sort "
133 "all boxes in the workspace by deacresing order of signal density "
134 "(total weighted event sum divided by box volume).\n"
135 "2.NumberOfEventsNormalization: This option is only valid for "
136 "MDEventWorkspaces. It will use the total weighted event sum divided"
137 "by the number of events. This can improve peak finding for "
138 "histogram-based"
139 "raw data which has been converted to an EventWorkspace. The threshold "
140 "for"
141 "peak finding can be controlled by the SingalThresholdFactor property "
142 "which should"
143 "be larger than 1. Note that this approach does not work for event-based "
144 "raw data.\n");
145
146 declareProperty(std::make_unique<PropertyWithValue<double>>("DensityThresholdFactor", 10.0, Direction::Input),
147 "The overall signal density of the workspace will be "
148 "multiplied by this factor \n"
149 "to get a threshold signal density below which boxes are NOT "
150 "considered to be peaks. See the help.\n"
151 "Default: 10.0");
152
153 setPropertySettings("DensityThresholdFactor",
154 std::make_unique<EnabledWhenProperty>(
156
157 declareProperty(std::make_unique<PropertyWithValue<double>>("SignalThresholdFactor", 1.5, Direction::Input),
158 "The overal signal value (not density!) normalized by the "
159 "number of events is compared to the specified signal "
160 "threshold. Boxes which are below this threshold are NOT "
161 "considered to be peaks."
162 "This property is enabled when the PeakFindingStrategy has "
163 "been set to NumberOfEventsNormalization.\n"
164 "The value of boxes which contain peaks will be above 1. See "
165 "the below for more information.\n"
166 "Default: 1.50");
167
168 setPropertySettings("SignalThresholdFactor",
169 std::make_unique<EnabledWhenProperty>("PeakFindingStrategy",
172
173 declareProperty("CalculateGoniometerForCW", false,
174 "This will calculate the goniometer rotation (around y-axis "
175 "only) for a constant wavelength. This only works for Q "
176 "sample workspaces.");
177
178 auto nonNegativeDbl = std::make_shared<BoundedValidator<double>>();
179 nonNegativeDbl->setLower(0);
180 declareProperty("Wavelength", DBL_MAX, nonNegativeDbl,
181 "Wavelength to use when calculating goniometer angle. If not"
182 "set will use the wavelength parameter on the instrument.");
183 setPropertySettings("Wavelength",
184 std::make_unique<EnabledWhenProperty>("CalculateGoniometerForCW",
186
187 declareProperty("InnerGoniometer", false,
188 "Whether the goniometer to be calculated is the most inner "
189 "(phi) or most outer (omega)");
190 setPropertySettings("InnerGoniometer",
191 std::make_unique<EnabledWhenProperty>("CalculateGoniometerForCW",
193
194 declareProperty("FlipX", false,
195 "Used when calculating goniometer angle if the q_lab x value "
196 "should be negative, hence the detector of the other side "
197 "(right) of the beam");
198 setPropertySettings("FlipX", std::make_unique<EnabledWhenProperty>(
199 "CalculateGoniometerForCW", Mantid::Kernel::ePropertyCriterion::IS_NOT_DEFAULT));
200 std::vector<std::string> peakTypes = {"Automatic", "Peak", "LeanElasticPeak"};
201 declareProperty("OutputType", "Automatic", std::make_shared<StringListValidator>(peakTypes),
202 "Type of Peak in OutputWorkspace");
203 declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>("OutputWorkspace", "", Direction::Output),
204 "An output PeaksWorkspace with the peaks' found positions.");
205
206 declareProperty("AppendPeaks", false,
207 "If checked, then append the peaks in the output workspace "
208 "if it exists. \n"
209 "If unchecked, the output workspace is replaced (Default).");
210
211 auto nonNegativeInt = std::make_shared<BoundedValidator<int>>();
212 nonNegativeInt->setLower(0);
213 declareProperty("EdgePixels", 0, nonNegativeInt, "Remove peaks that are at pixels this close to edge. ");
214}
215
216//----------------------------------------------------------------------------------------------
219 // Instrument associated with workspace
220 m_inst = ei->getInstrument();
221 // Find the run number
222 m_runNumber = ei->getRunNumber();
223
224 // Find the goniometer rotation matrix
225 m_goniometer = Mantid::Kernel::Matrix<double>(3, 3, true); // Default IDENTITY matrix
226 try {
227 m_goniometer = ei->mutableRun().getGoniometerMatrix();
228 } catch (std::exception &e) {
229 g_log.warning() << "Error finding goniometer matrix. It will not be set in "
230 "the peaks found.\n";
231 g_log.warning() << e.what() << '\n';
232 }
233}
234
236 // Check that the workspace dimensions are in Q-sample-frame or Q-lab-frame.
237 std::string dim0 = ws->getDimension(0)->getName();
238 if (dim0 == "H") {
239 dimType = HKL;
240 throw std::runtime_error("Cannot find peaks in a workspace that is already in HKL space.");
241 } else if (dim0 == "Q_lab_x") {
242 dimType = QLAB;
243 } else if (dim0 == "Q_sample_x")
245 else
246 throw std::runtime_error("Unexpected dimensions: need either Q_lab_x or Q_sample_x.");
247}
248
249void FindPeaksMD::determineOutputType(const std::string &peakType, const uint16_t numExperimentInfo) {
250 // This method will be expanded later to check a property on the
251 // input workspace which can specify a default peak type for that
252 // instrument.
253 m_leanElasticPeak = false;
254 if (peakType == "Automatic") {
255 if (numExperimentInfo == 0)
256 m_leanElasticPeak = true;
257 } else if (peakType == "LeanElasticPeak") {
258 m_leanElasticPeak = true;
259 } else { // Peak
260 if (numExperimentInfo == 0)
261 throw std::runtime_error("Cannot create Peak output with 0 expInfo");
262 }
263}
264
265//----------------------------------------------------------------------------------------------
273void FindPeaksMD::addPeak(const Geometry::ComponentInfo &compInfo, const V3D &Q, const double binCount,
274 const Geometry::InstrumentRayTracer &tracer) {
275 try {
276 auto p = this->createPeak(Q, binCount, tracer);
277 if (m_edge > 0) {
278 if (edgePixel(compInfo, p->getBankName(), p->getCol(), p->getRow(), m_edge))
279 return;
280 }
281 if (p->getDetectorID() != -1)
282 peakWS->addPeak(*p);
283 } catch (std::exception &e) {
284 g_log.notice() << "Error creating peak at " << Q << " because of '" << e.what() << "'. Peak will be skipped.\n";
285 }
286}
287
288//----------------------------------------------------------------------------------------------
296void FindPeaksMD::addLeanElasticPeak(const V3D &Q, const double binCount, const bool useGoniometer) {
297 auto p = this->createLeanElasticPeak(Q, binCount, useGoniometer);
298 peakWS->addPeak(*p);
299}
300
304std::shared_ptr<DataObjects::Peak> FindPeaksMD::createPeak(const Mantid::Kernel::V3D &Q, const double binCount,
305 const Geometry::InstrumentRayTracer &tracer) {
306 std::shared_ptr<DataObjects::Peak> p;
307 if (dimType == QLAB) {
308 // Build using the Q-lab-frame constructor
309 p = std::make_shared<Peak>(m_inst, Q);
310 // Save gonio matrix for later
311 p->setGoniometerMatrix(m_goniometer);
312 } else if (dimType == QSAMPLE) {
313 // Build using the Q-sample-frame constructor
314 bool calcGoniometer = getProperty("CalculateGoniometerForCW");
315
316 if (calcGoniometer) {
317 // Calculate Q lab from Q sample and wavelength
318 double wavelength = getProperty("Wavelength");
319 if (wavelength == DBL_MAX) {
320 if (m_inst->hasParameter("wavelength")) {
321 wavelength = m_inst->getNumberParameter("wavelength").at(0);
322 } else {
323 throw std::runtime_error("Could not get wavelength, neither "
324 "Wavelength algorithm property "
325 "set nor instrument wavelength parameter");
326 }
327 }
328
330 goniometer.calcFromQSampleAndWavelength(Q, wavelength, getProperty("FlipX"), getProperty("InnerGoniometer"));
331 std::vector<double> angles = goniometer.getEulerAngles("YZY");
332 g_log.information() << "Found goniometer rotation to be in YZY convention [" << angles[0] << ", " << angles[1]
333 << ". " << angles[2] << "] degrees for Q sample = " << Q << "\n";
334 p = std::make_shared<Peak>(m_inst, Q, goniometer.getR());
335
336 } else {
337 p = std::make_shared<Peak>(m_inst, Q, m_goniometer);
338 }
339 } else {
340 throw std::invalid_argument("Cannot Integrate peaks unless the dimension is QLAB or QSAMPLE");
341 }
342
343 try { // Look for a detector
344 p->findDetector(tracer);
345 } catch (...) { /* Ignore errors in ray-tracer */
346 }
347
348 p->setBinCount(binCount);
349 // Save the run number found before.
350 p->setRunNumber(m_runNumber);
351 return p;
352}
353
357std::shared_ptr<DataObjects::LeanElasticPeak>
358FindPeaksMD::createLeanElasticPeak(const Mantid::Kernel::V3D &Q, const double binCount, const bool useGoniometer) {
359 std::shared_ptr<DataObjects::LeanElasticPeak> p;
360 if (dimType == QSAMPLE) {
361 if (useGoniometer)
362 p = std::make_shared<LeanElasticPeak>(Q, m_goniometer);
363 else
364 p = std::make_shared<LeanElasticPeak>(Q);
365 } else {
366 throw std::invalid_argument("Cannot find peaks unless the dimension is QSAMPLE");
367 }
368
369 p->setBinCount(binCount);
370 // Save the run number found before.
371 p->setRunNumber(m_runNumber);
372 return p;
373}
374//----------------------------------------------------------------------------------------------
379template <typename MDE, size_t nd> void FindPeaksMD::findPeaks(typename MDEventWorkspace<MDE, nd>::sptr ws) {
380 if (nd < 3)
381 throw std::invalid_argument("Workspace must have at least 3 dimensions.");
382
383 if (isFullMDEvent<MDE, nd>()) {
384 m_addDetectors = true;
385 } else {
386 m_addDetectors = false;
387 g_log.warning("Workspace contains only lean events. Resultant "
388 "PeaksWorkspaces will not contain full detector "
389 "information.");
390 }
391
392 progress(0.01, "Refreshing Centroids");
393
394 // TODO: This might be slow, progress report?
395 // Make sure all centroids are fresh
396 // ws->getBox()->refreshCentroid();
397
398 // Calculate a threshold below which a box is too diffuse to be considered a
399 // peak.
400 signal_t threshold = m_useNumberOfEventsNormalization ? ws->getBox()->getSignalByNEvents() * m_signalThresholdFactor
402 threshold *= m_densityScaleFactor;
403
404 if (!std::isfinite(threshold)) {
405 g_log.warning() << "Infinite or NaN overall density found. Your input data "
406 "may be invalid. Using a 0 threshold instead.\n";
407 threshold = 0;
408 }
409 g_log.information() << "Threshold signal density: " << threshold << '\n';
410
411 using boxPtr = API::IMDNode *;
412 // We will fill this vector with pointers to all the boxes (up to a given
413 // depth)
414 typename std::vector<API::IMDNode *> boxes;
415
416 // Get all the MDboxes
417 progress(0.10, "Getting Boxes");
418 ws->getBox()->getBoxes(boxes, 1000, true);
419
420 // This pair is the <density, ptr to the box>
421 using dens_box = std::pair<double, API::IMDNode *>;
422
423 // Map that will sort the boxes by increasing density. The key = density;
424 // value = box *.
425 typename std::multimap<double, API::IMDNode *> sortedBoxes;
426
427 // --------------- Sort and Filter by Density -----------------------------
428 progress(0.20, "Sorting Boxes by Density");
429 auto it1 = boxes.begin();
430 auto it1_end = boxes.end();
431 for (; it1 != it1_end; it1++) {
432 auto box = *it1;
435 // Skip any boxes with too small a signal value.
436 if (value > threshold)
437 sortedBoxes.insert(dens_box(value, box));
438 }
439
440 // --------------- Find Peak Boxes -----------------------------
441 // List of chosen possible peak boxes.
442 std::vector<API::IMDNode *> peakBoxes;
443
444 prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
445
446 // used for selecting method for calculating BinCount
447 bool isMDEvent(ws->id().find("MDEventWorkspace") != std::string::npos);
448
449 int64_t numBoxesFound = 0;
450 // Now we go (backwards) through the map
451 // e.g. from highest density down to lowest density.
452 typename std::multimap<double, boxPtr>::reverse_iterator it2;
453 auto it2_end = sortedBoxes.rend();
454 for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
455 signal_t density = it2->first;
456 boxPtr box = it2->second;
457#ifndef MDBOX_TRACK_CENTROID
458 coord_t boxCenter[nd];
459 box->calculateCentroid(boxCenter);
460#else
461 const coord_t *boxCenter = box->getCentroid();
462#endif
463
464 // Compare to all boxes already picked.
465 bool badBox = false;
466 for (auto &peakBoxe : peakBoxes) {
467
468#ifndef MDBOX_TRACK_CENTROID
469 coord_t otherCenter[nd];
470 (*it3)->calculateCentroid(otherCenter);
471#else
472 const coord_t *otherCenter = peakBoxe->getCentroid();
473#endif
474
475 // Distance between this box and a box we already put in.
476 coord_t distSquared = 0.0;
477 for (size_t d = 0; d < nd; d++) {
478 coord_t dist = otherCenter[d] - boxCenter[d];
479 distSquared += (dist * dist);
480 }
481
482 // Reject this box if it is too close to another previously found box.
483 if (distSquared < peakRadiusSquared) {
484 badBox = true;
485 break;
486 }
487 }
488
489 // The box was not rejected for another reason.
490 if (!badBox) {
491 if (numBoxesFound++ >= m_maxPeaks) {
492 g_log.notice() << "Number of peaks found exceeded the limit of " << m_maxPeaks << ". Stopping peak finding.\n";
493 break;
494 }
495
496 peakBoxes.emplace_back(box);
497 g_log.debug() << "Found box at ";
498 for (size_t d = 0; d < nd; d++)
499 g_log.debug() << (d > 0 ? "," : "") << boxCenter[d];
500 g_log.debug() << "; Density = " << density << '\n';
501 // Report progres for each box found.
502 prog->report("Finding Peaks");
503 }
504 }
505
506 prog->resetNumSteps(numBoxesFound, 0.95, 1.0);
507
508 uint16_t numExperimentInfo = ws->getNumExperimentInfo();
509
510 if (numExperimentInfo == 0) {
511 // --- Convert the "boxes" to peaks ----
512 for (auto box : peakBoxes) {
513 // The center of the box = Q in the lab frame
514#ifndef MDBOX_TRACK_CENTROID
515 coord_t boxCenter[nd];
516 box->calculateCentroid(boxCenter);
517#else
518 const coord_t *boxCenter = box->getCentroid();
519#endif
520
521 // Q of the centroid of the box
522 V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
523
524 // The "bin count" used will be the box density.
525 double binCount = box->getSignalNormalized() * m_densityScaleFactor;
526 if (isMDEvent)
527 binCount = static_cast<double>(box->getNPoints());
528
529 // Create the peak
530 addLeanElasticPeak(Q, binCount);
531
532 // Report progres for each box found.
533 prog->report("Adding Peaks");
534
535 } // for each box found
536 } else {
537 for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
539 this->readExperimentInfo(ei);
540
542 Geometry::ComponentInfo const &compInfo = ei->componentInfo();
543 // Copy the instrument, sample, run to the peaks workspace.
544 peakWS->copyExperimentInfoFrom(ei.get());
545
546 // --- Convert the "boxes" to peaks ----
547 for (auto box : peakBoxes) {
548 // If no events from this experimental contribute to the box then skip
549 if (numExperimentInfo > 1) {
550 auto *mdbox = dynamic_cast<MDBox<MDE, nd> *>(box);
551 const std::vector<MDE> &events = mdbox->getEvents();
552 if (std::none_of(events.cbegin(), events.cend(), [&iexp, &numExperimentInfo](MDE event) {
553 return event.getExpInfoIndex() == iexp || event.getExpInfoIndex() >= numExperimentInfo;
554 }))
555 continue;
556 }
557
558 // If multiple goniometers than use the average one from the
559 // events in the box, that matches this expInfoIndex, this assumes
560 // the events are added in same order as the goniometers
561 if (ei->run().getNumGoniometers() > 1) {
562 const std::vector<MDE> &events = dynamic_cast<MDBox<MDE, nd> *>(box)->getEvents();
563 double sum = 0;
564 double count = 0;
565 for (const auto &event : events) {
566 if (event.getExpInfoIndex() == iexp) {
567 sum += event.getGoniometerIndex();
568 count++;
569 }
570 }
571 m_goniometer = ei->mutableRun().getGoniometerMatrix(lrint(sum / count));
572 }
573 // The center of the box = Q in the lab frame
574
575#ifndef MDBOX_TRACK_CENTROID
576 coord_t boxCenter[nd];
577 box->calculateCentroid(boxCenter);
578#else
579 const coord_t *boxCenter = box->getCentroid();
580#endif
581
582 // Q of the centroid of the box
583 V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
584
585 // The "bin count" used will be the box density or the number of events
586 // in the box
587 double binCount = box->getSignalNormalized() * m_densityScaleFactor;
588 if (isMDEvent)
589 binCount = static_cast<double>(box->getNPoints());
590
591 if (m_leanElasticPeak) {
592 addLeanElasticPeak(Q, binCount, true);
593 } else {
594 try {
595 auto p = this->createPeak(Q, binCount, tracer);
596 if (m_addDetectors) {
597 auto mdBox = dynamic_cast<MDBoxBase<MDE, nd> *>(box);
598 if (!mdBox) {
599 throw std::runtime_error("Failed to cast box to MDBoxBase");
600 }
601 addDetectors(*p, *mdBox);
602 }
603 if (p->getDetectorID() != -1) {
604 if (m_edge > 0) {
605 if (!edgePixel(compInfo, p->getBankName(), p->getCol(), p->getRow(), m_edge))
606 peakWS->addPeak(*p);
607 ;
608 } else {
609 peakWS->addPeak(*p);
610 }
611 g_log.information() << "Add new peak with Q-center = " << Q[0] << ", " << Q[1] << ", " << Q[2] << "\n";
612 }
613 } catch (std::exception &e) {
614 g_log.notice() << "Error creating peak at " << Q << " because of '" << e.what()
615 << "'. Peak will be skipped.\n";
616 }
617 }
618
619 // Report progress for each box found.
620 prog->report("Adding Peaks");
621
622 } // for each box found
623 }
624 }
625 g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks() << '\n';
626}
627
628//----------------------------------------------------------------------------------------------
634 size_t nd = ws->getNumDims();
635 if (nd < 3)
636 throw std::invalid_argument("Workspace must have at least 3 dimensions.");
637
638 g_log.warning("Workspace is an MDHistoWorkspace. Resultant PeaksWorkspaces "
639 "will not contain full detector information.");
640
641 // This pair is the <density, box index>
642 using dens_box = std::pair<double, size_t>;
643
644 // Map that will sort the boxes by increasing density. The key = density;
645 // value = box index.
646 std::multimap<double, size_t> sortedBoxes;
647
648 size_t numBoxes = ws->getNPoints();
649
650 // --------- Count the overall signal density -----------------------------
651 progress(0.10, "Counting Total Signal");
652 double totalSignal = 0;
653 for (size_t i = 0; i < numBoxes; i++)
654 totalSignal += ws->getSignalAt(i);
655 // Calculate the threshold density
656 double thresholdDensity =
657 (totalSignal * ws->getInverseVolume() / double(numBoxes)) * DensityThresholdFactor * m_densityScaleFactor;
658 if (!std::isfinite(thresholdDensity)) {
659 g_log.warning() << "Infinite or NaN overall density found. Your input data "
660 "may be invalid. Using a 0 threshold instead.\n";
661 thresholdDensity = 0;
662 }
663 g_log.information() << "Threshold signal density: " << thresholdDensity << '\n';
664
665 // -------------- Sort and Filter by Density -----------------------------
666 progress(0.20, "Sorting Boxes by Density");
667 for (size_t i = 0; i < numBoxes; i++) {
668 double density = ws->getSignalNormalizedAt(i) * m_densityScaleFactor;
669 // Skip any boxes with too small a signal density.
670 if (density > thresholdDensity)
671 sortedBoxes.insert(dens_box(density, i));
672 }
673
674 // --------------- Find Peak Boxes -----------------------------
675 // List of chosen possible peak boxes.
676 std::vector<size_t> peakBoxes;
677
678 prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
679
680 int64_t numBoxesFound = 0;
681 // Now we go (backwards) through the map
682 // e.g. from highest density down to lowest density.
683 std::multimap<double, size_t>::reverse_iterator it2;
684 auto it2_end = sortedBoxes.rend();
685 for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
686 signal_t density = it2->first;
687 size_t index = it2->second;
688 // Get the center of the box
689 VMD boxCenter = ws->getCenter(index);
690
691 // Compare to all boxes already picked.
692 bool badBox = false;
693 for (auto &peakBoxe : peakBoxes) {
694 VMD otherCenter = ws->getCenter(peakBoxe);
695
696 // Distance between this box and a box we already put in.
697 coord_t distSquared = 0.0;
698 for (size_t d = 0; d < nd; d++) {
699 coord_t dist = otherCenter[d] - boxCenter[d];
700 distSquared += (dist * dist);
701 }
702
703 // Reject this box if it is too close to another previously found box.
704 if (distSquared < peakRadiusSquared) {
705 badBox = true;
706 break;
707 }
708 }
709
710 // The box was not rejected for another reason.
711 if (!badBox) {
712 if (numBoxesFound++ >= m_maxPeaks) {
713 g_log.notice() << "Number of peaks found exceeded the limit of " << m_maxPeaks << ". Stopping peak finding.\n";
714 break;
715 }
716
717 peakBoxes.emplace_back(index);
718 g_log.debug() << "Found box at index " << index;
719 g_log.debug() << "; Density = " << density << '\n';
720 // Report progres for each box found.
721 prog->report("Finding Peaks");
722 }
723 }
724
725 if (ws->getNumExperimentInfo() == 0) {
726 // --- Convert the "boxes" to peaks ----
727 for (auto index : peakBoxes) {
728 // The center of the box = Q in the lab frame
729 VMD boxCenter = ws->getCenter(index);
730
731 // Q of the centroid of the box
732 V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
733
734 // The "bin count" used will be the box density.
735 double binCount = ws->getSignalNormalizedAt(index) * m_densityScaleFactor;
736
737 // Create the peak
738 addLeanElasticPeak(Q, binCount);
739
740 // Report progres for each box found.
741 prog->report("Adding Peaks");
742
743 } // for each box found
744 } else {
745 for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
746 ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
747 this->readExperimentInfo(ei);
749 Geometry::ComponentInfo const &compInfo = ei->componentInfo();
750
751 // Copy the instrument, sample, run to the peaks workspace.
752 peakWS->copyExperimentInfoFrom(ei.get());
753
754 // --- Convert the "boxes" to peaks ----
755 for (auto index : peakBoxes) {
756 // The center of the box = Q in the lab frame
757 VMD boxCenter = ws->getCenter(index);
758
759 // Q of the centroid of the box
760 V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
761
762 // The "bin count" used will be the box density.
763 double binCount = ws->getSignalNormalizedAt(index) * m_densityScaleFactor;
764
765 // Create the peak
767 addLeanElasticPeak(Q, binCount, true);
768 else
769 addPeak(compInfo, Q, binCount, tracer);
770
771 // Report progres for each box found.
772 prog->report("Adding Peaks");
773
774 } // for each box found
775 }
776 }
777 g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks() << '\n';
778} // namespace MDAlgorithms
779
780//----------------------------------------------------------------------------------------------
784
785 // The MDEventWorkspace as input
786 IMDWorkspace_sptr inWS = getProperty("InputWorkspace");
787 checkWorkspaceDims(inWS);
788 MDHistoWorkspace_sptr inMDHW = std::dynamic_pointer_cast<MDHistoWorkspace>(inWS);
789 IMDEventWorkspace_sptr inMDEW = std::dynamic_pointer_cast<IMDEventWorkspace>(inWS);
790
791 bool AppendPeaks = getProperty("AppendPeaks");
792
793 uint16_t numExperimentInfo = 0;
794 if (inMDHW)
795 numExperimentInfo = inMDHW->getNumExperimentInfo();
796 else if (inMDEW)
797 numExperimentInfo = inMDEW->getNumExperimentInfo();
798
799 std::string peakType = getProperty("OutputType");
800
801 determineOutputType(peakType, numExperimentInfo);
802
803 // Output peaks workspace, create if needed
804 peakWS = getProperty("OutputWorkspace");
805
806 if (!peakWS || !AppendPeaks) {
809 else
811 }
812
813 // Other parameters
814 double PeakDistanceThreshold = getProperty("PeakDistanceThreshold");
815 peakRadiusSquared = static_cast<coord_t>(PeakDistanceThreshold * PeakDistanceThreshold);
816
817 DensityThresholdFactor = getProperty("DensityThresholdFactor");
818 m_signalThresholdFactor = getProperty("SignalThresholdFactor");
819
820 std::string strategy = getProperty("PeakFindingStrategy");
822
823 m_maxPeaks = getProperty("MaxPeaks");
824 m_edge = this->getProperty("EdgePixels");
825
826 // Execute the proper algo based on the type of workspace
827 if (inMDHW) {
828 this->findPeaksHisto(inMDHW);
829 } else if (inMDEW) {
830 CALL_MDEVENT_FUNCTION3(this->findPeaks, inMDEW);
831 } else {
832 throw std::runtime_error("This algorithm can only find peaks on a "
833 "MDHistoWorkspace or a MDEventWorkspace; it does "
834 "not work on a regular MatrixWorkspace.");
835 }
836
837 // Do a sort by bank name (if peaks of class "Peak") and then descending bin count (intensity)
838 std::vector<std::pair<std::string, bool>> criteria;
839 criteria.emplace_back("RunNumber", true);
840 auto isPeaksWorkspace = std::dynamic_pointer_cast<PeaksWorkspace>(peakWS);
841 if (isPeaksWorkspace)
842 criteria.emplace_back("BankName", true);
843 criteria.emplace_back("bincount", false);
844 peakWS->sort(criteria);
845
846 for (auto i = 0; i != peakWS->getNumberPeaks(); ++i) {
847 Mantid::Geometry::IPeak &p = peakWS->getPeak(i);
848 p.setPeakNumber(i + 1);
849 }
850 // Save the output
851 setProperty("OutputWorkspace", peakWS);
852}
853
854//----------------------------------------------------------------------------------------------
857std::map<std::string, std::string> FindPeaksMD::validateInputs() {
858 std::map<std::string, std::string> result;
859 // Check for number of event normalzation
860 std::string strategy = getProperty("PeakFindingStrategy");
861
862 const bool useNumberOfEventsNormalization = strategy == numberOfEventsNormalization;
863 IMDWorkspace_sptr inWS = getProperty("InputWorkspace");
864 IMDEventWorkspace_sptr inMDEW = std::dynamic_pointer_cast<IMDEventWorkspace>(inWS);
865 MDHistoWorkspace_sptr inMDHW = std::dynamic_pointer_cast<MDHistoWorkspace>(inWS);
866
867 if (useNumberOfEventsNormalization && !inMDEW) {
868 result["PeakFindingStrategy"] = "The NumberOfEventsNormalization selection "
869 "can only be used with an MDEventWorkspace "
870 "as the input.";
871 }
872
873 uint16_t numExperimentInfo = 0;
874 if (inMDHW)
875 numExperimentInfo = inMDHW->getNumExperimentInfo();
876 else if (inMDEW)
877 numExperimentInfo = inMDEW->getNumExperimentInfo();
878
879 std::string peakType = getProperty("OutputType");
880
881 if (peakType == "Peak" && numExperimentInfo == 0)
882 result["OutputType"] = "The InputWorkspace doesn't contain any experiment information so the "
883 "OutputType cannot be Peak.";
884
885 return result;
886}
887
888} // namespace Mantid::MDAlgorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double value
The value of the point.
Definition FitMW.cpp:51
std::map< DeltaEMode::Type, std::string > index
#define CALL_MDEVENT_FUNCTION3(funcname, workspace)
Macro that makes it possible to call a templated method for a MDEventWorkspace using a IMDEventWorksp...
int count
counter
Definition Matrix.cpp:37
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
virtual void calculateCentroid(coord_t *) const =0
Calculate the centroid of this box and all sub-boxes.
virtual signal_t getSignalByNEvents() const
Definition IMDNode.h:266
virtual size_t getNumChildren() const =0
Get the # of children MDBoxBase'es (non-recursive)
virtual coord_t * getCentroid() const =0
Get the centroid of this box and all sub-boxes.
virtual uint64_t getNPoints() const =0
Get total number of points both in memory and on file if present;.
ExperimentInfo_sptr getExperimentInfo(const uint16_t expInfoIndex)
Get the ExperimentInfo for the given Experiment-Info Index.
A property class for workspaces.
The class LeanElasticPeaksWorkspace stores information about a set of SCD lean peaks.
Templated super-class of a multi-dimensional event "box".
Definition MDBoxBase.h:50
signal_t getSignalNormalized() const override
Returns the integrated signal from all points within, normalized for the cell volume.
Definition MDBoxBase.h:274
Templated class for a multi-dimensional event "box".
Definition MDBox.h:45
std::vector< MDE > & getEvents()
Get vector of events to change.
Definition MDBox.hxx:243
std::shared_ptr< MDEventWorkspace< MDE, nd > > sptr
Typedef for a shared pointer of this kind of event workspace.
const std::string id() const override
Get the data type (id) of the workspace.
Templated class holding data about a neutron detection event in N-dimensions (for example,...
Definition MDEvent.h:40
Templated class holding data about a neutron detection event in N-dimensions (for example,...
Definition MDLeanEvent.h:64
Structure describing a single-crystal peak.
Definition Peak.h:34
void addContributingDetID(const int id)
Add a detector ID that contributed to this peak.
Definition Peak.cpp:272
The class PeaksWorkspace stores information about a set of SCD peaks.
ComponentInfo : Provides a component centric view on to the instrument.
Class to represent a particular goniometer setting, which is described by the rotation matrix.
Definition Goniometer.h:55
std::vector< double > getEulerAngles(const std::string &convention="YZX")
Return Euler angles acording to a convention.
const Kernel::DblMatrix & getR() const
Return global rotation matrix.
void calcFromQSampleAndWavelength(const Mantid::Kernel::V3D &Q, double wavelength, bool flip_x=false, bool inner=false)
Calculate goniometer for rotation around y-asix for constant wavelength from Q Sample.
Structure describing a single-crystal peak.
Definition IPeak.h:26
virtual void setPeakNumber(int m_PeakNumber)=0
This class is responsible for tracking rays and accumulating a list of objects that are intersected a...
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void setPropertySettings(const std::string &name, std::unique_ptr< IPropertySettings const > settings)
Add a PropertySettings instance to the chain of settings for a given property.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
Numerical Matrix class.
Definition Matrix.h:42
The concrete, templated class for properties.
Class for 3D vectors.
Definition V3D.h:34
coord_t peakRadiusSquared
Estimated radius of peaks. Boxes closer than this are rejected.
Definition FindPeaksMD.h:88
eDimensionType dimType
Dimension type.
double m_signalThresholdFactor
Signal density factor.
void addPeak(const Geometry::ComponentInfo &compInfo, const Mantid::Kernel::V3D &Q, const double binCount, const Geometry::InstrumentRayTracer &tracer)
Adds a peak based on Q, bin count & a set of detector IDs.
bool m_useNumberOfEventsNormalization
Use number of events normalization for event workspaces.
std::shared_ptr< DataObjects::Peak > createPeak(const Mantid::Kernel::V3D &Q, const double binCount, const Geometry::InstrumentRayTracer &tracer)
Adds a peak based on Q, bin count.
void init() override
Initialise the properties.
void findPeaksHisto(const Mantid::DataObjects::MDHistoWorkspace_sptr &ws)
Run find peaks on a histo workspace.
void readExperimentInfo(const Mantid::API::ExperimentInfo_sptr &ei)
Read member variables from experiment info.
void exec() override
Run the algorithm.
void checkWorkspaceDims(const Mantid::API::IMDWorkspace_sptr &ws)
double DensityThresholdFactor
Thresholding factor.
Definition FindPeaksMD.h:91
signal_t m_densityScaleFactor
Arbitrary scaling factor for density to make more manageable numbers, especially for older file forma...
void determineOutputType(const std::string &peakType, const uint16_t numExperimentInfo)
int64_t m_maxPeaks
Max # of peaks.
Definition FindPeaksMD.h:94
void findPeaks(typename DataObjects::MDEventWorkspace< MDE, nd >::sptr ws)
Run find peaks on an MDEventWorkspace.
bool m_addDetectors
Flag to include the detectors within the peak.
std::unique_ptr< Mantid::API::Progress > prog
Progress reporter.
Mantid::API::IPeaksWorkspace_sptr peakWS
Output PeaksWorkspace.
Definition FindPeaksMD.h:85
static const std::string numberOfEventsNormalization
NumberOfEventNormalization.
std::map< std::string, std::string > validateInputs() override
Validate the inputs.
int m_edge
Number of edge pixels with no peaks.
Definition FindPeaksMD.h:97
Mantid::Kernel::Matrix< double > m_goniometer
Goniometer matrix.
std::shared_ptr< DataObjects::LeanElasticPeak > createLeanElasticPeak(const Mantid::Kernel::V3D &Q, const double binCount, const bool useGoniometer=false)
Adds a peak based on Q, bin count.
int m_runNumber
Run number of the peaks.
static const std::string volumeNormalization
VolumeNormalization.
Mantid::Geometry::Instrument_const_sptr m_inst
Instrument.
void addLeanElasticPeak(const Mantid::Kernel::V3D &Q, const double binCount, const bool useGoniometer=false)
Adds a peak based on Q, bin count.
std::shared_ptr< IMDEventWorkspace > IMDEventWorkspace_sptr
Shared pointer to Mantid::API::IMDEventWorkspace.
std::shared_ptr< ExperimentInfo > ExperimentInfo_sptr
Shared pointer to ExperimentInfo.
std::shared_ptr< IMDWorkspace > IMDWorkspace_sptr
Shared pointer to the IMDWorkspace base class.
void MANTID_DATAHANDLING_DLL addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::vector< std::string > &detectorNames)
Adds detector info to the sas group.
std::shared_ptr< PeaksWorkspace > PeaksWorkspace_sptr
Typedef for a shared pointer to a peaks workspace.
std::shared_ptr< LeanElasticPeaksWorkspace > LeanElasticPeaksWorkspace_sptr
Typedef for a shared pointer to a peaks workspace.
std::shared_ptr< MDHistoWorkspace > MDHistoWorkspace_sptr
A shared pointer to a MDHistoWorkspace.
MANTID_GEOMETRY_DLL bool edgePixel(ComponentInfo const &info, const std::string &bankName, int col, int row, int Edge)
Function to find peaks near detector edge.
Definition EdgePixel.cpp:24
float coord_t
Typedef for the data type to use for coordinate axes in MD objects such as MDBox, MDEventWorkspace,...
Definition MDTypes.h:27
double signal_t
Typedef for the signal recorded in a MDBox, etc.
Definition MDTypes.h:36
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54