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