mne-tools / mne-python

MNE: Magnetoencephalography (MEG) and Electroencephalography (EEG) in Python
https://mne.tools
BSD 3-Clause "New" or "Revised" License
2.66k stars 1.31k forks source link

ENH: Custom referencing #12283

Open qian-chu opened 9 months ago

qian-chu commented 9 months ago

Describe the new feature or enhancement

In some cases, especially intracranial EEG, re-referencing of channels should be local (e.g. within the same shaft/grid/strip) rather than global (i.e., ref all channels to one or an average of channels). This thus requires some flexible re-ref schemes, but the existing functions set_eeg_reference and set_bipolar_reference are not super convenient for such purposes. On the basis of the proposed function, multiple re-ref schemes can be added (for example see https://doi.org/10.1016/j.neuroimage.2018.08.020)

Describe your proposed implementation

A new preprocessing function mne.set_custom_reference(inst, ref_dict, copy=True, on_bad='warn', verbose=None) Where ref_dict contains a dictionary with keys being the source channel names and values being ref channel names. For example: ref_dict = {'A1': 'A1', 'A2': 'A1', 'A3': 'A1', 'A4': 'A1'} performs monopolar reference; ref_dict = {'A1': 'A2', 'A2': ['A1', 'A3'], 'A3': ['A2', 'A4'], 'A4': 'A3'} performs Laplacian reference. I'm happy to open a pull request draft if someone can confirm the value of the proposed function.

Describe possible alternatives

Alternatively we can expand mne.set_eeg_reference instead of writing a new function.

Additional context

No response

welcome[bot] commented 9 months ago

Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴

drammock commented 9 months ago

This has been asked before:

If you're willing to make a PR we'd be happy to have support for electrode-shaft referencing.

Can you have a look at those other issues (particularly the ideas about the API) and then tell us what approach best supports your use case as well as the other use cases mentioned there (if they're different)? Your dict-based approach looks very flexible --- we could maybe even allow mne.set_eeg_reference(ref_channels=dict(...)) --- but I want to be sure we're considering all use cases before we commit to an API.

qian-chu commented 9 months ago

This has been asked before:

If you're willing to make a PR we'd be happy to have support for electrode-shaft referencing.

Can you have a look at those other issues (particularly the ideas about the API) and then tell us what approach best supports your use case as well as the other use cases mentioned there (if they're different)? Your dict-based approach looks very flexible --- we could maybe even allow mne.set_eeg_reference(ref_channels=dict(...)) --- but I want to be sure we're considering all use cases before we commit to an API.

Thanks for mentioning the 2 relevant issues. My reflection is in iEEG there is no one-size-fits-all scheme. The optimal reference method varies depending on the research question (ERP? oscillations? epileptic activity? decoding?). I also echo with @larsoner in this comment that we cannot add support specifically for every scheme, especially since MNE doesn't support electrode grouping at the moment.

Thus in my opinion we can offer a flexible function that allows for local ref and people can start from there based on their needs, thus the dict approach. What do you think? We can probably add tutorials about how to implement different reference schemes with the API using ECoG or sEEG datasets.

Regarding whether to create a new API or just expand set_eeg_reference(), it has been suggested that we can add a new picks argument to set_eeg_reference(). This would be a nice addition but probably requires several iterations if people want to reref several shafts at the same time. I would personally prefer having a new API.

qian-chu commented 9 months ago

My colleague @AlexLepauvre is also interested in this topic. Alex, from your perspective (especially having seen different iEEG data from multiple centers) what might be most useful to the community?

mscheltienne commented 9 months ago

I'm a bit in favor of expanding the existing API instead of creating a new one. mne.set_eeg_reference(ref_channels=dict(...)) seems like an elegant solution with ref_channels : str | list[str] | dict[str, str | list[str]].

drammock commented 9 months ago

thinking further about this, I do really like the ref_channels=dict(...) approach: it's flexible and seems (to me at least) easy to understand, and probably not too hard to implement.

However, if we implement this, we probably can't prevent users from applying it to regular scalp EEG data. So one thing we should also think about though is our mechanisms for keeping track of whether EEG reference has been applied; how will we do that for this referencing approach? what will we do during apply_inverse when it checks for (and doesn't find) the desired situation of "EEG average reference applied"? (we do have mechanisms for overriding that particular safeguard --- and maybe that's already enough --- but there may be some documentation / user education to be done around the possible downsides of this kind of referencing)

AlexLepauvre commented 9 months ago

Hello everyone,

As @qian-chu mentioned, this is something for which we made a custom implementation in my project using data from various sites, using laplace referencing (ch2 = ch2 - mean(ch1, ch3)), so I would be happy to help out for the PR once we have figured out the details. In our case, we created a referencing dictionary for each single subject semi-automatically, which was then used to re-reference the channels.

In addition, we also took into account bad channels information. Any channel marked as bad was removed from the referencing scheme. If for a given channel, both references were marked as bad, the channel was also discarded, and if only one of the two was marked as bad, then bipolar referencing was applied.

One nice additional feature (especially for ECoG channels) would be electrodes relocalization. In case of bipolar referencing, it is best to relocate the channel as the midpoint between the two references. In the case of surface electrode, that might result in a point that is actually not on the cortical surface anymore. The relocated electrodes should be projected to the surface. This would be interesting to consider as well.

Would you think both features are relevant to implement?

larsoner commented 9 months ago

For the dict stuff, I think it should be okay for inverse modeling so long as we set CUSTOM_REF_ON so that modeling can't be done for now. Later when we want to support it we can keep track of the channels used as references properly in info and account for it properly in the inverse. But that can and should be follow-up PRs.

For the relocalization, I don't think we should mess with info['chs']['loc'] at all. I think it can be useful maybe for visualization to think about moving where the activation gets plotted or so. So this would be an option for project_sensors_onto_brain and should probably be a separate PR from the rereferencing-dict one as well.

qian-chu commented 8 months ago

To sum the discussion above, it seems that most are in favor of mne.set_eeg_reference(ref_channels=dict(...)) with certain things to keep in mind:

If this sounds good I'm going to create a PR draft.

drammock commented 8 months ago

To sum the discussion above, it seems that most are in favor of mne.set_eeg_reference(ref_channels=dict(...))

yes I think that's right

If this sounds good I'm going to create a PR draft.

go for it!

AlexLepauvre commented 8 months ago

Great :) @qian-chu, I will give it a go next week and we can see where we go from my initial commit.