-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
ENH: Add ability to reject epochs using callables #12195
Conversation
for more information, see https://pre-commit.ci
I am still working on this. Does anyone know of any good testing data that has massive amplitude spikes? |
The |
Thanks @larsoner! I am getting ready to write a tutorial (and the test though this question applies to both) as per @drammock's suggestion. First, I wanted to ask is the way I generated sample bad data appropriate or should I use the source simulator class (I did not really know how to get the desired large amplitude spike easily)? Here is what I made:
Second, I wanted to include a section of the tutorial for including boolean operations such as:
In the tutorial is it better to place the complicated lambda function in the construction or declare a new function like:
Third, I was wondering if instead of modifying Thanks for the help! :) |
I think using SourceSimulator would be overkill and direct data modification is fine!
New function is better/clearer, probably added as a short new section in https://mne.tools/stable/auto_tutorials/preprocessing/20_rejecting_bad_data.html / https://github.com/mne-tools/mne-python/blob/main/tutorials/preprocessing/20_rejecting_bad_data.py
Good question. I think as long as we clarify in the doc that the callable could detect all sorts of stuff -- either too large or too small amplitudes -- and that what goes in |
Okay, I was just asking because I personally don't think I would ever use both if I was trying to specify a minimum and a maximum absolute value and would just use boolean operators. In the tutorial, is it fine if I just use |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
@larsoner I think I am almost done with this. When you have a chance can you look over my comments and let me how you think I should address those problems. Thanks so much! |
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.
Looks like good progress so far!
Should I add a new reason, 'REJECT_CRITERIA', for when you drop based on reject or flat that includes the function or value used to drop?
I completely forgot about this. Based on the docs of epochs.drop(..., reason="USER"), the string can actually be arbitrary. Moreover it can be a list/tuple of reasons. So how about if we require that the callable return a good, reason
tuple like:
epochs.drop_bad(reject=lambda epoch: (epoch.max() > 1e-3, "> 1mV somewhere"))
Where good
must be bool
and reason
must be str, list, tuple
where each entry is a str
. This allows for nice stuff like:
def my_reject(epoch_data):
bad_idxs = np.where(np.percentile(epoch_data, axis=1) > 1e-3)
reasons = tuple(epochs.ch_name[bad_idx] for bad_idx in bad_idxs)
return len(bad_chs) > 0, reasons
And it also makes me think we should:
- Update
epochs.drop(...)
to allowreason=...
to be a list or tuple of reasons rather than a single string at some point. But doesn't have to be this PR unless you're really motivated. - Amend the
epochs.drop_log
attribute doc to mention that the entry can actually be somech_name
fromepochs.ch_names
as well when dropping based on flat or reject. This would be great and not too difficult to add to this PR, but not 100% necessary (I can create a new issue).
mne/epochs.py
Outdated
@@ -819,9 +819,14 @@ def _reject_setup(self, reject, flat): | |||
# check for invalid values | |||
for rej, kind in zip((reject, flat), ("Rejection", "Flat")): | |||
for key, val in rej.items(): | |||
if val is None or val < 0: | |||
if callable(val): |
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 would simplify to:
if callable(val):
continue
else:
name = f"{kind} dict value for {key}"
_validate_type(val, "numeric", name, extra="or callable")
if val < 0:
raise ValueError("{kind} {name} must be >= 0, got {val}")
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 going to do it this way, but it breaks a test. I think its because _validate_type
returns a TypeError and Pytest wants a ValueError:
for val in (None, -1): # protect against older MNE-C types
for kwarg in ("reject", "flat"):
pytest.raises(
ValueError,
Epochs,
raw,
events,
event_id,
tmin,
tmax,
picks=picks_meg,
preload=False,
**{kwarg: dict(grad=val)},
)
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.
We should just update the test, TypeError is a better error in the case where the type is wrong (i.e., _validate_type
does something better than what we do in main
)
mne/epochs.py
Outdated
if not is_callable: | ||
# make sure new thresholds are at least as stringent | ||
# as the old ones |
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.
... but I think you can make these checks more fine-grained without much more work. Instead of if any are callable, skip all checks (which is what you're doing now), you should within this loop be able to fairly easily make it so that if this type is or was callable, skip this check. I think it almost amounts to moving the conditional from above into this loop with a continue
when callables are or were present. As a bonus, your diff
will probably be smaller, too!
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.
Should I edit my test to check for this?
Co-authored-by: Eric Larson <[email protected]>
Co-authored-by: Eric Larson <[email protected]>
Yes it's possible to check Would supporting just |
Yes @larsoner! I think I might write new docustrings for |
Hey @larsoner, just pinging here. I think everything should be ready finally. Thanks so much for helping me with this :) |
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.
Just two tiny things I noticed, will commit and mark for merge-when-green. Thanks for working through my confusions on this one @withmywoessner !
maybe I'm misunderstanding something, but it looks like Circle's
@larsoner any idea why we got a false-positive pass from Circle here? |
Looks like for some reason it ran on his fork rather than our project. Not sure why |
Is this the reason why? @larsoner @drammock
|
thanks! hopefully that will fix it for your next PR. |
I had some problems before where CircleCI was not running because it said I was an unregistered user. I think that's why I clicked follow. Do you know how to fix this? @drammock |
I think what is supposed to happen is that if you create a CircleCI account that is linked to your GH account (maybe they always are?) then Circle will run the job automatically as long as it's not your first PR in the MNE-Python repo. But to be honest I haven't really had time to look into the situation / failures very carefully. Maybe I should create a new GH account so I can try it out / experience the rejection myself, and then document the fix 😆 |
I don't know :( I just poked around in the admin interface and I see no list of "members" and no way to "add" people... your list of organizations looks odd though (mine lists various user's names with images, and various named orgs, but no hash-like codes like |
Yes, Is there anything linking your CircleCI account to the mne-python public repo on your CircleCI account page? I don't see anything like that on mine. Thanks for looking into this! @drammock |
ok, IIUC they are using GitHub users & organizations as their source of "organizations" and since you're not a member of https://github.com/orgs/mne-tools you shouldn't expect to see "mne-tools" as an option within CircleCI. But since I am a member, I do see "mne-tools" as an org within CircleCI (along with other orgs I'm part of: pydata, sphinx-gallery, etc). So I think that part at least is working as expected? (I don't see any orgs listed on your GitHub profile so it makes sense you wouldn't see any within CircleCI). But I think (?) this shouldn't prevent the jobs from running; hopefully it was the "follow" setting that you've just changed; we'll know on your next PR I guess. |
Hopefully, @drammock 😬. Is there a way to change where CircleCI is run from in an existing PR? Compared to this one from #12382: It also has a different icon in the top left and says 'mne-tools' when I clicked on it. |
It looks good now. Maybe I just clicked it too early. Thanks! |
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Larson <[email protected]>
Reference issue
Example: Fixes ##11775.
What does this implement/fix?
Add ability to reject epochs using functions.
Ex: epochs = mne.Epochs(..., reject=dict(eeg=lambda x: True if np.median(x) > 1e-5 else False))