v2.0.0
Loading...
Searching...
No Matches
resample.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "resample.h"
18
19//=============================================================================================================
20// EIGEN INCLUDES
21//=============================================================================================================
22
23#include <Eigen/Core>
24
25//=============================================================================================================
26// QT INCLUDES
27//=============================================================================================================
28
29#include <QDebug>
30
31//=============================================================================================================
32// C++ INCLUDES
33//=============================================================================================================
34
35#include <cmath>
36#include <algorithm>
37
38//=============================================================================================================
39// USED NAMESPACES
40//=============================================================================================================
41
42using namespace UTILSLIB;
43using namespace Eigen;
44
45//=============================================================================================================
46// PRIVATE
47//=============================================================================================================
48
49int Resample::gcd(int a, int b)
50{
51 while (b) {
52 int t = b;
53 b = a % b;
54 a = t;
55 }
56 return a;
57}
58
59//=============================================================================================================
60
61RowVectorXd Resample::buildKernel(int p, int q, int iNZeros)
62{
63 int M = std::max(p, q);
64 int halfLen = iNZeros * M;
65 int L = 2 * halfLen + 1;
66
67 // Cutoff as a fraction of the sampling frequency at the upsampled rate p*oldSFreq.
68 // We want to cut at the lower Nyquist: min(oldSFreq, newSFreq)/2.
69 // In normalised terms (fraction of upsampled fs):
70 // cutoff_norm = min(p,q) / (2.0 * max(p,q)) ... but the conventional sinc parameterisation
71 // uses cutoff as a fraction of fs (not Nyquist), i.e. in [0,0.5].
72 // So fc_fs = min(p,q) / (2.0 * max(p,q)).
73 const double fc = static_cast<double>(std::min(p, q)) / (2.0 * static_cast<double>(M));
74
75 RowVectorXd h(L);
76 for (int k = 0; k < L; ++k) {
77 double n = k - halfLen;
78 double win = 0.54 - 0.46 * std::cos(2.0 * M_PI * k / (L - 1)); // Hamming
79
80 if (std::abs(n) < 1e-10) {
81 h(k) = 2.0 * fc * win;
82 } else {
83 // sinc(2*fc*n) * 2*fc = sin(2π*fc*n) / (π*n)
84 h(k) = std::sin(2.0 * M_PI * fc * n) / (M_PI * n) * win;
85 }
86 }
87
88 // Scale by p to restore unity gain after the conceptual upsampling-by-p step.
89 h *= static_cast<double>(p);
90 return h;
91}
92
93//=============================================================================================================
94
95RowVectorXd Resample::polyphaseConv(const RowVectorXd& vecX,
96 const RowVectorXd& vecH,
97 int p,
98 int q,
99 int halfLen)
100{
101 const long long nIn = static_cast<long long>(vecX.size());
102 const long long L = static_cast<long long>(vecH.size()); // = 2*halfLen + 1
103
104 // Output length: ceil(nIn * p / q)
105 const long long nOut = (nIn * p + q - 1) / q;
106
107 RowVectorXd y(nOut);
108
109 for (long long m = 0; m < nOut; ++m) {
110 // The filter delay (halfLen upsampled samples) is absorbed into the center index so that
111 // output sample 0 aligns with input sample 0.
112 long long center = m * static_cast<long long>(q) + halfLen;
113
114 // Iterate over all input indices j whose upsampled position j*p lies within
115 // the filter window [center - L + 1, center].
116 // That is: j*p in [center - L + 1, center]
117 // => j in [ceil((center-L+1)/p), floor(center/p)] ∩ [0, nIn-1]
118 long long j_min = (center - L + 1 + p - 1) / p; // ceil division
119 if (j_min < 0)
120 j_min = 0;
121 long long j_max = center / p;
122 if (j_max >= nIn)
123 j_max = nIn - 1;
124
125 double val = 0.0;
126 for (long long j = j_min; j <= j_max; ++j) {
127 long long tap = center - j * static_cast<long long>(p);
128 if (tap >= 0 && tap < L) {
129 val += vecH(static_cast<int>(tap)) * vecX(static_cast<int>(j));
130 }
131 }
132 y(static_cast<int>(m)) = val;
133 }
134
135 return y;
136}
137
138//=============================================================================================================
139// PUBLIC
140//=============================================================================================================
141
142RowVectorXd Resample::resample(const RowVectorXd& vecData,
143 double dNewSFreq,
144 double dOldSFreq,
145 int iNZeros)
146{
147 if (vecData.size() == 0) {
148 qWarning() << "Resample::resample: empty input.";
149 return vecData;
150 }
151 if (dNewSFreq <= 0.0 || dOldSFreq <= 0.0) {
152 qWarning() << "Resample::resample: sampling frequencies must be positive.";
153 return vecData;
154 }
155
156 // Represent ratio as integers to avoid floating-point drift.
157 // Multiply both rates by 1000 and reduce by GCD (handles e.g. 600.0/1000.0 → 3/5).
158 const int scale = 1000;
159 int p_raw = static_cast<int>(std::round(dNewSFreq * scale));
160 int q_raw = static_cast<int>(std::round(dOldSFreq * scale));
161 int g = gcd(p_raw, q_raw);
162 int p = p_raw / g;
163 int q = q_raw / g;
164
165 if (p == q) {
166 return vecData; // Same rate after reduction
167 }
168
169 const int halfLen = iNZeros * std::max(p, q);
170 RowVectorXd h = buildKernel(p, q, iNZeros);
171
172 return polyphaseConv(vecData, h, p, q, halfLen);
173}
174
175//=============================================================================================================
176
177MatrixXd Resample::resampleMatrix(const MatrixXd& matData,
178 double dNewSFreq,
179 double dOldSFreq,
180 const RowVectorXi& vecPicks,
181 int iNZeros)
182{
183 if (matData.size() == 0)
184 return matData;
185
186 // Pre-build kernel once for all channels
187 const int scale = 1000;
188 int p_raw = static_cast<int>(std::round(dNewSFreq * scale));
189 int q_raw = static_cast<int>(std::round(dOldSFreq * scale));
190 int g = gcd(p_raw, q_raw);
191 int p = p_raw / g;
192 int q = q_raw / g;
193
194 if (p == q)
195 return matData;
196
197 const int halfLen = iNZeros * std::max(p, q);
198 RowVectorXd h = buildKernel(p, q, iNZeros);
199
200 const int nIn = static_cast<int>(matData.cols());
201 const int nOut = static_cast<int>((static_cast<long long>(nIn) * p + q - 1) / q);
202 const int nCh = static_cast<int>(matData.rows());
203
204 MatrixXd result(nCh, nOut);
205
206 if (vecPicks.size() == 0) {
207 for (int i = 0; i < nCh; ++i) {
208 result.row(i) = polyphaseConv(matData.row(i), h, p, q, halfLen);
209 }
210 } else {
211 // Initialise to zero then fill picked rows (non-picked rows remain zero —
212 // callers using picks should be aware rows are not copied; use full resampling
213 // if a copy of non-MEG channels is needed).
214 result.setZero();
215 for (int k = 0; k < vecPicks.size(); ++k) {
216 int i = vecPicks(k);
217 if (i >= 0 && i < nCh) {
218 result.row(i) = polyphaseConv(matData.row(i), h, p, q, halfLen);
219 }
220 }
221 }
222
223 return result;
224}
#define M_PI
Declaration of Resample — polyphase anti-aliased rational resampling for MEG/EEG data.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
static Eigen::RowVectorXd resample(const Eigen::RowVectorXd &vecData, double dNewSFreq, double dOldSFreq, int iNZeros=10)
Definition resample.cpp:142
static Eigen::MatrixXd resampleMatrix(const Eigen::MatrixXd &matData, double dNewSFreq, double dOldSFreq, const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), int iNZeros=10)
Definition resample.cpp:177