-
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 interpolate_to method #13044
base: main
Are you sure you want to change the base?
Conversation
Hello! 👋 Thanks for opening your first pull request here! ❤️ We will try to get back to you soon. 🚴 |
for more information, see https://pre-commit.ci
…-python into interpolate_to
for more information, see https://pre-commit.ci
mne/channels/channels.py
Outdated
# Create a new info structure | ||
sfreq = self.info["sfreq"] | ||
ch_types = ["eeg"] * len(target_ch_names) | ||
new_info = create_info(ch_names=target_ch_names, sfreq=sfreq, ch_types=ch_types) | ||
new_info.set_montage(montage) | ||
|
||
# Create a simple old_info | ||
sfreq = self.info["sfreq"] | ||
ch_names = self.info["ch_names"] | ||
ch_types = ["eeg"] * len(ch_names) | ||
old_info = create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types) | ||
old_info.set_montage(self.info.get_montage()) | ||
|
||
# Compute mapping from current montage to target montage | ||
mapping = _map_meg_or_eeg_channels( | ||
old_info, new_info, mode="accurate", origin="auto" | ||
) | ||
|
||
# Apply the interpolation mapping | ||
data_interp = mapping.dot(data_orig) | ||
|
||
# Update bad channels | ||
new_info["bads"] = [ch for ch in self.info["bads"] if ch in target_ch_names] | ||
|
||
# Update the instance's info and data | ||
self.info = new_info | ||
self._data = data_interp |
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 have the impression that this will drop any other channel that is not EEG. It should not. Typically you would want to keep ECG and EOG channels. In other words this should only modify the EEG channels and leave the remaining channels unchanged.
def test_interpolate_to_eeg(montage_name): | ||
"""Test the interpolate_to method for EEG.""" | ||
# Load EEG data | ||
raw, _ = _load_data("eeg") |
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 also test that it works fine for epochs and evoked calling the container "inst" as above
I completely agree with your comment regarding preserving non-EEG channels (e.g., ECG, EOG) while only modifying the EEG channels. However, I’m struggling to find a clean way to achieve this without unintentionally dropping or altering the other channel types. Currently, my implementation effectively replaces the Info and data of EEG channels but does not account for the non-EEG channels. As you pointed out, this would result in losing the other channels, which is undesirable. To address this, I guess the solution likely involves: But, I’m not sure about the best way to merge the original non-EEG channels with the new EEG channels while preserving Info consistency. |
Hmmm, over in #12486 (comment) I suggested to follow the proposal in #9609 (comment) which had the API:
Since your use case is EEG, I guess we're skipping the MEG interpolation bit here and going straight to EEG. But I still think So I'd suggest to switch to the name from
The safest way is to stick with public functions like _validate_type(inst, (BaseRaw, BaseEpochs, Evoked), "inst", extra="when intepolating channels")
picks_good_eeg = pick_types(info, eeg=True, exclude="bads") # use only good EEG channels to interp
picks_remove_eeg = pick_types(info, eeg=True, exclude=()) # remove all EEG channels when interpolating (including bad)
picks_other = np.setdiff1d(np.arange(len(info["chs"])), picks_remove_eeg)
info_interp = ... # however you construct destination info from the desired montage
ch_interp = ... # however you construct the interpolation matrix
assert ch_interp.shape == (len(info_interp["chs"]), len(picks_good))
data_interp = ch_interp @ inst.get_data(picks_good_eeg)
# now we have our new data and our new info, create a new instance
if isinstance(inst, BaseRaw):
inst_interp = RawArray(info_interp, data_interp, first_samp=inst.first_samp)
elif isinstance(inst, BaseEpochs): # need other branches for Epochs and Evoked
inst_interp = EpochsArray(info_interp, data_interp[np.newaxis])
else:
assert isinstance(inst, Evoked) # guaranteed above
inst_interp = EvokedArray(info_interp, data_interp)
# concatenate new channels to the end of the old non-EEG ones
inst_out = inst.copy().pick(picks_other).load_data().add_channels([inst_interp], force_update_info=True)
# now reorder channels so that EEG are wherever they used to be
eeg_start_idx = picks_remove_eeg[0]
new_order = np.insert(
np.arange(len(picks_other)),
eeg_start_idx,
np.arange(len(picks_other)), len(inst_out.ch_names)),
)
inst_out.reorder_channels(new_order)
assert inst_out.ch_names[eeg_start_idx] == info_interp.ch_names[0]
return inst_out |
elif method == "MNE": | ||
info_eeg = pick_info(self.info, picks_from) | ||
mapping = _map_meg_or_eeg_channels( | ||
info_eeg, new_info, mode="accurate", origin="auto" |
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.
shouldn't this be:
info_eeg, new_info, mode="accurate", origin="auto" | |
info_eeg, new_info, mode="accurate", origin=origin |
not interpolated. | ||
|
||
reg : float | ||
The regularization parameter for the interpolation method (if applicable). |
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 regularization parameter for the interpolation method (if applicable). | |
The regularization parameter for the interpolation method (only used when the method is 'spline'). |
|
||
.. warning:: | ||
Be careful, only EEG channels are interpolated. Other channel types are | ||
not interpolated. |
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 looks like this warning applies to the entire function, not the method
parameter specifically. Perhaps move it to the main text, so above the Parameters
line.
Reference issue (if any)
Implements #12486
What does this implement/fix?
Implements
interpolate_to
next tointerpolate_bads
to interpolate EEG data to a given montageAdditional information
Interpolating channels using this implementation has shown to be effective in
Mellot, A., Collas, A., Chevallier, S., Engemann, D. and Gramfort, A., 2024. Physics-informed and Unsupervised Riemannian Domain Adaptation for Machine Learning on Heterogeneous EEG Datasets. EUSIPCO 2024