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

WIP epochs.plot_image(gfp=True) #4227

Closed

Conversation

jona-sassenhagen
Copy link
Contributor

I'm still annoyed by the fact that epochs.plot_image throws so many figures at you.

This allows one to plot the single-trial GFP.

@codecov-io
Copy link

codecov-io commented Apr 26, 2017

Codecov Report

Merging #4227 into master will decrease coverage by 0.04%.
The diff coverage is 90.26%.

@@            Coverage Diff            @@
##           master   #4227      +/-   ##
=========================================
- Coverage   86.24%   86.2%   -0.05%     
=========================================
  Files         357     358       +1     
  Lines       64566   65037     +471     
  Branches     9831    9922      +91     
=========================================
+ Hits        55685   56064     +379     
- Misses       6180    6237      +57     
- Partials     2701    2736      +35

@jona-sassenhagen
Copy link
Contributor Author

What do people think? Do we even want an epochs.plot_image(gfp=True) method?

I like it because I like "survey"-style plots that give you a super fast impression when called with defaults, and otherwise you always have to specify picks and you don't get the full answer either.

Also we should have epochs.plot_joint_image() (e.g. erpimage with joint topoplots), and for that we need this anyways.

@agramfort
Copy link
Member

agramfort commented Apr 27, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

Alternative API suggestion, inspired by the tfr.plot_joint API:

epochs.plot_image(aggregate='gfp')
epochs.plot_image(aggregate=np.std)
epochs.plot_image(aggregate=np.mean)
epochs.plot_image(aggregate=np.median)

(vs. epochs.plot_image(gfp=True))

Thoughts?

Copy link
Member

@teonbrooks teonbrooks left a comment

Choose a reason for hiding this comment

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

could you add an image of the plot when you have a chance? would be cool to check it out

@@ -65,7 +65,7 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None,
The scalings of the channel types to be applied for plotting.
If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15,
eog=1e6)`.
cmap : matplotlib colormap | (colormap, bool) | 'interactive'
cmap : None | matplotlib colormap | (colormap, bool) | 'interactive'
Copy link
Member

Choose a reason for hiding this comment

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

what does none yield?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, need to doc this ...
The point is, for the GFP we don't need a diverging cmap (None -> Reds), so I've switched from the RdBu_r default to None.

gfp : bool
If True, plot the Global Field Power (one figure per channel type).
The GFP is calculated on a single-trial basis for the image, but the
time series plot below shows the GFP of the average across epochs,
Copy link
Member

Choose a reason for hiding this comment

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

just curious, why on the average of epochs instead of the average of individual GFPs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No reason I think. I wrote the code this way without thinking, then realized that there are two possible options, and decided to just doc the one I chose. If you think it should go the other way around, I'll do it like that.

I should probably reword it though. It's not the most obvious way of saying "data.std(ch_ax).mean(trial_ax) vs. data.mean(trial_ax).std(ch_ax)".

Copy link
Member

Choose a reason for hiding this comment

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

"data.std(ch_ax).mean(trial_ax) vs. data.mean(trial_ax).std(ch_ax)".

that clarifies it much better

@mmagnuski
Copy link
Member

I like aggregate option - would use it with picks often. I prefer reduction as the kwarg name though (which reminds me of my neglected welch reduction PR :) )

@agramfort
Copy link
Member

agramfort commented Apr 28, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

@agramfort I sometimes stuff model fits or similar stuff into mne insts for plotting convenience. If your values are e.g. r or R^2, the std doesn't make so much sense anymore, but maybe the median or some other metric would.

Or what if the data isn't averaged to a common ref anymore, but still on a common scale (e.g. the bipolar stuff)? Essentially a bunch of data transformations might require a different measure.

Not saying it's super important, but it's only like 3 lines of code, 2 lines of doc and 2 lines of test so ...

@jona-sassenhagen
Copy link
Contributor Author

... any chance of having this one to actually default to gfp=True (or aggregate='gfp') in the future?

Reason: if called without specifying picks, the user gets 5 figures. That's a lot of plots, and the first 5 are usually far from the most important. The GFP is only 1-3 figures, and typically a lot more informative.

I doubt it would hurt any existing pipelines, because these probably don't rely on leaving picks unspecified and we changed the behavior for this case anyways a few releases ago.

@jona-sassenhagen
Copy link
Contributor Author

unknown-248

@teonbrooks

(This is a very high SNR data set though.)

Actually, "inferno" or 'viridis' look much better than "Reds" for a lot of my tests

@jona-sassenhagen
Copy link
Contributor Author

Votes on:

  1. defaulting epochs.plot_image to plotting the gfp if no picks are given, yes/no?

  2. having an aggregate param instead of a gfp param, yes/no?

  3. name for the aggregate param? aggregate vs. reduce vs. ..? (In any case, if [MRG] Adding AverageTFR.plot_joint() method #4134 is merged first, I will follow its lead!)

I'm for the 1st option in all cases.

evoked = epochs.average()
for ch_type in types_:
picks_ = list(picks[types == ch_type])
data_ = data[:, picks_, :].std(1)
Copy link
Member

Choose a reason for hiding this comment

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

you should not use std as you you don't want to subtract the mean for GFP

@agramfort
Copy link
Member

giving a try to this PR gives me:

In [2]: epochs.plot_image(gfp=True)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-2-a38d371c8036> in <module>()
----> 1 epochs.plot_image(gfp=True)

/Users/alex/work/src/mne-python/mne/epochs.py in plot_image(self, picks, sigma, vmin, vmax, colorbar, order, show, units, scalings, cmap, fig, axes, overlay_times, gfp)
   1009                                  show=show, units=units, scalings=scalings,
   1010                                  cmap=cmap, fig=fig, axes=axes,
-> 1011                                  overlay_times=overlay_times, gfp=gfp)
   1012
   1013     @verbose

/Users/alex/work/src/mne-python/mne/viz/epochs.py in plot_epochs_image(epochs, picks, sigma, vmin, vmax, colorbar, order, show, units, scalings, cmap, fig, axes, overlay_times, gfp)
    138             data_ = data[:, picks_, :].std(1)
    139             data_per_type.append(data_)
--> 140             evoked_data = scalings[ch_type] * evoked.data[picks_, :].std(0)
    141             evokeds.append(evoked_data)
    142         data = np.asarray(data_per_type)

IndexError: index 366 is out of bounds for axis 0 with size 366

I just tried

epochs.plot_image(gfp=True)

after running %run plot_visualize_epochs.py

@agramfort
Copy link
Member

agramfort commented May 1, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

group_by='selection' would be for interactive mode?

@agramfort
Copy link
Member

agramfort commented May 4, 2017 via email

@schekroud
Copy link

Sorry if misunderstanding conv up to now but was directed here to provide input if possible (and doesn't seem to be in same train of thought? could be wrong)

is it possible to have a: epochs.plot_image(average_over_picks = bool) param where instead of plotting all chans in picks, you plot one image of average voltage/field at all chosen channels? sometimes valuable if looking at broader cortical (e.g. visual) response ?
would require iterating over epochs to calculate evoked for average of channels, then plotting in exact same way as current function?

something like:

e.g. use:
mne.epochs.plot_image(epochs, picks = picks, average_over_picks=True) and do something like this within the function....

n_epochs = np.shape(epochs)[0] # number of epochs in struct
selected = []
for i in range(len(n_epochs)):
    i_evoked = epochs[i].average() #has a shape of [nchans,timesamples]
    picked = i_evoked.data[picks,:] #get data from chosen channels
    chanaverage = np.nanmean(picked,0) #average across chosen channels
    selected.append(chanaverage) #add single epoch data, averaged across chosen chans -> new array

I could be on the wrong path here, but would create new array of single epoch data with only one 'channel' (average of picked channels) which could be stored back as an EpochArray to feed into the original epochs.plot_image code?

@jona-sassenhagen
Copy link
Contributor Author

jona-sassenhagen commented May 5, 2017 via email

@schekroud
Copy link

ah ok, makes sense. mean would keep consistency with other non-python packages like FieldTrip, no? useful if people are working in both alongside each other as a sanity check (this is happening in my lab as a couple of us are trying to move people away from matlab...)

@jona-sassenhagen
Copy link
Contributor Author

I'm sure if we can convince a few MATLAB users to switch with that, it'll be possible to implement this :)

@jona-sassenhagen
Copy link
Contributor Author

@agramfort I finally tried the group_by thing in plot_raw and 1. we should have this for 10/20 data too (it's a predictable mapping from name to ROI) and I'd like to do that eventually, 2. i think that should be parallel to the aggregation.

partially because if you have a ROI-like parameter, grouping by ROI means aggregating via the mean actually makes sense and is very common. E.g.,

evoked.plot_image(group_by=my_ROI_dict, aggregate="mean")

Also you have to admit switching between grouping channels to aggregating over them via the GFP is really overloading the group_by keyword :)

@jona-sassenhagen
Copy link
Contributor Author

jona-sassenhagen commented May 9, 2017

Btw @schekroud don't use this style in Python:

n_epochs = np.shape(epochs)[0] # number of epochs in struct
selected = []
for i in range(len(n_epochs)):
    i_evoked = epochs[i].average() #has a shape of [nchans,timesamples]
    ...
    selected.append(some_function(i_evoked))

Almost always, you can do something like this:

for item in iterable_of_items:
    do_something_with(item)

And usually

collection = [do_something_with(item) for item in iterable_of_items]

That's much more readable!

In this case, I think you want something like:

[evoked.data.mean(0) for evoked in epochs.iter_evoked()]

or just straight-forward

epochs.get_data().mean(1)

This would give you the data averaged over channels.

My idea for this function is to have:

epochs.plot_image(aggregate=agg_kind)

where agg_kind can be False (for current behavior), 'gfp', or e.g. a function (e.g. np.mean) that will be applied to aggregate over channels.

@agramfort
Copy link
Member

agramfort commented May 9, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

You mean you'd be okay with something like:

evoked.plot_image(group_by=my_ROI_dict, reduce="gfp")

evoked.plot_image(group_by='type', reduce=np.mean)

?

@agramfort
Copy link
Member

agramfort commented May 9, 2017 via email

@schekroud
Copy link

thanks for the tips - only been python-ing for a (relatively) short period so brevity of code is an issue...

maybe grouping_measure is a better (read: less geeky) term than aggregate?
epochs.plot_image(group_by = ROI_dict, grouping_measure = 'gfp')
epochs.plot_image(group_by = ROI_dict, grouping_measure = np.mean)

@jona-sassenhagen
Copy link
Contributor Author

@agramfort I think I'm the only one who prefers aggregate :)

We have mne.combine_evokeds, so combine is another option.

@schekroud grouping_measure is too long IMO and also the wrong term- the grouping separates channels from each other, the aggregation reduces them.

@agramfort
Copy link
Member

agramfort commented May 9, 2017 via email

@teonbrooks
Copy link
Member

My 2c rankings:

  1. aggregate
  2. combine
  3. groupby

@agramfort
Copy link
Member

agramfort commented May 11, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

Oh god, we need some consensus here :) So what do we call this param? @jaeilepp maybe you have an opinion?

Copy link
Contributor

@jaeilepp jaeilepp left a comment

Choose a reason for hiding this comment

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

My vote goes for combine.

ax3 = axes[-1]
evoked = epochs.average(picks)
data = epochs.get_data()[:, picks, :]
axes += axes[-1]
Copy link
Contributor

Choose a reason for hiding this comment

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

What is going on here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't review the code right now, I've been putting it on hold until we decide on the API.

@jona-sassenhagen
Copy link
Contributor Author

Ok, then I'll do combine unless anybody has strong objections.

@agramfort
Copy link
Member

agramfort commented May 11, 2017 via email

@jona-sassenhagen
Copy link
Contributor Author

jona-sassenhagen commented May 11, 2017

Should it default to None or to 'type' though? I guess 'type'?

@mmagnuski
Copy link
Member

no strong feelings, combine is ok. I'll try to revive my welch-reduction PR in a few days and I'll change reduction to combine then.

@larsoner
Copy link
Member

I do like "reduce" because it's standard to talk about map-reduce operations, but I think "combine" is more common in MNE so +1 for that from me

@jona-sassenhagen
Copy link
Contributor Author

I'm working on this again ... next thing I'll do.

@jona-sassenhagen
Copy link
Contributor Author

Did something very weird. I'll open a new PR.

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

8 participants