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#include <fiff/fiff_file.h>
22
23//=============================================================================================================
24// QT INCLUDES
25//=============================================================================================================
26
27#include <QDebug>
28#include <QSet>
29
30//=============================================================================================================
31// USED NAMESPACES
32//=============================================================================================================
33
34using namespace UTILSLIB;
35using namespace FIFFLIB;
36using namespace Eigen;
37
38//=============================================================================================================
39// STATIC HELPERS
40//=============================================================================================================
41
45static QVector<int> findGoodEegIndices(const FiffInfo& info)
46{
47 QVector<int> indices;
48 const QSet<QString> badSet(info.bads.begin(), info.bads.end());
49 for (int i = 0; i < info.nchan; ++i) {
50 if (info.chs[i].kind == FIFFV_EEG_CH && !badSet.contains(info.ch_names[i])) {
51 indices.append(i);
52 }
53 }
54 return indices;
55}
56
57//=============================================================================================================
61static QVector<int> findAllEegIndices(const FiffInfo& info)
62{
63 QVector<int> indices;
64 for (int i = 0; i < info.nchan; ++i) {
65 if (info.chs[i].kind == FIFFV_EEG_CH) {
66 indices.append(i);
67 }
68 }
69 return indices;
70}
71
72//=============================================================================================================
73// FUNCTION IMPLEMENTATIONS
74//=============================================================================================================
75
76void UTILSLIB::setEegReference(MatrixXd& data,
77 FiffInfo& info,
78 const QStringList& refChannels,
79 bool projection)
80{
81 const bool useAverage = refChannels.isEmpty() || (refChannels.size() == 1 && refChannels.first().compare(QLatin1String("average"), Qt::CaseInsensitive) == 0);
82
83 if (projection) {
84 if (!useAverage) {
85 qWarning("setEegReference: projection is only supported for the average reference.");
86 return;
87 }
88 for (const FiffProj& proj : std::as_const(info.projs)) {
89 if (proj.kind == FIFFV_PROJ_ITEM_EEG_AVREF)
90 return;
91 }
92 QStringList names;
93 for (int idx : findGoodEegIndices(info))
94 names << info.ch_names[idx];
95 if (names.isEmpty()) {
96 qWarning("setEegReference: no good EEG channels for an average reference projector.");
97 return;
98 }
99 FiffNamedMatrix vec(1, static_cast<int>(names.size()), QStringList(), names,
100 MatrixXd::Constant(1, names.size(), 1.0 / std::sqrt(static_cast<double>(names.size()))));
101 info.projs.append(FiffProj(FIFFV_PROJ_ITEM_EEG_AVREF, false, QStringLiteral("Average EEG reference"), vec));
102 return;
103 }
104
105 if (data.rows() != info.nchan) {
106 qWarning("setEegReference: data row count (%lld) does not match info.nchan (%d).",
107 static_cast<long long>(data.rows()), info.nchan);
108 return;
109 }
110
111 if (data.cols() == 0 || data.rows() == 0) {
112 return;
113 }
114
115 // As mne.set_eeg_reference, bad EEG channels are neither reference nor re-referenced
116 const QVector<int> goodEegIdx = findGoodEegIndices(info);
117 if (findAllEegIndices(info).isEmpty()) {
118 qWarning("setEegReference: no EEG channels found in info.");
119 return;
120 }
121
122 // Compute reference signal
123 RowVectorXd refSignal;
124
125 if (useAverage) {
126 // Average reference: mean of all good EEG channels
127 if (goodEegIdx.isEmpty()) {
128 qWarning("setEegReference: all EEG channels are marked as bad.");
129 return;
130 }
131 refSignal = RowVectorXd::Zero(data.cols());
132 for (int idx : goodEegIdx) {
133 refSignal += data.row(idx);
134 }
135 refSignal /= static_cast<double>(goodEegIdx.size());
136 } else {
137 // Specific channel(s): mean of named channels
138 QVector<int> refIdx;
139 for (const QString& name : refChannels) {
140 int idx = info.ch_names.indexOf(name);
141 if (idx < 0) {
142 qWarning("setEegReference: reference channel '%s' not found.", qPrintable(name));
143 return;
144 }
145 refIdx.append(idx);
146 }
147 refSignal = RowVectorXd::Zero(data.cols());
148 for (int idx : refIdx) {
149 refSignal += data.row(idx);
150 }
151 refSignal /= static_cast<double>(refIdx.size());
152 }
153
154 for (int idx : goodEegIdx) {
155 data.row(idx) -= refSignal;
156 }
157}
158
159//=============================================================================================================
160
161void UTILSLIB::addReferenceChannels(MatrixXd& data,
162 FiffInfo& info,
163 const QStringList& chNames)
164{
165 if (chNames.isEmpty()) {
166 return;
167 }
168
169 const Eigen::Index nTimes = data.cols();
170 const Eigen::Index nOldCh = data.rows();
171 const int nNew = chNames.size();
172
173 // Expand data matrix with zero rows
174 MatrixXd newData(nOldCh + nNew, nTimes);
175 if (nOldCh > 0 && nTimes > 0) {
176 newData.topRows(nOldCh) = data;
177 }
178 newData.bottomRows(nNew).setZero();
179 data = newData;
180
181 // Find a template EEG channel for defaults
182 FiffChInfo templateCh;
183 bool haveTemplate = false;
184 for (int i = 0; i < info.nchan; ++i) {
185 if (info.chs[i].kind == FIFFV_EEG_CH) {
186 templateCh = info.chs[i];
187 haveTemplate = true;
188 break;
189 }
190 }
191
192 for (int n = 0; n < nNew; ++n) {
193 FiffChInfo ch;
194 if (haveTemplate) {
195 ch = templateCh;
196 }
197 ch.ch_name = chNames[n];
198 ch.kind = FIFFV_EEG_CH;
199 ch.scanNo = info.nchan + n + 1;
200 ch.logNo = ch.scanNo;
201
202 info.chs.append(ch);
203 info.ch_names.append(chNames[n]);
204 }
205 info.nchan += nNew;
206}
207
208//=============================================================================================================
209
210void UTILSLIB::setBipolarReference(MatrixXd& data,
211 FiffInfo& info,
212 const QStringList& anodes,
213 const QStringList& cathodes,
214 bool dropOriginals)
215{
216 if (anodes.size() != cathodes.size()) {
217 qWarning("setBipolarReference: anodes and cathodes must have the same length "
218 "(%lld vs %lld).",
219 static_cast<long long>(anodes.size()), static_cast<long long>(cathodes.size()));
220 return;
221 }
222
223 if (anodes.isEmpty()) {
224 return;
225 }
226
227 if (data.rows() != info.nchan) {
228 qWarning("setBipolarReference: data row count (%lld) does not match info.nchan (%d).",
229 static_cast<long long>(data.rows()), info.nchan);
230 return;
231 }
232
233 const int nPairs = anodes.size();
234 const Eigen::Index nTimes = data.cols();
235
236 // Resolve anode and cathode indices
237 QVector<int> anodeIdx(nPairs), cathodeIdx(nPairs);
238 for (int i = 0; i < nPairs; ++i) {
239 anodeIdx[i] = info.ch_names.indexOf(anodes[i]);
240 if (anodeIdx[i] < 0) {
241 qWarning("setBipolarReference: anode channel '%s' not found.", qPrintable(anodes[i]));
242 return;
243 }
244 cathodeIdx[i] = info.ch_names.indexOf(cathodes[i]);
245 if (cathodeIdx[i] < 0) {
246 qWarning("setBipolarReference: cathode channel '%s' not found.", qPrintable(cathodes[i]));
247 return;
248 }
249 }
250
251 // Collect the set of original channels involved
252 QSet<int> bipolarOriginals;
253 for (int i = 0; i < nPairs; ++i) {
254 bipolarOriginals.insert(anodeIdx[i]);
255 bipolarOriginals.insert(cathodeIdx[i]);
256 }
257
258 // Build bipolar data rows
259 MatrixXd bipolarData(nPairs, nTimes);
260 for (int i = 0; i < nPairs; ++i) {
261 bipolarData.row(i) = data.row(anodeIdx[i]) - data.row(cathodeIdx[i]);
262 }
263
264 // Build bipolar channel info
265 QList<FiffChInfo> bipolarChs;
266 QStringList bipolarNames;
267 for (int i = 0; i < nPairs; ++i) {
268 FiffChInfo ch = info.chs[anodeIdx[i]];
269 ch.ch_name = anodes[i] + "-" + cathodes[i];
270 ch.logNo = i + 1;
271 bipolarChs.append(ch);
272 bipolarNames.append(ch.ch_name);
273 }
274
275 if (dropOriginals) {
276 // Keep only non-bipolar-original rows + append bipolar rows
277 QVector<int> keepIdx;
278 for (int i = 0; i < static_cast<int>(data.rows()); ++i) {
279 if (!bipolarOriginals.contains(i)) {
280 keepIdx.append(i);
281 }
282 }
283
284 const int nKeep = keepIdx.size();
285 MatrixXd newData(nKeep + nPairs, nTimes);
286 QList<FiffChInfo> newChs;
287 QStringList newNames;
288
289 for (int i = 0; i < nKeep; ++i) {
290 newData.row(i) = data.row(keepIdx[i]);
291 newChs.append(info.chs[keepIdx[i]]);
292 newNames.append(info.ch_names[keepIdx[i]]);
293 }
294 for (int i = 0; i < nPairs; ++i) {
295 newData.row(nKeep + i) = bipolarData.row(i);
296 newChs.append(bipolarChs[i]);
297 newNames.append(bipolarNames[i]);
298 }
299
300 // Update scan numbers
301 for (int i = 0; i < newChs.size(); ++i) {
302 newChs[i].scanNo = i + 1;
303 }
304
305 data = newData;
306 info.chs = newChs;
307 info.ch_names = newNames;
308 info.nchan = static_cast<int>(newData.rows());
309 } else {
310 // Keep all original rows, append bipolar rows at the end
311 MatrixXd newData(data.rows() + nPairs, nTimes);
312 newData.topRows(data.rows()) = data;
313 newData.bottomRows(nPairs) = bipolarData;
314
315 for (int i = 0; i < nPairs; ++i) {
316 bipolarChs[i].scanNo = info.nchan + i + 1;
317 }
318
319 info.chs.append(bipolarChs);
320 info.ch_names.append(bipolarNames);
321 info.nchan += nPairs;
322 data = newData;
323 }
324}
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...
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
constexpr auto FIFFV_PROJ_ITEM_EEG_AVREF
Definition fiff_file.h:819
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, 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< FiffProj > projs
Definition fiff_info.h:292
QList< FiffChInfo > chs
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
Single SSP projection item: kind, active flag, desired flag and the named projection vector matrix.
Definition fiff_proj.h:82