diff --git a/.circleci/config.yml b/.circleci/config.yml index c0780cd047f..d032e40887d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -131,7 +131,8 @@ jobs: - run: name: Check Qt command: | - ./tools/check_qt_import.sh PyQt6 + set -x + ./tools/check_qt_import.sh ${MNE_QT_BACKEND} # Load tiny cache so that ~/.mne does not need to be created below - restore_cache: keys: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f8e812ecc3e..7e273605ff9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,34 +38,27 @@ stages: - job: All pool: vmImage: 'ubuntu-latest' - variables: - PYTHON_VERSION: '3.11' - PYTHON_ARCH: 'x64' steps: - - bash: echo $(COMMIT_MSG) - task: UsePythonVersion@0 inputs: - versionSpec: $(PYTHON_VERSION) - architecture: $(PYTHON_ARCH) + versionSpec: '3.14' + architecture: 'x64' addToPath: true - displayName: 'Get Python' + displayName: Get Python - bash: | set -eo pipefail python -m pip install --progress-bar off --upgrade pip build - python -m pip install --progress-bar off -ve .[hdf5] --group=test + python -m pip install --only-binary=":all:" --progress-bar off -ve .[hdf5] --group=test python -m pip uninstall -yq pytest-qt # don't want to set up display, etc. for this pre-commit install --install-hooks displayName: Install dependencies - - bash: | - make pre-commit + - bash: make pre-commit displayName: make pre-commit condition: always() - - bash: | - make nesting + - bash: make nesting displayName: make nesting condition: always() - - bash: | - make check-readme + - bash: make check-readme displayName: make check-readme condition: always() - bash: mypy @@ -75,7 +68,6 @@ stages: displayName: vulture condition: always() - - stage: Test condition: and(succeeded(), eq(dependencies.Check.outputs['Skip.result.start_main'], 'true')) dependsOn: ['Style', 'Check'] @@ -89,57 +81,50 @@ stages: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' OMP_NUM_THREADS: '1' - MNE_TEST_ALLOW_SKIP: '^.*(PySide6 causes segfaults).*$' + MNE_TEST_ALLOW_SKIP: '^$' # nothing MNE_BROWSER_PRECOMPUTE: 'false' steps: - bash: | - set -eo pipefail + set -xeo pipefail ./tools/setup_xvfb.sh sudo apt install -yq tcsh - displayName: 'Install Ubuntu dependencies' - - bash: | - source tools/get_minimal_commands.sh - displayName: 'Install minimal commands' + displayName: Install Ubuntu dependencies + - bash: ./tools/get_minimal_commands.sh + displayName: Get minimal commands - bash: | echo $PATH mne_surf2bem --version fsl_rigid_register --version - displayName: 'Test minimal commands' + displayName: Test minimal commands - task: UsePythonVersion@0 inputs: versionSpec: '3.12' architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef - python -m pip uninstall -yq mne - python -m pip install --progress-bar off --upgrade -e . --group=test - displayName: 'Install dependencies with pip' + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef + displayName: Install dependencies with pip + - bash: ./tools/check_qt_import.sh PySide6 + displayName: Check Qt import - bash: | - set -eo pipefail + set -xeo pipefail mne sys_info -pd mne sys_info -pd | grep "qtpy .*(PySide6=.*)$" displayName: Print config - - bash: | - set -eo pipefail - LD_DEBUG=libs python -c "from PySide6.QtWidgets import QApplication, QWidget; app = QApplication([]); import matplotlib; matplotlib.use('QtAgg'); import matplotlib.pyplot as plt; plt.figure()" - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh + displayName: Get testing version - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - displayName: 'Cache testing data' - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' + displayName: Download testing data - script: pytest -m "ultraslowtest or pgtest" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'slow and mne-qt-browser tests' - # Coverage + displayName: slow and mne-qt-browser tests - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() - job: Linux_Qt_Bindings @@ -148,59 +133,54 @@ stages: variables: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' - TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/gui mne/report" - MNE_TEST_ALLOW_SKIP: '^.*(PySide6 causes segfaults).*$' + TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/report" + MNE_TEST_ALLOW_SKIP: '^$' # nothing (can be overridden below) steps: - bash: ./tools/setup_xvfb.sh - displayName: 'Install Ubuntu dependencies' + displayName: Setup up Xvfb - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.14' architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off --upgrade --pre --only-binary=\"numpy,scipy,matplotlib,vtk\" numpy scipy matplotlib vtk + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e .[full-pyside6] --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" python -c "import vtk" - python -m pip install --progress-bar off --upgrade -ve .[full] --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" - displayName: 'Install dependencies with pip' + displayName: Install dependencies with pip - bash: | - set -e + set -xeo pipefail which mne mne sys_info -pd python ./tools/check_mne_location.py displayName: Print config - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh + displayName: Get testing version - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - displayName: 'Cache testing data' - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' + displayName: Download testing data + - bash: ./tools/check_qt_import.sh PySide6 + displayName: Check Qt import - bash: | - set -eo pipefail - python -m pip install PyQt6 - LD_DEBUG=libs python -c "from PyQt6.QtWidgets import QApplication, QWidget; app = QApplication([]); import matplotlib; matplotlib.use('QtAgg'); import matplotlib.pyplot as plt; plt.figure()" - displayName: 'Check Qt import' + set -xeo pipefail + mne sys_info -pd + mne sys_info -pd | grep "qtpy .* (PySide6=.*)$" + export MNE_TEST_ALLOW_SKIP="^.*PySide6 causes segfaults.*$" + PYTEST_QT_API=PySide6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} + python -m pip uninstall -yq PySide6 + displayName: PySide6 - bash: | - set -eo pipefail + set -xeo pipefail + python -m pip install PyQt6 mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" PYTEST_QT_API=PyQt6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PyQt6 PyQt6-sip PyQt6-Qt6 - displayName: 'PyQt6' - - bash: | - set -eo pipefail - python -m pip install "PySide6!=6.8.0,!=6.8.0.1,!=6.9.1" - mne sys_info -pd - mne sys_info -pd | grep "qtpy .* (PySide6=.*)$" - PYTEST_QT_API=PySide6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} - python -m pip uninstall -yq PySide6 - displayName: 'PySide6' + displayName: PyQt6 # PyQt5 leaves cruft behind, so run it last - bash: | set -eo pipefail @@ -209,10 +189,9 @@ stages: mne sys_info -pd | grep "qtpy .* (PyQt5=.*)$" PYTEST_QT_API=PyQt5 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PyQt5 PyQt5-sip PyQt5-Qt5 - displayName: 'PyQt5' - # Coverage + displayName: PyQt5 - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() - job: Windows @@ -224,10 +203,9 @@ stages: OPENBLAS_NUM_THREADS: '2' OMP_DYNAMIC: 'false' PYTHONUNBUFFERED: 1 - PYTHONIOENCODING: 'utf-8' AZURE_CI_WINDOWS: 'true' - PYTHON_ARCH: 'x64' MNE_CI_KIND: $(TEST_MODE) + MNE_TEST_ALLOW_SKIP: $(MNE_TEST_ALLOW_SKIP) timeoutInMinutes: 95 strategy: maxParallel: 4 @@ -235,42 +213,41 @@ stages: 3.11 pip: TEST_MODE: 'pip' PYTHON_VERSION: '3.11' + MNE_TEST_ALLOW_SKIP: "^.*(Requires MNE-C|CUDA not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*$" 3.13 pip pre: TEST_MODE: 'pip-pre' PYTHON_VERSION: '3.13' + MNE_TEST_ALLOW_SKIP: "^.*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*$" steps: - task: UsePythonVersion@0 inputs: versionSpec: $(PYTHON_VERSION) - architecture: $(PYTHON_ARCH) + architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail git clone --depth 1 https://github.com/pyvista/setup-headless-display-action.git MESA3D_VERSION=24.3.0 bash setup-headless-display-action/windows/install_opengl.sh displayName: Install OpenGL - bash: ./tools/azure_dependencies.sh - displayName: Install dependencies with pip - - script: pip install -e . - displayName: 'Install MNE-Python dev' + - script: pip install --only-binary=":all:" -e . + displayName: Install mne - script: mne sys_info -pd - displayName: 'Print config' + displayName: Print config - script: python -c "import numpy; numpy.show_config()" displayName: Print NumPy config - script: python -c "import numpy; import scipy.linalg; import sklearn.neighbors; from threadpoolctl import threadpool_info; from pprint import pprint; pprint(threadpool_info())" displayName: Print threadpoolctl info - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh - task: Cache@2 inputs: key: $(testing_version) path: C:\Users\VssAdministrator\mne_data - displayName: 'Cache testing data' + displayName: Cache testing data - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' + displayName: Download testing data - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'Run tests' + displayName: Run tests - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() diff --git a/doc/sphinxext/related_software.txt b/doc/sphinxext/related_software.txt index 2c38c82c089..fea75e73029 100644 --- a/doc/sphinxext/related_software.txt +++ b/doc/sphinxext/related_software.txt @@ -3,12 +3,12 @@ alphaCSC autoreject bycycle -conpy curryreader dcm2niix eeg_positions emd fooof +hedtools meegkit meggie mne-ari diff --git a/doc/sphinxext/related_software_nodeps.txt b/doc/sphinxext/related_software_nodeps.txt index 2b2e0c68945..e9ab63687dc 100644 --- a/doc/sphinxext/related_software_nodeps.txt +++ b/doc/sphinxext/related_software_nodeps.txt @@ -1,7 +1,7 @@ -# deps with onerous requirements (tensorflow, wxPython, mayavi, opencv) -cross-domain-saliency-maps -fsleyes -mne-kit-gui -mne-videobrowser -hedtools # because it limits our Pandas version currently +# deps with onerous requirements +conpy # requires future and subprocess32, which don't have wheels +cross-domain-saliency-maps # tensorflow +fsleyes # wxPython +mne-kit-gui # mayavi +mne-videobrowser # opencv zuna # requires pytorch diff --git a/mne/conftest.py b/mne/conftest.py index 2c2ee028b79..2ef937c7bd8 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -47,7 +47,6 @@ numerics, ) from mne.viz._figure import use_browser_backend -from mne.viz.backends._utils import _init_mne_qtapp # data from sample dataset test_path = testing.data_path(download=False) @@ -96,7 +95,6 @@ def pytest_configure(config: pytest.Config): "ultraslowtest: mark a test as ultraslow or to be run rarely", "pgtest: mark a test as relevant for mne-qt-browser", "pvtest: mark a test as relevant for pyvistaqt", - "allow_unclosed: allow unclosed pyvistaqt instances", ): config.addinivalue_line("markers", marker) @@ -751,7 +749,7 @@ def _check_skip_backend(name): @pytest.fixture(scope="session") -def pixel_ratio(): +def pixel_ratio(qapp): """Get the pixel ratio.""" # _check_qt_version will init an app for us, so no need for us to do it if not check_version("pyvista", "0.32") or not _check_qt_version(): @@ -759,8 +757,7 @@ def pixel_ratio(): from qtpy.QtCore import Qt from qtpy.QtWidgets import QMainWindow - app = _init_mne_qtapp() - app.processEvents() + qapp.processEvents() window = QMainWindow() window.setAttribute(Qt.WA_DeleteOnClose, True) ratio = float(window.devicePixelRatio()) @@ -1145,7 +1142,9 @@ def numba_conditional(monkeypatch, request): @pytest.fixture(scope="session") def _nbclient(): try: + import jupyter # noqa import nbformat + import nest_asyncio2 # noqa import trame # noqa from ipywidgets import Button # noqa from jupyter_client import AsyncKernelManager @@ -1258,24 +1257,20 @@ def nirx_snirf(request): @pytest.fixture -def qt_windows_closed(request): +def qt_windows_closed(request, qapp): """Ensure that no new Qt windows are open after a test.""" _check_skip_backend("pyvistaqt") - app = _init_mne_qtapp() - - app.processEvents() + qapp.processEvents() gc.collect() - n_before = len(app.topLevelWidgets()) - marks = set(mark.name for mark in request.node.iter_markers()) + n_before = len(qapp.topLevelWidgets()) yield - app.processEvents() + for _ in range(2): + qapp.processEvents() gc.collect() - if "allow_unclosed" in marks: - return # Don't check when the test fails if not _test_passed(request): return - widgets = app.topLevelWidgets() + widgets = qapp.topLevelWidgets() n_after = len(widgets) assert n_before == n_after, widgets[-4:] diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 5be82d27af6..fdac40839f5 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -2,6 +2,8 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import os + import pytest from mne.utils import _check_qt_version @@ -10,7 +12,7 @@ pytest.importorskip("nibabel") -def test_gui_api(renderer_notebook, nbexec, *, backend="qt"): +def test_gui_api_notebook(renderer_notebook, nbexec, *, backend="qt"): """Test GUI API.""" import contextlib import warnings @@ -386,6 +388,8 @@ def test_gui_api_qt(renderer_interactive_pyvistaqt): # TODO: After merging https://github.com/mne-tools/mne-python/pull/11567 # The Qt CI run started failing about 50% of the time, so let's skip this # for now. - if api == "PySide6": + if ( + os.getenv("AZURE_CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true" + ) and api == "PySide6": pytest.skip("PySide6 causes segfaults on CIs sometimes") - test_gui_api(None, None, backend="qt") + test_gui_api_notebook(None, None, backend="qt") diff --git a/mne/viz/_brain/tests/test_brain.py b/mne/viz/_brain/tests/test_brain.py index 7f0d178e538..212f300c81b 100644 --- a/mne/viz/_brain/tests/test_brain.py +++ b/mne/viz/_brain/tests/test_brain.py @@ -118,9 +118,6 @@ def GetPosition(self): return np.array(self.GetPickPosition()) - (0, 0, 100) -# TODO: allow_unclosed for macOS here as the conda and M1 builds show some -# windows stay open afterward -@pytest.mark.allow_unclosed def test_layered_mesh(renderer_interactive_pyvistaqt): """Test management of scalars/colormap overlay.""" mesh = _LayeredMesh( @@ -574,12 +571,46 @@ def __init__(self): brain.close() -# TODO: Figure out why brain_gc is problematic here on PyQt5 -@pytest.mark.allow_unclosed @testing.requires_testing_data -@pytest.mark.parametrize( - "sensor_colors, sensor_scales, expectation", - [ +def test_add_sensors_scales(renderer_interactive_pyvistaqt): + """Test sensor_scales parameter.""" + kwargs = dict(subject=subject, subjects_dir=subjects_dir) + hemi = "lh" + surf = "white" + cortex = "low_contrast" + title = "test" + size = (300, 300) + + brain = Brain( + hemi=hemi, + surf=surf, + size=size, + title=title, + cortex=cortex, + units="m", + silhouette=dict(decimate=0.95), + **kwargs, + ) + + proj_info = create_info([f"Ch{i}" for i in range(1, 7)], 1000, "seeg") + pos = ( + np.array( + [ + [25.85, 9.04, -5.38], + [33.56, 9.04, -5.63], + [40.44, 9.04, -5.06], + [46.75, 9.04, -6.78], + [-30.08, 9.04, 28.23], + [-32.95, 9.04, 37.99], + ] + ) + / 1000 + ) + proj_info.set_montage( + make_dig_montage(ch_pos=dict(zip(proj_info.ch_names, pos)), coord_frame="head") + ) + + for sensor_colors, sensor_scales, expectation in [ ( {"seeg": ["k"] * 5}, {"seeg": [2] * 6}, @@ -630,57 +661,14 @@ def __init__(self): {"seeg": [2] * 6}, nullcontext(), ), - ], -) -def test_add_sensors_scales( - renderer_interactive_pyvistaqt, - sensor_colors, - sensor_scales, - expectation, -): - """Test sensor_scales parameter.""" - kwargs = dict(subject=subject, subjects_dir=subjects_dir) - hemi = "lh" - surf = "white" - cortex = "low_contrast" - title = "test" - size = (300, 300) - - brain = Brain( - hemi=hemi, - surf=surf, - size=size, - title=title, - cortex=cortex, - units="m", - silhouette=dict(decimate=0.95), - **kwargs, - ) - - proj_info = create_info([f"Ch{i}" for i in range(1, 7)], 1000, "seeg") - pos = ( - np.array( - [ - [25.85, 9.04, -5.38], - [33.56, 9.04, -5.63], - [40.44, 9.04, -5.06], - [46.75, 9.04, -6.78], - [-30.08, 9.04, 28.23], - [-32.95, 9.04, 37.99], - ] - ) - / 1000 - ) - proj_info.set_montage( - make_dig_montage(ch_pos=dict(zip(proj_info.ch_names, pos)), coord_frame="head") - ) - with expectation: - brain.add_sensors( - proj_info, - trans=fname_trans, - sensor_colors=sensor_colors, - sensor_scales=sensor_scales, - ) + ]: + with expectation: + brain.add_sensors( + proj_info, + trans=fname_trans, + sensor_colors=sensor_colors, + sensor_scales=sensor_scales, + ) brain.close() @@ -989,25 +977,18 @@ def test_brain_time_viewer(renderer_interactive_pyvistaqt, pixel_ratio, brain_gc @testing.requires_testing_data @pytest.mark.parametrize( - "hemi", + "hemi, src", [ - "lh", - pytest.param("rh", marks=pytest.mark.slowtest), - pytest.param("split", marks=pytest.mark.slowtest), - pytest.param("both", marks=pytest.mark.slowtest), + ("lh", "surface"), + pytest.param("rh", "surface", marks=pytest.mark.slowtest), + pytest.param("split", "surface", marks=pytest.mark.slowtest), + pytest.param("both", "surface", marks=pytest.mark.slowtest), + pytest.param("lh", "vector", marks=pytest.mark.slowtest), + pytest.param("rh", "volume", marks=pytest.mark.slowtest), + pytest.param("both", "mixed", marks=pytest.mark.slowtest), ], ) -@pytest.mark.parametrize( - "src", - [ - "surface", - pytest.param("vector", marks=pytest.mark.slowtest), - pytest.param("volume", marks=pytest.mark.slowtest), - pytest.param("mixed", marks=pytest.mark.slowtest), - ], -) -@pytest.mark.slowtest -def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain_gc): +def test_brain_traces_basic(renderer_interactive_pyvistaqt, hemi, src, brain_gc): """Test brain traces.""" hemi_str = list() if src in ("surface", "vector", "mixed"): @@ -1061,26 +1042,29 @@ def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain assert "extract_mode" not in brain.widgets brain.close() - # test colormap - if src != "vector": - brain = _create_testing_brain( - hemi=hemi, - surf="white", - src=src, - show_traces=0.5, - initial_time=0, - volume_options=None, # for speed, don't upsample - n_time=1 if src == "mixed" else 5, - diverging=True, - add_data_kwargs=dict(colorbar_kwargs=dict(n_labels=3)), - ) - # mne_analyze should be chosen - ctab = brain._data["ctable"] - assert_array_equal(ctab[0], [0, 255, 255, 255]) # opaque cyan - assert_array_equal(ctab[-1], [255, 255, 0, 255]) # opaque yellow - assert_allclose(ctab[len(ctab) // 2], [128, 128, 128, 0], atol=3) - brain.close() +@testing.requires_testing_data +@pytest.mark.parametrize( + "hemi, src", + [ + ("lh", "surface"), + pytest.param("rh", "surface", marks=pytest.mark.slowtest), + pytest.param("split", "surface", marks=pytest.mark.slowtest), + pytest.param("both", "surface", marks=pytest.mark.slowtest), + pytest.param("lh", "vector", marks=pytest.mark.slowtest), + pytest.param("rh", "volume", marks=pytest.mark.slowtest), + pytest.param("both", "mixed", marks=pytest.mark.slowtest), + ], +) +def test_brain_traces_vertex( + renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain_gc +): + """Test brain traces vertex mode.""" + hemi_str = list() + if src in ("surface", "vector", "mixed"): + hemi_str.extend([hemi] if hemi in ("lh", "rh") else ["lh", "rh"]) + if src in ("mixed", "volume"): + hemi_str.extend(["vol"]) # vertex traces brain = _create_testing_brain( hemi=hemi, @@ -1227,6 +1211,26 @@ def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain assert_allclose(img.shape[0], screenshot_all.shape[0], atol=1) +def test_brain_traces_colormap(renderer_interactive_pyvistaqt, brain_gc): + """Test colormap selection.""" + brain = _create_testing_brain( + hemi="lh", + surf="white", + src="surface", + show_traces=0.5, + initial_time=0, + n_time=5, + diverging=True, + add_data_kwargs=dict(colorbar_kwargs=dict(n_labels=3)), + ) + # mne_analyze should be chosen + ctab = brain._data["ctable"] + assert_array_equal(ctab[0], [0, 255, 255, 255]) # opaque cyan + assert_array_equal(ctab[-1], [255, 255, 0, 255]) # opaque yellow + assert_allclose(ctab[len(ctab) // 2], [128, 128, 128, 0], atol=3) + brain.close() + + # TODO: don't skip on Windows, see # https://github.com/mne-tools/mne-python/pull/10935 # for some reason there is a dependency issue with ipympl even using pyvista @@ -1511,14 +1515,12 @@ def _create_testing_brain( colormap="auto", clim=clim, src=sample_src, + smoothing_steps=0, **kwargs, ) return brain_data -# TODO: allow_unclosed for macOS here as the conda build shows some -# windows stay open afterward -@pytest.mark.allow_unclosed def test_foci_mapping(tmp_path, renderer_interactive_pyvistaqt): """Test mapping foci to the surface.""" tiny_brain, _ = tiny(tmp_path) diff --git a/mne/viz/backends/_utils.py b/mne/viz/backends/_utils.py index b58d940439a..2b7b71799a0 100644 --- a/mne/viz/backends/_utils.py +++ b/mne/viz/backends/_utils.py @@ -388,6 +388,8 @@ def dec(meth, splash=splash, always_close=always_close): def func(self, *args, **kwargs): close_splash = always_close error = False + if not self: + return try: meth(self, *args, **kwargs) except Exception: @@ -402,7 +404,9 @@ def func(self, *args, **kwargs): try: for n in attr.split(".")[:-1]: parent = getattr(parent, n) - if name: + if not parent: + break + if parent and name: widget = getattr(parent, name, False) else: # empty string means "self" widget = parent @@ -416,6 +420,8 @@ def func(self, *args, **kwargs): delattr(parent, name) except Exception: pass + finally: + del parent, attr, do_close return func diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index b30ae802cf6..1569a6bac15 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -2,14 +2,13 @@ set -eo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -STD_ARGS="--progress-bar off --upgrade " +STD_ARGS="--progress-bar off --upgrade --only-binary=:all:" python -m pip install $STD_ARGS pip setuptools wheel if [ "${TEST_MODE}" == "pip" ]; then - python -m pip install $STD_ARGS --only-binary="numba,llvmlite,numpy,scipy,vtk,dipy,openmeeg" -e .[full] --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" + python -m pip install $STD_ARGS --only-binary="numba,llvmlite,numpy,scipy,vtk,dipy,openmeeg" -e .[full-pyside6] --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" elif [ "${TEST_MODE}" == "pip-pre" ]; then ${SCRIPT_DIR}/install_pre_requirements.sh python -m pip install $STD_ARGS --pre -e . --group=test_extra - echo "##vso[task.setvariable variable=MNE_TEST_ALLOW_SKIP].*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*" else echo "Unknown run type ${TEST_MODE}" exit 1 diff --git a/tools/circleci_bash_env.sh b/tools/circleci_bash_env.sh index fecb9627d4c..91df8a4f1b7 100755 --- a/tools/circleci_bash_env.sh +++ b/tools/circleci_bash_env.sh @@ -13,6 +13,7 @@ echo "set -o pipefail" >> $BASH_ENV echo "export XDG_RUNTIME_DIR=/tmp/runtime-circleci" >> $BASH_ENV echo "export MNE_FULL_DATE=true" >> $BASH_ENV echo "export MNE_3D_BACKEND=pyvistaqt" >> $BASH_ENV +echo "export MNE_QT_BACKEND=PySide6" >> $BASH_ENV echo "export MNE_BROWSER_BACKEND=qt" >> $BASH_ENV echo "export MNE_BROWSER_PRECOMPUTE=false" >> $BASH_ENV echo "export MNE_ADD_CONTRIBUTOR_IMAGE=true" >> $BASH_ENV diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 4b231368101..935c7842025 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -1,19 +1,19 @@ #!/bin/bash -ef -ONLY_BINARY="--only-binary \"numpy,dipy,scipy,matplotlib,pandas,statsmodels,netCDF4,h5py\"" - set -x python -m pip install --upgrade "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them -python -m pip install --upgrade --progress-bar off $ONLY_BINARY \ - -ve .[full] \ +python -m pip install --upgrade --only-binary=numpy,scipy \ + "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" mne-ari +python -m pip install --upgrade --only-binary=:all: \ + -ve .[full-pyside6] \ --group=test \ --group=doc-full \ - "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ - -r doc/sphinxext/related_software.txt \ - "git+https://github.com/mne-tools/mne-bids.git" \ - "git+https://github.com/mne-tools/mne-qt-browser.git" \ - "git+https://github.com/pyvista/pyvista.git" \ - "git+https://github.com/sphinx-gallery/sphinx-gallery.git" -python -m pip install --upgrade --progress-bar off --no-deps $ONLY_BINARY \ + "mne-bids @ https://github.com/mne-tools/mne-bids/archive/refs/heads/main.zip" \ + "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" \ + "pyvista @ https://github.com/pyvista/pyvista/archive/refs/heads/main.zip" \ + "pyvistaqt @ https://github.com/pyvista/pyvistaqt/archive/refs/heads/main.zip" \ + "sphinx-gallery @ https://github.com/sphinx-gallery/sphinx-gallery/archive/refs/heads/master.zip" \ + -r doc/sphinxext/related_software.txt +python -m pip install --upgrade --no-deps --only-binary=:all: \ -r doc/sphinxext/related_software_nodeps.txt diff --git a/tools/github_actions_dependencies.sh b/tools/github_actions_dependencies.sh index 3ee5986cfa9..09ebb655f61 100755 --- a/tools/github_actions_dependencies.sh +++ b/tools/github_actions_dependencies.sh @@ -3,7 +3,7 @@ set -eo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -ONLY_BINARY_ARG="--only-binary=numpy,scipy,matplotlib,numba,llvmlite,antio" +ONLY_BINARY_ARG="--only-binary=:all:" STD_ARGS="--progress-bar off --upgrade" INSTALL_ARGS="-e" if [ ! -z "$CONDA_ENV" ]; then diff --git a/tools/github_actions_download.sh b/tools/github_actions_download.sh index a503b562068..b67d430578e 100755 --- a/tools/github_actions_download.sh +++ b/tools/github_actions_download.sh @@ -1,8 +1,7 @@ #!/bin/bash -ef if [ "${MNE_CI_KIND}" != "minimal" ]; then - python -c 'import mne; mne.datasets.testing.data_path(verbose=True)'; - python -c "import mne; mne.datasets.misc.data_path(verbose=True)"; + python -uc "import mne; mne.datasets.testing.data_path(verbose=True)" # Make read-only to make sure we don't modify its contents TESTING_PATH=$(python -c "import mne; print(mne.datasets.testing.data_path(verbose=False))") echo "Testing data path: $TESTING_PATH" diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index cf4d6eaa3c0..f8ed3320e2c 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -8,7 +8,7 @@ if [[ "$MNE_CI_KIND" == "pip"* ]] || [[ "$MNE_CI_KIND" == "minimal" ]]; then # We should test an eager import somewhere, might as well be here echo "EAGER_IMPORT=true" | tee -a $GITHUB_ENV # Make sure nothing unexpected is skipped - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Numba not|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(CUDA not|Numba not|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV fi echo "MNE_QT_BACKEND=PySide6" | tee -a $GITHUB_ENV elif [[ "$MNE_CI_KIND" == "old" ]]; then @@ -19,7 +19,7 @@ elif [[ "$MNE_CI_KIND" == "conda" ]]; then echo "Setting conda env vars for $MNE_CI_KIND" echo "CONDA_ENV=environment.yml" | tee -a $GITHUB_ENV echo "MNE_LOGGING_LEVEL=warning" | tee -a $GITHUB_ENV - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|PySide6 causes segfaults|Accelerate|Flakey verbose behavior).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(CUDA not|Accelerate|Flakey verbose behavior|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV # Our cache_dir test has problems when the path is too long, so prevent it from getting too long if [[ "$CI_OS_NAME" == "macos"* ]]; then echo "PYTEST_DEBUG_TEMPROOT=/tmp" | tee -a $GITHUB_ENV