Mantid
Loading...
Searching...
No Matches
Smoothing.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2025 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 +
8#include "MantidKernel/DllConfig.h"
10
11#include <algorithm>
12#include <cmath>
13#include <stdexcept>
14#include <vector>
15
17
18namespace detail {
19template <typename T> struct Averager {
21 Averager() : m_total(0), m_npts(0) {}
26 virtual T term(T const &x) const = 0;
30 virtual T getAverage() const = 0;
34 virtual void accumulate(T const &x) {
35 if (!std::isnan(x)) {
36 m_total += term(x);
37 m_npts++;
38 }
39 }
43 virtual void separate(T const &x) {
44 if (!std::isnan(x)) {
45 m_total -= term(x);
46 m_npts--;
47 }
48 }
49
50protected:
52 unsigned int m_npts;
53};
54
56template <typename T> struct ArithmeticAverager : public Averager<T> {
57 T term(T const &x) const override { return x; }
58 T getAverage() const override { return this->m_total / this->m_npts; }
59};
60
62template <typename T> struct ErrorPropagationAverager : public Averager<T> {
63 T term(T const &x) const override { return x * x; }
64 T getAverage() const override { return std::sqrt(std::abs(this->m_total)) / this->m_npts; }
65};
66
68template <typename T> struct SumSquareAverager : public Averager<T> {
69 T term(T const &x) const override { return x * x; }
70 T getAverage() const override { return std::sqrt(std::abs(this->m_total) / this->m_npts); }
71};
72
74template <typename T> struct GeometricAverager : public Averager<T> {
75 GeometricAverager() { this->m_total = T(1); }
76 void accumulate(T const &x) override {
77 if (x != T(0) && !std::isnan(x)) {
78 this->m_total *= term(x);
79 this->m_npts++;
80 }
81 }
82 void separate(T const &x) override {
83 if (x != T(0) && !std::isnan(x)) {
84 this->m_total /= term(x);
85 this->m_npts--;
86 }
87 }
88 T term(T const &x) const override { return x; }
89 T getAverage() const override { return std::pow(std::abs(this->total), 1. / this->npts); }
90};
91
92template <typename T>
93std::vector<T> boxcarSmoothWithFunction(std::vector<T> const &input, unsigned int const numPoints,
94 Averager<T> &averager) {
95 if (numPoints < 3) {
96 throw std::invalid_argument("Boxcar Smoothing requires at least 3 points in the moving average");
97 }
98 if (numPoints % 2 == 0) {
99 throw std::invalid_argument("Boxcar Smoothing requires an odd number of points in the moving average");
100 }
101 if (input.size() < numPoints) {
102 throw std::invalid_argument("Boxcar Smoothing requires the vector size to be greater than the smoothing window");
103 }
104 // copy the input
105 std::size_t const vecSize = input.size();
106 std::vector<T> output(vecSize);
107 unsigned int const halfWidth = (numPoints - 1U) / 2U;
108
109 // First push the values ahead of the current point onto total
110 for (unsigned int k = 0; k < halfWidth; k++) {
111 averager.accumulate(input[k]);
112 }
113 // smoothed values at the beginning, where fewer than numPoints
114 for (unsigned int k = 0; k <= halfWidth; k++) {
115 unsigned int const kp = k + halfWidth;
116 // add the leading edge
117 averager.accumulate(input[kp]);
118 output[k] = averager.getAverage();
119 }
120 // main part, each average has numPoints
121 for (std::size_t k = halfWidth + 1; k < vecSize - halfWidth; k++) {
122 std::size_t const kp = k + halfWidth;
123 std::size_t const km = k - halfWidth - 1;
124 // remove the previous trailing edge
125 averager.separate(input[km]);
126 // add the new leading edge
127 averager.accumulate(input[kp]);
128 output[k] = averager.getAverage();
129 }
130 // smoothed values at very end, where fewer than numPoints
131 for (std::size_t k = vecSize - halfWidth; k < vecSize; k++) {
132 std::size_t const km = k - halfWidth;
133 // remove previous trailing edge
134 averager.separate(input[km - 1]);
135 output[k] = averager.getAverage();
136 }
137 return output;
138}
139} // namespace detail
140
141template <typename T> std::vector<T> boxcarSmooth(std::vector<T> const &input, unsigned int const numPoints) {
143 return detail::boxcarSmoothWithFunction(input, numPoints, averager);
144}
145
146template <typename T> std::vector<T> boxcarErrorSmooth(std::vector<T> const &input, unsigned int const numPoints) {
148 return detail::boxcarSmoothWithFunction(input, numPoints, averager);
149}
150
151template <typename T> std::vector<T> boxcarRMSESmooth(std::vector<T> const &input, unsigned int const numPoints) {
153 return detail::boxcarSmoothWithFunction(input, numPoints, averager);
154}
155
156template MANTID_KERNEL_DLL std::vector<double> boxcarSmooth(std::vector<double> const &, unsigned int const);
157template MANTID_KERNEL_DLL std::vector<double> boxcarRMSESmooth(std::vector<double> const &, unsigned int const);
158template MANTID_KERNEL_DLL std::vector<double> boxcarErrorSmooth(std::vector<double> const &, unsigned int const);
159
160// FFT SMOOTHING METHODS
161
162namespace fft {
163
164// filters
165template <typename Y> struct FFTFilter {
166 virtual Y operator()(std::size_t const index) const = 0;
167 virtual ~FFTFilter() = default;
168};
169
170template <typename Y> struct ZeroFilter : public FFTFilter<Y> {
171 // remove the higher frequencies by setting to zero
172 // REF: see example code at
173 // - https://www.gnu.org/software/gsl/doc/html/fft.html#overview-of-real-data-f
174 ZeroFilter(std::size_t const n) : m_cutoff(n) {}
175 Y operator()(std::size_t const index) const override { return (index >= m_cutoff ? 0 : 1); }
176
177private:
178 std::size_t m_cutoff;
179};
180
181template <typename Y> struct ButterworthFilter : public FFTFilter<Y> {
182 // remove the higher frequencies by tapering with a Butterworth filter
183 // SOME REFS:
184 // - https://scikit-image.org/docs/0.25.x/api/skimage.filters.html#skimage.filters.butterworth
185 // - https://isbweb.org/software/sigproc/bogert/filter.pdf
186 // - https://users.cs.cf.ac.uk/dave/Vision_lecture/node22.html
187 ButterworthFilter(unsigned int const n, unsigned int const o)
188 : m_two_order(2U * o), m_invcutoff(1.0 / static_cast<Y>(n)) {}
189 Y operator()(std::size_t const index) const override {
190 return 1.0 / (1.0 + std::pow(m_invcutoff * static_cast<Y>(index), m_two_order));
191 }
192
193private:
194 unsigned int m_two_order;
196};
197
198template <typename Y> std::vector<Y> fftSmoothWithFilter(std::vector<Y> const &input, FFTFilter<Y> const &filter) {
199 std::size_t const N = input.size();
200 std::vector<Y> output(input.cbegin(), input.cend());
201
202 // obtain the FFT
205 gsl_fft_real_transform(output.data(), 1, N, real_wt.get(), real_ws.get());
206 real_wt.reset(); // wavetable no longer needed
207
208 // NOTE: the halfcomplex storage requires special treatment of even/odd
209 // while we could use GSL's unpack, it is easier to alter the values in place
210 bool const even = (N % 2 == 0);
211 std::size_t const complex_size = (even ? N / 2 : (N - 1) / 2);
212 output[0] *= filter(0); // x[0] = z[0].real; z[0].imag = 0 and is not stored anywhere
213 for (std::size_t fn = 1; fn < complex_size + (even ? 0UL : 1UL); fn++) {
214 output[2 * fn - 1] *= filter(fn); // real parts
215 output[2 * fn] *= filter(fn); // imaginary parts
216 }
217 // for even data, last point is an unmatched real value
218 if (even) {
219 output[N - 1] *= filter(complex_size);
220 }
221
222 // transform back
224 gsl_fft_halfcomplex_inverse(output.data(), 1, N, hc_wt.get(), real_ws.get());
225 hc_wt.reset(); // wavetable no longer needed
226 real_ws.reset(); // workspace no longer needed
227
228 // return the smoothed result
229 return output;
230}
231
232} // namespace fft
233
234template <typename Y> std::vector<Y> fftSmooth(std::vector<Y> const &input, unsigned const cutoff) {
235 if (cutoff == 0) {
236 throw std::invalid_argument("The cutoff frequency must be greater than zero (" + std::to_string(cutoff) + " <= 0)");
237 } else if (cutoff > input.size()) {
238 throw std::invalid_argument("The cutoff frequency must be less than the array size( " + std::to_string(cutoff) +
239 " > " + std::to_string(input.size()) + ")");
240 }
241
242 fft::ZeroFilter<Y> filter(cutoff);
243 return fftSmoothWithFilter(input, filter);
244}
245
246template <typename Y>
247std::vector<Y> fftButterworthSmooth(std::vector<Y> const &input, unsigned const cutoff, unsigned const order) {
248 if (cutoff == 0) {
249 throw std::invalid_argument("The Butterworth cutoff frequency must be greater than zero (" +
250 std::to_string(cutoff) + " <= 0)");
251 }
252 if (cutoff > input.size()) {
253 throw std::invalid_argument("The Butterworth cutoff frequency must be less than the array size (" +
254 std::to_string(cutoff) + " > " + std::to_string(input.size()) + ")");
255 }
256 if (order < 1) {
257 throw std::invalid_argument("The Butterworth order must be nonzero (" + std::to_string(order) + " <= 0)");
258 }
259
260 fft::ButterworthFilter<Y> filter(cutoff, order);
261 return fft::fftSmoothWithFilter(input, filter);
262}
263
264template MANTID_KERNEL_DLL std::vector<double> fftSmooth(std::vector<double> const &, unsigned const);
265template MANTID_KERNEL_DLL std::vector<double> fftButterworthSmooth(std::vector<double> const &, unsigned const,
266 unsigned const);
267
268} // namespace Mantid::Kernel::Smoothing
std::map< DeltaEMode::Type, std::string > index
std::vector< T > boxcarSmoothWithFunction(std::vector< T > const &input, unsigned int const numPoints, Averager< T > &averager)
Definition Smoothing.cpp:93
std::vector< Y > fftSmoothWithFilter(std::vector< Y > const &input, FFTFilter< Y > const &filter)
std::vector< T > boxcarErrorSmooth(std::vector< T > const &input, unsigned int const numPoints)
Performs boxcar (moving average) smoothing on the input data, using error propagation formula.
std::vector< T > boxcarSmooth(std::vector< T > const &input, unsigned int const numPoints)
Performs boxcar (moving average) smoothing on the input data.
std::vector< Y > fftButterworthSmooth(std::vector< Y > const &input, unsigned const cutoff, unsigned const order)
Performs FFT smoothing on the input data, using a Butterworth filter NOTE: the input data MUST be def...
std::vector< T > boxcarRMSESmooth(std::vector< T > const &input, unsigned int const numPoints)
Performs boxcar (moving average) smoothing on the input data, using a RMSE average,...
std::vector< Y > fftSmooth(std::vector< Y > const &input, unsigned const cutoff)
Performs FFT smoothing on the input data, with high frequencies set to zero NOTE: the input data MUST...
real_wt_uptr make_gsl_real_wavetable(std::size_t dn)
Definition GSL_Helpers.h:33
std::unique_ptr< gsl_fft_halfcomplex_wavetable, GSLFree > hc_wt_uptr
Definition GSL_Helpers.h:31
std::unique_ptr< gsl_fft_real_workspace, GSLFree > real_ws_uptr
Definition GSL_Helpers.h:30
hc_wt_uptr make_gsl_hc_wavetable(std::size_t dn)
Definition GSL_Helpers.h:35
real_ws_uptr make_gsl_real_workspace(std::size_t dn)
Definition GSL_Helpers.h:34
std::unique_ptr< gsl_fft_real_wavetable, GSLFree > real_wt_uptr
Definition GSL_Helpers.h:29
std::string to_string(const wide_integer< Bits, Signed > &n)
Represents taking the arithmetic mean.
Definition Smoothing.cpp:56
T getAverage() const override
Retrieve the average of all included values.
Definition Smoothing.cpp:58
T term(T const &x) const override
A function returning a "term" in the average.
Definition Smoothing.cpp:57
virtual T getAverage() const =0
Retrieve the average of all included values.
virtual void separate(T const &x)
Remove values from the average.
Definition Smoothing.cpp:43
virtual T term(T const &x) const =0
A function returning a "term" in the average.
Averager()
A small ABC to represent taking an average over a few values.
Definition Smoothing.cpp:21
virtual void accumulate(T const &x)
Include values in the average.
Definition Smoothing.cpp:34
Represents propagating errors for values which had been arithmetically averaged.
Definition Smoothing.cpp:62
T getAverage() const override
Retrieve the average of all included values.
Definition Smoothing.cpp:64
T term(T const &x) const override
A function returning a "term" in the average.
Definition Smoothing.cpp:63
Represents taking the geometric mean.
Definition Smoothing.cpp:74
T getAverage() const override
Retrieve the average of all included values.
Definition Smoothing.cpp:89
T term(T const &x) const override
A function returning a "term" in the average.
Definition Smoothing.cpp:88
void separate(T const &x) override
Remove values from the average.
Definition Smoothing.cpp:82
void accumulate(T const &x) override
Include values in the average.
Definition Smoothing.cpp:76
Represents taking the root-mean-square averaege.
Definition Smoothing.cpp:68
T getAverage() const override
Retrieve the average of all included values.
Definition Smoothing.cpp:70
T term(T const &x) const override
A function returning a "term" in the average.
Definition Smoothing.cpp:69
Y operator()(std::size_t const index) const override
ButterworthFilter(unsigned int const n, unsigned int const o)
virtual Y operator()(std::size_t const index) const =0
Y operator()(std::size_t const index) const override