Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spurious values when using plot_topomap #5315

Closed
cbrnr opened this issue Jul 3, 2018 · 34 comments
Closed

Spurious values when using plot_topomap #5315

cbrnr opened this issue Jul 3, 2018 · 34 comments
Milestone

Comments

@cbrnr
Copy link
Contributor

cbrnr commented Jul 3, 2018

When I plot negative values using mne.viz.plot_topomap, I get spurious positive values near the boundary:

import numpy as np
import mne


m = mne.channels.read_montage("biosemi64")

info = mne.create_info(m.ch_names[:-3], sfreq=512, ch_types="eeg", montage=m)
np.random.seed(1)
values = np.random.randint(-100, 0, 64)
mne.viz.plot_topomap(values, info)

figure_1

This is likely an interpolation artifact, with possibly serious consequences when interpreting scalp maps. Or maybe it is specific to the biosemi montages.

@cbrnr cbrnr changed the title Wrong values when using plot_topomap Spurious values when using plot_topomap Jul 3, 2018
@agramfort
Copy link
Member

agramfort commented Jul 3, 2018 via email

@jasmainak
Copy link
Member

one could use monotonic interpolation like pchip ...

@agramfort
Copy link
Member

agramfort commented Jul 3, 2018 via email

@cbrnr
Copy link
Contributor Author

cbrnr commented Jul 4, 2018

EEGLAB produces a very similar output.

Would it be possible to plot the image only within the outermost electrodes, that is, without extrapolation? Would that solve the problem? Basically if some kind of skirt can be defined (yellow lines), don't draw the map outside:

skirt

@agramfort
Copy link
Member

agramfort commented Jul 4, 2018 via email

@larsoner
Copy link
Member

larsoner commented Jul 4, 2018 via email

@cbrnr
Copy link
Contributor Author

cbrnr commented Jul 4, 2018

Alright, I assume you mean #5085 - I've added an item to the list.

@cbrnr
Copy link
Contributor Author

cbrnr commented Aug 6, 2018

Moved to #5085.

@larsoner
Copy link
Member

Reopening so I don't forget to add this since I closed the currently linked PR

@larsoner larsoner reopened this Aug 25, 2018
@larsoner larsoner added this to the 0.17 milestone Aug 25, 2018
@larsoner larsoner modified the milestones: 0.17, 0.18 Oct 4, 2018
@jsosulski
Copy link
Contributor

This issue is exacerbated especially in cases where electrodes are missing from a data set. Note the dark blue area on the right outside the convex set of the available sensor locations.

image

This is probably caused predominantly by the smoothness assumption of the used CloughTocher2DInterpolator. I modified topomap.py by moving the outer extreme points for interpolation nearer to the center and using a LinearNDInterpolator instead of the CloughTocher2DInterpolator, which helps a little but makes the whole plot look less smooth (aside the square look), but generally this still does not seem optimal.

image

@agramfort
Copy link
Member

agramfort commented Nov 16, 2018 via email

@jsosulski
Copy link
Contributor

When using https://github.com/bbci/bbci_public (a matlab toolbox), I can choose how to extrapolate outside of the hull of the electrodes, e.g. extrapolate to zero. I can upload a plot on Monday when I have access to the matlab installation.

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 19, 2018

Can you also try my example from the initial comment?

@jsosulski
Copy link
Contributor

For reference, when using the option to extrapolate to zero, with the matlab toolbox, my plot looks like this:

image

@cbrnr I generated the random numbers in python with the same seed you used and plotted it against the biosemi64 layout. Only the contour lines were set differently, but I do not know how the levels are calculated in either mne or bbci.

image

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 19, 2018

I assume you also used the MATLAB toolbox and interpolate to zero? This looks much better than what MNE currently produces, because there are no red patches (positive values) anywhere. I think extrapolating to zero might be great to have - can anyone think of when this could go wrong? @jsosulski can this be implemented in Python? The square look from your previous post doesn't look too appealing to me.

@jsosulski
Copy link
Contributor

Yes I used the matlab toolbox for the second plot as well.
I think the main difference is that the plot_scalp method uses a grid based approach to interpolation and extrapolation while mne uses Delaunay tessellation (I think?) where I do not know how this can be realized. Maybe adding 'support points' outside the available sensor locations with the explicit value of Zero would help?
Additionally the default CloughTocher2DInterpolator tries to produce smooth curves between data points which leads to the observed positive extrapolation areas in the boundary area.

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 19, 2018

How difficult would it be to switch to a grid-based approach?

@mmagnuski
Copy link
Member

Yes, CloughTocher uses Delaunay triangulation, below is the one I get by default for one of my caps:
image
Looks like a disco ball, and the extra edge points seem to be too far away: seeing this made me realize why I was seeing elongated/stretched artifacts in my topos as you can see below:
topo a
topo b

@larsoner
Copy link
Member

We can do better by, say, taking the convex hull of points and expanding it outward (by the median electrode distance?) to use as the boundary condition instead of just a large square. Done variant like this should alleviate the problems

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 28, 2018

Or can we mimic the approach that's implemented in this MATLAB toolbox?

@mmagnuski
Copy link
Member

mmagnuski commented Nov 28, 2018

@larsoner I was thinking about something like that. I first started by changing just the mask outline to the convex hull of all points but it seemed to also change how these extra boundary points are set:
image
I'll check that later today. In the meantime @cbrnr or someone else who has time could check that matlab implementation. We'll see what works better.

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 28, 2018

https://github.com/scot-dev/scot includes a topoplot function that uses a grid (I think). With the data above, I get this image:

figure_1

So obviously this extrapolates to positive values on the head border. I think what we really need is an option to specify the value on the head border (by default this value could be 0).

@mmagnuski
Copy link
Member

I have tested the approach with adding zero points based on extended convex hull vertices, but it is not ideal (I'd have to play with convhull parameters to get more points):
topo_conv_hull
Red dots are based on covex hull and moved slightly outwards. I've used griddata with cubic interpolation above so it is not identical to what mne uses.

@mmagnuski
Copy link
Member

It is a bit better if I add more external points (red dots) where the distance between them is too high:
topo_conv_hull

@jsosulski
Copy link
Contributor

@mmagnuski Does your example data have higher positive values on the outer electrodes on the left/right and back or is this again an interpolation artefact? (due to the smoothness requirement)

@cbrnr
Copy link
Contributor Author

cbrnr commented Nov 28, 2018

Visbrain also has a topoplot object. I tried to plot the random data from above, but didn't succeed (the resulting plot looked rather strange). In addition, I got a message saying that VisPy is not yet compatible with Matplotlib 2.2+, whatever that means. Anyway, maybe we can still have a look what they do. There is also the R package eegUtils, which also includes a topoplot function.

@mmagnuski
Copy link
Member

Yes, the values are higher around the edges:
image

@mmagnuski
Copy link
Member

@cbrnr I didn't know about visbrain, thanks!

@larsoner
Copy link
Member

@mmagnuski I like your second example. We just need to think about how to generalize it. Maybe take the convex hull and subdivide each edge by the (rounded) number of "average distance"s it is? I think it would produce something a lot like you have but be pretty general.

@mmagnuski
Copy link
Member

By average distance do you mean some function of distance between the channels (like the median you previously mentioned)? If so - I was thinking about something like that, but it was quicker to just divide the lines that were too long. I can try to hack some code into mne this evening just to open a PR where we can discuss this further. @cbrnr - let me know if you get some insight from how the other libs you've checked do topos, we can implement multiple options and compare (some may be better visually and some in terms of performance - and that makes a difference when plotting tens of topos like during inspection of ICA components(it is usually too slow for me)).

@larsoner
Copy link
Member

By average distance do you mean some function of distance between the channels (like the median you previously mentioned)?

Yes

it was quicker to just divide the lines that were too long.

Yeah it worked to demonstrate that it looks better if the lines are not too long

I can try to hack some code into mne this evening just to open a PR where we can discuss this further

Great. Hopefully with your changes and #5472 (and someday #5471) everyone will finally be happy with the topomap plotting :)

@larsoner
Copy link
Member

we can implement multiple options and compare (some may be better visually and some in terms of performance - and that makes a difference when plotting tens of topos like during inspection of ICA components(it is usually too slow for me)).

We need to profile the calls to see what is actually slow. I did some work a while ago and IIRC the limitation was matplotlib / the number of effective points used in pcolormesh or some similar call.

@mmagnuski
Copy link
Member

Great. Hopefully with your changes and #5472 (and someday #5471) everyone will finally be happy with the topomap plotting :)

Sure, I even imagine a future where one could change the nose of the topo head with a kwarg (with the current option being named 'pinokio' ;) ).

We need to profile the calls to see what is actually slow. I did some work a while ago and IIRC the limitation was matplotlib / the number of effective points used in pcolormesh or some similar call.

Right, compared to plotting time all the interpolation steps might not matter much. Do you know if matplotlib is seriously considering using openGL? As far as I know Bokeh has it on one of their priorities now.

@mmagnuski
Copy link
Member

I'm going to close this one as fixed with #5754. We can open another issue concerning possible differences stemming from the interpolation method used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants