v2.0.0
Loading...
Searching...
No Matches
morlet_tfr.cpp
Go to the documentation of this file.
1//=============================================================================================================
33
34//=============================================================================================================
35// INCLUDES
36//=============================================================================================================
37
38#include "morlet_tfr.h"
39
40//=============================================================================================================
41// EIGEN INCLUDES
42//=============================================================================================================
43
44#include <Eigen/Core>
45//#ifndef EIGEN_FFTW_DEFAULT
46//#define EIGEN_FFTW_DEFAULT
47//#endif
48#include <unsupported/Eigen/FFT>
49
50//=============================================================================================================
51// STD INCLUDES
52//=============================================================================================================
53
54#include <cmath>
55#include <complex>
56#include <vector>
57
58//=============================================================================================================
59// USED NAMESPACES
60//=============================================================================================================
61
62using namespace UTILSLIB;
63using namespace Eigen;
64
65namespace
66{
67constexpr double MORLET_PI = 3.14159265358979323846;
68}
69
70//=============================================================================================================
71// DEFINE MEMBER METHODS
72//=============================================================================================================
73
74int MorletTfr::nextPow2(int n)
75{
76 int p = 1;
77 while (p < n) p <<= 1;
78 return p;
79}
80
81//=============================================================================================================
82
83VectorXcd MorletTfr::buildWavelet(double dFreq, double dSFreq, double dNCycles, int& halfLen)
84{
85 // Time-domain standard deviation: σ_t = nCycles / (2π·f)
86 const double sigma_t = dNCycles / (2.0 * MORLET_PI * dFreq);
87
88 // Truncate at ±4σ — captures > 99.99 % of energy
89 halfLen = static_cast<int>(std::round(4.0 * sigma_t * dSFreq));
90
91 const int nWav = 2 * halfLen + 1;
92 VectorXcd wavelet(nWav);
93
94 // L2-energy normalisation: A = (σ_t · √(2π))^(-0.5)
95 const double A = std::pow(sigma_t * std::sqrt(2.0 * MORLET_PI), -0.5);
96
97 for (int i = 0; i < nWav; ++i) {
98 const double t = static_cast<double>(i - halfLen) / dSFreq;
99 const double gauss = std::exp(-t * t / (2.0 * sigma_t * sigma_t));
100 const double phase = 2.0 * MORLET_PI * dFreq * t;
101 wavelet[i] = std::complex<double>(A * gauss * std::cos(phase),
102 A * gauss * std::sin(phase));
103 }
104 return wavelet;
105}
106
107//=============================================================================================================
108
109MorletTfrResult MorletTfr::compute(const RowVectorXd& vecData,
110 double dSFreq,
111 const RowVectorXd& vecFreqs,
112 double dNCycles)
113{
114 const int nTimes = static_cast<int>(vecData.cols());
115 const int nFreqs = static_cast<int>(vecFreqs.cols());
116
117 MorletTfrResult result;
118 result.matPower.resize(nFreqs, nTimes);
119 result.vecFreqs = vecFreqs;
120
121 // Pre-compute forward FFT of the (real) signal at the maximum needed convolution length.
122 // For each frequency the wavelet may differ in length; recompute convolution per frequency.
123 Eigen::FFT<double> fft;
124
125 for (int fi = 0; fi < nFreqs; ++fi) {
126 int halfLen = 0;
127 const VectorXcd wavelet = buildWavelet(vecFreqs[fi], dSFreq, dNCycles, halfLen);
128
129 const int nWav = static_cast<int>(wavelet.size());
130 const int nConv = nextPow2(nTimes + nWav - 1);
131
132 // --- FFT of zero-padded real signal ---
133 VectorXd sigPad = VectorXd::Zero(nConv);
134 sigPad.head(nTimes) = vecData.transpose();
135 VectorXcd sigSpec;
136 fft.fwd(sigSpec, sigPad);
137
138 // --- FFT of zero-padded complex wavelet (real & imag parts separately) ---
139 VectorXd wavReal = VectorXd::Zero(nConv);
140 VectorXd wavImag = VectorXd::Zero(nConv);
141 for (int k = 0; k < nWav; ++k) {
142 wavReal[k] = wavelet[k].real();
143 wavImag[k] = wavelet[k].imag();
144 }
145 VectorXcd wavSpecR, wavSpecI;
146 fft.fwd(wavSpecR, wavReal);
147 fft.fwd(wavSpecI, wavImag);
148 // Combine: FFT(real + i·imag) = FFT(real) + i·FFT(imag)
149 VectorXcd wavSpec = wavSpecR + std::complex<double>(0.0, 1.0) * wavSpecI;
150
151 // --- Multiply spectra and inverse FFT ---
152 VectorXcd product = sigSpec.array() * wavSpec.array();
153 VectorXcd conv;
154 fft.inv(conv, product);
155
156 // Trim to "same" length: skip the first halfLen samples (linear → same convolution)
157 for (int t = 0; t < nTimes; ++t)
158 result.matPower(fi, t) = std::norm(conv[t + halfLen]);
159 }
160
161 return result;
162}
163
164//=============================================================================================================
165
166QVector<MorletTfrResult> MorletTfr::computeMultiChannel(const MatrixXd& matData,
167 double dSFreq,
168 const RowVectorXd& vecFreqs,
169 double dNCycles,
170 const RowVectorXi& vecPicks)
171{
172 std::vector<int> picks;
173 if (vecPicks.size() > 0) {
174 picks.reserve(static_cast<std::size_t>(vecPicks.size()));
175 for (int i = 0; i < vecPicks.size(); ++i)
176 picks.push_back(vecPicks[i]);
177 } else {
178 picks.reserve(static_cast<std::size_t>(matData.rows()));
179 for (int i = 0; i < static_cast<int>(matData.rows()); ++i)
180 picks.push_back(i);
181 }
182
183 QVector<MorletTfrResult> results;
184 results.reserve(static_cast<int>(picks.size()));
185 for (int ch : picks)
186 results.append(compute(matData.row(ch), dSFreq, vecFreqs, dNCycles));
187 return results;
188}
MorletTfr class declaration — complex Morlet wavelet time-frequency representation.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Result of a Morlet TFR computation for one channel.
Definition morlet_tfr.h:67
Eigen::RowVectorXd vecFreqs
Centre frequencies in Hz, length n_freqs.
Definition morlet_tfr.h:69
Eigen::MatrixXd matPower
n_freqs × n_times, instantaneous power (amplitude²)
Definition morlet_tfr.h:68
static MorletTfrResult compute(const Eigen::RowVectorXd &vecData, double dSFreq, const Eigen::RowVectorXd &vecFreqs, double dNCycles=7.0)
static QVector< MorletTfrResult > computeMultiChannel(const Eigen::MatrixXd &matData, double dSFreq, const Eigen::RowVectorXd &vecFreqs, double dNCycles=7.0, const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi())