Skip to content

Commit d995aa2

Browse files
hoechenbergermscheltiennelarsonerdrammock
authored
Backport #9972 (#9977)
* Backport #9972 * Fix defaults * MRG: Add argument overwrite to export for raws and epochs to match save. (#9975) * Add argument overwrite to export for Raws. * Add argument overwrite to export for epochs. * Add tests. * Add entry to changelog. * Move entry to changelog to Bugs and fix spelling typo. * fix indentation for ..versionadded * typo fix. * fix tests. * Change versionadded from 1.0 to 0.24.1 [skip azp] [skip actions] * Add overwrite for evoked instances (mffpy related). * FIX: replace else statement with finally. * Use shutil.rmtree instead of os.remove since mffpy creates a directory. * Remove finally. * Update changelog. * improve comment preceding folder deletion. * Update mne/epochs.py [skip ci] Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Update mne/export/_egimff.py [skip ci] Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Update mne/export/_export.py [skip ci] Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Update mne/export/_export.py [skip ci] Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Update mne/export/_export.py [skip ci] Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Update mne/export/_egimff.py Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * Fix behavior when pathlib.Path are passed instead of strings. * Fix import and style. * add test for epochs as well. * Remove str(fname) as it's not needed. * fix positional/keyword arguments. Co-authored-by: Eric Larson <larson.eric.d@gmail.com> * remove dead link (no suitable alternative) [skip actions][skip azp] (#9979) * FIX: Backport fix * import fix for scipy 1.8 pre (#9973) * import fix for scipy 1.8 pre * fix arg oversight * FIX: A couple more * FIX: Found another * FIX: More [skip azp] [skip circle] * FIX: Correct [skip circle] [skip azp] * FIX: One more Co-authored-by: Eric Larson <larson.eric.d@gmail.com> Co-authored-by: Mathieu Scheltienne <73893616+mscheltienne@users.noreply.github.com> Co-authored-by: Eric Larson <larson.eric.d@gmail.com> Co-authored-by: Daniel McCloy <dan@mccloy.info>
1 parent 8efb01f commit d995aa2

File tree

19 files changed

+141
-42
lines changed

19 files changed

+141
-42
lines changed

doc/changes/0.18.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Bug
145145

146146
- Fix :func:`mne.gui.coregistration` and :ref:`mne coreg` crashing with segmentation fault when switching subjects by `Eric Larson`_
147147

148-
- Fix :func:`mne.io.read_raw_brainvision` to accommodate vmrk files which do not have any annotations by `Alexander Kovrig`_
148+
- Fix :func:`mne.io.read_raw_brainvision` to accommodate vmrk files which do not have any annotations by Alexander Kovrig
149149

150150
- Fix :meth:`mne.io.Raw.plot` and :meth:`mne.Epochs.plot` to auto-scale ``misc`` channel types by default by `Eric Larson`_
151151

doc/changes/0.24.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Version 0.24.1 (Unreleased)
2121

2222
- Fix bug with :class:`mne.Report` where figures were saved with ``bbox_inches='tight'``, which led to inconsistent sizes in sliders (:gh:`9966` by `Eric Larson`_)
2323

24+
- :func:`mne.gui.coregistration` and the ``mne coreg`` command didn't respect the ``inspect`` parameter (:gh:`9972` by `Richard Höchenberger`_)
25+
26+
- Add missing argument ``overwrite`` to :func:`mne.export.export_raw`, :func:`mne.export.export_epochs`, :func:`mne.export.export_evokeds` and :func:`mne.export.export_evokeds_mff` (:gh:`9975` by `Mathieu Scheltienne`_)
27+
2428
.. _changes_0_24_0:
2529

2630
Version 0.24.0 (2021-11-03)

doc/changes/names.inc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@
244244

245245
.. _Quentin Bertrand: https://github.com/QB3
246246

247-
.. _Alexander Kovrig: https://github.com/OpenSatori
248-
249247
.. _Kostiantyn Maksymenko: https://github.com/makkostya
250248

251249
.. _Thomas Radman: https://github.com/tradman

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ def reset_warnings(gallery_conf, fname):
855855
"default value of type 'dict' in an Any trait will", # traits
856856
'rcParams is deprecated', # PyVista rcParams -> global_theme
857857
'to mean no clipping',
858+
'Call to deprecated method ThresholdBetween',
858859
):
859860
warnings.filterwarnings( # deal with other modules having bad imports
860861
'ignore', message=".*%s.*" % key, category=DeprecationWarning)

mne/epochs.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,7 +1914,7 @@ def save(self, fname, split_size='2GB', fmt='single', overwrite=False,
19141914
split_naming, overwrite)
19151915

19161916
@verbose
1917-
def export(self, fname, fmt='auto', verbose=None):
1917+
def export(self, fname, fmt='auto', *, overwrite=False, verbose=None):
19181918
"""Export Epochs to external formats.
19191919
19201920
Supported formats: EEGLAB (set, uses :mod:`eeglabio`)
@@ -1924,14 +1924,17 @@ def export(self, fname, fmt='auto', verbose=None):
19241924
----------
19251925
%(export_params_fname)s
19261926
%(export_params_fmt)s
1927+
%(overwrite)s
1928+
1929+
.. versionadded:: 0.24.1
19271930
%(verbose)s
19281931
19291932
Notes
19301933
-----
19311934
%(export_eeglab_note)s
19321935
"""
19331936
from .export import export_epochs
1934-
export_epochs(fname, self, fmt, verbose)
1937+
export_epochs(fname, self, fmt, overwrite=overwrite, verbose=verbose)
19351938

19361939
def equalize_event_counts(self, event_ids=None, method='mintime'):
19371940
"""Equalize the number of trials in each condition.

mne/export/_egimff.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
#
44
# License: BSD-3-Clause
55

6+
import os
7+
import shutil
68
import datetime
9+
import os.path as op
710

811
import numpy as np
912

1013
from ..io.egi.egimff import _import_mffpy
1114
from ..io.pick import pick_types, pick_channels
12-
from ..utils import verbose
15+
from ..utils import verbose, _check_fname
1316

1417

1518
@verbose
16-
def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
19+
def export_evokeds_mff(fname, evoked, history=None, *, overwrite=False,
20+
verbose=None):
1721
"""Export evoked dataset to MFF.
1822
1923
Parameters
@@ -28,6 +32,9 @@ def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
2832
history.xml. This must adhere to the format described in
2933
mffpy.xml_files.History.content. If None, no history.xml will be
3034
written.
35+
%(overwrite)s
36+
37+
.. versionadded:: 0.24.1
3138
%(verbose)s
3239
3340
Notes
@@ -48,6 +55,11 @@ def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
4855
sampling_rate = int(info['sfreq'])
4956

5057
# Initialize writer
58+
# Future changes: conditions based on version or mffpy requirement if
59+
# https://github.com/BEL-Public/mffpy/pull/92 is merged and released.
60+
fname = _check_fname(fname, overwrite=overwrite)
61+
if op.exists(fname):
62+
os.remove(fname) if op.isfile(fname) else shutil.rmtree(fname)
5163
writer = mffpy.Writer(fname)
5264
current_time = pytz.utc.localize(datetime.datetime.utcnow())
5365
writer.addxml('fileInfo', recordTime=current_time)

mne/export/_export.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import os.path as op
77

88
from ._egimff import export_evokeds_mff
9-
from ..utils import verbose, logger, _validate_type
9+
from ..utils import verbose, logger, _validate_type, _check_fname
1010

1111

1212
@verbose
1313
def export_raw(fname, raw, fmt='auto', physical_range='auto',
14-
add_ch_type=False, verbose=None):
14+
add_ch_type=False, *, overwrite=False, verbose=None):
1515
"""Export Raw to external formats.
1616
1717
Supported formats:
@@ -27,13 +27,17 @@ def export_raw(fname, raw, fmt='auto', physical_range='auto',
2727
%(export_params_fmt)s
2828
%(export_params_physical_range)s
2929
%(export_params_add_ch_type)s
30+
%(overwrite)s
31+
32+
.. versionadded:: 0.24.1
3033
%(verbose)s
3134
3235
Notes
3336
-----
3437
%(export_eeglab_note)s
3538
%(export_edf_note)s
3639
"""
40+
fname = _check_fname(fname, overwrite=overwrite)
3741
supported_export_formats = { # format : extensions
3842
'eeglab': ('set',),
3943
'edf': ('edf',),
@@ -52,7 +56,7 @@ def export_raw(fname, raw, fmt='auto', physical_range='auto',
5256

5357

5458
@verbose
55-
def export_epochs(fname, epochs, fmt='auto', verbose=None):
59+
def export_epochs(fname, epochs, fmt='auto', *, overwrite=False, verbose=None):
5660
"""Export Epochs to external formats.
5761
5862
Supported formats: EEGLAB (set, uses :mod:`eeglabio`)
@@ -64,12 +68,16 @@ def export_epochs(fname, epochs, fmt='auto', verbose=None):
6468
epochs : instance of Epochs
6569
The epochs to export.
6670
%(export_params_fmt)s
71+
%(overwrite)s
72+
73+
.. versionadded:: 0.24.1
6774
%(verbose)s
6875
6976
Notes
7077
-----
7178
%(export_eeglab_note)s
7279
"""
80+
fname = _check_fname(fname, overwrite=overwrite)
7381
supported_export_formats = {
7482
'eeglab': ('set',),
7583
'edf': ('edf',),
@@ -87,7 +95,8 @@ def export_epochs(fname, epochs, fmt='auto', verbose=None):
8795

8896

8997
@verbose
90-
def export_evokeds(fname, evoked, fmt='auto', verbose=None):
98+
def export_evokeds(fname, evoked, fmt='auto', *, overwrite=False,
99+
verbose=None):
91100
"""Export evoked dataset to external formats.
92101
93102
This function is a wrapper for format-specific export functions. The export
@@ -109,6 +118,9 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
109118
Format of the export. Defaults to ``'auto'``, which will infer the
110119
format from the filename extension. See supported formats above for
111120
more information.
121+
%(overwrite)s
122+
123+
.. versionadded:: 0.24.1
112124
%(verbose)s
113125
114126
See Also
@@ -120,6 +132,7 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
120132
-----
121133
.. versionadded:: 0.24
122134
"""
135+
fname = _check_fname(fname, overwrite=overwrite)
123136
supported_export_formats = {
124137
'mff': ('mff',),
125138
'eeglab': ('set',),
@@ -134,7 +147,7 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
134147
logger.info(f'Exporting evoked dataset to {fname}...')
135148

136149
if fmt == 'mff':
137-
export_evokeds_mff(fname, evoked)
150+
export_evokeds_mff(fname, evoked, overwrite=overwrite)
138151
elif fmt == 'eeglab':
139152
raise NotImplementedError('Export to EEGLAB not implemented.')
140153
elif fmt == 'edf':

mne/export/tests/test_export.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ def test_export_raw_eeglab(tmp_path):
5151
assert_allclose(raw.times, raw_read.times)
5252
assert_allclose(raw.get_data(), raw_read.get_data())
5353

54+
# test overwrite
55+
with pytest.raises(FileExistsError, match='Destination file exists'):
56+
raw.export(temp_fname, overwrite=False)
57+
raw.export(temp_fname, overwrite=True)
58+
59+
# test pathlib.Path files
60+
raw.export(Path(temp_fname), overwrite=True)
61+
5462

5563
@pytest.mark.skipif(not _check_edflib_installed(strict=False),
5664
reason='edflib-python not installed')
@@ -78,7 +86,7 @@ def test_double_export_edf(tmp_path):
7886

7987
# export again
8088
raw_read.load_data()
81-
raw_read.export(temp_fname, add_ch_type=True)
89+
raw_read.export(temp_fname, add_ch_type=True, overwrite=True)
8290
raw_read = read_raw_edf(temp_fname, preload=True)
8391

8492
# stim channel should be dropped
@@ -180,28 +188,28 @@ def test_rawarray_edf(tmp_path):
180188
raw_bad.rename_channels({'1': 'abcdefghijklmnopqrstuvwxyz'})
181189
with pytest.raises(RuntimeError, match='Signal label'), \
182190
pytest.warns(RuntimeWarning, match='Data has a non-integer'):
183-
raw_bad.export(temp_fname)
191+
raw_bad.export(temp_fname, overwrite=True)
184192

185193
# include bad birthday that is non-EDF compliant
186194
bad_info = info.copy()
187195
bad_info['subject_info']['birthday'] = (1700, 1, 20)
188196
raw = RawArray(data, bad_info)
189197
with pytest.raises(RuntimeError, match='Setting patient birth date'):
190-
raw.export(temp_fname)
198+
raw.export(temp_fname, overwrite=True)
191199

192200
# include bad measurement date that is non-EDF compliant
193201
raw = RawArray(data, info)
194202
meas_date = datetime(year=1984, month=1, day=1, tzinfo=timezone.utc)
195203
raw.set_meas_date(meas_date)
196204
with pytest.raises(RuntimeError, match='Setting start date time'):
197-
raw.export(temp_fname)
205+
raw.export(temp_fname, overwrite=True)
198206

199207
# test that warning is raised if there are non-voltage based channels
200208
raw = RawArray(data, info)
201209
with pytest.warns(RuntimeWarning, match='The unit'):
202210
raw.set_channel_types({'9': 'hbr'})
203211
with pytest.warns(RuntimeWarning, match='Non-voltage channels'):
204-
raw.export(temp_fname)
212+
raw.export(temp_fname, overwrite=True)
205213

206214
# data should match up to the non-accepted channel
207215
raw_read = read_raw_edf(temp_fname, preload=True)
@@ -313,6 +321,14 @@ def test_export_epochs_eeglab(tmp_path, preload):
313321
assert_allclose(epochs.times, epochs_read.times)
314322
assert_allclose(epochs.get_data(), epochs_read.get_data())
315323

324+
# test overwrite
325+
with pytest.raises(FileExistsError, match='Destination file exists'):
326+
epochs.export(temp_fname, overwrite=False)
327+
epochs.export(temp_fname, overwrite=True)
328+
329+
# test pathlib.Path files
330+
epochs.export(Path(temp_fname), overwrite=True)
331+
316332

317333
@requires_version('mffpy', '0.5.7')
318334
@testing.requires_testing_data
@@ -355,6 +371,20 @@ def test_export_evokeds_to_mff(tmp_path, fmt, do_history):
355371
assert ave_exported.comment == ave.comment
356372
assert_allclose(ave_exported.times, ave.times)
357373

374+
# test overwrite
375+
with pytest.raises(FileExistsError, match='Destination file exists'):
376+
if do_history:
377+
export_evokeds_mff(export_fname, evoked, history=history,
378+
overwrite=False)
379+
else:
380+
export_evokeds(export_fname, evoked, overwrite=False)
381+
382+
if do_history:
383+
export_evokeds_mff(export_fname, evoked, history=history,
384+
overwrite=True)
385+
else:
386+
export_evokeds(export_fname, evoked, overwrite=True)
387+
358388

359389
@requires_version('mffpy', '0.5.7')
360390
@testing.requires_testing_data

mne/externals/pymatreader/pymatreader.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
import scipy.io
3232
import os
3333

34-
from scipy.io.matlab.miobase import get_matfile_version
35-
36-
from .utils import _import_h5py, _hdf5todict, _check_for_scipy_mat_struct
34+
from .utils import (_import_h5py, _hdf5todict, _check_for_scipy_mat_struct,
35+
get_matfile_version)
3736

3837
__all__ = 'read_mat'
3938

mne/externals/pymatreader/utils.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333
import numpy
3434
import scipy.io
3535

36+
try:
37+
from scipy.io.matlab.miobase import get_matfile_version
38+
from scipy.io.matlab.mio5_params import MatlabOpaque
39+
from scipy.io.matlab.mio5 import MatlabFunction
40+
except ModuleNotFoundError: # scipy 1.8
41+
from scipy.io._matlab.miobase import get_matfile_version
42+
from scipy.io._matlab.mio5_params import MatlabOpaque
43+
from scipy.io._matlab.mio5 import MatlabFunction
44+
3645
if sys.version_info <= (2, 7):
3746
chr = unichr # noqa This is needed for python 2 and 3 compatibility
3847

@@ -209,7 +218,7 @@ def _check_for_scipy_mat_struct(data):
209218
for key in data:
210219
data[key] = _check_for_scipy_mat_struct(data[key])
211220

212-
if isinstance(data, scipy.io.matlab.mio5_params.MatlabOpaque):
221+
if isinstance(data, MatlabOpaque):
213222
warn('Complex objects (like classes) are not supported. '
214223
'They are imported on a best effort base '
215224
'but your mileage will vary.')
@@ -222,7 +231,7 @@ def _check_for_scipy_mat_struct(data):
222231

223232
def _handle_scipy_ndarray(data):
224233
if data.dtype == numpy.dtype('object') and not \
225-
isinstance(data, scipy.io.matlab.mio5.MatlabFunction):
234+
isinstance(data, MatlabFunction):
226235
as_list = []
227236
for element in data:
228237
as_list.append(_check_for_scipy_mat_struct(element))

0 commit comments

Comments
 (0)