v2.0.0
Loading...
Searching...
No Matches
csd.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "csd.h"
18#include "dpss.h"
19
20//=============================================================================================================
21// EIGEN INCLUDES
22//=============================================================================================================
23
24#include <Eigen/Core>
25#include <Eigen/Dense>
26//#ifndef EIGEN_FFTW_DEFAULT
27//#define EIGEN_FFTW_DEFAULT
28//#endif
29#include <unsupported/Eigen/FFT>
30
31//=============================================================================================================
32// STD INCLUDES
33//=============================================================================================================
34
35#include <cmath>
36#include <complex>
37#include <vector>
38
39//=============================================================================================================
40// USED NAMESPACES
41//=============================================================================================================
42
43using namespace UTILSLIB;
44using namespace Eigen;
45
46namespace
47{
48constexpr double CSD_PI = 3.14159265358979323846;
49}
50
51//=============================================================================================================
52// DEFINE MEMBER METHODS
53//=============================================================================================================
54
55CsdResult Csd::computeMultitaper(const MatrixXd& matData,
56 double sfreq,
57 double fmin,
58 double fmax,
59 double halfBandwidth,
60 int nTapers)
61{
62 const int nChannels = static_cast<int>(matData.rows());
63 const int nTimes = static_cast<int>(matData.cols());
64 const int nFreqsFull = nTimes / 2 + 1;
65
66 if (fmax < 0.0)
67 fmax = sfreq / 2.0;
68
69 // 1. Compute DPSS tapers
70 DpssResult dpss = Dpss::compute(nTimes, halfBandwidth, nTapers);
71 const int nTap = static_cast<int>(dpss.matTapers.rows());
72
73 // Compute eigenvalue weight sum
74 double weightSum = 0.0;
75 for (int t = 0; t < nTap; ++t)
76 weightSum += dpss.vecEigenvalues[t];
77
78 // Build full frequency axis
79 const double freqRes = sfreq / static_cast<double>(nTimes);
80
81 // Determine frequency bin range
82 const int iBinMin = std::max(0, static_cast<int>(std::ceil(fmin / freqRes)));
83 const int iBinMax = std::min(nFreqsFull - 1, static_cast<int>(std::floor(fmax / freqRes)));
84 const int nFreqsSel = iBinMax - iBinMin + 1;
85
86 // Initialize per-frequency CSD accumulators
87 std::vector<MatrixXcd> csdAccum(static_cast<std::size_t>(nFreqsSel),
88 MatrixXcd::Zero(nChannels, nChannels));
89
90 Eigen::FFT<double> fft;
91
92 // 2. For each taper, compute FFT for all channels, then accumulate CSD
93 for (int t = 0; t < nTap; ++t) {
94 const double w = dpss.vecEigenvalues[t];
95
96 // Compute FFT spectra for all channels with this taper
97 MatrixXcd spectra(nChannels, nFreqsFull);
98
99 for (int ch = 0; ch < nChannels; ++ch) {
100 VectorXd tapered = matData.row(ch).transpose().array()
101 * dpss.matTapers.row(t).transpose().array();
102
103 VectorXcd spec;
104 fft.fwd(spec, tapered);
105
106 spectra.row(ch) = spec.head(nFreqsFull).transpose();
107 }
108
109 // Accumulate weighted CSD: X(:,f) * X(:,f)^H for selected frequency bins
110 for (int fi = 0; fi < nFreqsSel; ++fi) {
111 const int fBin = iBinMin + fi;
112 VectorXcd col = spectra.col(fBin);
113 csdAccum[static_cast<std::size_t>(fi)] += w * (col * col.adjoint());
114 }
115 }
116
117 // 3. Assemble result
118 CsdResult result;
119 result.vecFreqs.resize(nFreqsSel);
120 result.csdByFreq.resize(nFreqsSel);
121
122 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
123
124 for (int fi = 0; fi < nFreqsSel; ++fi) {
125 csdAccum[static_cast<std::size_t>(fi)] /= weightSum;
126 result.csdByFreq[fi] = csdAccum[static_cast<std::size_t>(fi)];
127 result.vecFreqs[fi] = (iBinMin + fi) * freqRes;
128 meanCsd += result.csdByFreq[fi];
129 }
130
131 if (nFreqsSel > 0)
132 meanCsd /= static_cast<double>(nFreqsSel);
133
134 result.matCsd = meanCsd;
135 return result;
136}
137
138//=============================================================================================================
139
140CsdResult Csd::computeFourier(const MatrixXd& matData,
141 double sfreq,
142 double fmin,
143 double fmax,
144 int nFft,
145 double overlap)
146{
147 const int nChannels = static_cast<int>(matData.rows());
148 const int nTimes = static_cast<int>(matData.cols());
149
150 if (nFft > nTimes)
151 nFft = nTimes;
152
153 const int nFreqsFull = nFft / 2 + 1;
154
155 if (fmax < 0.0)
156 fmax = sfreq / 2.0;
157
158 const double freqRes = sfreq / static_cast<double>(nFft);
159 const int iBinMin = std::max(0, static_cast<int>(std::ceil(fmin / freqRes)));
160 const int iBinMax = std::min(nFreqsFull - 1, static_cast<int>(std::floor(fmax / freqRes)));
161 const int nFreqsSel = iBinMax - iBinMin + 1;
162
163 // Build Hann window
164 VectorXd hannWin(nFft);
165 for (int n = 0; n < nFft; ++n) {
166 const double t = static_cast<double>(n) / static_cast<double>(nFft - 1);
167 hannWin[n] = 0.5 * (1.0 - std::cos(2.0 * CSD_PI * t));
168 }
169 const double winPow = hannWin.squaredNorm();
170
171 const int iStep = std::max(1, static_cast<int>(std::round(
172 static_cast<double>(nFft) * (1.0 - overlap))));
173
174 // Initialize per-frequency CSD accumulators
175 std::vector<MatrixXcd> csdAccum(static_cast<std::size_t>(nFreqsSel),
176 MatrixXcd::Zero(nChannels, nChannels));
177
178 Eigen::FFT<double> fft;
179 int nSeg = 0;
180
181 // Process each segment
182 for (int start = 0; start + nFft <= nTimes; start += iStep) {
183 // FFT all channels for this segment
184 MatrixXcd spectra(nChannels, nFreqsFull);
185
186 for (int ch = 0; ch < nChannels; ++ch) {
187 VectorXd seg = matData.row(ch).segment(start, nFft).transpose().array()
188 * hannWin.array();
189
190 VectorXcd spec;
191 fft.fwd(spec, seg);
192
193 spectra.row(ch) = spec.head(nFreqsFull).transpose();
194 }
195
196 // Accumulate CSD for selected frequency bins
197 for (int fi = 0; fi < nFreqsSel; ++fi) {
198 const int fBin = iBinMin + fi;
199 VectorXcd col = spectra.col(fBin);
200 csdAccum[static_cast<std::size_t>(fi)] += col * col.adjoint();
201 }
202
203 ++nSeg;
204 }
205
206 // 3. Assemble result
207 CsdResult result;
208 result.vecFreqs.resize(nFreqsSel);
209 result.csdByFreq.resize(nFreqsSel);
210
211 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
212
213 const double dNorm = (nSeg > 0) ? 1.0 / (static_cast<double>(nSeg) * winPow) : 0.0;
214
215 for (int fi = 0; fi < nFreqsSel; ++fi) {
216 csdAccum[static_cast<std::size_t>(fi)] *= dNorm;
217 result.csdByFreq[fi] = csdAccum[static_cast<std::size_t>(fi)];
218 result.vecFreqs[fi] = (iBinMin + fi) * freqRes;
219 meanCsd += result.csdByFreq[fi];
220 }
221
222 if (nFreqsSel > 0)
223 meanCsd /= static_cast<double>(nFreqsSel);
224
225 result.matCsd = meanCsd;
226 return result;
227}
228
229//=============================================================================================================
230
231CsdResult Csd::computeMorlet(const MatrixXd& matData,
232 double sfreq,
233 const RowVectorXd& frequencies,
234 int nCycles)
235{
236 const int nChannels = static_cast<int>(matData.rows());
237 const int nTimes = static_cast<int>(matData.cols());
238 const int nFreqs = static_cast<int>(frequencies.size());
239
240 CsdResult result;
241 result.vecFreqs = frequencies;
242 result.csdByFreq.resize(nFreqs);
243
244 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
245
246 for (int fi = 0; fi < nFreqs; ++fi) {
247 const double f = frequencies[fi];
248 const double sigma = static_cast<double>(nCycles) / (2.0 * CSD_PI * f);
249
250 // Determine wavelet length: ±3σ in samples
251 const int halfLen = static_cast<int>(std::ceil(3.0 * sigma * sfreq));
252 const int wavLen = 2 * halfLen + 1;
253
254 // Build Morlet wavelet
255 VectorXcd wavelet(wavLen);
256 double normFactor = 0.0;
257 for (int n = 0; n < wavLen; ++n) {
258 const double t = (static_cast<double>(n) - static_cast<double>(halfLen)) / sfreq;
259 const double gauss = std::exp(-t * t / (2.0 * sigma * sigma));
260 wavelet[n] = std::complex<double>(gauss * std::cos(2.0 * CSD_PI * f * t),
261 gauss * std::sin(2.0 * CSD_PI * f * t));
262 normFactor += gauss * gauss;
263 }
264 // Normalize wavelet for unit energy
265 normFactor = std::sqrt(normFactor);
266 if (normFactor > 0.0)
267 wavelet /= normFactor;
268
269 // Convolve each channel with the wavelet → complex analytic signal
270 MatrixXcd analytic(nChannels, nTimes);
271
272 for (int ch = 0; ch < nChannels; ++ch) {
273 for (int t = 0; t < nTimes; ++t) {
274 std::complex<double> sum(0.0, 0.0);
275 for (int k = 0; k < wavLen; ++k) {
276 const int idx = t - halfLen + k;
277 if (idx >= 0 && idx < nTimes) {
278 sum += matData(ch, idx) * std::conj(wavelet[k]);
279 }
280 }
281 analytic(ch, t) = sum;
282 }
283 }
284
285 // CSD at this frequency = mean over time of C(:,t) * C(:,t)^H
286 MatrixXcd csdAtFreq = MatrixXcd::Zero(nChannels, nChannels);
287 for (int t = 0; t < nTimes; ++t) {
288 VectorXcd col = analytic.col(t);
289 csdAtFreq += col * col.adjoint();
290 }
291 csdAtFreq /= static_cast<double>(nTimes);
292
293 result.csdByFreq[fi] = csdAtFreq;
294 meanCsd += csdAtFreq;
295 }
296
297 if (nFreqs > 0)
298 meanCsd /= static_cast<double>(nFreqs);
299
300 result.matCsd = meanCsd;
301 return result;
302}
Discrete Prolate Spheroidal Sequences (Slepian tapers) for multitaper spectral estimation.
Cross-spectral density (CSD) estimation via averaged windowed FFTs.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Result of a Cross-Spectral Density computation.
Definition csd.h:61
Eigen::MatrixXcd matCsd
n_channels × n_channels mean CSD across selected frequencies
Definition csd.h:62
Eigen::RowVectorXd vecFreqs
Frequency axis (Hz) for csdByFreq entries.
Definition csd.h:64
QVector< Eigen::MatrixXcd > csdByFreq
One n_ch × n_ch CSD matrix per selected frequency bin.
Definition csd.h:63
static CsdResult computeMultitaper(const Eigen::MatrixXd &matData, double sfreq, double fmin=0.0, double fmax=-1.0, double halfBandwidth=4.0, int nTapers=-1)
Definition csd.cpp:55
static CsdResult computeMorlet(const Eigen::MatrixXd &matData, double sfreq, const Eigen::RowVectorXd &frequencies, int nCycles=7)
Definition csd.cpp:231
static CsdResult computeFourier(const Eigen::MatrixXd &matData, double sfreq, double fmin=0.0, double fmax=-1.0, int nFft=256, double overlap=0.5)
Definition csd.cpp:140
Result of a DPSS taper computation.
Definition dpss.h:54
Eigen::MatrixXd matTapers
nTapers × N, each row is a unit-norm taper
Definition dpss.h:55
Eigen::VectorXd vecEigenvalues
Concentration ratios, length nTapers.
Definition dpss.h:56
static DpssResult compute(int N, double halfBandwidth, int nTapers=-1)
Definition dpss.cpp:48