mne-tools / mne-python

MNE: Magnetoencephalography (MEG) and Electroencephalography (EEG) in Python
https://mne.tools
BSD 3-Clause "New" or "Revised" License
2.63k stars 1.31k forks source link

plot_topo doesn't work for custom MEG layout #11732

Open dasdiptyajit opened 1 year ago

dasdiptyajit commented 1 year ago

Description of the problem

The issue is described in: https://mne.discourse.group/t/converting-gradiometers-to-virtual-magnetometers-for-neuromag-122/7056/6?u=dasdiptyajit

In short: During creating 2D custom layout, the API mne.channels.generate_2d_layout expects a continuous strings (e.g., without a space) for a channel name. For example, if the channel name is MAG 000 instead of MAG000, API plot_topo doesn’t work (i.e., creates an empty figure)

Steps to reproduce

"""
Test case: create custom layout and plot topoplot of evoked data
"""

# modules
import os
import numpy as np
import mne

# Create simulated evoked with 5 magnetometers
n_channels = 5
sampling_freq = 1000  # in Hertz
ch_names = [f"MAG{n:03}" for n in range(n_channels)]
ch_types = ["mag"] * n_channels
info = mne.create_info(ch_names, ch_types=ch_types, sfreq=sampling_freq)
times = np.linspace(0, 0.5, sampling_freq, endpoint=False)
data = np.random.rand(n_channels, len(times)) * 1e-12  # mag channels scale
simulated_evoked = mne.EvokedArray(data, info)
simulated_evoked.filter(1., 40.)

# define meg sample data
sample_data_folder = mne.datasets.sample.data_path()
sample_data_fname = os.path.join(sample_data_folder, "MEG", "sample", 'sample_audvis-ave.fif')
evk_vec_mag = mne.read_evokeds(sample_data_fname)[0]
evk_vec_mag = evk_vec_mag.copy().pick_types(meg='mag')

# create a custom layout for simulated evoked,
# we will first extract xy coordinates from sample data

chs = evk_vec_mag.info["chs"][:n_channels]  # extract only 5 channels positions
xy_pos = np.empty((len(chs), 2))
for ci, ch in enumerate(chs):
    xy_pos[ci] = ch["loc"][:2]

# we need picks indices for 2D layout
picks = mne.pick_channels(ch_names=info["ch_names"], include=[]).tolist()

# create layout with original channel names and plot topomap
custom_lay1 = mne.channels.generate_2d_layout(xy=xy_pos, ch_names=ch_names, ch_indices=picks,
                                              name='custom_layout1', normalize=True)
custom_lay1.plot()
simulated_evoked.plot_topo(layout=custom_lay1)

# channel names with spaces # error case
ch_names_spaces = [f"MAG {n:03}_v" for n in range(n_channels)]  # error occurs

# create layout with modified channel names and plot topomap
custom_lay2 = mne.channels.generate_2d_layout(xy=xy_pos, ch_names=ch_names_spaces, ch_indices=picks,
                                              name='custom_layout2', normalize=True)
custom_lay2.plot()
simulated_evoked.plot_topo(layout=custom_lay2)

Link to data

No response

Expected results

simulated_evoked.plot_topo(layout=custom_lay2) should return similar results as in simulated_evoked.plot_topo(layout=custom_lay1)

Actual results

simulated_evoked.plot_topo(layout=custom_lay2) returns an empty figure.

Additional information

Step1: Simulates some evoked data for magnetometers (channel locations are missing) Setp2: Extracts xy coordinates from MNE example data to create a layout for simulated data Step3: tests the plot_topo API with 2 custom layouts. (one with original channel names and another with spaces)

dasdiptyajit commented 1 year ago

@larsoner generate_2d_layout.pos doesn't return the same/similar matrix as it's for a build-in Layout. Is it because the function uses only 2D coordinates instead of 3D? It feels odd to me since the channel position differs quite a lot when you make a custom 2D layout for MEG. Any idea about it?

larsoner commented 1 year ago

The Neuromag layout wasn't generated using generate_2d_layout. I'm not sure how it was generated actually... but it's designed to be "nice" for the Neuromag system. The Neuromag system has sensor triplets all colocated at the same x/y/z. I don't think the automated generate_2d_layout deals with this in any smart way, it probably just puts them all in the same location. The custom/builtin layout splits them so two are to the left and one to the right for example.

dasdiptyajit commented 1 year ago

Yes, you are right! generate_2d_layout puts the pairs (Neuromag_122) in the same location, hence both sensors overlap. In-built layout is designed differently somehow. Thanks for the reply.