Skip to content
Snippets Groups Projects
CreateUserDefinedBackground.cpp 9.74 KiB
Newer Older
#include "MantidAlgorithms/CreateUserDefinedBackground.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/ITableWorkspace.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/TableRow.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/Interpolation.h"

#include <algorithm>

namespace Mantid {
namespace Algorithms {

// Key for the "normalize data to bin width" plot option
const std::string CreateUserDefinedBackground::AUTODISTRIBUTIONKEY =
    "graph1d.autodistribution";

using Mantid::API::WorkspaceProperty;
using Mantid::Kernel::Direction;
using Mantid::HistogramData::Histogram;
using Mantid::HistogramData::Frequencies;
using Mantid::HistogramData::FrequencyStandardDeviations;

// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(CreateUserDefinedBackground)

//----------------------------------------------------------------------------------------------

/// Algorithm's name for identification. @see Algorithm::name
const std::string CreateUserDefinedBackground::name() const {
  return "CreateUserDefinedBackground";
}

/// Algorithm's version for identification. @see Algorithm::version
int CreateUserDefinedBackground::version() const { return 1; }

/// Algorithm's category for identification. @see Algorithm::category
const std::string CreateUserDefinedBackground::category() const {
  return "CorrectionFunctions\\BackgroundCorrections";
}

/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string CreateUserDefinedBackground::summary() const {
  return "Creates a workspace of background data from a user-supplied set of "
         "points. This workspace can then be subtracted from the original "
         "data.";
}

//----------------------------------------------------------------------------------------------
/**
 * Initialize the algorithm's properties.
 */
void CreateUserDefinedBackground::init() {
  declareProperty(Kernel::make_unique<WorkspaceProperty<API::MatrixWorkspace>>(
                      "InputWorkspace", "", Direction::Input),
                  "Input workspace containing data and background");
  declareProperty(Kernel::make_unique<WorkspaceProperty<API::ITableWorkspace>>(
                      "BackgroundPoints", "", Direction::Input),
                  "Table containing user-defined background points");
  declareProperty(Kernel::make_unique<WorkspaceProperty<API::MatrixWorkspace>>(
                      "OutputBackgroundWorkspace", "", Direction::Output),
                  "Workspace containing background to be subtracted");
}

//----------------------------------------------------------------------------------------------
/**
 * Execute the algorithm.
 */
void CreateUserDefinedBackground::exec() {
  const API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
  API::ITableWorkspace_sptr pointsTable = getProperty("BackgroundPoints");

  // Clean up input points table and extend to range of data
  cleanUpTable(pointsTable);
  extendBackgroundToData(pointsTable, inputWS);

  // Generate output workspace with background data
  const auto outputWS = createBackgroundWorkspace(pointsTable, inputWS);

  setProperty("OutputBackgroundWorkspace",
              API::MatrixWorkspace_sptr(std::move(outputWS)));
}

/**
 * Cleans up input points table by sorting points and removing any (0, 0) blank
 * rows from the end of the table
 * (Only delete (0, 0) from the end as other (0, 0) are real points.)
 * @param table :: [input, output] Table of points to work on
 */
void CreateUserDefinedBackground::cleanUpTable(
    API::ITableWorkspace_sptr &table) const {
  // Delete blank (zero) rows at the end of the table
  std::vector<size_t> blankRows;
  const auto isZero = [](const double n) {
    return !(fabs(n) > std::numeric_limits<double>::epsilon());
  };
  for (size_t i = table->rowCount() - 1; i > 0; i--) {
    double x, y;
    API::TableRow row = table->getRow(i);
    row >> x >> y;
    if (!isZero(x)) {
      break;
    } else if (isZero(y)) {
      blankRows.push_back(i);
    }
  }
  for (const auto &row : blankRows) {
    table->removeRow(row);
  }

  // Sort the table
  std::vector<std::pair<std::string, bool>> sortArgs;
  sortArgs.emplace_back(table->getColumn(0)->name(), true);
  table->sort(sortArgs);
}

/**
 * Extend background to limits of data: if it doesn't extend this far already,
 * insert first and last points of data into it.
 * @param background :: [input, output] Table of background points to work on
 * @param data :: [input] Input workspace with data
 */
void CreateUserDefinedBackground::extendBackgroundToData(
    API::ITableWorkspace_sptr &background,
    const API::MatrixWorkspace_const_sptr &data) const {
  const auto &xPoints = data->points(0);

  // If first point > data minimum, insert a new first point
  if (background->Double(0, 0) > xPoints[0]) {
    background->insertRow(0);
    API::TableRow firstRow = background->getFirstRow();
    firstRow << xPoints.front() << background->Double(0, 1);
  }
  // If last point < data maximum, append a new last point
  const size_t endIndex = background->rowCount() - 1;
  if (background->Double(endIndex, 0) < xPoints.back()) {
    API::TableRow lastRow = background->appendRow();
    lastRow << xPoints.back() << background->Double(endIndex, 1);
  }
}

/**
 * Given a table of background points and the original workspace,
 * return a new workspace containing interpolated background data.
 * The same background is assumed for all spectra.
 * @param background :: [input] Table of background points
 * @param data :: [input] Original data workspace
 * @returns :: Workspace containing background to be subtracted
 */
API::MatrixWorkspace_sptr
CreateUserDefinedBackground::createBackgroundWorkspace(
    const API::ITableWorkspace_const_sptr &background,
    const API::MatrixWorkspace_const_sptr &data) const {
  auto outputWS = data->clone();
  const auto &xPoints = outputWS->points(0);
  const auto &xBinEdges = outputWS->binEdges(0);
  std::vector<double> yBackground;
  std::vector<double> eBackground(xPoints.size(), 0);
  // Interpolate Y data in the table to get y for each point
  const auto &lerp = getInterpolator(background, data);
  for (const auto &x : xPoints) {
    const double y = lerp.value(x);
    yBackground.push_back(y);
  auto histogram = outputWS->histogram(0);
  if (histogram.yMode() == Histogram::YMode::Frequencies) {
    histogram.setFrequencies(yBackground);
    histogram.setFrequencyStandardDeviations(eBackground);
  } else {
    if (data->isHistogramData() &&
        "On" ==
            Kernel::ConfigService::Instance().getString(AUTODISTRIBUTIONKEY)) {
      // Background data is actually frequencies, we put it into temporary to
      // benefit from automatic conversion in setCounts(), etc.
      histogram.setCounts(Frequencies(yBackground), xBinEdges);
      histogram.setCountStandardDeviations(
          FrequencyStandardDeviations(eBackground), xBinEdges);
      histogram.setCounts(yBackground);
      histogram.setCountStandardDeviations(eBackground);
  // Apply Y and E data to all spectra in the workspace
  for (size_t spec = 0; spec < outputWS->getNumberHistograms(); spec++) {
    // Setting same histogram for all spectra, data is shared, saving memory
    outputWS->setHistogram(spec, histogram);
  return API::MatrixWorkspace_sptr(std::move(outputWS));
/**
 * Set up and return an interpolation object using the given data
 * @param background :: [input] Background data to interpolate
 * @param workspace :: [input] Workspace to use for units
 * @returns :: Interpolation object ready for use
 */
Kernel::Interpolation CreateUserDefinedBackground::getInterpolator(
    const API::ITableWorkspace_const_sptr &background,
    const API::MatrixWorkspace_const_sptr &workspace) const {
  Kernel::Interpolation lerp;
  lerp.setMethod("linear");
  lerp.setXUnit(workspace->getAxis(0)->unit()->unitID());
  lerp.setYUnit(workspace->getAxis(1)->unit()->unitID());

  // Set up data from table
  const auto xColumn = background->getColumn(0);
  const auto yColumn = background->getColumn(1);
  for (size_t i = 0; i < background->rowCount(); i++) {
    double x = xColumn->cell<double>(i);
    double y = yColumn->cell<double>(i);
    lerp.addPoint(x, y);
  }
  return lerp;
}

/**
 * Validate input properties:
 * - Table of points must have two numeric columns (X, Y)
 * - Table of points must contain at least two points
 * - Input workspace must contain at least one spectrum and two points
 * - Input workspace must have common bins in all spectra
 * @returns :: map of property names to errors (empty if no errors)
 */
std::map<std::string, std::string>
CreateUserDefinedBackground::validateInputs() {
  std::map<std::string, std::string> errors;

  const static std::string pointsProp = "BackgroundPoints",
                           inputProp = "InputWorkspace";
  const API::ITableWorkspace_const_sptr pointsTable = getProperty(pointsProp);
  if (pointsTable) {
    if (pointsTable->columnCount() != 2) {
      errors[pointsProp] = "Table of points must have two columns (X, Y)";
    }
    for (size_t col = 0; col < pointsTable->columnCount(); col++) {
      const std::string colType = pointsTable->getColumn(col)->type();
      if (colType != "double" && colType != "int") {
        errors[pointsProp] = "Table of points must have numeric columns";
      }
    }
    if (pointsTable->rowCount() < 2) {
      errors[pointsProp] = "Table of points must contain at least two points";
    }
  }

  const API::MatrixWorkspace_const_sptr inputWS = getProperty(inputProp);
  if (inputWS) {
    if (inputWS->getNumberHistograms() == 0 || inputWS->blocksize() < 2) {
      errors[inputProp] = "Input workspace must contain some data";
    }
    if (!inputWS->isCommonBins()) {
      errors[inputProp] = "Input workspace must have common bins";
    }
  }

  return errors;
}

} // namespace Algorithms
} // namespace Mantid