-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[ENH] Add support for ieeg interpolation #12336
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
Conversation
@@ -213,10 +239,6 @@ def _interpolate_bads_meeg( | |||
# select the bad channels to be interpolated | |||
picks_bad = pick_channels(inst.info["ch_names"], bads_type, exclude=[]) | |||
|
|||
if method[ch_type] == "nan": | |||
inst._data[picks_bad] = np.nan |
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 is the line with the bug as far as I can tell, this should only work on raw
and have some odd and unexpected indexing behavior on 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.
As opposed to here https://github.com/mne-tools/mne-python/pull/12336/files#diff-fcfbd5c9390aa74bdd3005e5ae1c786df72afc324d3532273dc5937ef9674787L130-R131 which does it correctly
Confirmed, this is incorrect on
|
Oh odd, if you try the above code snippet on this branch, the data is correct but when you call
Looks like an unrelated bug. |
Looks like everything works as expected. This fixes both the ieeg quiet non-interpolations and the newly discovered |
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.
nice PR, thanks! A few questions/suggestions; I ran out of time and didn't thoroughly check the tests FWIW, so should get at least one other review (or another pass from me next week)
mne/channels/interpolation.py
Outdated
inst.info._check_consistency() | ||
bads_idx[picks] = [inst.ch_names[ch] in inst.info["bads"] for ch in picks] | ||
|
||
if len(picks) < 3 or bads_idx.sum() == 0: |
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.
[question] IDK how much of an edge case this is or if it's a realistic worry, but should we warn
if len(picks) < 3
? I can imagine maybe being surprised that the single bad channel wasn't interpolated because I (inadvertantly?) excluded all but one of the good channels (leaving just 2 channels in picks
)
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.
Yeah, I think maybe best just to kick it to the runtime error if there's a bad channel on a shaft with less than 3 contacts which is the edge case and I think an error is warranted there since that's not a good interpolation situation. So no need to silently return.
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 a warning is better. If you have 3 bad channels and this edge-case represents only one of them, the interpolation will fail and raise unless you mark the edge-case bad channel as good, interpolate, and mark it again as bad. I find this more error prone than warning, interpolating the 2 other channels and ignoring the edge-case one, and finally removing only the 2 interpolated channels from .info["bads"]
.
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 won't raise an error if there are 3 bad channels on a shaft (electrode) only if there aren't three good ones. If there are only two good channels, I don't think there is a use case where you want to use those to interpolate any of the bad channels so I'd still be disposed to an error. Although if you had 3 contacts (usually 4 for DBS) but as in a DBS and you wanted to interpolate the middle one on some epochs that would make sense so maybe it should just be two required.
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 mixed up bad channels/good ones in my comment. I agree there is no use case for interpolating along a shaft if you lack the information to do so. But I would still warn, else if you have 2 shafts each with some bad channels, one with less than 3 good contacts remaining; the interpolation function will fail even for the shaft which has enough information to interpolate.
raw.ch_names = [f"Shaft1_{k} for k in range(5)] + [f"Shaft2_{k} for k in range(5)]
raw.info["bads"] = [f"Shaft1_{k} for k in range(4)] + ["Shaft2_0"]
raw.interpolate_bads(...) # warning for the bad contacts on Shaft1, interpolation for Shaft2_0
assert raw.info["bads"] == [f"Shaft1_{k} for k in range(4)]
Or am I misunderstanding something and/or a multi-shaft recording is not realistic? (I lack experience with intra-cranial recordings)
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.
The length of the shaft isn't checked if the channels aren't marked bad so I think if you do mark it and try and interpolate it you should get an error rather than unreliable data. You would then have to drop that channel without interpolation to continue without an error which I think is the safest way to go and wouldn't cause major issues in multi-subject pipelines.
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 the other consideration if it's a warning is what do you do to the data in that channel? If it was supposed to be interpolated and you just warned and the data was left as is, that seems potentially misleading. If it only requires two other contacts, I think it's pretty safe that this is an unlikely edge case and the failure will be warranted so that the user can be notified to fix their data by dropping all the contacts on that electrode shaft.
Thanks for the review, some nice cleanups |
mne/channels/interpolation.py
Outdated
inst.info._check_consistency() | ||
bads_idx[picks] = [inst.ch_names[ch] in inst.info["bads"] for ch in picks] | ||
|
||
if len(picks) < 3 or bads_idx.sum() == 0: |
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.
The length of the shaft isn't checked if the channels aren't marked bad so I think if you do mark it and try and interpolate it you should get an error rather than unreliable data. You would then have to drop that channel without interpolation to continue without an error which I think is the safest way to go and wouldn't cause major issues in multi-subject pipelines.
Good to go by me, no rush though |
mne/channels/interpolation.py
Outdated
np.fill_diagonal(dist, np.inf) | ||
|
||
picks_bad = list(np.where(bads_idx_pos)[0]) | ||
while picks_bad: |
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.
can we avoid while loops and use a plain for? I find them usually unecessary and error prone.
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.
The while loop was used because you don't know if two bad channels are on the same shaft until you find the shaft. I can't think of an easy way around that with a for loop.
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 wonder if a helper function (_find_shafts
or so) that returns a list-of-lists of channel indices would be generally useful? If so, it could be used here to pre-compute the channels into shaft groupings, then a for-loop over picks_bad
would work.
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.
Sure, we could do that. I was just finding them for the bad contacts to save the computation but it's a very small amount
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.
Hmm, I wrote that but even if you know the shafts, you still have to iterate over picks_bad
(list of bad indices) and you don't know whether two are on the same shaft or not so you have to use a while loop.
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 never mind you can just loop over shafts
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Fixes #12333.
It also looked like there was a bug where epochs would have the channel indices of their epochs set to
nan
ifnan
was the method which was also fixed.