File: mne_doc_utils.py

package info (click to toggle)
python-mne 1.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 131,492 kB
  • sloc: python: 213,302; javascript: 12,910; sh: 447; makefile: 144
file content (291 lines) | stat: -rw-r--r-- 10,647 bytes parent folder | download
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()