Mantid
Loading...
Searching...
No Matches
IntegratePeaksMD2.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 +
9#include "MantidAPI/Column.h"
16#include "MantidAPI/Progress.h"
17#include "MantidAPI/Run.h"
18#include "MantidAPI/TableRow.h"
19#include "MantidAPI/TextAxis.h"
31#include "MantidHistogramData/LinearGenerator.h"
37#include "MantidKernel/Utils.h"
41
42#include "boost/math/distributions.hpp"
43
44#include <cmath>
45#include <fstream>
46#include <gsl/gsl_integration.h>
47
48namespace Mantid::MDAlgorithms {
49
50// Register the algorithm into the AlgorithmFactory
51DECLARE_ALGORITHM(IntegratePeaksMD2)
52
53using namespace Mantid::HistogramData;
54using namespace Mantid::Kernel;
55using namespace Mantid::API;
56using namespace Mantid::DataObjects;
57using namespace Mantid::DataObjects;
58using namespace Mantid::Geometry;
59
63 declareProperty(std::make_unique<WorkspaceProperty<IMDEventWorkspace>>("InputWorkspace", "", Direction::Input),
64 "An input MDEventWorkspace.");
65
66 auto radiiValidator = std::make_shared<ArrayBoundedValidator<double>>();
67 radiiValidator->setLower(0.0);
68 radiiValidator->setLowerExclusive(true);
69 declareProperty(std::make_unique<ArrayProperty<double>>("PeakRadius", std::vector<double>({1.0}), radiiValidator,
71 "Fixed radius around each peak position in which to integrate, or the "
72 "semi-axis lengths (a,b,c) describing an ellipsoid shape used for "
73 "integration (in the same units as the workspace).");
74
75 radiiValidator->setLowerExclusive(false);
76 declareProperty(std::make_unique<ArrayProperty<double>>("BackgroundInnerRadius", std::vector<double>({0.0}),
77 radiiValidator, Direction::Input),
78 "Inner radius, or three values for semi-axis lengths (a,b,c) of the "
79 "ellipsoid shape, used to evaluate the background of the peak.\n"
80 "If smaller than PeakRadius, then we assume BackgroundInnerRadius = "
81 "PeakRadius.");
82
83 declareProperty(std::make_unique<ArrayProperty<double>>("BackgroundOuterRadius", std::vector<double>({0.0}),
84 radiiValidator, Direction::Input),
85 "Outer radius, or three values for semi-axis lengths (a,b,c) of the "
86 "ellipsoid shape, to use to evaluate the background of the peak.\n"
87 "The signal density around the peak (BackgroundInnerRadius < r < "
88 "BackgroundOuterRadius) is used to estimate the background under the "
89 "peak.\n"
90 "If smaller than PeakRadius, no background measurement is done.");
91
92 declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>("PeaksWorkspace", "", Direction::Input),
93 "A PeaksWorkspace containing the peaks to integrate.");
94
95 declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>("OutputWorkspace", "", Direction::Output),
96 "The output PeaksWorkspace will be a copy of the input PeaksWorkspace "
97 "with the peaks' integrated intensities.");
98
99 declareProperty("ReplaceIntensity", true,
100 "Always replace intensity in PeaksWorkspacem (default).\n"
101 "If false, then do not replace intensity if calculated value "
102 "is 0 (used for SNSSingleCrystalReduction)");
103
104 declareProperty("IntegrateIfOnEdge", true,
105 "Only warning if all of peak outer radius is not on detector (default).\n"
106 "If false, do not integrate if the outer radius is not on a detector.");
107
108 declareProperty("AdaptiveQBackground", false,
109 "Default is false. If true, "
110 "BackgroundOuterRadius + AdaptiveQMultiplier * **|Q|** and "
111 "BackgroundInnerRadius + AdaptiveQMultiplier * **|Q|**");
112
113 declareProperty("Ellipsoid", false, "Default is sphere.");
114
115 declareProperty("FixQAxis", false, "Fix one axis of ellipsoid to be along direction of Q.");
116
117 declareProperty("Cylinder", false, "Default is sphere. Use next five parameters for cylinder.");
118
119 declareProperty(std::make_unique<PropertyWithValue<double>>("CylinderLength", 0.0, Direction::Input),
120 "Length of cylinder in which to integrate (in the same units as the "
121 "workspace).");
122
123 declareProperty(std::make_unique<PropertyWithValue<double>>("PercentBackground", 0.0, Direction::Input),
124 "Percent of CylinderLength that is background (20 is 20%)");
125
126 std::vector<std::string> peakNames = FunctionFactory::Instance().getFunctionNames<IPeakFunction>();
127 peakNames.emplace_back("NoFit");
128 declareProperty("ProfileFunction", "Gaussian", std::make_shared<StringListValidator>(peakNames),
129 "Fitting function for profile that is used only with "
130 "Cylinder integration.");
131
132 std::vector<std::string> integrationOptions(2);
133 integrationOptions[0] = "Sum";
134 integrationOptions[1] = "GaussianQuadrature";
135 auto integrationvalidator = std::make_shared<StringListValidator>(integrationOptions);
136 declareProperty("IntegrationOption", "GaussianQuadrature", integrationvalidator,
137 "Integration method for calculating intensity "
138 "used only with Cylinder integration.");
139
140 declareProperty(std::make_unique<FileProperty>("ProfilesFile", "", FileProperty::OptionalSave,
141 std::vector<std::string>(1, "profiles")),
142 "Save (Optionally) as Isaw peaks file with profiles included");
143
144 declareProperty("AdaptiveQMultiplier", 0.0,
145 "PeakRadius + AdaptiveQMultiplier * **|Q|** "
146 "so each peak has a "
147 "different integration radius. Q includes the 2*pi factor.");
148
149 declareProperty("CorrectIfOnEdge", false,
150 "Only warning if all of peak outer radius is not on detector (default).\n"
151 "If false, correct for volume off edge for both background and "
152 "intensity (the peak is assumed uniform Gaussian so this only applies "
153 "to spherical integration).");
154
155 declareProperty("UseOnePercentBackgroundCorrection", true,
156 "If this options is enabled, then the top 1% of the "
157 "background will be removed"
158 "before the background subtraction.");
159
160 // continued ellipsoid args
161 declareProperty("FixMajorAxisLength", true,
162 "This option is ignored if all peak radii are specified. "
163 "Otherwise, if True the ellipsoid radidi (proportional to "
164 "the sqrt of the eigenvalues of the covariance matrix) are "
165 "scaled such that the major axis radius is equal to the "
166 "PeakRadius. If False then the ellipsoid radii are set to "
167 "3 times the sqrt of the eigenvalues of the covariance "
168 "matrix");
169
170 declareProperty("UseCentroid", false,
171 "Perform integration on estimated centroid not peak position "
172 "(ignored if all three peak radii are specified).");
173
174 auto maxIterValidator = std::make_shared<BoundedValidator<int>>();
175 maxIterValidator->setLower(1);
176 declareProperty("MaxIterations", 1, maxIterValidator,
177 "Number of iterations in covariance estimation (ignored if all "
178 "peak radii are specified). 2-3 should be sufficient.");
179
181 "MaskEdgeTubes", true,
182 "Mask tubes on the edge of all banks in the PeaksWorkspace instrument (note the edge pixels at top/bottom of all "
183 "tubes will always be masked even if this property is False). Note the algorithm will treat "
184 "any masked pixels as edges (including pixels already masked prior to the execution of this algorithm) - this "
185 "means a custom mask can be applied to the PeaksWorkspace before integration.");
186
187 // Group Properties
188 std::string general_grp = "General Inputs";
189 std::string cylin_grp = "Cylindrical Integration";
190 std::string ellip_grp = "Ellipsoid Integration";
191
192 setPropertyGroup("InputWorkspace", general_grp);
193 setPropertyGroup("PeakRadius", general_grp);
194 setPropertyGroup("BackgroundInnerRadius", general_grp);
195 setPropertyGroup("BackgroundOuterRadius", general_grp);
196 setPropertyGroup("PeaksWorkspace", general_grp);
197 setPropertyGroup("OutputWorkspace", general_grp);
198 setPropertyGroup("ReplaceIntensity", general_grp);
199 setPropertyGroup("IntegrateIfOnEdge", general_grp);
200 setPropertyGroup("AdaptiveQBackground", general_grp);
201
202 setPropertyGroup("Ellipsoid", ellip_grp);
203 setPropertyGroup("FixQAxis", ellip_grp);
204
205 setPropertyGroup("Cylinder", cylin_grp);
206 setPropertyGroup("CylinderLength", cylin_grp);
207 setPropertyGroup("PercentBackground", cylin_grp);
208 setPropertyGroup("ProfileFunction", cylin_grp);
209 setPropertyGroup("IntegrationOption", cylin_grp);
210 setPropertyGroup("ProfilesFile", cylin_grp);
211
212 setPropertyGroup("AdaptiveQMultiplier", general_grp);
213 setPropertyGroup("CorrectIfOnEdge", general_grp);
214 setPropertyGroup("UseOnePercentBackgroundCorrection", general_grp);
215
216 setPropertyGroup("FixMajorAxisLength", ellip_grp);
217 setPropertyGroup("UseCentroid", ellip_grp);
218 setPropertyGroup("MaxIterations", ellip_grp);
219
220 setPropertyGroup("MaskEdgeTubes", general_grp);
221
222 // SetValue when another property value changes
223 setPropertySettings("Ellipsoid", std::make_unique<SetValueWhenProperty>(
224 "Cylinder", [](std::string ellipsoid, const std::string &cylinder) {
225 // Set Ellipsoid to 0, if user has set Cylinder to 1
226 if (ellipsoid == "1" && cylinder == "1") {
227 return std::string{"0"};
228 } else {
229 return ellipsoid;
230 };
231 }));
232 setPropertySettings("Cylinder", std::make_unique<SetValueWhenProperty>(
233 "Ellipsoid", [](std::string cylinder, const std::string &ellipsoid) {
234 // Set Cylinder to 0, if user has set Ellipsoid to 1
235 if (cylinder == "1" && ellipsoid == "1") {
236 return std::string{"0"};
237 } else {
238 return cylinder;
239 };
240 }));
241
242 // Set these Properties as visible only when Cylinder = 1
243 setPropertySettings("CylinderLength", std::make_unique<VisibleWhenProperty>("Cylinder", IS_EQUAL_TO, "1"));
244 setPropertySettings("PercentBackground", std::make_unique<VisibleWhenProperty>("Cylinder", IS_EQUAL_TO, "1"));
245 setPropertySettings("ProfileFunction", std::make_unique<VisibleWhenProperty>("Cylinder", IS_EQUAL_TO, "1"));
246 setPropertySettings("IntegrationOption", std::make_unique<VisibleWhenProperty>("Cylinder", IS_EQUAL_TO, "1"));
247 setPropertySettings("ProfilesFile", std::make_unique<VisibleWhenProperty>("Cylinder", IS_EQUAL_TO, "1"));
248
249 // Set these Properties as visible only when Ellipsoid = 1
250 setPropertySettings("FixQAxis", std::make_unique<VisibleWhenProperty>("Ellipsoid", IS_EQUAL_TO, "1"));
251 setPropertySettings("FixMajorAxisLength", std::make_unique<VisibleWhenProperty>("Ellipsoid", IS_EQUAL_TO, "1"));
252 setPropertySettings("UseCentroid", std::make_unique<VisibleWhenProperty>("Ellipsoid", IS_EQUAL_TO, "1"));
253 setPropertySettings("MaxIterations", std::make_unique<VisibleWhenProperty>("Ellipsoid", IS_EQUAL_TO, "1"));
254
255 // Disable / greyed out these Properties based on the value of another
256 setPropertySettings("CorrectIfOnEdge",
257 std::make_unique<Kernel::EnabledWhenProperty>("IntegrateIfOnEdge", IS_EQUAL_TO, "1"));
258}
259
260std::map<std::string, std::string> IntegratePeaksMD2::validateInputs() {
261 std::map<std::string, std::string> result;
262
263 std::vector<double> PeakRadius = getProperty("PeakRadius");
264 std::vector<double> BackgroundInnerRadius = getProperty("BackgroundInnerRadius");
265 std::vector<double> BackgroundOuterRadius = getProperty("BackgroundOuterRadius");
266 bool ellipsoid = getProperty("Ellipsoid");
267 bool cylinder = getProperty("Cylinder");
268
269 if (PeakRadius.size() != 1 && PeakRadius.size() != 3) {
270 std::stringstream errmsg;
271 errmsg << "Only one or three values should be specified";
272 result["PeakRadius"] = errmsg.str();
273 }
274
275 if (!ellipsoid && PeakRadius.size() != 1) {
276 std::stringstream errmsg;
277 errmsg << "One value must be specified when Ellipsoid is false";
278 result["PeakRadius"] = errmsg.str();
279 }
280
281 if (BackgroundInnerRadius.size() != 1 && BackgroundInnerRadius.size() != 3) {
282 std::stringstream errmsg;
283 errmsg << "Only one or three values should be specified";
284 result["BackgroundInnerRadius"] = errmsg.str();
285 }
286
287 if (!ellipsoid && BackgroundInnerRadius.size() != 1) {
288 std::stringstream errmsg;
289 errmsg << "One value must be specified when Ellipsoid is false";
290 result["BackgroundInnerRadius"] = errmsg.str();
291 }
292
293 if (BackgroundOuterRadius.size() != 1 && BackgroundOuterRadius.size() != 3) {
294 std::stringstream errmsg;
295 errmsg << "Only one or three values should be specified";
296 result["BackgroundOuterRadius"] = errmsg.str();
297 }
298
299 if (!ellipsoid && BackgroundOuterRadius.size() != 1) {
300 std::stringstream errmsg;
301 errmsg << "One value must be specified when Ellipsoid is false";
302 result["BackgroundOuterRadius"] = errmsg.str();
303 }
304
305 if (ellipsoid && cylinder) {
306 std::stringstream errmsg;
307 errmsg << "Ellipsoid and Cylinder cannot both be true";
308 result["Ellipsoid"] = errmsg.str();
309 result["Cylinder"] = errmsg.str();
310 }
311
312 return result;
313}
314
315//----------------------------------------------------------------------------------------------
320template <typename MDE, size_t nd> void IntegratePeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
321 if (nd != 3)
322 throw std::invalid_argument("For now, we expect the input MDEventWorkspace "
323 "to have 3 dimensions only.");
324
326 IPeaksWorkspace_sptr inPeakWS = getProperty("PeaksWorkspace");
327
329 IPeaksWorkspace_sptr peakWS = getProperty("OutputWorkspace");
330 if (peakWS != inPeakWS)
331 peakWS = inPeakWS->clone();
332 // This only fails in the unit tests which say that MaskBTP is not registered
333 bool maskTubes = getProperty("MaskEdgeTubes");
334 try {
335 PeaksWorkspace_sptr p = std::dynamic_pointer_cast<PeaksWorkspace>(inPeakWS);
336 if (p) {
337 if (maskTubes) {
338 runMaskDetectors(p, "Tube", "edges");
339 }
340 runMaskDetectors(p, "Pixel", "edges");
341 }
342 } catch (...) {
343 g_log.error("Can't execute MaskBTP algorithm for this instrument to set "
344 "edge for IntegrateIfOnEdge option");
345 }
346
347 calculateE1(inPeakWS->detectorInfo()); // fill E1Vec for use in detectorQ
349
351 std::vector<double> PeakRadius = getProperty("PeakRadius");
353 std::vector<double> BackgroundOuterRadius = getProperty("BackgroundOuterRadius");
355 std::vector<double> BackgroundInnerRadius = getProperty("BackgroundInnerRadius");
357 bool useOnePercentBackgroundCorrection = getProperty("UseOnePercentBackgroundCorrection");
358
359 bool manualEllip = false;
360 if (PeakRadius.size() > 1) {
361 manualEllip = true;
362 // make sure the background radii are 3 values (they default to 1)
363 if (BackgroundInnerRadius.size() == 1)
364 BackgroundInnerRadius.resize(3, BackgroundInnerRadius[0]);
365 if (BackgroundOuterRadius.size() == 1)
366 BackgroundOuterRadius.resize(3, BackgroundOuterRadius[0]);
367 }
368
369 double minInnerRadius = PeakRadius[0];
370 for (size_t r = 0; r < BackgroundInnerRadius.size(); r++) {
371 if (manualEllip) {
372 minInnerRadius = PeakRadius[r];
373 }
374 if (BackgroundInnerRadius[r] < minInnerRadius)
375 BackgroundInnerRadius[r] = minInnerRadius;
376 }
377 // Ellipsoid
378 bool isEllipse = getProperty("Ellipsoid");
379 bool qAxisIsFixed = getProperty("FixQAxis");
380 bool majorAxisLengthFixed = getProperty("FixMajorAxisLength");
381 bool useCentroid = getProperty("UseCentroid");
382 int maxCovarIter = getProperty("MaxIterations");
384 double cylinderLength = getProperty("CylinderLength");
385 Workspace2D_sptr wsProfile2D, wsFit2D, wsDiff2D;
386 size_t numSteps = 0;
387 bool cylinderBool = getProperty("Cylinder");
388 bool adaptiveQBackground = getProperty("AdaptiveQBackground");
389 double adaptiveQMultiplier = getProperty("AdaptiveQMultiplier");
390 double adaptiveQBackgroundMultiplier = 0.0;
391 if (adaptiveQBackground)
392 adaptiveQBackgroundMultiplier = adaptiveQMultiplier;
393 std::vector<double> PeakRadiusVector(peakWS->getNumberPeaks(), PeakRadius[0]);
394 std::vector<double> BackgroundInnerRadiusVector(peakWS->getNumberPeaks(), BackgroundInnerRadius[0]);
395 std::vector<double> BackgroundOuterRadiusVector(peakWS->getNumberPeaks(), BackgroundOuterRadius[0]);
396 if (cylinderBool) {
397 numSteps = 100;
398 size_t histogramNumber = peakWS->getNumberPeaks();
399 Workspace_sptr wsProfile = WorkspaceFactory::Instance().create("Workspace2D", histogramNumber, numSteps, numSteps);
400 wsProfile2D = std::dynamic_pointer_cast<Workspace2D>(wsProfile);
401 AnalysisDataService::Instance().addOrReplace("ProfilesData", wsProfile2D);
402 Workspace_sptr wsFit = WorkspaceFactory::Instance().create("Workspace2D", histogramNumber, numSteps, numSteps);
403 wsFit2D = std::dynamic_pointer_cast<Workspace2D>(wsFit);
404 AnalysisDataService::Instance().addOrReplace("ProfilesFit", wsFit2D);
405 Workspace_sptr wsDiff = WorkspaceFactory::Instance().create("Workspace2D", histogramNumber, numSteps, numSteps);
406 wsDiff2D = std::dynamic_pointer_cast<Workspace2D>(wsDiff);
407 AnalysisDataService::Instance().addOrReplace("ProfilesFitDiff", wsDiff2D);
408 auto newAxis1 = std::make_unique<TextAxis>(peakWS->getNumberPeaks());
409 auto newAxis2 = std::make_unique<TextAxis>(peakWS->getNumberPeaks());
410 auto newAxis3 = std::make_unique<TextAxis>(peakWS->getNumberPeaks());
411 auto newAxis1Raw = newAxis1.get();
412 auto newAxis2Raw = newAxis2.get();
413 auto newAxis3Raw = newAxis3.get();
414 wsProfile2D->replaceAxis(1, std::move(newAxis1));
415 wsFit2D->replaceAxis(1, std::move(newAxis2));
416 wsDiff2D->replaceAxis(1, std::move(newAxis3));
417 for (int i = 0; i < peakWS->getNumberPeaks(); ++i) {
418 // Get a direct ref to that peak.
419 IPeak &p = peakWS->getPeak(i);
420 std::ostringstream label;
421 label << Utils::round(p.getH()) << "_" << Utils::round(p.getK()) << "_" << Utils::round(p.getL()) << "_"
422 << p.getRunNumber();
423 newAxis1Raw->setLabel(i, label.str());
424 newAxis2Raw->setLabel(i, label.str());
425 newAxis3Raw->setLabel(i, label.str());
426 }
427 }
428 double percentBackground = getProperty("PercentBackground");
429 size_t peakMin = 0;
430 size_t peakMax = numSteps;
431 double ratio = 0.0;
432 if (cylinderBool) {
433 peakMin = static_cast<size_t>(static_cast<double>(numSteps) * percentBackground / 100.);
434 peakMax = numSteps - peakMin - 1;
435 size_t numPeakCh = peakMax - peakMin + 1; // number of peak channels
436 size_t numBkgCh = numSteps - numPeakCh; // number of background channels
437 ratio = static_cast<double>(numPeakCh) / static_cast<double>(numBkgCh);
438 }
440 bool replaceIntensity = getProperty("ReplaceIntensity");
441 bool integrateEdge = getProperty("IntegrateIfOnEdge");
442 bool correctEdge = getProperty("CorrectIfOnEdge");
443
444 std::string profileFunction = getProperty("ProfileFunction");
445 std::string integrationOption = getProperty("IntegrationOption");
446 std::ofstream out;
447 if (cylinderBool && profileFunction != "NoFit") {
448 std::string outFile = getProperty("InputWorkspace");
449 outFile.append(profileFunction);
450 outFile.append(".dat");
451 std::string save_path = ConfigService::Instance().getString("defaultsave.directory");
452 outFile = save_path + outFile;
453 out.open(outFile.c_str(), std::ofstream::out);
454 }
455 // volume of Background sphere with inner volume subtracted
456 double volumeBkg = 4.0 / 3.0 * M_PI * (std::pow(BackgroundOuterRadius[0], 3) - std::pow(BackgroundOuterRadius[0], 3));
457 // volume of PeakRadius sphere
458 double volumeRadius = 4.0 / 3.0 * M_PI * std::pow(PeakRadius[0], 3);
459
460 // Initialize progress reporting
461 int nPeaks = peakWS->getNumberPeaks();
462 Progress progress(this, 0., 1., nPeaks);
463 bool doParallel = cylinderBool ? false : Kernel::threadSafe(*ws, *peakWS);
464 PARALLEL_FOR_IF(doParallel)
465 for (int i = 0; i < nPeaks; ++i) {
467 progress.report();
468
469 // Get a direct ref to that peak.
470 IPeak &p = peakWS->getPeak(i);
471
472 // Get the peak center as a position in the dimensions of the workspace
473 V3D pos;
474 if (CoordinatesToUse == Mantid::Kernel::QLab) //"Q (lab frame)"
475 pos = p.getQLabFrame();
476 else if (CoordinatesToUse == Mantid::Kernel::QSample) //"Q (sample frame)"
477 pos = p.getQSampleFrame();
478 else if (CoordinatesToUse == Mantid::Kernel::HKL) //"HKL"
479 pos = p.getHKL();
480
481 // Do not integrate if sphere is off edge of detector
482
483 const double edgeDist = calculateDistanceToEdge(p.getQLabFrame());
484 if (edgeDist < std::max(BackgroundOuterRadius[0], PeakRadius[0])) {
485 g_log.warning() << "Warning: sphere/cylinder for integration is off edge "
486 "of detector for peak "
487 << i << "; radius of edge = " << edgeDist << '\n';
488 if (!integrateEdge) {
489 if (replaceIntensity) {
490 p.setIntensity(0.0);
491 p.setSigmaIntensity(0.0);
492 }
493 continue;
494 }
495 }
496
497 // Build the sphere transformation
498 bool dimensionsUsed[nd];
499 coord_t center[nd];
500 for (size_t d = 0; d < nd; ++d) {
501 dimensionsUsed[d] = true; // Use all dimensions
502 center[d] = static_cast<coord_t>(pos[d]);
503 }
504 signal_t signal = 0;
505 signal_t errorSquared = 0;
506 signal_t bgSignal = 0;
507 signal_t bgErrorSquared = 0;
508 double background_total = 0.0;
509 if (!cylinderBool) {
510 // modulus of Q
511 coord_t lenQpeak = 0.0;
512 if (adaptiveQMultiplier != 0.0) {
513 lenQpeak = 0.0;
514 for (size_t d = 0; d < nd; ++d) {
515 lenQpeak += center[d] * center[d];
516 }
517 lenQpeak = std::sqrt(lenQpeak);
518 }
519 double adaptiveRadius = adaptiveQMultiplier * lenQpeak + *std::max_element(PeakRadius.begin(), PeakRadius.end());
520 if (adaptiveRadius <= 0.0) {
521 g_log.error() << "Error: Radius for integration sphere of peak " << i << " is negative = " << adaptiveRadius
522 << '\n';
523 adaptiveRadius = 0.;
524 p.setIntensity(0.0);
525 p.setSigmaIntensity(0.0);
526 PeakRadiusVector[i] = 0.0;
527 BackgroundInnerRadiusVector[i] = 0.0;
528 BackgroundOuterRadiusVector[i] = 0.0;
529 continue;
530 }
531 PeakRadiusVector[i] = adaptiveRadius;
532 BackgroundInnerRadiusVector[i] = adaptiveQBackgroundMultiplier * lenQpeak +
533 *std::max_element(BackgroundInnerRadius.begin(), BackgroundInnerRadius.end());
534 BackgroundOuterRadiusVector[i] = adaptiveQBackgroundMultiplier * lenQpeak +
535 *std::max_element(BackgroundOuterRadius.begin(), BackgroundOuterRadius.end());
536 // define the radius squared for a sphere intially
537 CoordTransformDistance getRadiusSq(nd, center, dimensionsUsed);
538 // set spherical shape
539 PeakShape *sphereShape =
540 new PeakShapeSpherical(PeakRadiusVector[i], BackgroundInnerRadiusVector[i], BackgroundOuterRadiusVector[i],
541 CoordinatesToUse, this->name(), this->version());
542 p.setPeakShape(sphereShape);
543 const double scaleFactor = pow(PeakRadiusVector[i], 3) /
544 (pow(BackgroundOuterRadiusVector[i], 3) - pow(BackgroundInnerRadiusVector[i], 3));
545 // Integrate spherical background shell if specified
546 if (BackgroundOuterRadius[0] > PeakRadius[0]) {
547 // Get the total signal inside background shell
548 ws->getBox()->integrateSphere(
549 getRadiusSq, static_cast<coord_t>(pow(BackgroundOuterRadiusVector[i], 2)), bgSignal, bgErrorSquared,
550 static_cast<coord_t>(pow(BackgroundInnerRadiusVector[i], 2)), useOnePercentBackgroundCorrection);
551 // correct bg signal by Vpeak/Vshell (same for sphere and ellipse)
552 bgSignal *= scaleFactor;
553 bgErrorSquared *= scaleFactor * scaleFactor;
554 }
555 // if ellipsoid find covariance and centroid in spherical region
556 // using one-pass algorithm from https://doi.org/10.1145/359146.359153
557 if (isEllipse) {
558 // flat bg to subtract
559 const auto bgDensity = bgSignal / (4 * M_PI * pow(PeakRadiusVector[i], 3) / 3);
560 std::array<V3D, 3> eigenvects;
561 std::array<double, 3> eigenvals;
562 V3D translation(0.0, 0.0, 0.0); // translation from peak pos to centroid
563 if (PeakRadius.size() == 1) {
564 V3D mean(0.0, 0.0, 0.0); // vector to hold centroid
565 findEllipsoid<MDE, nd>(ws, getRadiusSq, pos, static_cast<coord_t>(pow(PeakRadiusVector[i], 2)), qAxisIsFixed,
566 useCentroid, bgDensity, eigenvects, eigenvals, mean, maxCovarIter);
567 if (!majorAxisLengthFixed) {
568 // replace radius for this peak with 3*stdev along major axis
569 auto max_stdev = sqrt(*std::max_element(eigenvals.begin(), eigenvals.end()));
570 BackgroundOuterRadiusVector[i] = 3 * max_stdev * (BackgroundOuterRadiusVector[i] / PeakRadiusVector[i]);
571 BackgroundInnerRadiusVector[i] = 3 * max_stdev * (BackgroundInnerRadiusVector[i] / PeakRadiusVector[i]);
572 PeakRadiusVector[i] = 3 * max_stdev;
573 }
574 if (useCentroid) {
575 // calculate translation to apply when drawing
576 translation = mean - pos;
577 // update integration center with mean
578 for (size_t d = 0; d < 3; ++d) {
579 center[d] = static_cast<coord_t>(mean[d]);
580 }
581 }
582 } else {
583 // Use the manually specified radii instead of finding them via
584 // findEllipsoid
585 std::transform(PeakRadius.cbegin(), PeakRadius.cend(), eigenvals.begin(),
586 [](double r) { return std::pow(r, 2.0); });
587 eigenvects[0] = V3D(1.0, 0.0, 0.0);
588 eigenvects[1] = V3D(0.0, 1.0, 0.0);
589 eigenvects[2] = V3D(0.0, 0.0, 1.0);
590 }
591 // transform ellispoid onto sphere of radius = R
592 getRadiusSq = CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */
593 eigenvects, eigenvals);
594 // Integrate ellipsoid background shell if specified
595 if (PeakRadius.size() == 1) {
596 if (BackgroundOuterRadius[0] > PeakRadius[0]) {
597 // Get the total signal inside "BackgroundOuterRadius"
598 bgSignal = 0;
599 bgErrorSquared = 0;
600 ws->getBox()->integrateSphere(
601 getRadiusSq, static_cast<coord_t>(pow(BackgroundOuterRadiusVector[i], 2)), bgSignal, bgErrorSquared,
602 static_cast<coord_t>(pow(BackgroundInnerRadiusVector[i], 2)), useOnePercentBackgroundCorrection);
603 // correct bg signal by Vpeak/Vshell (same as previously
604 // calculated for sphere)
605 bgSignal *= scaleFactor;
606 bgErrorSquared *= scaleFactor * scaleFactor;
607 }
608 // set peak shape
609 // get radii in same proprtion as eigenvalues
610 auto max_stdev = pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
611 PeakEllipsoidExtent peakRadii{0., 0., 0.};
612 PeakEllipsoidExtent backgroundInnerRadii{0., 0., 0.};
613 PeakEllipsoidExtent backgroundOuterRadii{0., 0., 0.};
614 for (size_t irad = 0; irad < peakRadii.size(); irad++) {
615 auto scale = pow(eigenvals[irad], 0.5) / max_stdev;
616 peakRadii[irad] = PeakRadiusVector[i] * scale;
617 backgroundInnerRadii[irad] = BackgroundInnerRadiusVector[i] * scale;
618 backgroundOuterRadii[irad] = BackgroundOuterRadiusVector[i] * scale;
619 }
620 PeakShape *ellipsoidShape =
621 new PeakShapeEllipsoid(eigenvects, peakRadii, backgroundInnerRadii, backgroundOuterRadii,
622 CoordinatesToUse, this->name(), this->version(), translation);
623 p.setPeakShape(ellipsoidShape);
624 } else {
625 // Use the manually specified radii instead of finding them via
626 // findEllipsoid
627 std::array<double, 3> eigenvals_background_inner;
628 std::array<double, 3> eigenvals_background_outer;
629 std::transform(BackgroundInnerRadius.cbegin(), BackgroundInnerRadius.cend(),
630 eigenvals_background_inner.begin(), [](double r) { return std::pow(r, 2.0); });
631 std::transform(BackgroundOuterRadius.cbegin(), BackgroundOuterRadius.cend(),
632 eigenvals_background_outer.begin(), [](double r) { return std::pow(r, 2.0); });
633
634 if (BackgroundOuterRadiusVector[0] > PeakRadiusVector[0]) {
635 // transform ellispoid onto sphere of radius = R
636 auto getRadiusSqInner = CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */
637 eigenvects, eigenvals_background_inner);
638 auto getRadiusSqOuter = CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */
639 eigenvects, eigenvals_background_outer);
640 // Get the total signal inside "BackgroundOuterRadius"
641 bgSignal = 0;
642 bgErrorSquared = 0;
643 signal_t bgSignalInner = 0;
644 signal_t bgSignalOuter = 0;
645 signal_t bgErrorSquaredInner = 0;
646 signal_t bgErrorSquaredOuter = 0;
647 ws->getBox()->integrateSphere(getRadiusSqInner,
648 static_cast<coord_t>(pow(BackgroundInnerRadiusVector[i], 2)), bgSignalInner,
649 bgErrorSquaredInner, 0.0, useOnePercentBackgroundCorrection);
650 ws->getBox()->integrateSphere(getRadiusSqOuter,
651 static_cast<coord_t>(pow(BackgroundOuterRadiusVector[i], 2)), bgSignalOuter,
652 bgErrorSquaredOuter, 0.0, useOnePercentBackgroundCorrection);
653 // correct bg signal by Vpeak/Vshell (same as previously
654 // calculated for sphere)
655 bgSignal = bgSignalOuter - bgSignalInner;
656 bgErrorSquared = bgErrorSquaredInner + bgErrorSquaredOuter;
657 g_log.debug() << "unscaled background signal from ellipsoid integration = " << bgSignal << '\n';
658 const double scale = (PeakRadius[0] * PeakRadius[1] * PeakRadius[2]) /
659 (BackgroundOuterRadius[0] * BackgroundOuterRadius[1] * BackgroundOuterRadius[2] -
660 BackgroundInnerRadius[0] * BackgroundInnerRadius[1] * BackgroundInnerRadius[2]);
661 bgSignal *= scale;
662 bgErrorSquared *= pow(scale, 2);
663 }
664 // set peak shape
665 // get radii in same proprtion as eigenvalues
666 auto max_stdev = pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
667 auto max_stdev_inner =
668 pow(*std::max_element(eigenvals_background_inner.begin(), eigenvals_background_inner.end()), 0.5);
669 auto max_stdev_outer =
670 pow(*std::max_element(eigenvals_background_outer.begin(), eigenvals_background_outer.end()), 0.5);
671 PeakEllipsoidExtent peakRadii{0., 0., 0.};
672 PeakEllipsoidExtent backgroundInnerRadii{0., 0., 0.};
673 PeakEllipsoidExtent backgroundOuterRadii{0., 0., 0.};
674 for (size_t irad = 0; irad < peakRadii.size(); irad++) {
675 peakRadii[irad] = PeakRadiusVector[i] * pow(eigenvals[irad], 0.5) / max_stdev;
676 backgroundInnerRadii[irad] =
677 BackgroundInnerRadiusVector[i] * pow(eigenvals_background_inner[irad], 0.5) / max_stdev_inner;
678 backgroundOuterRadii[irad] =
679 BackgroundOuterRadiusVector[i] * pow(eigenvals_background_outer[irad], 0.5) / max_stdev_outer;
680 }
681 PeakShape *ellipsoidShape =
682 new PeakShapeEllipsoid(eigenvects, peakRadii, backgroundInnerRadii, backgroundOuterRadii,
683 CoordinatesToUse, this->name(), this->version());
684 p.setPeakShape(ellipsoidShape);
685 }
686 }
687 ws->getBox()->integrateSphere(getRadiusSq, static_cast<coord_t>(PeakRadiusVector[i] * PeakRadiusVector[i]),
688 signal, errorSquared, 0.0 /* innerRadiusSquared */,
689 useOnePercentBackgroundCorrection);
690 //
691 } else {
692 CoordTransformDistance cylinder(nd, center, dimensionsUsed, 2);
693
694 // Perform the integration into whatever box is contained within.
695 Counts signal_fit(numSteps);
696 signal_fit = 0;
697
698 ws->getBox()->integrateCylinder(cylinder, static_cast<coord_t>(PeakRadius[0]),
699 static_cast<coord_t>(cylinderLength), signal, errorSquared,
700 signal_fit.mutableRawData());
701
702 // Integrate around the background radius
703 if (BackgroundOuterRadius[0] > PeakRadius[0]) {
704 // Get the total signal inside "BackgroundOuterRadius"
705 signal_fit = 0;
706
707 ws->getBox()->integrateCylinder(cylinder, static_cast<coord_t>(BackgroundOuterRadius[0]),
708 static_cast<coord_t>(cylinderLength), bgSignal, bgErrorSquared,
709 signal_fit.mutableRawData());
710
711 Points points(signal_fit.size(), LinearGenerator(0, 1));
712 wsProfile2D->setHistogram(i, points, signal_fit);
713
714 // Evaluate the signal inside "BackgroundInnerRadius"
715 signal_t interiorSignal = 0;
716 signal_t interiorErrorSquared = 0;
717
718 // Integrate this 3rd radius, if needed
719 if (BackgroundInnerRadius[0] != PeakRadius[0]) {
720 ws->getBox()->integrateCylinder(cylinder, static_cast<coord_t>(BackgroundInnerRadius[0]),
721 static_cast<coord_t>(cylinderLength), interiorSignal, interiorErrorSquared,
722 signal_fit.mutableRawData());
723 } else {
724 // PeakRadius == BackgroundInnerRadius, so use the previous
725 // value
726 interiorSignal = signal;
727 interiorErrorSquared = errorSquared;
728 }
729 // Subtract the peak part to get the intensity in the shell
730 // (BackgroundInnerRadius < r < BackgroundOuterRadius)
731 bgSignal -= interiorSignal;
732 // We can subtract the error (instead of adding) because the two
733 // values are 100% dependent; this is the same as integrating a
734 // shell.
735 bgErrorSquared -= interiorErrorSquared;
736 // Relative volume of peak vs the BackgroundOuterRadius cylinder
737 const double radiusRatio = (PeakRadius[0] / BackgroundOuterRadius[0]);
738 const double peakVolume = radiusRatio * radiusRatio * cylinderLength;
739
740 // Relative volume of the interior of the shell vs overall
741 // background
742 const double interiorRatio = (BackgroundInnerRadius[0] / BackgroundOuterRadius[0]);
743 // Volume of the bg shell, relative to the volume of the
744 // BackgroundOuterRadius cylinder
745 const double bgVolume = 1.0 - interiorRatio * interiorRatio * cylinderLength;
746
747 // Finally, you will multiply the bg intensity by this to get the
748 // estimated background under the peak volume
749 const double scaleFactor = peakVolume / bgVolume;
750 bgSignal *= scaleFactor;
751 bgErrorSquared *= scaleFactor * scaleFactor;
752 } else {
753 Points points(signal_fit.size(), LinearGenerator(0, 1));
754 wsProfile2D->setHistogram(i, points, signal_fit);
755 }
756
757 if (profileFunction == "NoFit") {
758 signal = 0.;
759 for (size_t j = 0; j < numSteps; j++) {
760 if (j < peakMin || j > peakMax)
761 background_total = background_total + wsProfile2D->y(i)[j];
762 else
763 signal = signal + wsProfile2D->y(i)[j];
764 }
765 errorSquared = std::fabs(signal);
766 } else {
767
768 auto fitAlgorithm = createChildAlgorithm("Fit", -1, -1, false);
769 // fitAlgorithm->setProperty("CreateOutput", true);
770 // fitAlgorithm->setProperty("Output", "FitPeaks1D");
771 std::string myFunc = std::string("name=LinearBackground;name=") + profileFunction;
772 auto maxPeak = std::max_element(signal_fit.begin(), signal_fit.end());
773
774 std::ostringstream strs;
775 strs << maxPeak[0];
776 std::string strMax = strs.str();
777 if (profileFunction == "Gaussian") {
778 myFunc += ", PeakCentre=50, Height=" + strMax;
779 fitAlgorithm->setProperty("Constraints", "40<f1.PeakCentre<60");
780 } else if (profileFunction == "BackToBackExponential" || profileFunction == "IkedaCarpenterPV") {
781 myFunc += ", X0=50, I=" + strMax;
782 fitAlgorithm->setProperty("Constraints", "40<f1.X0<60");
783 }
784 fitAlgorithm->setProperty("CalcErrors", true);
785 fitAlgorithm->setProperty("Function", myFunc);
786 fitAlgorithm->setProperty("InputWorkspace", wsProfile2D);
787 fitAlgorithm->setProperty("WorkspaceIndex", static_cast<int>(i));
788 try {
789 fitAlgorithm->executeAsChildAlg();
790 } catch (...) {
791 g_log.error("Can't execute Fit algorithm");
792 continue;
793 }
794
795 IFunction_sptr ifun = fitAlgorithm->getProperty("Function");
796 if (i == 0) {
797 out << std::setw(20) << "spectrum"
798 << " ";
799 for (size_t j = 0; j < ifun->nParams(); ++j)
800 out << std::setw(20) << ifun->parameterName(j) << " ";
801 out << std::setw(20) << "chi2"
802 << " ";
803 out << "\n";
804 }
805 out << std::setw(20) << i << " ";
806 for (size_t j = 0; j < ifun->nParams(); ++j) {
807 out << std::setw(20) << std::fixed << std::setprecision(10) << ifun->getParameter(j) << " ";
808 }
809 double chi2 = fitAlgorithm->getProperty("OutputChi2overDoF");
810 out << std::setw(20) << std::fixed << std::setprecision(10) << chi2 << "\n";
811
812 std::shared_ptr<const CompositeFunction> fun = std::dynamic_pointer_cast<const CompositeFunction>(ifun);
813
814 const auto &x = wsProfile2D->x(i);
815 wsFit2D->setSharedX(i, wsProfile2D->sharedX(i));
816 wsDiff2D->setSharedX(i, wsProfile2D->sharedX(i));
817
818 FunctionDomain1DVector domain(x.rawData());
819 FunctionValues yy(domain);
820 fun->function(domain, yy);
821 auto funcValues = yy.toVector();
822
823 wsFit2D->mutableY(i) = funcValues;
824 wsDiff2D->setSharedY(i, wsProfile2D->sharedY(i));
825 wsDiff2D->mutableY(i) -= wsFit2D->y(i);
826
827 // Calculate intensity
828 signal = 0.0;
829 if (integrationOption == "Sum") {
830 for (size_t j = peakMin; j <= peakMax; j++)
831 if (std::isfinite(yy[j]))
832 signal += yy[j];
833 } else {
834 gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);
835
836 double error;
837
838 gsl_function F;
839 F.function = &Mantid::MDAlgorithms::f_eval2;
840 F.params = &fun;
841
842 gsl_integration_qags(&F, x[peakMin], x[peakMax], 0, 1e-7, 1000, w, &signal, &error);
843
844 gsl_integration_workspace_free(w);
845 }
846 errorSquared = std::fabs(signal);
847 // Get background counts
848 for (size_t j = 0; j < numSteps; j++) {
849 double background = ifun->getParameter(0) + ifun->getParameter(1) * x[j];
850 if (j < peakMin || j > peakMax)
851 background_total = background_total + background;
852 }
853 }
854 }
855 checkOverlap(i, peakWS, CoordinatesToUse, 2.0 * std::max(PeakRadiusVector[i], BackgroundOuterRadiusVector[i]));
856 // Save it back in the peak object.
857 if (signal != 0. || replaceIntensity) {
858 double edgeMultiplier = 1.0;
859 double peakMultiplier = 1.0;
860 if (correctEdge) {
861 if (edgeDist < BackgroundOuterRadius[0]) {
862 double e1 = BackgroundOuterRadius[0] - edgeDist;
863 // volume of cap of sphere with h = edge
864 double f1 = M_PI * std::pow(e1, 2) / 3 * (3 * BackgroundOuterRadius[0] - e1);
865 edgeMultiplier = volumeBkg / (volumeBkg - f1);
866 }
867 if (edgeDist < PeakRadius[0]) {
868 double sigma = PeakRadius[0] / 3.0;
869 // assume gaussian peak
870 double e1 = std::exp(-std::pow(edgeDist, 2) / (2 * sigma * sigma)) * PeakRadius[0];
871 // volume of cap of sphere with h = edge
872 double f1 = M_PI * std::pow(e1, 2) / 3 * (3 * PeakRadius[0] - e1);
873 peakMultiplier = volumeRadius / (volumeRadius - f1);
874 }
875 }
876
877 p.setIntensity(peakMultiplier * signal - edgeMultiplier * (ratio * background_total + bgSignal));
878 p.setSigmaIntensity(sqrt(peakMultiplier * errorSquared +
879 edgeMultiplier * (ratio * ratio * std::fabs(background_total) + bgErrorSquared)));
880 }
881
882 g_log.information() << "Peak " << i << " at " << pos << ": signal " << signal << " (sig^2 " << errorSquared
883 << "), with background " << bgSignal + ratio * background_total << " (sig^2 "
884 << bgErrorSquared + ratio * ratio * std::fabs(background_total) << ") subtracted.\n";
886 }
888 // This flag is used by the PeaksWorkspace to evaluate whether it has
889 // been integrated.
890 peakWS->mutableRun().addProperty("PeaksIntegrated", 1, true);
891 // These flags are specific to the algorithm.
892 peakWS->mutableRun().addProperty("PeakRadius", PeakRadiusVector, true);
893 peakWS->mutableRun().addProperty("BackgroundInnerRadius", BackgroundInnerRadiusVector, true);
894 peakWS->mutableRun().addProperty("BackgroundOuterRadius", BackgroundOuterRadiusVector, true);
895
896 // save profiles in peaks file
897 const std::string outfile = getProperty("ProfilesFile");
898 if (outfile.length() > 0) {
899 IAlgorithm_sptr alg;
900 try {
901 alg = createChildAlgorithm("SaveIsawPeaks", -1, -1, false);
902 } catch (Exception::NotFoundError &) {
903 g_log.error("Can't locate SaveIsawPeaks algorithm");
904 throw;
905 }
906 alg->setProperty("InputWorkspace", peakWS);
907 alg->setProperty("ProfileWorkspace", wsProfile2D);
908 alg->setPropertyValue("Filename", outfile);
909 alg->execute();
910 }
911 // Save the output
912 setProperty("OutputWorkspace", peakWS);
913}
914
932template <typename MDE, size_t nd>
934 const CoordTransform &getRadiusSq, const V3D &pos, const coord_t &radiusSquared,
935 const bool &qAxisIsFixed, const bool &useCentroid, const double &bgDensity,
936 std::array<V3D, 3> &eigenvects, std::array<double, 3> &eigenvals, V3D &mean,
937 const int maxIter) {
938
939 // get leaf-only iterators over all boxes in ws
940 auto function = std::make_unique<Geometry::MDAlgorithms::MDBoxMaskFunction>(pos, radiusSquared);
941 MDBoxBase<MDE, nd> *baseBox = ws->getBox();
942 MDBoxIterator<MDE, nd> MDiter(baseBox, 1000, true, function.get());
943
944 // get initial vector of events inside sphere
945 std::vector<std::pair<V3D, double>> peak_events;
946
947 do {
948 auto *box = dynamic_cast<MDBox<MDE, nd> *>(MDiter.getBox());
949 if (box && !box->getIsMasked()) {
950
951 // simple check whether box is defintely not contained
952 coord_t boxCenter[nd];
953 box->getCenter(boxCenter);
954 V3D displacement; // vector between peak pos and box center
955 coord_t rboxSq = 0; // dist from center to vertex sq
956 for (size_t d = 0; d < nd; ++d) {
957 auto dim = box->getExtents(d);
958 rboxSq += static_cast<coord_t>(0.25 * dim.getSize() * dim.getSize());
959 displacement[d] = pos[d] - static_cast<double>(boxCenter[d]);
960 }
961
962 if (displacement.norm() < static_cast<double>(sqrt(rboxSq)) + static_cast<double>(sqrt(radiusSquared))) {
963 // box MIGHT intersect peak spherical region so go through events
964 const std::vector<MDE> &events = box->getConstEvents();
965 auto bg = bgDensity / (static_cast<double>(events.size()) * (box->getInverseVolume()));
966 // For each event
967 for (const auto &evnt : events) {
968
969 coord_t center_array[nd];
970 for (size_t d = 0; d < nd; ++d) {
971 center_array[d] = evnt.getCenter(d);
972 }
973 coord_t out[1];
974 auto *cen_ptr = center_array; // pointer to first element
975 getRadiusSq.apply(cen_ptr, out);
976
977 if (evnt.getSignal() > bg && out[0] < radiusSquared) {
978 // need in V3D for matrix maths later
979 V3D center;
980 for (size_t d = 0; d < nd; ++d) {
981 center[d] = static_cast<double>(center_array[d]);
982 }
983 peak_events.emplace_back(center, evnt.getSignal() - bg);
984 }
985 }
986 }
987 }
988 box->releaseEvents();
989 } while (MDiter.next());
990 calcCovar(peak_events, pos, radiusSquared, qAxisIsFixed, useCentroid, eigenvects, eigenvals, mean, maxIter);
991}
992
1008void IntegratePeaksMD2::calcCovar(const std::vector<std::pair<V3D, double>> &peak_events, const V3D &pos,
1009 const coord_t &radiusSquared, const bool &qAxisIsFixed, const bool &useCentroid,
1010 std::array<V3D, 3> &eigenvects, std::array<double, 3> &eigenvals, V3D &mean,
1011 const int maxIter) {
1012
1013 constexpr size_t num_dimensions = 3;
1014
1015 // to calc threshold mdsq to exclude events over 3 stdevs away
1016 boost::math::chi_squared chisq(static_cast<double>(num_dimensions));
1017 auto mdsq_max = boost::math::quantile(chisq, 0.997);
1018 Matrix<double> invCov; // required to calc mdsq
1019 double prev_cov_det = DBL_MAX;
1020
1021 // initialise mean with pos
1022 mean = pos;
1023 Matrix<double> Pinv(num_dimensions, num_dimensions);
1024 if (qAxisIsFixed) {
1025 // transformation from Qlab to Qhat, vhat and uhat,
1026 getPinv(pos, Pinv);
1027 mean = Pinv * mean;
1028 }
1029 Matrix<double> cov_mat(num_dimensions, num_dimensions);
1030
1031 for (int nIter = 0; nIter < maxIter; nIter++) {
1032
1033 // reset on each loop
1034 cov_mat.zeroMatrix();
1035 double w_sum = 0; // sum of weights
1036 size_t nmasked = 0; // num masked events outside 3stdevs
1037 auto prev_pos = mean;
1038
1039 for (size_t ievent = 0; ievent < peak_events.size(); ievent++) {
1040
1041 const auto event = peak_events[ievent];
1042 auto center = event.first;
1043 if (qAxisIsFixed) {
1044 // transform coords to Q, uhat, vhat basis
1045 center = Pinv * center;
1046 }
1047
1048 bool useEvent = true;
1049 if (nIter > 0) {
1050 // check if point within 3 stdevs of mean (in MD frame)
1051 // prev_pos is the mean if useCentroid
1052 const auto displ = center - prev_pos;
1053 auto mdsq = displ.scalar_prod(invCov * displ);
1054 if (mdsq > mdsq_max) {
1055 // exclude points outside 3 stdevs
1056 useEvent = false;
1057 nmasked += 1;
1058 }
1059 }
1060
1061 // if prev cov_mat chec
1062 if (useEvent) {
1063 const auto signal = event.second;
1064 w_sum += signal;
1065
1066 if (useCentroid) {
1067 // update mean
1068 mean += (center - mean) * (signal / w_sum);
1069 }
1070
1071 // weight for variance
1072 auto wi = signal * (w_sum - signal) / w_sum;
1073 size_t istart = 0;
1074 if (qAxisIsFixed) {
1075 // variance along Q (skipped in next nested loops below)
1076 cov_mat[0][0] += wi * pow((center[0] - mean[0]), 2);
1077 istart = 1;
1078 }
1079 for (size_t row = istart; row < cov_mat.numRows(); ++row) {
1080 for (size_t col = istart; col < cov_mat.numRows(); ++col) {
1081 // symmeteric matrix
1082 if (row <= col) {
1083 auto cov = wi * (center[row] - mean[row]) * (center[col] - mean[col]);
1084 if (row == col) {
1085 cov_mat[row][col] += cov;
1086 } else {
1087 cov_mat[row][col] += cov;
1088 cov_mat[col][row] += cov;
1089 }
1090 }
1091 }
1092 }
1093 }
1094 }
1095 // normalise the covariance matrix
1096 cov_mat /= w_sum; // normalise by sum of weights
1097
1098 // check if another iteration is required
1099 bool anyMasked = (nIter > 0) ? (nmasked > 0) : true;
1100 // check if ellipsoid volume greater than sphere
1101 auto cov_det = cov_mat.determinant();
1102 bool isEllipVolGreater = cov_det > pow(static_cast<double>(radiusSquared / 9), 3);
1103 // check for convergence of variances
1104 bool isConverged = (cov_det > 0.95 * prev_cov_det);
1105
1106 if (!anyMasked || isEllipVolGreater || isConverged) {
1107 break;
1108 } else {
1109 prev_cov_det = cov_det;
1110 // required to eval Mahalanobis distance
1111 invCov = Matrix<double>(cov_mat);
1112 invCov.Invert();
1113 }
1114 }
1115
1116 if (qAxisIsFixed) {
1117 // transform back to MD basis
1118 Matrix<double> P(Pinv);
1119 P.Transpose();
1120 mean = P * mean;
1121 cov_mat = P * cov_mat * Pinv;
1122 }
1123 Matrix<double> evecs; // hold eigenvectors
1124 Matrix<double> evals; // hold eigenvals in diag
1125 cov_mat.Diagonalise(evecs, evals);
1126
1127 auto min_eval = evals[0][0];
1128 for (size_t d = 1; d < num_dimensions; ++d) {
1129 min_eval = std::min(min_eval, evals[d][d]);
1130 }
1131 if (min_eval > static_cast<double>(radiusSquared / 9)) {
1132 // haven't found good covar - set to spherical region
1133 evals.identityMatrix();
1134 evals = evals * (static_cast<double>(radiusSquared) / 9);
1135 g_log.warning() << "Covariance of peak at ";
1136 pos.printSelf(g_log.warning());
1137 g_log.warning() << " is not well constrained, it has been set to spherical" << std::endl;
1138 }
1139
1140 // convert to vectors for output
1141 std::copy_n(evals.Diagonal().cbegin(), num_dimensions, eigenvals.begin());
1142 // set min eigenval to be small but non-zero (1e-6)
1143 // when no discernible peak above background
1144 std::replace_if(eigenvals.begin(), eigenvals.end(), [&](auto x) { return x < 1e-6; }, 1e-6);
1145
1146 // populate V3D vector of eigenvects (needed for ellipsoid shape)
1147 for (size_t ivect = 0; ivect < num_dimensions; ++ivect) {
1148 eigenvects[ivect] = V3D(evecs[0][ivect], evecs[1][ivect], evecs[2][ivect]);
1149 }
1150}
1151
1162 // loop over 3 mutually-orthogonal vectors until get one with
1163 // a component perp to Q (within tolerance)
1164 double dotprod = 1;
1165 size_t ii = 0;
1166 V3D qhat = q / q.norm();
1167 V3D tmp;
1168 do {
1169 tmp = V3D(0, 0, 0); // reset u
1170 tmp[ii] = 1.0;
1171 dotprod = qhat.scalar_prod(tmp);
1172 ii++;
1173 } while (abs(dotprod) > 1.0 - 1e-6);
1174 // populate Pinv with basis vector rows
1175 Pinv.setRow(0, qhat);
1176 tmp = qhat.cross_prod(tmp);
1177 Pinv.setRow(1, tmp / tmp.norm());
1178 tmp = qhat.cross_prod(tmp);
1179 Pinv.setRow(2, tmp / tmp.norm());
1180}
1181
1182/*
1183 * Define edges for each instrument by masking. For CORELLI, tubes 1 and
1184 *16, and pixels 0 and 255. Get Q in the lab frame for every peak, call it
1185 *C For every point on the edge, the trajectory in reciprocal space is a
1186 *straight line, going through O=V3D(0,0,0). Calculate a point at a fixed
1187 *momentum, say k=1. Q in the lab frame
1188 *E=V3D(-k*sin(tt)*cos(ph),-k*sin(tt)*sin(ph),k-k*cos(ph)).
1189 * Normalize E to 1: E=E*(1./E.norm())
1190 *
1191 * @param inst: instrument
1192 */
1194 for (size_t i = 0; i < detectorInfo.size(); ++i) {
1195 if (detectorInfo.isMonitor(i))
1196 continue; // skip monitor
1197 if (!detectorInfo.isMasked(i))
1198 continue; // edge is masked so don't check if not masked
1199 const auto &det = detectorInfo.detector(i);
1200 double tt1 = det.getTwoTheta(V3D(0, 0, 0), V3D(0, 0, 1)); // two theta
1201 double ph1 = det.getPhi(); // phi
1202 V3D E1 = V3D(-std::sin(tt1) * std::cos(ph1), -std::sin(tt1) * std::sin(ph1),
1203 1. - std::cos(tt1)); // end of trajectory
1204 E1 = E1 * (1. / E1.norm()); // normalize
1205 E1Vec.emplace_back(E1);
1206 }
1207}
1208
1219 double edgeDist = DBL_MAX;
1220 for (auto &E1 : E1Vec) {
1221 V3D distv = QLabFrame - E1 * (QLabFrame.scalar_prod(E1)); // distance to the
1222 // trajectory as a
1223 // vector
1224 edgeDist = std::min(edgeDist, distv.norm()); // want smallest dist to peak
1225 }
1226 return edgeDist;
1227}
1228
1230 const std::string &property, const std::string &values) {
1231 // For CORELLI do not count as edge if next to another detector bank
1232 if (property == "Tube" && peakWS->getInstrument()->getName() == "CORELLI") {
1233 auto alg = createChildAlgorithm("MaskBTP");
1234 alg->setProperty<Workspace_sptr>("Workspace", peakWS);
1235 alg->setProperty("Bank", "1,7,12,17,22,27,30,59,63,69,74,79,84,89");
1236 alg->setProperty(property, "1");
1237 if (!alg->execute())
1238 throw std::runtime_error("MaskDetectors Child Algorithm has not executed successfully");
1239 auto alg2 = createChildAlgorithm("MaskBTP");
1240 alg2->setProperty<Workspace_sptr>("Workspace", peakWS);
1241 alg2->setProperty("Bank", "6,11,16,21,26,29,58,62,68,73,78,83,88,91");
1242 alg2->setProperty(property, "16");
1243 if (!alg2->execute())
1244 throw std::runtime_error("MaskDetectors Child Algorithm has not executed successfully");
1245 } else {
1246 auto alg = createChildAlgorithm("MaskBTP");
1247 alg->setProperty<Workspace_sptr>("Workspace", peakWS);
1248 alg->setProperty(property, values);
1249 if (!alg->execute())
1250 throw std::runtime_error("MaskDetectors Child Algorithm has not executed successfully");
1251 }
1252}
1253
1255 Mantid::Kernel::SpecialCoordinateSystem CoordinatesToUse, double radius) {
1256 // Get a direct ref to that peak.
1257 const IPeak &p1 = peakWS->getPeak(i);
1258 V3D pos1;
1259 if (CoordinatesToUse == Kernel::QLab) //"Q (lab frame)"
1260 pos1 = p1.getQLabFrame();
1261 else if (CoordinatesToUse == Kernel::QSample) //"Q (sample frame)"
1262 pos1 = p1.getQSampleFrame();
1263 else if (CoordinatesToUse == Kernel::HKL) //"HKL"
1264 pos1 = p1.getHKL();
1265 for (int j = i + 1; j < peakWS->getNumberPeaks(); ++j) {
1266 // Get a direct ref to rest of peaks peak.
1267 const IPeak &p2 = peakWS->getPeak(j);
1268 V3D pos2;
1269 if (CoordinatesToUse == Kernel::QLab) //"Q (lab frame)"
1270 pos2 = p2.getQLabFrame();
1271 else if (CoordinatesToUse == Kernel::QSample) //"Q (sample frame)"
1272 pos2 = p2.getQSampleFrame();
1273 else if (CoordinatesToUse == Kernel::HKL) //"HKL"
1274 pos2 = p2.getHKL();
1275 if (pos1.distance(pos2) < radius) {
1276 g_log.warning() << " Warning: Peak integration spheres for peaks " << i << " and " << j
1277 << " overlap. Distance between peaks is " << pos1.distance(pos2) << '\n';
1278 }
1279 }
1280}
1281
1282//----------------------------------------------------------------------------------------------
1286 inWS = getProperty("InputWorkspace");
1287
1289}
1290
1291double f_eval2(double x, void *params) {
1292 std::shared_ptr<const API::CompositeFunction> fun =
1293 *reinterpret_cast<std::shared_ptr<const API::CompositeFunction> *>(params);
1294 FunctionDomain1DVector domain(x);
1295 FunctionValues yval(domain);
1296 fun->function(domain, yval);
1297 return yval[0];
1298}
1299
1300} // namespace Mantid::MDAlgorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
gsl_vector * tmp
double sigma
Definition GetAllEi.cpp:156
double error
#define CALL_MDEVENT_FUNCTION(funcname, workspace)
Macro that makes it possible to call a templated method for a MDEventWorkspace using a IMDEventWorksp...
#define PARALLEL_START_INTERRUPT_REGION
Begins a block to skip processing is the algorithm has been interupted Note the end of the block if n...
#define PARALLEL_END_INTERRUPT_REGION
Ends a block to skip processing is the algorithm has been interupted Note the start of the block if n...
#define PARALLEL_FOR_IF(condition)
Empty definitions - to enable set your complier to enable openMP.
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
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.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
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.
Unique SingleValueParameter Declaration for InputNDimensions.
virtual void apply(const coord_t *inputVector, coord_t *outVector) const =0
@ OptionalSave
to specify a file to write to but an empty string is
Implements FunctionDomain1D with its own storage in form of a std::vector.
A class to store values calculated by a function.
const std::vector< double > & toVector() const
Return the calculated values as a vector.
An interface to a peak function, which extend the interface of IFunctionWithLocation by adding method...
Helper class for reporting progress from algorithms.
Definition Progress.h:25
A property class for workspaces.
Unique CoordCenterVectorParam type declaration for ndimensional coordinate centers.
Templated super-class of a multi-dimensional event "box".
Definition MDBoxBase.h:50
void getCenter(coord_t *const center) const override
Get the center of the box.
Definition MDBoxBase.h:210
void integrateSphere(Mantid::API::CoordTransform &radiusTransform, const coord_t radiusSquared, signal_t &signal, signal_t &errorSquared, const coord_t innerRadiusSquared=0.0, const bool useOnePercentBackgroundCorrection=true) const override=0
Sphere (peak) integration.
void integrateCylinder(Mantid::API::CoordTransform &radiusTransform, const coord_t radius, const coord_t length, signal_t &signal, signal_t &errorSquared, std::vector< signal_t > &signal_fit) const override=0
Cylinder (peak) integration.
MDBoxIterator: iterate through MDBoxBase hierarchy down to a given maximum depth.
bool next() override
Advance to the next cell.
MDBoxBase< MDE, nd > * getBox() const
Return a pointer to the current box pointed to by the iterator.
Templated class for a multi-dimensional event "box".
Definition MDBox.h:45
std::shared_ptr< MDEventWorkspace< MDE, nd > > sptr
Typedef for a shared pointer of this kind of event workspace.
Kernel::SpecialCoordinateSystem getSpecialCoordinateSystem() const override
Get the coordinate system.
PeakShapeEllipsoid : PeakShape representing a 3D ellipsoid.
PeakShapeSpherical : PeakShape for a spherical peak.
Geometry::DetectorInfo is an intermediate step towards a DetectorInfo that is part of Instrument-2....
bool isMasked(const size_t index) const
Returns true if the detector is masked.
const Geometry::IDetector & detector(const size_t index) const
Return a const reference to the detector with given index.
size_t size() const
Returns the size of the DetectorInfo, i.e., the number of detectors in the instrument.
bool isMonitor(const size_t index) const
Returns true if the detector is a monitor.
virtual double getTwoTheta(const Kernel::V3D &observer, const Kernel::V3D &axis) const =0
Gives the angle of this detector object with respect to an axis.
Structure describing a single-crystal peak.
Definition IPeak.h:26
virtual double getH() const =0
virtual void setIntensity(double m_Intensity)=0
virtual void setPeakShape(Mantid::Geometry::PeakShape *shape)=0
virtual double getK() const =0
virtual int getRunNumber() const =0
virtual Mantid::Kernel::V3D getQSampleFrame() const =0
virtual double getL() const =0
virtual void setSigmaIntensity(double m_SigmaIntensity)=0
virtual Mantid::Kernel::V3D getQLabFrame() const =0
virtual Mantid::Kernel::V3D getHKL() const =0
PeakShape : Abstract type to describes the shape of a peak.
Definition PeakShape.h:20
Support for a property that holds an array of values.
Exception for when an item is not found in a collection.
Definition Exception.h:145
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 > settings)
void setPropertyGroup(const std::string &name, const std::string &group)
Set the group for a given property.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
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
T determinant() const
Calculate the determinant.
Definition Matrix.cpp:1048
T Invert()
LU inversion routine.
Definition Matrix.cpp:924
void zeroMatrix()
Set the matrix to zero.
Definition Matrix.cpp:646
void identityMatrix()
Makes the matrix an idenity matrix.
Definition Matrix.cpp:661
int Diagonalise(Matrix< T > &, Matrix< T > &) const
(only Symmetric matrix)
Definition Matrix.cpp:1319
void setRow(const size_t nRow, const std::vector< T > &newRow)
Definition Matrix.cpp:686
std::vector< T > Diagonal() const
Returns a vector of the diagonal.
Definition Matrix.cpp:1265
size_t numRows() const
Return the number of rows in the matrix.
Definition Matrix.h:144
Matrix< T > & Transpose()
Transpose the matrix.
Definition Matrix.cpp:793
The concrete, templated class for properties.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Class for 3D vectors.
Definition V3D.h:34
double distance(const V3D &v) const noexcept
Calculates the distance between two vectors.
Definition V3D.h:293
constexpr double scalar_prod(const V3D &v) const noexcept
Calculates the cross product.
Definition V3D.h:280
void printSelf(std::ostream &) const
Prints a text representation of itself in format "[x,y,z]".
Definition V3D.cpp:349
constexpr V3D cross_prod(const V3D &v) const noexcept
Cross product (this * argument)
Definition V3D.h:284
double norm() const noexcept
Definition V3D.h:269
std::vector< Kernel::V3D > E1Vec
save for all detector pixels
void runMaskDetectors(const Mantid::DataObjects::PeaksWorkspace_sptr &peakWS, const std::string &property, const std::string &values)
void calculateE1(const Geometry::DetectorInfo &detectorInfo)
Calculate if this Q is on a detector.
void getPinv(const Mantid::Kernel::V3D &q, Mantid::Kernel::Matrix< double > &Pinv)
Get the inverse of the matrix P.
void exec() override
Run the algorithm.
void findEllipsoid(const typename DataObjects::MDEventWorkspace< MDE, nd >::sptr ws, const Mantid::API::CoordTransform &getRadiusSq, const Mantid::Kernel::V3D &pos, const coord_t &radiusSquared, const bool &qAxisBool, const bool &useCentroid, const double &bgDensity, std::array< Mantid::Kernel::V3D, 3 > &eigenvects, std::array< double, 3 > &eigenvals, Mantid::Kernel::V3D &mean, const int maxIter=1)
Calculate the covariance matrix of a spherical region and store the eigenvectors and eigenvalues that...
void calcCovar(const std::vector< std::pair< Mantid::Kernel::V3D, double > > &peak_events, const Mantid::Kernel::V3D &pos, const coord_t &radiusSquared, const bool &qAxisIsFixed, const bool &useCentroid, std::array< Mantid::Kernel::V3D, 3 > &eigenvects, std::array< double, 3 > &eigenvals, Mantid::Kernel::V3D &mean, const int maxIter)
Calculate the covariance matrix of a spherical region and store the eigenvectors and eigenvalues that...
double calculateDistanceToEdge(const Mantid::Kernel::V3D &QLabFrame)
Calculate if this Q is on a detector The distance from C to OE is given by dv=C-E*(C....
int version() const override
Algorithm's version for identification.
void checkOverlap(int i, const Mantid::API::IPeaksWorkspace_sptr &peakWS, Mantid::Kernel::SpecialCoordinateSystem CoordinatesToUse, double radius)
Check if peaks overlap.
void init() override
Initialise the properties.
void integrate(typename DataObjects::MDEventWorkspace< MDE, nd >::sptr ws)
Integrate the peaks of the workspace using parameters saved in the algorithm class.
Mantid::API::IMDEventWorkspace_sptr inWS
Input MDEventWorkspace.
const std::string name() const override
Algorithm's name for identification.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
std::shared_ptr< IPeaksWorkspace > IPeaksWorkspace_sptr
shared pointer to Mantid::API::IPeaksWorkspace
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
std::complex< double > MANTID_API_DLL E1(std::complex< double > z)
Integral for Gamma.
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition IFunction.h:743
std::shared_ptr< Workspace2D > Workspace2D_sptr
shared pointer to Mantid::DataObjects::Workspace2D
std::array< double, PEAK_ELLIPSOID_DIMS > PeakEllipsoidExtent
std::shared_ptr< PeaksWorkspace > PeaksWorkspace_sptr
Typedef for a shared pointer to a peaks workspace.
long round(double x)
Custom rounding method for a double->long because none is portable in C++ (!)
Definition Utils.h:37
SpecialCoordinateSystem
Special coordinate systems for Q3D.
std::enable_if< std::is_pointer< Arg >::value, bool >::type threadSafe(Arg workspace)
Thread-safety check Checks the workspace to ensure it is suitable for multithreaded access.
double f_eval2(double x, void *params)
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