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