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//=============================================================================================================
63
64FiffProj::FiffProj(const FiffProj& p_FiffProj)
65: kind(p_FiffProj.kind)
66, active(p_FiffProj.active)
67, desc(p_FiffProj.desc)
68, data(p_FiffProj.data)
69{
70
71}
72
73//=============================================================================================================
74
75FiffProj::FiffProj( fiff_int_t p_kind, bool p_active, QString p_desc, FiffNamedMatrix& p_data)
76: kind(p_kind)
77, active(p_active)
78, desc(p_desc)
79, data(new FiffNamedMatrix(p_data))
80{
81
82}
83
84//=============================================================================================================
85
90
91//=============================================================================================================
92
93void FiffProj::activate_projs(QList<FiffProj> &p_qListFiffProj)
94{
95 // Activate the projection items
96 QList<FiffProj>::Iterator it;
97 for(it = p_qListFiffProj.begin(); it != p_qListFiffProj.end(); ++it)
98 it->active = true;
99
100 qInfo("\t%lld projection items activated.\n", p_qListFiffProj.size());
101}
102
103//=============================================================================================================
104
105fiff_int_t FiffProj::make_projector(const QList<FiffProj>& projs, const QStringList& ch_names, MatrixXd& proj, const QStringList& bads, MatrixXd& U)
106{
107 fiff_int_t nchan = ch_names.size();
108 if (nchan == 0)
109 {
110 throw std::invalid_argument("No channel names specified");
111 }
112
113// if(proj)
114// delete proj;
115 proj = MatrixXd::Identity(nchan,nchan);
116 fiff_int_t nproj = 0;
117 U = MatrixXd();
118
119 //
120 // Check trivial cases first
121 //
122 if (projs.size() == 0)
123 return 0;
124
125 fiff_int_t nvec = 0;
126 fiff_int_t k, l;
127 for (k = 0; k < projs.size(); ++k)
128 {
129 if (projs[k].active)
130 {
131 ++nproj;
132 nvec += projs[k].data->nrow;
133 }
134 }
135
136 if (nproj == 0) {
137 qWarning("FiffProj::make_projector - No projectors nproj=0\n");
138 return 0;
139 }
140
141 if (nvec <= 0) {
142 qWarning("FiffProj::make_projector - No rows in projector matrices found nvec<=0\n");
143 return 0;
144 }
145
146 //
147 // Pick the appropriate entries
148 //
149 MatrixXd vecs = MatrixXd::Zero(nchan,nvec);
150 nvec = 0;
151 fiff_int_t nonzero = 0;
152 qint32 p, c, i, j, v;
153 double onesize;
154 bool isBad = false;
155 RowVectorXi sel(nchan);
156 RowVectorXi vecSel(nchan);
157 sel.setConstant(-1);
158 vecSel.setConstant(-1);
159 for (k = 0; k < projs.size(); ++k)
160 {
161 if (projs[k].active)
162 {
163 FiffProj one = projs[k];
164
165 QMap<QString, int> uniqueMap;
166 for(l = 0; l < one.data->col_names.size(); ++l)
167 uniqueMap[one.data->col_names[l] ] = 0;
168
169 if (one.data->col_names.size() != uniqueMap.keys().size())
170 {
171 qWarning("Channel name list in projection item %d contains duplicate items",k);
172 return 0;
173 }
174
175 //
176 // Get the two selection vectors to pick correct elements from
177 // the projection vectors omitting bad channels
178 //
179 sel.resize(nchan);
180 vecSel.resize(nchan);
181 sel.setConstant(-1);
182 vecSel.setConstant(-1);
183 p = 0;
184 for (c = 0; c < nchan; ++c)
185 {
186 for (i = 0; i < one.data->col_names.size(); ++i)
187 {
188 if (QString::compare(ch_names.at(c),one.data->col_names[i]) == 0)
189 {
190 isBad = false;
191 for (j = 0; j < bads.size(); ++j)
192 {
193 if (QString::compare(ch_names.at(c),bads.at(j)) == 0)
194 {
195 isBad = true;
196 }
197 }
198
199 if (!isBad && sel[p] != c)
200 {
201 sel[p] = c;
202 vecSel[p] = i;
203 ++p;
204 }
205
206 }
207 }
208 }
209 sel.conservativeResize(p);
210 vecSel.conservativeResize(p);
211 //
212 // If there is something to pick, pickit
213 //
214 if (sel.cols() > 0)
215 for (v = 0; v < one.data->nrow; ++v)
216 for (i = 0; i < p; ++i)
217 vecs(sel[i],nvec+v) = one.data->data(v,vecSel[i]);
218
219 //
220 // Rescale for more straightforward detection of small singular values
221 //
222 for (v = 0; v < one.data->nrow; ++v)
223 {
224 onesize = sqrt((vecs.col(nvec+v).transpose()*vecs.col(nvec+v))(0,0));
225 if (onesize > 0.0)
226 {
227 vecs.col(nvec+v) = vecs.col(nvec+v)/onesize;
228 ++nonzero;
229 }
230 }
231 nvec += one.data->nrow;
232 }
233 }
234 //
235 // Check whether all of the vectors are exactly zero
236 //
237 if (nonzero == 0)
238 return 0;
239
240 //
241 // Reorthogonalize the vectors
242 //
243 JacobiSVD<MatrixXd> svd(vecs.block(0,0,vecs.rows(),nvec), ComputeFullU);
244 //Sort singular values and singular vectors
245 VectorXd S = svd.singularValues();
246 MatrixXd t_U = svd.matrixU();
248
249 //
250 // Throw away the linearly dependent guys
251 //
252 nproj = 0;
253 for(k = 0; k < S.size(); ++k)
254 if (S[k]/S[0] > 1e-2)
255 ++nproj;
256
257 U = t_U.block(0, 0, t_U.rows(), nproj);
258
259 //
260 // Here is the celebrated result
261 //
262 proj -= U*U.transpose();
263
264 return nproj;
265}
266
267//=============================================================================================================
268
269QList<FiffProj> FiffProj::compute_from_raw(const FiffRawData &raw,
270 const MatrixXi &events,
271 int eventCode,
272 float tmin,
273 float tmax,
274 int nGrad,
275 int nMag,
276 int nEeg,
277 const QMap<QString,double> &mapReject)
278{
279 QList<FiffProj> projs;
280 float sfreq = raw.info.sfreq;
281 int nchan = raw.info.nchan;
282
283 int minSamp = static_cast<int>(std::round(tmin * sfreq));
284 int maxSamp = static_cast<int>(std::round(tmax * sfreq));
285 int ns = maxSamp - minSamp + 1;
286
287 if (ns <= 0) {
288 qWarning() << "[FiffProj::compute_from_raw] Invalid time window.";
289 return projs;
290 }
291
292 // Classify channels
293 QList<int> gradIdx, magIdx, eegIdx;
294 for (int k = 0; k < nchan; ++k) {
295 if (raw.info.bads.contains(raw.info.ch_names[k]))
296 continue;
297 if (raw.info.chs[k].kind == FIFFV_MEG_CH) {
298 if (raw.info.chs[k].unit == FIFF_UNIT_T)
299 magIdx.append(k);
300 else
301 gradIdx.append(k);
302 } else if (raw.info.chs[k].kind == FIFFV_EEG_CH) {
303 eegIdx.append(k);
304 }
305 }
306
307 // Collect matching epochs
308 QList<MatrixXd> epochs;
309 double gradReject = mapReject.value("grad", 0.0);
310 double magReject = mapReject.value("mag", 0.0);
311 double eegReject = mapReject.value("eeg", 0.0);
312
313 for (int k = 0; k < events.rows(); ++k) {
314 if (events(k, 1) != 0 || events(k, 2) != eventCode)
315 continue;
316
317 int evSample = events(k, 0);
318 int epochStart = evSample + minSamp;
319 int epochEnd = evSample + maxSamp;
320
321 if (epochStart < raw.first_samp || epochEnd > raw.last_samp)
322 continue;
323
324 MatrixXd epochData, epochTimes;
325 if (!raw.read_raw_segment(epochData, epochTimes, epochStart, epochEnd))
326 continue;
327
328 // Simple peak-to-peak rejection
329 bool ok = true;
330 for (int c = 0; c < nchan && ok; ++c) {
331 if (raw.info.bads.contains(raw.info.ch_names[c]))
332 continue;
333 double pp = epochData.row(c).maxCoeff() - epochData.row(c).minCoeff();
334 if (raw.info.chs[c].kind == FIFFV_MEG_CH) {
335 if (raw.info.chs[c].unit == FIFF_UNIT_T && magReject > 0 && pp > magReject)
336 ok = false;
337 else if (raw.info.chs[c].unit != FIFF_UNIT_T && gradReject > 0 && pp > gradReject)
338 ok = false;
339 } else if (raw.info.chs[c].kind == FIFFV_EEG_CH && eegReject > 0 && pp > eegReject) {
340 ok = false;
341 }
342 }
343 if (!ok) continue;
344
345 epochs.append(epochData);
346 }
347
348 if (epochs.isEmpty()) {
349 qWarning() << "[FiffProj::compute_from_raw] No valid epochs found for event" << eventCode;
350 return projs;
351 }
352
353 qInfo() << "[FiffProj::compute_from_raw]" << epochs.size() << "epochs collected for event" << eventCode;
354
355 // Lambda: compute SVD-based projectors for a channel subset
356 auto computeProjForChannels = [&](const QList<int> &chIdx, int nVec, const QString &desc) {
357 if (nVec <= 0 || chIdx.isEmpty())
358 return;
359
360 int nRows = epochs.size() * ns;
361 MatrixXd dataMat(nRows, chIdx.size());
362
363 for (int e = 0; e < epochs.size(); ++e) {
364 for (int c = 0; c < chIdx.size(); ++c) {
365 dataMat.block(e * ns, c, ns, 1) = epochs[e].row(chIdx[c]).transpose();
366 }
367 }
368
369 // Remove column mean
370 VectorXd colMean = dataMat.colwise().mean();
371 dataMat.rowwise() -= colMean.transpose();
372
373 // SVD
374 Eigen::JacobiSVD<MatrixXd> svd(dataMat, Eigen::ComputeThinV);
375 MatrixXd V = svd.matrixV();
376
377 int nComp = qMin(nVec, static_cast<int>(V.cols()));
378
379 for (int v = 0; v < nComp; ++v) {
380 FiffProj proj;
382 proj.active = false;
383 proj.desc = QString("%1-v%2").arg(desc).arg(v + 1);
384
385 FiffNamedMatrix::SDPtr namedMatrix(new FiffNamedMatrix());
386 namedMatrix->nrow = 1;
387 namedMatrix->ncol = nchan;
388 namedMatrix->row_names.clear();
389 namedMatrix->col_names = raw.info.ch_names;
390 namedMatrix->data = MatrixXd::Zero(1, nchan);
391
392 for (int c = 0; c < chIdx.size(); ++c) {
393 namedMatrix->data(0, chIdx[c]) = V(c, v);
394 }
395
396 proj.data = namedMatrix;
397 projs.append(proj);
398 }
399
400 qInfo() << "[FiffProj::compute_from_raw] Created" << nComp << desc << "projection vector(s)";
401 };
402
403 computeProjForChannels(gradIdx, nGrad, "PCA-grad");
404 computeProjForChannels(magIdx, nMag, "PCA-mag");
405 computeProjForChannels(eegIdx, nEeg, "PCA-eeg");
406
407 return projs;
408}
Eigen::Matrix3f S
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
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
SSP projection item: a named projection vector set with active/desired flags, parsed from FIFFB_PROJ_...
float eegReject
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFV_PROJ_ITEM_FIELD
Definition fiff_file.h:816
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.
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:190
fiff_int_t kind
Definition fiff_proj.h:186
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:93
fiff_int_t last_samp
FiffInfo info
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:280