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