Repository: mne-tools/mne-python

Issue:
mne.io.read_raw cannot determine the correct reader when filename has multiple periods
### Description of the problem

A filename such as "my_eeg.2023.test.edf" will raise an error.

### Steps to reproduce

```Python
test = mne.ie.read_raw("my.file.name.edf")
```


### Link to data

_No response_

### Expected results

Should use the last extension and use the appropriate reader.

### Actual results

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 6
      3 file_to_process = "../data/test_eegs/patient.name.24-05-2021 18.14.46.edf"
      4 # Run the classifier, passing the name of the bucket, and the name of the key (i.e. filename).
      5 # Offsets are returned as a json string. The key is "offsets".
----> 6 offsets = classify(file_to_process)
      7 print(offsets)

File ~/Projects/company/project/src/classifier.py:66, in classify(filename)
     62 model.summary()
     64 file_to_process = filename
---> 66 raw_eeg = mne.io.read_raw(file_to_process, preload=True, encoding="latin1")
     67 raw_eeg.resample(DESIRED_SAMPLE_FREQ, n_jobs=4)
     68 filtered_eeg = raw_eeg.filter(l_freq=0.1, h_freq=10, picks=get_channels(raw_eeg))

File ~/Projects/company/project/src/mne/io/_read_raw.py:121, in read_raw(fname, preload, verbose, **kwargs)
    119 kwargs["preload"] = preload
    120 if ext not in readers:
--> 121     _read_unsupported(fname)
    122 these_readers = list(readers[ext].values())
    123 for reader in these_readers:

File ~/Projects/company/project/src/mne/io/_read_raw.py:41, in _read_unsupported(fname, **kwargs)
     39     msg += f" Try reading a {suggest} file instead."
     40 msg += " Consider using a dedicated reader function for more options."
---> 41 raise ValueError(msg)

ValueError: Unsupported file type (.name.24-05-2021 18.14.46.edf). Consider using a dedicated reader function for more options.

### Additional information

Platform:         Linux-5.10.0-21-amd64-x86_64-with-glibc2.31
Python:           3.10.9 (main, Jan 11 2023, 15:21:40) [GCC 11.2.0]
Executable:       /home/john/anaconda3/envs/company/bin/python
CPU:              : 8 cores
Memory:           46.9 GB

mne:              1.4.dev0
numpy:            1.24.2 {OpenBLAS 0.3.21 with 8 threads}
scipy:            1.10.1
matplotlib:       3.7.0 {backend=Qt5Agg}

sklearn:          1.2.1
numba:            Not found
nibabel:          Not found
nilearn:          Not found
dipy:             Not found
openmeeg:         Not found
cupy:             Not found
pandas:           1.5.3
pyvista:          Not found
pyvistaqt:        Not found
ipyvtklink:       Not found
vtk:              Not found
qtpy:             Not found
ipympl:           Not found
pyqtgraph:        Not found
pooch:            v1.6.0

mne_bids:         Not found
mne_nirs:         Not found
mne_features:     Not found
mne_qt_browser:   Not found
mne_connectivity: Not found
mne_icalabel:     Not found


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


Resolving patch:
diff --git a/mne/io/_read_raw.py b/mne/io/_read_raw.py
--- a/mne/io/_read_raw.py
+++ b/mne/io/_read_raw.py
@@ -12,7 +12,8 @@
                read_raw_fif, read_raw_eeglab, read_raw_cnt, read_raw_egi,
                read_raw_eximia, read_raw_nirx, read_raw_fieldtrip,
                read_raw_artemis123, read_raw_nicolet, read_raw_kit,
-               read_raw_ctf, read_raw_boxy, read_raw_snirf, read_raw_fil)
+               read_raw_ctf, read_raw_boxy, read_raw_snirf, read_raw_fil,
+               read_raw_nihon)
 from ..utils import fill_doc
 
 
@@ -29,6 +30,7 @@ def _read_unsupported(fname, **kwargs):
 # supported read file formats
 supported = {
     ".edf": dict(EDF=read_raw_edf),
+    ".eeg": dict(NihonKoden=read_raw_nihon),
     ".bdf": dict(BDF=read_raw_bdf),
     ".gdf": dict(GDF=read_raw_gdf),
     ".vhdr": dict(brainvision=read_raw_brainvision),
@@ -57,13 +59,23 @@ def _read_unsupported(fname, **kwargs):
 suggested = {
     ".vmrk": dict(brainvision=partial(_read_unsupported, suggest=".vhdr")),
     ".amrk": dict(brainvision=partial(_read_unsupported, suggest=".ahdr")),
-    ".eeg": dict(brainvision=partial(_read_unsupported, suggest=".vhdr")),
 }
 
 # all known file formats
 readers = {**supported, **suggested}
 
 
+def split_name_ext(fname):
+    """Return name and supported file extension."""
+    maxsuffixes = max(ext.count(".") for ext in supported)
+    suffixes = Path(fname).suffixes
+    for si in range(-maxsuffixes, 0):
+        ext = "".join(suffixes[si:]).lower()
+        if ext in readers:
+            return Path(fname).name[:-len(ext)], ext
+    return fname, None  # unknown file extension
+
+
 @fill_doc
 def read_raw(fname, *, preload=False, verbose=None, **kwargs):
     """Read raw file.
@@ -99,7 +111,7 @@ def read_raw(fname, *, preload=False, verbose=None, **kwargs):
     raw : mne.io.Raw
         Raw object.
     """
-    ext = "".join(Path(fname).suffixes)
+    _, ext = split_name_ext(fname)
     kwargs['verbose'] = verbose
     kwargs['preload'] = preload
     if ext not in readers:


Test patch:
diff --git a/mne/io/tests/test_read_raw.py b/mne/io/tests/test_read_raw.py
--- a/mne/io/tests/test_read_raw.py
+++ b/mne/io/tests/test_read_raw.py
@@ -5,11 +5,13 @@
 # License: BSD-3-Clause
 
 from pathlib import Path
+from shutil import copyfile
 
 import pytest
 
-from mne.io import read_raw
 from mne.datasets import testing
+from mne.io import read_raw
+from mne.io._read_raw import split_name_ext, readers
 
 
 base = Path(__file__).parent.parent
@@ -32,7 +34,7 @@ def test_read_raw_unsupported_multi(fname, tmp_path):
         read_raw(fname)
 
 
-@pytest.mark.parametrize('fname', ['x.vmrk', 'x.eeg'])
+@pytest.mark.parametrize('fname', ['x.vmrk', 'y.amrk'])
 def test_read_raw_suggested(fname):
     """Test handling of unsupported file types with suggested alternatives."""
     with pytest.raises(ValueError, match='Try reading'):
@@ -43,6 +45,8 @@ def test_read_raw_suggested(fname):
 
 
 @pytest.mark.parametrize('fname', [
+    base / 'tests/data/test_raw.fif',
+    base / 'tests/data/test_raw.fif.gz',
     base / 'edf/tests/data/test.edf',
     base / 'edf/tests/data/test.bdf',
     base / 'brainvision/tests/data/test.vhdr',
@@ -66,3 +70,22 @@ def test_read_raw_supported(fname):
     read_raw(fname, verbose=False)
     raw = read_raw(fname, preload=True)
     assert "data loaded" in str(raw)
+
+
+def test_split_name_ext():
+    """Test file name extension splitting."""
+    # test known extensions
+    for ext in readers:
+        assert split_name_ext(f"test{ext}")[1] == ext
+
+    # test unsupported extensions
+    for ext in ("this.is.not.supported", "a.b.c.d.e", "fif.gz.xyz"):
+        assert split_name_ext(f"test{ext}")[1] is None
+
+
+def test_read_raw_multiple_dots(tmp_path):
+    """Test if file names with multiple dots work correctly."""
+    src = base / 'edf/tests/data/test.edf'
+    dst = tmp_path / "test.this.file.edf"
+    copyfile(src, dst)
+    read_raw(dst)