v2.0.0
Loading...
Searching...
No Matches
morlet_tfr.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "morlet_tfr.h"
18
19//=============================================================================================================
20// EIGEN INCLUDES
21//=============================================================================================================
22
23#include <Eigen/Core>
24//#ifndef EIGEN_FFTW_DEFAULT
25//#define EIGEN_FFTW_DEFAULT
26//#endif
27#include <unsupported/Eigen/FFT>
28
29//=============================================================================================================
30// STD INCLUDES
31//=============================================================================================================
32
33#include <cmath>
34#include <complex>
35#include <vector>
36
37//=============================================================================================================
38// USED NAMESPACES
39//=============================================================================================================
40
41using namespace UTILSLIB;
42using namespace Eigen;
43
44namespace
45{
46constexpr double MORLET_PI = 3.14159265358979323846;
47}
48
49//=============================================================================================================
50// DEFINE MEMBER METHODS
51//=============================================================================================================
52
53int MorletTfr::nextPow2(int n)
54{
55 int p = 1;
56 while (p < n)
57 p <<= 1;
58 return p;
59}
60
61//=============================================================================================================
62
63VectorXcd MorletTfr::buildWavelet(double dFreq, double dSFreq, double dNCycles, int& halfLen, bool bZeroMean)
64{
65 const double sigma_t = dNCycles / (2.0 * MORLET_PI * dFreq);
66
67 // Same grid as np.arange(0.0, 5.0 * sigma_t, 1.0 / sfreq), mirrored around t = 0.
68 const double step = 1.0 / dSFreq;
69 halfLen = static_cast<int>(std::ceil(5.0 * sigma_t / step)) - 1;
70
71 const int nWav = 2 * halfLen + 1;
72 VectorXcd wavelet(nWav);
73 const double realOffset = bZeroMean ? std::exp(-2.0 * std::pow(MORLET_PI * dFreq * sigma_t, 2)) : 0.0;
74
75 for (int i = 0; i < nWav; ++i) {
76 const double t = (i - halfLen) * step;
77 const double gauss = std::exp(-t * t / (2.0 * sigma_t * sigma_t));
78 const double phase = 2.0 * MORLET_PI * dFreq * t;
79 wavelet[i] = gauss * std::complex<double>(std::cos(phase) - realOffset, std::sin(phase));
80 }
81 // Norm sqrt(2) for the complex wavelet, i.e. norm 1 for its real part.
82 wavelet /= std::sqrt(0.5) * wavelet.norm();
83 return wavelet;
84}
85
86//=============================================================================================================
87
88MorletTfrResult MorletTfr::compute(const RowVectorXd& vecData,
89 double dSFreq,
90 const RowVectorXd& vecFreqs,
91 double dNCycles)
92{
93 const int nTimes = static_cast<int>(vecData.cols());
94 const int nFreqs = static_cast<int>(vecFreqs.cols());
95
96 MorletTfrResult result;
97 result.matPower.resize(nFreqs, nTimes);
98 result.vecFreqs = vecFreqs;
99
100 // Pre-compute forward FFT of the (real) signal at the maximum needed convolution length.
101 // For each frequency the wavelet may differ in length; recompute convolution per frequency.
102 Eigen::FFT<double> fft;
103
104 for (int fi = 0; fi < nFreqs; ++fi) {
105 int halfLen = 0;
106 const VectorXcd wavelet = buildWavelet(vecFreqs[fi], dSFreq, dNCycles, halfLen, true);
107
108 const int nWav = static_cast<int>(wavelet.size());
109 const int nConv = nextPow2(nTimes + nWav - 1);
110
111 // --- FFT of zero-padded real signal ---
112 VectorXd sigPad = VectorXd::Zero(nConv);
113 sigPad.head(nTimes) = vecData.transpose();
114 VectorXcd sigSpec;
115 fft.fwd(sigSpec, sigPad);
116
117 // --- FFT of zero-padded complex wavelet (real & imag parts separately) ---
118 VectorXd wavReal = VectorXd::Zero(nConv);
119 VectorXd wavImag = VectorXd::Zero(nConv);
120 for (int k = 0; k < nWav; ++k) {
121 wavReal[k] = wavelet[k].real();
122 wavImag[k] = wavelet[k].imag();
123 }
124 VectorXcd wavSpecR, wavSpecI;
125 fft.fwd(wavSpecR, wavReal);
126 fft.fwd(wavSpecI, wavImag);
127 // Combine: FFT(real + i·imag) = FFT(real) + i·FFT(imag)
128 VectorXcd wavSpec = wavSpecR + std::complex<double>(0.0, 1.0) * wavSpecI;
129
130 // --- Multiply spectra and inverse FFT ---
131 VectorXcd product = sigSpec.array() * wavSpec.array();
132 VectorXcd conv;
133 fft.inv(conv, product);
134
135 // Trim to "same" length: skip the first halfLen samples (linear → same convolution)
136 for (int t = 0; t < nTimes; ++t)
137 result.matPower(fi, t) = std::norm(conv[t + halfLen]);
138 }
139
140 return result;
141}
142
143//=============================================================================================================
144
145QVector<MorletTfrResult> MorletTfr::computeMultiChannel(const MatrixXd& matData,
146 double dSFreq,
147 const RowVectorXd& vecFreqs,
148 double dNCycles,
149 const RowVectorXi& vecPicks)
150{
151 std::vector<int> picks;
152 if (vecPicks.size() > 0) {
153 picks.reserve(static_cast<std::size_t>(vecPicks.size()));
154 for (int i = 0; i < vecPicks.size(); ++i)
155 picks.push_back(vecPicks[i]);
156 } else {
157 picks.reserve(static_cast<std::size_t>(matData.rows()));
158 for (int i = 0; i < static_cast<int>(matData.rows()); ++i)
159 picks.push_back(i);
160 }
161
162 QVector<MorletTfrResult> results;
163 results.reserve(static_cast<int>(picks.size()));
164 for (int ch : picks)
165 results.append(compute(matData.row(ch), dSFreq, vecFreqs, dNCycles));
166 return results;
167}
Complex Morlet wavelet time-frequency representation (TFR).
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Result of a Morlet TFR computation for one channel.
Definition morlet_tfr.h:64
Eigen::RowVectorXd vecFreqs
Centre frequencies in Hz, length n_freqs.
Definition morlet_tfr.h:66
Eigen::MatrixXd matPower
n_freqs × n_times, instantaneous power (amplitude²)
Definition morlet_tfr.h:65
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())
static Eigen::VectorXcd buildWavelet(double dFreq, double dSFreq, double dNCycles, int &halfLen, bool bZeroMean=false)