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()
106 || (refChannels.size() == 1
107 && refChannels.first().compare(QLatin1String("average"), Qt::CaseInsensitive) == 0);
108
109 if (useAverage) {
110 // Average reference: mean of all good EEG channels
111 const QVector<int> goodEegIdx = findGoodEegIndices(info);
112 if (goodEegIdx.isEmpty()) {
113 qWarning("setEegReference: all EEG channels are marked as bad.");
114 return;
115 }
116 refSignal = RowVectorXd::Zero(data.cols());
117 for (int idx : goodEegIdx) {
118 refSignal += data.row(idx);
119 }
120 refSignal /= static_cast<double>(goodEegIdx.size());
121 } else {
122 // Specific channel(s): mean of named channels
123 QVector<int> refIdx;
124 for (const QString& name : refChannels) {
125 int idx = info.ch_names.indexOf(name);
126 if (idx < 0) {
127 qWarning("setEegReference: reference channel '%s' not found.", qPrintable(name));
128 return;
129 }
130 refIdx.append(idx);
131 }
132 refSignal = RowVectorXd::Zero(data.cols());
133 for (int idx : refIdx) {
134 refSignal += data.row(idx);
135 }
136 refSignal /= static_cast<double>(refIdx.size());
137 }
138
139 // Subtract reference from all EEG channels
140 for (int idx : allEegIdx) {
141 data.row(idx) -= refSignal;
142 }
143}
144
145//=============================================================================================================
146
147void UTILSLIB::addReferenceChannels(MatrixXd& data,
148 FiffInfo& info,
149 const QStringList& chNames)
150{
151 if (chNames.isEmpty()) {
152 return;
153 }
154
155 const Eigen::Index nTimes = data.cols();
156 const Eigen::Index nOldCh = data.rows();
157 const int nNew = chNames.size();
158
159 // Expand data matrix with zero rows
160 MatrixXd newData(nOldCh + nNew, nTimes);
161 if (nOldCh > 0 && nTimes > 0) {
162 newData.topRows(nOldCh) = data;
163 }
164 newData.bottomRows(nNew).setZero();
165 data = newData;
166
167 // Find a template EEG channel for defaults
168 FiffChInfo templateCh;
169 bool haveTemplate = false;
170 for (int i = 0; i < info.nchan; ++i) {
171 if (info.chs[i].kind == FIFFV_EEG_CH) {
172 templateCh = info.chs[i];
173 haveTemplate = true;
174 break;
175 }
176 }
177
178 for (int n = 0; n < nNew; ++n) {
179 FiffChInfo ch;
180 if (haveTemplate) {
181 ch = templateCh;
182 }
183 ch.ch_name = chNames[n];
184 ch.kind = FIFFV_EEG_CH;
185 ch.scanNo = info.nchan + n + 1;
186 ch.logNo = ch.scanNo;
187
188 info.chs.append(ch);
189 info.ch_names.append(chNames[n]);
190 }
191 info.nchan += nNew;
192}
193
194//=============================================================================================================
195
196void UTILSLIB::setBipolarReference(MatrixXd& data,
197 FiffInfo& info,
198 const QStringList& anodes,
199 const QStringList& cathodes,
200 bool dropOriginals)
201{
202 if (anodes.size() != cathodes.size()) {
203 qWarning("setBipolarReference: anodes and cathodes must have the same length "
204 "(%lld vs %lld).", static_cast<long long>(anodes.size()), static_cast<long long>(cathodes.size()));
205 return;
206 }
207
208 if (anodes.isEmpty()) {
209 return;
210 }
211
212 if (data.rows() != info.nchan) {
213 qWarning("setBipolarReference: data row count (%lld) does not match info.nchan (%d).",
214 static_cast<long long>(data.rows()), info.nchan);
215 return;
216 }
217
218 const int nPairs = anodes.size();
219 const Eigen::Index nTimes = data.cols();
220
221 // Resolve anode and cathode indices
222 QVector<int> anodeIdx(nPairs), cathodeIdx(nPairs);
223 for (int i = 0; i < nPairs; ++i) {
224 anodeIdx[i] = info.ch_names.indexOf(anodes[i]);
225 if (anodeIdx[i] < 0) {
226 qWarning("setBipolarReference: anode channel '%s' not found.", qPrintable(anodes[i]));
227 return;
228 }
229 cathodeIdx[i] = info.ch_names.indexOf(cathodes[i]);
230 if (cathodeIdx[i] < 0) {
231 qWarning("setBipolarReference: cathode channel '%s' not found.", qPrintable(cathodes[i]));
232 return;
233 }
234 }
235
236 // Collect the set of original channels involved
237 QSet<int> bipolarOriginals;
238 for (int i = 0; i < nPairs; ++i) {
239 bipolarOriginals.insert(anodeIdx[i]);
240 bipolarOriginals.insert(cathodeIdx[i]);
241 }
242
243 // Build bipolar data rows
244 MatrixXd bipolarData(nPairs, nTimes);
245 for (int i = 0; i < nPairs; ++i) {
246 bipolarData.row(i) = data.row(anodeIdx[i]) - data.row(cathodeIdx[i]);
247 }
248
249 // Build bipolar channel info
250 QList<FiffChInfo> bipolarChs;
251 QStringList bipolarNames;
252 for (int i = 0; i < nPairs; ++i) {
253 FiffChInfo ch = info.chs[anodeIdx[i]];
254 ch.ch_name = anodes[i] + "-" + cathodes[i];
255 ch.logNo = i + 1;
256 bipolarChs.append(ch);
257 bipolarNames.append(ch.ch_name);
258 }
259
260 if (dropOriginals) {
261 // Keep only non-bipolar-original rows + append bipolar rows
262 QVector<int> keepIdx;
263 for (int i = 0; i < static_cast<int>(data.rows()); ++i) {
264 if (!bipolarOriginals.contains(i)) {
265 keepIdx.append(i);
266 }
267 }
268
269 const int nKeep = keepIdx.size();
270 MatrixXd newData(nKeep + nPairs, nTimes);
271 QList<FiffChInfo> newChs;
272 QStringList newNames;
273
274 for (int i = 0; i < nKeep; ++i) {
275 newData.row(i) = data.row(keepIdx[i]);
276 newChs.append(info.chs[keepIdx[i]]);
277 newNames.append(info.ch_names[keepIdx[i]]);
278 }
279 for (int i = 0; i < nPairs; ++i) {
280 newData.row(nKeep + i) = bipolarData.row(i);
281 newChs.append(bipolarChs[i]);
282 newNames.append(bipolarNames[i]);
283 }
284
285 // Update scan numbers
286 for (int i = 0; i < newChs.size(); ++i) {
287 newChs[i].scanNo = i + 1;
288 }
289
290 data = newData;
291 info.chs = newChs;
292 info.ch_names = newNames;
293 info.nchan = static_cast<int>(newData.rows());
294 } else {
295 // Keep all original rows, append bipolar rows at the end
296 MatrixXd newData(data.rows() + nPairs, nTimes);
297 newData.topRows(data.rows()) = data;
298 newData.bottomRows(nPairs) = bipolarData;
299
300 for (int i = 0; i < nPairs; ++i) {
301 bipolarChs[i].scanNo = info.nchan + i + 1;
302 }
303
304 info.chs.append(bipolarChs);
305 info.ch_names.append(bipolarNames);
306 info.nchan += nPairs;
307 data = newData;
308 }
309}
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:88
QList< FiffChInfo > chs