"...Mantid/Testing/git@code.ornl.gov:mantidproject/mantid.git" did not exist on "c9bf0d1acd8b871e069803abeb15a658694b059b"
Newer
Older
#include "MantidAlgorithms/CalculateFlatBackground.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/SpectrumInfo.h"
Federico Montesino Pouzols
committed
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAPI/WorkspaceOpOverloads.h"
#include "MantidDataObjects/TableWorkspace.h"
#include "MantidGeometry/IDetector.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/EnabledWhenProperty.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/VectorHelper.h"
#include "MantidHistogramData/Histogram.h"
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <climits>
namespace Mantid {
namespace Algorithms {
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(CalculateFlatBackground)
using namespace Kernel;
using namespace API;
/// Enumeration for the different operating modes.
enum class Modes { LINEAR_FIT, MEAN, MOVING_AVERAGE };
void CalculateFlatBackground::init() {
declareProperty(
make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input),
"The input workspace must either have constant width bins or is a "
"distribution\n"
"workspace. It is also assumed that all spectra have the same X bin "
"boundaries");
declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
Direction::Output),
"Name to use for the output workspace.");
declareProperty("StartX", Mantid::EMPTY_DBL(),
"The X value at which to start the background fit. Mandatory "
"for the Linear Fit and Mean modes, ignored by Moving "
"Average.");
setPropertySettings("StartX", make_unique<EnabledWhenProperty>(
"Mode", IS_NOT_EQUAL_TO, "Moving Average"));
declareProperty("EndX", Mantid::EMPTY_DBL(),
"The X value at which to end the background fit. Mandatory "
"for the Linear Fit and Mean modes, ignored by Moving "
"Average.");
setPropertySettings("EndX", make_unique<EnabledWhenProperty>(
"Mode", IS_NOT_EQUAL_TO, "Moving Average"));
make_unique<ArrayProperty<int>>("WorkspaceIndexList"),
"Indices of the spectra that will have their background removed\n"
"default: modify all spectra");
std::vector<std::string> modeOptions{"Linear Fit", "Mean", "Moving Average"};
declareProperty("Mode", "Linear Fit",
boost::make_shared<StringListValidator>(modeOptions),
"The background count rate is estimated either by taking a "
"mean, doing a linear fit, or taking the\n"
"minimum of a moving average (default: Linear Fit)");
// Property to determine whether we subtract the background or just return the
// background.
std::vector<std::string> outputOptions{"Subtract Background",
"Return Background"};
declareProperty("OutputMode", "Subtract Background",
boost::make_shared<StringListValidator>(outputOptions),
"Once the background has been determined it can either be "
"subtracted from \n"
"the InputWorkspace and returned or just returned (default: "
"Subtract Background)");
declareProperty("SkipMonitors", false,
"By default, the algorithm calculates and removes background "
"from monitors in the same way as from normal detectors\n"
"If this property is set to true, background is not "
"calculated/removed from monitors.",
Direction::Input);
declareProperty("NullifyNegativeValues", true,
"When background is subtracted, signals in some time "
"channels may become negative.\n"
"If this option is true, signal in such bins is nullified "
"and the module of the removed signal"
"is added to the error. If false, the signal and errors are "
"left unchanged",
Direction::Input);
declareProperty("AveragingWindowWidth", Mantid::EMPTY_INT(),
"The width of the moving average window in bins. Mandatory "
"for the Moving Average mode.");
setPropertySettings(
"AveragingWindowWidth",
make_unique<EnabledWhenProperty>("Mode", IS_EQUAL_TO, "Moving Average"));
}
void CalculateFlatBackground::exec() {
// Retrieve the input workspace
API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
const size_t numHists = inputWS->getNumberHistograms();
const size_t blocksize = inputWS->blocksize();
if (blocksize == 1)
throw std::runtime_error("CalculateFlatBackground with only one bin is "
"not possible.");
const bool skipMonitors = getProperty("SkipMonitors");
const bool nullifyNegative = getProperty("NullifyNegativeValues");
const std::string modeString = getProperty("Mode");
Modes mode = Modes::LINEAR_FIT;
if (modeString == "Mean") {
mode = Modes::MEAN;
} else if (modeString == "Moving Average") {
mode = Modes::MOVING_AVERAGE;
}
int windowWidth = 0;
switch (mode) {
case Modes::LINEAR_FIT:
case Modes::MEAN:
if (getPointerToProperty("StartX")->isDefault()) {
throw std::runtime_error("StartX property not set to any value");
}
if (getPointerToProperty("EndX")->isDefault()) {
throw std::runtime_error("EndX property not set to any value");
}
// Get the required X range
checkRange(startX, endX);
break;
case Modes::MOVING_AVERAGE:
if (getPointerToProperty("AveragingWindowWidth")->isDefault()) {
throw std::runtime_error(
"AveragingWindowWidth property not set to any value");
windowWidth = getProperty("AveragingWindowWidth");
if (windowWidth <= 0) {
throw std::runtime_error("AveragingWindowWidth zero or negative");
}
if (blocksize < static_cast<size_t>(windowWidth)) {
throw std::runtime_error("AveragingWindowWidth is larger than the number "
"of bins in InputWorkspace");
}
break;
}
std::vector<int> wsInds = getProperty("WorkspaceIndexList");
// check if the user passed an empty list, if so all of spec will be processed
if (wsInds.empty()) {
wsInds.resize(numHists);
std::iota(wsInds.begin(), wsInds.end(), 0);
}
// Are we removing the background?
const bool removeBackground =
std::string(getProperty("outputMode")) == "Subtract Background";
// Initialize the progress reporting object
m_progress = Kernel::make_unique<Progress>(this, 0.0, 0.2, numHists);
MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
// If input and output workspaces are not the same, create a new workspace for
// the output
if (outputWS != inputWS) {
outputWS = WorkspaceFactory::Instance().create(inputWS);
}
// For logging purposes.
double backgroundTotal = 0;
size_t calculationCount = 0;
PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS, *outputWS))
for (int64_t i = 0; i < static_cast<int64_t>(numHists); ++i) {
PARALLEL_START_INTERUPT_REGION
// Extract a histogram from inputWS, work on it and, finally, put it to
// outputWS.
auto histogram = inputWS->histogram(i);
bool wasCounts = false;
if (histogram.yMode() == HistogramData::Histogram::YMode::Counts) {
wasCounts = true;
histogram.convertToFrequencies();
}
bool skipCalculation =
std::find(wsInds.cbegin(), wsInds.cend(), i) == wsInds.cend();
if (!skipCalculation && skipMonitors) {
const auto &spectrumInfo = inputWS->spectrumInfo();
if (!spectrumInfo.hasDetectors(i)) {
// Do nothing.
// not every spectra is the monitor or detector, some spectra have no
// instrument components attached.
g_log.information(" Can not find detector for spectra N: " +
std::to_string(i) +
" Processing background anyway\n");
} else if (spectrumInfo.isMonitor(i)) {
skipCalculation = true;
}
double background = 0;
double variance = 0;
if (!skipCalculation) {
++calculationCount;
// Now call the function the user selected to calculate the background
switch (mode) {
case Modes::LINEAR_FIT:
LinearFit(histogram, background, variance, startX, endX);
break;
case Modes::MEAN:
Mean(histogram, background, variance, startX, endX);
break;
case Modes::MOVING_AVERAGE:
MovingAverage(histogram, background, variance, windowWidth);
break;
}
}
if (background < 0) {
g_log.debug() << "The background for spectra index " << i
<< "was calculated to be " << background << '\n';
g_log.warning() << "Problem with calculating the background number of "
"counts spectrum with index " << i << ".";
if (removeBackground) {
g_log.warning() << " The spectrum has been left unchanged.\n";
} else {
g_log.warning() << " The output background has been set to zero.\n";
} else {
backgroundTotal += background;
}
HistogramData::Histogram outHistogram(histogram);
auto &ys = outHistogram.mutableY();
auto &es = outHistogram.mutableE();
if (removeBackground) {
// When subtracting backgrounds, act only if background is positive.
if (background >= 0) {
for (size_t j = 0; j < ys.size(); ++j) {
double val = ys[j] - background;
double err = std::sqrt(es[j] * es[j] + variance);
val = 0;
// The error estimate must go up in this nonideal situation and the
// value of background is a good estimate for it. However, don't
// reduce the error if it was already more than that
err = es[j] > background ? es[j] : background;
}
ys[j] = val;
es[j] = err;
}
}
} else {
for (size_t j = 0; j < ys.size(); ++j) {
if (background < 0) {
ys[j] = 0;
es[j] = 0;
} else if (nullifyNegative && (background > originalVal)) {
ys[j] = originalVal;
es[j] = es[j] > background ? es[j] : background;
} else {
ys[j] = background;
es[j] = std::sqrt(variance);
if (wasCounts) {
outHistogram.convertToCounts();
outputWS->setHistogram(i, outHistogram);
m_progress->report();
PARALLEL_END_INTERUPT_REGION
PARALLEL_CHECK_INTERUPT_REGION
g_log.debug() << calculationCount << " spectra corrected\n";
g_log.information() << "The mean background was "
<< backgroundTotal / static_cast<double>(calculationCount)
<< ".\n";
// Assign the output workspace to its property
setProperty("OutputWorkspace", outputWS);
}
/**
* Checks that the range parameters have been set correctly.
* @param startX the starting point
* @param endX the ending point
* @throw std::invalid_argument if XMin or XMax are not set, or XMax is less
* than XMin
*/
void CalculateFlatBackground::checkRange(double &startX, double &endX) {
// use the overloaded operator =() to get the X value stored in each property
startX = getProperty("StartX");
endX = getProperty("EndX");
if (startX > endX) {
const std::string failure("XMax must be greater than XMin.");
g_log.error(failure);
throw std::invalid_argument(failure);
}
}
/**
* Gets the mean number of counts in each bin the background region and the
* variance (error^2) of that number.
* @param histogram the histogram to operate on
* @param background an output variable for the calculated background
* @param variance an output variable for background's variance.
* @param startX an X-value in the first bin that will be considered, must not
* @param endX an X-value in the last bin that will be considered, must not
* @throw out_of_range if either startX or endX are out of the range of X-values
* @throw invalid_argument if endX has the value of first X-value one of the
void CalculateFlatBackground::Mean(const HistogramData::Histogram &histogram,
const double startX,
const double endX) const {
const auto &XS = histogram.x();
const auto &YS = histogram.y();
const auto &ES = histogram.e();
// the function checkRange should already have checked that startX <= endX,
// but we still need to check values weren't out side the ranges
if ((endX > XS.back()) || (startX < XS.front())) {
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
throw std::out_of_range("Either the property startX or endX is outside the "
"range of X-values present in one of the specified "
"spectra");
}
// Get the index of the first bin contains the X-value, which means this is an
// inclusive sum. The minus one is because lower_bound() returns index past
// the last index pointing to a lower value. For example if startX has a
// higher X value than the first bin boundary but lower than the second
// lower_bound returns 1, which is the index of the second bin boundary
ptrdiff_t startInd =
std::lower_bound(XS.begin(), XS.end(), startX) - XS.begin() - 1;
if (startInd == -1) { // happens if startX is the first X-value, e.g. the
// first X-value is zero and the user selects zero
startInd = 0;
}
// the -1 matches definition of startIn, see the comment above that statement
const ptrdiff_t endInd =
std::lower_bound(XS.begin() + startInd, XS.end(), endX) - XS.begin() - 1;
if (endInd == -1) { //
throw std::invalid_argument("EndX was set to the start of one of the "
"spectra, it must greater than the first "
"X-value in any of the specified spectra");
}
// the +1 is because this is an inclusive sum (includes each bin that contains
// each X-value). Hence if startInd == endInd we are still analyzing one bin
const double numBins = static_cast<double>(1 + endInd - startInd);
// the +1 here is because the accumulate() stops one before the location of
// the last iterator
background =
std::accumulate(YS.begin() + startInd, YS.begin() + endInd + 1, 0.0) /
numBins;
// The error on the total number of background counts in the background region
// is taken as the sqrt the total number counts. To get the the error on the
// counts in each bin just divide this by the number of bins. The variance =
// error^2 that is the total variance divide by the number of bins _squared_.
variance = std::accumulate(ES.begin() + startInd, ES.begin() + endInd + 1,
0.0, VectorHelper::SumSquares<double>()) /
(numBins * numBins);
}
/**
* Uses linear algorithm to do the fitting.
* @param background an output variable for the calculated background
* @param variance an output variable for background's variance, currently always
* zero.
* @param startX an X value in the first bin to be included in the fit
* @param endX an X value in the last bin to be included in the fit
void CalculateFlatBackground::LinearFit(
const HistogramData::Histogram &histogram, double &background,
double &variance, const double startX, const double endX) {
MatrixWorkspace_sptr WS = WorkspaceFactory::Instance().create(
"Workspace2D", 1, histogram.x().size(), histogram.y().size());
WS->setHistogram(0, histogram);
IAlgorithm_sptr childAlg = createChildAlgorithm("Fit");
IFunction_sptr func =
API::FunctionFactory::Instance().createFunction("LinearBackground");
childAlg->setProperty<IFunction_sptr>("Function", func);
childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
childAlg->setProperty<bool>("CreateOutput", true);
childAlg->setProperty<int>("WorkspaceIndex", 0);
childAlg->setProperty<double>("StartX", startX);
childAlg->setProperty<double>("EndX", endX);
// Default minimizer doesn't work properly even on the easiest cases,
// so Levenberg-MarquardtMD is used instead
childAlg->setProperty<std::string>("Minimizer", "Levenberg-MarquardtMD");
childAlg->executeAsChildAlg();
std::string outputStatus = childAlg->getProperty("OutputStatus");
if (outputStatus != "success") {
g_log.warning("Unable to successfully fit the data: " + outputStatus);
background = -1;
return;
}
Mantid::API::ITableWorkspace_sptr output =
childAlg->getProperty("OutputParameters");
// Find rows with parameters we are after
size_t rowA0, rowA1;
output->find(static_cast<std::string>("A0"), rowA0, 0);
output->find(static_cast<std::string>("A1"), rowA1, 0);
// Linear function is defined as A0 + A1*x
const double intercept = output->cell<double>(rowA0, 1);
const double slope = output->cell<double>(rowA1, 1);
const double centre = (startX + endX) / 2.0;
// Calculate the value of the flat background by taking the value at the
// centre point of the fit
background = slope * centre + intercept;
// ATM we don't calculate the error here.
variance = 0;
/**
* Utilizes cyclic boundary conditions when calculating the
* average in the window.
* @param histogram the histogram to operate on
* @param background an output variable for the calculated background
* @param variance an output variable for background's variance.
* @param windowWidth the width of the averaging window in bins
void CalculateFlatBackground::MovingAverage(
const HistogramData::Histogram &histogram, double &background,
double &variance, const size_t windowWidth) const {
const auto &ys = histogram.y();
const auto &es = histogram.e();
double currentMin = std::numeric_limits<double>::max();
double currentVariance = 0;
for (size_t i = 0; i < ys.size(); ++i) {
double sum = 0;
double varSqSum = 0;
for (size_t j = 0; j < windowWidth; ++j) {
size_t index = i + j;
if (index >= ys.size()) {
// Cyclic boundary conditions.
index -= ys.size();
}
sum += ys[index];
varSqSum += es[index] * es[index];
}
const double average = sum / static_cast<double>(windowWidth);
if (average < currentMin) {
currentMin = average;
currentVariance =
varSqSum / static_cast<double>(windowWidth * windowWidth);
}
background = currentMin;
variance = currentVariance;
}
} // namespace Mantid