-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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+1] Make read_eeglab_events public #4213
[MRG+1] Make read_eeglab_events public #4213
Conversation
Is there a way to partially share docs between functions? E.g., read_raw_eeglab calls read_eeglab_events, so they have a lot of the same kwarg docs. This simple PR is 100 LOC, most of which are copied doc strings. @Eric89GXL |
No way to do it partially unfortunately
|
Codecov Report
@@ Coverage Diff @@
## master #4213 +/- ##
==========================================
+ Coverage 86.18% 86.24% +0.05%
==========================================
Files 356 357 +1
Lines 64337 64558 +221
Branches 9798 9831 +33
==========================================
+ Hits 55451 55676 +225
Misses 6180 6180
+ Partials 2706 2702 -4 |
Ready from my end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise LGTM
mne/io/eeglab/eeglab.py
Outdated
"one event code per time point, so some events will be " | ||
"lost. You can use the function `mne.io.read_eeglab_events`" | ||
"to extract the full events array (which can then be " | ||
"passed to e.g. `mne.Epoch`." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> mne.Epochs. And the ending parenthesis can fit to the same line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And no reason to put backticks here, this is a warning not something Sphinx will try to render
|
||
Parameters | ||
---------- | ||
eeg : str | object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does scipy.io.loadmat return? Maybe use that instead of object
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns a dict, but really what's expected is the ['EEG']
result of that dict, which will be an np.ndarray
(even if it has ndim=0
, which is what one of the conditionals is for below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this works only for mat['eeg']
if it contains epochs
, right? Or does it work also for raw
... ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it works for epochs. It's intended for raw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay I see. Can we raise an error then if it contains epochs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just worried that you will get an obscure error if you try to give it mat['eeg']
and it contains epochs. Does it contain the eeg.event
field if it contains epochs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay cool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasmainak I hope I didn't come across as snarky here, I just really appreciate you having written the EEGLAB importer in the first place :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:) I think I'm not up to date with 21st century smileys yet, but no worries in any case :)
mne/io/eeglab/eeglab.py
Outdated
"""Create events array from EEGLAB structure. | ||
def read_eeglab_events(eeg, event_id=None, event_id_func='strip_to_integer', | ||
uint16_codec=None): | ||
r"""Create events array from EEGLAB structure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does r
in the beginning do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, but it's everywhere all of a sudden! Some doc thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, but do we really need it? I noticed a backslash and I think that was meant to be escaped, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest I put it there because I saw it in the other docstrings. I can remove it, but it's more consistent with the others in that file this way ...
mne/io/eeglab/eeglab.py
Outdated
@@ -576,19 +585,74 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0, | |||
logger.info('Ready.') | |||
|
|||
|
|||
def _read_eeglab_events(eeg, event_id=None, event_id_func='strip_to_integer'): | |||
"""Create events array from EEGLAB structure. | |||
def read_eeglab_events(eeg, event_id=None, event_id_func='strip_to_integer', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you need to update whats_new
as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise LGTM.
@timmidee can you see if it would work for you?
mne/io/eeglab/eeglab.py
Outdated
"one event code per time point, so some events will be " | ||
"lost. You can use the function `mne.io.read_eeglab_events`" | ||
"to extract the full events array (which can then be " | ||
"passed to e.g. `mne.Epoch`." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And no reason to put backticks here, this is a warning not something Sphinx will try to render
|
||
Parameters | ||
---------- | ||
eeg : str | object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns a dict, but really what's expected is the ['EEG']
result of that dict, which will be an np.ndarray
(even if it has ndim=0
, which is what one of the conditionals is for below)
looks good to me |
adff520
to
55d88ed
Compare
whats_new conflict. Don't you love rebasing. |
Otherwise gtg? |
mne/io/eeglab/eeglab.py
Outdated
@@ -381,6 +381,14 @@ def __init__(self, input_fname, montage, eog=(), event_id=None, | |||
|
|||
def _create_event_ch(self, events, n_samples=None): | |||
"""Create the event channel.""" | |||
if len(set(events[:, 0])) != len(events[:, 0]): | |||
warn("Some events overlap/occur at the same time sample. The " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can this happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the problem that prompted this fix: see #3938
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay @timmidee does this solve your problem?
you have my +1 after @timmidee has tested it and confirmed that it works |
Thanks a lot @jona-sassenhagen for taking a stab at this! |
@timmidee do you know how to check out a branch from my repo ..? |
(@jasmainak for some context, Tim and I sit and work on the same floor.) |
ah okay! just ask him IRL then if it's too much of a hassle to pull the branch :) |
@timmidee I won't come to the office tomorrow I think, can you perhaps share one of the original files with me? |
You also need to put this function in |
Works on the problematic data by @timmidee (see http://nbviewer.jupyter.org/gist/jona-sassenhagen/eb57f5c5735a838db9fc14a8d9e536be), please merge if green. |
Comment unaddressed above, please add to |
Argh I had forgotten to save that file before |
events = _read_eeglab_events(eeg, event_id=event_id, | ||
event_id_func=event_id_func) | ||
events = read_events_eeglab(eeg, event_id=event_id, | ||
event_id_func=event_id_func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you read all relevant info here? don't you loose str descriptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically what if you don't pass event_id in param can you imagine returning an event_id to know what you read ?
There was a problem hiding this comment.
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 what you're saying: this one just returns integers ..?
If there are str events and no event_id, the (un-parseable) str events are dropped with a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could return a dictionary mapping the string descriptions to the integers, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how that makes sense. The problem is the following:
- EEGLAB events can be arbitrary strs
- MNE stim chans or mne.Epochs(..., events, ...) must be int
The user knows what events are in the data, and in case they've forgotten, we print the str events in the warning. What we have to do is, we have to allow users to map strs to ints. So in this one, the workflow goes like this:
raw = mne.io.read_raw_eeglab(fname)
-> Warning: Events like the following will be dropped entirely:
"square", "circle". 100 event codes could not be mapped to integers.
Use the 'event_id' parameter to map such events manually.
event_id = {"square":1, "circle":2}
raw = mne.io.read_raw_eeglab(fname, event_id)
events = mne.find_events(raw)
epochs = mne.Epochs(raw, events, event_id)
for cond in event_id:
epochs[cond].average().plot(title=cond)
Seems fine to me. Or am I missing what you're going for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and any event not caught by event_id_func or event_id is dropped after the warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is predictable indeed, but I was just thinking that it's convenient for the user to get the event_id
since the string description already exists. Otherwise, they will have to construct the dictionary manually. But it's okay if you don't have the bandwidth to implement it or you think it's unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand ... the event_id
needs to exist anyways? Otherwise the user can't pass it to the function that retrieves the events (read_raw_eeglab or read_events_eeglab).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of an API like this:
raw = mne.io.read_raw_eeglab(fname)
events, event_id = read_events_eeglab(fname)
epochs = mne.Epochs(raw, events, event_id)
not sure if we're talking about the same thing :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how that'd work. Called without additional parameters, read_raw_eeglab and read_events_eeglab will perform a many-to-one mapping: strip every key of its non-numeric parts, and use that as the value. So it will do stuff like:
'S101' -> 101
'R101' -> 101
'101' -> 101
101 -> 101
'square' -> (dropped)
To recover square
, you'd have to use event_id. To have values distinguishing between 'S101' and 'R101', you'd have to use event_id too. So in any unambiguous mapping, the user will have to construct the event_id themselves before calling the function in the first place.
Apologies for slowing down the process. I muted the thread to focus on other work over the weekend. Thanks again for picking this up @jona-sassenhagen! Question I have is whether what the warning says about passing the events from read_events_eeglab() to mne.Epochs() is strictly true. Will mne.Epochs() accept overlap in the events structure? In the init for BaseEpochs the code reads:
This will only produce an error if both of the overlapping events are specified as time-locking events ("selected"), which is certainly not unthinkable in my specific use-case. I.e. the warning might confuse some users' next actions. |
I didn't actually know about this. Well, it does make sense, and also this concerns events after parsing event_id. (Also note the events can also be passed to e.g. |
Hmmmm that would seem double at first glance, could there be a case in which read_eeglab_events() would be used without using read_eeglab_events() first? Or perhaps an extra warning from read_eeglab_events() is useful if it not only states that there were duplicate time points but specifically that some functions might not accept that. |
How about this for a warning when constructing the raw object:
|
ok. I had the same feeling as @jasmainak but it seems you know what you're
doing :)
|
Better! |
I think @jasmainak 's and @agramfort 's idea is something we discussed and rejected when deciding on the API. In case you care, I'm laying out the rationale again:
I'm not in principle opposed to do a function like |
fair enough !
|
LGTM MRG+1 |
Thanks @jona-sassenhagen |
Thanks all. |
Fix #3938
@timmidee @jasmainak