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() * dpss.matTapers.row(t).transpose().array();
101
102 VectorXcd spec;
103 fft.fwd(spec, tapered);
104
105 spectra.row(ch) = spec.head(nFreqsFull).transpose();
106 }
107
108 // Accumulate weighted CSD: X(:,f) * X(:,f)^H for selected frequency bins
109 for (int fi = 0; fi < nFreqsSel; ++fi) {
110 const int fBin = iBinMin + fi;
111 VectorXcd col = spectra.col(fBin);
112 csdAccum[static_cast<std::size_t>(fi)] += w * (col * col.adjoint());
113 }
114 }
115
116 // 3. Assemble result
117 CsdResult result;
118 result.vecFreqs.resize(nFreqsSel);
119 result.csdByFreq.resize(nFreqsSel);
120
121 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
122
123 for (int fi = 0; fi < nFreqsSel; ++fi) {
124 csdAccum[static_cast<std::size_t>(fi)] /= weightSum;
125 result.csdByFreq[fi] = csdAccum[static_cast<std::size_t>(fi)];
126 result.vecFreqs[fi] = (iBinMin + fi) * freqRes;
127 meanCsd += result.csdByFreq[fi];
128 }
129
130 if (nFreqsSel > 0)
131 meanCsd /= static_cast<double>(nFreqsSel);
132
133 result.matCsd = meanCsd;
134 return result;
135}
136
137//=============================================================================================================
138
139CsdResult Csd::computeFourier(const MatrixXd& matData,
140 double sfreq,
141 double fmin,
142 double fmax,
143 int nFft,
144 double overlap)
145{
146 const int nChannels = static_cast<int>(matData.rows());
147 const int nTimes = static_cast<int>(matData.cols());
148
149 if (nFft > nTimes)
150 nFft = nTimes;
151
152 const int nFreqsFull = nFft / 2 + 1;
153
154 if (fmax < 0.0)
155 fmax = sfreq / 2.0;
156
157 const double freqRes = sfreq / static_cast<double>(nFft);
158 const int iBinMin = std::max(0, static_cast<int>(std::ceil(fmin / freqRes)));
159 const int iBinMax = std::min(nFreqsFull - 1, static_cast<int>(std::floor(fmax / freqRes)));
160 const int nFreqsSel = iBinMax - iBinMin + 1;
161
162 // Build Hann window
163 VectorXd hannWin(nFft);
164 for (int n = 0; n < nFft; ++n) {
165 const double t = static_cast<double>(n) / static_cast<double>(nFft - 1);
166 hannWin[n] = 0.5 * (1.0 - std::cos(2.0 * CSD_PI * t));
167 }
168 const double winPow = hannWin.squaredNorm();
169
170 const int iStep = std::max(1, static_cast<int>(std::round(static_cast<double>(nFft) * (1.0 - overlap))));
171
172 // Initialize per-frequency CSD accumulators
173 std::vector<MatrixXcd> csdAccum(static_cast<std::size_t>(nFreqsSel),
174 MatrixXcd::Zero(nChannels, nChannels));
175
176 Eigen::FFT<double> fft;
177 int nSeg = 0;
178
179 // Process each segment
180 for (int start = 0; start + nFft <= nTimes; start += iStep) {
181 // FFT all channels for this segment
182 MatrixXcd spectra(nChannels, nFreqsFull);
183
184 for (int ch = 0; ch < nChannels; ++ch) {
185 VectorXd seg = matData.row(ch).segment(start, nFft).transpose().array() * hannWin.array();
186
187 VectorXcd spec;
188 fft.fwd(spec, seg);
189
190 spectra.row(ch) = spec.head(nFreqsFull).transpose();
191 }
192
193 // Accumulate CSD for selected frequency bins
194 for (int fi = 0; fi < nFreqsSel; ++fi) {
195 const int fBin = iBinMin + fi;
196 VectorXcd col = spectra.col(fBin);
197 csdAccum[static_cast<std::size_t>(fi)] += col * col.adjoint();
198 }
199
200 ++nSeg;
201 }
202
203 // 3. Assemble result
204 CsdResult result;
205 result.vecFreqs.resize(nFreqsSel);
206 result.csdByFreq.resize(nFreqsSel);
207
208 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
209
210 const double dNorm = (nSeg > 0) ? 1.0 / (static_cast<double>(nSeg) * winPow) : 0.0;
211
212 for (int fi = 0; fi < nFreqsSel; ++fi) {
213 csdAccum[static_cast<std::size_t>(fi)] *= dNorm;
214 result.csdByFreq[fi] = csdAccum[static_cast<std::size_t>(fi)];
215 result.vecFreqs[fi] = (iBinMin + fi) * freqRes;
216 meanCsd += result.csdByFreq[fi];
217 }
218
219 if (nFreqsSel > 0)
220 meanCsd /= static_cast<double>(nFreqsSel);
221
222 result.matCsd = meanCsd;
223 return result;
224}
225
226//=============================================================================================================
227
228CsdResult Csd::computeMorlet(const MatrixXd& matData,
229 double sfreq,
230 const RowVectorXd& frequencies,
231 int nCycles)
232{
233 const int nChannels = static_cast<int>(matData.rows());
234 const int nTimes = static_cast<int>(matData.cols());
235 const int nFreqs = static_cast<int>(frequencies.size());
236
237 CsdResult result;
238 result.vecFreqs = frequencies;
239 result.csdByFreq.resize(nFreqs);
240
241 MatrixXcd meanCsd = MatrixXcd::Zero(nChannels, nChannels);
242
243 for (int fi = 0; fi < nFreqs; ++fi) {
244 const double f = frequencies[fi];
245 const double sigma = static_cast<double>(nCycles) / (2.0 * CSD_PI * f);
246
247 // Determine wavelet length: ±3σ in samples
248 const int halfLen = static_cast<int>(std::ceil(3.0 * sigma * sfreq));
249 const int wavLen = 2 * halfLen + 1;
250
251 // Build Morlet wavelet
252 VectorXcd wavelet(wavLen);
253 double normFactor = 0.0;
254 for (int n = 0; n < wavLen; ++n) {
255 const double t = (static_cast<double>(n) - static_cast<double>(halfLen)) / sfreq;
256 const double gauss = std::exp(-t * t / (2.0 * sigma * sigma));
257 wavelet[n] = std::complex<double>(gauss * std::cos(2.0 * CSD_PI * f * t),
258 gauss * std::sin(2.0 * CSD_PI * f * t));
259 normFactor += gauss * gauss;
260 }
261 // Normalize wavelet for unit energy
262 normFactor = std::sqrt(normFactor);
263 if (normFactor > 0.0)
264 wavelet /= normFactor;
265
266 // Convolve each channel with the wavelet → complex analytic signal
267 MatrixXcd analytic(nChannels, nTimes);
268
269 for (int ch = 0; ch < nChannels; ++ch) {
270 for (int t = 0; t < nTimes; ++t) {
271 std::complex<double> sum(0.0, 0.0);
272 for (int k = 0; k < wavLen; ++k) {
273 const int idx = t - halfLen + k;
274 if (idx >= 0 && idx < nTimes) {
275 sum += matData(ch, idx) * std::conj(wavelet[k]);
276 }
277 }
278 analytic(ch, t) = sum;
279 }
280 }
281
282 // CSD at this frequency = mean over time of C(:,t) * C(:,t)^H
283 MatrixXcd csdAtFreq = MatrixXcd::Zero(nChannels, nChannels);
284 for (int t = 0; t < nTimes; ++t) {
285 VectorXcd col = analytic.col(t);
286 csdAtFreq += col * col.adjoint();
287 }
288 csdAtFreq /= static_cast<double>(nTimes);
289
290 result.csdByFreq[fi] = csdAtFreq;
291 meanCsd += csdAtFreq;
292 }
293
294 if (nFreqs > 0)
295 meanCsd /= static_cast<double>(nFreqs);
296
297 result.matCsd = meanCsd;
298 return result;
299}
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:63
Eigen::MatrixXcd matCsd
n_channels × n_channels mean CSD across selected frequencies
Definition csd.h:64
Eigen::RowVectorXd vecFreqs
Frequency axis (Hz) for csdByFreq entries.
Definition csd.h:66
QVector< Eigen::MatrixXcd > csdByFreq
One n_ch × n_ch CSD matrix per selected frequency bin.
Definition csd.h:65
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:228
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:139
Result of a DPSS taper computation.
Definition dpss.h:56
Eigen::MatrixXd matTapers
nTapers × N, each row is a unit-norm taper
Definition dpss.h:57
Eigen::VectorXd vecEigenvalues
Concentration ratios, length nTapers.
Definition dpss.h:58
static DpssResult compute(int N, double halfBandwidth, int nTapers=-1)
Definition dpss.cpp:48