v2.0.0
Loading...
Searching...
No Matches
eeg_reference.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "eeg_reference.h"
18
19#include <fiff/fiff_info.h>
20#include <fiff/fiff_constants.h>
21
22//=============================================================================================================
23// QT INCLUDES
24//=============================================================================================================
25
26#include <QDebug>
27#include <QSet>
28
29//=============================================================================================================
30// USED NAMESPACES
31//=============================================================================================================
32
33using namespace UTILSLIB;
34using namespace FIFFLIB;
35using namespace Eigen;
36
37//=============================================================================================================
38// STATIC HELPERS
39//=============================================================================================================
40
44static QVector<int> findGoodEegIndices(const FiffInfo& info)
45{
46 QVector<int> indices;
47 const QSet<QString> badSet(info.bads.begin(), info.bads.end());
48 for (int i = 0; i < info.nchan; ++i) {
49 if (info.chs[i].kind == FIFFV_EEG_CH && !badSet.contains(info.ch_names[i])) {
50 indices.append(i);
51 }
52 }
53 return indices;
54}
55
56//=============================================================================================================
60static QVector<int> findAllEegIndices(const FiffInfo& info)
61{
62 QVector<int> indices;
63 for (int i = 0; i < info.nchan; ++i) {
64 if (info.chs[i].kind == FIFFV_EEG_CH) {
65 indices.append(i);
66 }
67 }
68 return indices;
69}
70
71//=============================================================================================================
72// FUNCTION IMPLEMENTATIONS
73//=============================================================================================================
74
75void UTILSLIB::setEegReference(MatrixXd& data,
76 const FiffInfo& info,
77 const QStringList& refChannels,
78 bool projection)
79{
80 if (projection) {
81 qWarning("setEegReference: projection mode is not yet implemented. "
82 "Falling back to direct data modification.");
83 }
84
85 if (data.rows() != info.nchan) {
86 qWarning("setEegReference: data row count (%lld) does not match info.nchan (%d).",
87 static_cast<long long>(data.rows()), info.nchan);
88 return;
89 }
90
91 if (data.cols() == 0 || data.rows() == 0) {
92 return;
93 }
94
95 // Determine which EEG channels to subtract from
96 const QVector<int> allEegIdx = findAllEegIndices(info);
97 if (allEegIdx.isEmpty()) {
98 qWarning("setEegReference: no EEG channels found in info.");
99 return;
100 }
101
102 // Compute reference signal
103 RowVectorXd refSignal;
104
105 const bool useAverage = refChannels.isEmpty() || (refChannels.size() == 1 && refChannels.first().compare(QLatin1String("average"), Qt::CaseInsensitive) == 0);
106
107 if (useAverage) {
108 // Average reference: mean of all good EEG channels
109 const QVector<int> goodEegIdx = findGoodEegIndices(info);
110 if (goodEegIdx.isEmpty()) {
111 qWarning("setEegReference: all EEG channels are marked as bad.");
112 return;
113 }
114 refSignal = RowVectorXd::Zero(data.cols());
115 for (int idx : goodEegIdx) {
116 refSignal += data.row(idx);
117 }
118 refSignal /= static_cast<double>(goodEegIdx.size());
119 } else {
120 // Specific channel(s): mean of named channels
121 QVector<int> refIdx;
122 for (const QString& name : refChannels) {
123 int idx = info.ch_names.indexOf(name);
124 if (idx < 0) {
125 qWarning("setEegReference: reference channel '%s' not found.", qPrintable(name));
126 return;
127 }
128 refIdx.append(idx);
129 }
130 refSignal = RowVectorXd::Zero(data.cols());
131 for (int idx : refIdx) {
132 refSignal += data.row(idx);
133 }
134 refSignal /= static_cast<double>(refIdx.size());
135 }
136
137 // Subtract reference from all EEG channels
138 for (int idx : allEegIdx) {
139 data.row(idx) -= refSignal;
140 }
141}
142
143//=============================================================================================================
144
145void UTILSLIB::addReferenceChannels(MatrixXd& data,
146 FiffInfo& info,
147 const QStringList& chNames)
148{
149 if (chNames.isEmpty()) {
150 return;
151 }
152
153 const Eigen::Index nTimes = data.cols();
154 const Eigen::Index nOldCh = data.rows();
155 const int nNew = chNames.size();
156
157 // Expand data matrix with zero rows
158 MatrixXd newData(nOldCh + nNew, nTimes);
159 if (nOldCh > 0 && nTimes > 0) {
160 newData.topRows(nOldCh) = data;
161 }
162 newData.bottomRows(nNew).setZero();
163 data = newData;
164
165 // Find a template EEG channel for defaults
166 FiffChInfo templateCh;
167 bool haveTemplate = false;
168 for (int i = 0; i < info.nchan; ++i) {
169 if (info.chs[i].kind == FIFFV_EEG_CH) {
170 templateCh = info.chs[i];
171 haveTemplate = true;
172 break;
173 }
174 }
175
176 for (int n = 0; n < nNew; ++n) {
177 FiffChInfo ch;
178 if (haveTemplate) {
179 ch = templateCh;
180 }
181 ch.ch_name = chNames[n];
182 ch.kind = FIFFV_EEG_CH;
183 ch.scanNo = info.nchan + n + 1;
184 ch.logNo = ch.scanNo;
185
186 info.chs.append(ch);
187 info.ch_names.append(chNames[n]);
188 }
189 info.nchan += nNew;
190}
191
192//=============================================================================================================
193
194void UTILSLIB::setBipolarReference(MatrixXd& data,
195 FiffInfo& info,
196 const QStringList& anodes,
197 const QStringList& cathodes,
198 bool dropOriginals)
199{
200 if (anodes.size() != cathodes.size()) {
201 qWarning("setBipolarReference: anodes and cathodes must have the same length "
202 "(%lld vs %lld).",
203 static_cast<long long>(anodes.size()), static_cast<long long>(cathodes.size()));
204 return;
205 }
206
207 if (anodes.isEmpty()) {
208 return;
209 }
210
211 if (data.rows() != info.nchan) {
212 qWarning("setBipolarReference: data row count (%lld) does not match info.nchan (%d).",
213 static_cast<long long>(data.rows()), info.nchan);
214 return;
215 }
216
217 const int nPairs = anodes.size();
218 const Eigen::Index nTimes = data.cols();
219
220 // Resolve anode and cathode indices
221 QVector<int> anodeIdx(nPairs), cathodeIdx(nPairs);
222 for (int i = 0; i < nPairs; ++i) {
223 anodeIdx[i] = info.ch_names.indexOf(anodes[i]);
224 if (anodeIdx[i] < 0) {
225 qWarning("setBipolarReference: anode channel '%s' not found.", qPrintable(anodes[i]));
226 return;
227 }
228 cathodeIdx[i] = info.ch_names.indexOf(cathodes[i]);
229 if (cathodeIdx[i] < 0) {
230 qWarning("setBipolarReference: cathode channel '%s' not found.", qPrintable(cathodes[i]));
231 return;
232 }
233 }
234
235 // Collect the set of original channels involved
236 QSet<int> bipolarOriginals;
237 for (int i = 0; i < nPairs; ++i) {
238 bipolarOriginals.insert(anodeIdx[i]);
239 bipolarOriginals.insert(cathodeIdx[i]);
240 }
241
242 // Build bipolar data rows
243 MatrixXd bipolarData(nPairs, nTimes);
244 for (int i = 0; i < nPairs; ++i) {
245 bipolarData.row(i) = data.row(anodeIdx[i]) - data.row(cathodeIdx[i]);
246 }
247
248 // Build bipolar channel info
249 QList<FiffChInfo> bipolarChs;
250 QStringList bipolarNames;
251 for (int i = 0; i < nPairs; ++i) {
252 FiffChInfo ch = info.chs[anodeIdx[i]];
253 ch.ch_name = anodes[i] + "-" + cathodes[i];
254 ch.logNo = i + 1;
255 bipolarChs.append(ch);
256 bipolarNames.append(ch.ch_name);
257 }
258
259 if (dropOriginals) {
260 // Keep only non-bipolar-original rows + append bipolar rows
261 QVector<int> keepIdx;
262 for (int i = 0; i < static_cast<int>(data.rows()); ++i) {
263 if (!bipolarOriginals.contains(i)) {
264 keepIdx.append(i);
265 }
266 }
267
268 const int nKeep = keepIdx.size();
269 MatrixXd newData(nKeep + nPairs, nTimes);
270 QList<FiffChInfo> newChs;
271 QStringList newNames;
272
273 for (int i = 0; i < nKeep; ++i) {
274 newData.row(i) = data.row(keepIdx[i]);
275 newChs.append(info.chs[keepIdx[i]]);
276 newNames.append(info.ch_names[keepIdx[i]]);
277 }
278 for (int i = 0; i < nPairs; ++i) {
279 newData.row(nKeep + i) = bipolarData.row(i);
280 newChs.append(bipolarChs[i]);
281 newNames.append(bipolarNames[i]);
282 }
283
284 // Update scan numbers
285 for (int i = 0; i < newChs.size(); ++i) {
286 newChs[i].scanNo = i + 1;
287 }
288
289 data = newData;
290 info.chs = newChs;
291 info.ch_names = newNames;
292 info.nchan = static_cast<int>(newData.rows());
293 } else {
294 // Keep all original rows, append bipolar rows at the end
295 MatrixXd newData(data.rows() + nPairs, nTimes);
296 newData.topRows(data.rows()) = data;
297 newData.bottomRows(nPairs) = bipolarData;
298
299 for (int i = 0; i < nPairs; ++i) {
300 bipolarChs[i].scanNo = info.nchan + i + 1;
301 }
302
303 info.chs.append(bipolarChs);
304 info.ch_names.append(bipolarNames);
305 info.nchan += nPairs;
306 data = newData;
307 }
308}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_EEG_CH
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
EEG re-referencing operators — common-average, single-electrode and REST.
FIFF file I/O, in-memory data structures and high-level readers/writers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
DSPSHARED_EXPORT void setBipolarReference(Eigen::MatrixXd &data, FIFFLIB::FiffInfo &info, const QStringList &anodes, const QStringList &cathodes, bool dropOriginals=true)
Create bipolar derivations from EEG channels.
DSPSHARED_EXPORT void setEegReference(Eigen::MatrixXd &data, const FIFFLIB::FiffInfo &info, const QStringList &refChannels=QStringList(), bool projection=false)
Re-reference EEG channels to average, specific channel(s), or REST.
DSPSHARED_EXPORT void addReferenceChannels(Eigen::MatrixXd &data, FIFFLIB::FiffInfo &info, const QStringList &chNames)
Add reference channel(s) back as zero-filled rows.
Per-channel FIFF descriptor: identifiers, kind, calibration, coil type, channel-frame coil position a...
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
QList< FiffChInfo > chs