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()
106 || (refChannels.size() == 1
107 && refChannels.first().compare(QLatin1String(
"average"), Qt::CaseInsensitive) == 0);
111 const QVector<int> goodEegIdx = findGoodEegIndices(info);
112 if (goodEegIdx.isEmpty()) {
113 qWarning(
"setEegReference: all EEG channels are marked as bad.");
116 refSignal = RowVectorXd::Zero(data.cols());
117 for (
int idx : goodEegIdx) {
118 refSignal += data.row(idx);
120 refSignal /=
static_cast<double>(goodEegIdx.size());
124 for (
const QString& name : refChannels) {
125 int idx = info.
ch_names.indexOf(name);
127 qWarning(
"setEegReference: reference channel '%s' not found.", qPrintable(name));
132 refSignal = RowVectorXd::Zero(data.cols());
133 for (
int idx : refIdx) {
134 refSignal += data.row(idx);
136 refSignal /=
static_cast<double>(refIdx.size());
140 for (
int idx : allEegIdx) {
141 data.row(idx) -= refSignal;
149 const QStringList& chNames)
151 if (chNames.isEmpty()) {
155 const Eigen::Index nTimes = data.cols();
156 const Eigen::Index nOldCh = data.rows();
157 const int nNew = chNames.size();
160 MatrixXd newData(nOldCh + nNew, nTimes);
161 if (nOldCh > 0 && nTimes > 0) {
162 newData.topRows(nOldCh) = data;
164 newData.bottomRows(nNew).setZero();
169 bool haveTemplate =
false;
170 for (
int i = 0; i < info.
nchan; ++i) {
172 templateCh = info.
chs[i];
178 for (
int n = 0; n < nNew; ++n) {
198 const QStringList& anodes,
199 const QStringList& cathodes,
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()));
208 if (anodes.isEmpty()) {
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);
218 const int nPairs = anodes.size();
219 const Eigen::Index nTimes = data.cols();
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]));
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]));
237 QSet<int> bipolarOriginals;
238 for (
int i = 0; i < nPairs; ++i) {
239 bipolarOriginals.insert(anodeIdx[i]);
240 bipolarOriginals.insert(cathodeIdx[i]);
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]);
250 QList<FiffChInfo> bipolarChs;
251 QStringList bipolarNames;
252 for (
int i = 0; i < nPairs; ++i) {
254 ch.
ch_name = anodes[i] +
"-" + cathodes[i];
256 bipolarChs.append(ch);
257 bipolarNames.append(ch.
ch_name);
262 QVector<int> keepIdx;
263 for (
int i = 0; i < static_cast<int>(data.rows()); ++i) {
264 if (!bipolarOriginals.contains(i)) {
269 const int nKeep = keepIdx.size();
270 MatrixXd newData(nKeep + nPairs, nTimes);
271 QList<FiffChInfo> newChs;
272 QStringList newNames;
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]]);
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]);
286 for (
int i = 0; i < newChs.size(); ++i) {
287 newChs[i].scanNo = i + 1;
293 info.
nchan =
static_cast<int>(newData.rows());
296 MatrixXd newData(data.rows() + nPairs, nTimes);
297 newData.topRows(data.rows()) = data;
298 newData.bottomRows(nPairs) = bipolarData;
300 for (
int i = 0; i < nPairs; ++i) {
301 bipolarChs[i].scanNo = info.
nchan + i + 1;
304 info.
chs.append(bipolarChs);
306 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,...