1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
|
"""Doc building utils."""
# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import gc
import os
import time
import warnings
from pathlib import Path
import numpy as np
import pyvista
import sphinx.util.logging
import mne
from mne.utils import (
_assert_no_instances,
_get_extra_data_path,
sizeof_fmt,
)
from mne.viz import Brain
sphinx_logger = sphinx.util.logging.getLogger("mne")
_np_print_defaults = np.get_printoptions()
def reset_warnings(gallery_conf, fname):
"""Ensure we are future compatible and ignore silly warnings."""
# In principle, our examples should produce no warnings.
# Here we cause warnings to become errors, with a few exceptions.
# This list should be considered alongside
# setup.cfg -> [tool:pytest] -> filterwarnings
# remove tweaks from other module imports or example runs
warnings.resetwarnings()
# restrict
warnings.filterwarnings("error")
# allow these, but show them
warnings.filterwarnings("always", '.*non-standard config type: "foo".*')
warnings.filterwarnings("always", '.*config type: "MNEE_USE_CUUDAA".*')
warnings.filterwarnings("always", ".*cannot make axes width small.*")
warnings.filterwarnings("always", ".*Axes that are not compatible.*")
warnings.filterwarnings("always", ".*FastICA did not converge.*")
# ECoG BIDS spec violations:
warnings.filterwarnings("always", ".*Fiducial point nasion not found.*")
warnings.filterwarnings("always", ".*DigMontage is only a subset of.*")
warnings.filterwarnings( # xhemi morph (should probably update sample)
"always", ".*does not exist, creating it and saving it.*"
)
# internal warnings
warnings.filterwarnings("default", module="sphinx")
# allow these warnings, but don't show them
for key in (
"invalid version and will not be supported", # pyxdf
"distutils Version classes are deprecated", # seaborn and neo
"is_categorical_dtype is deprecated", # seaborn
"`np.object` is a deprecated alias for the builtin `object`", # pyxdf
# nilearn, should be fixed in > 0.9.1
"In future, it will be an error for 'np.bool_' scalars to",
# sklearn hasn't updated to SciPy's sym_pos dep
"The 'sym_pos' keyword is deprecated",
# numba
"`np.MachAr` is deprecated",
# joblib hasn't updated to avoid distutils
"distutils package is deprecated",
# jupyter
"Jupyter is migrating its paths to use standard",
r"Widget\..* is deprecated\.",
# PyQt6
"Enum value .* is marked as deprecated",
# matplotlib PDF output
"The py23 module has been deprecated",
# pkg_resources
"Implementing implicit namespace packages",
"Deprecated call to `pkg_resources",
# nilearn
"pkg_resources is deprecated as an API",
r"The .* was deprecated in Matplotlib 3\.7",
# Matplotlib->tz
r"datetime\.datetime\.utcfromtimestamp",
# joblib
r"ast\.Num is deprecated",
r"Attribute n is deprecated and will be removed in Python 3\.14",
# numpydoc
r"ast\.NameConstant is deprecated and will be removed in Python 3\.14",
# pooch
r"Python 3\.14 will, by default, filter extracted tar archives.*",
# seaborn
r"DataFrameGroupBy\.apply operated on the grouping columns.*",
# pandas
r"\nPyarrow will become a required dependency of pandas.*",
# latexcodec
r"open_text is deprecated\. Use files.*",
# python-quantities, via neo
r"numpy\.core is deprecated and has been renamed to numpy\._core",
# matplotlib
"__array_wrap__ must accept context and return_scalar.*",
):
warnings.filterwarnings( # deal with other modules having bad imports
"ignore", message=f".*{key}.*", category=DeprecationWarning
)
warnings.filterwarnings(
"ignore",
message="Matplotlib is currently using agg, which is a non-GUI backend.*",
)
warnings.filterwarnings(
"ignore",
message=".*is non-interactive, and thus cannot.*",
)
# seaborn
warnings.filterwarnings(
"ignore",
message="The figure layout has changed to tight",
category=UserWarning,
)
# xarray/netcdf4
warnings.filterwarnings(
"ignore",
message=r"numpy\.ndarray size changed, may indicate.*",
category=RuntimeWarning,
)
# qdarkstyle
warnings.filterwarnings(
"ignore",
message=r".*Setting theme=.*6 in qdarkstyle.*",
category=RuntimeWarning,
)
# pandas, via seaborn (examples/time_frequency/time_frequency_erds.py)
for message in (
"use_inf_as_na option is deprecated.*",
r"iteritems is deprecated.*Use \.items instead\.",
"is_categorical_dtype is deprecated.*",
"The default of observed=False.*",
"When grouping with a length-1 list-like.*",
):
warnings.filterwarnings(
"ignore",
message=message,
category=FutureWarning,
)
# pandas in 50_epochs_to_data_frame.py
warnings.filterwarnings(
"ignore", message=r"invalid value encountered in cast", category=RuntimeWarning
)
# xarray _SixMetaPathImporter (?)
warnings.filterwarnings(
"ignore", message=r"falling back to find_module", category=ImportWarning
)
# Sphinx deps
warnings.filterwarnings(
"ignore", message="The str interface for _CascadingStyleSheet.*"
)
# mne-qt-browser until > 0.5.2 released
warnings.filterwarnings(
"ignore",
r"mne\.io\.pick.channel_indices_by_type is deprecated.*",
)
# parallel building
warnings.filterwarnings(
"ignore",
"A worker stopped while some jobs were given to the executor.*",
category=UserWarning,
)
# neo
warnings.filterwarnings(
"ignore",
"The 'copy' argument in Quantity is deprecated.*",
)
# In case we use np.set_printoptions in any tutorials, we only
# want it to affect those:
np.set_printoptions(**_np_print_defaults)
t0 = time.time()
def reset_modules(gallery_conf, fname, when):
"""Do the reset."""
import matplotlib.pyplot as plt
mne.viz.set_3d_backend("pyvistaqt")
pyvista.OFF_SCREEN = False
pyvista.BUILDING_GALLERY = True
from pyvista import Plotter # noqa
try:
from pyvistaqt import BackgroundPlotter # noqa
except ImportError:
BackgroundPlotter = None # noqa
try:
from vtkmodules.vtkCommonDataModel import vtkPolyData # noqa
except ImportError:
vtkPolyData = None # noqa
try:
from mne_qt_browser._pg_figure import MNEQtBrowser
except ImportError:
MNEQtBrowser = None
from mne.viz.backends.renderer import backend
_Renderer = backend._Renderer if backend is not None else None
reset_warnings(gallery_conf, fname)
# in case users have interactive mode turned on in matplotlibrc,
# turn it off here (otherwise the build can be very slow)
plt.ioff()
plt.rcParams["animation.embed_limit"] = 40.0
plt.rcParams["figure.raise_window"] = False
# https://github.com/sphinx-gallery/sphinx-gallery/pull/1243#issue-2043332860
plt.rcParams["animation.html"] = "html5"
# neo holds on to an exception, which in turn holds a stack frame,
# which will keep alive the global vars during SG execution
try:
import neo
neo.io.stimfitio.STFIO_ERR = None
except Exception:
pass
gc.collect()
# Agg does not call close_event so let's clean up on our own :(
# https://github.com/matplotlib/matplotlib/issues/18609
mne.viz.ui_events._cleanup_agg()
assert len(mne.viz.ui_events._event_channels) == 0, list(
mne.viz.ui_events._event_channels
)
orig_when = when
when = f"mne/conf.py:Resetter.__call__:{when}:{fname}"
# Support stuff like
# MNE_SKIP_INSTANCE_ASSERTIONS="Brain,Plotter,BackgroundPlotter,vtkPolyData,_Renderer" make html-memory # noqa: E501
# to just test MNEQtBrowser
skips = os.getenv("MNE_SKIP_INSTANCE_ASSERTIONS", "").lower()
prefix = ""
if skips not in ("true", "1", "all"):
prefix = "Clean "
skips = skips.split(",")
if "brain" not in skips:
_assert_no_instances(Brain, when) # calls gc.collect()
if Plotter is not None and "plotter" not in skips:
_assert_no_instances(Plotter, when)
if BackgroundPlotter is not None and "backgroundplotter" not in skips:
_assert_no_instances(BackgroundPlotter, when)
if vtkPolyData is not None and "vtkpolydata" not in skips:
_assert_no_instances(vtkPolyData, when)
if "_renderer" not in skips:
_assert_no_instances(_Renderer, when)
if MNEQtBrowser is not None and "mneqtbrowser" not in skips:
# Ensure any manual fig.close() events get properly handled
from mne_qt_browser._pg_figure import QApplication
inst = QApplication.instance()
if inst is not None:
for _ in range(2):
inst.processEvents()
_assert_no_instances(MNEQtBrowser, when)
# This will overwrite some Sphinx printing but it's useful
# for memory timestamps
if os.getenv("SG_STAMP_STARTS", "").lower() == "true":
import psutil
process = psutil.Process(os.getpid())
mem = sizeof_fmt(process.memory_info().rss)
print(f"{prefix}{time.time() - t0:6.1f} s : {mem}".ljust(22))
if fname == "50_configure_mne.py":
# This messes with the config, so let's do so in a temp dir
if orig_when == "before":
fake_home = Path(_get_extra_data_path()) / "temp"
fake_home.mkdir(exist_ok=True, parents=True)
os.environ["_MNE_FAKE_HOME_DIR"] = str(fake_home)
else:
assert orig_when == "after"
to_del = Path(os.environ["_MNE_FAKE_HOME_DIR"])
try:
(to_del / "mne-python.json").unlink()
except Exception:
pass
try:
to_del.rmdir()
except Exception:
pass
del os.environ["_MNE_FAKE_HOME_DIR"]
report_scraper = mne.report._ReportScraper()
mne_qt_browser_scraper = mne.viz._scraper._MNEQtBrowserScraper()
brain_scraper = mne.viz._brain._BrainScraper()
gui_scraper = mne.gui._GUIScraper()
|