v2.0.0
Loading...
Searching...
No Matches
fiff_proj.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
25#include "fiff_proj.h"
26#include "fiff_raw_data.h"
27#include "fiff_constants.h"
28#include "fiff_file.h"
29#include <stdio.h>
30#include <cmath>
31#include <math/linalg.h>
32
33//=============================================================================================================
34// EIGEN INCLUDES
35//=============================================================================================================
36
37#include <Eigen/SVD>
38#include <QDebug>
39
40#include <stdexcept>
41//=============================================================================================================
42// USED NAMESPACES
43//=============================================================================================================
44
45using namespace UTILSLIB;
46using namespace FIFFLIB;
47using namespace Eigen;
48
49//=============================================================================================================
50// DEFINE MEMBER METHODS
51//=============================================================================================================
52
54: kind(-1)
55, active(false)
56, desc("")
58{
59}
60
61//=============================================================================================================
62
63FiffProj::FiffProj(const FiffProj& p_FiffProj)
64: kind(p_FiffProj.kind)
65, active(p_FiffProj.active)
66, desc(p_FiffProj.desc)
67, data(p_FiffProj.data)
68{
69}
70
71//=============================================================================================================
72
73FiffProj::FiffProj(fiff_int_t p_kind, bool p_active, QString p_desc, FiffNamedMatrix& p_data)
74: kind(p_kind)
75, active(p_active)
76, desc(p_desc)
77, data(new FiffNamedMatrix(p_data))
78{
79}
80
81//=============================================================================================================
82
86
87//=============================================================================================================
88
89void FiffProj::activate_projs(QList<FiffProj>& p_qListFiffProj)
90{
91 // Activate the projection items
92 QList<FiffProj>::Iterator it;
93 for (it = p_qListFiffProj.begin(); it != p_qListFiffProj.end(); ++it)
94 it->active = true;
95
96 qInfo("\t%lld projection items activated.\n", static_cast<long long>(p_qListFiffProj.size()));
97}
98
99//=============================================================================================================
100
101fiff_int_t FiffProj::make_projector(const QList<FiffProj>& projs, const QStringList& ch_names, MatrixXd& proj, const QStringList& bads, MatrixXd& U)
102{
103 fiff_int_t nchan = ch_names.size();
104 if (nchan == 0) {
105 throw std::invalid_argument("No channel names specified");
106 }
107
108 // if(proj)
109 // delete proj;
110 proj = MatrixXd::Identity(nchan, nchan);
111 fiff_int_t nproj = 0;
112 U = MatrixXd();
113
114 //
115 // Check trivial cases first
116 //
117 if (projs.size() == 0)
118 return 0;
119
120 fiff_int_t nvec = 0;
121 fiff_int_t k, l;
122 for (k = 0; k < projs.size(); ++k) {
123 if (projs[k].active) {
124 ++nproj;
125 nvec += projs[k].data->nrow;
126 }
127 }
128
129 if (nproj == 0) {
130 qWarning("FiffProj::make_projector - No projectors nproj=0\n");
131 return 0;
132 }
133
134 if (nvec <= 0) {
135 qWarning("FiffProj::make_projector - No rows in projector matrices found nvec<=0\n");
136 return 0;
137 }
138
139 //
140 // Pick the appropriate entries
141 //
142 MatrixXd vecs = MatrixXd::Zero(nchan, nvec);
143 nvec = 0;
144 fiff_int_t nonzero = 0;
145 qint32 p, c, i, j, v;
146 double onesize;
147 bool isBad = false;
148 RowVectorXi sel(nchan);
149 RowVectorXi vecSel(nchan);
150 sel.setConstant(-1);
151 vecSel.setConstant(-1);
152 for (k = 0; k < projs.size(); ++k) {
153 if (projs[k].active) {
154 FiffProj one = projs[k];
155
156 QMap<QString, int> uniqueMap;
157 for (l = 0; l < one.data->col_names.size(); ++l)
158 uniqueMap[one.data->col_names[l]] = 0;
159
160 if (one.data->col_names.size() != uniqueMap.keys().size()) {
161 qWarning("Channel name list in projection item %d contains duplicate items", k);
162 return 0;
163 }
164
165 //
166 // Get the two selection vectors to pick correct elements from
167 // the projection vectors omitting bad channels
168 //
169 sel.resize(nchan);
170 vecSel.resize(nchan);
171 sel.setConstant(-1);
172 vecSel.setConstant(-1);
173 p = 0;
174 for (c = 0; c < nchan; ++c) {
175 for (i = 0; i < one.data->col_names.size(); ++i) {
176 if (QString::compare(ch_names.at(c), one.data->col_names[i]) == 0) {
177 isBad = false;
178 for (j = 0; j < bads.size(); ++j) {
179 if (QString::compare(ch_names.at(c), bads.at(j)) == 0) {
180 isBad = true;
181 }
182 }
183
184 if (!isBad && sel[p] != c) {
185 sel[p] = c;
186 vecSel[p] = i;
187 ++p;
188 }
189 }
190 }
191 }
192 sel.conservativeResize(p);
193 vecSel.conservativeResize(p);
194 //
195 // If there is something to pick, pickit
196 //
197 if (sel.cols() > 0)
198 for (v = 0; v < one.data->nrow; ++v)
199 for (i = 0; i < p; ++i)
200 vecs(sel[i], nvec + v) = one.data->data(v, vecSel[i]);
201
202 //
203 // Rescale for more straightforward detection of small singular values
204 //
205 for (v = 0; v < one.data->nrow; ++v) {
206 onesize = sqrt((vecs.col(nvec + v).transpose() * vecs.col(nvec + v))(0, 0));
207 if (onesize > 0.0) {
208 vecs.col(nvec + v) = vecs.col(nvec + v) / onesize;
209 ++nonzero;
210 }
211 }
212 nvec += one.data->nrow;
213 }
214 }
215 //
216 // Check whether all of the vectors are exactly zero
217 //
218 if (nonzero == 0)
219 return 0;
220
221 //
222 // Reorthogonalize the vectors
223 //
224 JacobiSVD<MatrixXd> svd(vecs.block(0, 0, vecs.rows(), nvec), ComputeFullU);
225 //Sort singular values and singular vectors
226 VectorXd S = svd.singularValues();
227 MatrixXd t_U = svd.matrixU();
229
230 //
231 // Throw away the linearly dependent guys
232 //
233 nproj = 0;
234 for (k = 0; k < S.size(); ++k)
235 if (S[k] / S[0] > 1e-2)
236 ++nproj;
237
238 U = t_U.block(0, 0, t_U.rows(), nproj);
239
240 //
241 // Here is the celebrated result
242 //
243 proj -= U * U.transpose();
244
245 return nproj;
246}
247
248//=============================================================================================================
249
250QList<FiffProj> FiffProj::compute_from_raw(const FiffRawData& raw,
251 const MatrixXi& events,
252 int eventCode,
253 float tmin,
254 float tmax,
255 int nGrad,
256 int nMag,
257 int nEeg,
258 const QMap<QString, double>& mapReject)
259{
260 QList<FiffProj> projs;
261 float sfreq = raw.info.sfreq;
262 int nchan = raw.info.nchan;
263
264 int minSamp = static_cast<int>(std::round(tmin * sfreq));
265 int maxSamp = static_cast<int>(std::round(tmax * sfreq));
266 int ns = maxSamp - minSamp + 1;
267
268 if (ns <= 0) {
269 qWarning() << "[FiffProj::compute_from_raw] Invalid time window.";
270 return projs;
271 }
272
273 // Classify channels
274 QList<int> gradIdx, magIdx, eegIdx;
275 for (int k = 0; k < nchan; ++k) {
276 if (raw.info.bads.contains(raw.info.ch_names[k]))
277 continue;
278 if (raw.info.chs[k].kind == FIFFV_MEG_CH) {
279 if (raw.info.chs[k].unit == FIFF_UNIT_T)
280 magIdx.append(k);
281 else
282 gradIdx.append(k);
283 } else if (raw.info.chs[k].kind == FIFFV_EEG_CH) {
284 eegIdx.append(k);
285 }
286 }
287
288 // Collect matching epochs
289 QList<MatrixXd> epochs;
290 double gradReject = mapReject.value("grad", 0.0);
291 double magReject = mapReject.value("mag", 0.0);
292 double eegReject = mapReject.value("eeg", 0.0);
293
294 for (int k = 0; k < events.rows(); ++k) {
295 if (events(k, 1) != 0 || events(k, 2) != eventCode)
296 continue;
297
298 int evSample = events(k, 0);
299 int epochStart = evSample + minSamp;
300 int epochEnd = evSample + maxSamp;
301
302 if (epochStart < raw.first_samp || epochEnd > raw.last_samp)
303 continue;
304
305 MatrixXd epochData, epochTimes;
306 if (!raw.read_raw_segment(epochData, epochTimes, epochStart, epochEnd))
307 continue;
308
309 // Simple peak-to-peak rejection
310 bool ok = true;
311 for (int c = 0; c < nchan && ok; ++c) {
312 if (raw.info.bads.contains(raw.info.ch_names[c]))
313 continue;
314 double pp = epochData.row(c).maxCoeff() - epochData.row(c).minCoeff();
315 if (raw.info.chs[c].kind == FIFFV_MEG_CH) {
316 if (raw.info.chs[c].unit == FIFF_UNIT_T && magReject > 0 && pp > magReject)
317 ok = false;
318 else if (raw.info.chs[c].unit != FIFF_UNIT_T && gradReject > 0 && pp > gradReject)
319 ok = false;
320 } else if (raw.info.chs[c].kind == FIFFV_EEG_CH && eegReject > 0 && pp > eegReject) {
321 ok = false;
322 }
323 }
324 if (!ok)
325 continue;
326
327 epochs.append(epochData);
328 }
329
330 if (epochs.isEmpty()) {
331 qWarning() << "[FiffProj::compute_from_raw] No valid epochs found for event" << eventCode;
332 return projs;
333 }
334
335 qInfo() << "[FiffProj::compute_from_raw]" << epochs.size() << "epochs collected for event" << eventCode;
336
337 // Lambda: compute SVD-based projectors for a channel subset
338 auto computeProjForChannels = [&](const QList<int>& chIdx, int nVec, const QString& desc) {
339 if (nVec <= 0 || chIdx.isEmpty())
340 return;
341
342 int nRows = epochs.size() * ns;
343 MatrixXd dataMat(nRows, chIdx.size());
344
345 for (int e = 0; e < epochs.size(); ++e) {
346 for (int c = 0; c < chIdx.size(); ++c) {
347 dataMat.block(e * ns, c, ns, 1) = epochs[e].row(chIdx[c]).transpose();
348 }
349 }
350
351 // SVD of the uncentred data, i.e. the eigenvectors of the second-moment
352 // matrix. MNE-C (compute_cov_raw_epoch with remove_sample_mean = FALSE)
353 // and mne.compute_proj_epochs both do this; removing the mean first
354 // yields a different, non-matching subspace.
355 Eigen::JacobiSVD<MatrixXd> svd(dataMat, Eigen::ComputeThinV);
356 MatrixXd V = svd.matrixV();
357
358 int nComp = qMin(nVec, static_cast<int>(V.cols()));
359
360 for (int v = 0; v < nComp; ++v) {
361 FiffProj proj;
363 proj.active = false;
364 proj.desc = QString("%1-v%2").arg(desc).arg(v + 1);
365
366 FiffNamedMatrix::SDPtr namedMatrix(new FiffNamedMatrix());
367 namedMatrix->nrow = 1;
368 namedMatrix->ncol = nchan;
369 namedMatrix->row_names.clear();
370 namedMatrix->col_names = raw.info.ch_names;
371 namedMatrix->data = MatrixXd::Zero(1, nchan);
372
373 for (int c = 0; c < chIdx.size(); ++c) {
374 namedMatrix->data(0, chIdx[c]) = V(c, v);
375 }
376
377 proj.data = namedMatrix;
378 projs.append(proj);
379 }
380
381 qInfo() << "[FiffProj::compute_from_raw] Created" << nComp << desc << "projection vector(s)";
382 };
383
384 computeProjForChannels(gradIdx, nGrad, "PCA-grad");
385 computeProjForChannels(magIdx, nMag, "PCA-mag");
386 computeProjForChannels(eegIdx, nEeg, "PCA-eeg");
387
388 return projs;
389}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_EEG_CH
#define FIFFV_MEG_CH
#define FIFF_UNIT_T
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFV_PROJ_ITEM_FIELD
Definition fiff_file.h:816
Eigen::Matrix3f S
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
SSP projection item: a named projection vector set with active/desired flags, parsed from FIFFB_PROJ_...
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
Static linear-algebra helpers: SVD-based conditioning, block-diagonal assembly, sorted index pairs.
FIFF file I/O, in-memory data structures and high-level readers/writers.
qint32 fiff_int_t
Definition fiff_types.h:86
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
QList< FiffChInfo > chs
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
QSharedDataPointer< FiffNamedMatrix > SDPtr
FiffNamedMatrix::SDPtr data
Definition fiff_proj.h:210
fiff_int_t kind
Definition fiff_proj.h:206
static QList< FiffProj > compute_from_raw(const FiffRawData &raw, const Eigen::MatrixXi &events, int eventCode, float tmin, float tmax, int nGrad, int nMag, int nEeg, const QMap< QString, double > &mapReject=QMap< QString, double >())
static fiff_int_t make_projector(const QList< FiffProj > &projs, const QStringList &ch_names, Eigen::MatrixXd &proj, const QStringList &bads=defaultQStringList, Eigen::MatrixXd &U=defaultMatrixXd)
static void activate_projs(QList< FiffProj > &p_qListFiffProj)
Definition fiff_proj.cpp:89
Continuous FIFF raw recording: FiffInfo plus a random-access directory of FIFF_DATA_BUFFER tags.
bool read_raw_segment(Eigen::MatrixXd &data, Eigen::MatrixXd &times, fiff_int_t from=-1, fiff_int_t to=-1, const Eigen::RowVectorXi &sel=defaultRowVectorXi, bool do_debug=false) const
static Eigen::VectorXi sort(Eigen::Matrix< T, Eigen::Dynamic, 1 > &v, bool desc=true)
Definition linalg.h:298