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 <algorithm>
24#include <cmath>
25
26#include <unsupported/Eigen/FFT>
27
28//=============================================================================================================
29// USED NAMESPACES
30//=============================================================================================================
31
32using namespace UTILSLIB;
33using namespace Eigen;
34
35//=============================================================================================================
36// DEFINE MEMBER METHODS
37//=============================================================================================================
38
39MatrixXd ConnectivityAec::compute(const MatrixXd& matData)
40{
41 const int nSig = static_cast<int>(matData.rows());
42 const int nSamples = static_cast<int>(matData.cols());
43
44 MatrixXd aec = MatrixXd::Identity(nSig, nSig);
45
46 if (nSig < 2 || nSamples < 2)
47 return aec;
48
49 // Compute envelopes
50 MatrixXd envs(nSig, nSamples);
51 for (int i = 0; i < nSig; ++i) {
52 envs.row(i) = hilbertEnvelope(matData.row(i).transpose()).transpose();
53 }
54
55 // Pairwise correlation of envelopes
56 for (int i = 0; i < nSig; ++i) {
57 for (int j = i + 1; j < nSig; ++j) {
58 double r = pearsonCorrelation(envs.row(i).transpose(), envs.row(j).transpose());
59 aec(i, j) = r;
60 aec(j, i) = r;
61 }
62 }
63
64 return aec;
65}
66
67//=============================================================================================================
68
69MatrixXd ConnectivityAec::computeOrthogonalized(const MatrixXd& matData)
70{
71 const int nSig = static_cast<int>(matData.rows());
72 const int nSamples = static_cast<int>(matData.cols());
73
74 MatrixXd aec = MatrixXd::Identity(nSig, nSig);
75
76 if (nSig < 2 || nSamples < 2)
77 return aec;
78
79 // For orthogonalized AEC, we orthogonalize signal j w.r.t. i, then correlate envelopes
80 for (int i = 0; i < nSig; ++i) {
81 VectorXd si = matData.row(i).transpose();
82 VectorXd envI = hilbertEnvelope(si);
83
84 for (int j = i + 1; j < nSig; ++j) {
85 VectorXd sj = matData.row(j).transpose();
86
87 // Orthogonalize j w.r.t. i: sj_orth = sj - (sj·si / si·si) * si
88 double siNorm2 = si.squaredNorm();
89 VectorXd sjOrth_ij;
90 if (siNorm2 > 1e-30)
91 sjOrth_ij = sj - (sj.dot(si) / siNorm2) * si;
92 else
93 sjOrth_ij = sj;
94
95 VectorXd envJOrth = hilbertEnvelope(sjOrth_ij);
96 double r_ij = std::abs(pearsonCorrelation(envI, envJOrth));
97
98 // Orthogonalize i w.r.t. j
99 double sjNorm2 = sj.squaredNorm();
100 VectorXd siOrth_ji;
101 if (sjNorm2 > 1e-30)
102 siOrth_ji = si - (si.dot(sj) / sjNorm2) * sj;
103 else
104 siOrth_ji = si;
105
106 VectorXd envJ = hilbertEnvelope(sj);
107 VectorXd envIOrth = hilbertEnvelope(siOrth_ji);
108 double r_ji = std::abs(pearsonCorrelation(envIOrth, envJ));
109
110 // Symmetrise
111 double r = (r_ij + r_ji) / 2.0;
112 aec(i, j) = r;
113 aec(j, i) = r;
114 }
115 }
116
117 return aec;
118}
119
120//=============================================================================================================
121
122VectorXd ConnectivityAec::hilbertEnvelope(const VectorXd& signal, int nFft)
123{
124 const Index n = signal.size();
125 const Index nPad = std::max<Index>(nFft, n);
126 if (n == 0) {
127 return VectorXd();
128 }
129 VectorXd padded = VectorXd::Zero(nPad);
130 padded.head(n) = signal;
131
132 FFT<double> fft;
133 fft.SetFlag(FFT<double>::HalfSpectrum);
134 VectorXcd half;
135 fft.fwd(half, padded);
136
137 // Analytic spectrum: DC and (even length) Nyquist kept, positive frequencies doubled, negative zeroed
138 VectorXcd spec = VectorXcd::Zero(nPad);
139 spec.head(half.size()) = half;
140 spec.segment(1, (nPad - 1) / 2) *= 2.0;
141
142 fft.ClearFlag(FFT<double>::HalfSpectrum);
143 VectorXcd analytic;
144 fft.inv(analytic, spec);
145 return analytic.head(n).cwiseAbs();
146}
147
148//=============================================================================================================
149
150double ConnectivityAec::pearsonCorrelation(const VectorXd& a, const VectorXd& b)
151{
152 const int n = static_cast<int>(a.size());
153 if (n < 2 || b.size() != n)
154 return 0.0;
155
156 double meanA = a.mean();
157 double meanB = b.mean();
158
159 VectorXd ac = a.array() - meanA;
160 VectorXd bc = b.array() - meanB;
161
162 double stdA = std::sqrt(ac.squaredNorm() / static_cast<double>(n - 1));
163 double stdB = std::sqrt(bc.squaredNorm() / static_cast<double>(n - 1));
164
165 if (stdA < 1e-15 || stdB < 1e-15)
166 return 0.0;
167
168 return ac.dot(bc) / (static_cast<double>(n - 1) * stdA * stdB);
169}
ConnectivityAec — Amplitude Envelope Correlation connectivity metric.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
static Eigen::MatrixXd compute(const Eigen::MatrixXd &matData)
static Eigen::MatrixXd computeOrthogonalized(const Eigen::MatrixXd &matData)
static Eigen::VectorXd hilbertEnvelope(const Eigen::VectorXd &signal, int nFft=0)
static double pearsonCorrelation(const Eigen::VectorXd &a, const Eigen::VectorXd &b)