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 currentSfreq = 1.0f / current->tstep;
86 const float tmin = current->tmin;
87 const float tmax = current->tmin + (current->np - 1) / currentSfreq;
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 / currentSfreq + 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 / currentSfreq + 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 qCritical("Failed to read evoked data from %s\n", name.toUtf8().constData());
149 return nullptr;
150 }
151
152 /*
153 * Extract fields from the FiffEvoked object
154 */
155 const int nchan_file = evoked.info.nchan;
156 const int nsamp = evoked.last - evoked.first + 1;
157 const float sfreq = evoked.info.sfreq;
158 const float dtmin = static_cast<float>(evoked.first) / sfreq;
159 const float highpass = evoked.info.highpass;
160 const float lowpass = evoked.info.lowpass;
161 const int nave = evoked.nave;
162 const int aspect_kind = evoked.aspect_kind;
163 const QList<FiffChInfo>& chs = evoked.info.chs;
164 const FiffId& id = evoked.info.meas_id;
165 const MatrixXf data = evoked.data.cast<float>(); /* nchan × nsamp, already calibrated */
166 const FiffCoordTrans& devHeadT = evoked.info.dev_head_t;
167
168 QString stim14_name;
169 /*
170 * Desired channels
171 */
172 QStringList names;
173 int nchan = 0;
174 /*
175 * Selected channels
176 */
177 Eigen::VectorXi sel;
178 int stim14 = -1;
179 /*
180 * Other stuff
181 */
182 float tmin, tmax;
183 int k, p, c, np, n1, n2;
184 MNEMeasData* res = nullptr;
185 MNEMeasData* new_data = add_to;
186 MNEMeasDataSet* dataset = nullptr;
187
188 stim14_name = qEnvironmentVariable(MNE_ENV_TRIGGER_CH);
189 if (stim14_name.isEmpty() || stim14_name.size() == 0)
190 stim14_name = MNE_DEFAULT_TRIGGER_CH;
191 // FiffTag::toChInfo strips the spaces from channel names.
192 stim14_name.remove(' ');
193
194 if (add_to) {
195 for (int i = 0; i < add_to->nchan; i++)
196 names.append(add_to->chs[i].ch_name);
197 nchan = add_to->nchan;
198 } else {
199 if (op) {
200 names = op->eigen_fields->col_names;
201 nchan = op->nchan;
202 } else if (fwd) {
203 names = fwd->collist;
204 nchan = fwd->ncol;
205 } else {
206 names = namesp;
207 nchan = nnamesp;
208 }
209 if (names.isEmpty())
210 nchan = 0;
211 }
212
213 if (!id.isEmpty())
214 qInfo("\tMeasurement file id: %s\n", id.toString().toUtf8().constData());
215
216 /*
217 * Pick out the necessary channels
218 */
219 if (nchan > 0) {
220 sel = Eigen::VectorXi::Constant(nchan, -1);
221 for (c = 0; c < nchan_file; c++) {
222 for (k = 0; k < nchan; k++) {
223 if (sel[k] == -1 && QString::compare(chs[c].ch_name, names[k]) == 0) {
224 sel[k] = c;
225 break;
226 }
227 }
228 if (QString(chs[c].ch_name).remove(' ') == stim14_name) {
229 stim14 = c;
230 }
231 }
232 for (k = 0; k < nchan; k++)
233 if (sel[k] == -1) {
234 qCritical("All channels needed were not in the MEG/EEG data file "
235 "(first missing: %s).",
236 names[k].toUtf8().constData());
237 return nullptr;
238 }
239 } else { /* Load all channels */
240 sel.resize(nchan_file);
241 sel.setZero();
242 for (c = 0, nchan = 0; c < nchan_file; c++) {
243 if (chs[c].kind == FIFFV_MEG_CH || chs[c].kind == FIFFV_EEG_CH) {
244 sel[nchan] = c;
245 nchan++;
246 }
247 if (QString(chs[c].ch_name).remove(' ') == stim14_name) {
248 stim14 = c;
249 }
250 }
251 }
252 /*
253 * Cut the data to the analysis time range
254 */
255 n1 = 0;
256 n2 = nsamp;
257 np = n2 - n1;
258 tmin = dtmin;
259 tmax = dtmin + (np - 1) / sfreq;
260 qInfo("\tData time range: %8.1f ... %8.1f ms\n", 1000 * tmin, 1000 * tmax);
261 /*
262 * Just put it together
263 */
264 if (!new_data) { /* We need a new meas data structure */
265 new_data = new MNEMeasData;
266 new_data->filename = name;
267 new_data->meas_id = id;
268 /*
269 * Getting starting time from measurement ID is not too accurate...
270 */
271 {
272 FiffTime md;
273 md.secs = evoked.info.meas_date[0];
274 md.usecs = evoked.info.meas_date[1];
275 if (md.secs != 0)
276 new_data->meas_date = md;
277 else if (!new_data->meas_id.isEmpty())
278 new_data->meas_date = new_data->meas_id.time;
279 else {
280 new_data->meas_date.secs = 0;
281 new_data->meas_date.usecs = 0;
282 }
283 }
284 new_data->lowpass = lowpass;
285 new_data->highpass = highpass;
286 new_data->nchan = nchan;
287 new_data->sfreq = sfreq;
288
289 if (!devHeadT.isEmpty()) {
290 new_data->meg_head_t = std::make_unique<FiffCoordTrans>(devHeadT);
291 qInfo("\tUsing MEG <-> head transform from the present data set\n");
292 }
293 if (op != nullptr && !op->mri_head_t.isEmpty()) { /* Copy if available */
294 new_data->mri_head_t = std::make_unique<FiffCoordTrans>(op->mri_head_t);
295 qInfo("\tPicked MRI <-> head transform from the inverse operator\n");
296 }
297 /*
298 * Channel list
299 */
300 for (k = 0; k < nchan; k++) {
301 new_data->chs.append(FiffChInfo());
302 new_data->chs[k] = chs[sel[k]];
303 }
304
305 new_data->op = op; /* Attach inverse operator */
306 new_data->fwd = fwd; /* ...or a fwd operator */
307 if (op) { /* Attach the projection operator and CTF compensation info to the data, too */
308 new_data->proj = MNEProjOp::read(name);
309 if (new_data->proj && new_data->proj->nitems > 0) {
310 qInfo("\tLoaded projection from %s:\n", name.toUtf8().data());
311 QTextStream errStream(stderr);
312 new_data->proj->report(errStream, QStringLiteral("\t\t"));
313 }
314 } else {
315 new_data->proj = MNEProjOp::read(name);
316 if (new_data->proj && new_data->proj->nitems > 0) {
317 qInfo("\tLoaded projection from %s:\n", name.toUtf8().data());
318 QTextStream errStream(stderr);
319 new_data->proj->report(errStream, QStringLiteral("\t\t"));
320 }
321 new_data->comp = MNECTFCompDataSet::read(name);
322 if (!new_data->comp) {
323 delete new_data;
324 return nullptr;
325 }
326 if (new_data->comp->ncomp > 0)
327 qInfo("\tRead %d compensation data sets from %s\n", new_data->comp->ncomp, name.toUtf8().data());
328 }
329 /*
330 * Bad channels — already read by FiffEvoked via FiffStream::read_meas_info()
331 */
332 {
333 new_data->badlist = evoked.info.bads;
334 new_data->nbad = new_data->badlist.size();
335 new_data->bad = Eigen::VectorXi::Zero(new_data->nchan);
336
337 for (int b = 0; b < new_data->nbad; b++) {
338 for (k = 0; k < new_data->nchan; k++) {
339 if (QString::compare(new_data->chs[k].ch_name, new_data->badlist[b], Qt::CaseInsensitive) == 0) {
340 new_data->bad[k] = 1;
341 break;
342 }
343 }
344 }
345 qInfo("\t%d bad channels read from %s%s", new_data->nbad, name.toUtf8().data(), new_data->nbad > 0 ? ":\n" : "\n");
346 if (new_data->nbad > 0) {
347 qInfo("\t\t");
348 for (k = 0; k < new_data->nbad; k++)
349 qInfo("%s%c", new_data->badlist[k].toUtf8().constData(), k < new_data->nbad - 1 ? ' ' : '\n');
350 }
351 }
352 }
353 /*
354 * New data set is created anyway
355 */
356 dataset = new MNEMeasDataSet;
357 dataset->tmin = tmin;
358 dataset->tstep = 1.0 / sfreq;
359 dataset->first = n1;
360 dataset->np = np;
361 dataset->nave = nave;
362 dataset->kind = aspect_kind;
363 dataset->data = Eigen::MatrixXf::Zero(np, nchan);
364 dataset->comment = evoked.comment;
365 dataset->baselines = Eigen::VectorXf::Zero(nchan);
366 /*
367 * Pick data from all channels
368 */
369 for (k = 0; k < nchan; k++) {
370 /*
371 * Shift the response
372 */
373 for (p = 0; p < np; p++)
374 dataset->data(p, k) = data(sel[k], p + n1);
375 }
376 /*
377 * Pick the digital trigger channel, too
378 */
379 if (stim14 >= 0) {
380 dataset->stim14 = Eigen::VectorXf(np);
381 for (p = 0; p < np; p++) /* Copy the data and correct for the possible non-unit calibration */
382 dataset->stim14[p] = data(stim14, p + n1) / chs[stim14].cal;
383 }
384 new_data->sets.append(dataset);
385 dataset = nullptr;
386 new_data->nset++;
387 if (!add_to)
388 new_data->current = new_data->sets[0];
389 res = new_data;
390 qInfo("\t%s dataset %s from %s\n",
391 add_to ? "Added" : "Loaded",
392 new_data->sets[new_data->nset - 1]->comment.toUtf8().constData() ? new_data->sets[new_data->nset - 1]->comment.toUtf8().constData() : "unknown", name.toUtf8().data());
393
394 if (res == nullptr && !add_to)
395 delete new_data;
396 return res;
397}
398
399//=============================================================================================================
400
402 int set,
405 const QStringList& namesp,
406 int nnamesp)
407
408{
409 return mne_read_meas_data_add(name, set, op, fwd, namesp, nnamesp, nullptr);
410}
#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.
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:130
#define MNE_DEFAULT_TRIGGER_CH
Default digital trigger channel name.
Definition mne_types.h:127
Legacy MNE-C measurement-data container assembling raw/evoked sets and their projection state.
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:77
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:69
bool isEmpty() const
Definition fiff_id.h:201
FiffTime time
Definition fiff_id.h:194
fiff_int_t meas_date[2]
Definition fiff_info.h:277
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:52
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)