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