Skip to content

Commit

Permalink
Set channel type to misc if we cannot perform a proper conversion fro…
Browse files Browse the repository at this point in the history
…m BIDS to MNE channel types

Previously, e.g. GSR and temperature channels could end up as "eeg" channels, for example.

Fixes mne-tools#1048
  • Loading branch information
hoechenberger committed Aug 19, 2022
1 parent 6ed57f5 commit 52edb4d
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 8 deletions.
2 changes: 2 additions & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <whats_new_previous_releases>`

.. include:: authors.rst
32 changes: 25 additions & 7 deletions mne_bids/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'
Expand All @@ -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:
Expand Down
30 changes: 30 additions & 0 deletions mne_bids/tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion mne_bids/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 52edb4d

Please sign in to comment.