visualizing sEEG electrode contacts (already in MNI space) on 'fsaverage' surface

Hi,

I am trying to visualize sEEG electrodes on the ‘fsaverage’ brain surface (following the “working with SEEG data” tutorial.).

I am stuck figuring out what coordinate frame transforms I need to apply to the montage – if at all – given that I have electrode locations already in the MNI space (which I believe is different from the tutorial).

Specifically, for each participant, I have a .csv file which contains the MNI coordinates (x, y, z) for each electrode on the sEEG shafts (Neuralynx seeg data). I do not have access to the individual subject’s recon-all data (electrode localization was done in a different lab). Example coordinate data looks like this (I am assuming these values must be millimeters):

print(coords)  # just a dataframe loaded via pd.read_csv
>>>
    Channels      MNI_1     MNI_2      MNI_3
0       LAMu -22.425952 -4.686374 -16.528827
1       LAM1 -25.003134 -4.285802 -19.544462
2       LAM2 -28.077597 -3.557858 -21.753622
3       LAM3 -33.196102 -1.944738 -21.954092
4       LAM4 -38.982658 -1.034873 -24.233653
..       ...        ...       ...        ...
103   RPINS4  32.841654 -3.428255  18.764148
104   RPINS5  31.769347 -1.041556  26.172012
105   RPINS6  31.037502  0.839024  33.411429
106   RPINS7  29.981158  3.350836  42.209800
107   RPINS8  29.024138  5.230155  49.399291

[108 rows x 4 columns]

Conceptually, given that my coordinates are already in the MNI space, I thought that the part of the tutorial that transforms the montage from the “head” → “mri” → “mni” coordinate systems would not be needed in my case.

So, I thought I would be able to create a montage from these values in the csv file (montage = mne.channels.create_dig_montage(coords_dict, "mni_tal"); epochs.set_montage(montage, on_missing='warn')) and plot on ‘fsaverage’ (full code below). But I am clearly missing something as the locations are misaligned and possibly also not correctly mapped left/right:

Plotting the montage itself (montage.plot(), looks more meaningful (at least the 3 shafts that have electrodes whose labels start with L* are on indeed on the left side):

The above suggest my electrode coordinate system is off. Any pointers on how to start troubleshooting this would be much appreciated! I don’t think I can share a MWE due to the nature of the data, but happy to provide more details if anything is unclear.

Full code snippet:

import mne
from mne.io import read_raw_fif
from mne.datasets import fetch_fsaverage
import pandas as pd

# input data
subjects_dir = "*/mne_data/MNE-fsaverage-data"
anat_csv = "/path/to/csv/file/with/mni/coordinates/coords_mni.csv"
raw = read_raw_fif("/path/to/seeg/sub-009_preproc_ieeg.fif")  # preprocessed seeg data

# epoch at start of audio playback
epochs = mne.Epochs(raw, event_id={"wav_playback"}, detrend=1, baseline=None)

# load the coordinate values
coords = pd.read_csv(anat_csv, sep="\t")
# only retain labels and x,y,z values
coords = coords[["Channels", "MNI_1", "MNI_2", "MNI_3"]] 

# convert channel coordinate values to meters
coords[["MNI_1", "MNI_2", "MNI_3"]] /= 1000
 # drop micro channels (labels start with `u`), not interested in these
coords = coords.loc[~coords["Channels"].str.contains("u")]

# create a dict for mne.channels.make_dig_montage()
ch_pos = dict(
    zip(
    coords["Channels"],
    coords[["MNI_1", "MNI_2", "MNI_3"]].to_numpy()
    )
)

# create montage
montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="mni_tal")

epochs.set_montage(montage, on_missing="warn")

brain = mne.viz.Brain(
    "fsaverage",
    subjects_dir=subjects_dir,
    background="white",
    units="m",
)
brain.add_sensors(epochs.info, 'fsaverage')

mne.sys_info()

Platform             Linux-5.10.102.1-microsoft-standard-WSL2-x86_64-with-glibc2.31
Python               3.11.8 | packaged by conda-forge | (main, Feb 16 2024, 20:53:32) [GCC 12.3.0]
Executable           /home/kriarm/miniconda3/envs/core/bin/python
CPU                  x86_64 (20 cores)
Memory               31.2 GB

Core
├☒ mne               1.7.0.dev158+g925f52282 (outdated, release 1.7.0 is available!)
├☑ numpy             1.26.4 (MKL 2022.1-Product with 10 threads)
├☑ scipy             1.12.0
└☑ matplotlib        3.8.3 (backend=module://matplotlib_inline.backend_inline)

Numerical (optional)
├☑ sklearn           1.4.1.post1
├☑ numba             0.59.0
├☑ nibabel           5.2.0
├☑ nilearn           0.10.2
├☑ dipy              1.9.0
├☑ openmeeg          2.5.7
├☑ pandas            2.2.1
├☑ h5io              0.1.9
├☑ h5py              3.10.0
└☐ unavailable       cupy

Visualization (optional)
├☑ pyvista           0.42.3 (OpenGL 4.5 (Core Profile) Mesa 20.2.6 via llvmpipe (LLVM 11.0.0, 256 bits))
├☑ pyvistaqt         0.11.0
├☑ vtk               9.2.6
├☑ qtpy              2.4.1 (PyQt5=5.15.8)
├☑ ipympl            0.9.3
├☑ pyqtgraph         0.13.3
├☑ mne-qt-browser    0.6.1
├☑ ipywidgets        8.1.1
├☑ trame_client      2.12.6
├☑ trame_server      2.12.1
├☑ trame_vtk         2.6.1
└☑ trame_vuetify     2.3.1

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

To update to the latest supported release version to get bugfixes and improvements, visit https://mne.tools/stable/install/updating.html

For posterity, will just post that plotting my MNI electrode locations on 'fsaverage' surface directly via brain.add_foci() instead of going via brain.add_sensors() seemed to have worked for me in the end (code and output below).

It does seem, given the tutorial, that brain.add_sensors() would be the preferred MNE way, however, but I couldn’t quite wrap my head around all the coordinate systems transforms. Still happy to receive pointers, though.

Full code and output

  SUBJECTS_DIR = "*/mne_data/MNE-fsaverage-data"
  
  # select left/right
  # (see `coords` data frame in first message)
  left_chans = coords.Channels.str.startswith("L")
  right_chans = coords.Channels.str.startswith("R")

  # convert to (n_elecs, 3) arrays
  xyz_left = coords.loc[left_chans, ["MNI_1", "MNI_2", "MNI_3"]].to_numpy()
  xyz_right = coords.loc[right_chans, ["MNI_1", "MNI_2", "MNI_3"]].to_numpy()

  brain = mne.viz.Brain(
      subject="fsaverage",
      cortex="low_contrast",
      hemi='split',
      alpha=0.25,
      background="white",
      subjects_dir=SUBJECTS_DIR,
      size=(1600, 800),
      show=True,
  )

  brain.add_foci(
      xyz_left,
      hemi="lh",
      color="yellow",
      scale_factor=0.3
  )

  brain.add_foci(
      xyz_right,
      hemi="rh",
      color="yellow",
      scale_factor=0.3
  )

2 Likes

Hi,
I think there is some issue in your plotting. The SEEG is in MNI space, but fsaverage is not. Shouldn’t you put them into the same space before plotting?
Correct me if I am wrong. Thanks