16#include "MantidHistogramData/Histogram.h"
24#include <boost/math/distributions/normal.hpp>
49 return "Finds peaks in a dataset through the use of a convolution vector";
59 "An input workspace.");
62 "An output workspace.");
63 declareProperty(
"CreateIntermediateWorkspaces",
false,
"Output workspaces showing intermediate working steps");
67 "Estimated PeakExtent of the peaks to be found");
69 "Optional: Estimated PeakExtent of the peaks to be found in number of bins");
71 "Minimum Signal/Noise ratio for a peak to be considered significant");
73 "Attempt to remove inflections in the data, where a local"
74 " minima/maxima occurs which is not signficiant enough to be considered a peak");
76 "When searching for peaks in the raw data around the iOverSigma maxima, take the highest value,"
77 " rather than favouring datapoints closer to the maxima");
86 if (validation_res.second != 0) {
87 throw std::invalid_argument(
"Validation error: " + validation_res.first);
90 for (
int i = 0; i < static_cast<int>(
m_specCount); i++) {
103 auto mustBeNonNegative{std::make_shared<Kernel::BoundedValidator<int>>()};
104 mustBeNonNegative->setLower(0);
105 m_validators.emplace(std::make_pair(
"mustBeNonNegative", std::move(mustBeNonNegative)));
107 auto mustBeGreaterThanZero{std::make_shared<Kernel::BoundedValidator<double>>()};
108 mustBeGreaterThanZero->setLowerExclusive(0.0);
109 m_validators.emplace(
"mustBeGreaterThanZero", std::move(mustBeGreaterThanZero));
111 auto mustBeGreaterThanOne{std::make_shared<Kernel::BoundedValidator<int>>()};
112 mustBeGreaterThanOne->setLowerExclusive(1);
113 m_validators.emplace(
"mustBeGreaterThanOne", std::move(mustBeGreaterThanOne));
117 std::string err_str{};
126 return std::make_pair(err_str, err_code);
130 std::string err_str{};
131 const double peakExtent =
getProperty(
"EstimatedPeakExtent");
132 const int peakExtentNBins =
getProperty(
"EstimatedPeakExtentNBins");
134 err_str +=
"Peak Extent has been given in x units and in number of bins. Please specify one or the other. ";
136 err_str +=
"You must specify either peakExtent or peakExtentNBins. ";
142 std::string err_str{};
144 err_str +=
"If both specified, EndWorkspaceIndex must be greater than StartWorkspaceIndex. ";
148 int specCount =
static_cast<int>(
m_inputDataWS->getNumberHistograms());
149 if (startWSIndex > specCount - 1 || endWSIndex > specCount - 1) {
150 err_str +=
"Specified Workspace indicies out of range. ";
169 int startWSIndex =
getProperty(
"StartWorkspaceIndex");
171 int specCount =
static_cast<int>(
m_inputDataWS->getNumberHistograms());
176 endWSIndex = specCount - 1;
186 const HistogramData::HistogramX *xData{&
m_inputDataWS->x(specNum)};
188 if (kernelBinCount.second) {
190 " exceeds the range of the x axis. Please reduce the peak extent.");
197 const double paddingSize = (std::ceil(
static_cast<double>(kernelBinCount.first) * 1.5) - 2) / 2;
199 Eigen::array<std::pair<double, double>, 1> paddings{
200 std::make_pair(std::ceil(paddingSize), std::floor(paddingSize))};
201 const auto yData_padded{yData.pad(paddings)};
202 const Eigen::array<ptrdiff_t, 1> dims({0});
203 const Tensor1D yConvOutput{yData_padded.convolve(kernel, dims)};
205 const Tensor1D eConvOutput{eData.square().convolve(kernel.square(), dims).sqrt()};
207 createSmoothKernel(
static_cast<size_t>(std::ceil(
static_cast<double>(kernelBinCount.first) / 2.0)))};
208 Tensor1D iOverSig{(yConvOutput / eConvOutput).unaryExpr([](
double val) {
return std::isfinite(val) ? val : 0.0; })};
209 const Tensor1D iOverSigConvOutput{iOverSig.convolve(smoothKernel, dims)};
210 extractPeaks(dataIndex, iOverSigConvOutput, xData, yData, kernelBinCount.first / 2);
213 std::lock_guard<std::mutex> lock(
m_mtx);
221 for (
int i{0}; i < binCount; i++) {
224 if (i < std::ceil(binCount) * 0.25 || i >= binCount * 0.75) {
225 kernel.data()[i] = -1.0;
227 kernel.data()[i] = 1.0;
235 for (
size_t i{0}; i < kernelSize; i++) {
236 kernel.data()[i] = 1.0 /
static_cast<double>(kernelSize);
242 const double peakExtent =
getProperty(
"EstimatedPeakExtent");
243 const int peakExtentNBins =
getProperty(
"EstimatedPeakExtentNBins");
244 bool sizeError{
false};
246 size_t kernelBinCount{0};
248 const double x1{xData->rawData()[
static_cast<size_t>(std::floor((xData->size() - 1) / 2))]};
249 const double x2{xData->rawData()[
static_cast<size_t>(std::floor((xData->size() - 1) / 2)) + 1]};
250 kernelBinCount =
static_cast<size_t>(std::floor(peakExtent * 2 / (x2 - x1)));
252 kernelBinCount =
static_cast<size_t>(peakExtentNBins);
254 if (kernelBinCount > xData->size()) {
257 return std::make_pair(kernelBinCount, sizeError);
261 Eigen::VectorXd xDataVec{0.5 * (
EigenMap_const(&xData->front(), xData->size() - 1) +
268 const size_t peakExtentBinNumber) {
269 int dataPointCount{0};
270 std::pair<int, double> dataRegionMax{0, 0.0};
271 std::vector<FindPeaksConvolve::PeakResult> peakCentres;
272 for (
auto i{0}; i < iOverSigma.size(); i++) {
274 if (dataPointCount == 0) {
275 dataRegionMax = std::make_pair(i, iOverSigma(i));
277 if (iOverSigma.data()[i] > dataRegionMax.second) {
278 dataRegionMax = {i, iOverSigma(i)};
283 if (dataPointCount >= 2) {
284 size_t rawPeakIndex{
findPeakInRawData(dataRegionMax.first, yData, peakExtentBinNumber)};
286 yData.data()[rawPeakIndex], dataRegionMax.second});
288 if (dataPointCount > 0) {
290 dataRegionMax = std::make_pair(0, 0.0);
299 return (xData->rawData()[xIndex] + xData->rawData()[xIndex + 1]) / 2;
301 return xData->rawData()[xIndex];
306 std::vector<FindPeaksConvolve::PeakResult> &peakCentres) {
307 const size_t peakCount = peakCentres.size();
312 std::lock_guard<std::mutex> lock(
m_mtx);
318 size_t peakExtentBinNumber) {
319 peakExtentBinNumber = (peakExtentBinNumber % 2 == 0) ? peakExtentBinNumber + 1 : peakExtentBinNumber;
320 int sliceStart{xIndex -
static_cast<int>(std::floor(
static_cast<double>(peakExtentBinNumber) / 2.0))};
321 size_t adjPeakExtentBinNumber{peakExtentBinNumber};
324 if (sliceStart < 0) {
325 startAdj = -sliceStart;
326 adjPeakExtentBinNumber = peakExtentBinNumber - startAdj;
329 if (sliceStart + adjPeakExtentBinNumber >
static_cast<size_t>(yData.size()) - 1) {
330 adjPeakExtentBinNumber =
static_cast<size_t>(yData.size()) - sliceStart;
333 Eigen::VectorXd::Index maxIndex;
335 const auto unweightedYData =
EigenMap_const(yData.data() + sliceStart, adjPeakExtentBinNumber);
336 unweightedYData.maxCoeff(&maxIndex);
339 const auto yDataMap =
EigenMap_const(yData.data() + sliceStart, adjPeakExtentBinNumber);
341 std::lock_guard<std::mutex> lock(
m_mtx);
342 const auto weightedYData = yDataMap.cwiseProduct(
EigenMap_const(
m_pdf.data() + startAdj, adjPeakExtentBinNumber));
343 weightedYData.maxCoeff(&maxIndex);
346 return static_cast<size_t>(maxIndex) + sliceStart;
350 std::lock_guard<std::mutex> lock(
m_mtx);
351 if (
m_pdf.size() == 0) {
352 m_pdf.resize(peakExtentBinNumber);
353 boost::math::normal_distribution<> dist(0.0,
354 peakExtentBinNumber / 2.0);
355 const int meanIdx{peakExtentBinNumber / 2};
356 for (
int i{0}; i < peakExtentBinNumber; ++i) {
358 m_pdf(i) = boost::math::pdf(dist,
x);
365 const HistogramData::HistogramX *xData) {
366 std::unique_ptr<EigenMap_const> xDataMap;
367 Eigen::VectorXd xDataCentredBins;
370 xDataMap = std::make_unique<EigenMap_const>(xDataCentredBins.data(), xDataCentredBins.size());
372 xDataMap = std::make_unique<EigenMap_const>(&xData->front(), xData->size());
375 const std::string iOverSigmaOutputName =
378 std::vector<double>(xDataMap->data(), xDataMap->data() + xDataMap->rows()),
379 std::vector<double>(iOverSigma.data(), iOverSigma.data() + iOverSigma.size()));
381 std::vector<double> xKernelData(kernel.size());
382 std::iota(std::begin(xKernelData), std::end(xKernelData), 0.0);
383 const std::string kernelOutputName =
386 std::vector<double>(kernel.data(), kernel.data() + kernel.size()));
387 return std::vector<std::string>{iOverSigmaOutputName, kernelOutputName};
391 const std::vector<double> &yData) {
393 alg->setProperty(
"OutputWorkspace", outputWsName);
394 alg->setProperty(
"DataX", xData);
395 alg->setProperty(
"DataY", yData);
398 API::AnalysisDataService::Instance().addOrReplace(outputWsName, algOutput);
402 const std::vector<std::string> outputTblNames{
"PeakCentre",
"PeakYPosition",
"PeakIOverSigma"};
403 std::unordered_map<std::string, API::ITableWorkspace_sptr> outputTbls{
createOutputTables(outputTblNames)};
406 if (noPeaksStr !=
"") {
407 g_log.
warning(
"No peaks found for spectrum index: " + noPeaksStr);
415 [](
const std::string &a,
const std::string &b) {
416 return std::stoi(a.substr(a.find_last_of(
"_") + 1, a.size())) <
417 std::stoi(b.substr(b.find_last_of(
"_") + 1, b.size()));
420 API::AnalysisDataService::Instance().addOrReplace(
"IntermediateWorkspaces", groupedOutput);
424std::unordered_map<std::string, API::ITableWorkspace_sptr>
426 std::unordered_map<std::string, API::ITableWorkspace_sptr> outputTbls;
427 for (
const auto &outputTblName : outputTblNames) {
429 tbl.first->second->addColumn(
"int",
"SpecIndex");
431 tbl.first->second->addColumn(
"double", outputTblName +
"_" +
std::to_string(i));
433 API::AnalysisDataService::Instance().addOrReplace(outputTblName, tbl.first->second);
439 const std::vector<std::string> &outputTblNames) {
442 alg->setProperty(
"InputWorkspaces", outputTblNames);
443 alg->setProperty(
"OutputWorkspace", outputName);
450 const std::vector<std::string> &outputTblNames,
451 const std::unordered_map<std::string, API::ITableWorkspace_sptr> &outputTbls) {
452 std::string noPeaksStr{
""};
456 for (
const auto &outputTblName : outputTblNames) {
457 auto tbl = outputTbls.find(outputTblName);
461 if (peak_i < spec.size()) {
462 const auto &peak = spec[peak_i];
463 row << peak.getAttribute(outputTblName);
477 if (attrString ==
"PeakCentre") {
479 }
else if (attrString ==
"PeakYPosition") {
481 }
else if (attrString ==
"PeakIOverSigma") {
#define DECLARE_ALGORITHM(classname)
Eigen::Map< const Eigen::VectorXd > EigenMap_const
Eigen::Tensor< double, 1 > Tensor1D
Eigen::TensorMap< const Eigen::Tensor< double, 1 > > TensorMap_const
#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.
TableRow represents a row in a TableWorkspace.
A property class for workspaces.
FindPeaksConvolve : Finds peak centres using convolution with a shoebox kernel to approximate the sec...
API::WorkspaceGroup_sptr groupOutputWorkspaces(const std::string &outputName, const std::vector< std::string > &outputTblNames)
const std::string validatePeakExtentInput() const
std::vector< std::vector< FindPeaksConvolve::PeakResult > > m_peakResults
std::unordered_map< std::string, API::ITableWorkspace_sptr > createOutputTables(const std::vector< std::string > &outputTblNames)
void init() override
Initialize the algorithm's properties.
std::vector< std::string > createIntermediateWorkspaces(const size_t dataIndex, const Tensor1D &kernel, const Tensor1D &iOverSigma, const HistogramData::HistogramX *xData)
double getXDataValue(const HistogramData::HistogramX *xData, const size_t xIndex) const
std::atomic< size_t > m_maxPeakCount
Tensor1D createKernel(const int binCount) const
void exec() override
Execute the algorithm.
Eigen::VectorXd centreBinsXData(const HistogramData::HistogramX *xData) const
void generateNormalPDF(const int peakExtentBinNumber)
bool m_findHighestDatapointInPeak
std::vector< std::string > m_intermediateWsNames
std::vector< int > m_specNums
int version() const override
Algorithm's version for identification.
size_t findPeakInRawData(const int xIndex, const TensorMap_const &yData, size_t peakExtentBinNumber)
void extractPeaks(const size_t dataIndex, const Tensor1D &iOverSigma, const HistogramData::HistogramX *xData, const TensorMap_const &yData, const size_t peakExtentBinNumber)
void performConvolution(const size_t dataIndex)
API::MatrixWorkspace_sptr m_inputDataWS
const std::string validateWorkspaceIndexInput() const
bool m_createIntermediateWorkspaces
std::pair< size_t, bool > getKernelBinCount(const HistogramData::HistogramX *xData) const
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
void storePeakResults(const size_t dataIndex, std::vector< FindPeaksConvolve::PeakResult > &peakCentres)
Tensor1D createSmoothKernel(const size_t kernelSize) const
const std::string category() const override
Algorithm's category for identification.
void storeClassProperties()
std::unordered_map< std::string, Kernel::IValidator_sptr > m_validators
std::pair< std::string, int > secondaryValidation() const
void outputIntermediateWorkspace(const std::string &wsName, const std::vector< double > &xData, const std::vector< double > &yData)
double m_iOverSigmaThreshold
std::string populateOutputWorkspaces(const std::vector< std::string > &outputTblNames, const std::unordered_map< std::string, API::ITableWorkspace_sptr > &outputTbls)
void initiateValidators()
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
The Logger class is in charge of the publishing messages from the framework through various channels.
void error(const std::string &msg)
Logs at error level.
void warning(const std::string &msg)
Logs at warning level.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::shared_ptr< Algorithm > Algorithm_sptr
Typedef for a shared pointer to an Algorithm.
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
Kernel::Logger g_log("DetermineSpinStateOrder")
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.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
std::string to_string(const wide_integer< Bits, Signed > &n)
double getAttribute(const std::string &attrString) const
@ Input
An input workspace.
@ Output
An output workspace.