Skip to content

Error when simulating raw data with head movement #13275

@jasmainak

Description

@jasmainak

Description of the problem

I get a bug deep in the forward code when trying to simulate raw data with head movement.

Steps to reproduce

import numpy as np

import mne
from mne.simulation import SourceSimulator

# Use MNE sample data paths
sample_path = mne.datasets.sample.data_path()
subjects_dir = sample_path / 'subjects'
subject = 'sample'

# Load info from an evoked file
evoked_fname = sample_path / 'MEG/sample/sample_audvis-ave.fif'
evoked = mne.read_evokeds(evoked_fname, condition=0, baseline=(None, 0))
info = evoked.info.copy()
sfreq = info['sfreq']

# Forward stuff
bem = subjects_dir / subject / 'bem' / f'{subject}-5120-5120-5120-bem-sol.fif'
trans = mne.read_trans(sample_path / "MEG" / "sample" / "sample_audvis_raw-trans.fif")
src = mne.read_source_spaces(subjects_dir / subject / 'bem' / f'{subject}-oct-6-src.fif')

selected_label =  mne.read_labels_from_annot(
    subject, regexp="caudalmiddlefrontal-lh", subjects_dir=subjects_dir
)[0]
location = 1915  # Use the index of the vertex as a seed
extent = 0.0  # One dipole source
label_dipole = mne.label.select_sources(
    subject,
    selected_label,
    location=location,
    extent=extent,
    subjects_dir=subjects_dir,
    random_state=42,
)

# Minimal SourceSimulator
duration = 10.  # seconds
sim = SourceSimulator(src, tstep=1.0 / sfreq, duration=duration)
n_times = int(duration * sfreq)
source_time_series = np.sin(2 * np.pi * 10 * np.arange(n_times) / sfreq) * 1e-9
sim.add_data(label_dipole, source_time_series, events=np.array([[0, 0, 1]]))

# Minimal head movement: dict with time points as keys
t1, t2 = 0, duration / 2
trans_matrices = {t1: info['dev_head_t']['trans'],
                  t2: info['dev_head_t']['trans']}

# Simulate raw
n_events = 10
events = np.zeros((n_events, 3), int)
events[:, 0] = np.random.choice(int(duration * sfreq), n_events,
                                replace=False)
events[:, 2] = 1

sim = SourceSimulator(src, tstep=1. / info['sfreq'],
                      duration=duration)
sim.add_data(label_dipole, source_time_series, events)
raw = mne.simulation.simulate_raw(
        info, sim, forward=None, src=src,
        head_pos=trans_matrices, bem=bem, trans=trans
)

Link to data

No response

Expected results

No error and raw data

Actual results

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
File ~/Desktop/04_github_repos/mne-opm/examples/reproduce_bug.py:59
     56 sim = SourceSimulator(src, tstep=1. / info['sfreq'],
     57                       duration=duration)
     58 sim.add_data(label_dipole, source_time_series, events)
---> 59 raw = mne.simulation.simulate_raw(
     60         info, sim, forward=None, src=src,
     61         head_pos=trans_matrices, bem=bem, trans=trans
     62 )

File <decorator-gen-408>:12, in simulate_raw(info, stc, trans, src, bem, head_pos, mindist, interp, n_jobs, use_cps, forward, first_samp, max_iter, verbose)

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/simulation/raw.py:364, in simulate_raw(info, stc, trans, src, bem, head_pos, mindist, interp, n_jobs, use_cps, forward, first_samp, max_iter, verbose)
    362 raw_datas.append(this_data)
    363 # Stim channel
--> 364 fwd, fi = interper.feed(this_stop - this_start)
    365 fi = fi[0]
    366 stc_data, stim_data, _ = _stc_data_event(
    367     stc_counted, fi, info["sfreq"], get_fwd.src, None if n == 0 else verts
    368 )

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/_ola.py:190, in _Interp2.feed(self, n_pts)
    188 # Convenience function for assembly
    189 out_arrays = None
--> 190 for o in self.feed_generator(n_pts):
    191     if out_arrays is None:
    192         out_arrays = [
    193             np.empty(v.shape + (n_pts,)) if v is not None else None
    194             for v in o[1]
    195         ]

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/_ola.py:131, in _Interp2.feed_generator(self, n_pts)
    129     eval_pt = self.control_points[self._left_idx + 1]
    130     logger.debug(f"  Eval @ {self._left_idx + 1} ({eval_pt})")
--> 131     self._right = self.values(eval_pt)
    132 assert self._right is not None
    133 left_point = self.control_points[self._left_idx]

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/simulation/raw.py:770, in _SimForwards.__call__(self, offset)
    768 assert self.offsets[self.idx] == offset
    769 self.idx += 1
--> 770 fwd = next(self.iter)
    771 self.src = fwd["src"]
    772 # XXX eventually we could speed this up by allowing the forward
    773 # solution code to only compute the normal direction

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/simulation/raw.py:863, in _iter_forward_solutions(***failed resolving arguments***)
    858     if not outside.all():
    859         raise RuntimeError(
    860             f"{np.sum(~outside)} MEG sensors collided with inner skull "
    861             f"surface for transform {ti}"
    862         )
--> 863     megfwd = _compute_forwards(
    864         rr, sensors=sensors, bem=bem, n_jobs=n_jobs, verbose=False
    865     )["meg"]
    866     megfwd = _to_forward_dict(megfwd, megnames)
    867 else:

File <decorator-gen-354>:10, in _compute_forwards(rr, bem, sensors, n_jobs, verbose)

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/forward/_compute_forward.py:838, in _compute_forwards(rr, bem, sensors, n_jobs, verbose)
    836 _check_option("solver", solver, ("mne", "openmeeg"))
    837 if bem["is_sphere"] or solver == "mne":
--> 838     fwd_data = _prep_field_computation(rr, sensors=sensors, bem=bem, n_jobs=n_jobs)
    839     Bs = _compute_forwards_meeg(
    840         rr, sensors=sensors, fwd_data=fwd_data, n_jobs=n_jobs
    841     )
    842 else:

File <decorator-gen-353>:12, in _prep_field_computation(rr, sensors, bem, n_jobs, verbose)

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/forward/_compute_forward.py:755, in _prep_field_computation(rr, sensors, bem, n_jobs, verbose)
    753     cf = FIFF.FIFFV_COORD_HEAD
    754     # multiply solution by "mults" here for simplicity
--> 755     solution = _bem_specify_coils(bem, coils, cf, mults, n_jobs)
    756 else:
    757     # Compute solution for EEG sensor
    758     logger.info("Setting up for EEG...")

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/forward/_compute_forward.py:199, in _bem_specify_coils(bem, coils, coord_frame, mults, n_jobs)
    179 """Set up for computing the solution at a set of MEG coils.
    180 
    181 Parameters
   (...)
    196     MEG solution
    197 """
    198 # Make sure MEG coils are in MRI coordinate frame to match BEM coords
--> 199 coils, coord_frame = _check_coil_frame(coils, coord_frame, bem)
    201 # leaving this in in case we want to easily add in the future
    202 # if method != 'simple':  # in ['ferguson', 'urankar']:
    203 #     raise NotImplementedError
   (...)
    207 
    208 # Process each of the surfaces
    209 rmags, cosmags, ws, bins = _triage_coils(coils)

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/forward/_compute_forward.py:53, in _check_coil_frame(coils, coord_frame, bem)
     50 if coord_frame != FIFF.FIFFV_COORD_MRI:
     51     if coord_frame == FIFF.FIFFV_COORD_HEAD:
     52         # Make a transformed duplicate
---> 53         coils, coord_frame = _dup_coil_set(coils, coord_frame, bem["head_mri_t"])
     54     else:
     55         raise RuntimeError(f"Bad coil coordinate frame {coord_frame}")

File ~/anaconda3/envs/mne_workshop/lib/python3.12/site-packages/mne/forward/_compute_forward.py:41, in _dup_coil_set(coils, coord_frame, t)
     39     if key in coil:
     40         coil[key] = apply_trans(t["trans"], coil[key], False)
---> 41 coil["r0"] = apply_trans(t["trans"], coil["r0"])
     42 coil["rmag"] = apply_trans(t["trans"], coil["rmag"])
     43 coil["cosmag"] = apply_trans(t["trans"], coil["cosmag"], False)

IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices

Additional information

Platform macOS-15.5-x86_64-i386-64bit
Python 3.12.2 | packaged by conda-forge | (main, Feb 16 2024, 21:00:12) [Clang 16.0.6 ]
Executable /Users/mainak/anaconda3/envs/mne_workshop/bin/python3.12
CPU Apple M1
machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTSE64 MON DSCPL VMX EST TM2 SSSE3 CX16 TPR PDCM SSE4.1 SSE4.2 AES SEGLIM64
machdep.cpu.feature_bits: 151121000215084031
machdep.cpu.family: 6 (8 cores)
Memory 8.0 GiB

Core
├☑ mne 1.9.0 (latest release)
├☑ numpy 1.26.4 (OpenBLAS 0.3.27 with 8 threads)
├☑ scipy 1.14.0
└☑ matplotlib 3.8.4 (backend=MacOSX)

Numerical (optional)
├☑ sklearn 1.5.0
├☑ numba 0.59.1
├☑ nibabel 5.2.1
├☑ nilearn 0.10.4
├☑ dipy 1.9.0
├☑ openmeeg 2.5.11
├☑ pandas 2.2.2
├☑ h5io 0.2.3
├☑ h5py 3.11.0
└☐ unavailable cupy

Visualization (optional)
├☑ pyvista 0.43.10 (OpenGL 4.1 Metal - 89.4 via Apple M1)
├☑ pyvistaqt 0.11.1
├☑ vtk 9.2.6
├☑ qtpy 2.4.1 (PyQt5=5.15.8)
├☑ ipympl 0.9.4
├☑ pyqtgraph 0.13.7
├☑ mne-qt-browser 0.6.3
├☑ ipywidgets 8.1.3
├☑ trame_client 3.2.0
├☑ trame_server 3.0.2
├☑ trame_vtk 2.8.9
└☑ trame_vuetify 2.6.0

Ecosystem (optional)
├☑ eeglabio 0.0.2-4
├☑ edfio 0.4.3
├☑ mffpy 0.9.0
├☑ pybv 0.7.5
└☐ unavailable mne-bids, mne-nirs, mne-features, mne-connectivity, mne-icalabel, mne-bids-pipeline, neo

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions