Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f23f300
Add argument overwrite to export for Raws.
mscheltienne Nov 8, 2021
2cb5426
Add argument overwrite to export for epochs.
mscheltienne Nov 8, 2021
18fa6aa
Add tests.
mscheltienne Nov 8, 2021
4e4bdfc
Add entry to changelog.
mscheltienne Nov 8, 2021
408629d
Move entry to changelog to Bugs and fix spelling typo.
mscheltienne Nov 8, 2021
2c39791
fix indentation for ..versionadded
mscheltienne Nov 8, 2021
991fbe9
typo fix.
mscheltienne Nov 8, 2021
9f9ac0a
fix tests.
mscheltienne Nov 8, 2021
184b747
Change versionadded from 1.0 to 0.24.1 [skip azp] [skip actions]
mscheltienne Nov 8, 2021
b3f327c
Add overwrite for evoked instances (mffpy related).
mscheltienne Nov 8, 2021
f5aaaf4
Merge branch 'main' into add_overwrite_to_export_raw
mscheltienne Nov 8, 2021
bd0f435
FIX: replace else statement with finally.
mscheltienne Nov 9, 2021
040d96b
Use shutil.rmtree instead of os.remove since mffpy creates a directory.
mscheltienne Nov 9, 2021
f105d63
Remove finally.
mscheltienne Nov 9, 2021
2f4caaf
Update changelog.
mscheltienne Nov 9, 2021
51dbfed
improve comment preceding folder deletion.
mscheltienne Nov 9, 2021
f7dd7a8
Update mne/epochs.py [skip ci]
mscheltienne Nov 9, 2021
100a833
Update mne/export/_egimff.py [skip ci]
mscheltienne Nov 9, 2021
e427165
Update mne/export/_export.py [skip ci]
mscheltienne Nov 9, 2021
bbf8db5
Update mne/export/_export.py [skip ci]
mscheltienne Nov 9, 2021
5b36c81
Update mne/export/_export.py [skip ci]
mscheltienne Nov 9, 2021
7ba6930
Update mne/export/_egimff.py
mscheltienne Nov 9, 2021
708d258
Fix behavior when pathlib.Path are passed instead of strings.
mscheltienne Nov 9, 2021
f829b14
Fix import and style.
mscheltienne Nov 9, 2021
c9e5ddf
add test for epochs as well.
mscheltienne Nov 9, 2021
221e280
Remove str(fname) as it's not needed.
mscheltienne Nov 9, 2021
34a19ad
fix positional/keyword arguments.
mscheltienne Nov 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Bugs

- Fix bug in :func:`mne.make_forward_solution` where sensor-sphere geometry check was incorrect (:gh:`9968` by `Eric Larson`_)

- Add 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`_)

- :func:`mne.gui.coregistration` and the ``mne coreg`` command didn't respect the ``inspect`` parameter (:gh:`9972` by `Richard Höchenberger`_)

API changes
Expand Down
7 changes: 5 additions & 2 deletions mne/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1914,7 +1914,7 @@ def save(self, fname, split_size='2GB', fmt='single', overwrite=False,
split_naming, overwrite)

@verbose
def export(self, fname, fmt='auto', verbose=None):
def export(self, fname, fmt='auto', *, overwrite=False, verbose=None):
"""Export Epochs to external formats.

Supported formats: EEGLAB (set, uses :mod:`eeglabio`)
Expand All @@ -1924,14 +1924,17 @@ def export(self, fname, fmt='auto', verbose=None):
----------
%(export_params_fname)s
%(export_params_fmt)s
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

Notes
-----
%(export_eeglab_note)s
"""
from .export import export_epochs
export_epochs(fname, self, fmt, verbose)
export_epochs(fname, self, fmt, overwrite=overwrite, verbose=verbose)

def equalize_event_counts(self, event_ids=None, method='mintime'):
"""Equalize the number of trials in each condition.
Expand Down
16 changes: 14 additions & 2 deletions mne/export/_egimff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
#
# License: BSD-3-Clause

import os
import shutil
import datetime
import os.path as op

import numpy as np

from ..io.egi.egimff import _import_mffpy
from ..io.pick import pick_types, pick_channels
from ..utils import verbose
from ..utils import verbose, _check_fname


@verbose
def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
def export_evokeds_mff(fname, evoked, history=None, *, overwrite=False,
verbose=None):
"""Export evoked dataset to MFF.

Parameters
Expand All @@ -28,6 +32,9 @@ def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
history.xml. This must adhere to the format described in
mffpy.xml_files.History.content. If None, no history.xml will be
written.
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

Notes
Expand All @@ -48,6 +55,11 @@ def export_evokeds_mff(fname, evoked, history=None, *, verbose=None):
sampling_rate = int(info['sfreq'])

# Initialize writer
# Future changes: conditions based on version or mffpy requirement if
# https://github.com/BEL-Public/mffpy/pull/92 is merged and released.
fname = _check_fname(fname, overwrite=overwrite)
if op.exists(fname):
os.remove(fname) if op.isfile(fname) else shutil.rmtree(fname)
writer = mffpy.Writer(fname)
current_time = pytz.utc.localize(datetime.datetime.utcnow())
writer.addxml('fileInfo', recordTime=current_time)
Expand Down
23 changes: 18 additions & 5 deletions mne/export/_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import os.path as op

from ._egimff import export_evokeds_mff
from ..utils import verbose, logger, _validate_type
from ..utils import verbose, logger, _validate_type, _check_fname


@verbose
def export_raw(fname, raw, fmt='auto', physical_range='auto',
add_ch_type=False, verbose=None):
add_ch_type=False, *, overwrite=False, verbose=None):
"""Export Raw to external formats.

Supported formats:
Expand All @@ -27,13 +27,17 @@ def export_raw(fname, raw, fmt='auto', physical_range='auto',
%(export_params_fmt)s
%(export_params_physical_range)s
%(export_params_add_ch_type)s
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

Notes
-----
%(export_eeglab_note)s
%(export_edf_note)s
"""
fname = _check_fname(fname, overwrite=overwrite)
supported_export_formats = { # format : extensions
'eeglab': ('set',),
'edf': ('edf',),
Expand All @@ -52,7 +56,7 @@ def export_raw(fname, raw, fmt='auto', physical_range='auto',


@verbose
def export_epochs(fname, epochs, fmt='auto', verbose=None):
def export_epochs(fname, epochs, fmt='auto', *, overwrite=False, verbose=None):
"""Export Epochs to external formats.

Supported formats: EEGLAB (set, uses :mod:`eeglabio`)
Expand All @@ -64,12 +68,16 @@ def export_epochs(fname, epochs, fmt='auto', verbose=None):
epochs : instance of Epochs
The epochs to export.
%(export_params_fmt)s
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

Notes
-----
%(export_eeglab_note)s
"""
fname = _check_fname(fname, overwrite=overwrite)
supported_export_formats = {
'eeglab': ('set',),
'edf': ('edf',),
Expand All @@ -87,7 +95,8 @@ def export_epochs(fname, epochs, fmt='auto', verbose=None):


@verbose
def export_evokeds(fname, evoked, fmt='auto', verbose=None):
def export_evokeds(fname, evoked, fmt='auto', *, overwrite=False,
verbose=None):
"""Export evoked dataset to external formats.

This function is a wrapper for format-specific export functions. The export
Expand All @@ -109,6 +118,9 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
Format of the export. Defaults to ``'auto'``, which will infer the
format from the filename extension. See supported formats above for
more information.
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

See Also
Expand All @@ -120,6 +132,7 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
-----
.. versionadded:: 0.24
"""
fname = _check_fname(fname, overwrite=overwrite)
supported_export_formats = {
'mff': ('mff',),
'eeglab': ('set',),
Expand All @@ -134,7 +147,7 @@ def export_evokeds(fname, evoked, fmt='auto', verbose=None):
logger.info(f'Exporting evoked dataset to {fname}...')

if fmt == 'mff':
export_evokeds_mff(fname, evoked)
export_evokeds_mff(fname, evoked, overwrite=overwrite)
elif fmt == 'eeglab':
raise NotImplementedError('Export to EEGLAB not implemented.')
elif fmt == 'edf':
Expand Down
40 changes: 35 additions & 5 deletions mne/export/tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ def test_export_raw_eeglab(tmp_path):
assert_allclose(raw.times, raw_read.times)
assert_allclose(raw.get_data(), raw_read.get_data())

# test overwrite
with pytest.raises(FileExistsError, match='Destination file exists'):
raw.export(temp_fname, overwrite=False)
raw.export(temp_fname, overwrite=True)

# test pathlib.Path files
raw.export(Path(temp_fname), overwrite=True)


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

# export again
raw_read.load_data()
raw_read.export(temp_fname, add_ch_type=True)
raw_read.export(temp_fname, add_ch_type=True, overwrite=True)
raw_read = read_raw_edf(temp_fname, preload=True)

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

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

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

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

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

# test overwrite
with pytest.raises(FileExistsError, match='Destination file exists'):
epochs.export(temp_fname, overwrite=False)
epochs.export(temp_fname, overwrite=True)

# test pathlib.Path files
epochs.export(Path(temp_fname), overwrite=True)


@requires_version('mffpy', '0.5.7')
@testing.requires_testing_data
Expand Down Expand Up @@ -355,6 +371,20 @@ def test_export_evokeds_to_mff(tmp_path, fmt, do_history):
assert ave_exported.comment == ave.comment
assert_allclose(ave_exported.times, ave.times)

# test overwrite
with pytest.raises(FileExistsError, match='Destination file exists'):
if do_history:
export_evokeds_mff(export_fname, evoked, history=history,
overwrite=False)
else:
export_evokeds(export_fname, evoked, overwrite=False)

if do_history:
export_evokeds_mff(export_fname, evoked, history=history,
overwrite=True)
else:
export_evokeds(export_fname, evoked, overwrite=True)


@requires_version('mffpy', '0.5.7')
@testing.requires_testing_data
Expand Down
8 changes: 6 additions & 2 deletions mne/io/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None,

@verbose
def export(self, fname, fmt='auto', physical_range='auto',
add_ch_type=False, verbose=None):
add_ch_type=False, *, overwrite=False, verbose=None):
"""Export Raw to external formats.

Supported formats: EEGLAB (set, uses :mod:`eeglabio`)
Expand All @@ -1490,6 +1490,9 @@ def export(self, fname, fmt='auto', physical_range='auto',
%(export_params_fmt)s
%(export_params_physical_range)s
%(export_params_add_ch_type)s
%(overwrite)s

.. versionadded:: 0.24.1
%(verbose)s

Notes
Expand All @@ -1499,7 +1502,8 @@ def export(self, fname, fmt='auto', physical_range='auto',
"""
from ..export import export_raw
export_raw(fname, self, fmt, physical_range=physical_range,
add_ch_type=add_ch_type, verbose=verbose)
add_ch_type=add_ch_type, overwrite=overwrite,
verbose=verbose)

def _tmin_tmax_to_start_stop(self, tmin, tmax):
start = int(np.floor(tmin * self.info['sfreq']))
Expand Down