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

BUG:mne.create_info function mismatch channel names with DigMontage #7169

Closed Mrswolf closed 4 years ago

Mrswolf commented 4 years ago

Describe the bug

In 0.19.2, mne.channels.read_montage has been replaced by mne.channels.make_standard_montage, which returns a DigMontage object.

However, mne.create_info with DigMontage can not match channel names in a case insensitive manner.

Steps to reproduce

import mne

montage = mne.channels.make_standard_montage('standard_1005')
info = mne.create_info(
    ['FPz'], 100, 
    ch_types=['eeg'], montage=montage)

Actual results

~/miniconda3/envs/ml/lib/python3.7/site-packages/mne/utils/_logging.py in wrapper(*args, **kwargs)
     88             with use_log_level(verbose_level):
     89                 return function(*args, **kwargs)
---> 90         return function(*args, **kwargs)
     91     return FunctionMaker.create(
     92         function, 'return decfunc(%(signature)s)',

~/miniconda3/envs/ml/lib/python3.7/site-packages/mne/io/meas_info.py in create_info(ch_names, sfreq, ch_types, montage, verbose)
   1798         for montage_ in montage:
   1799             if isinstance(montage_, (str, Montage, DigMontage)):
-> 1800                 _set_montage(info, montage_)
   1801             else:
   1802                 raise TypeError('Montage must be an instance of Montage, '

~/miniconda3/envs/ml/lib/python3.7/site-packages/mne/channels/montage.py in _set_montage(info, montage, update_ch_names, set_dig, raise_if_subset)
   1564             info_names=set([info['ch_names'][ii] for ii in _pick_chs(info)]),
   1565             montage_names=set(ch_pos),
-> 1566             raise_if_subset=_raise,  # XXX: deprecated param to remove in 0.20
   1567         )
   1568 

~/miniconda3/envs/ml/lib/python3.7/site-packages/mne/channels/montage.py in _check_ch_names_are_compatible(info_names, montage_names, raise_if_subset)
     91                 '\nYou can use `raise_if_subset=False` in `set_montage` to'
     92                 ' avoid this ValueError and get a DeprecationWarning instead.'
---> 93             ).format(n_ch=len(not_in_montage), ch_names=not_in_montage))
     94         else:
     95             # XXX: deprecated. to remove in 0.20 (raise_if_subset, too)

ValueError: DigMontage is a only a subset of info. There are 1 channel positions not present it the DigMontage. The required channels are: {'FPz'}.
You can use `raise_if_subset=False` in `set_montage` to avoid this ValueError and get a DeprecationWarning instead.
larsoner commented 4 years ago

It seems reasonable to me to make the name comparisons case insensitive. Okay with you @agramfort ?

agramfort commented 4 years ago

We doing the API change as mne requires unique channel names in case sensitive manner it was decided that case match should be enforced to avoid bugs. Maybe a param to set_montage could be added though. Something like match_case or something

TanTingyi commented 4 years ago

I meet the same bug. Channel naming rules are not uniform. Such as standard_1005: Fp1 & CP1.

agramfort commented 4 years ago

you mean our default standard montage are not uniformly named?

maybe a parameter in set_montage is the way.

TanTingyi commented 4 years ago

you mean our default standard montage are not uniformly named? maybe a parameter in set_montage is the way.

There isn't a 'set_montage' method for mne.create_info. When I only need info instance what can i do?

Mrswolf commented 4 years ago

@TanTingyi larsoner has already added match_case keyword to _set_montage, which is called by set_montage and create_info. Although we can not directly use match_case in create_info, now we can create raw object as follows:

import numpy as np
import mne

data = np.random.rand(1, 200)
info = mne.create_info(['FPz'], 100, ch_types=['eeg'])

raw = mne.io.RawArray(data, info)

montage =  mne.channels.make_standard_montage('standard_1005')
raw.set_montage(montage, match_case=False)

@larsoner Would you mind adding match_case option to create_info function in your PR?

agramfort commented 4 years ago

PR from @larsoner is now merged. I guess this one can now be closed.

larsoner commented 4 years ago

Agreed. I'd prefer to deprecate the montage argument in create_info rather than add new montage-related kwargs to it. Then set_montage would be the one place to go for this functionality.

agramfort commented 4 years ago

sounds good to me

dede3de commented 4 years ago

I am using raw.set_montage(montage, match_case=False) with the mnedev ('0.20.dev0'). However, it seems that it works with averaged epoched data, but not with raw data.

Everything works fine if I run:

data_path = r"D:\EEGdata";
fname = data_path + '\ppt10.set'
raw = mne.io.read_epochs_eeglab(fname)

sfreq = 1000
ch_types = ['eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eog','eog']
ch_names = ['FP1','FP2','F3', 'F4', 'C3', 'C4', 'P3', 'P4', 'O1', 'O2', 'F7', 'F8', 'T7', 'T8', 'P7', 'P8', 'FZ', 'CZ', 'PZ', 'OZ', 'FC1', 'FC2', 'CP1', 'CP2', 'FC5', 'FC6', 'CP5', 'CP6', 'FT9', 'FT10', 'TP9', 'TP10', 'F1', 'F2', 'C1', 'C2', 'P1', 'P2', 'AF3', 'AF4', 'FC3', 'FC4', 'CP3', 'CP4', 'PO3', 'PO4', 'F5', 'F6', 'C5', 'C6', 'P5', 'P6', 'AF7', 'AF8', 'FT7', 'FT8', 'TP7', 'TP8', 'PO7', 'PO8', 'FPZ', 'FCZ', 'CPZ', 'POZ', 'VEOG', 'HEOG']

info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
raw.info = info

montage =  mne.channels.make_standard_montage('standard_1020')
raw.set_montage(montage, match_case=False)

However, if I run

data_path = r"D:\EEGdata\raw";
raw_fname = data_path + '\ppt10_raw.set';
raw = mne.io.read_raw_eeglab(raw_fname);

it gives an error: TypeError: set_montage() got an unexpected keyword argument 'match_case'.

agramfort commented 4 years ago

now also part of #7262

dede3de commented 4 years ago

Does it now work with raw non-epoched data?

agramfort commented 4 years ago

it should work now in master

dede3de commented 4 years ago

I have downloaded the latest master version (0.20.dev0)

python setup.py develop
git pull origin master
From https://github.com/dede3de/mne-python
 * branch                master     -> FETCH_HEAD
Already up to date.

but the 'match_case' still doesn't work with raw data. Am I missing anything?

agramfort commented 4 years ago

can you provide a full snippet to replicate you observe? I see a match_case param in the code of current master

dede3de commented 4 years ago
data_path = r"D:\EEGdata\raw";
raw_fname = data_path + '\ppt10_raw.set';
raw = mne.io.read_raw_eeglab(raw_fname);

sfreq = 1000
ch_types = ['eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eeg','eog','eog']
ch_names = ['FP1','FP2','F3', 'F4', 'C3', 'C4', 'P3', 'P4', 'O1', 'O2', 'F7', 'F8', 'T7', 'T8', 'P7', 'P8', 'FZ', 'CZ', 'PZ', 'OZ', 'FC1', 'FC2', 'CP1', 'CP2', 'FC5', 'FC6', 'CP5', 'CP6', 'FT9', 'FT10', 'TP9', 'TP10', 'F1', 'F2', 'C1', 'C2', 'P1', 'P2', 'AF3', 'AF4', 'FC3', 'FC4', 'CP3', 'CP4', 'PO3', 'PO4', 'F5', 'F6', 'C5', 'C6', 'P5', 'P6', 'AF7', 'AF8', 'FT7', 'FT8', 'TP7', 'TP8', 'PO7', 'PO8', 'FPZ', 'FCZ', 'CPZ', 'POZ', 'VEOG', 'HEOG']

info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
raw.info = info

montage =  mne.channels.make_standard_montage('standard_1020')
raw.set_montage(montage, match_case=False)

TypeError: set_montage() got an unexpected keyword argument 'match_case'
drammock commented 4 years ago

Your git pull line shows you pulling from your own fork dede3de/mne-python. Are you certain that your fork's master branch is new enough to have the relevant changes? Do you still see the error if you pull from mne-tools/mne-python?

dede3de commented 4 years ago

I have tried to set up again the development environment from scratch, following the instructions here https://12983-1301584-gh.circle-artifacts.com/0/dev/contributing.html But again, the 'match_case' works with epoched data, but not with raw data. Is there anything I can check to make sure I have the latest master? Thanks!

agramfort commented 4 years ago

how do I get ppt10_raw.set ?

drammock commented 4 years ago

@dede3de note that the link to the contributor guide that you're using is a link to our CircleCI server (test builds for changes to the documentation), not the official docs, and it is at least a year old. The site for current master branch of development version is always at https://mne.tools/dev/, so you would want to use https://mne.tools/dev/install/contributing.html

That said, the contributor setup instructions haven't changed much since then, so your setup should be fine. If you want to be sure you're on latest master, do:

git checkout master
git fetch upstream
git merge upstream/master

You could also check by running git log -1 to see the most recent commit that you have locally, and see where that commit number falls in the commit history of master branch

That said, if it's working for epoched data then probably you must have the relevant changes already, and there's something weird about the continuous data file that is failing. If you could share ppt10_raw.set (as @agramfort asked) we can try to debug.

dede3de commented 4 years ago

Thank you @drammock for the explanation. I have followed your instructions and it now works. Thanks again!

iuri commented 2 years ago

Hello there, I'm trying to run set_montage in the BCI200 raw data but I'm still getting errors related to channels name. Raw data channels are: ch_names: Fc5., Fc3., Fc1., Fcz., Fc2., Fc4., Fc6., C5.., C3.., C1.., ...

Thus, when I ran

montage = mne.channels.make_standard_montage('biosemi64') print(montage)

<DigMontage | 0 extras (headshape), 0 HPIs, 3 fiducials, 64 channels> <bound method set_channel_types of <RawEDF | S003R03.edf, 64 x 20000 (125.0 s), ~9.8 MB, data loaded>>

raw.set_montage(montage)

I get the following error

ValueError Traceback (most recent call last) in () ----> 1 raw.set_montage(montage)

in set_montage(self, montage, match_case, match_alias, on_missing, verbose) 2 frames [/usr/local/lib/python3.7/dist-packages/mne/utils/check.py](https://localhost:8080/#) in _on_missing(on_missing, msg, name, error_klass) 870 on_missing = 'warn' if on_missing == 'warning' else on_missing 871 if on_missing == 'raise': --> 872 raise error_klass(msg) 873 elif on_missing == 'warn': 874 warn(msg) ValueError: DigMontage is only a subset of info. There are 64 channel positions not present in the DigMontage. The required channels are: ['Fc5.', 'Fc3.', 'Fc1.', 'Fcz.', 'Fc2.', 'Fc4.', 'Fc6.', 'C5..', 'C3..', 'C1..', 'Cz..', 'C2..', 'C4..', 'C6..', 'Cp5.', 'Cp3.', 'Cp1.', 'Cpz.', 'Cp2.', 'Cp4.', 'Cp6.', 'Fp1.', 'Fpz.', 'Fp2.', 'Af7.', 'Af3.', 'Afz.', 'Af4.', 'Af8.', 'F7..', 'F5..', 'F3..', 'F1..', 'Fz..', 'F2..', 'F4..', 'F6..', 'F8..', 'Ft7.', 'Ft8.', 'T7..', 'T8..', 'T9..', 'T10.', 'Tp7.', 'Tp8.', 'P7..', 'P5..', 'P3..', 'P1..', 'Pz..', 'P2..', 'P4..', 'P6..', 'P8..', 'Po7.', 'Po3.', 'Poz.', 'Po4.', 'Po8.', 'O1..', 'Oz..', 'O2..', 'Iz..']. Consider using inst.set_channel_types if these are not EEG channels, or use the on_missing parameter if the channel positions are allowed to be unknown in your analyses. However, they are EEG channels and positions must be known, thus: Datasamples I'm using are officially available at https://physionet.org/content/eegmmidb/1.0.0/ Must I rename channels as per indicated in this thread?
alexrockhill commented 2 years ago

You can try mne.datasets.eegbci.standardize(raw) before setting the montage or you can just remove the channel names like so raw.rename_channels({ch: ch.replace('.', '') for ch in raw.ch_names})

iuri commented 2 years ago

Thanks a lot Alex. The while point of renaming channels was because I'm stuck at ica.plot_components(outlines="skirt")

that returns the error

WARNING:root:Did not find any electrode locations (in the info object), will attempt to use digitization points instead. However, if digitization points do not correspond to the EEG electrodes, this will lead to bad results. Please verify that the sensor locations in the plot are accurate.

RuntimeError Traceback (most recent call last) in () ----> 1 ica.plot_components(outlines="skirt")

7 frames

in plot_ica_components(ica, picks, ch_type, res, vmin, vmax, cmap, sensors, colorbar, title, show, outlines, contours, image_interp, inst, plot_std, topomap_args, image_args, psd_args, reject, sphere, verbose) in plot_ica_components(ica, picks, ch_type, res, vmin, vmax, cmap, sensors, colorbar, title, show, outlines, contours, image_interp, inst, plot_std, topomap_args, image_args, psd_args, reject, sphere, verbose) [/usr/local/lib/python3.7/dist-packages/mne/channels/layout.py](https://localhost:8080/#) in _auto_topomap_coords(info, picks, ignore_overlap, to_sphere, sphere) 688 # Get EEG digitization points 689 if info['dig'] is None or len(info['dig']) == 0: --> 690 raise RuntimeError('No digitization points found.') 691 692 locs3d = np.array([point['r'] for point in info['dig'] RuntimeError: No digitization points found. Then when I googled it, I found set_montage command as a solution for that. https://github.com/mne-tools/mne-python/issues/4835 However, that didn`t solve the problem. at least in my case My final goal is to create a script to parse EEG data and build a file structure, which I could easily apply any sample of EEG signals/data from channels Fp1 and Fp2 in the following classification algorithm to decide where left or right command was imagined by the subject. https://github.com/wmvanvliet/neuroscience_tutorials/blob/master/eeg-bci/3.%20Imagined%20movement.ipynb
iuri commented 2 years ago

Does that make any sense at all?

Riccellisp commented 2 years ago

I'm trying to read an eeg data in csv format, but the digitized points still at zero. The acquisition equipment is the Emotiv epoch one, with 14 channels. The channel names are: ch_names = ['AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 'AF4']. Anyone with the same issue?

larsoner commented 2 years ago

@Riccellisp @iuri could you post to https://mne.discourse.group ? These seem more like usage questions than bugs

iuri commented 2 years ago

Hello Eric, Indeed, my question is totally about usage.

The essence, or context, of my question is regarding EDF conversion to .MAT,

The problem is that file structures are so different that make me lost, in the process of writing the algorithm. I have no idea how I could identify similar data samples in order to structure it in the proper places.

  1. MAT Data samples from BCI IV Competition, Berlin dataset 1. Matlab files (https://www.bbci.de/competition/iv/#dataset1 https://www.bbci.de/competition/iv/#dataset1)

  2. EDF Data samples from Physionet https://physionet.org/content/eegmmidb/1.0.0/

The goal is to run a python script, (https://github.com/wmvanvliet/neuroscience_tutorials/blob/master/eeg-bci/3.%20Imagined%20movement.ipynb https://github.com/wmvanvliet/neuroscience_tutorials/blob/master/eeg-bci/3.%20Imagined%20movement.ipynb) which works fine with MAT sample files, but with another data sample. For instance, I’ve chosen Physionet EDF files.

That's why I need to convert Physionet .EDF files to .MAT files, within the same file structure, to be able to run the script and analyze if the processing ( i.e. feature extraction, classification, Spatial filters, PSD - Potential Spectral Density, confusion matrix, accuracy, etc), are still valid/consistent.

Let me know if my approach makes any sense. Please.

Below, I will try to explain the scenario by writing examples of codes and comments.

i) .MAT file has beendefined as : Sample Shape: triasl[cl1] (59, 200, 100) # trials[cl1].shape Sample Shape: triasl[cl2] (59, 200, 100) # trials[cl2].shape Channels: 59 Sample rate: 100Hz

ii) while EDF file has been defined as: Samples Shape: (64, 20000) # xraw.shape Channels Nr.: 64 Sample rate: 160.00 Hz

So, there was no pre-processing step to the EDF file. Meaning, there’s no differentiation between left and right classes. Neither, channels data have been well structured by default as they are within .MAT files. (ie. as in trials[cl1] and trials[cl2])

Therefore, where can I find data corresponding to Fp1, Fpz and Fp2 within EDF? Where are the channels samples?

ANSW:

print(rawEEG.ch_names[21]) # Fp1 print(rawEEG.ch_names[22]) # Fpz print(rawEEG.ch_names[23]) # Fp2

Then, my guess is that their corresponding data is within xraw = rawEEG.get_data() xraw.shape # (64, 20000)

print(xraw[21]) # array([-7.1e-05, -8.3e-05, -8.4e-05, ..., 0.0e+00, 0.0e+00, 0.0e+00]) print(xraw[22]) # array([-5.8e-05, -7.1e-05, -7.1e-05, ..., 0.0e+00, 0.0e+00, 0.0e+00]) print(xraw[23]) # array([-6.0e-05, -7.4e-05, -7.1e-05, ..., 0.0e+00, 0.0e+00, 0.0e+00])

are they actually the samples I’m looking for?

.. and about the rest of the file structure?

I did even tried to adapt your functions: (i) psd(trials_filt[cl1])and psd(trials_filt[cl2]), (ii) logvar(trials) and (iii) train_lda(train[cl1].T, train[cl2].T),

but again I got stuck in the channel and data samples. They simply don’t match.

So, How I would post that in the discussion forum? I believe that's quite a bit our of the scope. rsrs ;)

Bet wishes, I

On May 25, 2022, at 12:37 PM, Eric Larson @.***> wrote:

@Riccellisp https://github.com/Riccellisp @iuri https://github.com/iuri could you post to https://mne.discourse.group https://mne.discourse.group/ ? These seem more like usage questions than bugs

— Reply to this email directly, view it on GitHub https://github.com/mne-tools/mne-python/issues/7169#issuecomment-1137449722, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEZZ5JMGDPU6CJX63WTKUTVLZCMBANCNFSM4KBNGL2A. You are receiving this because you were mentioned.

agramfort commented 2 years ago

please use the forum and not the bug tracker to discuss usage questions.

thank you

Message ID: @.***>

soso-maitha commented 2 years ago

i am getting the same error and I've spent a lot of time to find answers or examples to solve the error but i couldn't find helpful resources. How was your issue resolved ?

iuri commented 2 years ago

Hello Maitha, As indicated by Alexandre, we must use the forum for this type of questions.

https://mne.discourse.group/ https://mne.discourse.group/ https://mne.discourse.group/t/parsing-edf-dataset-to-mat-matlab-file-structure/5071/6 https://mne.discourse.group/t/parsing-edf-dataset-to-mat-matlab-file-structure/5071/6

On Jul 31, 2022, at 3:04 PM, Maitha Khanji @.***> wrote:

i am getting the same error and I've spent a lot of time to find answers or examples to solve the error but i couldn't find helpful resources. How was your issue resolved ?

— Reply to this email directly, view it on GitHub https://github.com/mne-tools/mne-python/issues/7169#issuecomment-1200471937, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEZZ5J5E7H7V6KMQXQHPW3VW25ZXANCNFSM4KBNGL2A. You are receiving this because you were mentioned.