From 476c5eeb11075a3aab5a8230884f0097d3e43691 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 26 May 2021 10:13:38 -0400 Subject: [PATCH] ENH: Add mne.export --- doc/changes/latest.inc | 2 + doc/export.rst | 17 ++++ doc/python_reference.rst | 1 + mne/__init__.py | 1 + mne/conftest.py | 3 + mne/epochs.py | 39 +------- mne/export/__init__.py | 1 + mne/export/_eeglab.py | 69 ++++++++++++++ mne/export/_export.py | 121 +++++++++++++++++++++++++ mne/export/tests/test_export.py | 64 +++++++++++++ mne/io/base.py | 43 +-------- mne/io/tests/test_raw.py | 24 +---- mne/io/utils.py | 21 ----- mne/tests/test_docstring_parameters.py | 3 +- mne/tests/test_epochs.py | 31 +------ mne/utils/__init__.py | 2 +- mne/utils/check.py | 43 --------- mne/utils/docs.py | 2 +- 18 files changed, 294 insertions(+), 193 deletions(-) create mode 100644 doc/export.rst create mode 100644 mne/export/__init__.py create mode 100644 mne/export/_eeglab.py create mode 100644 mne/export/_export.py create mode 100644 mne/export/tests/test_export.py diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index ee341d40815..9db709666dd 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -39,6 +39,8 @@ Enhancements - New function :func:`mne.label.find_pos_in_annot` to get atlas label for MRI coordinates. (:gh:`9376` by **by new contributor** |Marian Dovgialo|_) +- New namespace `mne.export` created to contain functions (such as `mne.export.export_raw` and `mne.export.export_epochs`) for exporting data to non-FIF formats (:gh:`9427` by `Eric Larson`_) + - Add support for Hitachi fNIRS devices in `mne.io.read_raw_hitachi` (:gh:`9391` by `Eric Larson`_) - Add support for ``picks`` in :func:`mne.stc_near_sensors` (:gh:`9396` by `Eric Larson`_) diff --git a/doc/export.rst b/doc/export.rst new file mode 100644 index 00000000000..a8349143ba8 --- /dev/null +++ b/doc/export.rst @@ -0,0 +1,17 @@ + +Exporting +================ + +:py:mod:`mne.export`: + +.. automodule:: mne.export + :no-members: + :no-inherited-members: + +.. currentmodule:: mne.export + +.. autosummary:: + :toctree: generated/ + + export_epochs + export_raw diff --git a/doc/python_reference.rst b/doc/python_reference.rst index a5d8ccdb7fd..76c306d4210 100644 --- a/doc/python_reference.rst +++ b/doc/python_reference.rst @@ -27,6 +27,7 @@ directly from a terminal, see :ref:`python_commands`. reading_raw_data file_io creating_from_arrays + export datasets visualization preprocessing diff --git a/mne/__init__.py b/mne/__init__.py index 5192d5f3f7d..66893ab4b60 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -121,6 +121,7 @@ from . import time_frequency from . import viz from . import decoding +from . import export # initialize logging set_log_level(None, False) diff --git a/mne/conftest.py b/mne/conftest.py index bb921494e45..8d1c655ee74 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -38,6 +38,9 @@ fname_trans = op.join(s_path, 'sample_audvis_trunc-trans.fif') +collect_ignore = ['export/_eeglab.py'] + + def pytest_configure(config): """Configure pytest options.""" # Markers diff --git a/mne/epochs.py b/mne/epochs.py index 5b35bf22e86..bc0dc7551cc 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -20,7 +20,6 @@ import numpy as np -from .io.utils import _get_als_coords_from_chs from .io.write import (start_file, start_block, end_file, end_block, write_int, write_float, write_float_matrix, write_double_matrix, write_complex_float_matrix, @@ -52,15 +51,14 @@ from .utils import (_check_fname, check_fname, logger, verbose, _time_mask, check_random_state, warn, _pl, sizeof_fmt, SizeMixin, copy_function_doc_to_method_doc, - _check_pandas_installed, _check_eeglabio_installed, + _check_pandas_installed, _check_preload, GetEpochsMixin, _prepare_read_metadata, _prepare_write_metadata, _check_event_id, _gen_events, _check_option, _check_combine, ShiftTimeMixin, _build_data_frame, _check_pandas_index_arguments, _convert_times, _scale_dataframe_data, _check_time_format, object_size, - _on_missing, _validate_type, _ensure_events, - _infer_check_export_fmt) + _on_missing, _validate_type, _ensure_events) from .utils.docs import fill_doc from .data.html_templates import epochs_template @@ -1836,37 +1834,8 @@ def export(self, fname, fmt='auto', verbose=None): ----- %(export_eeglab_note)s """ - supported_export_formats = { - 'eeglab': ('set',), - 'edf': ('edf',), - 'brainvision': ('eeg', 'vmrk', 'vhdr',) - } - fmt = _infer_check_export_fmt(fmt, fname, supported_export_formats) - - if fmt == 'eeglab': - _check_eeglabio_installed() - import eeglabio.epochs - # load data first - self.load_data() - - # remove extra epoc and STI channels - drop_chs = ['epoc', 'STI 014'] - ch_names = [ch for ch in self.ch_names if ch not in drop_chs] - cart_coords = _get_als_coords_from_chs(self.info['chs'], - drop_chs) - - eeglabio.epochs.export_set(fname, - data=self.get_data(picks=ch_names), - sfreq=self.info['sfreq'], - events=self.events, - tmin=self.tmin, tmax=self.tmax, - ch_names=ch_names, - event_id=self.event_id, - ch_locs=cart_coords) - elif fmt == 'edf': - raise NotImplementedError('Export to EDF format not implemented.') - elif fmt == 'brainvision': - raise NotImplementedError('Export to BrainVision not implemented.') + from .export import export_epochs + export_epochs(fname, self, fmt, verbose) def equalize_event_counts(self, event_ids=None, method='mintime'): """Equalize the number of trials in each condition. diff --git a/mne/export/__init__.py b/mne/export/__init__.py new file mode 100644 index 00000000000..8e4ab0dad9b --- /dev/null +++ b/mne/export/__init__.py @@ -0,0 +1 @@ +from ._export import export_raw, export_epochs diff --git a/mne/export/_eeglab.py b/mne/export/_eeglab.py new file mode 100644 index 00000000000..52ee06369d6 --- /dev/null +++ b/mne/export/_eeglab.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Authors: MNE Developers +# +# License: BSD (3-clause) + +import numpy as np + +from ..utils import _check_eeglabio_installed +_check_eeglabio_installed() +import eeglabio.raw # noqa: E402 +import eeglabio.epochs # noqa: E402 + + +def _export_raw(fname, raw): + # load data first + raw.load_data() + + # remove extra epoc and STI channels + drop_chs = ['epoc'] + if not (raw.filenames[0].endswith('.fif')): + drop_chs.append('STI 014') + + ch_names = [ch for ch in raw.ch_names if ch not in drop_chs] + cart_coords = _get_als_coords_from_chs(raw.info['chs'], drop_chs) + + annotations = [raw.annotations.description, + raw.annotations.onset, + raw.annotations.duration] + eeglabio.raw.export_set( + fname, data=raw.get_data(picks=ch_names), sfreq=raw.info['sfreq'], + ch_names=ch_names, ch_locs=cart_coords, annotations=annotations) + + +def _export_epochs(fname, epochs): + _check_eeglabio_installed() + # load data first + epochs.load_data() + + # remove extra epoc and STI channels + drop_chs = ['epoc', 'STI 014'] + ch_names = [ch for ch in epochs.ch_names if ch not in drop_chs] + cart_coords = _get_als_coords_from_chs(epochs.info['chs'], drop_chs) + + eeglabio.epochs.export_set( + fname, data=epochs.get_data(picks=ch_names), + sfreq=epochs.info['sfreq'], events=epochs.events, + tmin=epochs.tmin, tmax=epochs.tmax, ch_names=ch_names, + event_id=epochs.event_id, ch_locs=cart_coords) + + +def _get_als_coords_from_chs(chs, drop_chs=None): + """Extract channel locations in ALS format (x, y, z) from a chs instance. + + Returns + ------- + None if no valid coordinates are found (all zeros) + """ + if drop_chs is None: + drop_chs = [] + cart_coords = np.array([d['loc'][:3] for d in chs + if d['ch_name'] not in drop_chs]) + if cart_coords.any(): # has coordinates + # (-y x z) to (x y z) + cart_coords[:, 0] = -cart_coords[:, 0] # -y to y + # swap x (1) and y (0) + cart_coords[:, [0, 1]] = cart_coords[:, [1, 0]] + else: + cart_coords = None + return cart_coords diff --git a/mne/export/_export.py b/mne/export/_export.py new file mode 100644 index 00000000000..52afb4f8d4f --- /dev/null +++ b/mne/export/_export.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# Authors: MNE Developers +# +# License: BSD (3-clause) + +import os.path as op + +from ..utils import verbose, _validate_type + + +@verbose +def export_raw(fname, raw, fmt='auto', verbose=None): + """Export Raw to external formats. + + Supported formats: EEGLAB (set, uses :mod:`eeglabio`) + %(export_warning)s + + Parameters + ---------- + %(export_params_fname)s + raw : instance of Raw + The raw instance to export. + %(export_params_fmt)s + %(verbose)s + + Notes + ----- + %(export_eeglab_note)s + """ + supported_export_formats = { # format : extensions + 'eeglab': ('set',), + 'edf': ('edf',), + 'brainvision': ('eeg', 'vmrk', 'vhdr',) + } + fmt = _infer_check_export_fmt(fmt, fname, supported_export_formats) + + if fmt == 'eeglab': + from ._eeglab import _export_raw + _export_raw(fname, raw) + elif fmt == 'edf': + raise NotImplementedError('Export to EDF format not implemented.') + elif fmt == 'brainvision': + raise NotImplementedError('Export to BrainVision not implemented.') + + +@verbose +def export_epochs(fname, epochs, fmt='auto', verbose=None): + """Export Epochs to external formats. + + Supported formats: EEGLAB (set, uses :mod:`eeglabio`) + %(export_warning)s + + Parameters + ---------- + %(export_params_fname)s + epochs : instance of Epochs + The epochs to export. + %(export_params_fmt)s + %(verbose)s + + Notes + ----- + %(export_eeglab_note)s + """ + supported_export_formats = { + 'eeglab': ('set',), + 'edf': ('edf',), + 'brainvision': ('eeg', 'vmrk', 'vhdr',) + } + fmt = _infer_check_export_fmt(fmt, fname, supported_export_formats) + + if fmt == 'eeglab': + from ._eeglab import _export_epochs + _export_epochs(fname, epochs) + elif fmt == 'edf': + raise NotImplementedError('Export to EDF format not implemented.') + elif fmt == 'brainvision': + raise NotImplementedError('Export to BrainVision not implemented.') + + +def _infer_check_export_fmt(fmt, fname, supported_formats): + """Infer export format from filename extension if auto. + + Raises error if fmt is auto and no file extension found, + then checks format against supported formats, raises error if format is not + supported. + + Parameters + ---------- + fmt : str + Format of the export, will only infer the format from filename if fmt + is auto. + fname : str + Name of the target export file, only used when fmt is auto. + supported_formats : dict of str : tuple/list + Dictionary containing supported formats (as keys) and each format's + corresponding file extensions in a tuple/list (e.g. 'eeglab': ('set',)) + """ + _validate_type(fmt, str, 'fmt') + fmt = fmt.lower() + if fmt == "auto": + fmt = op.splitext(fname)[1] + if fmt: + fmt = fmt[1:].lower() + # find fmt in supported formats dict's tuples + fmt = next((k for k, v in supported_formats.items() if fmt in v), + fmt) # default to original fmt for raising error later + else: + raise ValueError(f"Couldn't infer format from filename {fname}" + " (no extension found)") + + if fmt not in supported_formats: + supported = [] + for format, extensions in supported_formats.items(): + ext_str = ', '.join(f'*.{ext}' for ext in extensions) + supported.append(f'{format} ({ext_str})') + + supported_str = ', '.join(supported) + raise ValueError(f"Format '{fmt}' is not supported. " + f"Supported formats are {supported_str}.") + return fmt diff --git a/mne/export/tests/test_export.py b/mne/export/tests/test_export.py new file mode 100644 index 00000000000..ef7ce429384 --- /dev/null +++ b/mne/export/tests/test_export.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +"""Test exporting functions.""" +# Authors: MNE Developers +# +# License: BSD (3-clause) + +from pathlib import Path +import os.path as op + +import pytest +import numpy as np +from numpy.testing import assert_allclose, assert_array_equal + +from mne import read_epochs_eeglab, Epochs +from mne.tests.test_epochs import _get_data +from mne.io import read_raw_fif, read_raw_eeglab +from mne.utils import _check_eeglabio_installed + + +@pytest.mark.skipif(not _check_eeglabio_installed(strict=False), + reason='eeglabio not installed') +def test_export_raw_eeglab(tmpdir): + """Test saving a Raw instance to EEGLAB's set format.""" + fname = (Path(__file__).parent.parent.parent / + "io" / "tests" / "data" / "test_raw.fif") + raw = read_raw_fif(fname) + raw.load_data() + temp_fname = op.join(str(tmpdir), 'test.set') + raw.export(temp_fname) + raw.drop_channels([ch for ch in ['epoc'] + if ch in raw.ch_names]) + raw_read = read_raw_eeglab(temp_fname, preload=True) + assert raw.ch_names == raw_read.ch_names + cart_coords = np.array([d['loc'][:3] for d in raw.info['chs']]) # just xyz + cart_coords_read = np.array([d['loc'][:3] for d in raw_read.info['chs']]) + assert_allclose(cart_coords, cart_coords_read) + assert_allclose(raw.times, raw_read.times) + assert_allclose(raw.get_data(), raw_read.get_data()) + + +@pytest.mark.skipif(not _check_eeglabio_installed(strict=False), + reason='eeglabio not installed') +@pytest.mark.parametrize('preload', (True, False)) +def test_export_epochs_eeglab(tmpdir, preload): + """Test saving an Epochs instance to EEGLAB's set format.""" + raw, events = _get_data()[:2] + raw.load_data() + epochs = Epochs(raw, events, preload=preload) + temp_fname = op.join(str(tmpdir), 'test.set') + epochs.export(temp_fname) + epochs.drop_channels([ch for ch in ['epoc', 'STI 014'] + if ch in epochs.ch_names]) + epochs_read = read_epochs_eeglab(temp_fname) + assert epochs.ch_names == epochs_read.ch_names + cart_coords = np.array([d['loc'][:3] + for d in epochs.info['chs']]) # just xyz + cart_coords_read = np.array([d['loc'][:3] + for d in epochs_read.info['chs']]) + assert_allclose(cart_coords, cart_coords_read) + assert_array_equal(epochs.events[:, 0], + epochs_read.events[:, 0]) # latency + assert epochs.event_id.keys() == epochs_read.event_id.keys() # just keys + assert_allclose(epochs.times, epochs_read.times) + assert_allclose(epochs.get_data(), epochs_read.get_data()) diff --git a/mne/io/base.py b/mne/io/base.py index 397836440fd..61978daffc0 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -21,9 +21,7 @@ import numpy as np from .constants import FIFF -from .utils import _construct_bids_filename, _check_orig_units, \ - _get_als_coords_from_chs -from ..utils.check import _infer_check_export_fmt +from .utils import _construct_bids_filename, _check_orig_units from .pick import (pick_types, pick_channels, pick_info, _picks_to_idx, channel_type) from .meas_info import write_meas_info @@ -49,8 +47,7 @@ copy_function_doc_to_method_doc, _validate_type, _check_preload, _get_argvalues, _check_option, _build_data_frame, _convert_times, _scale_dataframe_data, - _check_time_format, _arange_div, - _check_eeglabio_installed) + _check_time_format, _arange_div) from ..defaults import _handle_default from ..viz import plot_raw, plot_raw_psd, plot_raw_psd_topo, _RAW_CLIP_DEF from ..event import find_events, concatenate_events @@ -1470,40 +1467,8 @@ def export(self, fname, fmt='auto', verbose=None): ----- %(export_eeglab_note)s """ - supported_export_formats = { # format : extensions - 'eeglab': ('set',), - 'edf': ('edf',), - 'brainvision': ('eeg', 'vmrk', 'vhdr',) - } - fmt = _infer_check_export_fmt(fmt, fname, supported_export_formats) - - if fmt == 'eeglab': - _check_eeglabio_installed() - import eeglabio.raw - # load data first - self.load_data() - - # remove extra epoc and STI channels - drop_chs = ['epoc'] - if not (self.filenames[0].endswith('.fif')): - drop_chs.append('STI 014') - - ch_names = [ch for ch in self.ch_names if ch not in drop_chs] - cart_coords = _get_als_coords_from_chs(self.info['chs'], - drop_chs) - - annotations = [self.annotations.description, - self.annotations.onset, - self.annotations.duration] - eeglabio.raw.export_set(fname, data=self.get_data(picks=ch_names), - sfreq=self.info['sfreq'], - ch_names=ch_names, - ch_locs=cart_coords, - annotations=annotations) - elif fmt == 'edf': - raise NotImplementedError('Export to EDF format not implemented.') - elif fmt == 'brainvision': - raise NotImplementedError('Export to BrainVision not implemented.') + from ..export import export_raw + export_raw(fname, self, fmt, verbose=verbose) def _tmin_tmax_to_start_stop(self, tmin, tmax): start = int(np.floor(tmin * self.info['sfreq'])) diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py index a220c6cda4e..10936088ac9 100644 --- a/mne/io/tests/test_raw.py +++ b/mne/io/tests/test_raw.py @@ -24,13 +24,11 @@ from mne.io import read_raw_fif, RawArray, BaseRaw, Info, _writing_info_hdf5 from mne.io.base import _get_scaling from mne.utils import (_TempDir, catch_logging, _raw_annot, _stamp_to_dt, - object_diff, check_version, requires_pandas, - _check_eeglabio_installed) + object_diff, check_version, requires_pandas) from mne.io.meas_info import _get_valid_units from mne.io._digitization import DigPoint from mne.io.proj import Projection from mne.io.utils import _mult_cal_one -from mne.io import read_raw_eeglab def assert_named_constants(info): @@ -694,23 +692,3 @@ def test_get_data_units(): # not the good type with pytest.raises(TypeError, match='instance of None, str, or dict'): raw.get_data(units=['fT/cm', 'fT', 'uV']) - - -@pytest.mark.skipif(not _check_eeglabio_installed(strict=False), - reason='eeglabio not installed') -def test_export_eeglab(tmpdir): - """Test saving a Raw instance to EEGLAB's set format.""" - fname = Path(__file__).parent / "data" / "test_raw.fif" - raw = read_raw_fif(fname) - raw.load_data() - temp_fname = op.join(str(tmpdir), 'test.set') - raw.export(temp_fname) - raw.drop_channels([ch for ch in ['epoc'] - if ch in raw.ch_names]) - raw_read = read_raw_eeglab(temp_fname, preload=True) - assert raw.ch_names == raw_read.ch_names - cart_coords = np.array([d['loc'][:3] for d in raw.info['chs']]) # just xyz - cart_coords_read = np.array([d['loc'][:3] for d in raw_read.info['chs']]) - assert_allclose(cart_coords, cart_coords_read) - assert_allclose(raw.times, raw_read.times) - assert_allclose(raw.get_data(), raw_read.get_data()) diff --git a/mne/io/utils.py b/mne/io/utils.py index f0c700880f7..7c9ab92240b 100644 --- a/mne/io/utils.py +++ b/mne/io/utils.py @@ -312,24 +312,3 @@ def _construct_bids_filename(base, ext, part_idx): if dirname: use_fname = op.join(dirname, use_fname) return use_fname - - -def _get_als_coords_from_chs(chs, drop_chs=None): - """Extract channel locations in ALS format (x, y, z) from a chs instance. - - Returns - ------- - None if no valid coordinates are found (all zeros) - """ - if drop_chs is None: - drop_chs = [] - cart_coords = np.array([d['loc'][:3] for d in chs - if d['ch_name'] not in drop_chs]) - if cart_coords.any(): # has coordinates - # (-y x z) to (x y z) - cart_coords[:, 0] = -cart_coords[:, 0] # -y to y - # swap x (1) and y (0) - cart_coords[:, [0, 1]] = cart_coords[:, [1, 0]] - else: - cart_coords = None - return cart_coords diff --git a/mne/tests/test_docstring_parameters.py b/mne/tests/test_docstring_parameters.py index 8bcb6d6f8d4..2d4ba5d9394 100644 --- a/mne/tests/test_docstring_parameters.py +++ b/mne/tests/test_docstring_parameters.py @@ -27,6 +27,7 @@ 'mne.datasets.sample', 'mne.decoding', 'mne.dipole', + 'mne.export', 'mne.filter', 'mne.forward', 'mne.inverse_sparse', @@ -283,7 +284,7 @@ def test_documented(): 'decoding', 'events', 'file_io', 'forward', 'inverse', 'logging', 'most_used_classes', 'mri', 'preprocessing', 'reading_raw_data', 'realtime', 'report', 'sensor_space', 'simulation', 'source_space', - 'statistics', 'time_frequency', 'visualization') + 'statistics', 'time_frequency', 'visualization', 'export') known_names = list() for api_file in api_files: with open(op.join(doc_dir, f'{api_file}.rst'), 'rb') as fid: diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py index b92de270c4c..e565251101a 100644 --- a/mne/tests/test_epochs.py +++ b/mne/tests/test_epochs.py @@ -29,7 +29,7 @@ from mne.datasets import testing from mne.chpi import read_head_pos, head_pos_to_trans_rot_t from mne.event import merge_events -from mne.io import RawArray, read_raw_fif, read_epochs_eeglab +from mne.io import RawArray, read_raw_fif from mne.io.constants import FIFF from mne.io.proj import _has_eeg_average_ref_proj from mne.io.write import write_int, INT32_MAX, _get_split_size @@ -40,8 +40,7 @@ _handle_event_repeated, make_metadata) from mne.utils import (requires_pandas, object_diff, catch_logging, _FakeNoPandas, - assert_meg_snr, check_version, _dt_to_stamp, - _check_eeglabio_installed) + assert_meg_snr, check_version, _dt_to_stamp) data_path = testing.data_path(download=False) fname_raw_testing = op.join(data_path, 'MEG', 'sample', @@ -3106,32 +3105,6 @@ def test_save_complex_data(tmpdir, preload, is_complex, fmt, rtol): assert_allclose(data_read, data, rtol=rtol) -@pytest.mark.skipif(not _check_eeglabio_installed(strict=False), - reason='eeglabio not installed') -@pytest.mark.parametrize('preload', (True, False)) -def test_export_eeglab(tmpdir, preload): - """Test saving an Epochs instance to EEGLAB's set format.""" - raw, events = _get_data()[:2] - raw.load_data() - epochs = Epochs(raw, events, preload=preload) - temp_fname = op.join(str(tmpdir), 'test.set') - epochs.export(temp_fname) - epochs.drop_channels([ch for ch in ['epoc', 'STI 014'] - if ch in epochs.ch_names]) - epochs_read = read_epochs_eeglab(temp_fname) - assert epochs.ch_names == epochs_read.ch_names - cart_coords = np.array([d['loc'][:3] - for d in epochs.info['chs']]) # just xyz - cart_coords_read = np.array([d['loc'][:3] - for d in epochs_read.info['chs']]) - assert_allclose(cart_coords, cart_coords_read) - assert_array_equal(epochs.events[:, 0], - epochs_read.events[:, 0]) # latency - assert epochs.event_id.keys() == epochs_read.event_id.keys() # just keys - assert_allclose(epochs.times, epochs_read.times) - assert_allclose(epochs.get_data(), epochs_read.get_data()) - - def test_no_epochs(tmpdir): """Test that having the first epoch bad does not break writing.""" # a regression noticed in #5564 diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 628cdee714f..206b8db22ec 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -19,7 +19,7 @@ _check_freesurfer_home, _suggest, _require_version, _on_missing, _check_on_missing, int_like, _safe_input, _check_all_same_channel_names, path_like, _ensure_events, - _check_eeglabio_installed, _infer_check_export_fmt) + _check_eeglabio_installed) from .config import (set_config, get_config, get_config_path, set_cache_dir, set_memmap_min_size, get_subjects_dir, _get_stim_channel, sys_info, _get_extra_data_path, _get_root_dir, diff --git a/mne/utils/check.py b/mne/utils/check.py index d472c24819c..c66b7d13690 100644 --- a/mne/utils/check.py +++ b/mne/utils/check.py @@ -774,46 +774,3 @@ def _ensure_events(events): raise ValueError( f'events must be of shape (N, 3), got {events.shape}') return events - - -def _infer_check_export_fmt(fmt, fname, supported_formats): - """Infer export format from filename extension if auto. - - Raises error if fmt is auto and no file extension found, - then checks format against supported formats, raises error if format is not - supported. - - Parameters - ---------- - fmt : str - Format of the export, will only infer the format from filename if fmt - is auto. - fname : str - Name of the target export file, only used when fmt is auto. - supported_formats : dict of str : tuple/list - Dictionary containing supported formats (as keys) and each format's - corresponding file extensions in a tuple/list (e.g. 'eeglab': ('set',)) - """ - _validate_type(fmt, str, 'fmt') - fmt = fmt.lower() - if fmt == "auto": - fmt = op.splitext(fname)[1] - if fmt: - fmt = fmt[1:].lower() - # find fmt in supported formats dict's tuples - fmt = next((k for k, v in supported_formats.items() if fmt in v), - fmt) # default to original fmt for raising error later - else: - raise ValueError(f"Couldn't infer format from filename {fname}" - " (no extension found)") - - if fmt not in supported_formats: - supported = [] - for format, extensions in supported_formats.items(): - ext_str = ', '.join(f'*.{ext}' for ext in extensions) - supported.append(f'{format} ({ext_str})') - - supported_str = ', '.join(supported) - raise ValueError(f"Format '{fmt}' is not supported. " - f"Supported formats are {supported_str}.") - return fmt diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 75990ab6e48..976c51548b5 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -2326,7 +2326,7 @@ .. warning:: Since we are exporting to external formats, there's no guarantee that all the info will be preserved in the external format. To save in native MNE - format (``.fif``) without information loss, use :func:`save` instead. + format (``.fif``) without information loss, use ``save`` instead. """ docdict['export_params_fname'] = """ fname : str