diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index c2241d30d0f..bc65fe54237 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -54,6 +54,7 @@ Bugs - All functions accepting paths can now correctly handle :class:`~pathlib.Path` as input. Historically, we expected strings (instead of "proper" path objects), and only added :class:`~pathlib.Path` support in a few select places, leading to inconsistent behavior. (:gh:`11473` and :gh:`11499` by `Mathieu Scheltienne`_) - Fix visualization dialog compatibility with matplotlib 3.7 (:gh:`11409` by `Daniel McCloy`_ and `Eric Larson`_) - Expand tilde (user directory) in config keys (:gh:`11537` by `Clemens Brunner`_) +- Fix :func:`mne.io.read_raw` for file names containing multiple dots (:gh:`11521` by `Clemens Brunner`_) API changes ~~~~~~~~~~~ diff --git a/mne/io/_read_raw.py b/mne/io/_read_raw.py index e70a571e03b..5227b33ae86 100644 --- 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: diff --git a/mne/io/tests/test_read_raw.py b/mne/io/tests/test_read_raw.py index da95e816a6e..13c696f0f17 100644 --- 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)