Mantid
Loading...
Searching...
No Matches
EstimatePeakIntensities.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2026 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 +
10#include "MantidAPI/TableRow.h"
13#include "MantidHistogramData/Histogram.h"
14
15#include <algorithm>
16#include <cmath>
17#include <limits>
18#include <numeric>
19#include <stdexcept>
20
21namespace Mantid::Algorithms {
22
23using namespace API;
24using namespace Kernel;
25using namespace DataObjects;
26using HistogramData::Histogram;
27
28DECLARE_ALGORITHM(EstimatePeakIntensities)
29
30namespace {
31
33struct Estimate {
34 double intensity{0.0};
35 double sigma{0.0};
36 double background{0.0};
37 double centre{std::numeric_limits<double>::quiet_NaN()};
38};
39
41struct Window {
42 bool ok{false};
43 size_t istart{0};
44 size_t iend{0};
45};
46
49Window resolveWindow(const MatrixWorkspace &ws, const size_t wi, const double lo, const double hi) {
50 try {
51 const size_t istart = ws.yIndexOfX(lo, wi);
52 const size_t iend = ws.yIndexOfX(hi, wi);
53 if (iend > istart)
54 return {true, istart, iend};
55 } catch (const std::out_of_range &) {
56 // fall through to the invalid result
57 }
58 return {};
59}
60
65double centralMoment3(const double s1, const double s2, const double s3, const double count) {
66 const double mu = s1 / count;
67 const double mu2 = s2 / count;
68 const double mu3 = s3 / count;
69 return 2.0 * (mu * mu * mu) - (3.0 * mu * mu2) + mu3;
70}
71
99double estimateSkewBackground(const std::vector<double> &y) {
100 const size_t n = y.size();
101 if (n == 0)
102 return 0.0;
103 if (n == 1)
104 return y[0];
105
106 // indices of y sorted by descending value - peeled off largest first
107 std::vector<size_t> order(n);
108 std::iota(order.begin(), order.end(), size_t{0});
109 std::sort(order.begin(), order.end(), [&y](size_t a, size_t b) { return y[a] > y[b]; });
110
111 // running power sums of the remaining (candidate background) set, starting with every point
112 double s1 = 0.0, s2 = 0.0, s3 = 0.0;
113 for (const double v : y) {
114 s1 += v;
115 s2 += v * v;
116 s3 += v * v * v;
117 }
118
119 // best accepted background so far = every point (nothing peeled off yet)
120 double prevMoment = centralMoment3(s1, s2, s3, static_cast<double>(n));
121 double bgSum = s1;
122 double bgCount = static_cast<double>(n);
123
124 for (size_t dropped = 1; dropped < n; ++dropped) {
125 const double v = y[order[dropped - 1]]; // next largest point leaves the background set
126 s1 -= v;
127 s2 -= v * v;
128 s3 -= v * v * v;
129 const double count = static_cast<double>(n - dropped);
130 const double moment = centralMoment3(s1, s2, s3, count);
131 if (moment >= prevMoment || moment < 0.0)
132 break; // stop peeling - keep the last accepted background
133 prevMoment = moment;
134 bgSum = s1;
135 bgCount = count;
136 }
137 return bgSum / bgCount;
138}
139
143Estimate estimateWindow(const Histogram &histo, const Window &window, const double fallbackCentre) {
144 Estimate est;
145 est.centre = fallbackCentre;
146 if (!window.ok)
147 return est;
148
149 const auto &X = histo.x();
150 const auto &Y = histo.y();
151 const auto &E = histo.e();
152 const size_t istart = window.istart;
153 const size_t m = window.iend - istart;
154
155 std::vector<double> yseg(Y.begin() + istart, Y.begin() + window.iend);
156 if (!std::any_of(yseg.cbegin(), yseg.cend(), [](double v) { return v > 0.0; }))
157 return est; // no positive data -> zero estimate, keep the fallback centre
158
159 const double bg = estimateSkewBackground(yseg);
160
161 // trapezoidal integral of (y - bg)
162 double integral = 0.0;
163 for (size_t k = 0; k + 1 < m; ++k)
164 integral += 0.5 * ((yseg[k] - bg) + (yseg[k + 1] - bg)) * (X[istart + k + 1] - X[istart + k]);
165
166 // sigma = sqrt(sum((e * bin_width)^2))
167 double sig2 = 0.0;
168 for (size_t k = 0; k < m; ++k) {
169 const double bw = (k + 1 < m) ? (X[istart + k + 1] - X[istart + k])
170 : (m >= 2) ? (X[istart + m - 1] - X[istart + m - 2])
171 : 0.0;
172 const double t = E[istart + k] * bw;
173 sig2 += t * t;
174 }
175
176 const auto argmax = static_cast<size_t>(std::distance(yseg.cbegin(), std::max_element(yseg.cbegin(), yseg.cend())));
177
178 est.intensity = integral;
179 est.sigma = std::sqrt(sig2);
180 est.background = bg;
181 est.centre = X[istart + argmax];
182 return est;
183}
184
185} // namespace
186
188 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>("InputWorkspace", "", Direction::Input),
189 "Workspace whose spectra should be integrated.");
190 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>("PeakWindowWorkspace", "", Direction::Input),
191 "Per-spectrum integration windows, one spectrum per InputWorkspace spectrum, following the "
192 "FitPeaks FitPeakWindowWorkspace convention: each spectrum holds 2*nPeaks X values arranged as "
193 "[min0, max0, min1, max1, ...] (in InputWorkspace X units), so a peak's window can differ per "
194 "spectrum. A ragged workspace is accepted: every spectrum must hold a non-zero even number of "
195 "X values, but spectra may hold different numbers of [min, max] pairs, and a peak index "
196 "beyond a spectrum's pairs is reported with a NaN PeakCentre.");
197 declareProperty(std::make_unique<WorkspaceProperty<ITableWorkspace>>("OutputWorkspace", "", Direction::Output),
198 "Table with one row per (peak, spectrum): PeakIndex, WorkspaceIndex, Intensity, Sigma, "
199 "Background, PeakCentre. The number of peaks is the largest number of windows held by any "
200 "spectrum of PeakWindowWorkspace. PeakIndex is only the position of the window in that "
201 "spectrum's list, so rows sharing a PeakIndex refer to the same physical peak only if "
202 "PeakWindowWorkspace lists the windows in the same peak order for every spectrum.");
203}
204
205std::map<std::string, std::string> EstimatePeakIntensities::validateInputs() {
206 std::map<std::string, std::string> issues;
207 MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
208 MatrixWorkspace_const_sptr windowWS = getProperty("PeakWindowWorkspace");
209 if (!inputWS || !windowWS)
210 return issues;
211 if (windowWS->getNumberHistograms() != inputWS->getNumberHistograms()) {
212 issues["PeakWindowWorkspace"] = "must have the same number of spectra as InputWorkspace.";
213 return issues;
214 }
215 // spectra may hold different numbers of windows, so every one has to be checked
216 for (size_t wi = 0; wi < windowWS->getNumberHistograms(); ++wi) {
217 const size_t nx = windowWS->x(wi).size();
218 if (nx == 0 || nx % 2 != 0) {
219 issues["PeakWindowWorkspace"] = "each spectrum must hold a non-zero even number of X values ([min, max] "
220 "pairs, one per peak); spectrum " +
221 std::to_string(wi) + " holds " + std::to_string(nx) + ".";
222 break;
223 }
224 }
225 return issues;
226}
227
229 MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
230 MatrixWorkspace_const_sptr windowWS = getProperty("PeakWindowWorkspace");
231
232 const auto nHist = static_cast<int64_t>(inputWS->getNumberHistograms());
233
234 // the window workspace may be ragged, so the table is sized by the spectrum holding the most windows and
235 // spectra defining fewer are padded with the default (NaN centre) estimate
236 size_t nPeaks = 0;
237 size_t minPeaks = std::numeric_limits<size_t>::max();
238 for (int64_t i = 0; i < nHist; ++i) {
239 const size_t n = windowWS->x(static_cast<size_t>(i)).size() / 2;
240 nPeaks = std::max(nPeaks, n);
241 minPeaks = std::min(minPeaks, n);
242 }
243 if (nHist > 0 && minPeaks != nPeaks)
244 g_log.warning() << "PeakWindowWorkspace is ragged: spectra hold between " << minPeaks << " and " << nPeaks
245 << " windows. The table has " << nPeaks
246 << " peaks and rows for undefined windows are reported with a NaN PeakCentre. PeakIndex is "
247 "the position of the window in each spectrum's list, so it only identifies the same "
248 "physical peak across spectra if the windows are listed in the same peak order.\n";
249
250 // per (peak, spectrum) results, laid out peak-major (row = peak * nHist + wsIndex) so each peak's
251 // rows are contiguous; filled in the parallel loop, then copied into the table serially
252 const size_t nRows = nPeaks * static_cast<size_t>(nHist);
253 std::vector<Estimate> results(nRows);
254
255 Progress prog(this, 0.0, 1.0, nHist);
256
257 PARALLEL_FOR_IF(threadSafe(*inputWS, *windowWS))
258 for (int64_t i = 0; i < nHist; ++i) {
260 const auto wi = static_cast<size_t>(i);
261 const auto histo = inputWS->histogram(wi);
262 const auto &winX = windowWS->x(wi);
263 const size_t nPeaksHere = winX.size() / 2; // <= nPeaks; the rest keep the default estimate
264 for (size_t j = 0; j < nPeaksHere; ++j) {
265 const double lo = winX[2 * j];
266 const double hi = winX[2 * j + 1];
267 const Window window = resolveWindow(*inputWS, wi, lo, hi);
268 results[j * static_cast<size_t>(nHist) + wi] = estimateWindow(histo, window, 0.5 * (lo + hi));
269 }
270 prog.report();
272 }
274
275 ITableWorkspace_sptr out = std::make_shared<TableWorkspace>();
276 out->addColumn("int", "PeakIndex");
277 out->addColumn("int", "WorkspaceIndex");
278 out->addColumn("double", "Intensity");
279 out->addColumn("double", "Sigma");
280 out->addColumn("double", "Background");
281 out->addColumn("double", "PeakCentre");
282 for (size_t j = 0; j < nPeaks; ++j) {
283 for (int64_t i = 0; i < nHist; ++i) {
284 const Estimate &e = results[j * static_cast<size_t>(nHist) + static_cast<size_t>(i)];
285 TableRow row = out->appendRow();
286 row << static_cast<int>(j) << static_cast<int>(i) << e.intensity << e.sigma << e.background << e.centre;
287 }
288 }
289
290 setProperty("OutputWorkspace", out);
291}
292
293} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
double intensity
size_t istart
double centre
double background
int count
counter
Definition Matrix.cpp:37
#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.
Kernel::Logger & g_log
Definition Algorithm.h:423
Helper class for reporting progress from algorithms.
Definition Progress.h:25
TableRow represents a row in a TableWorkspace.
Definition TableRow.h:39
A property class for workspaces.
void exec() override
Virtual method - must be overridden by concrete algorithm.
void init() override
Virtual method - must be overridden by concrete algorithm.
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
double integral(double func(const double, const double, const double), const double a, const double b, const double g, const double w0)
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.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54