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