mne-tools / mne-tools/mne-python
Relationship between Layout.box and Layout.pos, creating custom layout from 2D X/Y array ignores padding
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.5k
- Forks
- 1.6k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 100
Description
Proposed documentation enhancement
I'm struggling to debug a topographic plot, partially because I'm lacking information as to how the custom layout I provide is used by the viz functions. A layout has a box argument which defines the 'box' dimension (x_min, x_max, y_min, y_max) and a pos argument which defines the unit-normalized position of the channel.. as (x, y, width, height), i.e. it also includes the 'box' size in the form of width and height.
Does someone know why we have both present, I would like to include this explanation in the documentation in someform.
Next, those 'box-size' information are not even in sync. If I generate a 2D layout from an X/Y numpy array with:
layout = generate_2d_layout(pos, w=0.11, h=0.08, ch_names=raw.ch_names, name="custom", pad=0.2)
I get:
In [7]: layout.box
Out[7]: (0, 0, 0.06675489570238238, 0.08784025372548736)
In [8]: layout.pos
Out[8]:
array([[3.23252215e-01, 9.93798268e-01, 1.10000000e-01, 8.00000000e-02],
[4.99765412e-01, 1.00000000e+00, 1.10000000e-01, 8.00000000e-02],
...
The padding argument does impact layout.box but not layout.pos.
What's more, plotting a topographic plot with mne.viz.plot_evoked_topo and a custom layout using different pad argument has no impact.
Finally, for some reason despite generate_2d_layout supposely normalizing the x/y position, all the axes do not fit in the figure yielded by mne.viz.plot_evoked_topo.
Code snippet if someone wants to play with the vizualization code: (requires MNE-LSL as I was looking for a dataset with 64 channels in a 10/20 montage)
import numpy as np
from matplotlib import pyplot as plt
from mne import make_fixed_length_epochs
from mne.channels import generate_2d_layout
from mne.io import read_raw_fif
from mne.viz import plot_evoked_topo
from mne_lsl.datasets import sample
fname = sample.data_path() / "sample-ant-raw.fif"
raw = read_raw_fif(fname, preload=True)
raw.drop_channels(["TRIGGER", "hEOG", "ECG", "EDA", "vEOG"])
raw.add_reference_channels(["CPz"])
raw.drop_channels(["M1", "M2", "PO5", "PO6"])
# fmt: off
raw.reorder_channels(
[
"Fp1", "Fpz", "Fp2", "F7", "F3", "Fz", "F4", "F8", "FC5", "FC1", "FC2",
"FC6", "T7", "C3", "Cz", "C4", "T8", "CP5", "CP1", "CP2", "CP6", "P7",
"P3", "Pz", "P4", "P8", "POz", "O1", "O2", "AF7", "AF3", "AF4", "AF8",
"F5", "F1", "F2", "F6", "FC3", "FCz", "FC4", "C5", "C1", "C2", "C6",
"CP3", "CP4", "P5", "P1", "P2", "P6", "PO3", "PO4", "FT7", "FT8", "TP7",
"TP8", "PO7", "PO8", "Oz", "CPz",
]
)
# fmt: on
raw.set_eeg_reference("average")
raw.set_montage("standard_1020")
epochs1 = make_fixed_length_epochs(raw.copy().crop(0, 80), id=1, preload=True)
epochs2 = make_fixed_length_epochs(raw.copy().crop(60, 140), id=2, preload=True)
evokeds = [epochs1.average(), epochs2.average()]
# create a layout based on 'pos' in .get_montage().plot(kind="topomap", sphere="eeglab")
pos = np.load(r"pos.npy")
fig, ax = plt.subplots(1, 1)
ax.scatter(pos[:, 0], pos[:, 1])
layout = generate_2d_layout(
pos, w=0.11, h=0.08, ch_names=raw.ch_names, name="custom", pad=0.2
)
# create topographic plot
fig, ax = plt.subplots(1, 1, figsize=(10, 10), layout=None)
plot_evoked_topo(
evoked=evokeds,
layout=layout,
color=["#3498DB", "gray"],
title="test",
vline=[0, 0.080],
exclude=(),
legend=False,
axes=ax,
show=False,
)
plt.show()
The file pos.npy was obtained by looking through the code of raw.get_montage().plot(kind="topomap", sphere="eeglab").
Scatter plot of the X/Y position used to generate the layout:
Topographic plot:
I will try to have a look in the coming days, if anyone has hints or ideas, they will be more than welcome :)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the Layout handling used by generate_2d_layout and plot_evoked_topo, focusing on how Layout.box, Layout.pos, and pad are interpreted. Reproduce the example with pos.npy, then trace the relevant documentation and visualization code. Done means documenting the relationship between box and pos and clarifying the observed padding and figure-fitting behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- matplotlib, numpy, python
- Domain
- data-visualization, documentation
- Issue type
- Documentation
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100