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

ERP Measurement Tool for MNE #7848

Open
jdkoen opened this issue May 28, 2020 · 40 comments
Open

ERP Measurement Tool for MNE #7848

jdkoen opened this issue May 28, 2020 · 40 comments
Milestone

Comments

@jdkoen
Copy link
Contributor

jdkoen commented May 28, 2020

The Evoked object only has methods to return peak amplitude and latency at present. Is there any interest in the development of additional amplitude and latency measures (e.g., average amplitude in a time window, fractional peak latency)? My thought would be to port the some of the measures implemented in ERPLAB's ERP Measurement Tool to MNE to help facilitate ERP analysis using MNE.

I am happy to lead development of this. These features could be added as methods to the Evoked object, or alternatively be included as a separate library. Is there a preference on which route is best?

Also, while I have coded in Python before, this will be my first time contributing to any Python software development. Thus, I would likely need some help and guidance on navigating development of unit tests and other aspects of contributing.

@agramfort
Copy link
Member

hi @jdkoen my suggestion would be to start small with what you consider the most key feature of ERPLAB. What would that be? We'll be happy to support you.

@jdkoen
Copy link
Contributor Author

jdkoen commented May 28, 2020

Thanks @agramfort! One of the key features (at least to me) is pulling out mean amplitude in a user defined time window. I will start with that, and post here if I have questions or need help.

@agramfort
Copy link
Member

agramfort commented May 29, 2020 via email

@jdkoen
Copy link
Contributor Author

jdkoen commented May 29, 2020 via email

@drammock
Copy link
Member

@jdkoen for a peak over a subset of channels, there are methods evoked.pick, evoked.pick_channels, and evoked.pick_types; these return an evoked instance with a reduced number of channels, so they can be chained with get_peak like so: evoked.copy().pick_channels(['Cz', 'Fz', 'C3', 'C4']).get_peak()

Note the use of copy() because otherwise the various pick methods will modify evoked in place.

@jdkoen
Copy link
Contributor Author

jdkoen commented May 29, 2020

Thanks @drammock for that feedback. That approach just slipped my mind. Makes a lot more sense now for how to format the mean signal measure I'm trying to add.

@mmagnuski
Copy link
Member

One of the key features (at least to me) is pulling out mean amplitude in a user defined time window.

Chaining can be useful here too:

evoked.copy().crop(tmin=0.09, tmax=0.11).pick_channels(channel_list).data.mean()

will give you average over specified time window and channel_list.

Maybe a good first step would be to add a short example/tutorial dealing with just a few of these measures and then we'll see if some turn out to be too complex to obtain with what is available in mne now.

@larsoner
Copy link
Member

larsoner commented Jun 1, 2020

Maybe a good first step would be to add a short example/tutorial dealing with just a few of these measures and then we'll see if some turn out to be too complex to obtain with what is available in mne now.

I think we should make a tutorial on ERP-style analyses that contain 90% of the analyses people would be interested in. Even if it's long, a .. contents:: at the top will make it easy for people to find what they're looking for. This exercise would make it easy to see what we should turn into a separate function in MNE, if anything.

@larsoner
Copy link
Member

@sappelhoff or @mmagnuski up for coding this in the next couple of weeks by any chance?

@larsoner larsoner added this to the 0.21 milestone Aug 24, 2020
@sappelhoff
Copy link
Member

I think we should make a tutorial on ERP-style analyses that contain 90% of the analyses people would be interested in. Even if it's long, a .. contents:: at the top will make it easy for people to find what they're looking for.

I am pretty packed so would be grateful if somebody else can take the time. But I agree that this would be very useful to have.

@mmagnuski
Copy link
Member

@larsoner Sorry, I won't be able to do this for the next release. But I will have more time in about 1.5 months - then I could help with such tutorial. :)

@larsoner
Copy link
Member

Sounds good. Or @jdkoen if you've made some progress, you could also take a stab at it if you want!

@jdkoen
Copy link
Contributor Author

jdkoen commented Aug 25, 2020 via email

@jdkoen
Copy link
Contributor Author

jdkoen commented Aug 31, 2020 via email

@larsoner
Copy link
Member

I would start with some minimal set and we can always add more later. No need to be feature-complete from the start. So this seems fine.

One other one I see/use from time to time is area under the curve, but again we can add that later

@larsoner
Copy link
Member

larsoner commented Sep 1, 2020

Just a cross-ref here that we should bring up the "head" coordinate frame standard in MNE in this tutorial as well (see #7455 for example)

@larsoner
Copy link
Member

larsoner commented Sep 9, 2020

Pushing the milestone to 0.22 for this one so it's not a blocker, but @jdkoen there is still time to get this in for 0.21 if you have time to make a push in the next few days

@larsoner larsoner modified the milestones: 0.21, 0.22 Sep 9, 2020
@jdkoen
Copy link
Contributor Author

jdkoen commented Sep 14, 2020 via email

@larsoner
Copy link
Member

larsoner commented Dec 1, 2020

@jdkoen we might push 0.22 up to a December rather than March release, feel free to give a PR a shot in the next week or so if you can, otherwise 0.23 is also useful!

@larsoner larsoner modified the milestones: 0.22, 0.23 Dec 1, 2020
@jdkoen
Copy link
Contributor Author

jdkoen commented Dec 6, 2020 via email

@hasibagen
Copy link

Hello, I was recently reading the chapter about erp peak in the luck's book, and I am very interested in erplab's peak finding tool. But mne's peak finding tool may indeed be a bit unusable for me. When I used mne to analyze eeg data, I exported evoke to pandas after all the analysis was completed to do time slices and find peaks. I feel that this method does have a big problem.
I am very interested in your work. If there is any progress or toolkit code, I will be very willing to try it.

@larsoner larsoner modified the milestones: 0.23, 0.24 Apr 19, 2021
@jdkoen
Copy link
Contributor Author

jdkoen commented Feb 3, 2022

  1. Thanks @SimonErnesto for sending this along. I think those functions are great! I've put some functions together as well and just made this gist with the code to share them. We definitely implemented things differently, and I will apply them to the example data you included in your repo. Note I have not tested out all of the methods yet as doing a write up of the data I'm applying some of them too took precedence (so it is a work in progress that might have bugs).

@agramfort to get at your question about implementation, one way would be to add methods to the Evoked class. I don't think these can just be added as optional inputs to existing methods (but I may be missing something). I can see three methods to do all of this, which I list below (names of methods are just meant to be descriptive for now).

  1. get_peak: This method already exists, but it does find a global maximum. This might not be ideal in all cases. I would recommend adding an option to find local peaks (using something like scipy.signal.find_peaks). This can be an option that is added. This can return both the peak and
  2. area_amplitude: This is meant to find the mean (or area) amplitude within a given time window. Mean amplitude would be straightforward, whereas area amplitude would use @SimonErnesto approach or the scipy.integrate.trapezoid. The latter is what is used in the ERP Measurement tool and is most similar to the Matlab implementation.
  3. fractional_latencies: This would use the above two methods to return the fractional time points (specified by the user).

Ideally, there would also be some other functions as well to plot the results easily so that the data can be inspected.

@agramfort does this approach fit within the scope/organization framework of MNE-Python development?

Another approach is to make a plugin for MNE python with different classes that could have a .fit() method to apply the computations to an instance/list of evoked objects.

@agramfort
Copy link
Member

agramfort commented Feb 6, 2022 via email

@larsoner larsoner modified the milestones: 1.0, 1.1 Feb 11, 2022
@larsoner larsoner modified the milestones: 1.1, 1.2 Jun 4, 2022
@larsoner larsoner modified the milestones: 1.2, 1.3 Sep 13, 2022
@larsoner larsoner modified the milestones: 1.3, 1.4 Dec 8, 2022
@larsoner larsoner modified the milestones: 1.4, 1.5 Apr 21, 2023
@larsoner larsoner modified the milestones: 1.5, 1.6 Jul 31, 2023
@larsoner larsoner modified the milestones: 1.6, 1.7 Nov 7, 2023
@withmywoessner
Copy link
Contributor

@agramfort @jdkoen I would like to start working on this soon. Do you still suggest creating an entirely new module?

@larsoner
Copy link
Member

larsoner commented Feb 2, 2024

@withmywoessner sure, I suggest adding a mne/stats/erp/_erp.py and in mne/stats/__init__.py add a from . import erp line, and in mne/stats/erp/__init__.py add a from ._erp import ... line. That should get the ball rolling.

Please start small in terms of actual additions. For example the first PR could add just #10647. Or maybe starting with the four measures you mention in #12406 wouldn't be too onerous to review assuming they are mathematically very simple (and well documented in some paper).

@drammock
Copy link
Member

drammock commented Feb 2, 2024

and in mne/stats/erp/__init__.py add a from ._erp import ... line.

nowadays this should go in the __init__.pyi file, not __init__.py. Also anything imported in __init__.pyi should also get added to __all__ variable in that .pyi file.

@larsoner
Copy link
Member

larsoner commented Feb 2, 2024

Ahh right didn't realize we had lazy-loader-ized mne/stats/!

@withmywoessner
Copy link
Contributor

Hey @drammock @larsoner, do you it would be best to have separate functions for mean amplitude and area? Or should I just include a setting in get_area (or some other name) since mean amp is just the area divided by the interval.

@larsoner
Copy link
Member

larsoner commented Feb 8, 2024

I'm torn because it doesn't seem useful to have two different functions because their functionalities are so similar, but it's hard to come up with a good name. Maybe get_area with a normalize=None | 'interval' and we say mean amplitude is just get_area(normalize='interval') or something like that.

@drammock
Copy link
Member

drammock commented Feb 8, 2024

I think I actually disagree here; we could think of get_mean as a convenience wrapper. It's such a common thing to want, it seems a shame to make people type all of get_area(normalize='interval') just to get the mean.

@withmywoessner
Copy link
Contributor

withmywoessner commented Feb 8, 2024

What about naming it something like get_integral then having a mode parameter mode='mean' | 'positive' | 'negative' | 'absolute' | None (standard integral)

I think I may be leaning more towards one function because it means documentation is in one place. I will ask my coworker who doesn't use Python.

EDIT: I Asked my coworker and he said he didn't care.

@larsoner
Copy link
Member

larsoner commented Feb 8, 2024

No strong feeling from me, get_mean plus get_area (or a get_integral) is fine with me

@withmywoessner
Copy link
Contributor

Okay, @drammock. I will defer to you if you think it would be better.

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

No branches or pull requests

9 participants