Repository: mne-tools/mne-bids

Issue:
Add support for Curry .cdt input files
### Describe the problem

It seems, bids conversion does not support Curry files (.cdt). Is there any reason for this and would anything speak against adding support for it?

### Describe your solution

One could extend the `ALLOWED_INPUT_EXTENSIONS` in `mne_bids.confg`.

### Describe possible alternatives

The alternative is an additional conversion step into the supported file-formats (not practical).

### Additional context

_No response_


Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

IIUC, the only reason any file system to be read in isn't supported is entirely due to guardrails from user feeding in data we don't know how to convert. 

Curry is pretty popular system and if it has a reader in mne then I think that's good to include. 

Would you be up to just modifying the necessary code and adding a unit test?
Thanks for the quick reply. MNE has a functional curry reader and yes, I can work on that! 

Resolving patch:
diff --git a/mne_bids/config.py b/mne_bids/config.py
--- a/mne_bids/config.py
+++ b/mne_bids/config.py
@@ -59,7 +59,8 @@
                      '.lay': 'Persyst', '.dat': 'Persyst',
                      '.EEG': 'Nihon Kohden',
                      '.cnt': 'Neuroscan', '.CNT': 'Neuroscan',
-                     '.bin': 'EGI'}
+                     '.bin': 'EGI',
+                     '.cdt': 'Curry'}
 
 ieeg_manufacturers = {'.vhdr': 'Brain Products', '.eeg': 'Brain Products',
                       '.edf': 'n/a', '.EDF': 'n/a', '.set': 'n/a',
@@ -79,7 +80,8 @@
           '.EEG': io.read_raw_nihon,
           '.cnt': io.read_raw_cnt, '.CNT': io.read_raw_cnt,
           '.bin': io.read_raw_egi,
-          '.snirf': io.read_raw_snirf}
+          '.snirf': io.read_raw_snirf,
+          '.cdt': io.read_raw_curry}
 
 
 # Merge the manufacturer dictionaries in a python2 / python3 compatible way
@@ -130,7 +132,7 @@
 ALLOWED_INPUT_EXTENSIONS = \
     allowed_extensions_meg + allowed_extensions_eeg + \
     allowed_extensions_ieeg + allowed_extensions_nirs + \
-    ['.lay', '.EEG', '.cnt', '.CNT', '.bin']
+    ['.lay', '.EEG', '.cnt', '.CNT', '.bin', '.cdt']
 
 # allowed suffixes (i.e. last "_" delimiter in the BIDS filenames before
 # the extension)
diff --git a/mne_bids/read.py b/mne_bids/read.py
--- a/mne_bids/read.py
+++ b/mne_bids/read.py
@@ -56,7 +56,8 @@ def _read_raw(raw_path, electrode=None, hsp=None, hpi=None,
     elif ext == '.fif':
         raw = reader[ext](raw_path, allow_maxshield, **kwargs)
 
-    elif ext in ['.ds', '.vhdr', '.set', '.edf', '.bdf', '.EDF', '.snirf']:
+    elif ext in ['.ds', '.vhdr', '.set', '.edf', '.bdf', '.EDF', '.snirf',
+                 '.cdt']:
         raw_path = Path(raw_path)
         raw = reader[ext](raw_path, **kwargs)
 


Test patch:
diff --git a/mne_bids/tests/test_write.py b/mne_bids/tests/test_write.py
--- a/mne_bids/tests/test_write.py
+++ b/mne_bids/tests/test_write.py
@@ -112,6 +112,7 @@ def fn(fname, *args, **kwargs):
 _read_raw_cnt = _wrap_read_raw(mne.io.read_raw_cnt)
 _read_raw_snirf = _wrap_read_raw(mne.io.read_raw_snirf)
 _read_raw_egi = _wrap_read_raw(mne.io.read_raw_egi)
+_read_raw_curry = _wrap_read_raw(mne.io.read_raw_curry)
 
 # parametrized directory, filename and reader for EEG/iEEG data formats
 test_eegieeg_data = [
@@ -120,6 +121,7 @@ def fn(fname, *args, **kwargs):
     ('NihonKohden', 'MB0400FU.EEG', _read_raw_nihon),
     ('CNT', 'scan41_short.cnt', _read_raw_cnt),
     ('EGI', 'test_egi.mff', _read_raw_egi),
+    ('curry', 'test_bdf_stim_channel Curry 8.cdt', _read_raw_curry),
 ]
 test_convert_data = test_eegieeg_data.copy()
 test_convert_data.append(('CTF', 'testdata_ctf.ds', _read_raw_ctf))
@@ -135,9 +137,11 @@ def fn(fname, *args, **kwargs):
     ('Persyst', 'BrainVision', 'sub-pt1_ses-02_task-monitor_acq-ecog_run-01_clip2.lay', _read_raw_persyst),  # noqa
     ('NihonKohden', 'BrainVision', 'MB0400FU.EEG', _read_raw_nihon),
     ('CNT', 'BrainVision', 'scan41_short.cnt', _read_raw_cnt),
+    ('curry', 'BrainVision', 'test_bdf_stim_channel Curry 8.cdt', _read_raw_curry),  # noqa
     ('Persyst', 'EDF', 'sub-pt1_ses-02_task-monitor_acq-ecog_run-01_clip2.lay', _read_raw_persyst),  # noqa
     ('NihonKohden', 'EDF', 'MB0400FU.EEG', _read_raw_nihon),
-    ('CNT', 'EDF', 'scan41_short.cnt', _read_raw_cnt)
+    ('CNT', 'EDF', 'scan41_short.cnt', _read_raw_cnt),
+    ('curry', 'EDF', 'test_bdf_stim_channel Curry 8.cdt', _read_raw_curry)
 ]
 
 data_path = testing.data_path(download=False)
@@ -1239,6 +1243,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         bids_output_path = write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            bids_output_path = write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1297,6 +1306,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1336,6 +1350,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1412,6 +1431,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1473,6 +1497,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
             match = r"^EDF\/EDF\+\/BDF files contain two fields .*"
             with pytest.warns(RuntimeWarning, match=match):
                 write_raw_bids(**kwargs)
+        elif dir_name == 'curry':
+            with pytest.warns(RuntimeWarning,
+                              match='Encountered data in "int" format. '
+                              'Converting to float32.'):
+                write_raw_bids(**kwargs)
         elif dir_name == 'Persyst':
             with pytest.warns(RuntimeWarning,
                               match='Encountered data in "double" format'):
@@ -1520,6 +1549,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=ieeg_raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1572,6 +1606,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs = dict(raw=ieeg_raw, bids_path=bids_path, overwrite=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1635,6 +1674,11 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
     kwargs.update(montage=ecog_montage, acpc_aligned=True)
     if dir_name == 'EDF':
         write_raw_bids(**kwargs)
+    elif dir_name == 'curry':
+        with pytest.warns(RuntimeWarning,
+                          match='Encountered data in "int" format. '
+                          'Converting to float32.'):
+            write_raw_bids(**kwargs)
     elif dir_name == 'NihonKohden':
         with pytest.warns(RuntimeWarning,
                           match='Encountered data in "short" format'):
@@ -1726,6 +1770,12 @@ def test_eegieeg(dir_name, fname, reader, _bids_validate, tmp_path):
                     output_path = _test_anonymize(
                         tmp_path / 'd', raw, bids_path
                     )
+        elif dir_name == 'curry':
+            with pytest.warns(RuntimeWarning,
+                              match='Encountered data in "int" format. '
+                              'Converting to float32.'):
+                write_raw_bids(**kwargs)
+                output_path = _test_anonymize(tmp_path / 'd', raw, bids_path)
         else:
             with pytest.warns(RuntimeWarning,
                               match='Encountered data in "double" format'):
@@ -3091,6 +3141,11 @@ def test_convert_eeg_formats(dir_name, format, fname, reader, tmp_path):
                               match='Encountered data in "int" format. '
                               'Converting to float32.'):
                 bids_output_path = write_raw_bids(**kwargs)
+        elif dir_name == 'curry':
+            with pytest.warns(RuntimeWarning,
+                              match='Encountered data in "int" format. '
+                              'Converting to float32.'):
+                bids_output_path = write_raw_bids(**kwargs)
         else:
             with pytest.warns(RuntimeWarning,
                               match='Encountered data in "double" format'):