v2.0.0
Loading...
Searching...
No Matches
connectivity_aec.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "connectivity_aec.h"
18
19//=============================================================================================================
20// STD INCLUDES
21//=============================================================================================================
22
23#include <cmath>
24#include <complex>
25#include <vector>
26
27//=============================================================================================================
28// USED NAMESPACES
29//=============================================================================================================
30
31using namespace UTILSLIB;
32using namespace Eigen;
33
34//=============================================================================================================
35// STATIC HELPERS
36//=============================================================================================================
37
38namespace {
39
40// Simple DFT-based Hilbert transform (no external FFT library needed)
41// For production use, this should be replaced with an FFT-based implementation
42void dftHilbert(const VectorXd& input, VectorXd& envelope)
43{
44 const int n = static_cast<int>(input.size());
45 envelope.resize(n);
46
47 if (n == 0)
48 return;
49
50 // Compute DFT
51 std::vector<std::complex<double>> X(static_cast<size_t>(n));
52 for (int k = 0; k < n; ++k) {
53 std::complex<double> sum(0.0, 0.0);
54 for (int t = 0; t < n; ++t) {
55 double angle = -2.0 * M_PI * k * t / n;
56 sum += input[t] * std::complex<double>(std::cos(angle), std::sin(angle));
57 }
58 X[static_cast<size_t>(k)] = sum;
59 }
60
61 // Apply Hilbert transform in frequency domain:
62 // H[0] = X[0], H[N/2] = X[N/2]
63 // H[k] = 2*X[k] for 0 < k < N/2
64 // H[k] = 0 for N/2 < k < N
65 std::vector<std::complex<double>> H(static_cast<size_t>(n));
66 H[0] = X[0];
67 for (int k = 1; k < (n + 1) / 2; ++k)
68 H[static_cast<size_t>(k)] = 2.0 * X[static_cast<size_t>(k)];
69 if (n % 2 == 0)
70 H[static_cast<size_t>(n / 2)] = X[static_cast<size_t>(n / 2)];
71 for (int k = (n + 1) / 2; k < n; ++k)
72 H[static_cast<size_t>(k)] = std::complex<double>(0.0, 0.0);
73
74 // Inverse DFT to get analytic signal
75 for (int t = 0; t < n; ++t) {
76 std::complex<double> sum(0.0, 0.0);
77 for (int k = 0; k < n; ++k) {
78 double angle = 2.0 * M_PI * k * t / n;
79 sum += H[static_cast<size_t>(k)] * std::complex<double>(std::cos(angle), std::sin(angle));
80 }
81 sum /= static_cast<double>(n);
82 envelope[t] = std::abs(sum);
83 }
84}
85
86} // anonymous namespace
87
88//=============================================================================================================
89// DEFINE MEMBER METHODS
90//=============================================================================================================
91
92MatrixXd ConnectivityAec::compute(const MatrixXd& matData)
93{
94 const int nSig = static_cast<int>(matData.rows());
95 const int nSamples = static_cast<int>(matData.cols());
96
97 MatrixXd aec = MatrixXd::Identity(nSig, nSig);
98
99 if (nSig < 2 || nSamples < 2)
100 return aec;
101
102 // Compute envelopes
103 MatrixXd envs(nSig, nSamples);
104 for (int i = 0; i < nSig; ++i) {
105 envs.row(i) = hilbertEnvelope(matData.row(i).transpose()).transpose();
106 }
107
108 // Pairwise correlation of envelopes
109 for (int i = 0; i < nSig; ++i) {
110 for (int j = i + 1; j < nSig; ++j) {
111 double r = pearsonCorrelation(envs.row(i).transpose(), envs.row(j).transpose());
112 aec(i, j) = r;
113 aec(j, i) = r;
114 }
115 }
116
117 return aec;
118}
119
120//=============================================================================================================
121
122MatrixXd ConnectivityAec::computeOrthogonalized(const MatrixXd& matData)
123{
124 const int nSig = static_cast<int>(matData.rows());
125 const int nSamples = static_cast<int>(matData.cols());
126
127 MatrixXd aec = MatrixXd::Identity(nSig, nSig);
128
129 if (nSig < 2 || nSamples < 2)
130 return aec;
131
132 // For orthogonalized AEC, we orthogonalize signal j w.r.t. i, then correlate envelopes
133 for (int i = 0; i < nSig; ++i) {
134 VectorXd si = matData.row(i).transpose();
135 VectorXd envI = hilbertEnvelope(si);
136
137 for (int j = i + 1; j < nSig; ++j) {
138 VectorXd sj = matData.row(j).transpose();
139
140 // Orthogonalize j w.r.t. i: sj_orth = sj - (sj·si / si·si) * si
141 double siNorm2 = si.squaredNorm();
142 VectorXd sjOrth_ij;
143 if (siNorm2 > 1e-30)
144 sjOrth_ij = sj - (sj.dot(si) / siNorm2) * si;
145 else
146 sjOrth_ij = sj;
147
148 VectorXd envJOrth = hilbertEnvelope(sjOrth_ij);
149 double r_ij = std::abs(pearsonCorrelation(envI, envJOrth));
150
151 // Orthogonalize i w.r.t. j
152 double sjNorm2 = sj.squaredNorm();
153 VectorXd siOrth_ji;
154 if (sjNorm2 > 1e-30)
155 siOrth_ji = si - (si.dot(sj) / sjNorm2) * sj;
156 else
157 siOrth_ji = si;
158
159 VectorXd envJ = hilbertEnvelope(sj);
160 VectorXd envIOrth = hilbertEnvelope(siOrth_ji);
161 double r_ji = std::abs(pearsonCorrelation(envIOrth, envJ));
162
163 // Symmetrise
164 double r = (r_ij + r_ji) / 2.0;
165 aec(i, j) = r;
166 aec(j, i) = r;
167 }
168 }
169
170 return aec;
171}
172
173//=============================================================================================================
174
175VectorXd ConnectivityAec::hilbertEnvelope(const VectorXd& signal)
176{
177 VectorXd env;
178 dftHilbert(signal, env);
179 return env;
180}
181
182//=============================================================================================================
183
184double ConnectivityAec::pearsonCorrelation(const VectorXd& a, const VectorXd& b)
185{
186 const int n = static_cast<int>(a.size());
187 if (n < 2 || b.size() != n)
188 return 0.0;
189
190 double meanA = a.mean();
191 double meanB = b.mean();
192
193 VectorXd ac = a.array() - meanA;
194 VectorXd bc = b.array() - meanB;
195
196 double stdA = std::sqrt(ac.squaredNorm() / static_cast<double>(n - 1));
197 double stdB = std::sqrt(bc.squaredNorm() / static_cast<double>(n - 1));
198
199 if (stdA < 1e-15 || stdB < 1e-15)
200 return 0.0;
201
202 return ac.dot(bc) / (static_cast<double>(n - 1) * stdA * stdB);
203}
constexpr int X
#define M_PI
ConnectivityAec — Amplitude Envelope Correlation connectivity metric.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
static Eigen::VectorXd hilbertEnvelope(const Eigen::VectorXd &signal)
static Eigen::MatrixXd compute(const Eigen::MatrixXd &matData)
static Eigen::MatrixXd computeOrthogonalized(const Eigen::MatrixXd &matData)
static double pearsonCorrelation(const Eigen::VectorXd &a, const Eigen::VectorXd &b)