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

MRG: Faster topomaps #4224

Merged
merged 3 commits into from May 1, 2017
Merged

MRG: Faster topomaps #4224

merged 3 commits into from May 1, 2017

Conversation

larsoner
Copy link
Member

This is an effort to make our topomap plotting faster interactively and in testing. cc @jona-sassenhagen can you push your changes to the tests?

Still WIP because I need to modify the picking to use PatchCollection (which I think should be possible) instead of iteration over Circle objects. @jaeilepp did you ever try this approach?

@jona-sassenhagen
Copy link
Contributor

@jona-sassenhagen can you push your changes to the tests?

I'm still profiling, sorry ...

@larsoner
Copy link
Member Author

Okay, well if you get anything useful let me know. In the meantime feel free to try this branch, too.

@codecov-io
Copy link

codecov-io commented Apr 25, 2017

Codecov Report

Merging #4224 into master will decrease coverage by 0.04%.
The diff coverage is 90%.

@@            Coverage Diff             @@
##           master    #4224      +/-   ##
==========================================
- Coverage   86.23%   86.19%   -0.05%     
==========================================
  Files         357      358       +1     
  Lines       64558    65004     +446     
  Branches     9831     9911      +80     
==========================================
+ Hits        55673    56030     +357     
- Misses       6182     6242      +60     
- Partials     2703     2732      +29

@larsoner
Copy link
Member Author

Looks like it successfully cut down testing time by about 5 minutes. The longest topomap test is now only 15 sec:

https://travis-ci.org/mne-tools/mne-python/jobs/225709024#L3282

@jona-sassenhagen
Copy link
Contributor

Awesome! With a bit of luck I'll be able to shave off another second or two :D

@larsoner
Copy link
Member Author

@jaeilepp I'm not going to work on this until tomorrow sometime, so if you feel like messing with the PatchCollection selection stuff in the meantime, feel free.

from ..viz import plot_tfr_topomap
if abs(eclick.x - erelease.x) < .1 or abs(eclick.y - erelease.y) < .1:
return
plt.ion() # turn interactive mode on
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should mess with matplotlib interactive status. Messing with global states is not a nice thing for a package to do.

It would be better to document any functions that need interactivity, telling people to activate interactive mode (maybe with docs somewhere about how to do it).

@larsoner
Copy link
Member Author

@kingjr feel free to try this to see if it works any better for you. If not, let me know which call(s) are slow for you.

for x, y in zip(pos_x, pos_y):
ax.add_artist(Circle(xy=(x, y), radius=0.003, color='k'))
patches += [Circle(xy=(x, y), radius=0.003)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not

patches = [Circle(xy=(x, y), radius=0.003) for x, y in zip(pos_x, pos_y)]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's better, just bad translation by me. Actually I'm not sure if this is really any better than ax.plot(), I should probably try that, too. (Need to check and see if the subset of selected points is also accessible there, though, just like with PatchCollection, though.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably benchmark all 3(+?) methods we're using:
ax.plot
patches
ax.scatter

for x, y in zip(pos_x, pos_y):
ax.add_artist(Circle(xy=(x, y), radius=0.003, color='k'))
patches += [Circle(xy=(x, y), radius=0.003)]
ax.add_collection(PatchCollection(patches, color='k'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaeilepp it's probably suboptimal that the legend for spatial_colors and this one use different code ...

spatial_colors doing ax.scatter(pos_x, pos_y, color=colors, s=25, marker='.', zorder=1)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is that it has to do with selection. These sensors need to be Lasso-able. The existing code makes it easy. But I think it should be possible at least with PatchCollection if not also whatever scatter returns with a bit of extra logic at the selection end.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... although we'd have to check to see if you can do point-by-point color with scatter like you can with PatchCollection

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand you correctly, but the (colored) legend for plot_joint is currently done with scatter.

Checking the history, your guess seems correct: I submitted this with add_artist patches and @jaeilepp turned it into scatter when he added interactivity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you must have this backward, because right now it's add_artist, so you must have done scatter and @jaeilepp did add_artist...

But in any case, we should figure out which is fastest among ax.plot, ax.scatter, and PatchCollection. Then we can try from fastest to slowest to see if sub-selection can be done. Do you have time to do some profiling?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you must have this backward, because right now it's add_artis

I meant this:
https://github.com/mne-tools/mne-python/blob/master/mne/viz/evoked.py#L167

Do you have time to do some profiling?

Not today, I got to sleep :) I can set up a preliminary benchmark, but nothing thorough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if you have time tomorrow morning feel free, otherwise I'll give it a shot

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scatter option might give another decent boost.
Though most of the test comes from creating (and perhaps closing) the new axes, not the actual plot (it obscures the differences between the actual sensor plotting: I think scatter is actually ~2x as fast as the patches, not just a bit.)
So then it's possible we could save a lot with the axes divider pattern ..?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also matplotlib/matplotlib#6664. If it were faster to make axes I wouldn't have had to do crazy refactoring of the topo code to use a single Axes, either. But in the meantime, yeah using a single axes instance could help. But let's do it in another PR.

@larsoner
Copy link
Member Author

So scatter, plot, PatchCollection, and master ordered by speed. I don't think plot allows per-vertex colors but scatter and PatchCollection do. So now we just need to check if those (with scatter preferred) when picked give the indices of the subselection.

@jaeilepp
Copy link
Contributor

Still WIP because I need to modify the picking to use PatchCollection (which I think should be possible) instead of iteration over Circle objects. @jaeilepp did you ever try this approach?

No, I haven't really touched the topomap code too much.

@kingjr
Copy link
Member

kingjr commented Apr 26, 2017

Seems like a x2 gain for me. Have you tried on the slider? There the gain should be noticeable.

@larsoner
Copy link
Member Author

larsoner commented Apr 26, 2017 via email

@kingjr
Copy link
Member

kingjr commented Apr 27, 2017

Is that 2x enough to be happy, be acceptable, or be better but still crappy?

yes, it's a good start

http://mne-tools.github.io/dev/auto_examples/visualization/make_report.html?highlight=slider

@larsoner
Copy link
Member Author

Okay now we use SciPy for the interpolation instead of using our own in-house radial basis function interpolation. I changed it not to use Rbf at all because it's slower than CloughTocher2DInterpolator but otherwise looks the same. In any case, things should be faster, especially in the case of animations and hopefully also in interactive mode (because we configure the interpolator once, more or less, instead of multiple times). Now the overhead is mostly in the plotting functions, and isn't too bad.

The example you listed @kingjr now rapidly pops up figures for me so I think the overhead is now mostly in figure/axis creation. Can you see if it got any faster for you?

Now I think I just need to make the selection bits work now that we use scatter, but it would be good if @jona-sassenhagen you could try it to make sure it's working well at your end, too.

@larsoner larsoner changed the title WIP: Faster topomaps (hopefully) MRG: Faster topomaps (hopefully) Apr 28, 2017
@jona-sassenhagen
Copy link
Contributor

@Eric89GXL I never use interactive functions, can't say anything to these.

In the notebook, I'm seeing a sizeable 30-50% speedup.

@larsoner
Copy link
Member Author

Okay, selection works now. Ready for review/merge from my end.

"""

def __init__(self, pos):
from scipy.spatial.qhull import Delaunay
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhhhh Delaunay again, I recognize this now! This is like a second math ed to me!

@larsoner larsoner changed the title MRG: Faster topomaps (hopefully) MRG: Faster topomaps Apr 28, 2017
for x, y in zip(pos_x, pos_y):
ax.add_artist(Circle(xy=(x, y), radius=0.003, color='k'))
ax.scatter(pos_x, pos_y, s=0.25, marker='o',
edgecolor=['k'] * len(pos_x), facecolor='none')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edgecolor='k'?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this explicit list-mode because we set them independently elsewhere later. (Also, it's possible that this might help mpl optimize for such a state we expect could come later...)

@agramfort
Copy link
Member

agramfort commented Apr 28, 2017 via email

@larsoner
Copy link
Member Author

I tried to make an example:

import mne
evoked = mne.read_evokeds(mne.datasets.testing.data_path() +
                          '/MEG/sample/sample_audvis-ave.fif')[0]
evoked.pick_types(meg=False, eeg=True)
evoked.pick_channels(evoked.ch_names[:10])
evoked.plot_topomap()

But even on master our (misleading) re-centering / re-scaling makes it weird (would have expected all the electrodes to be at the front of the head, not distributed all over):
screenshot from 2017-04-28 15-55-19
It's not changed much by this PR:
screenshot from 2017-04-28 15-55-06
Let me know if you have a better example...

@agramfort
Copy link
Member

agramfort commented Apr 29, 2017 via email

@larsoner
Copy link
Member Author

larsoner commented Apr 29, 2017 via email

@jaeilepp jaeilepp merged commit a42677f into mne-tools:master May 1, 2017
@jaeilepp
Copy link
Contributor

jaeilepp commented May 1, 2017

Thanks @Eric89GXL

@mmagnuski
Copy link
Member

But even on master our (misleading) re-centering / re-scaling makes it weird (would have expected all the electrodes to be at the front of the head, not distributed all over)

I also don't like how channels are re-scaled to match the whole head - are there plans to change it?

@jona-sassenhagen
Copy link
Contributor

jona-sassenhagen commented May 1, 2017 via email

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 this pull request may close these issues.

None yet

7 participants