Skip to content

Conversation

@moritz-gerster
Copy link
Contributor

@moritz-gerster moritz-gerster commented May 20, 2022

Summary

Using a common average projection currently only works for EEG data. It should also be available for intracranial data. This PR tries to solve it.

What is the problem?

The method set_eeg_reference works, when using a projection, only for EEG data. When working with intracranial data such as ECoG it breaks because it does not find any EEG channels:

Exception has occurred: ValueError
Cannot create EEG average reference projector (no EEG data found)

What is the fix/enhancement?

With this PR, it is possible to specify the type of channels used for the reference projection. It is straightforward to implement since set_eeg_reference has the argument "ch_type" implemented already.

Using a common average projection currently only works for EEG data. It should also be available for intracranial data. This commit tries to solve it.
@welcome
Copy link

welcome bot commented May 20, 2022

Hello! 👋 Thanks for opening your first pull request here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

mne/io/proj.py Outdated

@verbose
def make_eeg_average_ref_proj(info, activate=True, verbose=None):
def make_eeg_average_ref_proj(info, activate=True, ch_dict=None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have gone with ch_type=('eeg',) as API and
default parameter

would that do the job for you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you for the quick review @agramfort! I changed it in this commit 😊

mne/io/proj.py Outdated

ch_dict = {**{type_: True for type_ in ch_type},
'meg': False, 'ref_meg': False}
eeg_sel = pick_types(info, **ch_dict, exclude='bads')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can reuse the private function _picks_to_idx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @agramfort. I don't think I understand this private function though. How can I include it?

eeg_sel = _picks_to_idx(info, ch_type, none='data', exclude='bads', allow_empty=False, with_ref_meg=False)???

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go for none='eeg'

@drammock
Copy link
Member

@moritz-gerster there are some failing tests:

style test:

=================================== FAILURES ===================================
__________________________ test_docstring_parameters ___________________________
mne/tests/test_docstring_parameters.py:164: in test_docstring_parameters
    raise AssertionError(msg)
E   AssertionError: 
E   mne.io.proj.make_eeg_average_ref_proj : PR01 : Parameters {'ch_type'} not documented
E   mne.io.proj.make_eeg_average_ref_proj : PR02 : Unknown parameters {'ch_dict'}
E   2 errors

functionality tests:

================================== FAILURES ===================================
___________________________ test_set_eeg_reference ____________________________
mne\io\tests\test_reference.py:172: in test_set_eeg_reference
    reref, ref_data = set_eeg_reference(raw, copy=False, projection=True)
E   Failed: DID NOT WARN. No warnings of type (<class 'RuntimeWarning'>,) were emitted. The list of emitted warnings is: [].

Let us know if you need help understanding the problems or finding the right fix.

@moritz-gerster
Copy link
Contributor Author

@drammock thank you for your help! 😊

@moritz-gerster there are some failing tests:

style test:

=================================== FAILURES ===================================
__________________________ test_docstring_parameters ___________________________
mne/tests/test_docstring_parameters.py:164: in test_docstring_parameters
    raise AssertionError(msg)
E   AssertionError: 
E   mne.io.proj.make_eeg_average_ref_proj : PR01 : Parameters {'ch_type'} not documented
E   mne.io.proj.make_eeg_average_ref_proj : PR02 : Unknown parameters {'ch_dict'}
E   2 errors

I did update the docstring now but it seems there are still some doc tests failing. I don't understand why.

functionality tests:

================================== FAILURES ===================================
___________________________ test_set_eeg_reference ____________________________
mne\io\tests\test_reference.py:172: in test_set_eeg_reference
    reref, ref_data = set_eeg_reference(raw, copy=False, projection=True)
E   Failed: DID NOT WARN. No warnings of type (<class 'RuntimeWarning'>,) were emitted. The list of emitted warnings is: [].

I don't know how to fix this...

Any help would be appreciated! In the meantime, I will keep trying to find out myself.

mne/io/proj.py Outdated
activate : bool
If True projections are activated.
ch_type : tuple
List of channel types to use for reference projection.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moritz-gerster can you list the valid channel types. I would say this makes sense for EEG, iEEG and ECoG?

Copy link
Contributor Author

@moritz-gerster moritz-gerster May 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, @agramfort but also for DBS! We use the DBS leads to measure local field potentials. So it should be possible for the mne types eeg, seeg, dbs, and ecog.

@drammock
Copy link
Member

================================== FAILURES ===================================
___________________________ test_set_eeg_reference ____________________________
mne\io\tests\test_reference.py:172: in test_set_eeg_reference
    reref, ref_data = set_eeg_reference(raw, copy=False, projection=True)
E   Failed: DID NOT WARN. No warnings of type (<class 'RuntimeWarning'>,) were emitted. The list of emitted warnings is: [].

This means that on line 172 of the file mne/io/tests/test_reference.py there is code that explicitly expects a warning to be raised, but the warning wasn't raised. It's probably this:

if _has_eeg_average_ref_proj(inst.info['projs']):
warn('An average reference projection was already added. The data '
'has been left untouched.')

you'll need to figure out why that warning isn't triggered anymore when that test is run.

@agramfort
Copy link
Member

@moritz-gerster it was a bit more difficult than what I though. I pushed here. Let me know what you think.

Copy link
Member

@agramfort agramfort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so we still need to sort out the referencing with different channel types. One way I see to test for now is to get a proj for each channel type and use add_proj method to add both. Would this do the job for you @moritz-gerster ?

mne/__init__.py Outdated
@@ -1,3 +1,4 @@
import bullshit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know this package ;)

Copy link
Contributor Author

@moritz-gerster moritz-gerster Jul 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not coming back earlier, I am currently busy but I think I can come back to this next week!

In the meantime, I took a programming workshop and the instructor helped me to get my local version of mne running in my own virtual environment. To test that it works we added an error by importing bullshit :D I'll remove that of course.

@moritz-gerster
Copy link
Contributor Author

ok so we still need to sort out the referencing with different channel types. One way I see to test for now is to get a proj for each channel type and use add_proj method to add both. Would this do the job for you @moritz-gerster ?

Dear @agramfort, Yes that sounds good. I am a little lost now though: What exactly should I change in the code?

@larsoner
Copy link
Member

I think the idea would be to allow ch_type to be a list | tuple and iterate over it, creating and adding a separate projector for each type in the list

@moritz-gerster
Copy link
Contributor Author

I think the idea would be to allow ch_type to be a list | tuple and iterate over it, creating and adding a separate projector for each type in the list

@larsoner thanks for the hint. I am not sure though if this would solve my problem. I would like to apply an average reference projector to my combined LFP+ECOG electrodes. I don't want a separate reference projector for the ECOG and LFP electrodes respectively. I need a common average reference across both electrode types.

@larsoner
Copy link
Member

Ahh okay.

The separate average reference case can actually be solved easily by the user looping over N types and passing them as arguments one by one to get N separate projectors. So how about if they pass a list of N types, it makes one projector that jointly average references all of those types.

In other words, you still add support for list of types here, but in that case it produces a single projector.

@moritz-gerster
Copy link
Contributor Author

So how about if they pass a list of N types, it makes one projector that jointly average references all of those types.

@larsoner how do I average all these ch_type specific projections that I added to yield a single projection?

@larsoner
Copy link
Member

how do I average all these ch_type specific projections that I added to yield a single projection?

You would not average the projectors together, you would create a single projector that includes all the channels (of all of the given types together). Make sense?

@larsoner
Copy link
Member

@moritz-gerster do you plan to work on this soon-ish? If not, then someone else might be able to push this over the finish line

@moritz-gerster
Copy link
Contributor Author

Dear @larsoner, I am not sure. It is on my list but I cannot start working on it in the next four weeks. Also, even if I would come back I am not sure if I am proficient enough to finish it. If someone else would start working on that I'd be very happy!

@larsoner larsoner added this to the 1.2 milestone Sep 13, 2022
@larsoner
Copy link
Member

@agramfort this one will hopefully come back green or close to it. Ready for review/merge from my end

@larsoner larsoner self-assigned this Sep 15, 2022
Copy link
Member

@agramfort agramfort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a nitpick

@moritz-gerster can you check if it works for you before we merge?

Co-authored-by: Alexandre Gramfort <alexandre.gramfort@m4x.org>
@moritz-gerster
Copy link
Contributor Author

@moritz-gerster can you check if it works for you before we merge?

Yes, I'll check tomorrow!

@moritz-gerster
Copy link
Contributor Author

moritz-gerster commented Sep 16, 2022

Dear @larsoner and @agramfort,

I looked into the code and the projection now works! Thank you very much!!

There was some behavior of the code I found surprising though and it took me quite some time to understand it. I am not sure if this is a bug or on purpose. I guess this is not limited to intracranial data but to reference projections in general:

To test whether I can easily switch between the CAR projection and the monopolar reference, I plotted ECoG spectra side by side. I used

raw_CAR, _ = mne.set_eeg_reference(raw, ref_channels='average', copy=True, projection=True, ch_type='ecog')
psd_CAR, freqs = mne.time_frequency.psd_welch(raw_CAR, proj=True)
psd, freqs = mne.time_frequency.psd_welch(raw_CAR, proj=False)

However, the spectra looked identical. Only if I used

raw_CAR, _ = mne.set_eeg_reference(raw, ref_channels='average', copy=True, projection=True, ch_type='ecog')
raw_CAR.apply_proj()
psd_CAR, freqs = mne.time_frequency.psd_welch(raw_CAR)
psd, freqs = mne.time_frequency.psd_welch(raw)

the expected behavior occurred. The documentation of psd_welch (I know its deprecated) states

proj [bool]
Apply SSP projection vectors. If inst is ndarray this is not used.

The same is written in the new method compute_psd.

Does that mean only SSP projections should be used for plotting spectra but not reference projections?

@agramfort
Copy link
Member

agramfort commented Sep 16, 2022 via email

@moritz-gerster
Copy link
Contributor Author

ok @agramfort.

For me this was confusing because I thought the great benefit of the projection is that I can easily turn it on- and off during plotting etc. while just having a single raw instance in my code. But if this is intended the code is good to go!

@agramfort agramfort merged commit fefa7db into mne-tools:main Sep 16, 2022
@welcome
Copy link

welcome bot commented Sep 16, 2022

🎉 Congrats on merging your first pull request! 🥳 Looking forward to seeing more from you in the future! 💪

@agramfort
Copy link
Member

Thx @moritz-gerster @larsoner 🙏🙏

@drammock
Copy link
Member

For me this was confusing because I thought the great benefit of the projection is that I can easily turn it on- and off during plotting etc.

I agree that what you suggest should have worked, although since psd_welch is deprecated and we're about to release 1.2 we may not fix it. Can you try this instead? If this is broken we will definitely fix.

raw_CAR, _ = mne.set_eeg_reference(raw, ref_channels='average', copy=True,
                                   projection=True, ch_type='ecog')
for proj in (False, True):
    raw_CAR.compute_psd(proj=proj).plot()

@moritz-gerster
Copy link
Contributor Author

moritz-gerster commented Sep 16, 2022

Dear @drammock,

I tested it for compute_psd(). It shows the exact same behavior as psd_welch(): The projection is not applied during plotting, even if the projection kwarg is set to True raw_CAR.compute_psd(proj=True).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

5 participants