Skip to content
1 change: 1 addition & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~
Expand Down
18 changes: 15 additions & 3 deletions mne/io/_read_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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),
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
27 changes: 25 additions & 2 deletions mne/io/tests/test_read_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'):
Expand All @@ -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',
Expand All @@ -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)