Save epochs to fif with MRI space montage

  • MNE version: 0.24.0
  • operating system: Ubuntu 18.04

Hello everyone,

I am currently working on iEEG data. The sensors location we work with is in MRI space as the electrodes were located on the individuals T1 scan of each subject. In my preprocessing pipeline, I am keeping the electrodes in this coordinate space. I am then performing epoching to save the data.

But surprisingly, it seems that when saving the epochs data with the montage in MRI space, the montage gets converted automatically to head space. Is this behavior expected? Is there any way to save the data to fif while keeping the montage in MRI space?

Thanks for the support,

Kind regards,

Alex

Hello @AlexLepauvre,

Could you please share a minimal example demonstrating the issue, ideally with the sample dataset?

Which version of MNE are you using?
Edit Sorry, missed that info, you already stated you’re using 0.24

Does the problem persist with MNE 1.0.3?

Best wishes,
Richard

Dear Richard,

Thanks a lot for the quick reply.
Here is a code snippet that generates random epochs objects to show the issue:

import mne
import numpy as np
from numpy.random import normal
import tempfile
import os


# Create random montage:
montage = mne.channels.make_dig_montage(ch_pos={"ch_1": np.array([1, 2, 3]), "ch_2": np.array([4, 5, 6]),
                                                "ch_3": np.array([7, 8, 9])}, coord_frame="mri")

# Create random data:
info = mne.create_info(ch_names=['ch_1', 'ch_2', 'ch_3'],
                       ch_types=['ecog'] * 3,
                       sfreq=100)
data = np.array([[normal(loc=0, scale=2, size=100), normal(loc=0, scale=2, size=100),
                  normal(loc=0, scale=2, size=100)],
                 [normal(loc=0, scale=2, size=100), normal(loc=0, scale=2, size=100),
                  normal(loc=0, scale=2, size=100)]])
epochs = mne.EpochsArray(data, info, tmin=-0.2, verbose="ERROR")
epochs.set_montage(montage, verbose="ERROR")

# Printing the epochs montage info:
print("Coordinate frames before saving:")
print(epochs.get_montage().dig[0]["coord_frame"])

# Creating a temporary directory
temp_dir = tempfile.mkdtemp()
# Generate a file name
fname = temp_dir + os.sep + "-epo.fif"
epochs.save(fname)

loaded_epochs = mne.read_epochs(fname, verbose="ERROR")
print("Coordinate frames after saving:")
print(loaded_epochs.get_montage().dig[0]["coord_frame"])

The output is like so in my case:

Coordinate frames before saving:
5 (FIFFV_COORD_MRI)
Coordinate frames after saving:
4 (FIFFV_COORD_HEAD)

Process finished with exit code 0

I am indeed using 0.24. I am unfortunately currently trying to finalize a project and changing version right now would entail more checks than I have time to afford :confused:
Kind regards,
Alex

1 Like

I can reproduce the issue with MNE 1.0.3.

When simply saving / loading the DigMontage directly (i.e., not via Epochs), all works correctly:

# %%
from pathlib import Path
import mne
import numpy as np


# Create random montage:
montage = mne.channels.make_dig_montage(
    ch_pos={
        "ch_1": np.array([1, 2, 3]),
        "ch_2": np.array([4, 5, 6]),
        "ch_3": np.array([7, 8, 9])
    },
    coord_frame="mri"
)
path = Path('/tmp/montage.fif')
montage.save(path, overwrite=True) 

loaded_montage = mne.channels.read_dig_fif(path)
print(loaded_montage.dig[0]["coord_frame"])

produces

5 (FIFFV_COORD_MRI)

So something must be going wrong with the epochs roundtrip in your example.

Could you please open an issue on the GitHub issue tracker?

Thanks,
Richard

Thanks a lot for the feedback @richard,

I tried to do the following as well:

import mne
import numpy as np
from numpy.random import normal
import tempfile
import os
 
 
# Create random montage:
montage = mne.channels.make_dig_montage(ch_pos={"ch_1": np.array([1, 2, 3]), "ch_2": np.array([4, 5, 6]),
                                                "ch_3": np.array([7, 8, 9])}, coord_frame="mri")
 
# Create random data:
info = mne.create_info(ch_names=['ch_1', 'ch_2', 'ch_3'],
                       ch_types=['ecog'] * 3,
                       sfreq=100)
data = np.array([[normal(loc=0, scale=2, size=100), normal(loc=0, scale=2, size=100),
                   normal(loc=0, scale=2, size=100)],
                   [normal(loc=0, scale=2, size=100), normal(loc=0, scale=2, size=100),
                   normal(loc=0, scale=2, size=100)]])
epochs = mne.EpochsArray(data, info, tmin=-0.2, verbose="ERROR")
epochs.set_montage(montage, verbose="ERROR")
 
# Printing the epochs montage info:
print("Coordinate frames before saving:")
print(epochs.get_montage().dig[0]["coord_frame"])
 
# Creating a temporary directory
temp_dir = tempfile.mkdtemp()
# Generate a file name
fname = temp_dir + os.sep + "-epo.fif"
epochs.save(fname)
 
loaded_montage = mne.channels.read_dig_fif(fname)
print(loaded_montage.dig[0]["coord_frame"])

To see whether maybe the issue is just in the mne.read_epochs function, but the result is still the same. I will open an issue in github.

Kind regards,

Alex

1 Like

This is actually indended behavior:

MNE-Python prefers to have EEG channel locations in head space, always. So .apply_montage converts the montage to head space (if you get rid of the verbose='ERROR' you can see the warning message saying there are no fiducials so an identity transform is assumed). We probably want to keep this. If we allow electrodes to be in all kinds of coordinate spaces this means we have to verify the coordinate space every time we use channel locations.

But then this conversion should happen right away during setting of the montage, and not only during saving of the file to disk. So at least this discrepancy is a bug to me.

You’re right about that. I’ll look into it a little bit more.

1 Like

Thanks @wmvanvliet! LMK if you would like me to test something or help you out in some way. I don’t have much time to spare but I’m happy to help here and there if possible.