44static QVector<int> findGoodEegIndices(
const FiffInfo& info)
47 const QSet<QString> badSet(info.
bads.begin(), info.
bads.end());
48 for (
int i = 0; i < info.
nchan; ++i) {
60static QVector<int> findAllEegIndices(
const FiffInfo& info)
63 for (
int i = 0; i < info.
nchan; ++i) {
77 const QStringList& refChannels,
81 qWarning(
"setEegReference: projection mode is not yet implemented. "
82 "Falling back to direct data modification.");
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);
91 if (data.cols() == 0 || data.rows() == 0) {
96 const QVector<int> allEegIdx = findAllEegIndices(info);
97 if (allEegIdx.isEmpty()) {
98 qWarning(
"setEegReference: no EEG channels found in info.");
103 RowVectorXd refSignal;
105 const bool useAverage = refChannels.isEmpty() || (refChannels.size() == 1 && refChannels.first().compare(QLatin1String(
"average"), Qt::CaseInsensitive) == 0);
109 const QVector<int> goodEegIdx = findGoodEegIndices(info);
110 if (goodEegIdx.isEmpty()) {
111 qWarning(
"setEegReference: all EEG channels are marked as bad.");
114 refSignal = RowVectorXd::Zero(data.cols());
115 for (
int idx : goodEegIdx) {
116 refSignal += data.row(idx);
118 refSignal /=
static_cast<double>(goodEegIdx.size());
122 for (
const QString& name : refChannels) {
123 int idx = info.
ch_names.indexOf(name);
125 qWarning(
"setEegReference: reference channel '%s' not found.", qPrintable(name));
130 refSignal = RowVectorXd::Zero(data.cols());
131 for (
int idx : refIdx) {
132 refSignal += data.row(idx);
134 refSignal /=
static_cast<double>(refIdx.size());
138 for (
int idx : allEegIdx) {
139 data.row(idx) -= refSignal;
147 const QStringList& chNames)
149 if (chNames.isEmpty()) {
153 const Eigen::Index nTimes = data.cols();
154 const Eigen::Index nOldCh = data.rows();
155 const int nNew = chNames.size();
158 MatrixXd newData(nOldCh + nNew, nTimes);
159 if (nOldCh > 0 && nTimes > 0) {
160 newData.topRows(nOldCh) = data;
162 newData.bottomRows(nNew).setZero();
167 bool haveTemplate =
false;
168 for (
int i = 0; i < info.
nchan; ++i) {
170 templateCh = info.
chs[i];
176 for (
int n = 0; n < nNew; ++n) {
196 const QStringList& anodes,
197 const QStringList& cathodes,
200 if (anodes.size() != cathodes.size()) {
201 qWarning(
"setBipolarReference: anodes and cathodes must have the same length "
203 static_cast<long long>(anodes.size()),
static_cast<long long>(cathodes.size()));
207 if (anodes.isEmpty()) {
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);
217 const int nPairs = anodes.size();
218 const Eigen::Index nTimes = data.cols();
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]));
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]));
236 QSet<int> bipolarOriginals;
237 for (
int i = 0; i < nPairs; ++i) {
238 bipolarOriginals.insert(anodeIdx[i]);
239 bipolarOriginals.insert(cathodeIdx[i]);
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]);
249 QList<FiffChInfo> bipolarChs;
250 QStringList bipolarNames;
251 for (
int i = 0; i < nPairs; ++i) {
253 ch.
ch_name = anodes[i] +
"-" + cathodes[i];
255 bipolarChs.append(ch);
256 bipolarNames.append(ch.
ch_name);
261 QVector<int> keepIdx;
262 for (
int i = 0; i < static_cast<int>(data.rows()); ++i) {
263 if (!bipolarOriginals.contains(i)) {
268 const int nKeep = keepIdx.size();
269 MatrixXd newData(nKeep + nPairs, nTimes);
270 QList<FiffChInfo> newChs;
271 QStringList newNames;
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]]);
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]);
285 for (
int i = 0; i < newChs.size(); ++i) {
286 newChs[i].scanNo = i + 1;
292 info.
nchan =
static_cast<int>(newData.rows());
295 MatrixXd newData(data.rows() + nPairs, nTimes);
296 newData.topRows(data.rows()) = data;
297 newData.bottomRows(nPairs) = bipolarData;
299 for (
int i = 0; i < nPairs; ++i) {
300 bipolarChs[i].scanNo = info.
nchan + i + 1;
303 info.
chs.append(bipolarChs);
305 info.
nchan += nPairs;
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
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,...