Mantid
Loading...
Searching...
No Matches
SmoothMD.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 +
11#include "MantidAPI/Progress.h"
20
21#include <algorithm>
22#include <boost/tuple/tuple.hpp>
23#include <limits>
24#include <map>
25#include <memory>
26#include <numeric>
27#include <sstream>
28#include <stack>
29#include <string>
30#include <utility>
31#include <vector>
32
33using namespace Mantid::Kernel;
34using namespace Mantid::API;
35using namespace Mantid::DataObjects;
36
37// Typedef for width vector
38using WidthVector = std::vector<double>;
39
40// Typedef for kernel vector
41using KernelVector = std::vector<double>;
42
43// Typedef for an optional md histo workspace
44using OptionalIMDHistoWorkspace_const_sptr = boost::optional<IMDHistoWorkspace_const_sptr>;
45
46// Typedef for a smoothing function
49
50// Typedef for a smoothing function map keyed by name.
51using SmoothFunctionMap = std::map<std::string, SmoothFunction>;
52
53namespace {
54
59SmoothFunctionMap makeFunctionMap(Mantid::MDAlgorithms::SmoothMD *instance) {
60 using std::placeholders::_1;
61 using std::placeholders::_2;
62 using std::placeholders::_3;
63 return {{"Hat", std::bind(&Mantid::MDAlgorithms::SmoothMD::hatSmooth, instance, _1, _2, _3)},
64 {"Gaussian", std::bind(&Mantid::MDAlgorithms::SmoothMD::gaussianSmooth, instance, _1, _2, _3)}};
65}
66} // namespace
67
68namespace Mantid::MDAlgorithms {
69
70/*
71 * Create a Gaussian kernel. The returned kernel is a 1D vector,
72 * the order of which matches the linear indices returned by
73 * the findNeighbourIndexesByWidth1D method.
74 * @param fwhm : Full Width Half Maximum of the Gaussian (in units of pixels)
75 * @return The Gaussian kernel
76 */
77KernelVector gaussianKernel(const double fwhm) {
78
79 // Calculate sigma from FWHM
80 // FWHM = 2 sqrt(2 * ln(2)) * sigma
81 const double sigma = (fwhm * 0.42463) / 2.0;
82 const double sigma_factor = M_SQRT1_2 / (fwhm * 0.42463);
83
84 KernelVector kernel_one_side;
85 // Start from centre and calculate values going outwards until value < 0.02
86 // We have to truncate the function at some point and 0.02 is chosen
87 // for consistency with Horace
88 // Use erf to get the value of the Gaussian integrated over the width of the
89 // pixel, more accurate than just using centre value of pixel and erf is fast
90 double pixel_value = std::erf(0.5 * sigma_factor) * sigma;
91 int pixel_count = 0;
92 while (pixel_value > 0.02) {
93 kernel_one_side.emplace_back(pixel_value);
94 pixel_count++;
95 pixel_value =
96 (std::erf((pixel_count + 0.5) * sigma_factor) - std::erf((pixel_count - 0.5) * sigma_factor)) * 0.5 * sigma;
97 }
98
99 // Create the symmetric kernel vector
100 KernelVector kernel;
101 kernel.resize(kernel_one_side.size() * 2 - 1);
102 std::reverse_copy(kernel_one_side.cbegin(), kernel_one_side.cend(), kernel.begin());
103 std::copy(kernel_one_side.cbegin() + 1, kernel_one_side.cend(), kernel.begin() + kernel_one_side.size());
104
105 return normaliseKernel(kernel);
106}
107
108/*
109 * Normalise the kernel
110 * It is necessary to renormlise where the kernel overlaps edges of the
111 * workspace
112 * The contributing elements should sum to unity
113 */
114KernelVector renormaliseKernel(KernelVector kernel, const std::vector<bool> &validity) {
115
116 if (validity.size() == kernel.size() && std::find(validity.cbegin(), validity.cend(), false) != validity.cend()) {
117 // Use validity as a mask
118 for (size_t i = 0; i < kernel.size(); ++i) {
119 kernel[i] *= validity[i];
120 }
121
122 kernel = normaliseKernel(kernel);
123 }
124
125 return kernel;
126}
127
128/*
129 * Normalise the kernel so that sum of valid elements is unity
130 */
132 double sum_kernel_recip = 1.0 / std::accumulate(kernel.cbegin(), kernel.cend(), 0.0);
133 std::transform(kernel.cbegin(), kernel.cend(), kernel.begin(),
134 [sum_kernel_recip](const auto &value) { return value * sum_kernel_recip; });
135 return kernel;
136}
137
138// Register the algorithm into the AlgorithmFactory
139DECLARE_ALGORITHM(SmoothMD)
140
141//----------------------------------------------------------------------------------------------
142
143
144const std::string SmoothMD::name() const { return "SmoothMD"; }
145
147int SmoothMD::version() const { return 1; }
148
150const std::string SmoothMD::category() const { return "MDAlgorithms\\Transforms"; }
151
153const std::string SmoothMD::summary() const { return "Smooth an MDHistoWorkspace according to a weight function"; }
154
164 const IMDHistoWorkspace_sptr &weightingWS) {
165
166 const bool useWeights = (weightingWS != nullptr);
167 uint64_t nPoints = toSmooth->getNPoints();
168 Progress progress(this, 0.0, 1.0, size_t(double(nPoints) * 1.1));
169 // Create the output workspace.
170 IMDHistoWorkspace_sptr outWS(toSmooth->clone());
171 progress.reportIncrement(size_t(double(nPoints) * 0.1)); // Report ~10% progress
172
173 const int nThreads = Mantid::API::FrameworkManager::Instance().getNumOMPThreads(); // NThreads to Request
174
175 auto iterators = toSmooth->createIterators(nThreads, nullptr);
176
178 for (int it = 0; it < int(iterators.size()); ++it) { // NOLINT
179
181 auto iterator = dynamic_cast<MDHistoWorkspaceIterator *>(iterators[it].get());
182
183 if (!iterator) {
184 throw std::logic_error("Failed to cast IMDIterator to MDHistoWorkspaceIterator");
185 }
186
187 do {
188 // Gets all vertex-touching neighbours
189 size_t iteratorIndex = iterator->getLinearIndex();
190
191 if (useWeights) {
192
193 // Check that we could measure here.
194 if (weightingWS->getSignalAt(iteratorIndex) == 0) {
195
196 outWS->setSignalAt(iteratorIndex, std::numeric_limits<double>::quiet_NaN());
197 outWS->setErrorSquaredAt(iteratorIndex, std::numeric_limits<double>::quiet_NaN());
198
199 continue; // Skip we couldn't measure here.
200 }
201 }
202
203 // Explicitly cast the doubles to int
204 // We've already checked in the validator that the doubles we have are odd
205 // integer values and well below max int
206 std::vector<int> widthVectorInt;
207 widthVectorInt.resize(widthVector.size());
208 std::transform(widthVector.cbegin(), widthVector.cend(), widthVectorInt.begin(),
209 [](double w) -> int { return static_cast<int>(w); });
210
211 std::vector<size_t> neighbourIndexes = iterator->findNeighbourIndexesByWidth(widthVectorInt);
212
213 size_t nNeighbours = neighbourIndexes.size();
214 double sumSignal = iterator->getSignal();
215 double sumSqError = iterator->getError();
216 for (auto neighbourIndex : neighbourIndexes) {
217 if (useWeights) {
218 if (weightingWS->getSignalAt(neighbourIndex) == 0) {
219 // Nothing measured here. We cannot use that neighbouring point.
220 nNeighbours -= 1;
221 continue;
222 }
223 }
224 sumSignal += toSmooth->getSignalAt(neighbourIndex);
225 double error = toSmooth->getErrorAt(neighbourIndex);
226 sumSqError += (error * error);
227 }
228
229 // Calculate the mean
230 outWS->setSignalAt(iteratorIndex, sumSignal / double(nNeighbours + 1));
231 // Calculate the sample variance
232 outWS->setErrorSquaredAt(iteratorIndex, sumSqError / double(nNeighbours + 1));
233
234 progress.report();
235
236 } while (iterator->next());
238 }
240
241 return outWS;
242}
243
256 const WidthVector &widthVector,
257 const IMDHistoWorkspace_sptr &weightingWS) {
258 const bool useWeights = weightingWS.get() != 0;
259 uint64_t nPoints = toSmooth->getNPoints();
260 Progress progress(this, 0.0, 1.0, size_t(double(nPoints) * 1.1));
261 // Create the output workspace
262 IMDHistoWorkspace_sptr outWS(toSmooth->clone().release());
263 // Create a temporary workspace
264 IMDHistoWorkspace_sptr tempWS(toSmooth->clone().release());
265 // Report ~10% progress
266 progress.reportIncrement(size_t(double(nPoints) * 0.1));
267
268 // Create a kernel for each dimension and
269 std::vector<KernelVector> gaussian_kernels;
270 gaussian_kernels.reserve(widthVector.size());
271 std::transform(widthVector.cbegin(), widthVector.cend(), std::back_inserter(gaussian_kernels),
272 [](const auto width) { return gaussianKernel(width); });
273
274 const int nThreads = Mantid::API::FrameworkManager::Instance().getNumOMPThreads(); // NThreads to Request
275
276 auto write_ws = tempWS;
277 for (size_t dimension_number = 0; dimension_number < widthVector.size(); ++dimension_number) {
278
279 auto iterators = toSmooth->createIterators(nThreads, nullptr);
280
281 // Alternately write to each workspace
283 if (dimension_number % 2 == 0) {
284 read_ws = outWS;
285 write_ws = tempWS;
286 } else {
287 read_ws = tempWS;
288 write_ws = outWS;
289 }
290
292 for (int it = 0; it < int(iterators.size()); ++it) { // NOLINT
293
295 auto iterator = dynamic_cast<MDHistoWorkspaceIterator *>(iterators[it].get());
296 if (!iterator) {
297 throw std::logic_error("Failed to cast IMDIterator to MDHistoWorkspaceIterator");
298 }
299
300 do {
301
302 // Gets linear index at current position
303 size_t iteratorIndex = iterator->getLinearIndex();
304
305 if (useWeights) {
306
307 // Check that we could measure here.
308 if (weightingWS->getSignalAt(iteratorIndex) == 0) {
309
310 write_ws->setSignalAt(iteratorIndex, std::numeric_limits<double>::quiet_NaN());
311 write_ws->setErrorSquaredAt(iteratorIndex, std::numeric_limits<double>::quiet_NaN());
312
313 continue; // Skip we couldn't measure here.
314 }
315 }
316
317 std::pair<std::vector<size_t>, std::vector<bool>> indexesAndValidity = iterator->findNeighbourIndexesByWidth1D(
318 static_cast<int>(gaussian_kernels[dimension_number].size()), static_cast<int>(dimension_number));
319 std::vector<size_t> neighbourIndexes = std::get<0>(indexesAndValidity);
320 std::vector<bool> indexValidity = std::get<1>(indexesAndValidity);
321 auto normalised_kernel = renormaliseKernel(gaussian_kernels[dimension_number], indexValidity);
322
323 // Convolve signal with kernel
324 double sumSignal = 0;
325 double sumSquareError = 0;
326 double error = 0;
327 for (size_t i = 0; i < neighbourIndexes.size(); ++i) {
328 if (indexValidity[i]) {
329 sumSignal += read_ws->getSignalAt(neighbourIndexes[i]) * normalised_kernel[i];
330 error = read_ws->getErrorAt(neighbourIndexes[i]) * normalised_kernel[i];
331 sumSquareError += error * error;
332 }
333
334 write_ws->setSignalAt(iteratorIndex, sumSignal);
335 write_ws->setErrorSquaredAt(iteratorIndex, sumSquareError);
336 }
337 progress.report();
338
339 } while (iterator->next());
341 }
343 }
344
345 return write_ws;
346}
347
348//----------------------------------------------------------------------------------------------
352 declareProperty(std::make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>("InputWorkspace", "", Direction::Input),
353 "An input MDHistoWorkspace to smooth.");
354
355 auto widthVectorValidator = std::make_shared<CompositeValidator>();
356 auto boundedValidator = std::make_shared<ArrayBoundedValidator<double>>(1, 1000);
357 widthVectorValidator->add(boundedValidator);
358 widthVectorValidator->add(std::make_shared<MandatoryValidator<WidthVector>>());
359
360 declareProperty(std::make_unique<ArrayProperty<double>>("WidthVector", widthVectorValidator, Direction::Input),
361 "Width vector. Either specify the width in n-pixels for each "
362 "dimension, or provide a single entry (n-pixels) for all "
363 "dimensions. Must be odd integers if Hat function is chosen.");
364
365 const std::array<std::string, 2> allFunctionTypes = {{"Hat", "Gaussian"}};
366 const std::string first = allFunctionTypes.front();
367
368 std::stringstream docBuffer;
369 docBuffer << "Smoothing function. Defaults to " << first;
371 std::make_unique<PropertyWithValue<std::string>>(
372 "Function", first, std::make_shared<ListValidator<std::string>>(allFunctionTypes), Direction::Input),
373 docBuffer.str());
374
375 std::array<std::string, 1> unitOptions = {{"pixels"}};
376
377 std::stringstream docUnits;
378 docUnits << "The units that WidthVector has been specified in. Allowed "
379 "values are: ";
380 for (auto const &unitOption : unitOptions) {
381 docUnits << unitOption << ", ";
382 }
384 "Units", "pixels", std::make_shared<ListValidator<std::string>>(unitOptions), Direction::Input),
385 docUnits.str());
386
387 declareProperty(std::make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>("InputNormalizationWorkspace", "",
389 "Multidimensional weighting workspace. Optional.");
390
391 declareProperty(std::make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>("OutputWorkspace", "", Direction::Output),
392 "An output smoothed MDHistoWorkspace.");
393}
394
395//----------------------------------------------------------------------------------------------
399
400 // Get the input workspace to smooth
401 IMDHistoWorkspace_sptr toSmooth = this->getProperty("InputWorkspace");
402
403 // Get the input weighting workspace
404 IMDHistoWorkspace_sptr weightingWS = this->getProperty("InputNormalizationWorkspace");
405
406 // Get the width vector
407 std::vector<double> widthVector = this->getProperty("WidthVector");
408 if (widthVector.size() == 1) {
409 // Pad the width vector out to the right size if only one entry has been
410 // provided.
411 widthVector = std::vector<double>(toSmooth->getNumDims(), widthVector.front());
412 }
413
414 // Find the chosen smooth operation
415 const std::string smoothFunctionName = this->getProperty("Function");
416 SmoothFunctionMap functionMap = makeFunctionMap(this);
417 SmoothFunction smoothFunction = functionMap[smoothFunctionName];
418 // invoke the smoothing operation
419 auto smoothed = smoothFunction(toSmooth, widthVector, weightingWS);
420
421 setProperty("OutputWorkspace", smoothed);
422}
423
428std::map<std::string, std::string> SmoothMD::validateInputs() {
429
430 std::map<std::string, std::string> product;
431
432 IMDHistoWorkspace_sptr toSmoothWs = this->getProperty("InputWorkspace");
433
434 // Function type
435 std::string function_type = this->getProperty("Function");
436
437 // Check the width vector
438 const std::string widthVectorPropertyName = "WidthVector";
439 std::vector<double> widthVector = this->getProperty(widthVectorPropertyName);
440
441 if (widthVector.size() != 1 && widthVector.size() != toSmoothWs->getNumDims()) {
442 product.emplace(widthVectorPropertyName, widthVectorPropertyName + " can either have one entry or needs to "
443 "have entries for each dimension of the "
444 "InputWorkspace.");
445 } else if (function_type == "Hat") {
446 // If Hat function is used then widthVector must contain odd integers only
447 for (auto const widthEntry : widthVector) {
448 double intpart;
449 if (modf(widthEntry, &intpart) != 0.0) {
450 std::stringstream message;
451 message << widthVectorPropertyName
452 << " entries must be (odd) integers "
453 "when Hat function is chosen. "
454 "Bad entry is "
455 << widthEntry;
456 product.emplace(widthVectorPropertyName, message.str());
457 } else if (static_cast<unsigned long>(widthEntry) % 2 == 0) {
458 std::stringstream message;
459 message << widthVectorPropertyName
460 << " entries must be odd integers "
461 "when Hat function is chosen. "
462 "Bad entry is "
463 << widthEntry;
464 }
465 }
466 }
467
468 // Check the dimensionality of the normalization workspace
469 const std::string normalisationWorkspacePropertyName = "InputNormalizationWorkspace";
470
471 IMDHistoWorkspace_sptr normWs = this->getProperty(normalisationWorkspacePropertyName);
472 if (normWs) {
473 const size_t nDimsNorm = normWs->getNumDims();
474 const size_t nDimsSmooth = toSmoothWs->getNumDims();
475 if (nDimsNorm != nDimsSmooth) {
476 std::stringstream message;
477 message << normalisationWorkspacePropertyName
478 << " has a different number of dimensions than InputWorkspace. "
479 "Shapes of inputs must be the same. Cannot continue "
480 "smoothing.";
481 product.insert(std::make_pair(normalisationWorkspacePropertyName, message.str()));
482 } else {
483 // Loop over dimensions and check nbins.
484 for (size_t i = 0; i < nDimsNorm; ++i) {
485 const size_t nBinsNorm = normWs->getDimension(i)->getNBins();
486 const size_t nBinsSmooth = toSmoothWs->getDimension(i)->getNBins();
487 if (nBinsNorm != nBinsSmooth) {
488 std::stringstream message;
489 message << normalisationWorkspacePropertyName << ". Number of bins from dimension with index " << i
490 << " do not match. " << nBinsSmooth << " expected. Got " << nBinsNorm
491 << ". Shapes of inputs must be the same. Cannot "
492 "continue smoothing.";
493 product.emplace(normalisationWorkspacePropertyName, message.str());
494 break;
495 }
496 }
497 }
498 }
499
500 return product;
501}
502
503} // namespace Mantid::MDAlgorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double value
The value of the point.
Definition: FitMW.cpp:51
double sigma
Definition: GetAllEi.cpp:156
double error
Definition: IndexPeaks.cpp:133
#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...
Definition: MultiThreaded.h:94
#define PARALLEL_FOR_NO_WSP_CHECK()
#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_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
std::vector< double > KernelVector
Definition: SmoothMD.cpp:41
boost::optional< IMDHistoWorkspace_const_sptr > OptionalIMDHistoWorkspace_const_sptr
Definition: SmoothMD.cpp:44
std::map< std::string, SmoothFunction > SmoothFunctionMap
Definition: SmoothMD.cpp:51
std::function< IMDHistoWorkspace_sptr(IMDHistoWorkspace_const_sptr, const WidthVector &, IMDHistoWorkspace_sptr)> SmoothFunction
Definition: SmoothMD.cpp:48
std::vector< double > WidthVector
Definition: SmoothMD.cpp:38
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
A property class for workspaces.
An implementation of IMDIterator that iterates through a MDHistoWorkspace.
Support for a property that holds an array of values.
Definition: ArrayProperty.h:28
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
ListValidator is a validator that requires the value of a property to be one of a defined list of pos...
Definition: ListValidator.h:29
Validator to check that a property is not left empty.
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...
SmoothMD : Algorithm for smoothing MDHistoWorkspaces.
Definition: SmoothMD.h:26
const std::string category() const override
Algorithm's category for identification.
Definition: SmoothMD.cpp:150
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
Definition: SmoothMD.cpp:153
std::shared_ptr< Mantid::API::IMDHistoWorkspace > gaussianSmooth(const std::shared_ptr< const Mantid::API::IMDHistoWorkspace > &toSmooth, const std::vector< double > &widthVector, const std::shared_ptr< Mantid::API::IMDHistoWorkspace > &weightingWS)
Gaussian function smoothing.
Definition: SmoothMD.cpp:255
int version() const override
Algorithm's version for identification.
Definition: SmoothMD.cpp:147
void exec() override
Execute the algorithm.
Definition: SmoothMD.cpp:398
std::map< std::string, std::string > validateInputs() override
validateInputs
Definition: SmoothMD.cpp:428
std::shared_ptr< Mantid::API::IMDHistoWorkspace > hatSmooth(const std::shared_ptr< const Mantid::API::IMDHistoWorkspace > &toSmooth, const std::vector< double > &widthVector, const std::shared_ptr< Mantid::API::IMDHistoWorkspace > &weightingWS)
Hat function smoothing.
Definition: SmoothMD.cpp:163
void init() override
Initialize the algorithm's properties.
Definition: SmoothMD.cpp:351
std::shared_ptr< IMDHistoWorkspace > IMDHistoWorkspace_sptr
shared pointer to Mantid::API::IMDHistoWorkspace
std::shared_ptr< const IMDHistoWorkspace > IMDHistoWorkspace_const_sptr
shared pointer to Mantid::API::IMDHistoWorkspace (const version)
DLLExport std::vector< double > gaussianKernel(const double fwhm)
Definition: SmoothMD.cpp:77
DLLExport std::vector< double > renormaliseKernel(std::vector< double > kernel, const std::vector< bool > &validity)
Definition: SmoothMD.cpp:114
DLLExport std::vector< double > normaliseKernel(std::vector< double > kernel)
Definition: SmoothMD.cpp:131
STL namespace.
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54