Skip to content

forward

tit.source.forward

Rebuild MNE-compatible EEG forward solutions from SimNIBS head models.

For a subject with an existing m2m_<id> head model, :func:prepare_forward produces the three files an MNE source-reconstruction needs:

  • sub-<id>_net-<net>-fwd.fif -- the forward solution (gain matrix)
  • sub-<id>_net-<net>-src.fif -- the cortical source space
  • sub-<id>_net-<net>-morph.h5 -- a SourceMorph onto fsaverage

plus the point-electrode leadfield (*_leadfield.hdf5) and the head<->MRI transform (*-trans.fif) they are derived from. All outputs land under derivatives/SimNIBS/sub-<id>/forward/ -- a namespace distinct from the optimizer's leadfields/ (which uses modeled electrodes for stimulation dose; the EEG forward uses point electrodes per the SimNIBS EEG convention).

The expensive FEM leadfield is computed at most once per (subject, net): a re-run reuses the cached HDF5 and only re-assembles the MNE files.

Runs under simnibs_python (imports both mne and simnibs)::

simnibs_python -m tit.source forward_config.json

See Also

tit.source.config.ForwardConfig : Parameter object. tit.source.fsaverage.project_fields_to_fsaverage : The companion field-mapping pipeline that puts simulation fields on the same fsaverage grid.

prepare_forward

prepare_forward(subject_id: str, cfg: ForwardConfig, *, output_dir: str | Path | None = None) -> tuple[Path, Path, Path]

Rebuild one subject's forward, source-space, and fsaverage morph files.

Parameters

subject_id : str Subject identifier without the sub- prefix (e.g. "101"). cfg : ForwardConfig EEG net, fsaverage spacing, FEM worker count, and overwrite flag. output_dir : str or pathlib.Path or None Destination directory. Defaults to pm.forward(subject_id) (derivatives/SimNIBS/sub-<id>/forward/).

Returns

tuple of pathlib.Path (fwd_path, src_path, morph_path).

Source code in tit/source/forward.py
def prepare_forward(
    subject_id: str,
    cfg: ForwardConfig,
    *,
    output_dir: str | Path | None = None,
) -> tuple[Path, Path, Path]:
    """Rebuild one subject's forward, source-space, and fsaverage morph files.

    Parameters
    ----------
    subject_id : str
        Subject identifier without the ``sub-`` prefix (e.g. ``"101"``).
    cfg : ForwardConfig
        EEG net, fsaverage spacing, FEM worker count, and overwrite flag.
    output_dir : str or pathlib.Path or None
        Destination directory.  Defaults to ``pm.forward(subject_id)``
        (``derivatives/SimNIBS/sub-<id>/forward/``).

    Returns
    -------
    tuple of pathlib.Path
        ``(fwd_path, src_path, morph_path)``.
    """
    pm = get_path_manager()
    m2m_dir = Path(pm.m2m(subject_id))
    if not m2m_dir.is_dir():
        raise FileNotFoundError(f"m2m directory not found: {m2m_dir}")

    forward_dir = (
        Path(output_dir) if output_dir is not None else Path(pm.forward(subject_id))
    )
    forward_dir.mkdir(parents=True, exist_ok=True)

    stem = f"sub-{subject_id}_net-{cfg.eeg_net}"
    expected = (
        forward_dir / f"{stem}-fwd.fif",
        forward_dir / f"{stem}-src.fif",
        forward_dir / f"{stem}-morph.h5",
    )
    if not cfg.overwrite and _forward_outputs_valid(*expected):
        logger.info("Forward outputs already present for %s; skipping.", subject_id)
        return expected

    electrodes, fiducials = _read_simnibs_montage(m2m_dir, cfg.eeg_net)
    montage_info, trans = _build_montage_info(electrodes, fiducials)

    info_path = forward_dir / f"{stem}-info.fif"
    trans_path = forward_dir / f"{stem}-trans.fif"
    mne.io.RawArray(
        np.zeros((len(montage_info.ch_names), 1)), montage_info, verbose=False
    ).save(str(info_path), overwrite=True, verbose=False)
    mne.write_trans(str(trans_path), trans, overwrite=True)

    # Redundancy guard: reuse the FEM leadfield if one is already cached.
    leadfield_hdf5 = None if cfg.overwrite else _find_existing_leadfield(forward_dir)
    if leadfield_hdf5 is not None:
        logger.info("Reusing cached leadfield: %s", leadfield_hdf5.name)
    else:
        eeg_csv = m2m_dir / "eeg_positions" / f"{cfg.eeg_net}.csv"
        leadfield_hdf5 = _compute_leadfield(m2m_dir, forward_dir, eeg_csv, cfg.cpus)

    _run(
        [
            "prepare_eeg_forward",
            "mne",
            str(m2m_dir),
            str(leadfield_hdf5),
            str(info_path),
            str(trans_path),
            "--fsaverage",
            str(cfg.fsaverage_spacing),
        ],
        f"prepare_eeg_forward for sub-{subject_id}",
        cwd=forward_dir,
    )
    return _rename_generated_outputs(forward_dir, stem)