27#include <QCoreApplication>
56: m_pSettings(pSettings)
75void ComputeFwd::initFwd()
77 m_bInitialized =
false;
84 m_listMegChs = QList<FiffChInfo>();
85 m_listEegChs = QList<FiffChInfo>();
86 m_listCompChs = QList<FiffChInfo>();
104 QTextStream* filteredStream =
nullptr;
110 qInfo(
"Source space : %s", m_pSettings->srcname.toUtf8().constData());
111 if (!(m_pSettings->transname.isEmpty()) || !(m_pSettings->mriname.isEmpty())) {
112 qInfo(
"MRI -> head transform source : %s", !(m_pSettings->mriname.isEmpty()) ? m_pSettings->mriname.toUtf8().constData() : m_pSettings->transname.toUtf8().constData());
114 qInfo(
"MRI and head coordinates are assumed to be identical.");
116 qInfo(
"Measurement data : %s", m_pSettings->measname.toUtf8().constData());
117 if (!m_pSettings->bemname.isEmpty()) {
118 qInfo(
"BEM model : %s", m_pSettings->bemname.toUtf8().constData());
120 qInfo(
"Sphere model : origin at (% 7.2f % 7.2f % 7.2f) mm",
121 1000.0f * m_pSettings->r0[
X], 1000.0f * m_pSettings->r0[
Y], 1000.0f * m_pSettings->r0[
Z]);
122 if (m_pSettings->include_eeg) {
123 if (m_pSettings->eeg_model_file.isEmpty()) {
124 qWarning(
"EEG model file not specified; using default.");
127 m_eegModels->fwd_list_eeg_sphere_models();
129 if (m_pSettings->eeg_model_name.isEmpty()) {
130 m_pSettings->eeg_model_name = QString(
"Default");
132 m_eegModel.reset(m_eegModels->fwd_select_eeg_sphere_model(m_pSettings->eeg_model_name));
137 if (!m_eegModel->fwd_setup_eeg_sphere_model(m_pSettings->eeg_sphere_rad, m_pSettings->use_equiv_eeg, 3)) {
141 qInfo(
"Using EEG sphere model \"%s\" with scalp radius %7.1f mm",
142 m_pSettings->eeg_model_name.toUtf8().constData(), 1000 * m_pSettings->eeg_sphere_rad);
143 qInfo(
"%s the electrode locations to scalp", m_pSettings->scale_eeg_pos ?
"Scale" :
"Do not scale");
145 m_eegModel->scale_pos = m_pSettings->scale_eeg_pos;
146 m_eegModel->r0 = m_pSettings->r0;
149 qInfo(
"%s field computations", m_pSettings->accurate ?
"Accurate" :
"Standard");
150 qInfo(
"Do computations in %s coordinates.", FiffCoordTrans::frame_name(m_pSettings->coord_frame).toUtf8().constData());
151 qInfo(
"%s source orientations", m_pSettings->fixed_ori ?
"Fixed" :
"Free");
152 if (m_pSettings->compute_grad) {
153 qInfo(
"Compute derivatives with respect to source location coordinates");
155 qInfo(
"Destination for the solution : %s", m_pSettings->solname.toUtf8().constData());
156 if (m_pSettings->do_all) {
157 qInfo(
"Calculate solution for all source locations.");
159 if (m_pSettings->nlabel > 0) {
160 qInfo(
"Source space will be restricted to sources in %d labels", m_pSettings->nlabel);
164 qInfo(
"Reading %s...", m_pSettings->srcname.toUtf8().constData());
168 for (k = 0, m_iNSource = 0; k < static_cast<int>(m_spaces.size()); k++) {
169 if (m_pSettings->do_all) {
170 m_spaces[k]->enable_all_sources();
172 m_iNSource += m_spaces[k]->nuse;
174 if (m_iNSource == 0) {
175 qCritical(
"No sources are active in these source spaces. --all option should be used.");
178 qInfo(
"Read %d source spaces a total of %d active source locations",
static_cast<int>(m_spaces.size()), m_iNSource);
184 if (!m_pSettings->mriname.isEmpty()) {
186 if (m_mri_head_t.isEmpty()) {
190 QFile mriFile(m_pSettings->mriname);
192 if (mriStream->open()) {
193 m_mri_id.version = mriStream->id().version;
194 m_mri_id.machid[0] = mriStream->id().machid[0];
195 m_mri_id.machid[1] = mriStream->id().machid[1];
196 m_mri_id.time = mriStream->id().time;
203 if (m_mri_id.isEmpty()) {
204 qCritical(
"Couldn't read MRI file id (How come?)");
207 }
else if (!m_pSettings->transname.isEmpty()) {
216 m_mri_head_t.print();
220 if (!m_pSettings->pFiffInfo) {
221 QFile measname(m_pSettings->measname);
224 FIFFLIB::FiffInfo fiffInfo;
225 if (!pStream->open()) {
226 qCritical() <<
"Could not open Stream.";
230 if (!pStream->read_meas_info(pStream->dirtree(), fiffInfo, DirNode)) {
231 qCritical() <<
"Could not find the channel information.";
235 m_pInfoBase = QSharedPointer<FIFFLIB::FiffInfo>(
new FiffInfo(fiffInfo));
237 m_pInfoBase = m_pSettings->pFiffInfo;
240 qCritical(
"ComputeFwd::initFwd(): no FiffInfo");
243 m_pInfoBase->mne_read_meg_comp_eeg_ch_info(m_listMegChs,
251 if (!m_pSettings->meg_head_t.isEmpty()) {
252 m_meg_head_t = m_pSettings->meg_head_t;
254 if (m_meg_head_t.isEmpty()) {
255 qCritical(
"MEG -> head coordinate transformation not found.");
259 m_iNChan = iNMeg + iNEeg;
262 qInfo(
"Read %3d MEG channels from %s", iNMeg, m_pSettings->measname.toUtf8().constData());
265 qInfo(
"Read %3d MEG compensation channels from %s", iNComp, m_pSettings->measname.toUtf8().constData());
268 qInfo(
"Read %3d EEG channels from %s", iNEeg, m_pSettings->measname.toUtf8().constData());
270 if (!m_pSettings->include_meg) {
271 qInfo(
"MEG not requested. MEG channels omitted.");
272 m_listMegChs.clear();
273 m_listCompChs.clear();
277 m_meg_head_t.print();
278 if (!m_pSettings->include_eeg) {
279 qInfo(
"EEG not requested. EEG channels omitted.");
280 m_listEegChs.clear();
290 if (m_pSettings->include_meg) {
291 m_qPath = QString(QCoreApplication::applicationDirPath() +
"/../resources/general/coilDefinitions/coil_def.dat");
292 if (!QCoreApplication::startingUp()) {
293 m_qPath = QCoreApplication::applicationDirPath() + QString(
"/../resources/general/coilDefinitions/coil_def.dat");
294 }
else if (!QFile::exists(m_qPath)) {
295 m_qPath =
"../resources/general/coilDefinitions/coil_def.dat";
309 if (m_compData->ncomp > 0) {
310 qInfo(
"%d compensation data sets in %s", m_compData->ncomp, m_pSettings->measname.toUtf8().constData());
312 m_listCompChs.clear();
324 m_megcoils = m_templates->create_meg_coils(m_listMegChs,
332 m_compcoils = m_templates->create_meg_coils(m_listCompChs,
347 qInfo(
"MRI coordinate coil definitions created.");
349 m_megcoils = m_templates->create_meg_coils(m_listMegChs,
358 m_compcoils = m_templates->create_meg_coils(m_listCompChs,
370 qInfo(
"Head coordinate coil definitions created.");
379 qInfo(
"Source spaces are now in %s coordinates.", FiffCoordTrans::frame_name(m_pSettings->coord_frame).toUtf8().constData());
383 if (!m_pSettings->bemname.isEmpty()) {
385 m_pSettings->bemname = bemsolname;
387 qInfo(
"Setting up the BEM model using %s...", m_pSettings->bemname.toUtf8().constData());
388 qInfo(
"Loading surfaces...");
392 qInfo(
"Three-layer model surfaces loaded.");
398 qInfo(
"Homogeneous model surface loaded.");
400 if (iNEeg > 0 && m_bemModel->nsurf == 1) {
401 qCritical(
"Cannot use a homogeneous model in EEG calculations.");
404 qInfo(
"Loading the solution matrix...");
405 if (m_bemModel->fwd_bem_load_recompute_solution(m_pSettings->bemname.toUtf8().data(),
FWD_BEM_UNKNOWN,
false) ==
FAIL) {
409 qInfo(
"Employing the head->MRI coordinate transform with the BEM model.");
410 if (m_bemModel->fwd_bem_set_head_mri_t(m_mri_head_t) ==
FAIL) {
414 qInfo(
"BEM model %s is now set up", m_bemModel->sol_name.toUtf8().constData());
416 qInfo(
"Using the sphere model.");
417 m_bemModel = std::make_unique<FwdBemModel>();
422 if (m_pSettings->filter_spaces) {
423 if (!m_pSettings->mindistoutname.isEmpty()) {
424 filteredFile.setFileName(m_pSettings->mindistoutname);
425 if (!filteredFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
426 qCritical() << m_pSettings->mindistoutname;
429 filteredStream =
new QTextStream(&filteredFile);
430 qInfo(
"Omitted source space points will be output to : %s", m_pSettings->mindistoutname.toUtf8().constData());
433 m_pSettings->bemname,
436 filteredStream, m_pSettings->use_threads);
437 delete filteredStream;
438 filteredStream =
nullptr;
440 m_bInitialized =
true;
445void ComputeFwd::populateMetadata(MNEForwardSolution& fwd)
452 int nmeg = m_megcoils ? m_megcoils->ncoil() : 0;
453 int neeg = m_eegels ? m_eegels->ncoil() : 0;
454 fwd.
nchan = nmeg + neeg;
466 for (
int i = 0; i < static_cast<int>(m_spaces.size()); ++i) {
468 fwd.
nsource += m_spaces[i]->nuse;
476 const int nOri = m_pSettings->fixed_ori ? 1 : 3;
479 for (
int i = 0, p = 0; i < static_cast<int>(m_spaces.size()); ++i) {
480 const MNESourceSpace& s = *m_spaces[i];
481 for (
int k = 0; k < s.
nuse; ++k, ++p) {
486 fwd.
source_nn.middleRows(3 * p, 3) = Matrix3f::Identity();
499 for (
int i = 0; i < nmeg; ++i) {
500 fwd.
info.
chs.append(m_listMegChs[i]);
503 for (
int i = 0; i < neeg; ++i) {
504 fwd.
info.
chs.append(m_listEegChs[i]);
509 if (!m_pSettings->measname.isEmpty()) {
510 QFile fileBad(m_pSettings->measname);
512 if (t_pStreamBads->open()) {
513 fwd.
info.
bads = t_pStreamBads->read_bad_channels(t_pStreamBads->dirtree());
514 t_pStreamBads->close();
523 if (!m_bInitialized) {
524 qCritical(
"ComputeFwd::calculateFwd - the forward computation could not be set up.");
527 auto fwdSolution = std::make_unique<MNEForwardSolution>();
528 populateMetadata(*fwdSolution);
533 iNMeg = m_megcoils->ncoil();
536 iNEeg = m_eegels->ncoil();
538 if (!m_bemModel || m_bemModel->nsurf == 0) {
539 m_pSettings->use_threads =
false;
543 if (m_spaces[0]->coord_frame != m_pSettings->coord_frame) {
551 if ((m_bemModel->compute_forward_meg(m_spaces,
555 m_pSettings->fixed_ori,
557 m_pSettings->use_threads,
558 *m_meg_forward.data(),
559 *m_meg_forward_grad.data(),
560 m_pSettings->compute_grad)) ==
FAIL) {
565 if ((m_bemModel->compute_forward_eeg(m_spaces,
567 m_pSettings->fixed_ori,
569 m_pSettings->use_threads,
570 *m_eeg_forward.data(),
571 *m_eeg_forward_grad.data(),
572 m_pSettings->compute_grad)) ==
FAIL) {
578 if (iNMeg > 0 && iNEeg > 0) {
579 if (m_meg_forward->data.cols() != m_eeg_forward->data.cols()) {
580 qWarning() <<
"The MEG and EEG forward solutions do not match";
583 fwdSolution->sol->clear();
584 fwdSolution->sol->nrow = m_meg_forward->nrow + m_eeg_forward->nrow;
585 fwdSolution->sol->ncol = m_meg_forward->ncol;
586 fwdSolution->sol->data = MatrixXd(fwdSolution->sol->nrow, fwdSolution->sol->ncol);
587 fwdSolution->sol->data.block(0, 0, m_meg_forward->nrow, m_meg_forward->ncol) = m_meg_forward->data;
588 fwdSolution->sol->data.block(m_meg_forward->nrow, 0, m_eeg_forward->nrow, m_eeg_forward->ncol) = m_eeg_forward->data;
589 fwdSolution->sol->row_names = m_meg_forward->row_names;
590 fwdSolution->sol->row_names.append(m_eeg_forward->row_names);
591 fwdSolution->sol->col_names = m_meg_forward->col_names;
592 }
else if (iNMeg > 0) {
593 fwdSolution->sol = m_meg_forward;
595 fwdSolution->sol = m_eeg_forward;
598 if (m_pSettings->compute_grad) {
599 if (iNMeg > 0 && iNEeg > 0) {
600 if (m_meg_forward_grad->data.cols() != m_eeg_forward_grad->data.cols()) {
601 qWarning() <<
"The MEG and EEG forward solutions do not match";
604 fwdSolution->sol_grad->clear();
605 fwdSolution->sol_grad->nrow = m_meg_forward_grad->nrow + m_eeg_forward_grad->nrow;
606 fwdSolution->sol_grad->ncol = m_meg_forward_grad->ncol;
607 fwdSolution->sol_grad->data = MatrixXd(fwdSolution->sol_grad->nrow, fwdSolution->sol_grad->ncol);
608 fwdSolution->sol_grad->data.block(0, 0, m_meg_forward_grad->nrow, m_meg_forward_grad->ncol) = m_meg_forward_grad->data;
609 fwdSolution->sol_grad->data.block(m_meg_forward_grad->nrow, 0, m_eeg_forward_grad->nrow, m_eeg_forward_grad->ncol) = m_eeg_forward_grad->data;
610 fwdSolution->sol_grad->row_names = m_meg_forward_grad->row_names;
611 fwdSolution->sol_grad->row_names.append(m_eeg_forward_grad->row_names);
612 fwdSolution->sol_grad->col_names = m_meg_forward_grad->col_names;
613 }
else if (iNMeg > 0) {
614 fwdSolution->sol_grad = m_meg_forward_grad;
616 fwdSolution->sol_grad = m_eeg_forward_grad;
631 iNMeg = m_megcoils->ncoil();
636 iNComp = m_compcoils->ncoil();
646 m_megcoils = m_templates->create_meg_coils(m_listMegChs,
654 m_compcoils = m_templates->create_meg_coils(m_listCompChs,
663 m_megcoils = m_templates->create_meg_coils(m_listMegChs,
672 m_compcoils = m_templates->create_meg_coils(m_listCompChs,
682 if (m_spaces[0]->coord_frame != m_pSettings->coord_frame) {
689 if ((m_bemModel->compute_forward_meg(m_spaces,
693 m_pSettings->fixed_ori,
695 m_pSettings->use_threads,
696 *m_meg_forward.data(),
697 *m_meg_forward_grad.data(),
698 m_pSettings->compute_grad)) ==
FAIL) {
703 m_meg_head_t = transDevHead;
707 fwd.
sol->data.block(0, 0, m_meg_forward->nrow, m_meg_forward->ncol) = m_meg_forward->data;
708 if (m_pSettings->compute_grad) {
709 fwd.
sol_grad->data.block(0, 0, m_meg_forward_grad->nrow, m_meg_forward_grad->ncol) = m_meg_forward_grad->data;
#define FIFFV_COORD_DEVICE
#define FIFFV_MNE_FIXED_ORI
#define FIFFV_MNE_FREE_ORI
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
Top-level driver that assembles the MEG/EEG lead-field matrix G from a source space,...
Forward solution (gain matrix mapping source dipoles to sensor measurements).
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
constexpr int FWD_COIL_ACCURACY_NORMAL
constexpr int FWD_COIL_ACCURACY_ACCURATE
constexpr int FWD_BEM_UNKNOWN
static bool checkEegLocations(const QList< FiffChInfo > &chs, int nch)
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
static FiffCoordTrans combine(int from, int to, const FiffCoordTrans &t1, const FiffCoordTrans &t2)
FiffCoordTrans inverted() const
QSharedPointer< FiffDirNode > SPtr
128-bit FIFF identifier: hardware machine ID plus creation time, stamped on every file and block.
FiffCoordTrans dev_head_t
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
QSharedPointer< FiffStream > SPtr
std::unique_ptr< MNELIB::MNEForwardSolution > calculateFwd()
bool updateHeadPos(const FIFFLIB::FiffCoordTrans &transDevHead, MNELIB::MNEForwardSolution &fwd)
ComputeFwd(std::shared_ptr< ComputeFwdSettings > pSettings)
static FwdBemModel::UPtr fwd_bem_load_three_layer_surfaces(const QString &name)
Load a three-layer BEM model (scalp, outer skull, inner skull) from a FIFF file.
static QString fwd_bem_make_bem_sol_name(const QString &name)
Build a standard BEM solution file name from a model name.
static FwdBemModel::UPtr fwd_bem_load_homog_surface(const QString &name)
Load a single-layer (homogeneous) BEM model from a FIFF file.
static FwdCoilSet::UPtr read_coil_defs(const QString &name)
static FwdCoilSet::UPtr create_eeg_els(const QList< FIFFLIB::FiffChInfo > &chs, int nch, const FIFFLIB::FiffCoordTrans &t=FIFFLIB::FiffCoordTrans())
static FwdEegSphereModelSet * fwd_load_eeg_sphere_models(const QString &p_sFileName, FwdEegSphereModelSet *now)
static std::unique_ptr< MNECTFCompDataSet > read(const QString &name)
In-memory representation of an -fwd.fif forward solution.
FIFFLIB::fiff_int_t nsource
FIFFLIB::FiffInfoBase info
MNELIB::MNESourceSpaces src
FIFFLIB::fiff_int_t source_ori
FIFFLIB::FiffCoordTrans mri_head_t
FIFFLIB::FiffNamedMatrix::SDPtr sol_grad
Eigen::MatrixX3f source_nn
Eigen::MatrixX3f source_rr
FIFFLIB::fiff_int_t coord_frame
FIFFLIB::fiff_int_t nchan
FIFFLIB::FiffNamedMatrix::SDPtr sol
static int restrict_sources_to_labels(std::vector< std::unique_ptr< MNESourceSpace > > &spaces, const QStringList &labels, int nlabel)
static int filter_source_spaces(const MNESurface &surf, float limit, const FIFFLIB::FiffCoordTrans &mri_head_t, std::vector< std::unique_ptr< MNESourceSpace > > &spaces, QTextStream *filtered)
static int read_source_spaces(const QString &name, std::vector< std::unique_ptr< MNESourceSpace > > &spaces)
static int transform_source_spaces_to(int coord_frame, const FIFFLIB::FiffCoordTrans &t, std::vector< std::unique_ptr< MNESourceSpace > > &spaces)
void append(const MNESourceSpace &space)
static FiffCoordTrans combine(int from, int to, const FiffCoordTrans &t1, const FiffCoordTrans &t2)
static FiffCoordTrans readFShead2mriTransform(const QString &name)
static FiffCoordTrans readMriTransform(const QString &name)
static FiffCoordTrans identity(int from, int to)