mne-bids: Sample NIRS data not read, problem with channel naming

Describe the bug

Sample NIRS data in the BIDS format can not be read due to some problems with channel names. This applies to the data set in the example below, but can be replicated with the data “Auditory Speech and Noise” dataset. Upgrading mne, mne_bids and mne_nirs from the latest official versions to the dev versions did not resolve the issue.

If the channels.tsv file in the subjects directory is renamed (channels.tsvxxx), the data can be read.

Steps to reproduce

from mne_bids import BIDSPath, read_raw_bids
from mne_nirs.datasets import fnirs_motor_group

bpath = BIDSPath(subject='01', task="tapping", root=fnirs_motor_group.data_path(), \
                     datatype="nirs", suffix="nirs", extension=".snirf")
raw = read_raw_bids(bids_path=bpath, verbose=False)

Expected results

I expect the data to be read.

Actual results

ValueError                                Traceback (most recent call last)
Cell In[8], line 6
      2 from mne_nirs.datasets import fnirs_motor_group
      4 bpath = BIDSPath(subject='01', task="tapping", root=fnirs_motor_group.data_path(), \
      5                      datatype="nirs", suffix="nirs", extension=".snirf")
----> 6 raw = read_raw_bids(bids_path=bpath, verbose=False)

File <decorator-gen-616>:10, in read_raw_bids(bids_path, extra_params, verbose)

File ~\anaconda3\envs\mne_13\lib\site-packages\mne_bids\read.py:780, in read_raw_bids(bids_path, extra_params, verbose)
    775 channels_fname = _find_matching_sidecar(bids_path,
    776                                         suffix='channels',
    777                                         extension='.tsv',
    778                                         on_error='warn')
    779 if channels_fname is not None:
--> 780     raw = _handle_channels_reading(channels_fname, raw)
    782 # Try to find an associated electrodes.tsv and coordsystem.json
    783 # to get information about the status and type of present channels
    784 on_error = 'warn' if suffix == 'ieeg' else 'ignore'

File ~\anaconda3\envs\mne_13\lib\site-packages\mne_bids\read.py:606, in _handle_channels_reading(channels_fname, raw)
    603     for bids_ch_name, raw_ch_name in zip(ch_names_tsv,
    604                                          raw.ch_names.copy()):
    605         if bids_ch_name != raw_ch_name:
--> 606             raw.rename_channels({raw_ch_name: bids_ch_name})
    608 # Set the channel types in the raw data according to channels.tsv
    609 channel_type_bids_mne_map_available_channels = {
    610     ch_name: ch_type
    611     for ch_name, ch_type in channel_type_bids_mne_map.items()
    612     if ch_name in raw.ch_names
    613 }

File <decorator-gen-43>:12, in rename_channels(self, mapping, allow_duplicates, verbose)

File ~\anaconda3\envs\mne_13\lib\site-packages\mne\channels\channels.py:414, in SetChannelsMixin.rename_channels(self, mapping, allow_duplicates, verbose)
    411 from ..io import BaseRaw
    413 ch_names_orig = list(self.info['ch_names'])
--> 414 rename_channels(self.info, mapping, allow_duplicates)
    416 # Update self._orig_units for Raw
    417 if isinstance(self, BaseRaw):
    418     # whatever mapping was provided, now we can just use a dict

File <decorator-gen-51>:12, in rename_channels(info, mapping, allow_duplicates, verbose)

File ~\anaconda3\envs\mne_13\lib\site-packages\mne\channels\channels.py:1113, in rename_channels(info, mapping, allow_duplicates, verbose)
   1111 # check that all the channel names are unique
   1112 if len(ch_names) != len(np.unique(ch_names)) and not allow_duplicates:
-> 1113     raise ValueError('New channel names are not unique, renaming failed')
   1115 # do the remapping in info
   1116 info['bads'] = bads

ValueError: New channel names are not unique, renaming failed

Additional information

Platform:         Windows-10-10.0.19045-SP0
Python:           3.10.8 | packaged by conda-forge | (main, Nov 22 2022, 08:16:33) [MSC v.1929 64 bit (AMD64)]
Executable:       C:\Users\loew_admin\anaconda3\envs\mne_13\python.exe
CPU:              Intel64 Family 6 Model 158 Stepping 9, GenuineIntel: 4 cores
Memory:           7.8 GB

mne:              1.4.dev0
numpy:            1.23.5 {MKL 2021.4-Product with 4 threads}
scipy:            1.10.0
matplotlib:       3.6.2 {backend=module://matplotlib_inline.backend_inline}

sklearn:          1.2.0
numba:            0.56.4
nibabel:          5.0.0
nilearn:          0.10.0
dipy:             1.5.0
openmeeg:         Not found
cupy:             Not found
pandas:           1.5.2
pyvista:          0.37.0 {OpenGL 4.5.0 - Build 31.0.101.2114 via Intel(R) HD Graphics 630}
pyvistaqt:        0.9.0
ipyvtklink:       0.2.3
vtk:              9.2.5
qtpy:             2.3.0 {PyQt5=5.15.6}
ipympl:           Not found
pyqtgraph:        0.13.1
pooch:            v1.6.0

mne_bids:         0.13.dev0
mne_nirs:         0.6.dev0
mne_features:     Not found
mne_qt_browser:   0.4.0
mne_connectivity: Not found
mne_icalabel:     Not found

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Comments: 26 (21 by maintainers)

Most upvoted comments

I explored this issue a bit more using my own data. I went from the data in original NIRx format to MNE raw, to snirf and finally to BIDS format. It looks all fine with regard to the channel names.

I seems that the channels.tsv file provided with sample data is the problem. When I read MNE-NIRS sample data with read_raw_snirf and then use write_raw_bids to export the data, then the channels.tsv is different from the channels.tsv file that comes with the sample data. The newly created channels.tsv file corresponds exactly to channel names in the data imported using read_raw_snirf. Maybe @rob-luke could check how he created the BIDS files from his original nirx files?

Below the code to create a new channels.tsv from a snirf file.

from pathlib import Path
import mne_nirs
from mne.io import read_raw_snirf
from mne_bids import BIDSPath, read_raw_bids, write_raw_bids

datapath = mne_nirs.datasets.block_speech_noise.data_path()
bpath = BIDSPath(root=datapath, subject='01', session="01", suffix="nirs", extension=".snirf", task="AudioSpeechNoise", datatype="nirs")
raw_snirf = read_raw_snirf(bpath, verbose=False)

bpath_out = BIDSPath(root=Path.cwd() / 'testchannelstsv', subject='01', session="01", suffix="nirs", extension=".snirf", task="AudioSpeechNoise", datatype="nirs")
write_raw_bids(raw_snirf, bpath_out, overwrite=True)