v2.0.0
Loading...
Searching...
No Matches
bad_channel_detect.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "bad_channel_detect.h"
18
19//=============================================================================================================
20// EIGEN INCLUDES
21//=============================================================================================================
22
23#include <Eigen/Core>
24
25//=============================================================================================================
26// C++ INCLUDES
27//=============================================================================================================
28
29#include <cmath>
30#include <algorithm>
31
32//=============================================================================================================
33// USED NAMESPACES
34//=============================================================================================================
35
36using namespace UTILSLIB;
37using namespace Eigen;
38
39//=============================================================================================================
40// PRIVATE
41//=============================================================================================================
42
43double BadChannelDetect::pearsonCorr(const RowVectorXd& a, const RowVectorXd& b)
44{
45 if (a.size() != b.size() || a.size() == 0) return 0.0;
46
47 RowVectorXd ac = a.array() - a.mean();
48 RowVectorXd bc = b.array() - b.mean();
49
50 double normA = ac.norm();
51 double normB = bc.norm();
52 if (normA < 1e-30 || normB < 1e-30) return 0.0;
53
54 return ac.dot(bc) / (normA * normB);
55}
56
57//=============================================================================================================
58
59double BadChannelDetect::median(QVector<double> values)
60{
61 if (values.isEmpty()) return 0.0;
62 std::sort(values.begin(), values.end());
63 const int n = values.size();
64 if (n % 2 == 1) return values[n / 2];
65 return 0.5 * (values[n / 2 - 1] + values[n / 2]);
66}
67
68//=============================================================================================================
69// PUBLIC
70//=============================================================================================================
71
72QVector<int> BadChannelDetect::detect(const MatrixXd& matData,
73 const Params& params)
74{
75 QVector<int> flat = detectFlat(matData, params.dFlatThreshold);
76 QVector<int> noisy = detectHighVariance(matData, params.dVarZThresh);
77 QVector<int> low = detectLowCorrelation(matData, params.dCorrThresh, params.iNeighbours);
78
79 // Union without duplicates
80 QVector<bool> flagged(static_cast<int>(matData.rows()), false);
81 for (int i : flat) if (i >= 0 && i < matData.rows()) flagged[i] = true;
82 for (int i : noisy) if (i >= 0 && i < matData.rows()) flagged[i] = true;
83 for (int i : low) if (i >= 0 && i < matData.rows()) flagged[i] = true;
84
85 QVector<int> result;
86 for (int i = 0; i < static_cast<int>(matData.rows()); ++i) {
87 if (flagged[i]) result.append(i);
88 }
89 return result;
90}
91
92//=============================================================================================================
93
94QVector<int> BadChannelDetect::detectFlat(const MatrixXd& matData,
95 double dThreshold)
96{
97 QVector<int> bad;
98 for (int ch = 0; ch < matData.rows(); ++ch) {
99 const RowVectorXd row = matData.row(ch);
100 double ptp = row.maxCoeff() - row.minCoeff();
101 if (ptp < dThreshold) bad.append(ch);
102 }
103 return bad;
104}
105
106//=============================================================================================================
107
108QVector<int> BadChannelDetect::detectHighVariance(const MatrixXd& matData,
109 double dZThresh)
110{
111 const int nCh = static_cast<int>(matData.rows());
112 if (nCh < 3) return {}; // Cannot compute meaningful statistics with < 3 channels
113
114 // Compute per-channel standard deviation
115 QVector<double> stds(nCh);
116 for (int ch = 0; ch < nCh; ++ch) {
117 const RowVectorXd row = matData.row(ch);
118 double mean = row.mean();
119 double var = (row.array() - mean).square().mean();
120 stds[ch] = std::sqrt(var);
121 }
122
123 // Median and MAD of std across channels
124 double med = median(stds);
125
126 QVector<double> absDevs(nCh);
127 for (int ch = 0; ch < nCh; ++ch)
128 absDevs[ch] = std::abs(stds[ch] - med);
129 double mad = median(absDevs);
130
131 // Consistency factor: MAD -> sigma for Gaussian = 1.4826
132 double sigma = 1.4826 * mad;
133
134 // If the robust scale collapses because most channels are identical and only
135 // a few are outliers, fall back to the classical std-dev across channel stds.
136 if (sigma < 1e-30) {
137 double meanStd = 0.0;
138 for (double value : stds) {
139 meanStd += value;
140 }
141 meanStd /= static_cast<double>(nCh);
142
143 double varStd = 0.0;
144 for (double value : stds) {
145 const double diff = value - meanStd;
146 varStd += diff * diff;
147 }
148 sigma = std::sqrt(varStd / static_cast<double>(nCh));
149 }
150
151 if (sigma < 1e-30) return {}; // All channels identical (e.g. all zeros)
152
153 QVector<int> bad;
154 for (int ch = 0; ch < nCh; ++ch) {
155 double z = (stds[ch] - med) / sigma;
156 if (z > dZThresh) bad.append(ch);
157 }
158 return bad;
159}
160
161//=============================================================================================================
162
163QVector<int> BadChannelDetect::detectLowCorrelation(const MatrixXd& matData,
164 double dCorrThresh,
165 int iNeighbours)
166{
167 const int nCh = static_cast<int>(matData.rows());
168 if (nCh < 2) return {};
169
170 QVector<int> bad;
171
172 for (int ch = 0; ch < nCh; ++ch) {
173 int nValid = 0;
174 double sumC = 0.0;
175
176 int lo = std::max(0, ch - iNeighbours);
177 int hi = std::min(nCh - 1, ch + iNeighbours);
178
179 for (int nb = lo; nb <= hi; ++nb) {
180 if (nb == ch) continue;
181 double c = std::abs(pearsonCorr(matData.row(ch), matData.row(nb)));
182 sumC += c;
183 ++nValid;
184 }
185
186 if (nValid == 0) continue;
187
188 double meanCorr = sumC / static_cast<double>(nValid);
189 if (meanCorr < dCorrThresh) bad.append(ch);
190 }
191
192 return bad;
193}
Declaration of BadChannelDetect — automated detection of bad MEG/EEG channels.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
BadChannelDetectParams Params
static QVector< int > detect(const Eigen::MatrixXd &matData, const Params &params=Params())
static QVector< int > detectHighVariance(const Eigen::MatrixXd &matData, double dZThresh=4.0)
static QVector< int > detectLowCorrelation(const Eigen::MatrixXd &matData, double dCorrThresh=0.4, int iNeighbours=5)
static QVector< int > detectFlat(const Eigen::MatrixXd &matData, double dThreshold=1e-13)