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_evoked.h"
28#include "fiff_constants.h"
29#include "fiff_file.h"
30#include <stdio.h>
31#include <cmath>
32#include <math/linalg.h>
33
34//=============================================================================================================
35// EIGEN INCLUDES
36//=============================================================================================================
37
38#include <Eigen/SVD>
39#include <QDebug>
40
41#include <array>
42#include <stdexcept>
43//=============================================================================================================
44// USED NAMESPACES
45//=============================================================================================================
46
47using namespace UTILSLIB;
48using namespace FIFFLIB;
49using namespace Eigen;
50
51//=============================================================================================================
52// DEFINE STATIC FUNCTIONS
53//=============================================================================================================
54
55namespace
56{
57
59std::array<QList<int>, 3> pcaChannelGroups(const FiffInfo& info)
60{
61 std::array<QList<int>, 3> groups;
62 for (int k = 0; k < info.chs.size(); ++k) {
63 if (info.bads.contains(info.ch_names[k])) {
64 continue;
65 }
66 if (info.chs[k].kind == FIFFV_MEG_CH) {
67 groups[info.chs[k].unit == FIFF_UNIT_T ? 1 : 0].append(k);
68 } else if (info.chs[k].kind == FIFFV_EEG_CH) {
69 groups[2].append(k);
70 }
71 }
72 return groups;
73}
74
79QList<FiffProj> pcaProjectors(const MatrixXd& data, const QList<int>& chIdx, const QStringList& chNames,
80 int nVec, const QStringList& descs)
81{
82 QList<FiffProj> projs;
83 if (nVec <= 0 || chIdx.isEmpty()) {
84 return projs;
85 }
86 MatrixXd sub(chIdx.size(), data.cols());
87 for (int c = 0; c < chIdx.size(); ++c) {
88 sub.row(c) = data.row(chIdx[c]);
89 }
90 const JacobiSVD<MatrixXd> svd(sub, ComputeThinU);
91 const int nComp = qMin(nVec, static_cast<int>(svd.matrixU().cols()));
92 for (int v = 0; v < nComp; ++v) {
93 FiffProj proj;
95 proj.active = false;
96 proj.desc = descs.value(v);
97 proj.data->nrow = 1;
98 proj.data->ncol = static_cast<int>(chNames.size());
99 proj.data->col_names = chNames;
100 proj.data->data = MatrixXd::Zero(1, chNames.size());
101 for (int c = 0; c < chIdx.size(); ++c) {
102 proj.data->data(0, chIdx[c]) = svd.matrixU()(c, v);
103 }
104 projs.append(proj);
105 }
106 return projs;
107}
108
109} // namespace
110
111//=============================================================================================================
112// DEFINE MEMBER METHODS
113//=============================================================================================================
114
116: kind(-1)
117, active(false)
118, desc("")
119, data(FiffNamedMatrix::SDPtr(new FiffNamedMatrix()))
120{
121}
122
123//=============================================================================================================
124
125FiffProj::FiffProj(const FiffProj& p_FiffProj)
126: kind(p_FiffProj.kind)
127, active(p_FiffProj.active)
128, desc(p_FiffProj.desc)
129, data(p_FiffProj.data)
130{
131}
132
133//=============================================================================================================
134
135FiffProj::FiffProj(fiff_int_t p_kind, bool p_active, QString p_desc, FiffNamedMatrix& p_data)
136: kind(p_kind)
137, active(p_active)
138, desc(p_desc)
139, data(FiffNamedMatrix::SDPtr(new FiffNamedMatrix(p_data)))
140{
141}
142
143//=============================================================================================================
144
148
149//=============================================================================================================
150
151void FiffProj::activate_projs(QList<FiffProj>& p_qListFiffProj)
152{
153 // Activate the projection items
154 QList<FiffProj>::Iterator it;
155 for (it = p_qListFiffProj.begin(); it != p_qListFiffProj.end(); ++it)
156 it->active = true;
157
158 qInfo("\t%lld projection items activated.\n", static_cast<long long>(p_qListFiffProj.size()));
159}
160
161//=============================================================================================================
162
163fiff_int_t FiffProj::make_projector(const QList<FiffProj>& projs, const QStringList& ch_names, MatrixXd& proj, const QStringList& bads, MatrixXd& U)
164{
165 fiff_int_t nchan = ch_names.size();
166 if (nchan == 0) {
167 throw std::invalid_argument("No channel names specified");
168 }
169
170 // if(proj)
171 // delete proj;
172 proj = MatrixXd::Identity(nchan, nchan);
173 fiff_int_t nproj = 0;
174 U = MatrixXd();
175
176 //
177 // Check trivial cases first
178 //
179 if (projs.size() == 0)
180 return 0;
181
182 fiff_int_t nvec = 0;
183 fiff_int_t k, l;
184 for (k = 0; k < projs.size(); ++k) {
185 if (projs[k].active) {
186 ++nproj;
187 nvec += projs[k].data->nrow;
188 }
189 }
190
191 if (nproj == 0) {
192 qWarning("FiffProj::make_projector - No projectors nproj=0\n");
193 return 0;
194 }
195
196 if (nvec <= 0) {
197 qWarning("FiffProj::make_projector - No rows in projector matrices found nvec<=0\n");
198 return 0;
199 }
200
201 //
202 // Pick the appropriate entries
203 //
204 MatrixXd vecs = MatrixXd::Zero(nchan, nvec);
205 nvec = 0;
206 fiff_int_t nonzero = 0;
207 qint32 p, c, i, j, v;
208 double onesize;
209 bool isBad = false;
210 RowVectorXi sel(nchan);
211 RowVectorXi vecSel(nchan);
212 sel.setConstant(-1);
213 vecSel.setConstant(-1);
214 for (k = 0; k < projs.size(); ++k) {
215 if (projs[k].active) {
216 FiffProj one = projs[k];
217
218 QMap<QString, int> uniqueMap;
219 for (l = 0; l < one.data->col_names.size(); ++l)
220 uniqueMap[one.data->col_names[l]] = 0;
221
222 if (one.data->col_names.size() != uniqueMap.keys().size()) {
223 qWarning("Channel name list in projection item %d contains duplicate items", k);
224 return 0;
225 }
226
227 //
228 // Get the two selection vectors to pick correct elements from
229 // the projection vectors omitting bad channels
230 //
231 sel.resize(nchan);
232 vecSel.resize(nchan);
233 sel.setConstant(-1);
234 vecSel.setConstant(-1);
235 p = 0;
236 for (c = 0; c < nchan; ++c) {
237 for (i = 0; i < one.data->col_names.size(); ++i) {
238 if (QString::compare(ch_names.at(c), one.data->col_names[i]) == 0) {
239 isBad = false;
240 for (j = 0; j < bads.size(); ++j) {
241 if (QString::compare(ch_names.at(c), bads.at(j)) == 0) {
242 isBad = true;
243 }
244 }
245
246 if (!isBad && sel[p] != c) {
247 sel[p] = c;
248 vecSel[p] = i;
249 ++p;
250 }
251 }
252 }
253 }
254 sel.conservativeResize(p);
255 vecSel.conservativeResize(p);
256 //
257 // If there is something to pick, pickit
258 //
259 if (sel.cols() > 0)
260 for (v = 0; v < one.data->nrow; ++v)
261 for (i = 0; i < p; ++i)
262 vecs(sel[i], nvec + v) = one.data->data(v, vecSel[i]);
263
264 //
265 // Rescale for more straightforward detection of small singular values
266 //
267 for (v = 0; v < one.data->nrow; ++v) {
268 onesize = sqrt((vecs.col(nvec + v).transpose() * vecs.col(nvec + v))(0, 0));
269 if (onesize > 0.0) {
270 vecs.col(nvec + v) = vecs.col(nvec + v) / onesize;
271 ++nonzero;
272 }
273 }
274 nvec += one.data->nrow;
275 }
276 }
277 //
278 // Check whether all of the vectors are exactly zero
279 //
280 if (nonzero == 0)
281 return 0;
282
283 //
284 // Reorthogonalize the vectors
285 //
286 JacobiSVD<MatrixXd> svd(vecs.block(0, 0, vecs.rows(), nvec), ComputeFullU);
287 //Sort singular values and singular vectors
288 VectorXd S = svd.singularValues();
289 MatrixXd t_U = svd.matrixU();
291
292 //
293 // Throw away the linearly dependent guys
294 //
295 nproj = 0;
296 for (k = 0; k < S.size(); ++k)
297 if (S[k] / S[0] > 1e-2)
298 ++nproj;
299
300 U = t_U.block(0, 0, t_U.rows(), nproj);
301
302 //
303 // Here is the celebrated result
304 //
305 proj -= U * U.transpose();
306
307 return nproj;
308}
309
310//=============================================================================================================
311
312QList<FiffProj> FiffProj::compute_from_raw(const FiffRawData& raw,
313 const MatrixXi& events,
314 int eventCode,
315 float tmin,
316 float tmax,
317 int nGrad,
318 int nMag,
319 int nEeg,
320 const QMap<QString, double>& mapReject)
321{
322 float sfreq = raw.info.sfreq;
323 int nchan = raw.info.nchan;
324
325 int minSamp = static_cast<int>(std::round(tmin * sfreq));
326 int maxSamp = static_cast<int>(std::round(tmax * sfreq));
327 int ns = maxSamp - minSamp + 1;
328
329 if (ns <= 0) {
330 qWarning() << "[FiffProj::compute_from_raw] Invalid time window.";
331 return {};
332 }
333
334 // Collect matching epochs
335 QList<MatrixXd> epochs;
336 double gradReject = mapReject.value("grad", 0.0);
337 double magReject = mapReject.value("mag", 0.0);
338 double eegReject = mapReject.value("eeg", 0.0);
339
340 for (int k = 0; k < events.rows(); ++k) {
341 if (events(k, 1) != 0 || events(k, 2) != eventCode)
342 continue;
343
344 int evSample = events(k, 0);
345 int epochStart = evSample + minSamp;
346 int epochEnd = evSample + maxSamp;
347
348 if (epochStart < raw.first_samp || epochEnd > raw.last_samp)
349 continue;
350
351 MatrixXd epochData, epochTimes;
352 if (!raw.read_raw_segment(epochData, epochTimes, epochStart, epochEnd))
353 continue;
354
355 // Simple peak-to-peak rejection
356 bool ok = true;
357 for (int c = 0; c < nchan && ok; ++c) {
358 if (raw.info.bads.contains(raw.info.ch_names[c]))
359 continue;
360 double pp = epochData.row(c).maxCoeff() - epochData.row(c).minCoeff();
361 if (raw.info.chs[c].kind == FIFFV_MEG_CH) {
362 if (raw.info.chs[c].unit == FIFF_UNIT_T && magReject > 0 && pp > magReject)
363 ok = false;
364 else if (raw.info.chs[c].unit != FIFF_UNIT_T && gradReject > 0 && pp > gradReject)
365 ok = false;
366 } else if (raw.info.chs[c].kind == FIFFV_EEG_CH && eegReject > 0 && pp > eegReject) {
367 ok = false;
368 }
369 }
370 if (!ok)
371 continue;
372
373 epochs.append(epochData);
374 }
375
376 if (epochs.isEmpty()) {
377 qWarning() << "[FiffProj::compute_from_raw] No valid epochs found for event" << eventCode;
378 return {};
379 }
380
381 qInfo() << "[FiffProj::compute_from_raw]" << epochs.size() << "epochs collected for event" << eventCode;
382
383 // SVD of the uncentred data (MNE-C compute_cov_raw_epoch with remove_sample_mean = FALSE,
384 // mne.compute_proj_epochs); removing the mean first yields a different subspace.
385 MatrixXd data(nchan, epochs.size() * ns);
386 for (int e = 0; e < epochs.size(); ++e) {
387 data.middleCols(e * ns, ns) = epochs[e];
388 }
389 const std::array<QList<int>, 3> groups = pcaChannelGroups(raw.info);
390 const int nVec[3] = {nGrad, nMag, nEeg};
391 const char* kinds[3] = {"grad", "mag", "eeg"};
392 QList<FiffProj> projs;
393 for (int g = 0; g < 3; ++g) {
394 QStringList descs;
395 for (int v = 0; v < nVec[g]; ++v) {
396 descs << QString("PCA-%1-v%2").arg(kinds[g]).arg(v + 1);
397 }
398 projs += pcaProjectors(data, groups[g], raw.info.ch_names, nVec[g], descs);
399 }
400
401 return projs;
402}
403
404//=============================================================================================================
405
406QList<FiffProj> FiffProj::compute_from_evoked(const FiffEvoked& evoked, int nGrad, int nMag, int nEeg)
407{
408 if (evoked.data.cols() == 0 || evoked.times.size() == 0) {
409 return {};
410 }
411 const QString prefix = QString("%1-%2").arg(evoked.times(0), 0, 'f', 3).arg(evoked.times(evoked.times.size() - 1), 0, 'f', 3);
412 const std::array<QList<int>, 3> groups = pcaChannelGroups(evoked.info);
413 const int nVec[3] = {nGrad, nMag, nEeg};
414 const char* kinds[3] = {"planar", "axial", "eeg"};
415 QList<FiffProj> projs;
416 for (int g = 0; g < 3; ++g) {
417 QStringList descs;
418 for (int v = 0; v < nVec[g]; ++v) {
419 descs << QString("%1-%2-PCA-%3").arg(kinds[g], prefix).arg(v + 1, 2, 10, QChar('0'));
420 }
421 projs += pcaProjectors(evoked.data, groups[g], evoked.info.ch_names, nVec[g], descs);
422 }
423 return projs;
424}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_EEG_CH
#define FIFFV_MEG_CH
constexpr auto FIFF_UNIT_T
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
constexpr auto FIFFV_PROJ_ITEM_FIELD
Definition fiff_file.h:814
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).
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Definition fiff_evoked.h:77
Eigen::RowVectorXf times
Eigen::MatrixXd data
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
QList< FiffChInfo > chs
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.
Definition fiff_proj.h:82
FiffNamedMatrix::SDPtr data
Definition fiff_proj.h:228
fiff_int_t kind
Definition fiff_proj.h:224
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)
static QList< FiffProj > compute_from_evoked(const FiffEvoked &evoked, int nGrad=2, int nMag=2, int nEeg=2)
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