ex_inverse_mne_raw
Example of a computation of a raw inverse.
Libraries: Inverse, 3D Visualization, FreeSurfer, MNE Core, FIFF I/O, Math, Utilities
Options
| Option | Description |
|---|---|
--fileIn | The input file <in>. |
--surfType | FsSurface type <type>. |
--annotType | FsAnnotation type <type>. |
--subject | Selected subject <subject>. |
--subjectPath | Selected subject path <subjectPath>. |
--fwd | Path to forwad solution <file>. |
--cov | Path to the covariance <file>. |
--aveIdx | The average <index> to choose from the average file. |
--eve | Path to the event <file>. |
--hemi | Selected hemisphere <hemi>. |
--snr | The SNR value used for computation <snr>. |
--numAve | The <value> for the number of averages. |
--method | Inverse estimation <method>, i.e., 'MNE', 'dSPM' or 'sLORETA'. |
--invOut | Path to inverse <file>, which is to be written. |
--stcOut | Path to stc <file>, which is to be written. |
--keepComp | Keep compensators. |
--pickAll | Pick all channels. |
--destComps | <Destination> of the compensator which is to be calculated. |
Build and run
cmake --build build/developer-dynamic --target ex_inverse_mne_raw
./out/Release/examples/ex_inverse_mne_raw --help
The default inputs come from the MNE sample dataset.
Used in the API reference
Source
src/examples/ex_inverse_mne_raw/main.cpp
//=============================================================================================================
/**
* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) 2012-2026 MNE-CPP Authors
*
* @file main.cpp
* @author Gabriel Motta <gabrielbenmotta@gmail.com>;
* Christoph Dinh <christoph.dinh@mne-cpp.org>;
* Juan GPC <jgarciaprieto@mgh.harvard.edu>;
* Lorenz Esch <lorenz.esch@tu-ilmenau.de>
* @since 0.1.0
* @date July, 2012
* @brief Example of a computation of a raw inverse
*/
//=============================================================================================================
// INCLUDES
//=============================================================================================================
#include <iostream>
#include <vector>
#include <math.h>
//=============================================================================================================
// MNE INCLUDES
//=============================================================================================================
#include <fs/fs_label.h>
#include <fs/fs_surface.h>
#include <fs/fs_surfaceset.h>
#include <fs/fs_annotationset.h>
#include <fiff/fiff_evoked.h>
#include <fiff/fiff.h>
#include <mne/mne.h>
#include <mne/mne_epoch_data_list.h>
#include <inv/inv_source_estimate.h>
#include <time.h>
#include <inv/minimum_norm/inv_minimum_norm.h>
#include <disp3D/view/brainview.h>
#include <disp3D/model/braintreemodel.h>
#include <math/linalg.h>
#include <utils/generics/mne_logger.h>
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================
#include <QApplication>
#include <QCommandLineParser>
#include <QDir>
#include <QVector3D>
#include <QRandomGenerator>
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================
using namespace MNELIB;
using namespace FSLIB;
using namespace FIFFLIB;
using namespace INVLIB;
using namespace UTILSLIB;
using namespace Eigen;
using namespace DISP3DLIB;
//=============================================================================================================
// MAIN
//=============================================================================================================
//=============================================================================================================
/**
* The function main marks the entry point of the program.
* By default, main has the storage class extern.
*
* @param[in] argc (argument count) is an integer that indicates how many arguments were entered on the command line when the program was started.
* @param[in] argv (argument vector) is an array of pointers to arrays of character objects. The array objects are null-terminated strings, representing the arguments that were entered on the command line when the program was started.
* @return the value that was set to exit() (which is 0 if exit() is called via quit()).
*/
int main(int argc, char* argv[])
{
#ifdef STATICBUILD
// Q_INIT_RESOURCE(mne_disp3d);
#endif
qInstallMessageHandler(MNELogger::customLogWriter);
QApplication a(argc, argv);
// Command Line Parser
QCommandLineParser parser;
parser.setApplicationDescription("Compute Raw Inverse MNE Example");
parser.addHelpOption();
QCommandLineOption inputOption("fileIn", "The input file <in>.", "in", QCoreApplication::applicationDirPath() + "/../resources/data/MNE-sample-data/MEG/sample/sample_audvis_raw.fif");
QCommandLineOption surfOption("surfType", "FsSurface type <type>.", "type", "inflated");
QCommandLineOption annotOption("annotType", "FsAnnotation type <type>.", "type", "aparc.a2009s");
QCommandLineOption subjectOption("subject", "Selected subject <subject>.", "subject", "sample");
QCommandLineOption subjectPathOption("subjectPath", "Selected subject path <subjectPath>.", "subjectPath", QCoreApplication::applicationDirPath() + "/../resources/data/MNE-sample-data/subjects");
QCommandLineOption fwdOption("fwd", "Path to forwad solution <file>.", "file", QCoreApplication::applicationDirPath() + "/../resources/data/MNE-sample-data/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif");
QCommandLineOption covFileOption("cov", "Path to the covariance <file>.", "file", QCoreApplication::applicationDirPath() + "/../resources/data/MNE-sample-data/MEG/sample/sample_audvis-cov.fif");
QCommandLineOption evokedIndexOption("aveIdx", "The average <index> to choose from the average file.", "index", "1");
QCommandLineOption eventsFileOption("eve", "Path to the event <file>.", "file", QCoreApplication::applicationDirPath() + "/../resources/data/MNE-sample-data/MEG/sample/sample_audvis_raw-eve.fif");
QCommandLineOption hemiOption("hemi", "Selected hemisphere <hemi>.", "hemi", "2");
QCommandLineOption snrOption("snr", "The SNR value used for computation <snr>.", "snr", "1.0"); //3.0;//0.1;//3.0;
QCommandLineOption numberAveragesOption("numAve", "The <value> for the number of averages.", "value", "40");
QCommandLineOption methodOption("method", "Inverse estimation <method>, i.e., 'MNE', 'dSPM' or 'sLORETA'.", "method", "dSPM"); //"MNE" | "dSPM" | "sLORETA"
QCommandLineOption invFileOutOption("invOut", "Path to inverse <file>, which is to be written.", "file", "");
QCommandLineOption stcFileOutOption("stcOut", "Path to stc <file>, which is to be written.", "file", "");
QCommandLineOption keepCompOption("keepComp", "Keep compensators.", "keepComp", "false");
QCommandLineOption pickAllOption("pickAll", "Pick all channels.", "pickAll", "true");
QCommandLineOption destCompsOption("destComps", "<Destination> of the compensator which is to be calculated.", "destination", "0");
parser.addOption(inputOption);
parser.addOption(surfOption);
parser.addOption(annotOption);
parser.addOption(subjectOption);
parser.addOption(subjectPathOption);
parser.addOption(fwdOption);
parser.addOption(covFileOption);
parser.addOption(evokedIndexOption);
parser.addOption(eventsFileOption);
parser.addOption(snrOption);
parser.addOption(numberAveragesOption);
parser.addOption(hemiOption);
parser.addOption(methodOption);
parser.addOption(invFileOutOption);
parser.addOption(stcFileOutOption);
parser.addOption(keepCompOption);
parser.addOption(pickAllOption);
parser.addOption(destCompsOption);
parser.process(a);
//
// Load files
//
QFile t_fileFwd(parser.value(fwdOption));
QFile t_fileCov(parser.value(covFileOption));
QFile t_fileRaw(parser.value(inputOption));
QString t_sEventName = parser.value(eventsFileOption);
FsSurfaceSet t_surfSet(parser.value(subjectOption), parser.value(hemiOption).toInt(), parser.value(surfOption), parser.value(subjectPathOption));
FsAnnotationSet t_annotationSet(parser.value(subjectOption), parser.value(hemiOption).toInt(), parser.value(annotOption), parser.value(subjectPathOption));
//
// Settings
//
qint32 event = parser.value(evokedIndexOption).toInt();
float tmin = -0.2f;
float tmax = 0.5f;
bool keep_comp = false;
if (parser.value(keepCompOption) == "false" || parser.value(keepCompOption) == "0") {
keep_comp = false;
} else if (parser.value(keepCompOption) == "true" || parser.value(keepCompOption) == "1") {
keep_comp = true;
}
fiff_int_t dest_comp = parser.value(destCompsOption).toInt();
bool pick_all = false;
if (parser.value(pickAllOption) == "false" || parser.value(pickAllOption) == "0") {
pick_all = false;
} else if (parser.value(pickAllOption) == "true" || parser.value(pickAllOption) == "1") {
pick_all = true;
}
qint32 k, p;
//
// Setup for reading the raw data
//
FiffRawData raw(t_fileRaw);
RowVectorXi picks;
if (pick_all) {
//
// Pick all
//
picks.resize(raw.info.nchan);
for (k = 0; k < raw.info.nchan; ++k)
picks(k) = k;
//
} else {
QStringList include;
include << "STI 014";
bool want_meg = true;
bool want_eeg = false;
bool want_stim = false;
// picks = Fiff::pick_types(raw.info, want_meg, want_eeg, want_stim, include, raw.info.bads);
picks = raw.info.pick_types(want_meg, want_eeg, want_stim, include, raw.info.bads); //prefer member function
}
QStringList ch_names;
for (k = 0; k < picks.cols(); ++k)
ch_names << raw.info.ch_names[picks(0, k)];
//
// Set up projection
//
if (raw.info.projs.size() == 0)
printf("No projector specified for these data\n");
else {
//
// Activate the projection items
//
for (k = 0; k < raw.info.projs.size(); ++k)
raw.info.projs[k].active = true;
printf("%lld projection items activated\n",
static_cast<long long>(raw.info.projs.size()));
//
// Create the projector
//
// fiff_int_t nproj = MNE::make_projector_info(raw.info, raw.proj); Using the member function instead
fiff_int_t nproj = raw.info.make_projector(raw.proj);
if (nproj == 0) {
printf("The projection vectors do not apply to these channels\n");
} else {
printf("Created an SSP operator (subspace dimension = %d)\n", nproj);
}
}
//
// Set up the CTF compensator
//
// qint32 current_comp = MNE::get_current_comp(raw.info);
qint32 current_comp = raw.info.get_current_comp();
if (current_comp > 0)
printf("Current compensation grade : %d\n", current_comp);
if (keep_comp)
dest_comp = current_comp;
if (current_comp != dest_comp) {
qDebug() << "This part needs to be debugged";
if (MNE::make_compensator(raw.info, current_comp, dest_comp, raw.comp)) {
// raw.info.chs = MNE::set_current_comp(raw.info.chs,dest_comp);
raw.info.set_current_comp(dest_comp);
printf("Appropriate compensator added to change to grade %d.\n", dest_comp);
} else {
printf("Could not make the compensator\n");
return 0;
}
}
//
// Read the events
//
QFile t_EventFile;
MatrixXi events;
if (t_sEventName.size() == 0) {
p = t_fileRaw.fileName().indexOf(".fif");
if (p > 0) {
t_sEventName = t_fileRaw.fileName().replace(p, 4, "-eve.fif");
} else {
printf("Raw file name does not end properly\n");
return 0;
}
// events = mne_read_events(t_sEventName);
t_EventFile.setFileName(t_sEventName);
MNE::read_events_from_fif(t_EventFile, events);
printf("Events read from %s\n", t_sEventName.toUtf8().constData());
} else {
//
// Binary file
//
p = t_fileRaw.fileName().indexOf(".fif");
if (p > 0) {
t_EventFile.setFileName(t_sEventName);
if (!MNE::read_events_from_fif(t_EventFile, events)) {
printf("Error while read events.\n");
return 0;
}
printf("Binary event file %s read\n", t_sEventName.toUtf8().constData());
} else {
//
// Text file
//
printf("Text file %s is not supported jet.\n", t_sEventName.toUtf8().constData());
// try
// events = load(eventname);
// catch
// error(me,mne_omit_first_line(lasterr));
// end
// if size(events,1) < 1
// error(me,'No data in the event file');
// end
// //
// // Convert time to samples if sample number is negative
// //
// for p = 1:size(events,1)
// if events(p,1) < 0
// events(p,1) = events(p,2)*raw.info.sfreq;
// end
// end
// //
// // Select the columns of interest (convert to integers)
// //
// events = int32(events(:,[1 3 4]));
// //
// // New format?
// //
// if events(1,2) == 0 && events(1,3) == 0
// fprintf(1,'The text event file %s is in the new format\n',eventname);
// if events(1,1) ~= raw.first_samp
// error(me,'This new format event file is not compatible with the raw data');
// end
// else
// fprintf(1,'The text event file %s is in the old format\n',eventname);
// //
// // Offset with first sample
// //
// events(:,1) = events(:,1) + raw.first_samp;
// end
}
}
//
// Select the desired events
//
qint32 count = 0;
MatrixXi selected = MatrixXi::Zero(1, events.rows());
for (p = 0; p < events.rows(); ++p) {
if (events(p, 1) == 0 && events(p, 2) == event) {
selected(0, count) = p;
++count;
}
}
selected.conservativeResize(1, count);
if (count > 0)
printf("%d matching events found\n", count);
else {
printf("No desired events found.\n");
return 0;
}
fiff_int_t event_samp, from, to;
MatrixXd timesDummy;
MNEEpochDataList data;
MNEEpochData* epoch = NULL;
MatrixXd times;
for (p = 0; p < count; ++p) {
//
// Read a data segment
//
event_samp = events(selected(p), 0);
from = event_samp + tmin * raw.info.sfreq;
to = event_samp + floor(tmax * raw.info.sfreq + 0.5);
epoch = new MNEEpochData();
if (raw.read_raw_segment(epoch->epoch, timesDummy, from, to, picks)) {
if (p == 0) {
times.resize(1, to - from + 1);
for (qint32 i = 0; i < times.cols(); ++i)
times(0, i) = ((float)(from - event_samp + i)) / raw.info.sfreq;
}
epoch->event = event;
epoch->tmin = ((float)(from) - (float)(raw.first_samp)) / raw.info.sfreq;
epoch->tmax = ((float)(to) - (float)(raw.first_samp)) / raw.info.sfreq;
data.append(MNEEpochData::SPtr(epoch)); //List takes ownwership of the pointer - no delete need
} else {
printf("Can't read the event data segments");
return 0;
}
}
if (data.size() > 0) {
printf("Read %lld epochs, %d samples each.\n",
static_cast<long long>(data.size()),
static_cast<qint32>(data[0]->epoch.cols()));
//DEBUG
std::cout << data[0]->epoch.block(0, 0, 10, 10) << std::endl;
qDebug() << data[0]->epoch.rows() << " x " << data[0]->epoch.cols();
std::cout << times.block(0, 0, 1, 10) << std::endl;
qDebug() << times.rows() << " x " << times.cols();
}
// Calculate the average
// Option 1 - Random selection
VectorXi vecSel(50);
for (qint32 i = 0; i < vecSel.size(); ++i) {
qint32 val = QRandomGenerator::global()->bounded(0, count);
vecSel(i) = val;
}
// //Option 3 - Take all epochs
// VectorXi vecSel(data.size());
// for(qint32 i = 0; i < vecSel.size(); ++i)
// {
// vecSel(i) = i;
// }
// //Option 3 - Manual selection
// VectorXi vecSel(20);
// vecSel << 76, 74, 13, 61, 97, 94, 75, 71, 60, 56, 26, 57, 56, 0, 52, 72, 33, 86, 96, 67;
// std::cout << "Select following epochs to average:\n" << vecSel << std::endl;
FiffEvoked evoked = data.average(raw.info, tmin * raw.info.sfreq, floor(tmax * raw.info.sfreq + 0.5), vecSel, true); //FIFFLIB::defaultVectorXi
QPair<float, float> baseline(-1.0f, -1.0f);
evoked.applyBaselineCorrection(baseline);
//########################################################################################
// Source Estimate
//
// Settings
//
double snr = parser.value(snrOption).toDouble();
QString method(parser.value(methodOption));
QString t_sFileNameInv(parser.value(invFileOutOption));
QString t_sFileNameStc(parser.value(stcFileOutOption));
float lambda2 = 1.0f / pow(snr, 2);
qDebug() << "Start calculation with: SNR" << snr << "; Lambda" << lambda2 << "; Method" << method << "; stc:" << t_sFileNameStc;
//
// Load data
//
MNEForwardSolution t_Fwd(t_fileFwd);
if (t_Fwd.isEmpty())
return 1;
FiffCov noise_cov(t_fileCov);
//
// regularize noise covariance
//
noise_cov = noise_cov.regularize(evoked.info, 0.05, 0.05, 0.1, true);
//
// make an inverse operators
//
FiffInfo info = evoked.info;
MNEInverseOperator inverse_operator(info, t_Fwd, noise_cov, 0.2f, 0.8f);
//
// save inverse operator
//
if (!t_sFileNameInv.isEmpty()) {
QFile t_fileInverse(t_sFileNameInv);
inverse_operator.write(t_fileInverse);
}
//
// Compute inverse solution
//
InvMinimumNorm minimumNorm(inverse_operator, lambda2, method);
InvSourceEstimate sourceEstimate = minimumNorm.calculateInverse(evoked);
if (sourceEstimate.isEmpty())
return 1;
// View activation time-series
std::cout << "\nsourceEstimate:\n"
<< sourceEstimate.data.block(0, 0, 10, 10) << std::endl;
std::cout << "time\n"
<< sourceEstimate.times.block(0, 0, 1, 10) << std::endl;
std::cout << "timeMin\n"
<< sourceEstimate.times[0] << std::endl;
std::cout << "timeMax\n"
<< sourceEstimate.times[sourceEstimate.times.size() - 1] << std::endl;
std::cout << "time step\n"
<< sourceEstimate.tstep << std::endl;
qDebug() << "Sampling frequency" << raw.info.sfreq;
qDebug() << "Number of Steps" << sourceEstimate.data.cols();
VectorXd s;
double t_dConditionNumber = Linalg::getConditionNumber(t_Fwd.sol->data, s);
std::cout << "Condition Number:\n"
<< t_dConditionNumber << std::endl;
std::cout << "ForwardSolution" << t_Fwd.sol->data.block(0, 0, 10, 10) << std::endl;
//Source Estimate end
//########################################################################################
// Write source estimate to temp files for visualization
int nVertLh = t_Fwd.src[0].nuse;
InvSourceEstimate stcLh, stcRh;
stcLh.data = sourceEstimate.data.topRows(nVertLh);
stcLh.vertices = sourceEstimate.vertices.head(nVertLh);
stcLh.tmin = sourceEstimate.tmin;
stcLh.tstep = sourceEstimate.tstep;
stcLh.times = sourceEstimate.times;
stcRh.data = sourceEstimate.data.bottomRows(sourceEstimate.data.rows() - nVertLh);
stcRh.vertices = sourceEstimate.vertices.tail(sourceEstimate.vertices.size() - nVertLh);
stcRh.tmin = sourceEstimate.tmin;
stcRh.tstep = sourceEstimate.tstep;
stcRh.times = sourceEstimate.times;
QString tmpDir = QDir::tempPath();
QString lhStcPath = tmpDir + "/mnecpp_ex_inverse_mne_raw-lh.stc";
QString rhStcPath = tmpDir + "/mnecpp_ex_inverse_mne_raw-rh.stc";
QFile lhStcFile(lhStcPath);
stcLh.write(lhStcFile);
QFile rhStcFile(rhStcPath);
stcRh.write(rhStcFile);
BrainView* pBrainView = new BrainView();
BrainTreeModel* pModel = new BrainTreeModel();
pBrainView->setModel(pModel);
for (auto it = t_surfSet.data().constBegin(); it != t_surfSet.data().constEnd(); ++it) {
int hIdx = it.key();
QString hemi = (it.value().hemi() == 0) ? "lh" : "rh";
QString surfType = it.value().surf().isEmpty() ? "inflated" : it.value().surf();
pModel->addSurface(parser.value(subjectOption), hemi, surfType, it.value());
if (t_annotationSet.size() > hIdx)
pModel->addAnnotation(parser.value(subjectOption), hemi, t_annotationSet[hIdx]);
}
QObject::connect(pBrainView, &BrainView::sourceEstimateLoaded, [&](int /*nTimePoints*/) {
pBrainView->setSourceColormap("Hot");
pBrainView->setSourceThresholds(0.0f, 0.5f, 20.0f);
pBrainView->setRealtimeLooping(true);
pBrainView->setRealtimeInterval(17);
pBrainView->startRealtimeStreaming();
});
pBrainView->loadSourceEstimate(lhStcPath, rhStcPath);
if (!t_sFileNameStc.isEmpty()) {
QFile t_fileStc(t_sFileNameStc);
sourceEstimate.write(t_fileStc);
}
pBrainView->show();
return a.exec();
}