Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion doc/changes/0.18.inc
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Bug

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

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

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

Expand Down
4 changes: 4 additions & 0 deletions doc/changes/0.24.inc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Version 0.24.1 (Unreleased)

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

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

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

.. _changes_0_24_0:

Version 0.24.0 (2021-11-03)
Expand Down
2 changes: 0 additions & 2 deletions doc/changes/names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,6 @@

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

.. _Alexander Kovrig: https://github.com/OpenSatori

.. _Kostiantyn Maksymenko: https://github.com/makkostya

.. _Thomas Radman: https://github.com/tradman
Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ def reset_warnings(gallery_conf, fname):
"default value of type 'dict' in an Any trait will", # traits
'rcParams is deprecated', # PyVista rcParams -> global_theme
'to mean no clipping',
'Call to deprecated method ThresholdBetween',
):
warnings.filterwarnings( # deal with other modules having bad imports
'ignore', message=".*%s.*" % key, category=DeprecationWarning)
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
5 changes: 2 additions & 3 deletions mne/externals/pymatreader/pymatreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
import scipy.io
import os

from scipy.io.matlab.miobase import get_matfile_version

from .utils import _import_h5py, _hdf5todict, _check_for_scipy_mat_struct
from .utils import (_import_h5py, _hdf5todict, _check_for_scipy_mat_struct,
get_matfile_version)

__all__ = 'read_mat'

Expand Down
13 changes: 11 additions & 2 deletions mne/externals/pymatreader/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
import numpy
import scipy.io

try:
from scipy.io.matlab.miobase import get_matfile_version
from scipy.io.matlab.mio5_params import MatlabOpaque
from scipy.io.matlab.mio5 import MatlabFunction
except ModuleNotFoundError: # scipy 1.8
from scipy.io._matlab.miobase import get_matfile_version
from scipy.io._matlab.mio5_params import MatlabOpaque
from scipy.io._matlab.mio5 import MatlabFunction

if sys.version_info <= (2, 7):
chr = unichr # noqa This is needed for python 2 and 3 compatibility

Expand Down Expand Up @@ -209,7 +218,7 @@ def _check_for_scipy_mat_struct(data):
for key in data:
data[key] = _check_for_scipy_mat_struct(data[key])

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

def _handle_scipy_ndarray(data):
if data.dtype == numpy.dtype('object') and not \
isinstance(data, scipy.io.matlab.mio5.MatlabFunction):
isinstance(data, MatlabFunction):
as_list = []
for element in data:
as_list.append(_check_for_scipy_mat_struct(element))
Expand Down
7 changes: 2 additions & 5 deletions mne/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,8 @@ def coregistration(tabbed=False, split=True, width=None, inst=None,
different color.

.. versionadded:: 0.16
interaction : str | None
Can be 'terrain' (default None), use terrain-style interaction (where
"up" is the Z/superior direction), or 'trackball' to use
orientationless interactions.
%(scene_interaction_None)s
Defaults to ``'trackball'``.

.. versionadded:: 0.16
scale : float | None
Expand Down Expand Up @@ -147,7 +145,6 @@ def coregistration(tabbed=False, split=True, width=None, inst=None,
'project_eeg': project_eeg,
'scale_by_distance': scale_by_distance,
'mark_inside': mark_inside,
'interaction': interaction,
'scale': scale,
'advanced_rendering': advanced_rendering,
}
Expand Down
8 changes: 7 additions & 1 deletion mne/gui/_coreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class CoregistrationUI(HasTraits):
Display the window as soon as it is ready. Defaults to True.
standalone : bool
If True, start the Qt application event loop. Default to False.
%(scene_interaction)s
Defaults to ``'trackball'``.

.. versionadded:: 0.24.1
%(verbose)s
"""

Expand All @@ -95,7 +99,8 @@ def __init__(self, info_file, subject=None, subjects_dir=None,
head_transparency=None, hpi_coils=None,
head_shape_points=None, eeg_channels=None, orient_glyphs=None,
sensor_opacity=None, trans=None, size=None, bgcolor=None,
show=True, standalone=False, verbose=None):
show=True, standalone=False, interaction='trackball',
verbose=None):
from ..viz.backends.renderer import _get_renderer

def _get_default(var, val):
Expand Down Expand Up @@ -156,6 +161,7 @@ def _get_default(var, val):
self._renderer = _get_renderer(
size=self._defaults["size"], bgcolor=self._defaults["bgcolor"])
self._renderer._window_close_connect(self._clean)
self._renderer.set_interaction(interaction)

# setup the model
self._info = info
Expand Down
Loading