Two EEG references at the same time?

Dear colleagues:

Iā€™m creating a MNE gui and something something intrigues me:
Neurophisiologists consider ā€œbipolarā€ as ā€œmontageā€, on the other hand, mne takes ā€œbipolarā€ as reference which tecnically I think is correct. IĀ“ve managed to create the interface bellow with referential and bipolar montages and all references available in mne. My question is:
Is it possible to get referential montage with, for instance, average (CAR) , CSD (laplacian) or even Cz references ā€œandā€ using those references (one at a time of course) build bipolar montages?
I mean, combine electrodes with specific references to build bipolar plots.
Today, in my methods, I destroy the references, get back to original raw to build bipolar. Thanks in advance

  • MNE version: e.g. 0.24.0
  • operating system: Windows 11 python 3.8

Iā€™m having trouble understanding what it is you want to be able to do.

In MNE-Python montage means the locations of the sensors in 3-D space. Montages do not know anything about which electrodes were used as reference.

The one possible exception is cases where there are channels that are explicitly named ā€œrefā€ or ā€œref_1ā€ or similar (and even then, thereā€™s no way to know from looking just at the montage whether the other channels were actually measured with the ā€œrefā€ channel as reference ā€” because again, montages donā€™t contain any information about the values of measured signals at each electrode, they only contain information about spatial location).

So I donā€™t know what you mean when you say ā€œreferential montageā€ or ā€œbipolar montageā€. Iā€™m also not clear what you mean by ā€œbipolar plotā€ā€¦ what kind of plot do you want (location of sensors? time-series of each sensor signal after applying a bipolar reference? something else?) Can you try to explain again?

For a clĆ­nical neurophysiologista montage is combinations of electrodes displayed in a plot(screen), for example montage1: Fp1-Ref, F7-Ref, T3 -Ref ā€¦ etc and in this case the reference could be ā€œA1ā€ , ā€œA2ā€ , ā€œA1+A2ā€, Cz, Laplacian, average, etc. Even if montage2 is Fp1-F7, F7-T3, T3 -T5 We can take F1 referenced to average and combine with F7 referenced to average. Something like this: (Fp1-Ref) - (F7-Ref) , Ref can be AVG , laplacian, Cz and so on. I believe that I misunderstand how mne works. Thanks for your comments.

OK, so it sounds like you want to take some data that have common average reference (CAR), CSD, or some other specific channel as reference, and then create new (virtual) channels that are the difference between two already-referenced channels.

You can do this by calling raw.set_eeg_reference (or raw.set_bipolar_reference) first; if using CAR, set projection=False. Then, you can create your ā€œdifference channelsā€ by getting all of the ā€œfirst channelsā€ and ā€œsubtracted channelsā€ separately, subtracting the arrays, then recreating the Raw object. Something like:

raw.set_eeg_reference('Cz')  # or 'average' or 'REST' or...
difference_pairs = [('Fp1', 'F7'), ('F3', 'C3'), ('F8', 'T4')]  # just doing 3 as illustration
first_chs, second_chs = list(zip(*difference_pairs))
first_data = raw.get_data(picks=first_chs)
second_data = raw.get_data(picks=second_chs)
info = mne.create_info(
    ch_names=[f'{a}-{b}' for a, b in difference_pairs],
    sfreq=raw.info.get('sfreq'),
    ch_types='eeg'
)
new_raw = mne.io.RawArray(
    data=first_data - second_data,
    info=info,
    first_samp=raw.first_samp
)

WoW! That is exactly what Iā€™m talking about. Many thanks!!!

Hi Professor McCloy,

Iā€™ve been working with your code and it works like a ā€˜charmā€™ when doing average reference, but when I try Cz and Rest nothing happens (no error in terminal), on the other hand when I do ā€˜mne.preprocessing.compute_current_source_densityā€™ as reference, it plots correct but with very high amplitude.

my alterations in your code for Cz are:

 #-------------------
     def make_bipolar01_Cz(self): # dont change anything (no error in terminal)
         plt.clf()
 
         re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'), ('T3', 'T5'), ('T5', 'O1'), ('Fp1', 'F3'), ('F3', 'C3'),
                                   ('C3', 'P3'), ('P3', 'O1'), ('Fp2', 'F8'), ('F8', 'T4'), ('T4', 'T6'), ('T6', 'O2'),
                                   ('Fp2', 'F4'), ('F4', 'C4'), ('C4', 'P4'), ('P4', 'O2'), ('Fz', 'Cz'), ('Cz', 'Pz'),
                                   ('Pz', 'Oz')]
       
         #EEG_raw.set_eeg_reference('Cz', projection=False) #doesnot work
         rereferenced_raw, ref_data = mne.set_eeg_reference(EEG_raw, ['Cz'],
                                                            copy=True)
 
         difference_pairs = re_reference_bipolar01
         first_chs, second_chs = list(zip(*difference_pairs))
         first_data = rereferenced_raw.get_data(picks=first_chs)
         second_data = rereferenced_raw.get_data(picks=second_chs)
 
         info = mne.create_info(
             ch_names=[f'{a}-{b}' for a, b in difference_pairs],
             sfreq=rereferenced_raw.info.get('sfreq'),
             ch_types='eeg'
         )
 
         bipolar_01_Reref_Cz = mne.io.RawArray(
             data=first_data - second_data,
             info=info,
             first_samp=rereferenced_raw.first_samp
         )
 
         EEG_raw = copy.deepcopy(bipolar_01_Reref_Cz)
 #--------------------

Bellow works but very high amplitude

#-------------------
    def make_bipolar01_CSD(self):  #works but very high amplitude


        raw_copy = mne.preprocessing.compute_current_source_density(Pages.EEG_raw)

        re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'), ('T3', 'T5'), ('T5', 'O1'), ('Fp1', 'F3'), ('F3', 'C3'),
                                  ('C3', 'P3'), ('P3', 'O1'), ('Fp2', 'F8'), ('F8', 'T4'), ('T4', 'T6'), ('T6', 'O2'),
                                  ('Fp2', 'F4'), ('F4', 'C4'), ('C4', 'P4'), ('P4', 'O2'), ('Fz', 'Cz'), ('Cz', 'Pz'),
                                  ('Pz', 'Oz')]

        difference_pairs = re_reference_bipolar01
        first_chs, second_chs = list(zip(*difference_pairs))
        first_data = EEG_raw_copy.get_data(picks=first_chs)
        second_data = EEG_raw_copy.get_data(picks=second_chs)

        info = mne.create_info(
            ch_names=[f'{a}-{b}' for a, b in difference_pairs],
            sfreq=EEG_raw_copy.info.get('sfreq'),
            ch_types='eeg'
        )

        bipolar_01_Reref_CSD = mne.io.RawArray(
            data=first_data - second_data,
            info=info,
            first_samp=EEG_raw_copy.first_samp
        )

        Pages.EEG_raw = copy.deepcopy(bipolar_01_Reref_CSD)

#-------------------

The last one works perfectly:


#-------------------

def make_bipolar01_CAR(self):
plt.clf()

    Pages.EEG_raw.set_eeg_reference('average', projection=False)

re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'),...

    difference_pairs = reference_bipolar01
    first_chs, second_chs = list(zip(*difference_pairs))
    first_data = Pages.EEG_raw.get_data(picks=first_chs)
    second_data = Pages.EEG_raw.get_data(picks=second_chs)

    info = mne.create_info(
        ch_names=[f'{a}-{b}' for a, b in difference_pairs],
        sfreq=Pages.dirty_EDF.info.get('sfreq'),
        ch_types='eeg'
    )

    bipolar_01_Reref_CAR = mne.io.RawArray(
        data=first_data - second_data,
        info=info,
        first_samp=Pages.dirty_EDF.first_samp
    )

    bipolar_01_Reref_CAR.set_eeg_reference('average', projection=False)

    Pages.EEG_raw = copy.deepcopy(bipolar_01_Reref_CAR)

[quote="PauloKanda, post:6, topic:4129, full:true"]
Hi Professor  McCloy,

I've been working with your code and it works like a 'charm' when doing average reference, but when I try Cz and Rest nothing happens (no error in terminal), on the other hand when I do 'mne.preprocessing.compute_current_source_density' as reference, it plots correct but with very high amplitude.

my alterations in your code for Cz are:
 

#-------------------
def make_bipolar01_Cz(self): # dont change anything (no error in terminal)
plt.clf()

     re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'), ('T3', 'T5'), ('T5', 'O1'), ('Fp1', 'F3'), ('F3', 'C3'),
                               ('C3', 'P3'), ('P3', 'O1'), ('Fp2', 'F8'), ('F8', 'T4'), ('T4', 'T6'), ('T6', 'O2'),
                               ('Fp2', 'F4'), ('F4', 'C4'), ('C4', 'P4'), ('P4', 'O2'), ('Fz', 'Cz'), ('Cz', 'Pz'),
                               ('Pz', 'Oz')]
   
     #EEG_raw.set_eeg_reference('Cz', projection=False) #doesnot work
     rereferenced_raw, ref_data = mne.set_eeg_reference(EEG_raw, ['Cz'],
                                                        copy=True)

     difference_pairs = re_reference_bipolar01
     first_chs, second_chs = list(zip(*difference_pairs))
     first_data = rereferenced_raw.get_data(picks=first_chs)
     second_data = rereferenced_raw.get_data(picks=second_chs)

     info = mne.create_info(
         ch_names=[f'{a}-{b}' for a, b in difference_pairs],
         sfreq=rereferenced_raw.info.get('sfreq'),
         ch_types='eeg'
     )

     bipolar_01_Reref_Cz = mne.io.RawArray(
         data=first_data - second_data,
         info=info,
         first_samp=rereferenced_raw.first_samp
     )

     EEG_raw = copy.deepcopy(bipolar_01_Reref_Cz)

#--------------------


Bellow works but very high amplitude

#-------------------
def make_bipolar01_CSD(self): #works but very high amplitude

    raw_copy = mne.preprocessing.compute_current_source_density(Pages.EEG_raw)

    re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'), ('T3', 'T5'), ('T5', 'O1'), ('Fp1', 'F3'), ('F3', 'C3'),
                              ('C3', 'P3'), ('P3', 'O1'), ('Fp2', 'F8'), ('F8', 'T4'), ('T4', 'T6'), ('T6', 'O2'),
                              ('Fp2', 'F4'), ('F4', 'C4'), ('C4', 'P4'), ('P4', 'O2'), ('Fz', 'Cz'), ('Cz', 'Pz'),
                              ('Pz', 'Oz')]

    difference_pairs = re_reference_bipolar01
    first_chs, second_chs = list(zip(*difference_pairs))
    first_data = EEG_raw_copy.get_data(picks=first_chs)
    second_data = EEG_raw_copy.get_data(picks=second_chs)

    info = mne.create_info(
        ch_names=[f'{a}-{b}' for a, b in difference_pairs],
        sfreq=EEG_raw_copy.info.get('sfreq'),
        ch_types='eeg'
    )

    bipolar_01_Reref_CSD = mne.io.RawArray(
        data=first_data - second_data,
        info=info,
        first_samp=EEG_raw_copy.first_samp
    )

    Pages.EEG_raw = copy.deepcopy(bipolar_01_Reref_CSD)

#-------------------

The last one works perfectly:

#-------------------   

 def make_bipolar01_CAR(self):
        plt.clf()

        Pages.EEG_raw.set_eeg_reference('average', projection=False)
    
	re_reference_bipolar01 = [('Fp1', 'F7'), ('F7', 'T3'),...

        difference_pairs = reference_bipolar01
        first_chs, second_chs = list(zip(*difference_pairs))
        first_data = Pages.EEG_raw.get_data(picks=first_chs)
        second_data = Pages.EEG_raw.get_data(picks=second_chs)
    
        info = mne.create_info(
            ch_names=[f'{a}-{b}' for a, b in difference_pairs],
            sfreq=Pages.dirty_EDF.info.get('sfreq'),
            ch_types='eeg'
        )
    
        bipolar_01_Reref_CAR = mne.io.RawArray(
            data=first_data - second_data,
            info=info,
            first_samp=Pages.dirty_EDF.first_samp
        )

        bipolar_01_Reref_CAR.set_eeg_reference('average', projection=False)

        Pages.EEG_raw = copy.deepcopy(bipolar_01_Reref_CAR)

Any suggestion will be greatly appreciated.
Thank you for your time.

please try to reduce your code samples down to a minimal size, and provide something that we can copy/paste into our own terminals and have it reproduce the error youā€™re seeing. If possible, use the MNE-Python sample dataset or another dataset that has MNE-Python built-in support. If you canā€™t reproduce the error with a built-in dataset, then please also provide a link where we can download a (preferably small-ish) file that does exhibit the error when run through your (preferably short) code sample.

As an aside: your code sample formatting in the prior post is a little messed up; when you reply please double-check that the code parts of your new post are formatted as code / prose parts are not formatted as code.

Correction of the code edited down bellow.

Sorry to be pedantic, but this does not conform to my request that you

In particular: the path C:\\raw.fif will only exist on Windows computers (not Mac or Linux) and refers to a file that may not even be there on a given Windows computer. I notice that you do provide a link to raw.fif at the end of your post; if that is necessary to replicate the problem then thatā€™s fine, but in that case the line should be sample_data_raw_file = 'raw.fif' (without the C:\\ prefix) so that it can be loaded from the current working directory.

However, I suspect it would be possible to recreate this problem with the sample dataset (which is easier because most users already have it downloaded and it can be easily loaded no matter what their current working directory is). In our tutorials we have lots of examples of the following pattern:

sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
                                    'sample_audvis_filt-0-40_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file)

(this was copy-pasted from the first code block of the first sub-section of the first tutorial). The functions of the form mne.datasets.*.data_path() exist for exactly this use case: being able to write a couple lines of code that will load a particular data file no matter whose computer they are run on or what the current working directory is (data_path() will even download the dataset automatically if itā€™s not found). Please in future try to recreate your problems using a file from the sample dataset, and use this pattern (or an equivalent one) when creating code samples to illustrate your problems/questions.

Next bit of advice: Youā€™re providing 5 blocks of code ranging from 16-45 lines each. Many of those lines are redundant copy/pastes of each other, so it would be possible to do something like this:

raw.set_eeg_reference('Cz') # this fails, but works if I change 'Cz' to 'average' or []
... rest of first code block

This saves readers quite a lot of time because we can read one block of 16 lines instead of three blocks that are identical except for their first line. These are just a few ways to improve your post so that it is easier to answer. There are lots more resources online about how to write good ā€œMCVā€ (minimal, complete, verifiable) or ā€œMWEā€ (minimal working example) kind of questions.

In summary: we want to help you, but please take the time to make your questions as succinct as possible. The less time it takes to read your question and reproduce the problem locally, the more likely you are to get help.

Dear Professor McCoy,

You are not being pedantic, you are guiding me and I thank you for that.
The fact is that when I do:

sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
                                    'sample_audvis_filt-0-40_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file, preload=True )
print(raw.ch_names)

raw.set_eeg_reference(['EEG 001'])

difference_pairs = [('EEG 002', 'EEG 003'), ('EEG 004', 'EEG 005'), ('EEG 006','EEG 007')]# just doing 3 as illustration
first_chs, second_chs = list(zip(*difference_pairs))
first_data = raw.get_data(picks=first_chs)
second_data = raw.get_data(picks=second_chs)
info = mne.create_info(
    ch_names=[f'{a}-{b}' for a, b in difference_pairs],
    sfreq=raw.info.get('sfreq'),
    ch_types='eeg'
)

new_raw = mne.io.RawArray(
    data=first_data - second_data,
    info=info,
    first_samp=raw.first_samp
)

new_raw.plot()

It works! Consequently I assume that the problem is with my data configuration. Iā€™ll work to correct it. In this session you taught me
how to summarize data, use your samples and try before asking.
I (really) appreciate it.

1 Like

Hi Professor Mccoy,

If help someone:

After some ā€œtry and errorā€ Iā€™ve got Cz re-reference working(I mean, Cz as reference of bipolar montage) simply removing Cz from difference_pairs:

difference_pairs = [('Fp1', 'F7'), ('F7', 'T3'), ('T3', 'T5'), ('T5', 'O1'), ('Fp1', 'F3'), ('F3', 'C3'),
                               ('C3', 'P3'), ('P3', 'O1'), ('Fp2', 'F8'), ('F8', 'T4'), ('T4', 'T6'), ('T6', 'O2'),
                               ('Fp2', 'F4'), ('F4', 'C4'), ('C4', 'P4'), ('P4', 'O2'), 'Pz', 'Oz')]

Kind regards
PKanda :slight_smile:

1 Like