45static QVector<int> findGoodEegIndices(
const FiffInfo& info)
48 const QSet<QString> badSet(info.
bads.begin(), info.
bads.end());
49 for (
int i = 0; i < info.
nchan; ++i) {
61static QVector<int> findAllEegIndices(
const FiffInfo& info)
64 for (
int i = 0; i < info.
nchan; ++i) {
78 const QStringList& refChannels,
81 const bool useAverage = refChannels.isEmpty() || (refChannels.size() == 1 && refChannels.first().compare(QLatin1String(
"average"), Qt::CaseInsensitive) == 0);
85 qWarning(
"setEegReference: projection is only supported for the average reference.");
93 for (
int idx : findGoodEegIndices(info))
95 if (names.isEmpty()) {
96 qWarning(
"setEegReference: no good EEG channels for an average reference projector.");
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()))));
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);
111 if (data.cols() == 0 || data.rows() == 0) {
116 const QVector<int> goodEegIdx = findGoodEegIndices(info);
117 if (findAllEegIndices(info).isEmpty()) {
118 qWarning(
"setEegReference: no EEG channels found in info.");
123 RowVectorXd refSignal;
127 if (goodEegIdx.isEmpty()) {
128 qWarning(
"setEegReference: all EEG channels are marked as bad.");
131 refSignal = RowVectorXd::Zero(data.cols());
132 for (
int idx : goodEegIdx) {
133 refSignal += data.row(idx);
135 refSignal /=
static_cast<double>(goodEegIdx.size());
139 for (
const QString& name : refChannels) {
140 int idx = info.
ch_names.indexOf(name);
142 qWarning(
"setEegReference: reference channel '%s' not found.", qPrintable(name));
147 refSignal = RowVectorXd::Zero(data.cols());
148 for (
int idx : refIdx) {
149 refSignal += data.row(idx);
151 refSignal /=
static_cast<double>(refIdx.size());
154 for (
int idx : goodEegIdx) {
155 data.row(idx) -= refSignal;
163 const QStringList& chNames)
165 if (chNames.isEmpty()) {
169 const Eigen::Index nTimes = data.cols();
170 const Eigen::Index nOldCh = data.rows();
171 const int nNew = chNames.size();
174 MatrixXd newData(nOldCh + nNew, nTimes);
175 if (nOldCh > 0 && nTimes > 0) {
176 newData.topRows(nOldCh) = data;
178 newData.bottomRows(nNew).setZero();
183 bool haveTemplate =
false;
184 for (
int i = 0; i < info.
nchan; ++i) {
186 templateCh = info.
chs[i];
192 for (
int n = 0; n < nNew; ++n) {
212 const QStringList& anodes,
213 const QStringList& cathodes,
216 if (anodes.size() != cathodes.size()) {
217 qWarning(
"setBipolarReference: anodes and cathodes must have the same length "
219 static_cast<long long>(anodes.size()),
static_cast<long long>(cathodes.size()));
223 if (anodes.isEmpty()) {
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);
233 const int nPairs = anodes.size();
234 const Eigen::Index nTimes = data.cols();
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]));
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]));
252 QSet<int> bipolarOriginals;
253 for (
int i = 0; i < nPairs; ++i) {
254 bipolarOriginals.insert(anodeIdx[i]);
255 bipolarOriginals.insert(cathodeIdx[i]);
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]);
265 QList<FiffChInfo> bipolarChs;
266 QStringList bipolarNames;
267 for (
int i = 0; i < nPairs; ++i) {
269 ch.
ch_name = anodes[i] +
"-" + cathodes[i];
271 bipolarChs.append(ch);
272 bipolarNames.append(ch.
ch_name);
277 QVector<int> keepIdx;
278 for (
int i = 0; i < static_cast<int>(data.rows()); ++i) {
279 if (!bipolarOriginals.contains(i)) {
284 const int nKeep = keepIdx.size();
285 MatrixXd newData(nKeep + nPairs, nTimes);
286 QList<FiffChInfo> newChs;
287 QStringList newNames;
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]]);
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]);
301 for (
int i = 0; i < newChs.size(); ++i) {
302 newChs[i].scanNo = i + 1;
308 info.
nchan =
static_cast<int>(newData.rows());
311 MatrixXd newData(data.rows() + nPairs, nTimes);
312 newData.topRows(data.rows()) = data;
313 newData.bottomRows(nPairs) = bipolarData;
315 for (
int i = 0; i < nPairs; ++i) {
316 bipolarChs[i].scanNo = info.
nchan + i + 1;
319 info.
chs.append(bipolarChs);
321 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...
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
constexpr auto FIFFV_PROJ_ITEM_EEG_AVREF
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,...
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.