From 6cb7bea4d58a8dc529df5548207a1290b87a47f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Mon, 26 Jul 2021 18:26:14 +0200 Subject: [PATCH 01/32] WIP: Expand ~ in _check_fname() Currently just a proof of concept for FIFF reading. --- doc/changes/latest.inc | 2 ++ mne/io/base.py | 3 +-- mne/io/fiff/tests/test_raw_fiff.py | 14 ++++++++++++++ mne/utils/check.py | 2 ++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 3533df52314..09b3af4e481 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -119,6 +119,8 @@ Enhancements - :meth:`mne.preprocessing.ICA.fit` now emits a warning if any of the ``start``, ``stop``, ``reject``, and ``flat`` parameters are passed when performing ICA on `~mne.Epochs`. These parameters only have an effect on `~mne.io.Raw` data and were previously silently ignored in the case of `~mne.Epochs` (:gh:`9605` by `Richard Höchenberger`_) +- All functions for reading and writing files should now automatically handle ``~`` correctly on Linux and macOS machines and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) + Bugs ~~~~ - Fix bug with :meth:`mne.Epochs.crop` and :meth:`mne.Evoked.crop` when ``include_tmax=False``, where the last sample was always cut off, even when ``tmax > epo.times[-1]`` (:gh:`9378` **by new contributor** |Jan Sosulski|_) diff --git a/mne/io/base.py b/mne/io/base.py index b97838d1a1b..901ed589ded 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -1416,7 +1416,6 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None, or all forms of SSS). It is recommended not to concatenate and then save raw files for this reason. """ - fname = op.abspath(fname) endings = ('raw.fif', 'raw_sss.fif', 'raw_tsss.fif', '_meg.fif', '_eeg.fif', '_ieeg.fif') endings += tuple([f'{e}.gz' for e in endings]) @@ -1448,7 +1447,7 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None, '"double", not "short"') # check for file existence - _check_fname(fname, overwrite) + fname = _check_fname(fname, overwrite) if proj: info = deepcopy(self.info) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index 98199016bf9..1b568f19b78 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -1786,3 +1786,17 @@ def test_corrupted(tmpdir): with pytest.warns(RuntimeWarning, match='.*tag directory.*corrupt.*'): raw_bad = read_raw_fif(bad_fname) assert_allclose(raw.get_data(), raw_bad.get_data()) + + +@testing.requires_testing_data +def test_expand_user(): + dir_sample = pathlib.Path('~/mne_data/MNE-testing-data/MEG/sample/') + fname_in = dir_sample / 'sample_audvis_trunc_raw.fif' + dir_out = dir_sample / 'tmp' + dir_out.expanduser().mkdir(exist_ok=True) + fname_out = dir_out / 'raw.fif' + + raw = read_raw_fif(fname=fname_in, preload=True) + raw.save(fname=fname_out, overwrite=True) + + shutil.rmtree(dir_out.expanduser()) diff --git a/mne/utils/check.py b/mne/utils/check.py index b0d131a04dd..d7bb32e2484 100644 --- a/mne/utils/check.py +++ b/mne/utils/check.py @@ -155,6 +155,8 @@ def _check_fname(fname, overwrite=False, must_exist=False, name='File', need_dir=False): """Check for file existence, and return string of its absolute path.""" _validate_type(fname, 'path-like', name) + fname = op.expanduser(fname) + if op.exists(fname): if not overwrite: raise FileExistsError('Destination file exists. Please use option ' From 5006a4a9abe00bcceb80d3d59425a83716ed8633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 18:28:14 +0200 Subject: [PATCH 02/32] docstyle --- mne/io/fiff/tests/test_raw_fiff.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index 1b568f19b78..de39a14d1f9 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -1790,6 +1790,7 @@ def test_corrupted(tmpdir): @testing.requires_testing_data def test_expand_user(): + """Test that we're expanding `~` before reading and writing.""" dir_sample = pathlib.Path('~/mne_data/MNE-testing-data/MEG/sample/') fname_in = dir_sample / 'sample_audvis_trunc_raw.fif' dir_out = dir_sample / 'tmp' From 996f16b992083a4dd6c19344caa01907148ccf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 19:00:41 +0200 Subject: [PATCH 03/32] Add more data types --- mne/cov.py | 5 +++-- mne/epochs.py | 28 +++++++++++++--------------- mne/evoked.py | 13 ++++++++----- mne/forward/forward.py | 10 +++++----- mne/io/eeglab/eeglab.py | 2 +- mne/io/kit/kit.py | 4 ++-- mne/minimum_norm/inverse.py | 6 ++++-- mne/source_estimate.py | 12 ++++++------ mne/transforms.py | 1 + mne/utils/docs.py | 6 ++++++ 10 files changed, 49 insertions(+), 38 deletions(-) diff --git a/mne/cov.py b/mne/cov.py index 9adfaf95736..024243a80fc 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -38,7 +38,7 @@ warn, copy_function_doc_to_method_doc, _pl, _undo_scaling_cov, _scaled_array, _validate_type, _check_option, eigh, fill_doc, _on_missing, - _check_on_missing) + _check_on_missing, _check_fname) from . import viz from .fixes import (BaseEstimator, EmpiricalCovariance, _logdet, @@ -153,7 +153,7 @@ def save(self, fname): """ check_fname(fname, 'covariance', ('-cov.fif', '-cov.fif.gz', '_cov.fif', '_cov.fif.gz')) - + fname = _check_fname(fname=fname) fid = start_file(fname) try: @@ -384,6 +384,7 @@ def read_cov(fname, verbose=None): """ check_fname(fname, 'covariance', ('-cov.fif', '-cov.fif.gz', '_cov.fif', '_cov.fif.gz')) + fname = _check_fname(fname=fname, must_exist=True) f, tree = fiff_open(fname)[:2] with f as fid: return Covariance(**_read_cov(fid, tree, FIFF.FIFFV_MNE_NOISE_COV, diff --git a/mne/epochs.py b/mne/epochs.py index 4bfa793d6cf..4215dd88ea4 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1738,8 +1738,8 @@ def save(self, fname, split_size='2GB', fmt='single', overwrite=False, Parameters ---------- fname : str - The name of the file, which should end with -epo.fif or - -epo.fif.gz. + The name of the file, which should end with ``-epo.fif`` or + ``-epo.fif.gz``. split_size : str | int Large raw files are automatically split into multiple pieces. This parameter specifies the maximum size of each piece. If the @@ -1771,8 +1771,8 @@ def save(self, fname, split_size='2GB', fmt='single', overwrite=False, check_fname(fname, 'epochs', ('-epo.fif', '-epo.fif.gz', '_epo.fif', '_epo.fif.gz')) - # check for file existence - _check_fname(fname, overwrite) + # check for file existence and expand `~` if present + fname = _check_fname(fname=fname, overwrite=overwrite) split_size_bytes = _get_split_size(split_size) @@ -3058,14 +3058,11 @@ def read_epochs(fname, proj=True, preload=True, verbose=None): Parameters ---------- - fname : str | file-like - The epochs filename to load. Filename should end with -epo.fif or - -epo.fif.gz. If a file-like object is provided, preloading must be - used. + %(epochs_fname)s %(proj_epochs)s preload : bool - If True, read all epochs from disk immediately. If False, epochs will - be read on demand. + If True, read all epochs from disk immediately. If ``False``, epochs + will be read on demand. %(verbose)s Returns @@ -3099,9 +3096,7 @@ class EpochsFIF(BaseEpochs): Parameters ---------- - fname : str | file-like - The name of the file, which should end with -epo.fif or -epo.fif.gz. If - a file-like object is provided, preloading must be used. + %(epochs_fname)s %(proj_epochs)s preload : bool If True, read all epochs from disk immediately. If False, epochs will @@ -3119,8 +3114,11 @@ class EpochsFIF(BaseEpochs): def __init__(self, fname, proj=True, preload=True, verbose=None): # noqa: D102 if isinstance(fname, str): - check_fname(fname, 'epochs', ('-epo.fif', '-epo.fif.gz', - '_epo.fif', '_epo.fif.gz')) + check_fname( + fname=fname, filetype='epochs', + endings=('-epo.fif', '-epo.fif.gz', '_epo.fif', '_epo.fif.gz') + ) + fname = _check_fname(fname=fname, must_exist=True) elif not preload: raise ValueError('preload must be used with file-like objects') diff --git a/mne/evoked.py b/mne/evoked.py index 580f440d939..4ec07ad37b2 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -22,7 +22,7 @@ fill_doc, _check_option, ShiftTimeMixin, _build_data_frame, _check_pandas_installed, _check_pandas_index_arguments, _convert_times, _scale_dataframe_data, _check_time_format, - _check_preload) + _check_preload, _check_fname) from .viz import (plot_evoked, plot_evoked_topomap, plot_evoked_field, plot_evoked_image, plot_evoked_topo) from .viz.evoked import plot_evoked_white, plot_evoked_joint @@ -127,6 +127,7 @@ def __init__(self, fname, condition=None, proj=True, verbose=None): # noqa: D102 _validate_type(proj, bool, "'proj'") # Read the requested data + fname = _check_fname(fname=fname, must_exist=must_exist) self.info, self.nave, self._aspect_kind, self.comment, self.times, \ self.data, self.baseline = _read_evoked(fname, condition, kind, allow_maxshield) @@ -277,7 +278,10 @@ def apply_baseline(self, baseline=(None, 0), *, verbose=None): return self def save(self, fname): - """Save dataset to file. + """Save evoked data to a file. + + .. note:: To write multiple conditions into a single file, use + `mne.write_evokeds` instead. Parameters ---------- @@ -287,13 +291,11 @@ def save(self, fname): Notes ----- - To write multiple conditions into a single file, use - `mne.write_evokeds`. - .. versionchanged:: 0.23 Information on baseline correction will be stored with the data, and will be restored when reading again via `mne.read_evokeds`. """ + fname = _check_fname(fname=fname) write_evokeds(fname, self) def __repr__(self): # noqa: D105 @@ -1397,6 +1399,7 @@ def _write_evokeds(fname, evoked, check=True, *, on_mismatch='raise'): if check: check_fname(fname, 'evoked', ('-ave.fif', '-ave.fif.gz', '_ave.fif', '_ave.fif.gz')) + fname = _check_fname(fname=fname) if not isinstance(evoked, list): evoked = [evoked] diff --git a/mne/forward/forward.py b/mne/forward/forward.py index baa41410207..3d33e5bd85c 100644 --- a/mne/forward/forward.py +++ b/mne/forward/forward.py @@ -421,7 +421,7 @@ def read_forward_solution(fname, include=(), exclude=(), verbose=None): """ check_fname(fname, 'forward', ('-fwd.fif', '-fwd.fif.gz', '_fwd.fif', '_fwd.fif.gz')) - + fname = _check_fname(fname=fname, must_exist=True) # Open the file, create directory logger.info('Reading forward solution from %s...' % fname) f, tree, _ = fiff_open(fname) @@ -702,8 +702,8 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None): Parameters ---------- fname : str - File name to save the forward solution to. It should end with -fwd.fif - or -fwd.fif.gz. + File name to save the forward solution to. It should end with + ``-fwd.fif`` or ``-fwd.fif.gz``. fwd : Forward Forward solution. %(overwrite)s @@ -731,8 +731,8 @@ def write_forward_solution(fname, fwd, overwrite=False, verbose=None): check_fname(fname, 'forward', ('-fwd.fif', '-fwd.fif.gz', '_fwd.fif', '_fwd.fif.gz')) - # check for file existence - _check_fname(fname, overwrite) + # check for file existence and expand `~` if present + fname = _check_fname(fname, overwrite) fid = start_file(fname) start_block(fid, FIFF.FIFFB_MNE) diff --git a/mne/io/eeglab/eeglab.py b/mne/io/eeglab/eeglab.py index ea86f7a009f..c90abc0e412 100644 --- a/mne/io/eeglab/eeglab.py +++ b/mne/io/eeglab/eeglab.py @@ -446,6 +446,7 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0, baseline=None, reject=None, flat=None, reject_tmin=None, reject_tmax=None, eog=(), verbose=None, uint16_codec=None): # noqa: D102 + input_fname = _check_fname(fname=input_fname, must_exist=True) eeg = _check_load_mat(input_fname, uint16_codec) if not ((events is None and event_id is None) or @@ -507,7 +508,6 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0, events = read_events(events) logger.info('Extracting parameters from %s...' % input_fname) - input_fname = op.abspath(input_fname) info, eeg_montage, _ = _get_info(eeg, eog=eog) for key, val in event_id.items(): diff --git a/mne/io/kit/kit.py b/mne/io/kit/kit.py index 2ada860f8c7..f8b045de7b5 100644 --- a/mne/io/kit/kit.py +++ b/mne/io/kit/kit.py @@ -17,7 +17,7 @@ from ..pick import pick_types from ...utils import (verbose, logger, warn, fill_doc, _check_option, - _stamp_to_dt) + _stamp_to_dt, _check_fname) from ...transforms import apply_trans, als_ras_trans from ..base import BaseRaw from ..utils import _mult_cal_one @@ -386,8 +386,8 @@ def __init__(self, input_fname, events, event_id=None, tmin=0, if isinstance(events, str): events = read_events(events) + input_fname = _check_fname(fname=input_fname, must_exist=True) logger.info('Extracting KIT Parameters from %s...' % input_fname) - input_fname = op.abspath(input_fname) self.info, kit_info = get_kit_info( input_fname, allow_unknown_format, standardize_names) kit_info.update(filename=input_fname) diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index 97dea064e72..75ff0fa37d2 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -41,7 +41,7 @@ from ..source_estimate import _make_stc, _get_src_type from ..utils import (check_fname, logger, verbose, warn, _validate_type, _check_compensation_grade, _check_option, - _check_depth, _check_src_normal) + _check_depth, _check_src_normal, _check_fname) INVERSE_METHODS = ('MNE', 'dSPM', 'sLORETA', 'eLORETA') @@ -113,7 +113,7 @@ def read_inverse_operator(fname, verbose=None): """ check_fname(fname, 'inverse operator', ('-inv.fif', '-inv.fif.gz', '_inv.fif', '_inv.fif.gz')) - + fname = _check_fname(fname=fname, must_exist=True) # # Open the file, create directory # @@ -331,6 +331,8 @@ def write_inverse_operator(fname, inv, verbose=None): """ check_fname(fname, 'inverse operator', ('-inv.fif', '-inv.fif.gz', '_inv.fif', '_inv.fif.gz')) + fname = _check_fname(fname=fname) + _validate_type(inv, InverseOperator, 'inv') # diff --git a/mne/source_estimate.py b/mne/source_estimate.py index e0db73b1e3f..8f804881a04 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -32,7 +32,7 @@ _check_stc_units, _check_pandas_installed, _check_pandas_index_arguments, _convert_times, _ensure_int, _build_data_frame, _check_time_format, _check_path_like, - sizeof_fmt, object_size) + sizeof_fmt, object_size, _check_fname) from .viz import (plot_source_estimates, plot_vector_source_estimates, plot_volume_source_estimates) from .io.base import TimeMixin @@ -250,7 +250,7 @@ def read_source_estimate(fname, subject=None): """ # noqa: E501 fname_arg = fname _validate_type(fname, 'path-like', 'fname') - fname = str(fname) + fname = _check_fname(fname=fname, must_exist=True) # make sure corresponding file(s) can be found ftype = None @@ -622,7 +622,7 @@ def save(self, fname, ftype='h5', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = str(fname) + fname = _check_fname(fname=fname) if ftype != 'h5': raise ValueError('%s objects can only be written as HDF5 files.' % (self.__class__.__name__,)) @@ -1595,7 +1595,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = str(fname) + fname = _check_fname(fname=fname) _check_option('ftype', ftype, ['stc', 'w', 'h5']) lh_data = self.data[:len(self.lh_vertno)] @@ -2084,7 +2084,7 @@ def save_as_volume(self, fname, src, dest='mri', mri_resolution=False, """ import nibabel as nib _validate_type(fname, 'path-like', 'fname') - fname = str(fname) + fname = _check_fname(fname=fname) img = self.as_volume(src, dest=dest, mri_resolution=mri_resolution, format=format) nib.save(img, fname) @@ -2187,7 +2187,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = str(fname) + fname = _check_fname(fname=fname) _check_option('ftype', ftype, ['stc', 'w', 'h5']) if ftype != 'h5' and len(self.vertices) != 1: raise ValueError('Can only write to .stc or .w if a single volume ' diff --git a/mne/transforms.py b/mne/transforms.py index 81e0c68661b..129357d175d 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -571,6 +571,7 @@ def write_trans(fname, trans): """ check_fname(fname, 'trans', ('-trans.fif', '-trans.fif.gz', '_trans.fif', '_trans.fif.gz')) + fname = _check_fname(fname=fname) fid = start_file(fname) write_coord_trans(fid, trans) end_file(fid) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index e3dee986cab..168bea39953 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -267,6 +267,12 @@ If proj is False no projections will be applied which is the recommended value if SSPs are not used for cleaning the data. """ +docdict['epochs_fname'] = """ +fname : str | file-like + + The epochs to load. If a filename, should end with ``-epo.fif`` or + ``-epo.fif.gz``. If a file-like object, preloading must be used. +""" # Reject by annotation docdict['reject_by_annotation_all'] = """ From 2c0d14c89df08d1c4d92857ca1ed7209adf4c737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 19:03:59 +0200 Subject: [PATCH 04/32] Fix typo --- mne/evoked.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/evoked.py b/mne/evoked.py index 4ec07ad37b2..3e5f0b48e84 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -127,7 +127,7 @@ def __init__(self, fname, condition=None, proj=True, verbose=None): # noqa: D102 _validate_type(proj, bool, "'proj'") # Read the requested data - fname = _check_fname(fname=fname, must_exist=must_exist) + fname = _check_fname(fname=fname, must_exist=True) self.info, self.nave, self._aspect_kind, self.comment, self.times, \ self.data, self.baseline = _read_evoked(fname, condition, kind, allow_maxshield) From b4d589fe6ca4adc446d7e763a8a58cfdf06bc34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 19:09:07 +0200 Subject: [PATCH 05/32] Add another one --- mne/io/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mne/io/base.py b/mne/io/base.py index 901ed589ded..f5218a975c3 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -1446,7 +1446,7 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None, raise ValueError('Complex data must be saved as "single" or ' '"double", not "short"') - # check for file existence + # check for file existence and expand `~` if present fname = _check_fname(fname, overwrite) if proj: @@ -2142,6 +2142,9 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start, raise RuntimeError('Cannot write raw file with no data: %s -> %s ' '(max: %s) requested' % (start, stop, n_times_max)) + # Expand `~` if present + fname = _check_fname(fname=fname) + base, ext = op.splitext(fname) if part_idx > 0: if split_naming == 'neuromag': From 98f495d47e57bd369abe4ade2c23c5cb0c478112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 19:53:26 +0200 Subject: [PATCH 06/32] Fixes --- mne/cov.py | 2 +- mne/evoked.py | 4 ++-- mne/io/base.py | 2 +- mne/minimum_norm/inverse.py | 2 +- mne/source_estimate.py | 8 ++++---- mne/transforms.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mne/cov.py b/mne/cov.py index 024243a80fc..9ee913d3546 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -153,7 +153,7 @@ def save(self, fname): """ check_fname(fname, 'covariance', ('-cov.fif', '-cov.fif.gz', '_cov.fif', '_cov.fif.gz')) - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) fid = start_file(fname) try: diff --git a/mne/evoked.py b/mne/evoked.py index 3e5f0b48e84..aa7af26f3a1 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -295,7 +295,7 @@ def save(self, fname): Information on baseline correction will be stored with the data, and will be restored when reading again via `mne.read_evokeds`. """ - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) write_evokeds(fname, self) def __repr__(self): # noqa: D105 @@ -1399,7 +1399,7 @@ def _write_evokeds(fname, evoked, check=True, *, on_mismatch='raise'): if check: check_fname(fname, 'evoked', ('-ave.fif', '-ave.fif.gz', '_ave.fif', '_ave.fif.gz')) - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) if not isinstance(evoked, list): evoked = [evoked] diff --git a/mne/io/base.py b/mne/io/base.py index f5218a975c3..4183c642673 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -2143,7 +2143,7 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start, '(max: %s) requested' % (start, stop, n_times_max)) # Expand `~` if present - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=overwrite) base, ext = op.splitext(fname) if part_idx > 0: diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index 75ff0fa37d2..9c0796abacd 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -331,7 +331,7 @@ def write_inverse_operator(fname, inv, verbose=None): """ check_fname(fname, 'inverse operator', ('-inv.fif', '-inv.fif.gz', '_inv.fif', '_inv.fif.gz')) - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) _validate_type(inv, InverseOperator, 'inv') diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 8f804881a04..b6e00c5baf8 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -622,7 +622,7 @@ def save(self, fname, ftype='h5', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) if ftype != 'h5': raise ValueError('%s objects can only be written as HDF5 files.' % (self.__class__.__name__,)) @@ -1595,7 +1595,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) _check_option('ftype', ftype, ['stc', 'w', 'h5']) lh_data = self.data[:len(self.lh_vertno)] @@ -2084,7 +2084,7 @@ def save_as_volume(self, fname, src, dest='mri', mri_resolution=False, """ import nibabel as nib _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) img = self.as_volume(src, dest=dest, mri_resolution=mri_resolution, format=format) nib.save(img, fname) @@ -2187,7 +2187,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) _check_option('ftype', ftype, ['stc', 'w', 'h5']) if ftype != 'h5' and len(self.vertices) != 1: raise ValueError('Can only write to .stc or .w if a single volume ' diff --git a/mne/transforms.py b/mne/transforms.py index 129357d175d..072f12ed911 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -571,7 +571,7 @@ def write_trans(fname, trans): """ check_fname(fname, 'trans', ('-trans.fif', '-trans.fif.gz', '_trans.fif', '_trans.fif.gz')) - fname = _check_fname(fname=fname) + fname = _check_fname(fname=fname, overwrite=True) fid = start_file(fname) write_coord_trans(fid, trans) end_file(fid) From 540cc576bb76066c9d4f2daabf73e9be85a09749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 21:54:16 +0200 Subject: [PATCH 07/32] =?UTF-8?q?Fix=20fix=20fix=20=F0=9F=90=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mne/cov.py | 2 +- mne/epochs.py | 3 ++- mne/evoked.py | 2 +- mne/forward/forward.py | 2 +- mne/minimum_norm/inverse.py | 2 +- mne/source_estimate.py | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mne/cov.py b/mne/cov.py index 9ee913d3546..b45ce070432 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -384,7 +384,7 @@ def read_cov(fname, verbose=None): """ check_fname(fname, 'covariance', ('-cov.fif', '-cov.fif.gz', '_cov.fif', '_cov.fif.gz')) - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, overwrite='read') f, tree = fiff_open(fname)[:2] with f as fid: return Covariance(**_read_cov(fid, tree, FIFF.FIFFV_MNE_NOISE_COV, diff --git a/mne/epochs.py b/mne/epochs.py index 4215dd88ea4..829ef6a1a16 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -3118,7 +3118,8 @@ def __init__(self, fname, proj=True, preload=True, fname=fname, filetype='epochs', endings=('-epo.fif', '-epo.fif.gz', '_epo.fif', '_epo.fif.gz') ) - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, + overwrite='read') elif not preload: raise ValueError('preload must be used with file-like objects') diff --git a/mne/evoked.py b/mne/evoked.py index aa7af26f3a1..6377b58bf36 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -127,7 +127,7 @@ def __init__(self, fname, condition=None, proj=True, verbose=None): # noqa: D102 _validate_type(proj, bool, "'proj'") # Read the requested data - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, overwrite='read') self.info, self.nave, self._aspect_kind, self.comment, self.times, \ self.data, self.baseline = _read_evoked(fname, condition, kind, allow_maxshield) diff --git a/mne/forward/forward.py b/mne/forward/forward.py index 3d33e5bd85c..1a0a1249caf 100644 --- a/mne/forward/forward.py +++ b/mne/forward/forward.py @@ -421,7 +421,7 @@ def read_forward_solution(fname, include=(), exclude=(), verbose=None): """ check_fname(fname, 'forward', ('-fwd.fif', '-fwd.fif.gz', '_fwd.fif', '_fwd.fif.gz')) - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, overwrite='read') # Open the file, create directory logger.info('Reading forward solution from %s...' % fname) f, tree, _ = fiff_open(fname) diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index 9c0796abacd..c31578c0c6b 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -113,7 +113,7 @@ def read_inverse_operator(fname, verbose=None): """ check_fname(fname, 'inverse operator', ('-inv.fif', '-inv.fif.gz', '_inv.fif', '_inv.fif.gz')) - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, overwrite='read') # # Open the file, create directory # diff --git a/mne/source_estimate.py b/mne/source_estimate.py index b6e00c5baf8..841113fef7c 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -250,7 +250,7 @@ def read_source_estimate(fname, subject=None): """ # noqa: E501 fname_arg = fname _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname, must_exist=True) + fname = _check_fname(fname=fname, must_exist=True, overwrite='read') # make sure corresponding file(s) can be found ftype = None From 4567d42938cc9044e7ce0f49b5b39118b1d6b849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 22:41:58 +0200 Subject: [PATCH 08/32] Fix FIFF --- mne/io/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mne/io/base.py b/mne/io/base.py index 4183c642673..3addfed4c95 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -1447,7 +1447,7 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None, '"double", not "short"') # check for file existence and expand `~` if present - fname = _check_fname(fname, overwrite) + fname = _check_fname(fname=fname, overwrite=overwrite) if proj: info = deepcopy(self.info) @@ -2178,7 +2178,9 @@ def _write_raw(fname, raw, info, picks, fmt, data_type, reset_range, start, raw, info, picks, fid, cals, part_idx, start, stop, buffer_size, prev_fname, split_size, use_fname, projector, drop_small_buffer, fmt, fname, reserved_fname, - data_type, reset_range, split_naming, overwrite) + data_type, reset_range, split_naming, + overwrite=True # we've started writing already above + ) if final_fname != use_fname: assert split_naming == 'bids' logger.info(f'Renaming BIDS split file {op.basename(final_fname)}') From 97cbbe1eec95079a934975a48276f69e31fdbebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 22:44:20 +0200 Subject: [PATCH 09/32] Fix KIT --- mne/io/kit/kit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/io/kit/kit.py b/mne/io/kit/kit.py index c724aeca33f..d962cd5af21 100644 --- a/mne/io/kit/kit.py +++ b/mne/io/kit/kit.py @@ -386,7 +386,8 @@ def __init__(self, input_fname, events, event_id=None, tmin=0, if isinstance(events, str): events = read_events(events) - input_fname = _check_fname(fname=input_fname, must_exist=True) + input_fname = _check_fname(fname=input_fname, must_exist=True, + overwrite='read') logger.info('Extracting KIT Parameters from %s...' % input_fname) self.info, kit_info = get_kit_info( input_fname, allow_unknown_format, standardize_names) From 5669af2561bb9acb52462cd6a53f05ce6baced3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 22:53:36 +0200 Subject: [PATCH 10/32] Fix STC --- mne/source_estimate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 841113fef7c..edb383d1ace 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -250,7 +250,11 @@ def read_source_estimate(fname, subject=None): """ # noqa: E501 fname_arg = fname _validate_type(fname, 'path-like', 'fname') - fname = _check_fname(fname=fname, must_exist=True, overwrite='read') + + # Expand `~` if present + # We're not using _check_fname() here due to the particularities involved + # in STC file handling (-lh, -rh suffix) executed below + fname = op.expanduser(fname) # make sure corresponding file(s) can be found ftype = None From 80bd7fbd94ebab9feea28768203b7d1d64f425f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 28 Jul 2021 22:56:13 +0200 Subject: [PATCH 11/32] Fix test_plot_evoked_cov() --- mne/viz/tests/test_evoked.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index 2320b898986..896b127dce7 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -80,7 +80,7 @@ def test_plot_evoked_cov(): evoked.plot(noise_cov=cov, time_unit='s') with pytest.raises(TypeError, match='Covariance'): evoked.plot(noise_cov=1., time_unit='s') - with pytest.raises(IOError, match='No such file'): + with pytest.raises(FileNotFoundError, match='File does not exist'): evoked.plot(noise_cov='nonexistent-cov.fif', time_unit='s') raw = read_raw_fif(raw_sss_fname) events = make_fixed_length_events(raw) From 44b38635ba918d0e9a1cbea5a527f411993546bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 15:53:12 +0200 Subject: [PATCH 12/32] Fix EEGLAB --- mne/io/eeglab/eeglab.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/io/eeglab/eeglab.py b/mne/io/eeglab/eeglab.py index c90abc0e412..01fe901f38a 100644 --- a/mne/io/eeglab/eeglab.py +++ b/mne/io/eeglab/eeglab.py @@ -446,7 +446,8 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0, baseline=None, reject=None, flat=None, reject_tmin=None, reject_tmax=None, eog=(), verbose=None, uint16_codec=None): # noqa: D102 - input_fname = _check_fname(fname=input_fname, must_exist=True) + input_fname = _check_fname(fname=input_fname, must_exist=True, + overwrite='read') eeg = _check_load_mat(input_fname, uint16_codec) if not ((events is None and event_id is None) or From 70dd64002acab98445de68197709dfbf50ef2edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 16:02:29 +0200 Subject: [PATCH 13/32] Don't touch users' home directory during tests --- mne/io/fiff/tests/test_raw_fiff.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index de39a14d1f9..df289ac225e 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -5,6 +5,7 @@ # License: BSD-3-Clause from copy import deepcopy +from pathlib import Path from functools import partial from io import BytesIO import os @@ -1789,15 +1790,17 @@ def test_corrupted(tmpdir): @testing.requires_testing_data -def test_expand_user(): +def test_expand_user(tmp_path, monkeypatch): """Test that we're expanding `~` before reading and writing.""" - dir_sample = pathlib.Path('~/mne_data/MNE-testing-data/MEG/sample/') - fname_in = dir_sample / 'sample_audvis_trunc_raw.fif' - dir_out = dir_sample / 'tmp' - dir_out.expanduser().mkdir(exist_ok=True) - fname_out = dir_out / 'raw.fif' + monkeypatch.setenv('HOME', str(tmp_path)) - raw = read_raw_fif(fname=fname_in, preload=True) - raw.save(fname=fname_out, overwrite=True) + path_in = Path(fif_fname) + path_out = tmp_path / path_in.name - shutil.rmtree(dir_out.expanduser()) + shutil.copyfile( + src=path_in, + dst=path_out + ) + + raw = read_raw_fif(fname=path_in, preload=True) + raw.save(fname=path_out, overwrite=True) From 1356a2d3a673f8a1b432e9a0a360f32c71ae9182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 16:03:13 +0200 Subject: [PATCH 14/32] Fix docstring --- mne/utils/docs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 964aa2ee82a..f30f2640a49 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -268,8 +268,7 @@ recommended value if SSPs are not used for cleaning the data. """ docdict['epochs_fname'] = """ -fname : str | file-like - +fname : path-like | file-like The epochs to load. If a filename, should end with ``-epo.fif`` or ``-epo.fif.gz``. If a file-like object, preloading must be used. """ From 1c495647f88efa7b390ef393221ca670b822d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 16:05:32 +0200 Subject: [PATCH 15/32] Fix type check --- mne/epochs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index 88d41e0c627..e29953d74b8 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -59,7 +59,8 @@ _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) + _on_missing, _validate_type, _ensure_events, + _check_path_like) from .utils.docs import fill_doc from .data.html_templates import epochs_template @@ -3114,7 +3115,7 @@ class EpochsFIF(BaseEpochs): @verbose def __init__(self, fname, proj=True, preload=True, verbose=None): # noqa: D102 - if isinstance(fname, str): + if _check_path_like(fname): check_fname( fname=fname, filetype='epochs', endings=('-epo.fif', '-epo.fif.gz', '_epo.fif', '_epo.fif.gz') From 52090273d8d9f20a305324b3db39fc40b63d6958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 16:06:57 +0200 Subject: [PATCH 16/32] Revert doc change --- mne/evoked.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/evoked.py b/mne/evoked.py index 6377b58bf36..469a8cc2b75 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -280,9 +280,6 @@ def apply_baseline(self, baseline=(None, 0), *, verbose=None): def save(self, fname): """Save evoked data to a file. - .. note:: To write multiple conditions into a single file, use - `mne.write_evokeds` instead. - Parameters ---------- fname : str @@ -291,6 +288,9 @@ def save(self, fname): Notes ----- + To write multiple conditions into a single file, use + `mne.write_evokeds`. + .. versionchanged:: 0.23 Information on baseline correction will be stored with the data, and will be restored when reading again via `mne.read_evokeds`. From d45fcd6b77e58d71000388a5dc2e8d26afcd0a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 20:20:42 +0200 Subject: [PATCH 17/32] Address reviewer comments --- mne/source_estimate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mne/source_estimate.py b/mne/source_estimate.py index edb383d1ace..0fd9d8cf071 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -251,10 +251,10 @@ def read_source_estimate(fname, subject=None): fname_arg = fname _validate_type(fname, 'path-like', 'fname') - # Expand `~` if present - # We're not using _check_fname() here due to the particularities involved - # in STC file handling (-lh, -rh suffix) executed below - fname = op.expanduser(fname) + # expand `~` without checking whether the file actually exists – we'll + # take care of that later, as it's complicated by the different suffixes + # STC files can have + fname = _check_fname(fname=fname, overwrite='read', must_exist=False) # make sure corresponding file(s) can be found ftype = None From 9a1d5da22b0910553d5eedd1c1e849e61f173704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 21:51:39 +0200 Subject: [PATCH 18/32] Expand `~` in get_subjects_dir() too --- mne/utils/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index 59811d89241..e2d1b0b7b03 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -17,7 +17,8 @@ import numpy as np -from .check import _validate_type, _check_pyqt5_version, _check_option +from .check import (_validate_type, _check_pyqt5_version, _check_option, + _check_fname) from .docs import fill_doc from ._logging import warn, logger @@ -367,6 +368,11 @@ def get_subjects_dir(subjects_dir=None, raise_error=False): subjects_dir = get_config('SUBJECTS_DIR', raise_error=raise_error) if subjects_dir is not None: subjects_dir = str(subjects_dir) + + subjects_dir = _check_fname( + fname=subjects_dir, overwrite='read', must_exist=True, need_dir=True, + name='subjects_dir' + ) return subjects_dir From e389a279f5c4eac41824eb606aae1063fb6255cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Tue, 5 Oct 2021 22:24:46 +0200 Subject: [PATCH 19/32] Fix for subjects_dir=None --- mne/utils/config.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index e2d1b0b7b03..6a21b0f33a6 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -367,12 +367,11 @@ def get_subjects_dir(subjects_dir=None, raise_error=False): if subjects_dir is None: subjects_dir = get_config('SUBJECTS_DIR', raise_error=raise_error) if subjects_dir is not None: - subjects_dir = str(subjects_dir) + subjects_dir = _check_fname( + fname=subjects_dir, overwrite='read', must_exist=True, + need_dir=True, name='subjects_dir' + ) - subjects_dir = _check_fname( - fname=subjects_dir, overwrite='read', must_exist=True, need_dir=True, - name='subjects_dir' - ) return subjects_dir From 2c989c56b6bd86a036b8bbb1b957c76e9ff5f76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 6 Oct 2021 09:46:50 +0200 Subject: [PATCH 20/32] Fix tests --- mne/io/fiff/tests/test_raw_fiff.py | 5 +++-- mne/report/tests/test_report.py | 1 + mne/utils/tests/test_config.py | 15 ++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index df289ac225e..d6aaf0977a6 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -1796,11 +1796,12 @@ def test_expand_user(tmp_path, monkeypatch): path_in = Path(fif_fname) path_out = tmp_path / path_in.name + path_home = f'~/{path_in.name}' shutil.copyfile( src=path_in, dst=path_out ) - raw = read_raw_fif(fname=path_in, preload=True) - raw.save(fname=path_out, overwrite=True) + raw = read_raw_fif(fname=path_home, preload=True) + raw.save(fname=path_home, overwrite=True) diff --git a/mne/report/tests/test_report.py b/mne/report/tests/test_report.py index 360ca1626a5..02cc14b1ea8 100644 --- a/mne/report/tests/test_report.py +++ b/mne/report/tests/test_report.py @@ -442,6 +442,7 @@ def test_add_htmls_to_section(): assert (repr(report)) +@testing.requires_testing_data def test_add_slider_to_section(tmpdir): """Test adding a slider with a series of images to mne report.""" tempdir = str(tmpdir) diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index f381119daee..cf7238f4847 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -89,17 +89,22 @@ def test_sys_info(): assert 'Platform: macOS-' in out -def test_get_subjects_dir(monkeypatch, tmpdir): +def test_get_subjects_dir(tmp_path, monkeypatch): """Test get_subjects_dir().""" + subjects_dir = tmp_path / 'foo' + subjects_dir.mkdir() + # String - subjects_dir = '/foo' - assert get_subjects_dir(subjects_dir) == subjects_dir + assert get_subjects_dir(str(subjects_dir)) == str(subjects_dir) # Path - subjects_dir = Path('/foo') assert get_subjects_dir(subjects_dir) == str(subjects_dir) # `None` - monkeypatch.setenv('_MNE_FAKE_HOME_DIR', str(tmpdir)) + monkeypatch.setenv('_MNE_FAKE_HOME_DIR', str(tmp_path)) monkeypatch.delenv('SUBJECTS_DIR', raising=False) assert get_subjects_dir() is None + + # Expand `~` + monkeypatch.setenv('HOME', str(tmp_path)) + assert get_subjects_dir('~/foo') == str(subjects_dir) From 2d56533f10909117b80ee8f95e8a5a9025d7469a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 6 Oct 2021 11:11:56 +0200 Subject: [PATCH 21/32] Try to fix tests on Windows --- mne/io/fiff/tests/test_raw_fiff.py | 2 +- mne/utils/tests/test_config.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index d6aaf0977a6..1a5903b1de4 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -1796,7 +1796,7 @@ def test_expand_user(tmp_path, monkeypatch): path_in = Path(fif_fname) path_out = tmp_path / path_in.name - path_home = f'~/{path_in.name}' + path_home = Path('~') / path_in.name shutil.copyfile( src=path_in, diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index cf7238f4847..17db9fc5ee7 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -107,4 +107,5 @@ def test_get_subjects_dir(tmp_path, monkeypatch): # Expand `~` monkeypatch.setenv('HOME', str(tmp_path)) + monkeypatch.setenv('USERPROFILE', str(tmp_path)) # Windows assert get_subjects_dir('~/foo') == str(subjects_dir) From 6e874f5eb910118e266e45a5cafcd4a4ace4d134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 6 Oct 2021 13:38:31 +0200 Subject: [PATCH 22/32] Try to fix another test on Windows --- mne/io/fiff/tests/test_raw_fiff.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index 1a5903b1de4..31a6c28195d 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -1793,6 +1793,7 @@ def test_corrupted(tmpdir): def test_expand_user(tmp_path, monkeypatch): """Test that we're expanding `~` before reading and writing.""" monkeypatch.setenv('HOME', str(tmp_path)) + monkeypatch.setenv('USERPROFILE', str(tmp_path)) # Windows path_in = Path(fif_fname) path_out = tmp_path / path_in.name From 16e865a24327f6eeee765590e8a6a140562c09cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Wed, 6 Oct 2021 14:37:08 +0200 Subject: [PATCH 23/32] Update changelog [skip azp][skip azure] --- doc/changes/latest.inc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index c9ecd88d3b3..8d5af17d32e 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -187,6 +187,8 @@ Enhancements - All functions for reading and writing files should now automatically handle ``~`` correctly on Linux and macOS machines and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) +- All functions accepting a FreeSurfer subjects directory via a ``subjects_dir`` parameter can now consume :py:class:`pathlib.Path` objects too (used to be only strings) (:gh:`9613` by `Richard Höchenberger`_) + Bugs ~~~~ - Fix bug in :meth:`mne.io.Raw.pick` and related functions when parameter list contains channels which are not in info instance (:gh:`9708` **by new contributor** |Evgeny Goldstein|_) From 921d1e1ca9e1bc849cfb217c49adf11593360e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:26:15 +0200 Subject: [PATCH 24/32] Rename _check_path_like() -> _path_like() --- mne/epochs.py | 4 ++-- mne/source_estimate.py | 4 ++-- mne/source_space.py | 4 ++-- mne/transforms.py | 4 ++-- mne/utils/__init__.py | 2 +- mne/utils/check.py | 2 +- mne/utils/tests/test_check.py | 8 ++++---- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index e29953d74b8..d7099d8c3e1 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -60,7 +60,7 @@ _check_pandas_index_arguments, _convert_times, _scale_dataframe_data, _check_time_format, object_size, _on_missing, _validate_type, _ensure_events, - _check_path_like) + _path_like) from .utils.docs import fill_doc from .data.html_templates import epochs_template @@ -3115,7 +3115,7 @@ class EpochsFIF(BaseEpochs): @verbose def __init__(self, fname, proj=True, preload=True, verbose=None): # noqa: D102 - if _check_path_like(fname): + if _path_like(fname): check_fname( fname=fname, filetype='epochs', endings=('-epo.fif', '-epo.fif.gz', '_epo.fif', '_epo.fif.gz') diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 0fd9d8cf071..8b99726e78e 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -31,7 +31,7 @@ fill_doc, _check_option, _validate_type, _check_src_normal, _check_stc_units, _check_pandas_installed, _check_pandas_index_arguments, _convert_times, _ensure_int, - _build_data_frame, _check_time_format, _check_path_like, + _build_data_frame, _check_time_format, _path_like, sizeof_fmt, object_size, _check_fname) from .viz import (plot_source_estimates, plot_vector_source_estimates, plot_volume_source_estimates) @@ -2991,7 +2991,7 @@ def _volume_labels(src, labels, mri_resolution): extra = ' when using a volume source space' _import_nibabel('use volume atlas labels') _validate_type(labels, ('path-like', list, tuple), 'labels' + extra) - if _check_path_like(labels): + if _path_like(labels): mri = labels infer_labels = True else: diff --git a/mne/source_space.py b/mne/source_space.py index e8dea0b0857..5cda384828a 100644 --- a/mne/source_space.py +++ b/mne/source_space.py @@ -35,7 +35,7 @@ read_freesurfer_lut, get_mni_fiducials, _check_mri) from .utils import (get_subjects_dir, check_fname, logger, verbose, fill_doc, _ensure_int, check_version, _get_call_line, warn, - _check_fname, _check_path_like, _check_sphere, + _check_fname, _path_like, _check_sphere, _validate_type, _check_option, _is_numeric, _pl, _suggest, object_size, sizeof_fmt) from .parallel import parallel_func, check_n_jobs @@ -2187,7 +2187,7 @@ def _ensure_src(src, kind=None, extra='', verbose=None): _check_option( 'kind', kind, (None, 'surface', 'volume', 'mixed', 'discrete')) msg = 'src must be a string or instance of SourceSpaces%s' % (extra,) - if _check_path_like(src): + if _path_like(src): src = str(src) if not op.isfile(src): raise IOError('Source space file "%s" not found' % src) diff --git a/mne/transforms.py b/mne/transforms.py index 649a4370370..fec6fa479ec 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -20,7 +20,7 @@ from .io.write import start_file, end_file, write_coord_trans from .defaults import _handle_default from .utils import (check_fname, logger, verbose, _ensure_int, _validate_type, - _check_path_like, get_subjects_dir, fill_doc, _check_fname, + _path_like, get_subjects_dir, fill_doc, _check_fname, _check_option, _require_version, wrapped_stdout) @@ -450,7 +450,7 @@ def _get_trans(trans, fro='mri', to='head', allow_none=True): if allow_none: types += (None,) _validate_type(trans, types, 'trans') - if _check_path_like(trans): + if _path_like(trans): trans = str(trans) if trans == 'fsaverage': trans = op.join(op.dirname(__file__), 'data', 'fsaverage', diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 4afb1d2e825..b12af51abf8 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -14,7 +14,7 @@ _validate_type, _check_info_inv, _check_channels_spatial_filter, _check_one_ch_type, _check_rank, _check_option, _check_depth, _check_combine, - _check_path_like, _check_src_normal, _check_stc_units, + _path_like, _check_src_normal, _check_stc_units, _check_pyqt5_version, _check_sphere, _check_time_format, _check_freesurfer_home, _suggest, _require_version, _on_missing, _check_on_missing, int_like, _safe_input, diff --git a/mne/utils/check.py b/mne/utils/check.py index e54a42c559a..b07ebb6a43d 100644 --- a/mne/utils/check.py +++ b/mne/utils/check.py @@ -422,7 +422,7 @@ def _validate_type(item, types=None, item_name=None, type_name=None): f"got {type(item)} instead.") -def _check_path_like(item): +def _path_like(item): """Validate that `item` is `path-like`. Parameters diff --git a/mne/utils/tests/test_check.py b/mne/utils/tests/test_check.py index d55da37794c..57f51a7625a 100644 --- a/mne/utils/tests/test_check.py +++ b/mne/utils/tests/test_check.py @@ -18,7 +18,7 @@ from mne.utils import (check_random_state, _check_fname, check_fname, _check_subject, requires_mayavi, traits_test, _check_mayavi_version, _check_info_inv, _check_option, - check_version, _check_path_like, _validate_type, + check_version, _path_like, _validate_type, _suggest, _on_missing, requires_nibabel, _safe_input) data_path = testing.data_path(download=False) @@ -182,9 +182,9 @@ def test_check_path_like(): pathlib_path = Path(base_dir) no_path = dict(foo='bar') - assert _check_path_like(str_path) is True - assert _check_path_like(pathlib_path) is True - assert _check_path_like(no_path) is False + assert _path_like(str_path) is True + assert _path_like(pathlib_path) is True + assert _path_like(no_path) is False def test_validate_type(): From 8ddd8271907e131ab85cef0f370852a7215c4cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:35:02 +0200 Subject: [PATCH 25/32] Add TODO comments to remind us of missing overwrite params --- mne/cov.py | 1 + mne/evoked.py | 2 ++ mne/source_estimate.py | 8 +++++++- mne/time_frequency/csd.py | 1 + mne/transforms.py | 1 + 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/mne/cov.py b/mne/cov.py index e2d474b63d0..893cf70a1fc 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -153,6 +153,7 @@ def save(self, fname): """ check_fname(fname, 'covariance', ('-cov.fif', '-cov.fif.gz', '_cov.fif', '_cov.fif.gz')) + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) fid = start_file(fname) diff --git a/mne/evoked.py b/mne/evoked.py index 469a8cc2b75..9acdb9ce67b 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -295,6 +295,7 @@ def save(self, fname): Information on baseline correction will be stored with the data, and will be restored when reading again via `mne.read_evokeds`. """ + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) write_evokeds(fname, self) @@ -1399,6 +1400,7 @@ def _write_evokeds(fname, evoked, check=True, *, on_mismatch='raise'): if check: check_fname(fname, 'evoked', ('-ave.fif', '-ave.fif.gz', '_ave.fif', '_ave.fif.gz')) + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) if not isinstance(evoked, list): diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 8b99726e78e..b3cd8cbc8d7 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -626,6 +626,7 @@ def save(self, fname, ftype='h5', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) if ftype != 'h5': raise ValueError('%s objects can only be written as HDF5 files.' @@ -636,7 +637,9 @@ def save(self, fname, ftype='h5', verbose=None): dict(vertices=self.vertices, data=self.data, tmin=self.tmin, tstep=self.tstep, subject=self.subject, src_type=self._src_type), - title='mnepython', overwrite=True) + title='mnepython', + # TODO: Add `overwrite` param to method signature + overwrite=True) @copy_function_doc_to_method_doc(plot_source_estimates) def plot(self, subject=None, surface='inflated', hemi='lh', @@ -1599,6 +1602,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) _check_option('ftype', ftype, ['stc', 'w', 'h5']) @@ -2088,6 +2092,7 @@ def save_as_volume(self, fname, src, dest='mri', mri_resolution=False, """ import nibabel as nib _validate_type(fname, 'path-like', 'fname') + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) img = self.as_volume(src, dest=dest, mri_resolution=mri_resolution, format=format) @@ -2191,6 +2196,7 @@ def save(self, fname, ftype='stc', verbose=None): %(verbose_meth)s """ _validate_type(fname, 'path-like', 'fname') + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) _check_option('ftype', ftype, ['stc', 'w', 'h5']) if ftype != 'h5' and len(self.vertices) != 1: diff --git a/mne/time_frequency/csd.py b/mne/time_frequency/csd.py index 1bba4f8ea58..765dbb26198 100644 --- a/mne/time_frequency/csd.py +++ b/mne/time_frequency/csd.py @@ -459,6 +459,7 @@ def save(self, fname): if not fname.endswith('.h5'): fname += '.h5' + # TODO: Add `overwrite` param to method signature write_hdf5(fname, self.__getstate__(), overwrite=True, title='conpy') def copy(self): diff --git a/mne/transforms.py b/mne/transforms.py index fec6fa479ec..7f086219738 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -574,6 +574,7 @@ def write_trans(fname, trans): """ check_fname(fname, 'trans', ('-trans.fif', '-trans.fif.gz', '_trans.fif', '_trans.fif.gz')) + # TODO: Add `overwrite` param to method signature fname = _check_fname(fname=fname, overwrite=True) fid = start_file(fname) write_coord_trans(fid, trans) From b22313d85a572fb4a42279af6f531241768f1859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:36:57 +0200 Subject: [PATCH 26/32] Rephrase changelog entry --- doc/changes/latest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 8d5af17d32e..9ba87b49b90 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -185,7 +185,7 @@ Enhancements - Add :func:`mne.preprocessing.ieeg.project_sensors_onto_brain` to project ECoG sensors onto the pial surface to compensate for brain shift (:gh:`9800` by `Alex Rockhill`_) -- All functions for reading and writing files should now automatically handle ``~`` correctly on Linux and macOS machines and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) +- All functions for reading and writing files should now automatically handle ``~`` (the tilde charater) and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) - All functions accepting a FreeSurfer subjects directory via a ``subjects_dir`` parameter can now consume :py:class:`pathlib.Path` objects too (used to be only strings) (:gh:`9613` by `Richard Höchenberger`_) From 25a85a4d33e5a706a33c905bcbe410cceceb7a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:41:20 +0200 Subject: [PATCH 27/32] Slightly chang _check_fname() logic as suggested in review --- mne/utils/check.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mne/utils/check.py b/mne/utils/check.py index b07ebb6a43d..45681a527c4 100644 --- a/mne/utils/check.py +++ b/mne/utils/check.py @@ -156,7 +156,11 @@ def _check_fname(fname, overwrite=False, must_exist=False, name='File', need_dir=False): """Check for file existence, and return string of its absolute path.""" _validate_type(fname, 'path-like', name) - fname = op.expanduser(fname) + fname = str( + Path(fname) + .expanduser() + .absolute() + ) if op.exists(fname): if not overwrite: @@ -180,7 +184,8 @@ def _check_fname(fname, overwrite=False, must_exist=False, name='File', f'{name} does not have read permissions: {fname}') elif must_exist: raise FileNotFoundError(f'{name} does not exist: {fname}') - return str(op.abspath(fname)) + + return fname def _check_subject(first, second, *, raise_error=True, From bd76c1ebb3d1a22c860d11de4543558734f57d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:52:44 +0200 Subject: [PATCH 28/32] =?UTF-8?q?It=20seems=20something=20went=20wrong=20w?= =?UTF-8?q?hen=20merging=20main=20=F0=9F=A4=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mne/minimum_norm/inverse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index 1ffbfc4fdee..853d0f37c17 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -42,6 +42,7 @@ from ..utils import (check_fname, logger, verbose, warn, _validate_type, _check_compensation_grade, _check_option, _check_depth, _check_src_normal, _check_fname) +from ..data.html_templates import inverse_operator_template INVERSE_METHODS = ('MNE', 'dSPM', 'sLORETA', 'eLORETA') From 8a0c8b09244ed75c8009b8fe39c6f91515762e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 11:56:42 +0200 Subject: [PATCH 29/32] Fix a typo --- doc/changes/latest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 90993d2eb2b..400fc1155a1 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -191,7 +191,7 @@ Enhancements - Add :func:`mne.preprocessing.ieeg.project_sensors_onto_brain` to project ECoG sensors onto the pial surface to compensate for brain shift (:gh:`9800` by `Alex Rockhill`_) -- All functions for reading and writing files should now automatically handle ``~`` (the tilde charater) and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) +- All functions for reading and writing files should now automatically handle ``~`` (the tilde character) and expand it to the user's home directory. Should you come across any function that doesn't do it, please do let us know! (:gh:`9613` by `Richard Höchenberger`_) - All functions accepting a FreeSurfer subjects directory via a ``subjects_dir`` parameter can now consume :py:class:`pathlib.Path` objects too (used to be only strings) (:gh:`9613` by `Richard Höchenberger`_) From 48c25048e752dbad48061ba153bb714b7e94aab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 12:01:04 +0200 Subject: [PATCH 30/32] Fix more issues resulting from merging main --- mne/utils/tests/test_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/utils/tests/test_check.py b/mne/utils/tests/test_check.py index 57f51a7625a..1b6bd956b54 100644 --- a/mne/utils/tests/test_check.py +++ b/mne/utils/tests/test_check.py @@ -176,8 +176,8 @@ def test_check_option(): assert _check_option('option', 'bad', ['valid']) -def test_check_path_like(): - """Test _check_path_like().""" +def test_path_like(): + """Test _path_like().""" str_path = str(base_dir) pathlib_path = Path(base_dir) no_path = dict(foo='bar') From 41555e217fe2a8884fd6d546b791509d835bdf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 12:26:06 +0200 Subject: [PATCH 31/32] And another one --- mne/report/report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mne/report/report.py b/mne/report/report.py index 37e88fc68e0..52aab770f35 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -37,7 +37,7 @@ from .._freesurfer import _reorient_image, _mri_orientation from ..utils import (logger, verbose, get_subjects_dir, warn, _ensure_int, fill_doc, _check_option, _validate_type, _safe_input, - _check_path_like, use_log_level, deprecated) + _path_like, use_log_level, deprecated) from ..viz import (plot_events, plot_alignment, plot_cov, plot_projs_topomap, plot_compare_evokeds, set_3d_view, get_3d_backend) from ..viz.misc import _plot_mri_contours, _get_bem_plotting_surfaces @@ -1498,7 +1498,7 @@ def add_figure(self, fig, title, *, caption=None, image_format=None, figs = (fig,) for fig in figs: - if _check_path_like(fig): + if _path_like(fig): raise TypeError( f'It seems you passed a path to `add_figure`. However, ' f'only Matplotlib figures, Mayavi scenes, and NumPy ' @@ -1582,9 +1582,9 @@ def add_figs_to_section(self, figs, captions, section='custom', _check_scale(scale) if ( - _check_path_like(figs) or + _path_like(figs) or (hasattr(figs, '__iter__') and - any(_check_path_like(f) for f in figs)) + any(_path_like(f) for f in figs)) ): raise TypeError( 'It seems you passed a path to `add_figs_to_section`. ' From 622da4d33694aa40a8abff78e334a4a938bde567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 8 Oct 2021 12:40:04 +0200 Subject: [PATCH 32/32] Hopefully the last one --- mne/report/tests/test_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/report/tests/test_report.py b/mne/report/tests/test_report.py index a1fad18783d..43d2da70e9b 100644 --- a/mne/report/tests/test_report.py +++ b/mne/report/tests/test_report.py @@ -466,6 +466,7 @@ def test_add_html(): assert (repr(report)) +@testing.requires_testing_data def test_multiple_figs(tmpdir): """Test adding a slider with a series of figures to a Report.""" tempdir = str(tmpdir)