From 52edb4d34a06d43d69ad69255445763dc333930f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 19 Aug 2022 09:21:07 +0200 Subject: [PATCH] Set channel type to misc if we cannot perform a proper conversion from BIDS to MNE channel types Previously, e.g. GSR and temperature channels could end up as "eeg" channels, for example. Fixes #1048 --- doc/whats_new.rst | 2 ++ mne_bids/read.py | 32 +++++++++++++++++++++++++------- mne_bids/tests/test_read.py | 30 ++++++++++++++++++++++++++++++ mne_bids/utils.py | 2 +- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 2f07a5303..afefb24b0 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -94,6 +94,8 @@ Detailed list of changes - Internal helper function to :func:`~mne_bids.read_raw_bids` would reject BrainVision data if ``_scans.tsv`` listed a ``.eeg`` file instead of ``.vhdr``, by `Teon Brooks`_ (:gh:`1034`) +- Whenever :func:`~mne_bids.read_raw_bids` encounters a channel type that currently doesn't translate into an appropriate MNE channel type, the channel type will now be set to ``'misc``. Previously, seemingly arbitrary channel types would be applied, e.g. ``'eeg'`` for GSR and temperature channels, by `Richard Höchenberger`_ (:gh:`1052`) + :doc:`Find out what was new in previous releases ` .. include:: authors.rst diff --git a/mne_bids/read.py b/mne_bids/read.py index df4d566e1..76d500c45 100644 --- a/mne_bids/read.py +++ b/mne_bids/read.py @@ -507,12 +507,19 @@ def _handle_channels_reading(channels_fname, raw): # Now we can do some work. # The "type" column is mandatory in BIDS. We can use it to set channel # types in the raw data using a mapping between channel types - channel_type_dict = dict() + channel_type_bids_mne_map = dict() # Get the best mapping we currently have from BIDS to MNE nomenclature bids_to_mne_ch_types = _get_ch_type_mapping(fro='bids', to='mne') ch_types_json = channels_dict['type'] for ch_name, ch_type in zip(ch_names_tsv, ch_types_json): + # We don't map MEG channels for now, as there's no clear 1:1 mapping + # from BIDS to MNE coil types. + if ch_type in ( + 'MEGGRADAXIAL', 'MEGMAG', 'MEGREFGRADAXIAL', 'MEGGRADPLANAR', + 'MEGREFMAG', 'MEGOTHER' + ): + continue # Try to map from BIDS nomenclature to MNE, leave channel type # untouched if we are uncertain @@ -530,8 +537,16 @@ def _handle_channels_reading(channels_fname, raw): 'will raise an error in the future.') warn(msg) - if updated_ch_type is not None: - channel_type_dict[ch_name] = updated_ch_type + if updated_ch_type is None: + # We don't have an appropriate mapping, so make it a "misc" channel + channel_type_bids_mne_map[ch_name] = 'misc' + warn( + f'No BIDS -> MNE mapping found for channel type "{ch_type}". ' + f'Type of channel "{ch_name}" will be set to "misc".' + ) + else: + # We found a mapping, so use it + channel_type_bids_mne_map[ch_name] = updated_ch_type # Special handling for (synthesized) stimulus channel synthesized_stim_ch_name = 'STI 014' @@ -556,16 +571,19 @@ def _handle_channels_reading(channels_fname, raw): raw.rename_channels({raw_ch_name: bids_ch_name}) # Set the channel types in the raw data according to channels.tsv - ch_type_map_avail = { + channel_type_bids_mne_map_available_channels = { ch_name: ch_type - for ch_name, ch_type in channel_type_dict.items() + for ch_name, ch_type in channel_type_bids_mne_map.items() if ch_name in raw.ch_names } - ch_diff = set(channel_type_dict.keys()) - set(ch_type_map_avail.keys()) + ch_diff = ( + set(channel_type_bids_mne_map.keys()) - + set(channel_type_bids_mne_map_available_channels.keys()) + ) if ch_diff: warn(f'Cannot set channel type for the following channels, as they ' f'are missing in the raw data: {", ".join(sorted(ch_diff))}') - raw.set_channel_types(ch_type_map_avail) + raw.set_channel_types(channel_type_bids_mne_map_available_channels) # Set bad channels based on _channels.tsv sidecar if 'status' in channels_dict: diff --git a/mne_bids/tests/test_read.py b/mne_bids/tests/test_read.py index 0907da8b3..ea29968fc 100644 --- a/mne_bids/tests/test_read.py +++ b/mne_bids/tests/test_read.py @@ -1069,6 +1069,36 @@ def test_handle_channel_type_casing(tmp_path): read_raw_bids(bids_path) +@pytest.mark.filterwarnings(warning_str['channel_unit_changed']) +def test_handle_non_mne_channel_type(tmp_path): + """Test that channel types not known to MNE will be read as 'misc'.""" + bids_path = _bids_path.copy().update(root=tmp_path) + raw = _read_raw_fif(raw_fname, verbose=False) + + write_raw_bids(raw, bids_path, overwrite=True, + verbose=False) + + channels_tsv_path = bids_path.copy().update( + root=tmp_path, + datatype='meg', + suffix='channels', + extension='.tsv' + ).fpath + + channels_data = _from_tsv(channels_tsv_path) + # Violates BIDS, but ensures we won't have an appropriate + # BIDS -> MNE mapping. + ch_idx = -1 + channels_data['type'][ch_idx] = 'FOOBAR' + _to_tsv(data=channels_data, fname=channels_tsv_path) + + with pytest.warns(RuntimeWarning, match='will be set to \"misc\"'): + raw = read_raw_bids(bids_path) + + # Should be a 'misc' channel. + assert raw.get_channel_types([channels_data['name'][ch_idx]]) == ['misc'] + + @pytest.mark.filterwarnings(warning_str['channel_unit_changed']) def test_bads_reading(tmp_path): bids_path = _bids_path.copy().update(root=tmp_path, datatype='meg') diff --git a/mne_bids/utils.py b/mne_bids/utils.py index 7af2a5b62..8e0454814 100644 --- a/mne_bids/utils.py +++ b/mne_bids/utils.py @@ -78,7 +78,7 @@ def _get_ch_type_mapping(fro='mne', to='bids'): mapping = dict(EEG='eeg', MISC='misc', TRIG='stim', EMG='emg', ECOG='ecog', SEEG='seeg', EOG='eog', ECG='ecg', RESP='resp', NIRS='fnirs_cw_amplitude', - # No MEG channels for now + # No MEG channels for now (see Notes above) # Many to one mapping VEOG='eog', HEOG='eog', DBS='dbs') else: