v2.0.0
Loading...
Searching...
No Matches
mne_meas_data.cpp
Go to the documentation of this file.
1//=============================================================================================================
36
37//=============================================================================================================
38// INCLUDES
39//=============================================================================================================
40
41#include "mne_meas_data.h"
42#include "mne_meas_data_set.h"
44
45#include "mne_types.h"
46#include "mne_named_matrix.h"
47
48#include <fiff/fiff_types.h>
50#include <fiff/fiff_evoked.h>
51
52#include <vector>
53
54#include <QFile>
55#include <QTextStream>
56#include <QDebug>
57
58//=============================================================================================================
59// USED NAMESPACES
60//=============================================================================================================
61
62using namespace Eigen;
63using namespace FIFFLIB;
64using namespace MNELIB;
65using namespace MNELIB;
66
67
68//=============================================================================================================
69// DEFINE MEMBER METHODS
70//=============================================================================================================
71
73: sfreq(0.0f)
74, nchan(0)
75, highpass(0.0f)
76, lowpass(0.0f)
77, op(nullptr)
78, fwd(nullptr)
79, chsel(nullptr)
80, nbad(0)
81, ch_major(false)
82, nset(0)
83, current(nullptr)
84{
85 meas_date.secs = 0;
86 meas_date.usecs = 0;
87}
88
89//=============================================================================================================
90
92{
93 for (int k = 0; k < nset; k++)
94 delete sets[k];
95
96 delete chsel;
97}
98
99//=============================================================================================================
100
101void MNEMeasData::adjust_baselines(float bmin, float bmax)
102{
103 if (!current)
104 return;
105
106 const float sfreq = 1.0f / current->tstep;
107 const float tmin = current->tmin;
108 const float tmax = current->tmin + (current->np - 1) / sfreq;
109
110 int b1, b2;
111 if (bmin < tmin)
112 b1 = 0;
113 else if (bmin > tmax)
114 b1 = current->np;
115 else {
116 for (b1 = 0; b1 / sfreq + tmin < bmin; b1++)
117 ;
118 b1 = qBound(0, b1, current->np);
119 }
120 if (bmax < tmin)
121 b2 = 0;
122 else if (bmax > tmax)
123 b2 = current->np;
124 else {
125 for (b2 = current->np; b2 / sfreq + tmin > bmax; b2--)
126 ;
127 b2 = qBound(0, b2, current->np);
128 }
129
130 Eigen::MatrixXf &bdata = current->data;
131 if (b2 > b1) {
132 for (int c = 0; c < nchan; c++) {
133 float ave = 0.0f;
134 for (int s = b1; s < b2; s++)
135 ave += bdata(s, c);
136 ave /= (b2 - b1);
137 current->baselines[c] += ave;
138 for (int s = 0; s < current->np; s++)
139 bdata(s, c) -= ave;
140 }
141 qInfo("\t%s : using baseline %7.1f ... %7.1f ms\n",
142 current->comment.toUtf8().constData() ? current->comment.toUtf8().constData() : "unknown",
143 1000 * (tmin + b1 / sfreq),
144 1000 * (tmin + b2 / sfreq));
145 }
146}
147
148//=============================================================================================================
149
151 int set,
154 const QStringList& namesp,
155 int nnamesp,
156 MNEMeasData *add_to) /* Add to this */
157/*
158 * Read an evoked-response data file
159 */
160{
161 /*
162 * Read evoked data via FiffEvoked (no baseline correction, no projection
163 * — MNEMeasData handles projections separately via MNEProjOp).
164 */
165 QFile file(name);
166 FiffEvoked evoked;
167 if (!FiffEvoked::read(file, evoked, set - 1,
168 QPair<float,float>(-1.0f, -1.0f), false))
169 {
170 qCritical("Failed to read evoked data from %s\n", name.toUtf8().constData());
171 return nullptr;
172 }
173
174 /*
175 * Extract fields from the FiffEvoked object
176 */
177 const int nchan_file = evoked.info.nchan;
178 const int nsamp = evoked.last - evoked.first + 1;
179 const float sfreq = evoked.info.sfreq;
180 const float dtmin = static_cast<float>(evoked.first) / sfreq;
181 const float highpass = evoked.info.highpass;
182 const float lowpass = evoked.info.lowpass;
183 const int nave = evoked.nave;
184 const int aspect_kind = evoked.aspect_kind;
185 const QList<FiffChInfo>& chs = evoked.info.chs;
186 const FiffId& id = evoked.info.meas_id;
187 const MatrixXf data = evoked.data.cast<float>(); /* nchan × nsamp, already calibrated */
188 const FiffCoordTrans& devHeadT = evoked.info.dev_head_t;
189
190 QString stim14_name;
191 /*
192 * Desired channels
193 */
194 QStringList names;
195 int nchan = 0;
196 /*
197 * Selected channels
198 */
199 Eigen::VectorXi sel;
200 int stim14 = -1;
201 /*
202 * Other stuff
203 */
204 float tmin,tmax;
205 int k,p,c,np,n1,n2;
206 MNEMeasData* res = nullptr;
207 MNEMeasData* new_data = add_to;
208 MNEMeasDataSet* dataset = nullptr;
209
210 stim14_name = qEnvironmentVariable(MNE_ENV_TRIGGER_CH);
211 if (stim14_name.isEmpty() || stim14_name.size() == 0)
212 stim14_name = MNE_DEFAULT_TRIGGER_CH;
213
214 if (add_to) {
215 for (int i = 0; i < add_to->nchan; i++)
216 names.append(add_to->chs[i].ch_name);
217 nchan = add_to->nchan;
218 }
219 else {
220 if (op) {
221 names = op->eigen_fields->col_names;
222 nchan = op->nchan;
223 }
224 else if (fwd) {
225 names = fwd->collist;
226 nchan = fwd->ncol;
227 }
228 else {
229 names = namesp;
230 nchan = nnamesp;
231 }
232 if (names.isEmpty())
233 nchan = 0;
234 }
235
236 if (!id.isEmpty())
237 qInfo("\tMeasurement file id: %s\n", id.toString().toUtf8().constData());
238
239 /*
240 * Pick out the necessary channels
241 */
242 if (nchan > 0) {
243 sel = Eigen::VectorXi::Constant(nchan, -1);
244 for (c = 0; c < nchan_file; c++) {
245 for (k = 0; k < nchan; k++) {
246 if (sel[k] == -1 && QString::compare(chs[c].ch_name,names[k]) == 0) {
247 sel[k] = c;
248 break;
249 }
250 }
251 if (QString::compare(stim14_name,chs[c].ch_name) == 0) {
252 stim14 = c;
253 }
254 }
255 for (k = 0; k < nchan; k++)
256 if (sel[k] == -1) {
257 qCritical("All channels needed were not in the MEG/EEG data file "
258 "(first missing: %s).",names[k].toUtf8().constData());
259 return nullptr;
260 }
261 }
262 else { /* Load all channels */
263 sel.resize(nchan_file);
264 sel.setZero();
265 for (c = 0, nchan = 0; c < nchan_file; c++) {
266 if (chs[c].kind == FIFFV_MEG_CH || chs[c].kind == FIFFV_EEG_CH) {
267 sel[nchan] = c;
268 nchan++;
269 }
270 if (QString::compare(stim14_name,chs[c].ch_name) == 0) {
271 stim14 = c;
272 }
273 }
274 }
275 /*
276 * Cut the data to the analysis time range
277 */
278 n1 = 0;
279 n2 = nsamp;
280 np = n2 - n1;
281 tmin = dtmin;
282 tmax = dtmin + (np-1)/sfreq;
283 qInfo("\tData time range: %8.1f ... %8.1f ms\n",1000*tmin,1000*tmax);
284 /*
285 * Just put it together
286 */
287 if (!new_data) { /* We need a new meas data structure */
288 new_data = new MNEMeasData;
289 new_data->filename = name;
290 new_data->meas_id = id;
291 /*
292 * Getting starting time from measurement ID is not too accurate...
293 */
294 {
295 FiffTime md;
296 md.secs = evoked.info.meas_date[0];
297 md.usecs = evoked.info.meas_date[1];
298 if (md.secs != 0)
299 new_data->meas_date = md;
300 else if (!new_data->meas_id.isEmpty())
301 new_data->meas_date = new_data->meas_id.time;
302 else {
303 new_data->meas_date.secs = 0;
304 new_data->meas_date.usecs = 0;
305 }
306 }
307 new_data->lowpass = lowpass;
308 new_data->highpass = highpass;
309 new_data->nchan = nchan;
310 new_data->sfreq = sfreq;
311
312 if (!devHeadT.isEmpty()) {
313 new_data->meg_head_t = std::make_unique<FiffCoordTrans>(devHeadT);
314 qInfo("\tUsing MEG <-> head transform from the present data set\n");
315 }
316 if (op != nullptr && !op->mri_head_t.isEmpty()) { /* Copy if available */
317 new_data->mri_head_t = std::make_unique<FiffCoordTrans>(op->mri_head_t);
318 qInfo("\tPicked MRI <-> head transform from the inverse operator\n");
319 }
320 /*
321 * Channel list
322 */
323 for (k = 0; k < nchan; k++) {
324 new_data->chs.append(FiffChInfo());
325 new_data->chs[k] = chs[sel[k]];
326 }
327
328 new_data->op = op; /* Attach inverse operator */
329 new_data->fwd = fwd; /* ...or a fwd operator */
330 if (op) { /* Attach the projection operator and CTF compensation info to the data, too */
331 new_data->proj = MNEProjOp::read(name);
332 if (new_data->proj && new_data->proj->nitems > 0) {
333 qInfo("\tLoaded projection from %s:\n",name.toUtf8().data());
334 QTextStream errStream(stderr);
335 new_data->proj->report(errStream, QStringLiteral("\t\t"));
336 }
337 }
338 else {
339 new_data->proj = MNEProjOp::read(name);
340 if (new_data->proj && new_data->proj->nitems > 0) {
341 qInfo("\tLoaded projection from %s:\n",name.toUtf8().data());
342 QTextStream errStream(stderr);
343 new_data->proj->report(errStream, QStringLiteral("\t\t"));
344 }
345 new_data->comp = MNECTFCompDataSet::read(name);
346 if (!new_data->comp) {
347 delete new_data;
348 return nullptr;
349 }
350 if (new_data->comp->ncomp > 0)
351 qInfo("\tRead %d compensation data sets from %s\n",new_data->comp->ncomp,name.toUtf8().data());
352 }
353 /*
354 * Bad channels — already read by FiffEvoked via FiffStream::read_meas_info()
355 */
356 {
357 new_data->badlist = evoked.info.bads;
358 new_data->nbad = new_data->badlist.size();
359 new_data->bad = Eigen::VectorXi::Zero(new_data->nchan);
360
361 for (int b = 0; b < new_data->nbad; b++) {
362 for (k = 0; k < new_data->nchan; k++) {
363 if (QString::compare(new_data->chs[k].ch_name,new_data->badlist[b],Qt::CaseInsensitive) == 0) {
364 new_data->bad[k] = 1;
365 break;
366 }
367 }
368 }
369 qInfo("\t%d bad channels read from %s%s",new_data->nbad,name.toUtf8().data(),new_data->nbad > 0 ? ":\n" : "\n");
370 if (new_data->nbad > 0) {
371 qInfo("\t\t");
372 for (k = 0; k < new_data->nbad; k++)
373 qInfo("%s%c",new_data->badlist[k].toUtf8().constData(),k < new_data->nbad-1 ? ' ' : '\n');
374 }
375 }
376 }
377 /*
378 * New data set is created anyway
379 */
380 dataset = new MNEMeasDataSet;
381 dataset->tmin = tmin;
382 dataset->tstep = 1.0/sfreq;
383 dataset->first = n1;
384 dataset->np = np;
385 dataset->nave = nave;
386 dataset->kind = aspect_kind;
387 dataset->data = Eigen::MatrixXf::Zero(np, nchan);
388 dataset->comment = evoked.comment;
389 dataset->baselines = Eigen::VectorXf::Zero(nchan);
390 /*
391 * Pick data from all channels
392 */
393 for (k = 0; k < nchan; k++) {
394 /*
395 * Shift the response
396 */
397 for (p = 0; p < np; p++)
398 dataset->data(p, k) = data(sel[k], p + n1);
399 }
400 /*
401 * Pick the digital trigger channel, too
402 */
403 if (stim14 >= 0) {
404 dataset->stim14 = Eigen::VectorXf(np);
405 for (p = 0; p < np; p++) /* Copy the data and correct for the possible non-unit calibration */
406 dataset->stim14[p] = data(stim14, p + n1) / chs[stim14].cal;
407 }
408 new_data->sets.append(dataset); dataset = nullptr;
409 new_data->nset++;
410 if (!add_to)
411 new_data->current = new_data->sets[0];
412 res = new_data;
413 qInfo("\t%s dataset %s from %s\n",
414 add_to ? "Added" : "Loaded",
415 new_data->sets[new_data->nset-1]->comment.toUtf8().constData() ? new_data->sets[new_data->nset-1]->comment.toUtf8().constData() : "unknown",name.toUtf8().data());
416
417 if (res == nullptr && !add_to)
418 delete new_data;
419 return res;
420}
421
422//=============================================================================================================
423
425 int set,
428 const QStringList& namesp,
429 int nnamesp)
430
431{
432 return mne_read_meas_data_add(name,set,op,fwd,namesp,nnamesp,nullptr);
433}
FiffEvoked class declaration.
Old fiff_type declarations - replace them.
FiffCoordTrans class declaration.
#define FIFFV_EEG_CH
#define FIFFV_MEG_CH
Legacy MNE-C constants and common typedefs.
#define MNE_ENV_TRIGGER_CH
Environment variable overriding the trigger channel name.
Definition mne_types.h:152
#define MNE_DEFAULT_TRIGGER_CH
Default digital trigger channel name.
Definition mne_types.h:149
MNENamedMatrix class declaration.
MNEMeasData class declaration.
MNEInverseOperator class declaration.
MNEMeasDataSet class declaration.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Channel info descriptor.
Coordinate transformation description.
Eigen::MatrixXd data
fiff_int_t aspect_kind
static bool read(QIODevice &p_IODevice, FiffEvoked &p_FiffEvoked, QVariant setno=0, QPair< float, float > t_baseline=defaultFloatPair, bool proj=true, fiff_int_t p_aspect_kind=FIFFV_ASPECT_AVERAGE)
Universally unique identifier.
Definition fiff_id.h:68
bool isEmpty() const
Definition fiff_id.h:189
FiffTime time
Definition fiff_id.h:181
fiff_int_t meas_date[2]
Definition fiff_info.h:260
QList< FiffChInfo > chs
FiffCoordTrans dev_head_t
Time stamp record storing seconds and microseconds since epoch.
Definition fiff_time.h:61
static std::unique_ptr< MNECTFCompDataSet > read(const QString &name)
MNE-style inverse operator.
static MNEMeasData * mne_read_meas_data_add(const QString &name, int set, MNEInverseOperator *op, MNENamedMatrix *fwd, const QStringList &namesp, int nnamesp, MNEMeasData *add_to)
Read an evoked-response data set and append it to an existing container.
MNEMeasDataSet * current
void adjust_baselines(float bmin, float bmax)
Adjust baseline offset of the current data set.
~MNEMeasData()
Destroys the measurement data and all owned data sets.
MNEInverseOperator * op
MNENamedMatrix * fwd
MNEMeasData()
Constructs an empty measurement data container.
FIFFLIB::FiffTime meas_date
std::unique_ptr< FIFFLIB::FiffCoordTrans > meg_head_t
QList< MNEMeasDataSet * > sets
std::unique_ptr< MNECTFCompDataSet > comp
QList< FIFFLIB::FiffChInfo > chs
std::unique_ptr< FIFFLIB::FiffCoordTrans > mri_head_t
mneChSelection chsel
FIFFLIB::FiffId meas_id
std::unique_ptr< MNEProjOp > proj
Eigen::VectorXi bad
static MNEMeasData * mne_read_meas_data(const QString &name, int set, MNEInverseOperator *op, MNENamedMatrix *fwd, const QStringList &namesp, int nnamesp)
Read an evoked-response data set into a new container.
Single measurement epoch or average within MNEMeasData.
A dense matrix with named rows and columns.
static std::unique_ptr< MNEProjOp > read(const QString &name)