"""Configuration file for the Sphinx documentation builder.
This file only contains a selection of the most common options. For a full
list see the documentation:
https://www.sphinx-doc.org/en/master/usage/configuration.html
"""
import faulthandler
import os
import subprocess
import sys
import tomllib
from datetime import UTC, datetime
from importlib.metadata import metadata
from pathlib import Path
import matplotlib
import sphinx
from intersphinx_registry import get_intersphinx_mapping
from numpydoc import docscrape
from sphinx.config import is_serializable
from sphinx.domains.changeset import versionlabels
from sphinx_gallery.sorting import ExplicitOrder
from yaml import safe_load
import mne
import mne.html_templates._templates
from mne.utils import (
linkcode_resolve,
run_subprocess,
)
assert linkcode_resolve is not None
matplotlib.use("agg")
faulthandler.enable()
os.environ["_MNE_BROWSER_NO_BLOCK"] = "true"
os.environ["MNE_BROWSER_OVERVIEW_MODE"] = "hidden"
os.environ["MNE_BROWSER_THEME"] = "light"
os.environ["MNE_3D_OPTION_THEME"] = "light"
os.environ["NUMBA_CAPTURED_ERRORS"] = "new_style"
mne.html_templates._templates._COLLAPSED = True
curpath = Path(__file__).parent.resolve(strict=True)
sys.path.append(str(curpath / "sphinxext"))
from credit_tools import generate_credit_rst
from mne_doc_utils import (
check_links,
report_scraper,
reset_warnings,
sphinx_logger,
)
project = "MNE"
td = datetime.now(tz=UTC)
project_copyright = (
f'2012–{td.year}, MNE Developers. Last updated <time datetime="{td.isoformat()}" class="localized">{td.strftime("%Y-%m-%d %H:%M %Z")}</time>.\n'
"""<script type="text/javascript">
function formatTimestamp() {
document.querySelectorAll("time.localized").forEach(el => {
const d = new Date(el.getAttribute("datetime"));
el.textContent = d.toLocaleString("sv-SE", { "timeZoneName": "short" });
});
}
if (document.readyState !== "loading") {
formatTimestamp();
} else {
document.addEventListener("DOMContentLoaded", formatTimestamp);
}
</script>"""
)
if os.getenv("MNE_FULL_DATE", "false").lower() != "true":
project_copyright = f"2012–{td.year}, MNE Developers. Last updated locally."
release = mne.__version__
sphinx_logger.info(f"Building documentation for MNE {release} ({mne.__file__})")
version = ".".join(release.split(".")[:2])
needs_sphinx = "6.0"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.coverage",
"sphinx.ext.doctest",
"sphinx.ext.graphviz",
"sphinx.ext.intersphinx",
"sphinx.ext.linkcode",
"sphinx.ext.mathjax",
"sphinx.ext.todo",
"matplotlib.sphinxext.plot_directive",
"numpydoc",
"sphinx_copybutton",
"sphinx_design",
"sphinx_gallery.gen_gallery",
"sphinxcontrib.bibtex",
"sphinxcontrib.youtube",
"sphinxcontrib.towncrier.ext",
"contrib_avatars",
"gen_commands",
"gen_names",
"gh_substitutions",
"mne_substitutions",
"newcontrib_substitutions",
"unit_role",
"related_software",
"directive_formatting",
]
templates_path = ["_templates"]
exclude_patterns = ["_includes", "changes/dev"]
source_suffix = ".rst"
master_doc = "index"
unused_docs = []
exclude_trees = ["_build"]
default_role = "py:obj"
modindex_common_prefix = ["mne."]
copybutton_prompt_text = r">>> |\.\.\. |\$ "
copybutton_prompt_is_regexp = True
towncrier_draft_working_directory = str(curpath.parent)
intersphinx_mapping = {
"nitime": ("https://nipy.org/nitime/", None),
"mne_bids": ("https://mne.tools/mne-bids/stable", None),
"mne-connectivity": ("https://mne.tools/mne-connectivity/stable", None),
"mne-gui-addons": ("https://mne.tools/mne-gui-addons", None),
"picard": ("https://mind-inria.github.io/picard/", None),
"eeglabio": ("https://eeglabio.readthedocs.io/en/latest", None),
"pybv": ("https://pybv.readthedocs.io/en/latest", None),
}
intersphinx_mapping.update(
get_intersphinx_mapping(
packages=set(
"""
imageio matplotlib numpy pandas python scipy statsmodels sklearn numba joblib nibabel
seaborn patsy pyvista dipy nilearn pyqtgraph
""".strip().split()
),
)
)
intersphinx_mapping["joblib"] = ("https://joblib.readthedocs.io/en/stable", None)
docscrape.ClassDoc.extra_public_methods = mne.utils._doc_special_members
numpydoc_class_members_toctree = False
numpydoc_show_inherited_class_members = {
"mne.Forward": False,
"mne.Projection": False,
"mne.SourceSpaces": False,
}
numpydoc_attributes_as_param_list = True
numpydoc_xref_param_type = True
numpydoc_xref_aliases = {
"file-like": ":term:`file-like <python:file object>`",
"iterator": ":term:`iterator <python:iterator>`",
"path-like": ":term:`path-like`",
"array-like": ":term:`array_like <numpy:array_like>`",
"Path": ":class:`python:pathlib.Path`",
"bool": ":ref:`bool <python:typebool>`",
"colormap": ":ref:`colormap <matplotlib:colormaps>`",
"color": ":doc:`color <matplotlib:api/colors_api>`",
"Axes": "matplotlib.axes.Axes",
"Figure": "matplotlib.figure.Figure",
"Axes3D": "mpl_toolkits.mplot3d.axes3d.Axes3D",
"ColorbarBase": "matplotlib.colorbar.ColorbarBase",
"LeaveOneOut": "sklearn.model_selection.LeaveOneOut",
"MetadataRequest": "sklearn.utils.metadata_routing.MetadataRequest",
"estimator": "sklearn.base.BaseEstimator",
"joblib.Parallel": "joblib.Parallel",
"Nifti1Image": "nibabel.nifti1.Nifti1Image",
"Nifti2Image": "nibabel.nifti2.Nifti2Image",
"SpatialImage": "nibabel.spatialimages.SpatialImage",
"Label": "mne.Label",
"Forward": "mne.Forward",
"Evoked": "mne.Evoked",
"Info": "mne.Info",
"SourceSpaces": "mne.SourceSpaces",
"Epochs": "mne.Epochs",
"Layout": "mne.channels.Layout",
"EvokedArray": "mne.EvokedArray",
"BiHemiLabel": "mne.BiHemiLabel",
"AverageTFR": "mne.time_frequency.AverageTFR",
"AverageTFRArray": "mne.time_frequency.AverageTFRArray",
"EpochsTFR": "mne.time_frequency.EpochsTFR",
"EpochsTFRArray": "mne.time_frequency.EpochsTFRArray",
"RawTFR": "mne.time_frequency.RawTFR",
"RawTFRArray": "mne.time_frequency.RawTFRArray",
"Raw": "mne.io.Raw",
"ICA": "mne.preprocessing.ICA",
"Covariance": "mne.Covariance",
"Annotations": "mne.Annotations",
"DigMontage": "mne.channels.DigMontage",
"VectorSourceEstimate": "mne.VectorSourceEstimate",
"VolSourceEstimate": "mne.VolSourceEstimate",
"VolVectorSourceEstimate": "mne.VolVectorSourceEstimate",
"MixedSourceEstimate": "mne.MixedSourceEstimate",
"MixedVectorSourceEstimate": "mne.MixedVectorSourceEstimate",
"SourceEstimate": "mne.SourceEstimate",
"Projection": "mne.Projection",
"ConductorModel": "mne.bem.ConductorModel",
"Dipole": "mne.Dipole",
"DipoleFixed": "mne.DipoleFixed",
"InverseOperator": "mne.minimum_norm.InverseOperator",
"CrossSpectralDensity": "mne.time_frequency.CrossSpectralDensity",
"SourceMorph": "mne.SourceMorph",
"Xdawn": "mne.preprocessing.Xdawn",
"Report": "mne.Report",
"TimeDelayingRidge": "mne.decoding.TimeDelayingRidge",
"Vectorizer": "mne.decoding.Vectorizer",
"UnsupervisedSpatialFilter": "mne.decoding.UnsupervisedSpatialFilter",
"TemporalFilter": "mne.decoding.TemporalFilter",
"SSD": "mne.decoding.SSD",
"Scaler": "mne.decoding.Scaler",
"SPoC": "mne.decoding.SPoC",
"PSDEstimator": "mne.decoding.PSDEstimator",
"LinearModel": "mne.decoding.LinearModel",
"FilterEstimator": "mne.decoding.FilterEstimator",
"EMS": "mne.decoding.EMS",
"CSP": "mne.decoding.CSP",
"Beamformer": "mne.beamformer.Beamformer",
"Transform": "mne.transforms.Transform",
"Coregistration": "mne.coreg.Coregistration",
"Figure3D": "mne.viz.Figure3D",
"EOGRegression": "mne.preprocessing.EOGRegression",
"Spectrum": "mne.time_frequency.Spectrum",
"EpochsSpectrum": "mne.time_frequency.EpochsSpectrum",
"EpochsFIF": "mne.Epochs",
"EpochsEEGLAB": "mne.Epochs",
"EpochsKIT": "mne.Epochs",
"BaseRaw": "mne.io.Raw",
"RawANT": "mne.io.Raw",
"RawArtemis123": "mne.io.Raw",
"RawBCI2k": "mne.io.Raw",
"RawBDF": "mne.io.Raw",
"RawBOXY": "mne.io.Raw",
"RawBrainVision": "mne.io.Raw",
"RawBTi": "mne.io.Raw",
"RawCNT": "mne.io.Raw",
"RawCTF": "mne.io.Raw",
"RawCurry": "mne.io.Raw",
"RawEDF": "mne.io.Raw",
"RawEEGLAB": "mne.io.Raw",
"RawEGI": "mne.io.Raw",
"RawEximia": "mne.io.Raw",
"RawEyelink": "mne.io.Raw",
"RawFIL": "mne.io.Raw",
"RawGDF": "mne.io.Raw",
"RawHitachi": "mne.io.Raw",
"RawKIT": "mne.io.Raw",
"RawNedf": "mne.io.Raw",
"RawNeuralynx": "mne.io.Raw",
"RawNicolet": "mne.io.Raw",
"RawNihon": "mne.io.Raw",
"RawNSX": "mne.io.Raw",
"RawMEF": "mne.io.Raw",
"RawNIRX": "mne.io.Raw",
"RawPersyst": "mne.io.Raw",
"RawSNIRF": "mne.io.Raw",
"Calibration": "mne.preprocessing.eyetracking.Calibration",
"dipy.align.AffineMap": "dipy.align.imaffine.AffineMap",
"dipy.align.DiffeomorphicMap": "dipy.align.imwarp.DiffeomorphicMap",
}
numpydoc_xref_ignore = {
"and",
"as",
"between",
"class",
"data",
"instance",
"instances",
"input",
"of",
"default",
"same",
"shape",
"or",
"the",
"with",
"length",
"pair",
"matplotlib",
"optional",
"kwargs",
"in",
"dtype",
"object",
"n_vertices",
"n_faces",
"n_channels",
"m",
"n",
"n_events",
"n_colors",
"n_times",
"obj",
"n_chan",
"n_epochs",
"n_picks",
"n_ch_groups",
"n_dipoles",
"n_ica_components",
"n_pos",
"n_node_names",
"n_tapers",
"n_signals",
"n_step",
"n_freqs",
"wsize",
"Tx",
"M",
"N",
"p",
"q",
"r",
"n_observations",
"n_regressors",
"n_cols",
"n_frequencies",
"n_tests",
"n_samples",
"n_peaks",
"n_permutations",
"nchan",
"n_points",
"n_features",
"n_parts",
"n_features_new",
"n_components",
"n_labels",
"n_events_in",
"n_splits",
"n_scores",
"n_outputs",
"n_trials",
"n_estimators",
"n_tasks",
"nd_features",
"n_classes",
"n_targets",
"n_slices",
"n_hpi",
"n_fids",
"n_elp",
"n_pts",
"n_tris",
"n_nodes",
"n_nonzero",
"n_events_out",
"n_segments",
"n_orient_inv",
"n_orient_fwd",
"n_orient",
"n_dipoles_lcmv",
"n_dipoles_fwd",
"n_picks_ref",
"n_coords",
"n_meg",
"n_good_meg",
"n_moments",
"n_patterns",
"n_new_events",
"mapping",
"to",
"any",
"pandas",
"polars",
"default",
"_Renderer",
"n_triangles",
"CoregistrationUI",
"DipoleFitUI",
"mne_qt_browser.figure.MNEQtBrowser",
"pooch.Unzip",
"pooch.Untar",
"pooch.HTTPDownloader",
}
numpydoc_validate = True
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
pyproject = tomllib.loads(pyproject_path.read_text("utf-8"))
pyproject_nv = pyproject["tool"]["numpydoc_validation"]
numpydoc_validation_checks = set(pyproject_nv["checks"])
numpydoc_validation_exclude = set(pyproject_nv["exclude"])
examples_dirs = ["../tutorials", "../examples"]
gallery_dirs = ["auto_tutorials", "auto_examples"]
os.environ["_MNE_BUILDING_DOC"] = "true"
scrapers = (
"matplotlib",
"mne_doc_utils.gui_scraper",
"mne_doc_utils.brain_scraper",
"pyvista",
"mne_doc_utils.report_scraper",
"mne_doc_utils.mne_qt_browser_scraper",
)
compress_images = ("images", "thumbnails")
if sys.platform.startswith("win"):
try:
subprocess.check_call(["optipng", "--version"])
except Exception:
compress_images = ()
sphinx_gallery_parallel = int(os.getenv("MNE_DOC_BUILD_N_JOBS", "1"))
sphinx_gallery_conf = {
"doc_module": ("mne",),
"reference_url": dict(mne=None),
"examples_dirs": examples_dirs,
"subsection_order": ExplicitOrder(
[
"../examples/io/",
"../examples/simulation/",
"../examples/preprocessing/",
"../examples/visualization/",
"../examples/time_frequency/",
"../examples/stats/",
"../examples/decoding/",
"../examples/connectivity/",
"../examples/forward/",
"../examples/inverse/",
"../examples/realtime/",
"../examples/datasets/",
"../tutorials/intro/",
"../tutorials/io/",
"../tutorials/raw/",
"../tutorials/preprocessing/",
"../tutorials/epochs/",
"../tutorials/evoked/",
"../tutorials/time-freq/",
"../tutorials/forward/",
"../tutorials/inverse/",
"../tutorials/stats-sensor-space/",
"../tutorials/stats-source-space/",
"../tutorials/machine-learning/",
"../tutorials/clinical/",
"../tutorials/simulation/",
"../tutorials/sample-datasets/",
"../tutorials/visualization/",
"../tutorials/misc/",
]
),
"gallery_dirs": gallery_dirs,
"default_thumb_file": os.path.join("_static", "mne_helmet.png"),
"backreferences_dir": "generated",
"plot_gallery": "True",
"thumbnail_size": (160, 112),
"remove_config_comments": True,
"min_reported_time": 1.0,
"abort_on_example_error": False,
"reset_modules": (
"matplotlib",
"mne_doc_utils.reset_modules",
),
"reset_modules_order": "both",
"image_scrapers": scrapers,
"show_memory": sys.platform == "linux" and sphinx_gallery_parallel == 1,
"line_numbers": False,
"within_subsection_order": "FileNameSortKey",
"capture_repr": ("_repr_html_",),
"junit": os.path.join("..", "test-results", "sphinx-gallery", "junit.xml"),
"matplotlib_animations": True,
"compress_images": compress_images,
"filename_pattern": "^((?!sgskip).)*$",
"exclude_implicit_doc": {
r"mne\.io\.read_raw_fif",
r"mne\.io\.Raw",
r"mne\.Epochs",
r"mne.datasets.*",
},
"show_api_usage": "unused",
"api_usage_ignore": (
"("
".*__.*__|"
".*Base.*|.*Array.*|mne.Vector.*|mne.Mixed.*|mne.Vol.*|"
"mne.coreg.Coregistration.*|"
".*utils.*|.*verbose()|.*copy()|.*update()|.*save()|"
".*get_data()|"
".*add_channels()|.*add_reference_channels()|"
".*anonymize()|.*apply_baseline()|.*apply_function()|"
".*apply_hilbert()|.*as_type()|.*decimate()|"
".*drop()|.*drop_channels()|.*drop_log_stats()|"
".*export()|.*get_channel_types()|"
".*get_montage()|.*interpolate_bads()|.*next()|"
".*pick()|.*pick_channels()|.*pick_types()|"
".*plot_sensors()|.*rename_channels()|"
".*reorder_channels()|.*savgol_filter()|"
".*set_eeg_reference()|.*set_channel_types()|"
".*set_meas_date()|.*set_montage()|.*shift_time()|"
".*time_as_index()|.*to_data_frame()|"
".*clear()|.*fromkeys()|.*get()|.*items()|"
".*keys()|.*pop()|.*popitem()|.*setdefault()|"
".*values()|"
".*apply()|.*decision_function()|.*fit()|"
".*fit_transform()|.*get_params()|.*predict()|"
".*predict_proba()|.*set_params()|.*transform()|"
".*.remove.*|.*.write.*)"
),
"copyfile_regex": r".*index\.rst",
"parallel": sphinx_gallery_parallel,
}
assert is_serializable(sphinx_gallery_conf)
def append_attr_meth_examples(app, what, name, obj, options, lines):
"""Append SG examples backreferences to method and attr docstrings."""
if what in ("attribute", "method"):
size = os.path.getsize(
os.path.join(
os.path.dirname(__file__),
"generated",
f"{name}.examples",
)
)
if size > 0:
lines += """
.. _sphx_glr_backreferences_{1}:
.. rubric:: Examples using ``{0}``:
.. minigallery:: {1}
""".format(name.split(".")[-1], name).split("\n")
def fix_sklearn_inherited_docstrings(app, what, name, obj, options, lines):
"""Fix sklearn docstrings because they use autolink and we do not."""
if (
name.startswith("mne.decoding.") or name.startswith("mne.preprocessing.Xdawn")
) and name.endswith(
(
".get_metadata_routing",
".fit",
".fit_transform",
".set_output",
".transform",
)
):
if ":Parameters:" in lines:
loc = lines.index(":Parameters:")
else:
loc = lines.index(":Returns:")
lines.insert(loc, "")
lines.insert(loc, ".. default-role:: autolink")
lines.insert(loc, "")
graphviz_dot_args = [
"-Gsep=-0.5",
"-Gpad=0.5",
"-Nshape=box",
"-Nfontsize=20",
"-Nfontname=Open Sans,Arial",
]
graphviz_output_format = "svg"
user_agent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36"
linkcheck_ignore = [
"https://doi.org/10.1002/",
"https://doi.org/10.1017/",
"https://doi.org/10.1016/",
"https://doi.org/10.1021/",
"https://doi.org/10.1063/",
"https://doi.org/10.1073/",
"https://doi.org/10.1080/",
"https://doi.org/10.1088/",
"https://doi.org/10.1090/",
"https://doi.org/10.1093/",
"https://doi.org/10.1098/",
"https://doi.org/10.1101/",
"https://doi.org/10.1103/",
"https://doi.org/10.1111/",
"https://doi.org/10.1126/",
"https://doi.org/10.1137/",
"https://doi.org/10.1145/",
"https://doi.org/10.5281/",
"https://doi.org/10.1155/",
"https://doi.org/10.1161/",
"https://doi.org/10.1162/",
"https://doi.org/10.1167/",
"https://doi.org/10.1177/",
"https://doi.org/10.1523/",
"https://doi.org/10.3109/",
"https://doi.org/10.3390/",
"https://hms.harvard.edu/",
"https://stackoverflow.com/questions/21752259/python-why-pickle",
"https://mitpress.mit.edu/9780262525855",
"https://zenodo.org",
"https://blender.org",
"https://home.alexk101.dev",
"https://www.mq.edu.au/",
"https://www.biorxiv.org/content/10.1101/",
"https://www.researchgate.net/profile/",
"https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html",
r"https://scholar.google.com/scholar\?cites=12188330066413208874&as_ylo=2014",
r"https://scholar.google.com/scholar\?cites=1521584321377182930&as_ylo=2013",
"https://www.research.chop.edu/imaging",
"http://prdownloads.sourceforge.net/optipng",
"https://sourceforge.net/projects/aespa/files/",
"https://sourceforge.net/projects/ezwinports/files/",
r"https://.*\.sourceforge\.net/",
"https://www.cogsci.nl/smathot",
"https://www.mathworks.com/products/compiler/matlab-runtime.html",
"https://medicine.umich.edu/dept/khri/ross-maddox-phd",
"http://blog.kaggle.com/2015/08/12/july-2015-scripts-of-the-week",
"https://openwetware.org/wiki/Beauchamp:FreeSurfer",
"https://hal.archives-ouvertes.fr/hal-01848442",
"http://www.cs.ucl.ac.uk/staff/d.barber/brml",
"https://www.cea.fr",
"http://www.humanconnectome.org/data",
"https://www.mail-archive.com/freesurfer@nmr.mgh.harvard.edu",
"https://surfer.nmr.mgh.harvard.edu/fswiki/mri_normalize",
"https://launchpad.net",
"https://doi.org/10.7488/ds/1556",
"https://datashare.is.ed.ac.uk/handle/10283",
"https://imaging.mrc-cbu.cam.ac.uk/imaging/MniTalairach",
"https://www.nyu.edu/",
"https://speakerdeck.com/dengemann/",
"https://www.dtu.dk/english/service/phonebook/person",
"https://www.gnu.org/software/make/",
"https://www.macports.org/",
"https://hastie.su.domains/CASI",
"http://ilabs.washington.edu",
"https://psychophysiology.cpmc.columbia.edu",
"https://erc.easme-web.eu",
"https://www.crnl.fr",
"https://megcore.nih.gov/index.php/Staff",
r"ides\.html",
"{{inst.url}}",
]
linkcheck_anchors = False
linkcheck_timeout = 15
linkcheck_retries = 3
linkcheck_report_timeouts_as_broken = False
autosummary_generate = True
autodoc_default_options = {"inherited-members": None}
autodoc_typehints = "none"
bibtex_bibfiles = ["./references.bib"]
bibtex_style = "unsrt"
bibtex_footbibliography_header = ""
nitpicky = True
show_warning_types = True
nitpick_ignore = [
("py:class", "None. Remove all items from D."),
(
"py:class",
"v, remove specified key and return the corresponding value.",
),
("py:class", "an object providing a view on D's values"),
("py:class", "a shallow copy of D"),
("py:class", "(k, v), remove and return some (key, value) pair as a"),
("py:class", "_FuncT"),
("py:class", "mne.utils._logging._FuncT"),
("py:class", "None. Remove all items from od."),
]
nitpick_ignore_regex = [
("py:class", "a set-like object providing a view on D's (items|keys)"),
("py:class", r"None\. Update D from (dict|mapping)/iterable E and F\."),
("py:.*", r"mne\.io\.BaseRaw.*"),
("py:.*", r"mne\.BaseEpochs.*"),
("py:.*", r"mne\.io\..*\.Raw.*"),
("py:.*", r"mne\.epochs\.EpochsFIF.*"),
("py:.*", r"mne\.io\..*\.Epochs.*"),
(
"py:obj",
"(filename|metadata|proj|times|tmax|tmin|annotations|ch_names"
"|compensation_grade|duration|filenames|first_samp|first_time"
"|last_samp|n_times|proj|times|tmax|tmin)",
),
]
suppress_warnings = [
"image.nonlocal_uri",
]
versionlabels["versionadded"] = sphinx.locale._("New in v%s")
html_theme = "pydata_sphinx_theme"
switcher_version_match = "dev" if ".dev" in release else version
html_theme_options = {
"icon_links": [
dict(
name="Discord (office hours)",
url="https://discord.gg/rKfvxTuATa",
icon="fa-brands fa-discord fa-fw",
),
dict(
name="Mastodon",
url="https://fosstodon.org/@mne",
icon="fa-brands fa-mastodon fa-fw",
attributes=dict(rel="me"),
),
dict(
name="Q&A Forum",
url="https://mne.discourse.group/",
icon="fa-brands fa-discourse fa-fw",
),
dict(
name="Code Repository",
url="https://github.com/mne-tools/mne-python",
icon="fa-brands fa-github fa-fw",
),
dict(
name="Sponsor us on GitHub",
url="https://github.com/sponsors/mne-tools",
icon="fa-regular fa-heart fa-fw",
),
dict(
name="Donate via OpenCollective",
url="https://opencollective.com/mne-python",
icon="fa-custom fa-opencollective fa-fw",
),
],
"icon_links_label": "External Links",
"use_edit_page_button": False,
"navigation_with_keys": False,
"show_toc_level": 1,
"article_header_start": [],
"navbar_end": [
"theme-switcher",
"version-switcher",
"navbar-icon-links",
],
"navbar_align": "left",
"navbar_persistent": ["search-button"],
"footer_start": ["copyright"],
"secondary_sidebar_items": ["page-toc", "edit-this-page"],
"analytics": dict(google_analytics_id="G-5TBCPCRB6X"),
"switcher": {
"json_url": "https://mne.tools/dev/_static/versions.json",
"version_match": switcher_version_match,
},
"show_version_warning_banner": True,
"back_to_top_button": False,
}
html_logo = "_static/mne_logo_small.svg"
html_favicon = "_static/favicon.ico"
html_static_path = ["_static"]
html_css_files = [
"style.css",
]
html_js_files = [
("js/custom-icons.js", {"defer": "defer"}),
]
html_extra_path = [
"contributing.html",
"documentation.html",
"getting_started.html",
"install_mne_python.html",
]
html_sidebars = {
"index": ["sidebar-quicklinks.html"],
}
html_show_sourcelink = False
html_copy_source = False
html_show_sphinx = False
with open("_static/sponsors.yml") as fid:
sponsors_partners = safe_load(fid)
current = sponsors_partners.pop("current")
current_sponsors = list()
former_sponsors = list()
for key, val in sponsors_partners["sponsors"].items():
if "img" in val:
val["name"] = key
(current_sponsors if key in current else former_sponsors).append(val)
else:
assert "light" in val and "dark" in val
for mode in ("light", "dark"):
(current_sponsors if key in current else former_sponsors).append(
dict(
name=f"{key}{'_dk' if mode == 'dark' else ''}",
title=val["title"],
img=val[mode],
klass=f"only-{mode}",
)
)
current_institutions = list()
former_institutions = list()
for key, val in sponsors_partners["partner_institutions"].items():
if "img" in val:
val["name"] = key
(current_institutions if key in current else former_institutions).append(val)
else:
assert "light" in val and "dark" in val
for mode in ("light", "dark"):
(current_institutions if key in current else former_institutions).append(
dict(
name=f"{key}{'_dk' if mode == 'dark' else ''}",
title=val["title"],
img=val[mode],
klass=f"only-{mode}",
url=val["url"],
)
)
html_context = {
"default_mode": "auto",
"github_user": "mne-tools",
"github_repo": "mne-python",
"github_version": "main",
"doc_path": "doc",
"current_sponsors_partners": current,
"current_sponsors": current_sponsors,
"former_sponsors": former_sponsors,
"all_sponsors": [*current_sponsors, *former_sponsors],
"current_institutions": current_institutions,
"former_institutions": former_institutions,
"all_institutions": [*current_institutions, *former_institutions],
"carousel": [
dict(
title="Source Estimation",
text="Distributed, sparse, mixed-norm, beam\u00adformers, dipole fitting, and more.",
url="auto_tutorials/inverse/index.html",
img="sphx_glr_30_mne_dspm_loreta_008.gif",
alt="dSPM",
),
dict(
title="Machine Learning",
text="Advanced decoding models including time general\u00adiza\u00adtion.",
url="auto_tutorials/machine-learning/50_decoding.html",
img="sphx_glr_50_decoding_006.png",
alt="Decoding",
),
dict(
title="Encoding Models",
text="Receptive field estima\u00adtion with optional smooth\u00adness priors.",
url="auto_tutorials/machine-learning/30_strf.html",
img="sphx_glr_30_strf_001.png",
alt="STRF",
),
dict(
title="Statistics",
text="Parametric and non-parametric, permutation tests and clustering.",
url="auto_tutorials/stats-source-space/index.html",
img="sphx_glr_20_cluster_1samp_spatiotemporal_001.png",
alt="Clusters",
),
dict(
title="Connectivity",
text="All-to-all spectral and effective connec\u00adtivity measures.",
url="https://mne.tools/mne-connectivity/stable/auto_examples/mne_inverse_label_connectivity.html",
img="https://mne.tools/mne-connectivity/stable/_images/sphx_glr_mne_inverse_label_connectivity_001.png",
alt="Connectivity",
),
dict(
title="Data Visualization",
text="Explore your data from multiple perspectives.",
url="auto_tutorials/evoked/20_visualize_evoked.html",
img="sphx_glr_20_visualize_evoked_010.png",
alt="Visualization",
),
],
}
htmlhelp_basename = "mne-doc"
plot_include_source = True
plot_formats = [("png", 96)]
plot_html_show_formats = False
plot_html_show_source_link = False
font_size = 13 * 72 / 96.0
plot_rcparams = {
"font.size": font_size,
"axes.titlesize": font_size,
"axes.labelsize": font_size,
"xtick.labelsize": font_size,
"ytick.labelsize": font_size,
"legend.fontsize": font_size,
"figure.figsize": (6, 5),
"figure.subplot.bottom": 0.2,
"figure.subplot.left": 0.2,
"figure.subplot.right": 0.9,
"figure.subplot.top": 0.85,
"figure.subplot.wspace": 0.4,
"text.usetex": False,
}
latex_documents = []
latex_logo = "_static/logo.png"
latex_toplevel_sectioning = "part"
reset_warnings(None, None)
brand_icons = ("apple", "linux", "windows", "discourse", "python")
fixed_width_icons = (
"book",
"code-branch",
"newspaper",
"circle-question",
"quote-left",
"bug-slash",
"comment",
"computer-mouse",
"hand-sparkles",
"pencil",
"text-slash",
"universal-access",
"wand-magic-sparkles",
"discourse",
"python",
)
other_icons = (
"hand-paper",
"question",
"rocket",
"server",
"code",
"desktop",
"terminal",
"cloud-arrow-down",
"wrench",
"hourglass-half",
)
icon_class = dict()
for icon in brand_icons + fixed_width_icons + other_icons:
icon_class[icon] = ("fa-brands",) if icon in brand_icons else ("fa-solid",)
icon_class[icon] += ("fa-fw",) if icon in fixed_width_icons else ()
rst_prolog = ""
for icon, classes in icon_class.items():
rst_prolog += f"""
.. |{icon}| raw:: html
<i class="{" ".join(classes + (f"fa-{icon}",))}"></i>
"""
rst_prolog += """
.. |ensp| unicode:: U+2002 .. EN SPACE
.. currentmodule:: mne
"""
min_py = metadata("mne")["Requires-Python"].lstrip(" =<>")
rst_prolog += f"\n.. |min_python_version| replace:: {min_py}\n"
needed_plot_redirects = {
"10_epochs_overview.py",
"10_evoked_overview.py",
"10_overview.py",
"10_preprocessing_overview.py",
"10_raw_overview.py",
"10_reading_meg_data.py",
"15_handling_bad_channels.py",
"20_event_arrays.py",
"20_events_from_raw.py",
"20_reading_eeg_data.py",
"20_rejecting_bad_data.py",
"20_visualize_epochs.py",
"20_visualize_evoked.py",
"30_annotate_raw.py",
"30_epochs_metadata.py",
"30_filtering_resampling.py",
"30_info.py",
"30_reading_fnirs_data.py",
"35_artifact_correction_regression.py",
"40_artifact_correction_ica.py",
"40_autogenerate_metadata.py",
"40_sensor_locations.py",
"40_visualize_raw.py",
"45_projectors_background.py",
"50_artifact_correction_ssp.py",
"50_configure_mne.py",
"50_epochs_to_data_frame.py",
"55_setting_eeg_reference.py",
"59_head_positions.py",
"60_make_fixed_length_epochs.py",
"60_maxwell_filtering_sss.py",
"70_fnirs_processing.py",
"3d_to_2d.py",
"brainstorm_data.py",
"channel_epochs_image.py",
"cluster_stats_evoked.py",
"compute_csd.py",
"compute_mne_inverse_epochs_in_label.py",
"compute_mne_inverse_raw_in_label.py",
"compute_mne_inverse_volume.py",
"compute_source_psd_epochs.py",
"covariance_whitening_dspm.py",
"custom_inverse_solver.py",
"decoding_csp_eeg.py",
"decoding_csp_timefreq.py",
"decoding_spatio_temporal_source.py",
"decoding_spoc_CMC.py",
"decoding_time_generalization_conditions.py",
"decoding_unsupervised_spatial_filter.py",
"decoding_xdawn_eeg.py",
"define_target_events.py",
"dics_source_power.py",
"eeg_csd.py",
"eeg_on_scalp.py",
"eeglab_head_sphere.py",
"elekta_epochs.py",
"ems_filtering.py",
"eog_artifact_histogram.py",
"evoked_arrowmap.py",
"evoked_ers_source_power.py",
"evoked_topomap.py",
"evoked_whitening.py",
"fdr_stats_evoked.py",
"find_ref_artifacts.py",
"fnirs_artifact_removal.py",
"forward_sensitivity_maps.py",
"gamma_map_inverse.py",
"hf_sef_data.py",
"ica_comparison.py",
"interpolate_bad_channels.py",
"label_activation_from_stc.py",
"label_from_stc.py",
"label_source_activations.py",
"left_cerebellum_volume_source.py",
"limo_data.py",
"linear_model_patterns.py",
"linear_regression_raw.py",
"meg_sensors.py",
"mixed_norm_inverse.py",
"mixed_source_space_inverse.py",
"mne_cov_power.py",
"mne_helmet.py",
"mne_inverse_coherence_epochs.py",
"mne_inverse_envelope_correlation.py",
"mne_inverse_envelope_correlation_volume.py",
"mne_inverse_psi_visual.py",
"morph_surface_stc.py",
"morph_volume_stc.py",
"movement_compensation.py",
"movement_detection.py",
"multidict_reweighted_tfmxne.py",
"muscle_detection.py",
"opm_data.py",
"otp.py",
"parcellation.py",
"psf_ctf_label_leakage.py",
"psf_ctf_vertices.py",
"psf_ctf_vertices_lcmv.py",
"publication_figure.py",
"rap_music.py",
"trap_music.py",
"read_inverse.py",
"read_neo_format.py",
"read_noise_covariance_matrix.py",
"read_stc.py",
"receptive_field_mtrf.py",
"resolution_metrics.py",
"resolution_metrics_eegmeg.py",
"roi_erpimage_by_rt.py",
"sensor_noise_level.py",
"sensor_permutation_test.py",
"sensor_regression.py",
"shift_evoked.py",
"simulate_evoked_data.py",
"simulate_raw_data.py",
"simulated_raw_data_using_subject_anatomy.py",
"snr_estimate.py",
"source_label_time_frequency.py",
"source_power_spectrum.py",
"source_power_spectrum_opm.py",
"source_simulator.py",
"source_space_morphing.py",
"source_space_snr.py",
"source_space_time_frequency.py",
"ssd_spatial_filters.py",
"ssp_projs_sensitivity_map.py",
"temporal_whitening.py",
"time_frequency_erds.py",
"time_frequency_global_field_power.py",
"time_frequency_mixed_norm_inverse.py",
"time_frequency_simulated.py",
"topo_compare_conditions.py",
"topo_customized.py",
"vector_mne_solution.py",
"virtual_evoked.py",
"xdawn_denoising.py",
"xhemi.py",
}
api_redirects = {
"connectivity",
"covariance",
"creating_from_arrays",
"datasets",
"decoding",
"events",
"export",
"file_io",
"forward",
"inverse",
"logging",
"most_used_classes",
"mri",
"preprocessing",
"python_reference",
"reading_raw_data",
"realtime",
"report",
"sensor_space",
"simulation",
"source_space",
"statistics",
"time_frequency",
"visualization",
}
ex = "auto_examples"
co = "connectivity"
mne_conn = "https://mne.tools/mne-connectivity/stable"
tu = "auto_tutorials"
pr = "preprocessing"
di = "discussions"
sm = "source-modeling"
fw = "forward"
nv = "inverse"
sn = "stats-sensor-space"
sr = "stats-source-space"
sd = "sample-datasets"
ml = "machine-learning"
tf = "time-freq"
si = "simulation"
vi = "visualization"
custom_redirects = {
"credit": "credits/credit",
"funding": "credits/sponsors",
"install/contributing": "development/contributing",
"overview/cite": "documentation/cite",
"overview/get_help": "help/index",
"overview/people": "credits/leaders",
"overview/roadmap": "development/roadmap",
"whats_new": "development/whats_new",
f"{tu}/evoked/plot_eeg_erp": f"{tu}/evoked/30_eeg_erp",
f"{tu}/evoked/plot_whitened": f"{tu}/evoked/40_whitened",
f"{tu}/misc/plot_modifying_data_inplace": f"{tu}/intro/15_inplace",
f"{tu}/misc/plot_report": f"{tu}/intro/70_report",
f"{tu}/misc/plot_seeg": f"{tu}/clinical/20_seeg",
f"{tu}/misc/plot_ecog": f"{tu}/clinical/30_ecog",
f"{tu}/{ml}/plot_receptive_field": f"{tu}/{ml}/30_strf",
f"{tu}/{ml}/plot_sensors_decoding": f"{tu}/{ml}/50_decoding",
f"{tu}/{sm}/plot_background_freesurfer": f"{tu}/{fw}/10_background_freesurfer",
f"{tu}/{sm}/plot_source_alignment": f"{tu}/{fw}/20_source_alignment",
f"{tu}/{sm}/plot_forward": f"{tu}/{fw}/30_forward",
f"{tu}/{sm}/plot_eeg_no_mri": f"{tu}/{fw}/35_eeg_no_mri",
f"{tu}/{sm}/plot_background_freesurfer_mne": f"{tu}/{fw}/50_background_freesurfer_mne",
f"{tu}/{sm}/plot_fix_bem_in_blender": f"{tu}/{fw}/80_fix_bem_in_blender",
f"{tu}/{sm}/plot_compute_covariance": f"{tu}/{fw}/90_compute_covariance",
f"{tu}/{sm}/plot_object_source_estimate": f"{tu}/{nv}/10_stc_class",
f"{tu}/{sm}/plot_dipole_fit": f"{tu}/{nv}/20_dipole_fit",
f"{tu}/{sm}/plot_mne_dspm_source_localization": f"{tu}/{nv}/30_mne_dspm_loreta",
f"{tu}/{sm}/plot_dipole_orientations": f"{tu}/{nv}/35_dipole_orientations",
f"{tu}/{sm}/plot_mne_solutions": f"{tu}/{nv}/40_mne_fixed_free",
f"{tu}/{sm}/plot_beamformer_lcmv": f"{tu}/{nv}/50_beamformer_lcmv",
f"{tu}/{sm}/plot_visualize_stc": f"{tu}/{nv}/60_visualize_stc",
f"{tu}/{sm}/plot_eeg_mri_coords": f"{tu}/{nv}/70_eeg_mri_coords",
f"{tu}/{sd}/plot_brainstorm_phantom_elekta": f"{tu}/{nv}/80_brainstorm_phantom_elekta",
f"{tu}/{sd}/plot_brainstorm_phantom_ctf": f"{tu}/{nv}/85_brainstorm_phantom_ctf",
f"{tu}/{sd}/plot_phantom_4DBTi": f"{tu}/{nv}/90_phantom_4DBTi",
f"{tu}/{sd}/plot_brainstorm_auditory": f"{tu}/io/60_ctf_bst_auditory",
f"{tu}/{sd}/plot_sleep": f"{tu}/clinical/60_sleep",
f"{tu}/{di}/plot_background_filtering": f"{tu}/{pr}/25_background_filtering",
f"{tu}/{di}/plot_background_statistics": f"{tu}/{sn}/10_background_stats",
f"{tu}/{sn}/plot_stats_cluster_erp": f"{tu}/{sn}/20_erp_stats",
f"{tu}/{sn}/plot_stats_cluster_1samp_test_time_frequency": f"{tu}/{sn}/40_cluster_1samp_time_freq",
f"{tu}/{sn}/plot_stats_cluster_time_frequency": f"{tu}/{sn}/50_cluster_between_time_freq",
f"{tu}/{sn}/plot_stats_spatio_temporal_cluster_sensors": f"{tu}/{sn}/75_cluster_ftest_spatiotemporal",
f"{tu}/{sr}/plot_stats_cluster_spatio_temporal": f"{tu}/{sr}/20_cluster_1samp_spatiotemporal",
f"{tu}/{sr}/plot_stats_cluster_spatio_temporal_2samp": f"{tu}/{sr}/30_cluster_ftest_spatiotemporal",
f"{tu}/{sr}/plot_stats_cluster_spatio_temporal_repeated_measures_anova": f"{tu}/{sr}/60_cluster_rmANOVA_spatiotemporal",
f"{tu}/{sr}/plot_stats_cluster_time_frequency_repeated_measures_anova": f"{tu}/{sn}/70_cluster_rmANOVA_time_freq",
f"{tu}/{tf}/plot_sensors_time_frequency": f"{tu}/{tf}/20_sensors_time_frequency",
f"{tu}/{tf}/plot_ssvep": f"{tu}/{tf}/50_ssvep",
f"{tu}/{si}/plot_creating_data_structures": f"{tu}/{si}/10_array_objs",
f"{tu}/{si}/plot_point_spread": f"{tu}/{si}/70_point_spread",
f"{tu}/{si}/plot_dics": f"{tu}/{si}/80_dics",
f"{tu}/{tf}/plot_eyetracking": f"{tu}/{pr}/90_eyetracking_data",
f"{ex}/{co}/mne_inverse_label_connectivity": f"{mne_conn}/{ex}/mne_inverse_label_connectivity",
f"{ex}/{co}/cwt_sensor_connectivity": f"{mne_conn}/{ex}/cwt_sensor_connectivity",
f"{ex}/{co}/mixed_source_space_connectivity": f"{mne_conn}/{ex}/mixed_source_space_connectivity",
f"{ex}/{co}/mne_inverse_coherence_epochs": f"{mne_conn}/{ex}/mne_inverse_coherence_epochs",
f"{ex}/{co}/mne_inverse_connectivity_spectrum": f"{mne_conn}/{ex}/mne_inverse_connectivity_spectrum",
f"{ex}/{co}/mne_inverse_envelope_correlation_volume": f"{mne_conn}/{ex}/mne_inverse_envelope_correlation_volume",
f"{ex}/{co}/mne_inverse_envelope_correlation": f"{mne_conn}/{ex}/mne_inverse_envelope_correlation",
f"{ex}/{co}/mne_inverse_psi_visual": f"{mne_conn}/{ex}/mne_inverse_psi_visual",
f"{ex}/{co}/sensor_connectivity": f"{mne_conn}/{ex}/sensor_connectivity",
f"{ex}/{vi}/publication_figure": f"{tu}/{vi}/10_publication_figure",
f"{ex}/{vi}/sensor_noise_level": f"{tu}/{pr}/50_artifact_correction_ssp",
f"{ex}/{vi}/montage_sgskip": f"{ex}/{vi}/montage",
}
REDIRECT_TEMPLATE = """\
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="1; url={to}">
<script type="text/javascript">
window.location.href = "{to}"
</script>
<title>Page Redirection</title>
</head>
<body>
If you are not redirected automatically, follow this <a href='{to}'>link</a>.
</body>
</html>"""
def check_existing_redirect(path):
"""Make sure existing HTML files are redirects, before overwriting."""
if path.is_file():
with open(path) as fid:
for _ in range(8):
next(fid)
line = fid.readline()
if "Page Redirection" not in line:
raise RuntimeError(
"Attempted overwrite of HTML file with a redirect, where the "
"original file was not already a redirect."
)
def _check_valid_builder(app, exception):
valid_builder = isinstance(app.builder, sphinx.builders.html.StandaloneHTMLBuilder)
return valid_builder and exception is None
def make_gallery_redirects(app, exception):
"""Make HTML redirects for our sphinx gallery pages."""
if not _check_valid_builder(app, exception):
return
sg_conf = app.config["sphinx_gallery_conf"]
for src_dir, out_dir in zip(sg_conf["examples_dirs"], sg_conf["gallery_dirs"]):
root = (Path(app.srcdir) / src_dir).resolve()
fnames = [
pyfile.relative_to(root)
for pyfile in root.rglob(r"**/*.py")
if pyfile.name in needed_plot_redirects
]
for fname in fnames:
dirname = Path(app.outdir) / out_dir / fname.parent
to_fname = fname.with_suffix(".html").name
fr_fname = f"plot_{to_fname}"
to_path = dirname / to_fname
fr_path = dirname / fr_fname
assert to_path.is_file(), (fname, to_path)
with open(fr_path, "w") as fid:
fid.write(REDIRECT_TEMPLATE.format(to=to_fname))
sphinx_logger.info(
f"Added {len(fnames):3d} HTML plot_* redirects for {out_dir}"
)
def make_api_redirects(app, exception):
"""Make HTML redirects for our API pages."""
if not _check_valid_builder(app, exception):
return
for page in api_redirects:
fname = f"{page}.html"
fr_path = Path(app.outdir) / fname
to_path = Path(app.outdir) / "api" / fname
check_existing_redirect(fr_path)
with open(fr_path, "w") as fid:
fid.write(REDIRECT_TEMPLATE.format(to=to_path))
sphinx_logger.info(f"Added {len(api_redirects):3d} HTML API redirects")
def make_custom_redirects(app, exception):
"""Make HTML redirects for miscellaneous pages."""
if not _check_valid_builder(app, exception):
return
for _fr, _to in custom_redirects.items():
fr = f"{_fr}.html"
to = f"{_to}.html"
fr_path = Path(app.outdir) / fr
check_existing_redirect(fr_path)
if to.startswith("http"):
to_path = to
else:
to_path = Path(app.outdir) / to
assert to_path.is_file(), to_path
os.makedirs(Path(app.outdir) / "overview", exist_ok=True)
defunct_gallery_folders = (
"misc",
"discussions",
"source-modeling",
"sample-datasets",
"connectivity",
)
parts = fr_path.relative_to(Path(app.outdir)).parts
if (
len(parts) > 1
and parts[1] in defunct_gallery_folders
and not fr_path.parent.exists()
):
os.makedirs(fr_path.parent, exist_ok=True)
with open(fr_path, "w") as fid:
fid.write(REDIRECT_TEMPLATE.format(to=to_path))
sphinx_logger.info(f"Added {len(custom_redirects):3d} HTML custom redirects")
def make_version(app, exception):
"""Make a text file with the git version."""
if not (
isinstance(app.builder, sphinx.builders.html.StandaloneHTMLBuilder)
and exception is None
):
return
try:
stdout, _ = run_subprocess(["git", "rev-parse", "HEAD"], verbose=False)
except Exception as exc:
sphinx_logger.warning(f"Failed to write _version.txt: {exc}")
return
with open(os.path.join(app.outdir, "_version.txt"), "w") as fid:
fid.write(stdout)
sphinx_logger.info(f'Added "{stdout.rstrip()}" > _version.txt')
def rstjinja(app, docname, source):
"""Use Jinja to process the sponsors page."""
if app.builder.format != "html":
return
if docname == "credits/sponsors":
src = source[0]
rendered = app.builder.templates.render_string(src, app.config.html_context)
source[0] = rendered
def set_toc_level(app, pagename, templatename, context, doctree):
"""Show the auto-generated related-software subsections in the right sidebar."""
if pagename == "install/mne_tools_suite":
context["theme_show_toc_level"] = 2
def setup(app):
"""Set up the Sphinx app."""
app.connect("autodoc-process-docstring", append_attr_meth_examples)
app.connect("autodoc-process-docstring", fix_sklearn_inherited_docstrings)
app.connect("builder-inited", check_links, priority=5)
app.connect("builder-inited", generate_credit_rst, priority=10)
app.connect("builder-inited", report_scraper.set_dirs, priority=20)
app.connect("build-finished", make_gallery_redirects)
app.connect("build-finished", make_api_redirects)
app.connect("build-finished", make_custom_redirects)
app.connect("build-finished", make_version)
app.connect("source-read", rstjinja)
app.connect("html-page-context", set_toc_level)