diff --git a/README.rst b/README.rst index 247b2d8cf11..70b3310b69a 100644 --- a/README.rst +++ b/README.rst @@ -88,9 +88,9 @@ Dependencies The minimum required dependencies to run MNE-Python are: - Python >= 3.8 -- NumPy >= 1.20.2 -- SciPy >= 1.6.3 -- Matplotlib >= 3.4.0 +- NumPy >= 1.21.2 +- SciPy >= 1.7.1 +- Matplotlib >= 3.4.3 - pooch >= 1.5 - tqdm - Jinja2 @@ -98,7 +98,7 @@ The minimum required dependencies to run MNE-Python are: For full functionality, some functions require: -- Scikit-learn >= 0.24.2 +- Scikit-learn >= 1.0 - joblib >= 0.15 (for parallelization control) - mne-qt-browser >= 0.1 (for fast raw data visualization) - Qt5 >= 5.12 via one of the following bindings (for fast raw data visualization and interactive 3D visualization): @@ -108,10 +108,10 @@ For full functionality, some functions require: - PyQt5 >= 5.12 - PySide2 >= 5.12 -- Numba >= 0.53.1 +- Numba >= 0.54.0 - NiBabel >= 3.2.1 - OpenMEEG >= 2.5.6 -- Pandas >= 1.2.4 +- Pandas >= 1.3.2 - Picard >= 0.3 - CuPy >= 9.0.0 (for NVIDIA CUDA acceleration) - DIPY >= 1.4.0 diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc new file mode 100644 index 00000000000..bdc6c923547 --- /dev/null +++ b/doc/changes/latest.inc @@ -0,0 +1,34 @@ +.. NOTE: we use cross-references to highlight new functions and classes. + Please follow the examples below like :func:`mne.stats.f_mway_rm`, so the + whats_new page will have a link to the function/class documentation. + +.. NOTE: there are 3 separate sections for changes, based on type: + - "Enhancements" for new features + - "Bugs" for bug fixes + - "API changes" for backward-incompatible changes + +.. NOTE: changes from first-time contributors should be added to the TOP of + the relevant section (Enhancements / Bugs / API changes), and should look + like this (where xxxx is the pull request number): + + - description of enhancement/bugfix/API change (:gh:`xxxx` by + :newcontrib:`Firstname Lastname`) + + Also add a corresponding entry for yourself in doc/changes/names.inc + +.. _current: + +Current (1.6.dev0) +------------------ + +Enhancements +~~~~~~~~~~~~ +- None yet + +Bugs +~~~~ +- None yet + +API changes +~~~~~~~~~~~ +- None yet diff --git a/doc/install/installers.rst b/doc/install/installers.rst index e73b5e2d6e0..08c0b000497 100644 --- a/doc/install/installers.rst +++ b/doc/install/installers.rst @@ -15,7 +15,7 @@ Got any questions? Let us know on the `MNE Forum`_! :class-content: text-center :name: linux-installers - .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.4.2/MNE-Python-1.4.2_0-Linux.sh + .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.5.0/MNE-Python-1.5.0_0-Linux.sh :ref-type: ref :color: primary :shadow: @@ -29,14 +29,14 @@ Got any questions? Let us know on the `MNE Forum`_! .. code-block:: console - $ sh ./MNE-Python-1.4.2_0-Linux.sh + $ sh ./MNE-Python-1.5.0_0-Linux.sh .. tab-item:: macOS (Intel) :class-content: text-center :name: macos-intel-installers - .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.4.2/MNE-Python-1.4.2_0-macOS_Intel.pkg + .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.5.0/MNE-Python-1.5.0_0-macOS_Intel.pkg :ref-type: ref :color: primary :shadow: @@ -52,7 +52,7 @@ Got any questions? Let us know on the `MNE Forum`_! :class-content: text-center :name: macos-apple-installers - .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.4.2/MNE-Python-1.4.2_0-macOS_M1.pkg + .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.5.0/MNE-Python-1.5.0_0-macOS_M1.pkg :ref-type: ref :color: primary :shadow: @@ -68,7 +68,7 @@ Got any questions? Let us know on the `MNE Forum`_! :class-content: text-center :name: windows-installers - .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.4.2/MNE-Python-1.4.2_0-Windows.exe + .. button-link:: https://github.com/mne-tools/mne-installers/releases/download/v1.5.0/MNE-Python-1.5.0_0-Windows.exe :ref-type: ref :color: primary :shadow: @@ -116,7 +116,7 @@ information, including a line that will read something like: .. code-block:: - Using Python: /some/directory/mne-python_1.4.2_0/bin/python + Using Python: /some/directory/mne-python_1.5.0_0/bin/python This path is what you need to enter in VS Code when selecting the Python interpreter. diff --git a/mne/epochs.py b/mne/epochs.py index dbd558c7be3..069f88ddb42 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1287,7 +1287,7 @@ def plot( n_epochs=20, n_channels=20, title=None, - events=None, + events=False, event_color=None, order=None, show=True, diff --git a/mne/io/eyelink/eyelink.py b/mne/io/eyelink/eyelink.py index 72fa5567814..333470e0dd3 100644 --- a/mne/io/eyelink/eyelink.py +++ b/mne/io/eyelink/eyelink.py @@ -60,13 +60,13 @@ @fill_doc def read_raw_eyelink( fname, - preload=False, - verbose=None, + *, create_annotations=True, apply_offsets=False, find_overlaps=False, overlap_threshold=0.05, - gap_description=None, + preload=False, + verbose=None, ): """Reader for an Eyelink .asc file. @@ -74,8 +74,6 @@ def read_raw_eyelink( ---------- fname : path-like Path to the eyelink file (.asc). - %(preload)s - %(verbose)s create_annotations : bool | list (default True) Whether to create mne.Annotations from occular events (blinks, fixations, saccades) and experiment messages. If a list, must @@ -100,16 +98,8 @@ def read_raw_eyelink( the left and right eyes are separated by less than 50 ms, and the blink stop times of the left and right eyes are separated by less than 50 ms, then the blink will be merged into a single :class:`mne.Annotations`. - gap_description : str (default 'BAD_ACQ_SKIP') - Label for annotations that span across the gap period between the - blocks. Uses ``'BAD_ACQ_SKIP'`` by default so that these time periods will - be considered bad by MNE and excluded from operations like epoching. - - .. deprecated:: 1.5 - - This parameter is deprecated and will be removed in version 1.6. Use - :meth:`mne.Annotations.rename` if you want something other than - ``BAD_ACQ_SKIP`` as the annotation label. + %(preload)s + %(verbose)s Returns ------- @@ -132,13 +122,12 @@ def read_raw_eyelink( raw_eyelink = RawEyelink( fname, - preload=preload, - verbose=verbose, create_annotations=create_annotations, apply_offsets=apply_offsets, find_overlaps=find_overlaps, overlap_threshold=overlap_threshold, - gap_desc=gap_description, + preload=preload, + verbose=verbose, ) return raw_eyelink @@ -171,14 +160,6 @@ class RawEyelink(BaseRaw): the :class:`mne.Annotations` will be kept separate (i.e. "blink_L", "blink_R"). If the gap is smaller than the threshold, the :class:`mne.Annotations` will be merged (i.e. "blink_both"). - gap_desc : str - If there are multiple recording blocks in the file, the description of - the annotation that will span across the gap period between the - blocks. Default is ``None``, which uses 'BAD_ACQ_SKIP' by default so that these - timeperiods will be considered bad by MNE and excluded from operations like - epoching. Note that this parameter is deprecated and will be removed in 1.6. - Use ``mne.annotations.rename`` instead. - %(preload)s %(verbose)s @@ -197,13 +178,13 @@ class RawEyelink(BaseRaw): def __init__( self, fname, - preload=False, - verbose=None, + *, create_annotations=True, apply_offsets=False, find_overlaps=False, overlap_threshold=0.05, - gap_desc=None, + preload=False, + verbose=None, ): logger.info("Loading {}".format(fname)) @@ -214,16 +195,6 @@ def __init__( self._meas_date = None self._rec_info = None self._ascii_sfreq = None - if gap_desc is None: - gap_desc = "BAD_ACQ_SKIP" - else: - warn( - "gap_description is deprecated in 1.5 and will be removed in 1.6, " - "use raw.annotations.rename to use a description other than " - "'BAD_ACQ_SKIP'", - FutureWarning, - ) - self._gap_desc = gap_desc self.dataframes = {} # ======================== Parse ASCII File ========================= @@ -244,7 +215,7 @@ def __init__( if n_blocks > 1: logger.info( f"There are {n_blocks} recording blocks in this file. Times between" - f" blocks will be annotated with {self._gap_desc}." + f" blocks will be annotated with BAD_ACQ_SKIP." ) self.dataframes["samples"] = _adjust_times( self.dataframes["samples"], self._ascii_sfreq @@ -651,11 +622,10 @@ def _create_info(self, ch_names, sfreq): def _make_gap_annots(self, key="recording_blocks"): """Create Annotations for gap periods between recording blocks.""" df = self.dataframes[key] - gap_desc = self._gap_desc onsets = df["end_time"].iloc[:-1] diffs = df["time"].shift(-1) - df["end_time"] durations = diffs.iloc[:-1] - descriptions = [gap_desc] * len(onsets) + descriptions = ["BAD_ACQ_SKIP"] * len(onsets) return Annotations(onset=onsets, duration=durations, description=descriptions) def _make_eyelink_annots(self, df_dict, create_annots, apply_offsets): diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 0951a1e29ee..6c2c705d10c 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -67,7 +67,6 @@ _import_nibabel, _import_pymatreader_funcs, _check_head_radius, - has_nibabel, ) from .config import ( set_config, @@ -129,24 +128,16 @@ from .progressbar import ProgressBar from ._testing import ( run_command_if_main, - requires_sklearn, - requires_version, requires_mne, requires_good_network, - requires_pandas, - requires_h5py, ArgvSetter, SilenceStdout, has_freesurfer, has_mne_c, _TempDir, buggy_mkl_svd, - requires_numpydoc, requires_freesurfer, - requires_nitime, requires_mne_mark, - requires_neuromag2ft, - requires_pylsl, assert_object_equal, assert_and_remove_boundary_annot, _raw_annot, @@ -156,7 +147,6 @@ assert_stcs_equal, _click_ch_name, requires_openmeeg_mark, - requires_mne_qt_browser, ) from .numerics import ( hashfunc, diff --git a/mne/utils/_testing.py b/mne/utils/_testing.py index f4c35c1b8e1..a8a68d9b135 100644 --- a/mne/utils/_testing.py +++ b/mne/utils/_testing.py @@ -125,51 +125,6 @@ def requires_freesurfer(arg): ) -# %% -# Deprecated -def requires_version(library, min_version="0.0"): - """Check for a library version.""" - warn( - f"requires_version({repr(library)}, min_version={repr(min_version)}) " - "is deprecated and will be removed in 1.6, use pytest.importorskip(" - f"{repr(library)}, minversion={repr(min_version)}) instead", - FutureWarning, - ) - import pytest - - reason = f"Requires {library}" - if min_version != "0.0": - reason += f" version >= {min_version}" - return pytest.mark.skipif(not check_version(library, min_version), reason=reason) - - -def requires_module(function, name, call=None): - """Skip a test if package is not available (decorator).""" - msg = f"@requires_module({repr(name)}) is deprecated and will be removed " f"in 1.6" - if call is None: - msg += f" use pytest.importorskip({repr(name)}) instead" - else: - msg += f" use pytest.mark.skipif instead with the condition:\n\n{call}\n" - warn(msg, FutureWarning) - return _requires_module(function, name, call=call) - - -_n2ft_call = """ -if 'NEUROMAG2FT_ROOT' not in os.environ: - raise ImportError -""" -requires_pandas = partial(requires_module, name="pandas") -requires_pylsl = partial(requires_module, name="pylsl") -requires_sklearn = partial(requires_module, name="sklearn") -requires_mne_qt_browser = partial(requires_module, name="mne_qt_browser") -requires_neuromag2ft = partial(requires_module, name="neuromag2ft", call=_n2ft_call) -requires_nitime = partial(requires_module, name="nitime") -requires_h5py = partial(requires_module, name="h5py") -requires_numpydoc = partial(requires_version, "numpydoc", "1.0") - -# %% End deprecated - - def run_command_if_main(): """Run a given command if it's __main__.""" local_vars = inspect.currentframe().f_back.f_locals diff --git a/mne/utils/check.py b/mne/utils/check.py index 51b72923ba2..6c368059594 100644 --- a/mne/utils/check.py +++ b/mne/utils/check.py @@ -16,7 +16,6 @@ import numpy as np from ..fixes import _median_complex, _compare_version -from .docs import deprecated from ._logging import warn, logger, verbose, _record_warnings, _verbose_safe_false @@ -1203,12 +1202,6 @@ def _to_rgb(*args, name="color", alpha=False): ) from None -@deprecated("has_nibabel is deprecated and will be removed in 1.5") -def has_nibabel(): - """Check if nibabel is installed.""" - return check_version("nibabel") # pragma: no cover - - def _import_nibabel(why="use MRI files"): try: import nibabel as nib diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index c1ee9fa2f5b..1e462b4c6d4 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -817,8 +817,8 @@ def plot_epochs( .. versionadded:: 0.14.0 - .. versionchanged:: 1.5 - Passing ``events=None`` is deprecated and will be removed in version 1.6. + .. versionchanged:: 1.6 + Passing ``events=None`` was disallowed. The new equivalent is ``events=False``. %(event_color)s Defaults to ``None``. @@ -968,13 +968,7 @@ def plot_epochs( boundary_times = np.arange(len(epochs) + 1) * len(epochs.times) / sfreq # events - if events is None: - warn( - "The current default events=None is deprecated and will change to " - "events=True in MNE 1.6. Set events=False to suppress this warning.", - category=FutureWarning, - ) - events = False + _validate_type(events, (bool, np.ndarray), "events") if events is False: event_nums = None event_times = None diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py index 5166109daf4..a225bdb0a7e 100644 --- a/mne/viz/tests/test_epochs.py +++ b/mne/viz/tests/test_epochs.py @@ -18,16 +18,13 @@ from mne.event import make_fixed_length_events from mne.viz import plot_drop_log -# TODO: deprecation cycle handling, remove this and all `**ev` after 1.5 release -ev = dict(events=False) - def test_plot_epochs_not_preloaded(epochs_unloaded, browser_backend): """Test plotting non-preloaded epochs.""" if platform.machine() == "arm64": pytest.xfail("Flakey verbose behavior on macOS arm64") assert epochs_unloaded._data is None - epochs_unloaded.plot(**ev) + epochs_unloaded.plot() assert epochs_unloaded._data is None @@ -36,7 +33,7 @@ def test_plot_epochs_basic(epochs, epochs_full, noise_cov_io, capsys, browser_ba assert len(epochs.events) == 1 with epochs.info._unlock(): epochs.info["lowpass"] = 10.0 # allow heavy decim during plotting - fig = epochs.plot(**ev, scalings=None, title="Epochs") + fig = epochs.plot(scalings=None, title="Epochs") ticks = fig._get_ticklabels("x") assert ticks == ["2"] browser_backend._close_all() @@ -45,38 +42,38 @@ def test_plot_epochs_basic(epochs, epochs_full, noise_cov_io, capsys, browser_ba assert noise_cov_io["bads"] == [] assert epochs.info["bads"] == [] # all good with pytest.warns(RuntimeWarning, match="projection"): - epochs.plot(**ev, noise_cov=noise_cov_io) + epochs.plot(noise_cov=noise_cov_io) browser_backend._close_all() # add a channel to the epochs.info['bads'] epochs.info["bads"] = [epochs.ch_names[0]] with pytest.warns(RuntimeWarning, match="projection"): - epochs.plot(**ev, noise_cov=noise_cov_io) + epochs.plot(noise_cov=noise_cov_io) browser_backend._close_all() # add a channel to cov['bads'] noise_cov_io["bads"] = [epochs.ch_names[1]] with pytest.warns(RuntimeWarning, match="projection"): - epochs.plot(**ev, noise_cov=noise_cov_io) + epochs.plot(noise_cov=noise_cov_io) browser_backend._close_all() # have a data channel missing from the covariance noise_cov_io["names"] = noise_cov_io["names"][:306] noise_cov_io["data"] = noise_cov_io["data"][:306][:306] with pytest.warns(RuntimeWarning, match="projection"): - epochs.plot(**ev, noise_cov=noise_cov_io) + epochs.plot(noise_cov=noise_cov_io) browser_backend._close_all() # other options - fig = epochs[0].plot(**ev, picks=[0, 2, 3], scalings=None) + fig = epochs[0].plot(picks=[0, 2, 3], scalings=None) fig._fake_keypress("escape") with pytest.raises(ValueError, match="No appropriate channels found"): - epochs.plot(**ev, picks=[]) + epochs.plot(picks=[]) # gh-5906 assert len(epochs_full) == 7 epochs_full.info["bads"] = [epochs_full.ch_names[0]] capsys.readouterr() # test title error handling with pytest.raises(TypeError, match="title must be None or a string, got"): - epochs_full.plot(**ev, title=7) + epochs_full.plot(title=7) # test auto-generated title, and selection mode - epochs_full.plot(**ev, group_by="selection", title="") + epochs_full.plot(group_by="selection", title="") @pytest.mark.parametrize( @@ -84,26 +81,26 @@ def test_plot_epochs_basic(epochs, epochs_full, noise_cov_io, capsys, browser_ba ) def test_plot_epochs_scalings(epochs, scalings, browser_backend): """Test the valid options for scalings.""" - epochs.plot(**ev, scalings=scalings) + epochs.plot(scalings=scalings) def test_plot_epochs_colors(epochs, browser_backend): """Test epoch_colors, for compatibility with autoreject.""" epoch_colors = [["r"] * len(epochs.ch_names) for _ in range(len(epochs.events))] - epochs.plot(**ev, epoch_colors=epoch_colors) + epochs.plot(epoch_colors=epoch_colors) with pytest.raises(ValueError, match="length equal to the number of epo"): # epochs obj has only 1 epoch epochs.plot(epoch_colors=[["r"], ["b"]]) with pytest.raises(ValueError, match=r"epoch colors for epoch \d+ has"): # need 1 color for each channel - epochs.plot(**ev, epoch_colors=[["r"]]) + epochs.plot(epoch_colors=[["r"]]) # also test event_color - epochs.plot(**ev, event_color="b") + epochs.plot(event_color="b") def test_plot_epochs_scale_bar(epochs, browser_backend): """Test scale bar for epochs.""" - fig = epochs.plot(**ev) + fig = epochs.plot() texts = fig._get_scale_bar_texts() # mag & grad in this instance if browser_backend.name == "pyqtgraph": @@ -135,7 +132,7 @@ def test_plot_epochs_clicks(epochs, epochs_full, capsys, browser_backend): assert n_epochs - 1 == len(epochs) # test marking bad channels # need more than 1 epoch this time - fig = epochs_full.plot(**ev, n_epochs=3) + fig = epochs_full.plot(n_epochs=3) first_ch = fig._get_ticklabels("y")[0] assert first_ch not in fig.mne.info["bads"] fig._click_ch_name(ch_index=0, button=1) # click ch name to mark bad @@ -156,7 +153,7 @@ def test_plot_epochs_clicks(epochs, epochs_full, capsys, browser_backend): fig._close_event() # XXX workaround, MPL Agg doesn't trigger close event assert len(epochs_full) == 6 # test rightclick → image plot - fig = epochs_full.plot(**ev) + fig = epochs_full.plot() fig._click_ch_name(ch_index=0, button=3) # show image plot assert len(fig.mne.child_figs) == 1 # test scroll wheel @@ -168,7 +165,7 @@ def test_plot_epochs_keypresses(epochs_full, browser_backend): """Test plot_epochs keypress interaction.""" # we need more than 1 epoch epochs_full.drop_bad(dict(mag=4e-12)) # for histogram plot coverage - fig = epochs_full.plot(**ev, n_epochs=3) + fig = epochs_full.plot(n_epochs=3) # make sure green vlines are visible first (for coverage) sample_idx = len(epochs_full.times) // 2 # halfway through the first epoch x = fig.mne.traces[0].get_xdata()[sample_idx] @@ -270,7 +267,7 @@ def test_plot_epochs_nodata(browser_backend): info = create_info(2, 1000.0, "stim") epochs = EpochsArray(data, info) with pytest.raises(ValueError, match="consider passing picks explicitly"): - epochs.plot(**ev) + epochs.plot() @pytest.mark.slowtest @@ -456,11 +453,11 @@ def test_plot_epochs_ctf(raw_ctf, browser_backend): ) evts = make_fixed_length_events(raw_ctf) epochs = Epochs(raw_ctf, evts, preload=True) - epochs.plot(**ev) + epochs.plot() browser_backend._close_all() # test butterfly - fig = epochs.plot(**ev, butterfly=True) + fig = epochs.plot(butterfly=True) # leave fullscreen testing to Raw / _figure abstraction (too annoying here) keys = ( "b", @@ -519,4 +516,4 @@ def test_plot_epochs_selection_butterfly(raw, browser_backend): events = make_fixed_length_events(raw)[:1] epochs = Epochs(raw, events, tmin=0, tmax=0.5, preload=True, baseline=None) assert len(epochs) == 1 - epochs.plot(**ev, group_by="selection", butterfly=True) + epochs.plot(group_by="selection", butterfly=True) diff --git a/requirements_base.txt b/requirements_base.txt index 6755036ef1e..7ba8a8b1658 100644 --- a/requirements_base.txt +++ b/requirements_base.txt @@ -1,7 +1,7 @@ # requirements for basic MNE-Python functionality -numpy>=1.15.4 -scipy>=1.6.3 -matplotlib>=3.4.0 +numpy>=1.21.2 +scipy>=1.7.1 +matplotlib>=3.4.3 tqdm pooch>=1.5 decorator diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index 6b479c76b34..7ef561249f1 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -4,7 +4,7 @@ set -eo pipefail -x # old and minimal use conda if [[ "$MNE_CI_KIND" == "old" ]]; then echo "Setting conda env vars for old" - echo "CONDA_DEPENDENCIES=numpy=1.20.2 scipy=1.6.3 matplotlib=3.4 pandas=1.2.4 scikit-learn=0.24.2" >> $GITHUB_ENV + echo "CONDA_DEPENDENCIES=numpy=1.21.2 scipy=1.7.1 matplotlib=3.4.3 pandas=1.3.2 scikit-learn=1.0" >> $GITHUB_ENV echo "MNE_IGNORE_WARNINGS_IN_TESTS=true" >> $GITHUB_ENV echo "MNE_SKIP_NETWORK_TESTS=1" >> $GITHUB_ENV elif [[ "$MNE_CI_KIND" == "minimal" ]]; then