Disclaimer: I did not look at the code part. Running latest
plot_topomap, looking at the description of the argument
The outlines to be drawn. If ‘head’, the default head scheme will be drawn. If ‘skirt’ the head scheme will be drawn, but sensors are allowed to be plotted outside of the head circle.
So if I get it right,
'head' will crop the electrodes outside the head circle. Let’s give it a try, with
sphere='eeglab' to push sensors further away from the head circle.
import numpy as np
from matplotlib import pyplot as plt
from mne import create_info
from mne.channels import make_standard_montage
from mne.viz import plot_topomap
montage = make_standard_montage("standard_1020")
ch_names = [
ch for ch in montage.ch_names
if ch not in ("P7", "P8", "T3", "T5", "T4", "T6")
info = create_info(ch_names, 1, "eeg")
data = np.random.randn(len(ch_names))
f, ax = plt.subplots(1, 2)
plot_topomap(data, info, outlines="head", sphere="eeglab", axes=ax)
plot_topomap(data, info, outlines="skirt", sphere="eeglab", axes=ax)
I am not seeing the difference, are you?
I never understood this either. Never seemed to work for me… I wonder if @drammock or @cbrnr have an idea?
My advice would be to find a tutorial that demos both options (if it exists) and look at older versions of our docs until you find a version where they look different. That at least provides a starting point!
also tagging @mmagnuski on this one, as I think he may have the best / most recent grasp of the topomap plotting code.
Thanks for tagging me @drammock!
There was a time, when
outlines actually did make a difference, but since the restructuring of the topomap plotting lead by @larsoner (but I am also to blame )
outlines does not make a difference.
In old mne-topomap times the
outlines='head' positioned all channels within the head circle and cropped the interpolation limits where the circle ended. On the other hand,
outlines='skirt' allowed the channels and the interpolated map to extend beyond the head circle. This was because at that time how the channel positions were drawn with respect to the head outline was mostly dictated by aesthetic preferences (“do you prefer the channels to be packed inside the head outline?”). Currently they convey how sensors are placed with respect to the head, where head is represented by a sphere (in a vacuum ), whose position and radius is controlled by the
sphere argument. So
outlines should no longer make a difference.
So, if we want to stay consistent, I think we should remove the
outlines argument. This is the easiest way out. However, I saw some posts on this forum that suggest that some people prefer to have all the channels plotted inside the head outline (even if it makes the channel-outline relationship arbitrary) so we could also repurpose
outlines to match their needs (
outlines='head' could change the sphere position so that all channels are within the head outline).
I much prefer the simple solution of removing
outlines (plus maybe extending our tutorials/examples).
+1 to thus!! Thanks for the detailed explanation, @mmagnuski!
Thanks for the explanation, so it’s an argument forgotten from a refactoring. Looking at the code, the only difference it makes is here: mne-python/topomap.py at aef49669fe1bdf19221e03e85cf961671508e0bb · mne-tools/mne-python · GitHub
However, I saw some posts on this forum that suggest that some people prefer to have all the channels plotted inside the head outline (even if it makes the channel-outline relationship arbitrary) so we could also repurpose
outlines to match their needs (
outlines='head' could change the sphere position so that all channels are within the head outline)
Isn’t that what
sphere='auto' is supposed to do?
+1 to deprecate it as well, either as part of or after Standardize topomap args by drammock · Pull Request #11123 · mne-tools/mne-python · GitHub ? @drammock
As it turns out,
skirt are not the only options, you can also pass custom dicts or matplotlib patches (or callables that create patches). So in #11123 what I’m doing is just deprecating the value
skirt. So now the options are
'head' (what we’re all familiar with)
- a custom
dict (for heads of different shapes?)
- a patch or patch-returning callable (for really complicated masking?)
head is now the only allowed string value I wonder if it should become
'auto' (i.e., just use the standard, familiar, built-in head outline). WDYT?
also tagging @larsoner and @agramfort for opinions here.
I wouldn’t bother changing
'auto' but deprecating
skirt makes sense to me
The docstring does mention the
dict and the
patch-returning callable, but do we really support them?
dict: We don’t mention which keys have to be present, so except if someone digs in _make_head_outlines it’s hard to figure out what this
dict is suppose to look like. e.g. the key
clip_radius is required by _setup_interp.
Callable: looking again at _make_head_outlines, it’s structured as:
if outlines in ('head', 'skirt', None):
elif isinstance(outlines, dict):
raise ValueError('Invalid value for `outlines`.')
we need to keep supporting dict (at least in the short term) because many of our other functions internally call the (public)
plot_topomap function and pass in a dict for the
outlines argument. As for the patch: I think the docstring is unclear, the patch should be passed as part of the dict (at least if I’m reading the code correctly?) But I think both the docstring and that part of the code could use some love. #11123 is complex enough as it is, so I’ll probably do any patch/dict cleanup / simplification in a subsequent PR. @mscheltienne if you have time and inclination, feel free to look and propose a better docstring / code path for dict and patch handling.
Ok sure. In the long run, I think it would be better to replace the internal calls to
_plot_topomap, and thus be able to remove the
outlines argument entirely from the public API.
Of course no need to do all that in a single PR
For Patch, yes it looks like it has to be provided in the
dict, if I read
_get_patch correctly. There is no way anyone figures that out from the documentation. I can’t imagine a user providing that dictionary.
Maybe the dict should actually be a proper dataclass or at least a TypedDict so it’s obvious which fields must be present.
This is in general an approach I’d like to see adopted across MNE‘s code base.
I had the same thought, but yeah, not gonna tackle that in #11123
'auto' fits the sphere to the digitization points so you can have channels outside the head outline - because you will likely have some channels below the sphere origin after it has been fit.