From 63ec69eccfbbc5cb17c3f6d76ee334483fea39b3 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 14:39:20 -0500 Subject: [PATCH 01/14] MAINT: Post-release deprecations --- mne/conftest.py | 17 +++------- mne/datasets/eegbci/eegbci.py | 26 ++-------------- mne/decoding/csp.py | 12 ------- mne/decoding/ems.py | 2 +- mne/decoding/tests/test_base.py | 2 +- mne/fixes.py | 8 ++--- mne/io/base.py | 19 +----------- mne/io/egi/tests/test_egi.py | 3 -- mne/io/fiff/tests/test_raw_fiff.py | 8 ----- mne/io/neuralynx/neuralynx.py | 2 +- mne/source_estimate.py | 16 ---------- mne/tests/test_source_estimate.py | 3 +- mne/time_frequency/tfr.py | 50 ------------------------------ mne/utils/linalg.py | 2 +- mne/viz/_3d.py | 8 ++--- mne/viz/backends/_abstract.py | 2 +- mne/viz/backends/_notebook.py | 4 +-- mne/viz/backends/_pyvista.py | 4 +-- mne/viz/backends/_qt.py | 6 ++-- mne/viz/backends/tests/_utils.py | 44 -------------------------- mne/viz/evoked.py | 2 +- mne/viz/montage.py | 24 +++----------- mne/viz/tests/test_raw.py | 2 +- mne/viz/topomap.py | 8 ++--- pyproject.toml | 2 +- tools/vulture_allowlist.py | 1 - 26 files changed, 36 insertions(+), 241 deletions(-) delete mode 100644 mne/viz/backends/tests/_utils.py diff --git a/mne/conftest.py b/mne/conftest.py index d18b440dc9c..9896b73a368 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -34,6 +34,7 @@ _pl, _record_warnings, _TempDir, + check_version, numerics, ) @@ -636,21 +637,15 @@ def _use_backend(backend_name, interactive): def _check_skip_backend(name): from mne.viz.backends._utils import _notebook_vtk_works - from mne.viz.backends.tests._utils import ( - has_imageio_ffmpeg, - has_pyvista, - has_pyvistaqt, - ) + from mne.viz.backends.tests._utils import has_imageio_ffmpeg - if not has_pyvista(): - pytest.skip("Test skipped, requires pyvista.") + pytest.importorskip("pyvista") if not has_imageio_ffmpeg(): pytest.skip("Test skipped, requires imageio-ffmpeg") if name == "pyvistaqt": if not _check_qt_version(): pytest.skip("Test skipped, requires Qt.") - if not has_pyvistaqt(): - pytest.skip("Test skipped, requires pyvistaqt") + pytest.importorskip("pyvistaqt") else: assert name == "notebook", name if not _notebook_vtk_works(): @@ -660,10 +655,8 @@ def _check_skip_backend(name): @pytest.fixture(scope="session") def pixel_ratio(): """Get the pixel ratio.""" - from mne.viz.backends.tests._utils import has_pyvista - # _check_qt_version will init an app for us, so no need for us to do it - if not has_pyvista() or not _check_qt_version(): + if not check_version("pyvista", "0.32") or not _check_qt_version(): return 1.0 from qtpy.QtCore import Qt from qtpy.QtWidgets import QMainWindow diff --git a/mne/datasets/eegbci/eegbci.py b/mne/datasets/eegbci/eegbci.py index 91d78f57a03..7b6b3e8bdc8 100644 --- a/mne/datasets/eegbci/eegbci.py +++ b/mne/datasets/eegbci/eegbci.py @@ -9,7 +9,7 @@ from os import path as op from pathlib import Path -from ...utils import _url_to_local_path, logger, verbose, warn +from ...utils import _url_to_local_path, logger, verbose from ..utils import _do_path_update, _downloader_params, _get_path, _log_time_size EEGMI_URL = "https://physionet.org/files/eegmmidb/1.0.0/" @@ -94,10 +94,9 @@ def data_path(url, path=None, force_update=False, update_path=None, *, verbose=N @verbose def load_data( - subjects=None, - runs=None, + subjects, + runs, *, - subject=None, path=None, force_update=False, update_path=None, @@ -117,9 +116,6 @@ def load_data( The subjects to use. Can be in the range of 1-109 (inclusive). runs : int | list of int The runs to use (see Notes for details). - subject : int - This parameter is deprecated and will be removed in mne version 1.9. - Please use ``subjects`` instead. path : None | path-like Location of where to look for the EEGBCI data. If ``None``, the environment variable or config parameter ``MNE_DATASETS_EEGBCI_PATH`` is used. If neither @@ -170,22 +166,6 @@ def load_data( """ import pooch - # XXX: Remove this with mne 1.9 ↓↓↓ - # Also remove the subject parameter at that point. - # Also remove the `None` default for subjects and runs params at that point. - if subject is not None: - subjects = subject - warn( - "The ``subject`` parameter is deprecated and will be removed in version " - "1.9. Use the ``subjects`` parameter (note the `s`) to suppress this " - "warning.", - FutureWarning, - ) - del subject - if subjects is None or runs is None: - raise ValueError("You must pass the parameters ``subjects`` and ``runs``.") - # ↑↑↑ - t0 = time.time() if not hasattr(subjects, "__iter__"): diff --git a/mne/decoding/csp.py b/mne/decoding/csp.py index 1261ca82055..c277815fa26 100644 --- a/mne/decoding/csp.py +++ b/mne/decoding/csp.py @@ -18,7 +18,6 @@ _verbose_safe_false, fill_doc, pinv, - warn, ) @@ -304,7 +303,6 @@ def plot_patterns( info, components=None, *, - average=None, ch_type=None, scalings=None, sensors=True, @@ -342,7 +340,6 @@ def plot_patterns( :func:`mne.create_info`. components : float | array of float | None The patterns to plot. If ``None``, all components will be shown. - %(average_plot_evoked_topomap)s %(ch_type_topomap)s scalings : dict | float | None The scalings of the channel types to be applied for plotting. @@ -391,9 +388,6 @@ def plot_patterns( if components is None: components = np.arange(self.n_components) - if average is not None: - warn("`average` is deprecated and will be removed in 1.10.", FutureWarning) - # set sampling frequency to have 1 component per time point info = cp.deepcopy(info) with info._unlock(): @@ -403,7 +397,6 @@ def plot_patterns( # the call plot_topomap fig = patterns.plot_topomap( times=components, - average=average, ch_type=ch_type, scalings=scalings, sensors=sensors, @@ -438,7 +431,6 @@ def plot_filters( info, components=None, *, - average=None, ch_type=None, scalings=None, sensors=True, @@ -525,9 +517,6 @@ def plot_filters( if components is None: components = np.arange(self.n_components) - if average is not None: - warn("`average` is deprecated and will be removed in 1.10.", FutureWarning) - # set sampling frequency to have 1 component per time point info = cp.deepcopy(info) with info._unlock(): @@ -537,7 +526,6 @@ def plot_filters( # the call plot_topomap fig = filters.plot_topomap( times=components, - average=average, ch_type=ch_type, scalings=scalings, sensors=sensors, diff --git a/mne/decoding/ems.py b/mne/decoding/ems.py index 911b25e6692..b3e72a30e21 100644 --- a/mne/decoding/ems.py +++ b/mne/decoding/ems.py @@ -188,7 +188,7 @@ def compute_ems( data[:, this_picks] /= np.std(data[:, this_picks]) # Setup cross-validation. Need to use _set_cv to deal with sklearn - # deprecation of cv objects. + # changes in cv object handling. y = epochs.events[:, 2] _, cv_splits = _set_cv(cv, "classifier", X=y, y=y) diff --git a/mne/decoding/tests/test_base.py b/mne/decoding/tests/test_base.py index 4ec6ed4d281..6d915dd24f9 100644 --- a/mne/decoding/tests/test_base.py +++ b/mne/decoding/tests/test_base.py @@ -320,7 +320,7 @@ def test_get_coef_multiclass(n_features, n_targets): ], ) # TODO: Need to fix this properly in LinearModel -@pytest.mark.filterwarnings("ignore:'multi_class' was deprecated in.*:FutureWarning") +@pytest.mark.filterwarnings("ignore:'multi_class' was depr.*:FutureWarning") @pytest.mark.filterwarnings("ignore:lbfgs failed to converge.*:") def test_get_coef_multiclass_full(n_classes, n_channels, n_times): """Test a full example with pattern extraction.""" diff --git a/mne/fixes.py b/mne/fixes.py index 2b37de982c5..2aed20492ec 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -22,12 +22,10 @@ from math import log import numpy as np +from packaging.version import parse ############################################################################### -# distutils - -# distutils has been deprecated since Python 3.10 and was removed -# from the standard library with the release of Python 3.12. +# distutils LooseVersion removed in Python 3.12 def _compare_version(version_a, operator, version_b): @@ -48,8 +46,6 @@ def _compare_version(version_a, operator, version_b): bool The result of the version comparison. """ - from packaging.version import parse - mapping = {"<": "lt", "<=": "le", "==": "eq", "!=": "ne", ">=": "ge", ">": "gt"} with warnings.catch_warnings(record=True): warnings.simplefilter("ignore") diff --git a/mne/io/base.py b/mne/io/base.py index 54d3334b5c8..4f5f2436bd7 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -3187,7 +3187,7 @@ def concatenate_raws( @fill_doc -def match_channel_orders(insts=None, copy=True, *, raws=None): +def match_channel_orders(insts, copy=True): """Ensure consistent channel order across instances (Raw, Epochs, or Evoked). Parameters @@ -3196,9 +3196,6 @@ def match_channel_orders(insts=None, copy=True, *, raws=None): List of :class:`~mne.io.Raw`, :class:`~mne.Epochs`, or :class:`~mne.Evoked` instances to order. %(copy_df)s - raws : list - This parameter is deprecated and will be removed in mne version 1.9. - Please use ``insts`` instead. Returns ------- @@ -3206,20 +3203,6 @@ def match_channel_orders(insts=None, copy=True, *, raws=None): List of instances (Raw, Epochs, or Evoked) with channel orders matched according to the order they had in the first item in the ``insts`` list. """ - # XXX: remove "raws" parameter and logic below with MNE version 1.9 - # and remove default parameter value of insts - if raws is not None: - warn( - "The ``raws`` parameter is deprecated and will be removed in version " - "1.9. Use the ``insts`` parameter to suppress this warning.", - DeprecationWarning, - ) - insts = raws - elif insts is None: - # both insts and raws is None - raise ValueError( - "You need to pass a list of Raw, Epochs, or Evoked to ``insts``." - ) insts = deepcopy(insts) if copy else insts ch_order = insts[0].ch_names for inst in insts[1:]: diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py index d91b4fbd264..584714d9c8a 100644 --- a/mne/io/egi/tests/test_egi.py +++ b/mne/io/egi/tests/test_egi.py @@ -57,9 +57,6 @@ egi_pause_w1337_events = None egi_pause_w1337_skips = [(21956000.0, 40444000.0), (60936000.0, 89332000.0)] -# TODO: Remove once complete deprecation / FutureWarning about events_as_annonations -pytestmark = pytest.mark.filterwarnings("ignore:.*events_as_annotation.*:FutureWarning") - @requires_testing_data @pytest.mark.parametrize( diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py index 3e89ee335c9..1ae0cc52901 100644 --- a/mne/io/fiff/tests/test_raw_fiff.py +++ b/mne/io/fiff/tests/test_raw_fiff.py @@ -479,14 +479,6 @@ def test_concatenate_raws_order(): match_channel_orders(insts=raws, copy=True) raw_concat = concatenate_raws(raws) - # XXX: remove in version 1.9 - with pytest.warns(DeprecationWarning, match="``raws`` parameter is deprecated"): - match_channel_orders(raws=raws) - - # XXX: remove in version 1.9 - with pytest.raises(ValueError, match="need to pass a list"): - match_channel_orders() - # Now passes because all raws have the same order match_channel_orders(insts=raws, copy=False) raw_concat = concatenate_raws(raws) diff --git a/mne/io/neuralynx/neuralynx.py b/mne/io/neuralynx/neuralynx.py index 2b9bed80ae8..56ff9fa4adb 100644 --- a/mne/io/neuralynx/neuralynx.py +++ b/mne/io/neuralynx/neuralynx.py @@ -77,7 +77,7 @@ def read_raw_neuralynx( ) -# Helper for neo deprecation of exclude_filename -> exclude_filenames in 0.13.2 +# Helper for neo change of exclude_filename -> exclude_filenames in 0.13.2 def _exclude_kwarg(exclude_fnames): from neo.io import NeuralynxIO diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 1c0d6c1bc72..39205e28af2 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -3918,22 +3918,6 @@ def stc_near_sensors( _validate_type(src, (None, SourceSpaces), "src") _check_option("mode", mode, ("sum", "single", "nearest", "weighted")) if surface == "auto": - if src is not None: - pial_fname = op.join(subjects_dir, subject, "surf", "lh.pial") - pial_rr = read_surface(pial_fname)[0] - src_surf_is_pial = ( - op.isfile(pial_fname) - and src[0]["rr"].shape == pial_rr.shape - and np.allclose(src[0]["rr"], pial_rr) - ) - if not src_surf_is_pial: - warn( - "In version 1.8, ``surface='auto'`` will be the default " - "which will use the surface in ``src`` instead of the " - "pial surface when ``src != None``. Pass ``surface='pial'`` " - "or ``surface=None`` to suppress this warning", - DeprecationWarning, - ) surface = "pial" if src is None or src.kind == "surface" else None # create a copy of Evoked using ecog, seeg and dbs diff --git a/mne/tests/test_source_estimate.py b/mne/tests/test_source_estimate.py index 9d24ad1d7bb..e4fa5a36b25 100644 --- a/mne/tests/test_source_estimate.py +++ b/mne/tests/test_source_estimate.py @@ -1711,8 +1711,7 @@ def test_stc_near_sensors(tmp_path): for s in src: transform_surface_to(s, "head", trans, copy=False) assert src[0]["coord_frame"] == FIFF.FIFFV_COORD_HEAD - with pytest.warns(DeprecationWarning, match="instead of the pial"): - stc_src = stc_near_sensors(evoked, src=src, **kwargs) + stc_src = stc_near_sensors(evoked, src=src, **kwargs) assert len(stc_src.data) == 7928 with pytest.warns(RuntimeWarning, match="not included"): # some removed stc_src_full = compute_source_morph( diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index eaf173092bb..cf9f3631146 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -1187,9 +1187,6 @@ def __init__( f'{classname} got unsupported parameter value{_pl(problem)} ' f'{" and ".join(problem)}.' ) - # shim for tfr_array_morlet deprecation warning (TODO: remove after 1.7 release) - if method == "morlet": - method_kw.setdefault("zero_mean", True) # check method valid_methods = ["morlet", "multitaper"] if isinstance(inst, BaseEpochs): @@ -2946,48 +2943,6 @@ class EpochsTFR(BaseTFR, GetEpochsMixin): %(picks_good_data_noref)s %(proj_psd)s %(decim_tfr)s - %(events_epochstfr)s - - .. deprecated:: 1.7 - Pass an instance of :class:`~mne.Epochs` as ``inst`` instead, or use - :class:`~mne.time_frequency.EpochsTFRArray` which retains the old API. - %(event_id_epochstfr)s - - .. deprecated:: 1.7 - Pass an instance of :class:`~mne.Epochs` as ``inst`` instead, or use - :class:`~mne.time_frequency.EpochsTFRArray` which retains the old API. - selection : array - List of indices of selected events (not dropped or ignored etc.). For - example, if the original event array had 4 events and the second event - has been dropped, this attribute would be np.array([0, 2, 3]). - - .. deprecated:: 1.7 - Pass an instance of :class:`~mne.Epochs` as ``inst`` instead, or use - :class:`~mne.time_frequency.EpochsTFRArray` which retains the old API. - drop_log : tuple of tuple - A tuple of the same length as the event array used to initialize the - ``EpochsTFR`` object. If the i-th original event is still part of the - selection, drop_log[i] will be an empty tuple; otherwise it will be - a tuple of the reasons the event is not longer in the selection, e.g.: - - - ``'IGNORED'`` - If it isn't part of the current subset defined by the user - - ``'NO_DATA'`` or ``'TOO_SHORT'`` - If epoch didn't contain enough data names of channels that - exceeded the amplitude threshold - - ``'EQUALIZED_COUNTS'`` - See :meth:`~mne.Epochs.equalize_event_counts` - - ``'USER'`` - For user-defined reasons (see :meth:`~mne.Epochs.drop`). - - .. deprecated:: 1.7 - Pass an instance of :class:`~mne.Epochs` as ``inst`` instead, or use - :class:`~mne.time_frequency.EpochsTFRArray` which retains the old API. - %(metadata_epochstfr)s - - .. deprecated:: 1.7 - Pass an instance of :class:`~mne.Epochs` as ``inst`` instead, or use - :class:`~mne.time_frequency.EpochsTFRArray` which retains the old API. %(n_jobs)s %(verbose)s %(method_kw_tfr)s @@ -3031,11 +2986,6 @@ def __init__( picks=None, proj=False, decim=1, - events=None, - event_id=None, - selection=None, - drop_log=None, - metadata=None, n_jobs=None, verbose=None, **method_kw, diff --git a/mne/utils/linalg.py b/mne/utils/linalg.py index 4e4c9ba23c5..9382aad50f2 100644 --- a/mne/utils/linalg.py +++ b/mne/utils/linalg.py @@ -190,7 +190,7 @@ def _sym_mat_pow(A, power, rcond=1e-7, reduce_rank=False, return_s=False): return out -# SciPy deprecation of pinv + pinvh rcond (never worked properly anyway) +# SciPy pinv + pinvh rcond never worked properly anyway so we roll our own def pinvh(a, rtol=None): """Compute a pseudo-inverse of a Hermitian matrix. diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 7f8fb98b07a..73f0065a58d 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2917,11 +2917,9 @@ def _plot_and_correct(*, params, cut_coords): params["axes"].clear() if params.get("fig_anat") is not None and plot_kwargs["colorbar"]: params["fig_anat"]._cbar.ax.clear() - with warnings.catch_warnings(record=True): # nilearn bug; ax recreated - warnings.simplefilter("ignore", DeprecationWarning) - params["fig_anat"] = nil_func( - params["img_idx"], cut_coords=cut_coords, **plot_kwargs - ) + params["fig_anat"] = nil_func( + params["img_idx"], cut_coords=cut_coords, **plot_kwargs + ) params["fig_anat"]._cbar.outline.set_visible(False) for key in "xyz": params.update({"ax_" + key: params["fig_anat"].axes[key].ax}) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index c847da4486b..81b21cbef3c 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -991,7 +991,7 @@ def _set_size(self, width=None, height=None): # ------------------------------------ # Non-object-based Widget Abstractions # ------------------------------------ -# These are planned to be deprecated in favor of the simpler, object- +# These are planned to be removed in favor of the simpler, object- # oriented abstractions above when time allows. diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 38ff6784bc5..2a6a20278f7 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -18,7 +18,7 @@ Button, Checkbox, Dropdown, - # non-object-based-abstraction-only widgets, deprecate + # non-object-based-abstraction-only widgets, remove FloatSlider, GridBox, HBox, @@ -806,7 +806,7 @@ def show(self): # ------------------------------------ # Non-object-based Widget Abstractions # ------------------------------------ -# These are planned to be deprecated in favor of the simpler, object- +# These are planned to be removed in favor of the simpler, object- # oriented abstractions above when time allows. diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 620793ca47a..8fe4b4bf1d8 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -1096,9 +1096,7 @@ def _3d_to_2d(plotter, xyz): def _close_all(): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - close_all() + close_all() _FIGURES.clear() diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 259b8da5e1d..78b02a6d05b 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -25,7 +25,7 @@ QObject, Qt, QTimer, - # non-object-based-abstraction-only, deprecate + # non-object-based-abstraction-only, remove Signal, ) from qtpy.QtGui import QCursor, QIcon, QKeyEvent @@ -33,7 +33,7 @@ QButtonGroup, QCheckBox, QComboBox, - # non-object-based-abstraction-only, deprecate + # non-object-based-abstraction-only, remove QDockWidget, QDoubleSpinBox, QFileDialog, @@ -842,7 +842,7 @@ def _clean(self): # ------------------------------------ # Non-object-based Widget Abstractions # ------------------------------------ -# These are planned to be deprecated in favor of the simpler, object- +# These are planned to be removed in favor of the simpler, object- # oriented abstractions above when time allows. diff --git a/mne/viz/backends/tests/_utils.py b/mne/viz/backends/tests/_utils.py deleted file mode 100644 index 5e668a4dc75..00000000000 --- a/mne/viz/backends/tests/_utils.py +++ /dev/null @@ -1,44 +0,0 @@ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import warnings - -import pytest - - -def has_pyvista(): - """Check that PyVista is installed.""" - try: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - import pyvista # noqa: F401 - return True - except ImportError: - return False - - -def has_pyvistaqt(): - """Check that PyVistaQt is installed.""" - try: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - import pyvistaqt # noqa: F401 - return True - except ImportError: - return False - - -def has_imageio_ffmpeg(): - """Check if imageio-ffmpeg is installed.""" - try: - import imageio_ffmpeg # noqa: F401 - - return True - except ImportError: - return False - - -skips_if_not_pyvistaqt = pytest.mark.skipif( - not has_pyvistaqt(), reason="requires pyvistaqt" -) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 1e4b1dfb4c6..10ec5459e02 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -2009,7 +2009,7 @@ def plot_evoked_joint( from matplotlib import ticker cbar = fig.colorbar(map_ax[0].images[0], ax=map_ax, cax=cbar_ax, shrink=0.8) - cbar.ax.grid(False) # auto-removal deprecated as of 2021/10/05 + cbar.ax.grid(False) if isinstance(contours, list | np.ndarray): cbar.set_ticks(contours) else: diff --git a/mne/viz/montage.py b/mne/viz/montage.py index b7f1724cb87..221cc21f7a0 100644 --- a/mne/viz/montage.py +++ b/mne/viz/montage.py @@ -19,8 +19,7 @@ def plot_montage( montage, *, - scale=None, - scale_factor=None, + scale=1.0, show_names=True, kind="topomap", show=True, @@ -36,9 +35,7 @@ def plot_montage( The montage to visualize. scale : float Determines the scale of the channel points and labels; values < 1 will scale - down, whereas values > 1 will scale up. Default to None, which implies 1. - scale_factor : float - Determines the size of the points. Deprecated, use scale instead. + down, whereas values > 1 will scale up. show_names : bool | list Whether to display all channel names. If a list, only the channel names in the list are shown. Defaults to True. @@ -61,16 +58,7 @@ def plot_montage( from ..channels import DigMontage, make_dig_montage - if scale_factor is not None: - msg = "scale_factor has been deprecated and will be removed. Use scale instead." - if scale is not None: - raise ValueError( - " ".join(["scale and scale_factor cannot be used together.", msg]) - ) - logger.info(msg) - if scale is None: - scale = 1 - + _validate_type(scale, "numeric", "scale") _check_option("kind", kind, ["topomap", "3d"]) _validate_type(montage, DigMontage, item_name="montage") ch_names = montage.ch_names @@ -112,11 +100,7 @@ def plot_montage( axes=axes, ) - if scale_factor is not None: - # scale points - collection = fig.axes[0].collections[0] - collection.set_sizes([scale_factor]) - elif scale is not None: + if scale != 1.0: # scale points collection = fig.axes[0].collections[0] collection.set_sizes([scale * 10]) diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py index a2565927feb..f6b16fe27ad 100644 --- a/mne/viz/tests/test_raw.py +++ b/mne/viz/tests/test_raw.py @@ -970,7 +970,7 @@ def test_plot_raw_psd(raw, raw_orig): """Test plotting of raw psds.""" raw_unchanged = raw.copy() spectrum = raw.compute_psd() - # deprecation change handler + # change handler old_defaults = dict(picks="data", exclude="bads") fig = spectrum.plot(average=False, amplitude=False) # normal mode diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 147919a9c9d..dd63a626683 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -3456,11 +3456,9 @@ def _trigradient(x, y, z): """Take gradients of z on a mesh.""" from matplotlib.tri import CubicTriInterpolator, Triangulation - with warnings.catch_warnings(): # catch matplotlib warnings - warnings.filterwarnings("ignore", category=DeprecationWarning) - tri = Triangulation(x, y) - tci = CubicTriInterpolator(tri, z) - dx, dy = tci.gradient(tri.x, tri.y) + tri = Triangulation(x, y) + tci = CubicTriInterpolator(tri, z) + dx, dy = tci.gradient(tri.x, tri.y) return dx, dy diff --git a/pyproject.toml b/pyproject.toml index 363844769f6..ace765af47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ full-no-qt = [ "nilearn", "numba", "openmeeg >= 2.5.5", - "pandas", + "pandas", # >= 1.4 "pillow", # for `Brain.save_image` and `mne.Report` "pyarrow", # only needed to avoid a deprecation warning in pandas "pybv", diff --git a/tools/vulture_allowlist.py b/tools/vulture_allowlist.py index ce5e95124d4..5962dbe99ff 100644 --- a/tools/vulture_allowlist.py +++ b/tools/vulture_allowlist.py @@ -34,7 +34,6 @@ comments_new items_new has_imageio_ffmpeg -has_pyvista f4 set_channel_types_eyetrack _use_test_3d_backend From 64e402bd9d0ca530dc7c0dd50e09443cfd2e4bf9 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 14:48:01 -0500 Subject: [PATCH 02/14] FIX: Min --- mne/utils/dataframe.py | 15 +++------------ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/mne/utils/dataframe.py b/mne/utils/dataframe.py index 0511433001d..43c680727e2 100644 --- a/mne/utils/dataframe.py +++ b/mne/utils/dataframe.py @@ -4,8 +4,6 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -from inspect import signature - import numpy as np from ..defaults import _handle_default @@ -51,21 +49,14 @@ def _convert_times(times, time_format, meas_date=None, first_time=0): def _inplace(df, method, **kwargs): - # Handle transition: inplace=True (pandas <1.5) → copy=False (>=1.5) - # and 3.0 warning: - # E DeprecationWarning: The copy keyword is deprecated and will be removed in a - # future version. Copy-on-Write is active in pandas since 3.0 which utilizes a - # lazy copy mechanism that defers copies until necessary. Use .copy() to make - # an eager copy if necessary. _meth = getattr(df, method) # used for set_index() and rename() + # TODO: deprecation in Pandas 3.0, can remove once we require 3+ if check_version("pandas", "3.0"): return _meth(**kwargs) - elif "copy" in signature(_meth).parameters: - return _meth(**kwargs, copy=False) else: - _meth(**kwargs, inplace=True) - return df + # "copy" in signature(_meth).parameters + return _meth(**kwargs, copy=False) @verbose diff --git a/pyproject.toml b/pyproject.toml index ace765af47a..3626da6f3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ full-no-qt = [ "nilearn", "numba", "openmeeg >= 2.5.5", - "pandas", # >= 1.4 + "pandas", # >= 1.5 "pillow", # for `Brain.save_image` and `mne.Report` "pyarrow", # only needed to avoid a deprecation warning in pandas "pybv", From 5d839158e03d66db4e729a641e375a40f5d741db Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 14:57:35 -0500 Subject: [PATCH 03/14] FIX: Gone --- mne/viz/backends/tests/test_abstract.py | 2 -- mne/viz/backends/tests/test_renderer.py | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mne/viz/backends/tests/test_abstract.py b/mne/viz/backends/tests/test_abstract.py index bfb3663146a..365f4b70fd9 100644 --- a/mne/viz/backends/tests/test_abstract.py +++ b/mne/viz/backends/tests/test_abstract.py @@ -5,7 +5,6 @@ from pathlib import Path from mne.viz.backends.renderer import _get_backend -from mne.viz.backends.tests._utils import skips_if_not_pyvistaqt def _do_widget_tests(backend): @@ -106,7 +105,6 @@ def callback(x=None): window._close() -@skips_if_not_pyvistaqt def test_widget_abstraction_pyvistaqt(renderer_pyvistaqt): """Test the GUI widgets abstraction.""" backend = _get_backend() diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index 2c524f4145f..88506bae210 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -13,18 +13,19 @@ from mne.viz import Figure3D, get_3d_backend, set_3d_backend from mne.viz.backends._utils import ALLOWED_QUIVER_MODES from mne.viz.backends.renderer import _get_renderer -from mne.viz.backends.tests._utils import skips_if_not_pyvistaqt @pytest.mark.parametrize( "backend", [ - pytest.param("pyvistaqt", marks=skips_if_not_pyvistaqt), + pytest.param("pyvistaqt"), pytest.param("foo", marks=pytest.mark.xfail(raises=ValueError)), ], ) def test_backend_environment_setup(backend, monkeypatch): """Test set up 3d backend based on env.""" + if backend == "pyvistaqt": + pytest.importorskip("pyvistaqt") monkeypatch.setenv("MNE_3D_BACKEND", backend) monkeypatch.setattr("mne.viz.backends.renderer.MNE_3D_BACKEND", None) assert os.environ["MNE_3D_BACKEND"] == backend # just double-check From dbf74c12c3e6a05a75403b9eb9cde7f0d559e68a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 15:05:23 -0500 Subject: [PATCH 04/14] FIX: Gone --- mne/conftest.py | 6 ++---- tools/vulture_allowlist.py | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mne/conftest.py b/mne/conftest.py index 9896b73a368..fc3bc3b7a53 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -637,15 +637,13 @@ def _use_backend(backend_name, interactive): def _check_skip_backend(name): from mne.viz.backends._utils import _notebook_vtk_works - from mne.viz.backends.tests._utils import has_imageio_ffmpeg pytest.importorskip("pyvista") - if not has_imageio_ffmpeg(): - pytest.skip("Test skipped, requires imageio-ffmpeg") + pytest.importorskip("imageio_ffmpeg") if name == "pyvistaqt": + pytest.importorskip("pyvistaqt") if not _check_qt_version(): pytest.skip("Test skipped, requires Qt.") - pytest.importorskip("pyvistaqt") else: assert name == "notebook", name if not _notebook_vtk_works(): diff --git a/tools/vulture_allowlist.py b/tools/vulture_allowlist.py index 5962dbe99ff..24bcd9af64a 100644 --- a/tools/vulture_allowlist.py +++ b/tools/vulture_allowlist.py @@ -33,7 +33,6 @@ captions_new comments_new items_new -has_imageio_ffmpeg f4 set_channel_types_eyetrack _use_test_3d_backend From 1449a7dca8511102c9a14fcabc42c0f9702fd58f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 15:19:07 -0500 Subject: [PATCH 05/14] FIX: Missed --- mne/channels/montage.py | 4 +--- mne/decoding/csp.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/mne/channels/montage.py b/mne/channels/montage.py index a6ded682de9..87550d66807 100644 --- a/mne/channels/montage.py +++ b/mne/channels/montage.py @@ -361,8 +361,7 @@ def __repr__(self): def plot( self, *, - scale=None, - scale_factor=None, + scale=1.0, show_names=True, kind="topomap", show=True, @@ -373,7 +372,6 @@ def plot( return plot_montage( self, scale=scale, - scale_factor=scale_factor, show_names=show_names, kind=kind, show=show, diff --git a/mne/decoding/csp.py b/mne/decoding/csp.py index c277815fa26..9e12335cdbe 100644 --- a/mne/decoding/csp.py +++ b/mne/decoding/csp.py @@ -468,7 +468,6 @@ def plot_filters( :func:`mne.create_info`. components : float | array of float | None The patterns to plot. If ``None``, all components will be shown. - %(average_plot_evoked_topomap)s %(ch_type_topomap)s scalings : dict | float | None The scalings of the channel types to be applied for plotting. From 5905947117e07e89175d70a7b1443702d0098e1f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 15:34:08 -0500 Subject: [PATCH 06/14] FIX: Why --- tools/hooks/update_environment_file.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index 78df3eb5f9c..6422699a8f2 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -4,6 +4,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import difflib import re from pathlib import Path @@ -86,4 +87,9 @@ def split_dep(dep): {newline.join(sorted(conda_deps, key=str.casefold))} {pip_section}""" # noqa: E501 -(repo_root / "environment.yml").write_text(env) +env_file = repo_root / "environment.yml" +old_env = env_file.read_text("utf-8") +if old_env != env: + diff = "\n".join(difflib.unified_diff(old_env.splitlines(), env.splitlines())) + env_file.write_text(env, encoding="utf-8") + print(f"Updated {env_file} with diff:\n{diff}") From 4ed62e9d7848b0663563e04a8e0f295c2989783f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 15:34:41 -0500 Subject: [PATCH 07/14] FIX: Text --- tools/hooks/update_environment_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index 6422699a8f2..b88308e20db 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -91,5 +91,5 @@ def split_dep(dep): old_env = env_file.read_text("utf-8") if old_env != env: diff = "\n".join(difflib.unified_diff(old_env.splitlines(), env.splitlines())) + print(f"Updating {env_file} with diff:\n{diff}") env_file.write_text(env, encoding="utf-8") - print(f"Updated {env_file} with diff:\n{diff}") From 7d585c911705600fc5f226420674bcc0cace7c3d Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 19 Dec 2024 16:58:32 -0500 Subject: [PATCH 08/14] FIX: Remove in version --- mne/datasets/eegbci/tests/test_eegbci.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/mne/datasets/eegbci/tests/test_eegbci.py b/mne/datasets/eegbci/tests/test_eegbci.py index 40ef5ee030f..e9f63fee288 100644 --- a/mne/datasets/eegbci/tests/test_eegbci.py +++ b/mne/datasets/eegbci/tests/test_eegbci.py @@ -2,7 +2,6 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import pytest from mne.datasets import eegbci @@ -14,14 +13,3 @@ def test_eegbci_download(tmp_path, fake_retrieve): fnames = eegbci.load_data(subj, runs=[3], path=tmp_path, update_path=False) assert len(fnames) == 1, subj assert fake_retrieve.call_count == 4 - - # XXX: remove in version 1.9 - with pytest.warns(FutureWarning, match="The ``subject``"): - fnames = eegbci.load_data( - subject=subjects, runs=[3], path=tmp_path, update_path=False - ) - assert len(fnames) == 4 - - # XXX: remove in version 1.9 - with pytest.raises(ValueError, match="You must pass the parameters"): - fnames = eegbci.load_data(path=tmp_path, update_path=False) From 323d48b98d703988d88b508e297219d7302cf4b7 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 20 Dec 2024 10:50:45 -0500 Subject: [PATCH 09/14] FIX: Hook --- environment.yml | 8 +++---- pyproject.toml | 8 +++---- tools/environment_old.yml | 19 ++++++++-------- tools/hooks/update_environment_file.py | 31 +++++++++++++++++++++++--- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/environment.yml b/environment.yml index 898132bc0a4..172a5ed9748 100644 --- a/environment.yml +++ b/environment.yml @@ -23,16 +23,16 @@ dependencies: - joblib - jupyter - lazy_loader >=0.3 - - matplotlib >=3.6 + - matplotlib >=3.7 - mffpy >=0.5.7 - mne-qt-browser - nibabel - nilearn - numba - - numpy >=1.23,<3 + - numpy >=1.25,<3 - openmeeg >=2.5.5 - packaging - - pandas + - pandas >=2.0 - pillow - pip - pooch >=1.5 @@ -47,7 +47,7 @@ dependencies: - qdarkstyle !=3.2.2 - qtpy - scikit-learn - - scipy >=1.9 + - scipy >=1.11.1 - sip - snirf - statsmodels diff --git a/pyproject.toml b/pyproject.toml index 3626da6f3df..dd29118fb3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,11 +23,11 @@ dependencies = [ "decorator", "jinja2", "lazy_loader >= 0.3", - "matplotlib >= 3.6", - "numpy >= 1.23,<3", + "matplotlib >= 3.7", # 2023/02/13 + "numpy >= 1.25,<3", # 2023/06/17 "packaging", "pooch >= 1.5", - "scipy >= 1.9", + "scipy >= 1.11.1", # 2023/06/25 "tqdm", ] description = "MNE-Python project for MEG and EEG data analysis." @@ -111,7 +111,7 @@ full-no-qt = [ "nilearn", "numba", "openmeeg >= 2.5.5", - "pandas", # >= 1.5 + "pandas >= 2.0", # 2023/04/03 "pillow", # for `Brain.save_image` and `mne.Report` "pyarrow", # only needed to avoid a deprecation warning in pandas "pybv", diff --git a/tools/environment_old.yml b/tools/environment_old.yml index 4515f9cd611..de79d26eed6 100644 --- a/tools/environment_old.yml +++ b/tools/environment_old.yml @@ -1,17 +1,18 @@ +# THIS FILE IS AUTO-GENERATED BY tools/hooks/update_environment_file.py AND WILL BE OVERWRITTEN name: mne channels: - conda-forge dependencies: - - python=3.10 - - numpy=1.24 - - scipy=1.10 - - matplotlib=3.6 - - pandas=1.5.2 - - scikit-learn=1.2 - - nibabel # whichever one works + - python =3.10 + - numpy =1.25 + - scipy =1.11.1 + - matplotlib =3.7 + - pandas =2.0 + - scikit-learn + - nibabel - tqdm - - pooch + - pooch =1.5 - decorator - packaging - jinja2 - - lazy_loader + - lazy_loader =0.3 diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index b88308e20db..98fbe530267 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -49,6 +49,11 @@ def split_dep(dep): translations = dict(neo="python-neo") pip_deps = set() conda_deps = set() +check_old = ( + "numpy scipy matplotlib pandas scikit-learn nibabel tqdm pooch decorator " + "packaging jinja2 lazy_loader" +).split() +old_deps = [None] * len(check_old) for dep in deps: package_name, version_spec = split_dep(dep) # handle package name differences @@ -64,6 +69,12 @@ def split_dep(dep): pip_deps.add(f" {line}") else: conda_deps.add(line) + # old deps + if package_name in check_old: + # Pull out >= part, change to =, remove < (which should be after comma) + old_deps[check_old.index(package_name)] = line.replace(">=", "=").split(",")[0] +for di, dep in enumerate(old_deps): + assert dep is not None, f"Missing {check_old[di]}" # TODO: temporary workaround while we wait for a release containing the fix for # https://github.com/mamba-org/mamba/issues/3467 @@ -77,15 +88,16 @@ def split_dep(dep): """ pip_section = pip_section if len(pip_deps) else "" # prepare the env file -env = f"""\ +header = f"""\ # THIS FILE IS AUTO-GENERATED BY {'/'.join(Path(__file__).parts[-3:])} AND WILL BE OVERWRITTEN name: mne channels: - conda-forge -dependencies: +dependencies:""" # noqa: E501 +env = f"""{header} - python {req_python} {newline.join(sorted(conda_deps, key=str.casefold))} -{pip_section}""" # noqa: E501 +{pip_section}""" env_file = repo_root / "environment.yml" old_env = env_file.read_text("utf-8") @@ -93,3 +105,16 @@ def split_dep(dep): diff = "\n".join(difflib.unified_diff(old_env.splitlines(), env.splitlines())) print(f"Updating {env_file} with diff:\n{diff}") env_file.write_text(env, encoding="utf-8") + +# Now we also updated tools/environment_old.yml +env_file = repo_root / "tools" / "environment_old.yml" +old_env = env_file.read_text("utf-8") +use_python = req_python.replace(">=", "=") +env = f"""{header} + - python {use_python} +{newline.join(old_deps)} +""" +if old_env != env: + diff = "\n".join(difflib.unified_diff(old_env.splitlines(), env.splitlines())) + print(f"Updating {env_file} with diff:\n{diff}") + env_file.write_text(env, encoding="utf-8") From ae14a5e06fee974e525732cab25112948c5becc3 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 20 Dec 2024 16:51:30 -0500 Subject: [PATCH 10/14] FIX: Some --- environment.yml | 2 +- mne/commands/mne_flash_bem.py | 20 -------------------- mne/report/report.py | 1 + mne/time_frequency/tests/test_spectrum.py | 4 +--- mne/utils/dataframe.py | 20 ++++---------------- pyproject.toml | 2 +- tools/environment_old.yml | 2 +- 7 files changed, 9 insertions(+), 42 deletions(-) diff --git a/environment.yml b/environment.yml index 172a5ed9748..4dc45788af1 100644 --- a/environment.yml +++ b/environment.yml @@ -47,7 +47,7 @@ dependencies: - qdarkstyle !=3.2.2 - qtpy - scikit-learn - - scipy >=1.11.1 + - scipy >=1.11 - sip - snirf - statsmodels diff --git a/mne/commands/mne_flash_bem.py b/mne/commands/mne_flash_bem.py index b6c7a1b795d..63bcb79d9d8 100644 --- a/mne/commands/mne_flash_bem.py +++ b/mne/commands/mne_flash_bem.py @@ -95,17 +95,6 @@ def run(): "been registered with the T1.mgz file." ), ) - parser.add_option( - "-n", - "--noconvert", - dest="noconvert", - action="store_true", - default=False, - help=( - "[DEPRECATED] Assume that the Flash MRI images " - "have already been converted to mgz files" - ), - ) parser.add_option( "-u", "--unwarp", @@ -139,15 +128,6 @@ def run(): help="Use copies instead of symlinks for surfaces", action="store_true", ) - parser.add_option( - "-p", - "--flash-path", - dest="flash_path", - default=None, - help="[DEPRECATED] The directory containing flash5.mgz " - "files (defaults to " - "$SUBJECTS_DIR/$SUBJECT/mri/flash/parameter_maps", - ) options, _ = parser.parse_args() diff --git a/mne/report/report.py b/mne/report/report.py index 7cab9774c8e..732c1a5c8b3 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -3415,6 +3415,7 @@ def _add_raw( init_kwargs.setdefault("fmax", fmax) plot_kwargs.setdefault("show", False) with warnings.catch_warnings(): + # SciPy warning about too short a data segment given the window size warnings.simplefilter(action="ignore", category=FutureWarning) fig = raw.compute_psd(**init_kwargs).plot(**plot_kwargs) self._add_figure( diff --git a/mne/time_frequency/tests/test_spectrum.py b/mne/time_frequency/tests/test_spectrum.py index 2f612357e70..69c8a7713d4 100644 --- a/mne/time_frequency/tests/test_spectrum.py +++ b/mne/time_frequency/tests/test_spectrum.py @@ -306,8 +306,6 @@ def test_unaggregated_spectrum_to_data_frame(raw, long_format, method, output): pytest.importorskip("pandas") from pandas.testing import assert_frame_equal - from mne.utils.dataframe import _inplace - # aggregated spectrum → dataframe orig_df = raw.compute_psd(method=method).to_data_frame(long_format=long_format) # unaggregated welch or complex multitaper → @@ -329,7 +327,7 @@ def test_unaggregated_spectrum_to_data_frame(raw, long_format, method, output): orig_df = orig_df.loc[subset(orig_df["freq"])] # sort orig_df, because at present we can't actually prevent pandas from # sorting at the agg step *sigh* - _inplace(orig_df, "sort_values", by=grouping_cols, ignore_index=True) + orig_df.sort_values(by=grouping_cols, ignore_index=True) # aggregate df = df.drop(columns=drop_cols) gb = df.groupby(grouping_cols, as_index=False, observed=False) diff --git a/mne/utils/dataframe.py b/mne/utils/dataframe.py index 43c680727e2..3d72399382a 100644 --- a/mne/utils/dataframe.py +++ b/mne/utils/dataframe.py @@ -8,7 +8,6 @@ from ..defaults import _handle_default from ._logging import logger, verbose -from .check import check_version @verbose @@ -48,17 +47,6 @@ def _convert_times(times, time_format, meas_date=None, first_time=0): return times -def _inplace(df, method, **kwargs): - _meth = getattr(df, method) # used for set_index() and rename() - - # TODO: deprecation in Pandas 3.0, can remove once we require 3+ - if check_version("pandas", "3.0"): - return _meth(**kwargs) - else: - # "copy" in signature(_meth).parameters - return _meth(**kwargs, copy=False) - - @verbose def _build_data_frame( inst, @@ -86,16 +74,16 @@ def _build_data_frame( df.insert(i, k, v) # build Index if long_format: - df = _inplace(df, "set_index", keys=default_index) + df.set_index(keys=default_index) df.columns.name = col_kind elif index is not None: - df = _inplace(df, "set_index", keys=index) + df.set_index(keys=index) if set(index) == set(default_index): df.columns.name = col_kind # long format if long_format: df = df.stack().reset_index() - df = _inplace(df, "rename", columns={0: "value"}) + df.rename(columns={0: "value"}) # add column for channel types (as appropriate) ch_map = ( None @@ -113,7 +101,7 @@ def _build_data_frame( df.insert(col_index, "ch_type", ch_type) # restore index if index is not None: - df = _inplace(df, "set_index", keys=index) + df.set_index(keys=index) # convert channel/vertex/ch_type columns to factors to_factor = [ c for c in df.columns.tolist() if c not in ("freq", "time", "value") diff --git a/pyproject.toml b/pyproject.toml index dd29118fb3f..1447b44f644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "numpy >= 1.25,<3", # 2023/06/17 "packaging", "pooch >= 1.5", - "scipy >= 1.11.1", # 2023/06/25 + "scipy >= 1.11", # 2023/06/25 "tqdm", ] description = "MNE-Python project for MEG and EEG data analysis." diff --git a/tools/environment_old.yml b/tools/environment_old.yml index de79d26eed6..46826bdbcf4 100644 --- a/tools/environment_old.yml +++ b/tools/environment_old.yml @@ -5,7 +5,7 @@ channels: dependencies: - python =3.10 - numpy =1.25 - - scipy =1.11.1 + - scipy =1.11 - matplotlib =3.7 - pandas =2.0 - scikit-learn From 94b8195185ef0fe198f9b55c7eab24710f4a5430 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 20 Dec 2024 17:40:17 -0500 Subject: [PATCH 11/14] FIX: Revert --- mne/time_frequency/tests/test_spectrum.py | 4 +++- mne/utils/dataframe.py | 29 +++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/mne/time_frequency/tests/test_spectrum.py b/mne/time_frequency/tests/test_spectrum.py index 69c8a7713d4..2f612357e70 100644 --- a/mne/time_frequency/tests/test_spectrum.py +++ b/mne/time_frequency/tests/test_spectrum.py @@ -306,6 +306,8 @@ def test_unaggregated_spectrum_to_data_frame(raw, long_format, method, output): pytest.importorskip("pandas") from pandas.testing import assert_frame_equal + from mne.utils.dataframe import _inplace + # aggregated spectrum → dataframe orig_df = raw.compute_psd(method=method).to_data_frame(long_format=long_format) # unaggregated welch or complex multitaper → @@ -327,7 +329,7 @@ def test_unaggregated_spectrum_to_data_frame(raw, long_format, method, output): orig_df = orig_df.loc[subset(orig_df["freq"])] # sort orig_df, because at present we can't actually prevent pandas from # sorting at the agg step *sigh* - orig_df.sort_values(by=grouping_cols, ignore_index=True) + _inplace(orig_df, "sort_values", by=grouping_cols, ignore_index=True) # aggregate df = df.drop(columns=drop_cols) gb = df.groupby(grouping_cols, as_index=False, observed=False) diff --git a/mne/utils/dataframe.py b/mne/utils/dataframe.py index 3d72399382a..0511433001d 100644 --- a/mne/utils/dataframe.py +++ b/mne/utils/dataframe.py @@ -4,10 +4,13 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +from inspect import signature + import numpy as np from ..defaults import _handle_default from ._logging import logger, verbose +from .check import check_version @verbose @@ -47,6 +50,24 @@ def _convert_times(times, time_format, meas_date=None, first_time=0): return times +def _inplace(df, method, **kwargs): + # Handle transition: inplace=True (pandas <1.5) → copy=False (>=1.5) + # and 3.0 warning: + # E DeprecationWarning: The copy keyword is deprecated and will be removed in a + # future version. Copy-on-Write is active in pandas since 3.0 which utilizes a + # lazy copy mechanism that defers copies until necessary. Use .copy() to make + # an eager copy if necessary. + _meth = getattr(df, method) # used for set_index() and rename() + + if check_version("pandas", "3.0"): + return _meth(**kwargs) + elif "copy" in signature(_meth).parameters: + return _meth(**kwargs, copy=False) + else: + _meth(**kwargs, inplace=True) + return df + + @verbose def _build_data_frame( inst, @@ -74,16 +95,16 @@ def _build_data_frame( df.insert(i, k, v) # build Index if long_format: - df.set_index(keys=default_index) + df = _inplace(df, "set_index", keys=default_index) df.columns.name = col_kind elif index is not None: - df.set_index(keys=index) + df = _inplace(df, "set_index", keys=index) if set(index) == set(default_index): df.columns.name = col_kind # long format if long_format: df = df.stack().reset_index() - df.rename(columns={0: "value"}) + df = _inplace(df, "rename", columns={0: "value"}) # add column for channel types (as appropriate) ch_map = ( None @@ -101,7 +122,7 @@ def _build_data_frame( df.insert(col_index, "ch_type", ch_type) # restore index if index is not None: - df.set_index(keys=index) + df = _inplace(df, "set_index", keys=index) # convert channel/vertex/ch_type columns to factors to_factor = [ c for c in df.columns.tolist() if c not in ("freq", "time", "value") From 2fd67741342e122c689ea731af38e1ea1c53869d Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 20 Dec 2024 19:59:33 -0500 Subject: [PATCH 12/14] FIX: When --- tools/hooks/sync_dependencies.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/hooks/sync_dependencies.py b/tools/hooks/sync_dependencies.py index 0878a5f56eb..baba1643d72 100755 --- a/tools/hooks/sync_dependencies.py +++ b/tools/hooks/sync_dependencies.py @@ -4,6 +4,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import difflib import re from importlib.metadata import metadata from pathlib import Path @@ -92,4 +93,9 @@ def _prettify_pin(pin): skip = False if not skip: out_lines.append(line) -README_PATH.write_text("\n".join(out_lines) + "\n", encoding="utf-8") +new = "\n".join(out_lines) + "\n" +old = README_PATH.read_text("utf-8") +if new != old: + diff = "\n".join(difflib.unified_diff(old.splitlines(), new.splitlines())) + print(f"Updating {README_PATH} with diff:\n{diff}") + README_PATH.write_text(new, encoding="utf-8") From ea26fc690e588aa99550dfd0969e118cb9c188f8 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Sat, 21 Dec 2024 13:12:15 -0500 Subject: [PATCH 13/14] FIX: Version --- .pre-commit-config.yaml | 2 +- README.rst | 2 +- tools/dev/Makefile | 3 +++ tools/hooks/sync_dependencies.py | 4 ++++ tools/hooks/update_environment_file.py | 3 +-- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e7a931b5713..06918b99de1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -78,7 +78,7 @@ repos: language: python entry: ./tools/hooks/sync_dependencies.py files: pyproject.toml - additional_dependencies: ["mne"] + additional_dependencies: ["mne==1.9.0"] # zizmor - repo: https://github.com/woodruffw/zizmor-pre-commit diff --git a/README.rst b/README.rst index 50e0daaa52c..cfa7488324f 100644 --- a/README.rst +++ b/README.rst @@ -72,7 +72,7 @@ The minimum required dependencies to run MNE-Python are: .. ↓↓↓ BEGIN CORE DEPS LIST. DO NOT EDIT! HANDLED BY PRE-COMMIT HOOK ↓↓↓ -- `Python `__ ≥ 3.9 +- `Python `__ ≥ 3.10 - `NumPy `__ ≥ 1.23 - `SciPy `__ ≥ 1.9 - `Matplotlib `__ ≥ 3.6 diff --git a/tools/dev/Makefile b/tools/dev/Makefile index e2f61735a7e..9128d758289 100644 --- a/tools/dev/Makefile +++ b/tools/dev/Makefile @@ -25,3 +25,6 @@ feature-requests-12-to-2-months-old.json: clean: @rm bug-reports-12-to-2-months-old.json || true + +dep: + cd ../../ && git grep -iI "\(deprecat\|futurewarning\)" -- ':!*.js' ':!*/conftest.py' ':!*/docs.py' ':!*/test_docs.py' ':!*/utils/__init__.pyi' mne/ diff --git a/tools/hooks/sync_dependencies.py b/tools/hooks/sync_dependencies.py index baba1643d72..a07cc633b3c 100755 --- a/tools/hooks/sync_dependencies.py +++ b/tools/hooks/sync_dependencies.py @@ -6,6 +6,10 @@ import difflib import re + +# NB here we use metadata from the latest stable release because this goes in our +# README, which should apply to the latest release (rather than dev). +# For oldest supported dev dependencies, see update_environment_file.py. from importlib.metadata import metadata from pathlib import Path diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index 98fbe530267..8cac6193959 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -11,8 +11,7 @@ import tomllib repo_root = Path(__file__).resolve().parents[2] -with open(repo_root / "pyproject.toml", "rb") as fid: - pyproj = tomllib.load(fid) +pyproj = tomllib.loads((repo_root / "pyproject.toml").read_text("utf-8")) # Get our "full" dependences from `pyproject.toml`, but actually ignore the # "full" section as it's just "full-noqt" plus PyQt6, and for conda we need PySide From 5b663be76d052b92b5e03595bd3103e5e285702f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Sat, 21 Dec 2024 14:02:14 -0500 Subject: [PATCH 14/14] FIX: Remove --- mne/commands/tests/test_commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/commands/tests/test_commands.py b/mne/commands/tests/test_commands.py index ae885d3c21d..30a625e9578 100644 --- a/mne/commands/tests/test_commands.py +++ b/mne/commands/tests/test_commands.py @@ -334,7 +334,7 @@ def test_flash_bem(tmp_path): # First test without flash30 with ArgvSetter( - ("-d", tempdir, "-s", "sample", "-n", "-r", "-3"), + ("-d", tempdir, "-s", "sample", "-r", "-3"), disable_stdout=False, disable_stderr=False, ): @@ -361,7 +361,6 @@ def test_flash_bem(tmp_path): tempdir, "-s", "sample", - "-n", "-3", str(mridata_path / "flash" / "mef30.mgz"), "-5",