-
Notifications
You must be signed in to change notification settings - Fork 122
/
SmoothMD.cpp
582 lines (488 loc) · 20.2 KB
/
SmoothMD.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
#include "MantidMDAlgorithms/SmoothMD.h"
#include "MantidAPI/FrameworkManager.h"
#include "MantidAPI/IMDHistoWorkspace.h"
#include "MantidAPI/IMDIterator.h"
#include "MantidAPI/Progress.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/ArrayBoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/MandatoryValidator.h"
#include "MantidKernel/PropertyWithValue.h"
#include "MantidKernel/MultiThreaded.h"
#include "MantidDataObjects/MDHistoWorkspaceIterator.h"
#include <boost/make_shared.hpp>
#include <vector>
#include <stack>
#include <numeric>
#include <map>
#include <string>
#include <sstream>
#include <utility>
#include <limits>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/tuple/tuple.hpp>
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::DataObjects;
// Typedef for width vector
typedef std::vector<double> WidthVector;
// Typedef for kernel vector
typedef std::vector<double> KernelVector;
// Typedef for an optional md histo workspace
typedef boost::optional<IMDHistoWorkspace_const_sptr>
OptionalIMDHistoWorkspace_const_sptr;
// Typedef for a smoothing function
typedef boost::function<IMDHistoWorkspace_sptr(
IMDHistoWorkspace_const_sptr, const WidthVector &,
OptionalIMDHistoWorkspace_const_sptr)> SmoothFunction;
// Typedef for a smoothing function map keyed by name.
typedef std::map<std::string, SmoothFunction> SmoothFunctionMap;
namespace {
/**
* @brief functions
* @return Allowed smoothing functions
*/
std::vector<std::string> functions() {
std::vector<std::string> propOptions;
propOptions.push_back("Hat");
propOptions.push_back("Gaussian");
return propOptions;
}
/**
* Maps a function name to a smoothing function
* @return function map
*/
SmoothFunctionMap makeFunctionMap(Mantid::MDAlgorithms::SmoothMD *instance) {
return {
{"Hat", boost::bind(&Mantid::MDAlgorithms::SmoothMD::hatSmooth, instance,
_1, _2, _3)},
{"Gaussian", boost::bind(&Mantid::MDAlgorithms::SmoothMD::gaussianSmooth,
instance, _1, _2, _3)}};
}
}
namespace Mantid {
namespace MDAlgorithms {
/*
* Create a Gaussian kernel. The returned kernel is a 1D vector,
* the order of which matches the linear indices returned by
* the findNeighbourIndexesByWidth1D method.
* @param fwhm : Full Width Half Maximum of the Gaussian (in units of pixels)
* @return The Gaussian kernel
*/
KernelVector gaussianKernel(const double fwhm) {
// Calculate sigma from FWHM
// FWHM = 2 sqrt(2 * ln(2)) * sigma
const double sigma = (fwhm * 0.42463) / 2.0;
const double sigma_factor = M_SQRT1_2 / (fwhm * 0.42463);
KernelVector kernel_one_side;
// Start from centre and calculate values going outwards until value < 0.02
// We have to truncate the function at some point and 0.02 is chosen
// for consistency with Horace
// Use erf to get the value of the Gaussian integrated over the width of the
// pixel, more accurate than just using centre value of pixel and erf is fast
double pixel_value = std::erf(0.5 * sigma_factor) * sigma;
int pixel_count = 0;
while (pixel_value > 0.02) {
kernel_one_side.push_back(pixel_value);
pixel_count++;
pixel_value = (std::erf((pixel_count + 0.5) * sigma_factor) -
std::erf((pixel_count - 0.5) * sigma_factor)) *
0.5 * sigma;
}
// Create the symmetric kernel vector
KernelVector kernel;
kernel.resize(kernel_one_side.size() * 2 - 1);
std::reverse_copy(kernel_one_side.cbegin(), kernel_one_side.cend(),
kernel.begin());
std::copy(kernel_one_side.cbegin() + 1, kernel_one_side.cend(),
kernel.begin() + kernel_one_side.size());
return normaliseKernel(kernel);
}
/*
* Normalise the kernel
* It is necessary to renormlise where the kernel overlaps edges of the
* workspace
* The contributing elements should sum to unity
*/
KernelVector renormaliseKernel(KernelVector kernel,
std::vector<bool> validity) {
if (std::accumulate(validity.cbegin(), validity.cend(), 0.0) <
kernel.size()) {
// Use validity as a mask
for (size_t i = 0; i < kernel.size(); ++i) {
kernel[i] *= validity[i];
}
kernel = normaliseKernel(kernel);
}
return kernel;
}
/*
* Normalise the kernel so that sum of valid elements is unity
*/
KernelVector normaliseKernel(KernelVector kernel) {
double sum_kernel_recip =
1.0 / std::accumulate(kernel.cbegin(), kernel.cend(), 0.0);
for (auto &pixel : kernel) {
pixel *= sum_kernel_recip;
}
return kernel;
}
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(SmoothMD)
//----------------------------------------------------------------------------------------------
/** Constructor
*/
SmoothMD::SmoothMD() {}
//----------------------------------------------------------------------------------------------
/** Destructor
*/
SmoothMD::~SmoothMD() {}
//----------------------------------------------------------------------------------------------
/// Algorithms name for identification. @see Algorithm::name
const std::string SmoothMD::name() const { return "SmoothMD"; }
/// Algorithm's version for identification. @see Algorithm::version
int SmoothMD::version() const { return 1; }
/// Algorithm's category for identification. @see Algorithm::category
const std::string SmoothMD::category() const {
return "MDAlgorithms\\Transforms";
}
/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string SmoothMD::summary() const {
return "Smooth an MDHistoWorkspace according to a weight function";
}
/**
* Hat function smoothing. All weights even. Hat function boundaries beyond
* width.
* @param toSmooth : Workspace to smooth
* @param widthVector : Width vector
* @param weightingWS : Weighting workspace (optional)
* @return Smoothed MDHistoWorkspace
*/
IMDHistoWorkspace_sptr
SmoothMD::hatSmooth(IMDHistoWorkspace_const_sptr toSmooth,
const WidthVector &widthVector,
OptionalIMDHistoWorkspace_const_sptr weightingWS) {
const bool useWeights = weightingWS.is_initialized();
uint64_t nPoints = toSmooth->getNPoints();
Progress progress(this, 0, 1, size_t(double(nPoints) * 1.1));
// Create the output workspace.
IMDHistoWorkspace_sptr outWS(toSmooth->clone());
progress.reportIncrement(
size_t(double(nPoints) * 0.1)); // Report ~10% progress
const int nThreads = Mantid::API::FrameworkManager::Instance()
.getNumOMPThreads(); // NThreads to Request
auto iterators = toSmooth->createIterators(nThreads, nullptr);
PARALLEL_FOR_NO_WSP_CHECK()
for (int it = 0; it < int(iterators.size()); ++it) { // NOLINT
PARALLEL_START_INTERUPT_REGION
boost::scoped_ptr<MDHistoWorkspaceIterator> iterator(
dynamic_cast<MDHistoWorkspaceIterator *>(iterators[it]));
if (!iterator) {
throw std::logic_error(
"Failed to cast IMDIterator to MDHistoWorkspaceIterator");
}
do {
// Gets all vertex-touching neighbours
size_t iteratorIndex = iterator->getLinearIndex();
if (useWeights) {
// Check that we could measure here.
if ((*weightingWS)->getSignalAt(iteratorIndex) == 0) {
outWS->setSignalAt(iteratorIndex,
std::numeric_limits<double>::quiet_NaN());
outWS->setErrorSquaredAt(iteratorIndex,
std::numeric_limits<double>::quiet_NaN());
continue; // Skip we couldn't measure here.
}
}
// Explicitly cast the doubles to int
// We've already checked in the validator that the doubles we have are odd
// integer values and well below max int
std::vector<int> widthVectorInt;
widthVectorInt.reserve(widthVector.size());
for (auto const widthEntry : widthVector) {
widthVectorInt.push_back(static_cast<int>(widthEntry));
}
std::vector<size_t> neighbourIndexes =
iterator->findNeighbourIndexesByWidth(widthVectorInt);
size_t nNeighbours = neighbourIndexes.size();
double sumSignal = iterator->getSignal();
double sumSqError = iterator->getError();
for (auto neighbourIndex : neighbourIndexes) {
if (useWeights) {
if ((*weightingWS)->getSignalAt(neighbourIndex) == 0) {
// Nothing measured here. We cannot use that neighbouring point.
nNeighbours -= 1;
continue;
}
}
sumSignal += toSmooth->getSignalAt(neighbourIndex);
double error = toSmooth->getErrorAt(neighbourIndex);
sumSqError += (error * error);
}
// Calculate the mean
outWS->setSignalAt(iteratorIndex, sumSignal / double(nNeighbours + 1));
// Calculate the sample variance
outWS->setErrorSquaredAt(iteratorIndex,
sumSqError / double(nNeighbours + 1));
progress.report();
} while (iterator->next());
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
return outWS;
}
/**
* Gaussian function smoothing.
* The Gaussian function is linearly separable, allowing convolution
* of a multidimensional Gaussian kernel with the workspace to be carried out by
* a convolution with a 1D Gaussian kernel in each dimension. This
* reduces the number of calculations overall.
* @param toSmooth : Workspace to smooth
* @param widthVector : Width vector
* @param weightingWS : Weighting workspace (optional)
* @return Smoothed MDHistoWorkspace
*/
IMDHistoWorkspace_sptr
SmoothMD::gaussianSmooth(IMDHistoWorkspace_const_sptr toSmooth,
const WidthVector &widthVector,
OptionalIMDHistoWorkspace_const_sptr weightingWS) {
const bool useWeights = weightingWS.is_initialized();
uint64_t nPoints = toSmooth->getNPoints();
Progress progress(this, 0, 1, size_t(double(nPoints) * 1.1));
// Create the output workspace
IMDHistoWorkspace_sptr outWS(toSmooth->clone().release());
// Create a temporary workspace
IMDHistoWorkspace_sptr tempWS(toSmooth->clone().release());
progress.reportIncrement(
size_t(double(nPoints) * 0.1)); // Report ~10% progress
// Create a kernel for each dimension and
std::vector<KernelVector> gaussian_kernels;
gaussian_kernels.reserve(widthVector.size());
for (const auto width : widthVector) {
gaussian_kernels.push_back(gaussianKernel(width));
}
const int nThreads = Mantid::API::FrameworkManager::Instance()
.getNumOMPThreads(); // NThreads to Request
auto read_ws = outWS;
auto write_ws = tempWS;
for (size_t dimension_number = 0; dimension_number < widthVector.size();
++dimension_number) {
auto iterators = toSmooth->createIterators(nThreads, NULL);
// Alternately write to each workspace
if (dimension_number % 2 == 0) {
read_ws = outWS;
write_ws = tempWS;
} else {
read_ws = tempWS;
write_ws = outWS;
}
PARALLEL_FOR_NO_WSP_CHECK()
for (int it = 0; it < int(iterators.size()); ++it) { // NOLINT
PARALLEL_START_INTERUPT_REGION
boost::scoped_ptr<MDHistoWorkspaceIterator> iterator(
dynamic_cast<MDHistoWorkspaceIterator *>(iterators[it]));
if (!iterator) {
throw std::logic_error(
"Failed to cast IMDIterator to MDHistoWorkspaceIterator");
}
do {
// Gets linear index at current position
size_t iteratorIndex = iterator->getLinearIndex();
if (useWeights) {
// Check that we could measure here.
if ((*weightingWS)->getSignalAt(iteratorIndex) == 0) {
write_ws->setSignalAt(iteratorIndex,
std::numeric_limits<double>::quiet_NaN());
write_ws->setErrorSquaredAt(
iteratorIndex, std::numeric_limits<double>::quiet_NaN());
continue; // Skip we couldn't measure here.
}
}
std::pair<std::vector<size_t>, std::vector<bool>> indexesAndValidity =
iterator->findNeighbourIndexesByWidth1D(
static_cast<int>(gaussian_kernels[dimension_number].size()),
static_cast<int>(dimension_number));
std::vector<size_t> neighbourIndexes = std::get<0>(indexesAndValidity);
std::vector<bool> indexValidity = std::get<1>(indexesAndValidity);
auto normalised_kernel = renormaliseKernel(
gaussian_kernels[dimension_number], indexValidity);
// Convolve signal with kernel
double sumSignal = 0;
double sumSquareError = 0;
double error = 0;
for (size_t i = 0; i < neighbourIndexes.size(); ++i) {
if (indexValidity[i]) {
sumSignal += read_ws->getSignalAt(neighbourIndexes[i]) *
normalised_kernel[i];
error =
read_ws->getErrorAt(neighbourIndexes[i]) * normalised_kernel[i];
sumSquareError += error * error;
}
write_ws->setSignalAt(iteratorIndex, sumSignal);
write_ws->setErrorSquaredAt(iteratorIndex, sumSquareError);
}
progress.report();
} while (iterator->next());
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
}
return write_ws;
}
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void SmoothMD::init() {
declareProperty(make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>(
"InputWorkspace", "", Direction::Input),
"An input MDHistoWorkspace to smooth.");
auto widthVectorValidator = boost::make_shared<CompositeValidator>();
auto boundedValidator =
boost::make_shared<ArrayBoundedValidator<double>>(1, 1000);
widthVectorValidator->add(boundedValidator);
widthVectorValidator->add(
boost::make_shared<MandatoryValidator<WidthVector>>());
declareProperty(
make_unique<ArrayProperty<double>>("WidthVector", widthVectorValidator,
Direction::Input),
"Width vector. Either specify the width in n-pixels for each "
"dimension, or provide a single entry (n-pixels) for all "
"dimensions. Must be odd integers if Hat function is chosen.");
const auto allFunctionTypes = functions();
const std::string first = allFunctionTypes.front();
std::stringstream docBuffer;
docBuffer << "Smoothing function. Defaults to " << first;
declareProperty(
Kernel::make_unique<PropertyWithValue<std::string>>(
"Function", first,
boost::make_shared<ListValidator<std::string>>(allFunctionTypes),
Direction::Input),
docBuffer.str());
std::vector<std::string> unitOptions = {"pixels"};
std::stringstream docUnits;
docUnits << "The units that WidthVector has been specified in. Allowed "
"values are: ";
for (auto const &unitOption : unitOptions) {
docUnits << unitOption << ", ";
}
declareProperty(
Kernel::make_unique<PropertyWithValue<std::string>>(
"Units", "pixels",
boost::make_shared<ListValidator<std::string>>(unitOptions),
Direction::Input),
docUnits.str());
declareProperty(make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>(
"InputNormalizationWorkspace", "", Direction::Input,
PropertyMode::Optional),
"Multidimensional weighting workspace. Optional.");
declareProperty(make_unique<WorkspaceProperty<API::IMDHistoWorkspace>>(
"OutputWorkspace", "", Direction::Output),
"An output smoothed MDHistoWorkspace.");
}
//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
*/
void SmoothMD::exec() {
// Get the input workspace to smooth
IMDHistoWorkspace_sptr toSmooth = this->getProperty("InputWorkspace");
// Get the input weighting workspace
IMDHistoWorkspace_sptr weightingWS =
this->getProperty("InputNormalizationWorkspace");
OptionalIMDHistoWorkspace_const_sptr optionalWeightingWS;
if (weightingWS) {
optionalWeightingWS = weightingWS;
}
// Get the width vector
std::vector<double> widthVector = this->getProperty("WidthVector");
if (widthVector.size() == 1) {
// Pad the width vector out to the right size if only one entry has been
// provided.
widthVector =
std::vector<double>(toSmooth->getNumDims(), widthVector.front());
}
// Find the chosen smooth operation
const std::string smoothFunctionName = this->getProperty("Function");
SmoothFunctionMap functionMap = makeFunctionMap(this);
SmoothFunction smoothFunction = functionMap[smoothFunctionName];
// invoke the smoothing operation
auto smoothed = smoothFunction(toSmooth, widthVector, optionalWeightingWS);
setProperty("OutputWorkspace", smoothed);
}
/**
* validateInputs
* @return map of property names to errors.
*/
std::map<std::string, std::string> SmoothMD::validateInputs() {
std::map<std::string, std::string> product;
IMDHistoWorkspace_sptr toSmoothWs = this->getProperty("InputWorkspace");
// Function type
std::string function_type = this->getProperty("Function");
// Check the width vector
const std::string widthVectorPropertyName = "WidthVector";
std::vector<double> widthVector = this->getProperty(widthVectorPropertyName);
if (widthVector.size() != 1 &&
widthVector.size() != toSmoothWs->getNumDims()) {
product.emplace(widthVectorPropertyName,
widthVectorPropertyName +
" can either have one entry or needs to "
"have entries for each dimension of the "
"InputWorkspace.");
} else if (function_type == "Hat") {
// If Hat function is used then widthVector must contain odd integers only
for (auto const widthEntry : widthVector) {
double intpart;
if (modf(widthEntry, &intpart) != 0.0) {
std::stringstream message;
message << widthVectorPropertyName << " entries must be (odd) integers "
"when Hat function is chosen. "
"Bad entry is " << widthEntry;
product.emplace(widthVectorPropertyName, message.str());
} else if (static_cast<unsigned long>(widthEntry) % 2 == 0) {
std::stringstream message;
message << widthVectorPropertyName << " entries must be odd integers "
"when Hat function is chosen. "
"Bad entry is " << widthEntry;
}
}
}
// Check the dimensionality of the normalization workspace
const std::string normalisationWorkspacePropertyName =
"InputNormalizationWorkspace";
IMDHistoWorkspace_sptr normWs =
this->getProperty(normalisationWorkspacePropertyName);
if (normWs) {
const size_t nDimsNorm = normWs->getNumDims();
const size_t nDimsSmooth = toSmoothWs->getNumDims();
if (nDimsNorm != nDimsSmooth) {
std::stringstream message;
message << normalisationWorkspacePropertyName
<< " has a different number of dimensions than InputWorkspace. "
"Shapes of inputs must be the same. Cannot continue "
"smoothing.";
product.insert(
std::make_pair(normalisationWorkspacePropertyName, message.str()));
} else {
// Loop over dimensions and check nbins.
for (size_t i = 0; i < nDimsNorm; ++i) {
const size_t nBinsNorm = normWs->getDimension(i)->getNBins();
const size_t nBinsSmooth = toSmoothWs->getDimension(i)->getNBins();
if (nBinsNorm != nBinsSmooth) {
std::stringstream message;
message << normalisationWorkspacePropertyName
<< ". Number of bins from dimension with index " << i
<< " do not match. " << nBinsSmooth << " expected. Got "
<< nBinsNorm << ". Shapes of inputs must be the same. Cannot "
"continue smoothing.";
product.emplace(normalisationWorkspacePropertyName, message.str());
break;
}
}
}
}
return product;
}
} // namespace MDAlgorithms
} // namespace Mantid