From 72e5c1ba40010bfe5fb0da6c3e17ec002444ff0f Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Sun, 18 Oct 2026 12:27:09 +0200 Subject: [PATCH 1/3] Show the version in the docs title and drop the version notes above code blocks --- doc/AGENTS.md | 2 +- doc/CONVENTIONS.md | 11 +++--- doc/conf.py | 53 ++-------------------------- doc/sphinx_ext/fix_links/__init__.py | 1 - doc/sphinx_ext/fix_links/parser.py | 31 ++-------------- 5 files changed, 10 insertions(+), 88 deletions(-) diff --git a/doc/AGENTS.md b/doc/AGENTS.md index 044f13f0d..0afaa19b9 100644 --- a/doc/AGENTS.md +++ b/doc/AGENTS.md @@ -5,7 +5,7 @@ - `AGENTS.md` and `CONVENTIONS.md` are not part of the built documentation, `exclude_patterns` in `doc/conf.py` lists them. - Markdown is parsed by the `fix_links` extension (`doc/sphinx_ext/fix_links`), which extends MyST: among others, it - substitutes `{{ NAME }}` in code and renders banners on top of code blocks, see CONVENTIONS.md. + substitutes `{{ NAME }}` in code, see CONVENTIONS.md. - Check changes by building the documentation from the repository root, warnings are errors: ```bash diff --git a/doc/CONVENTIONS.md b/doc/CONVENTIONS.md index 88da68214..a0bfa7b83 100644 --- a/doc/CONVENTIONS.md +++ b/doc/CONVENTIONS.md @@ -159,12 +159,9 @@ documentation, the latest release. Likewise, the NVIDIA image of that release is release, the CUDA version of the image `latest` points to while that release is the newest, and `{{ CUDA_STRING }}` the PyTorch backend of that version (`cu` and the version without dots). Name another CUDA version only where the text is about choosing one; `tools/Docker/build.py --print_supported cuda|rocm` lists the -supported versions. -`fix_links` also renders a note on top of each code block that uses them (`fix_links_substitution_banners` in -`doc/conf.py`), with a separate text for `{{ FASTSURFER_VERSION }}`, `{{ CUDA_STRING }}` and both: that the commands -use the latest release (in the development documentation), and that images for other CUDA versions are available on -Docker Hub. Do not add such notes by hand. Where the version is not about an image, such as the folder of the macOS -package, use `{{ PACKAGE_VERSION }}`, the same version without the note. The download of the launcher uses +supported versions. Commands name these images without a note about the version: the title and the announcement bar +of each page say which version a tree documents. Where the version is not about an image, such as the folder of the +macOS package, use `{{ PACKAGE_VERSION }}`, the same version. The download of the launcher uses `{{ LAUNCHER_URL }}`, the copy published next to the pages of the same tree (`stable` or `dev`). `README.md` is shown on GitHub, where nothing is substituted, so it names the `stable` copy. @@ -176,7 +173,7 @@ and ROCm images of `pyproject.toml`, and `{{ CUDA_LEGACY_STRING }}` is the image The software in the images of that release, which is also what the native installation clones (`--branch stable`), comes with versions from the same tree: `{{ PYTHON_VERSION }}` is `tool.python.version` in `pyproject.toml`, `{{ UBUNTU_VERSION }}` the Ubuntu version of `tool.docker.runtime_base` in `pyproject.toml`, and -`{{ FREESURFER_VERSION }}` is `tool.freesurfer.version` in `pyproject.toml`. Code blocks that use them get no note. +`{{ FREESURFER_VERSION }}` is `tool.freesurfer.version` in `pyproject.toml`. reST files are not substituted, so commands there use the `latest` image instead: `deepmi/fastsurfer:latest` and `$HOME/my_singularity_images/fastsurfer-latest.sif`. diff --git a/doc/conf.py b/doc/conf.py index 3996eceb1..f44cd9504 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -192,7 +192,7 @@ def _tool_values_gitref(ref: str | None, *keys: str) -> tuple[str, ...]: # configure substitutions, fix_links also replaces string substitutions inside code, which MyST does not myst_substitutions = { "FASTSURFER_VERSION": image_version, - # the same version for the folder of the macOS package, without the note on docker images + # the same version, for the folder of the macOS package rather than an image "PACKAGE_VERSION": image_version, "CUDA_STRING": image_cuda, "CUDA_VERSION": version_cuda, @@ -246,7 +246,8 @@ def _tool_values_gitref(ref: str | None, *keys: str) -> tuple[str, ...]: # served next to the pages, so the launcher downloads from deep-mi.org/FastSurfer// html_extra_path = ["../tools/fastsurfer"] html_js_files = ["doc-version-link.js"] # points the announcement bar at the sibling doc tree -html_title = project +# top left and in the browser tab: the release this tree documents, or dev +html_title = f"{project} {image_version}" if documents_a_release else f"{project} dev" html_show_sphinx = False # Documentation to change footer icons: @@ -266,15 +267,6 @@ def _tool_values_gitref(ref: str | None, *keys: str) -> tuple[str, ...]: ], } -# doc.yml publishes each build to gh-pages under the ref it was built from, so that ref is what -# says whether this tree documents a release. It is read from the environment rather than from git, -# because actions/checkout leaves a detached HEAD and `git branch --show-current` is empty there. -# The tag pattern matches the release tags this project actually uses, all of them X.Y.Z. A tag -# with a suffix falls through to the development wording, which is the safe way round. doc.yml -# publishes no tags today, so only "stable" reaches this in practice. -publish_ref = os.environ.get("GITHUB_REF_NAME", "") -documents_a_release = publish_ref == "stable" or re.fullmatch(r"v\d+\.\d+\.\d+", publish_ref) is not None - # The announcement bar is the only site-wide notice furo offers, so it also carries the link to the # other published tree. The href here is a fallback that is only correct at the tree root; # doc-version-link.js rewrites it for the page it actually ends up on. @@ -428,42 +420,3 @@ def import_from_path(module_name, file_path): "/overview/intro": ("/index.rst", "/overview/index.rst"), } fix_links_project_root = Path("..") -# set of substitution names => text (one MyST markdown paragraph) of the note fix_links renders on top of each fenced -# code block that uses exactly these of the names used here as `{{ name }}`, sets without an entry get no note -# the commands use the official images, which may not match this tree's version (FastSurfer, default CUDA version) -_docker_hub = f"[Docker Hub](https://hub.docker.com/r/deepmi/fastsurfer/tags?name=v{image_version})" -_torch_docs_url = f"(https://pytorch.org/docs)" -if documents_a_release: - fix_links_substitution_banners = { - frozenset({"CUDA_STRING"}): ( - f"The commands below use {image_cuda}, which references the default CUDA version ({version_cuda}) of " - f"FastSurfer {image_version}. Other CUDA versions are supported by [PyTorch]({_torch_docs_url}), but " - f"depend on the PyTorch version. If you use `uv`, then `--torch-backend auto` automatically lets `uv` " - f"decide." - ), - frozenset({"FASTSURFER_VERSION", "CUDA_STRING"}): ( - f"The commands below use the tagged FastSurfer image `:{image_cuda}-v{image_version}`. CUDA {version_cuda} " - f"is the default CUDA version bundled in both `:latest` and that image. Images of {image_version} for " - f"other CUDA versions, ROCm and CPU are available on {_docker_hub}." - ), - } -else: - _latest_release_str = ( - f"This documents the development version {version}. Official Docker images only exist for releases, so the " - f"commands below use the latest release, {image_version}" - ) - _build_image = "To run the development version, {doc}`build your own image `." - fix_links_substitution_banners = { - frozenset({"FASTSURFER_VERSION"}): f"{_latest_release_str}. {_build_image}", - frozenset({"CUDA_STRING"}): ( - f"{_latest_release_str}. The commands below use {image_cuda}, which references the default CUDA version " - f"({version_cuda}) of FastSurfer {image_version}. The default PyTorch and CUDA versions might be different " - f"for this development version (see supported [PyTorch's documentation]({_torch_docs_url}). If you use " - f"`uv`, then `--torch-backend auto` automatically lets `uv` decide." - ), - frozenset({"FASTSURFER_VERSION", "CUDA_STRING"}): ( - f"{_latest_release_str}, for its default CUDA version, {version_cuda}. Images of {image_version} for other " - f"CUDA versions, ROCm and CPU are available on {_docker_hub}. {_build_image}" - ), - } - diff --git a/doc/sphinx_ext/fix_links/__init__.py b/doc/sphinx_ext/fix_links/__init__.py index 1fd8efbda..496389d20 100644 --- a/doc/sphinx_ext/fix_links/__init__.py +++ b/doc/sphinx_ext/fix_links/__init__.py @@ -14,7 +14,6 @@ def setup(app: Sphinx): app.add_config_value("fix_links_target", {}, "env", dict) app.add_config_value("fix_links_alternative_targets", {}, "env", dict) app.add_config_value("fix_links_project_root", Path("."), "env", Path) - app.add_config_value("fix_links_substitution_banners", {}, "env", dict) app.add_domain(MySTReplaceDomain) app.connect("missing-reference", resolve_xref) diff --git a/doc/sphinx_ext/fix_links/parser.py b/doc/sphinx_ext/fix_links/parser.py index 889e71c14..41c8945d2 100644 --- a/doc/sphinx_ext/fix_links/parser.py +++ b/doc/sphinx_ext/fix_links/parser.py @@ -1,5 +1,4 @@ import re -from collections.abc import Collection from functools import wraps from os.path import relpath from pathlib import Path @@ -18,18 +17,6 @@ _SUBSTITUTION = re.compile(r"\{\{\s*([A-Za-z_]\w*)\s*\}\}") -def find_banner_lines(text: str, names: Collection[str]) -> dict[int, set[str]]: - """Map the (1-based) numbers of lines in `text` that use substitutions in `names` to the names they use. - - Splits lines like markdown-it, so the numbers match the (docutils, 1-based) token maps of `text`. - """ - lines = {} - for lineno, line in enumerate(re.split(r"\r\n?|\n", text), start=1): - if used := {m[1] for m in _SUBSTITUTION.finditer(line)}.intersection(names): - lines[lineno] = used - return lines - - def wrap_include_run(method): @wraps(method) def _wrapper(include_instance: Include): @@ -58,24 +45,15 @@ class Renderer(SphinxRenderer): """ Renderer object to automatically fix headings that are not consecutive levels in (included) Markdown files. Also includes alternative targets into anchors that - are rendered, but do not match a target, renders a note on top of fenced code - blocks that use substitutions listed in `fix_links_substitution_banners`, and - replaces substitutions inside code, which MyST does not. + are rendered, but do not match a target, and replaces substitutions inside code, + which MyST does not. """ def __init__(self, parser: MarkdownIt): self._heading_base: Optional[int] = None - # line => substitution names for the outermost fence containing that line, see find_banner_lines - self.banner_lines: dict[int, set[str]] = {} super().__init__(parser) def render_fence(self, token: SyntaxTreeNode) -> None: - if token.map: - # popping the lines keeps nested fences and the note itself from getting the note again - used = set().union(*(self.banner_lines.pop(line, ()) for line in range(*token.map))) - # a single paragraph keeps the note short, so it pops no lines of a following fence - if note := self.sphinx_env.config.fix_links_substitution_banners.get(frozenset(used)): - self.nested_render_text(f"```{{note}}\n{note}\n```", token.map[0] - 1) self._substitute_in_code(token) super().render_fence(token) @@ -228,10 +206,6 @@ def parse(self, inputstring: str, document: nodes.document) -> None: ) config = merge_file_level(config, topmatter, warning) - # banners are looked up by the lines of inputstring, which the token maps index - banner_names = set().union(*env.config.fix_links_substitution_banners) - banner_lines = find_banner_lines(inputstring, banner_names) - from contextlib import contextmanager @contextmanager @@ -243,7 +217,6 @@ def _restore(node, cfg_name: str, values: tuple[str]): cfg["."] = before parser = create_md_parser(config, Renderer) - cast(Renderer, parser.renderer).banner_lines = banner_lines with _restore( env.config, "fix_links_alternative_targets", From 078e88c84df8f61399d5050c7485cd91275daa29 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Sun, 18 Oct 2026 12:31:10 +0200 Subject: [PATCH 2/3] Shorten the title of the container page to Containers --- doc/overview/CONTAINERS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/overview/CONTAINERS.md b/doc/overview/CONTAINERS.md index 3c1d7f5b8..4d7cdf08a 100644 --- a/doc/overview/CONTAINERS.md +++ b/doc/overview/CONTAINERS.md @@ -1,5 +1,5 @@ -Running FastSurfer in a container -================================ +Containers +========== This page explains the commands that run FastSurfer in our Apptainer (Singularity) or Docker images: the flags of the container, and how FastSurfer's own flags work inside it. To get an image, see the installation page of your system ([Linux](install/LINUX.md), [Windows](install/WINDOWS.md), [Intel Macs](install/MACOS.md#docker-intel-macs)); the From e625b6a1ab85ff55faca874b8459642c2c95dcba Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Sun, 18 Oct 2026 12:38:07 +0200 Subject: [PATCH 3/3] Use title case in page titles and add the launcher to the command reference --- doc/developer/GIT-HOOKS.md | 2 +- doc/developer/docker.rst | 2 +- doc/overview/CONTAINERS.md | 4 ++-- doc/overview/OUTPUT_FILES.md | 2 +- doc/overview/install/NATIVE.md | 2 +- doc/scripts/LAUNCHER.md | 13 +++++++++++++ doc/scripts/advanced.rst | 2 +- doc/scripts/index.rst | 7 ++++--- doc/scripts/recon_surf.rst | 2 +- doc/scripts/recon_surfreg.rst | 2 +- 10 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 doc/scripts/LAUNCHER.md diff --git a/doc/developer/GIT-HOOKS.md b/doc/developer/GIT-HOOKS.md index bd044221c..c487a38c9 100644 --- a/doc/developer/GIT-HOOKS.md +++ b/doc/developer/GIT-HOOKS.md @@ -1,4 +1,4 @@ -Git hooks +Git Hooks ========= The FastSurfer team has developed a pre-commit hook script to help implement diff --git a/doc/developer/docker.rst b/doc/developer/docker.rst index e888c8094..667b4a544 100644 --- a/doc/developer/docker.rst +++ b/doc/developer/docker.rst @@ -1,4 +1,4 @@ -Building Docker images +Building Docker Images ---------------------- .. include:: ../../tools/Docker/README.md diff --git a/doc/overview/CONTAINERS.md b/doc/overview/CONTAINERS.md index 4d7cdf08a..ff96b5620 100644 --- a/doc/overview/CONTAINERS.md +++ b/doc/overview/CONTAINERS.md @@ -92,8 +92,8 @@ paths in the arguments, so relative paths only work inside the current directory images are installed, such a program uses the newest instead of asking. `--print_only` prints the commands instead of running them, `--update_launcher` -replaces the launcher with the current one, and `--help` lists all launcher options. The sections -below explain the commands it runs. +replaces the launcher with the current one, and `--help` lists all launcher options, as does its +[reference](../scripts/LAUNCHER.md). The sections below explain the commands it runs. Before starting many runs at once, e.g. a SLURM array, run the launcher once (e.g. on one subject), so the image is downloaded and built only once rather than by every job. diff --git a/doc/overview/OUTPUT_FILES.md b/doc/overview/OUTPUT_FILES.md index 1878ac5a9..85ce63e3d 100644 --- a/doc/overview/OUTPUT_FILES.md +++ b/doc/overview/OUTPUT_FILES.md @@ -1,4 +1,4 @@ -Output files +Output Files ============ Segmentation module ------------------- diff --git a/doc/overview/install/NATIVE.md b/doc/overview/install/NATIVE.md index f98d663f1..956a8ff15 100644 --- a/doc/overview/install/NATIVE.md +++ b/doc/overview/install/NATIVE.md @@ -1,4 +1,4 @@ -Installing from source +Installing from Source ====================== A native installation runs FastSurfer directly on your system, without a container. It is the setup for developers and for systems where containers are not available. You install all dependencies yourself (system packages, Python diff --git a/doc/scripts/LAUNCHER.md b/doc/scripts/LAUNCHER.md new file mode 100644 index 000000000..5b53081f3 --- /dev/null +++ b/doc/scripts/LAUNCHER.md @@ -0,0 +1,13 @@ +fastsurfer (launcher) +===================== +`fastsurfer` runs FastSurfer with one command on every system: in its container image with Apptainer, Singularity or +Docker, or on a Mac with Apple silicon with the macOS package. It takes the flags of +[run_fastsurfer.sh](RUN_FASTSURFER.md) and needs only Python, not FastSurfer itself. +[The launcher](../overview/CONTAINERS.md#the-launcher) explains how it chooses the image, and the +[quick start](../overview/QUICKSTART.md) how to download it. + +Usage +----- +```{command-output} python3 tools/fastsurfer --help +:cwd: /../ +``` diff --git a/doc/scripts/advanced.rst b/doc/scripts/advanced.rst index 6772aa68b..b0718803a 100644 --- a/doc/scripts/advanced.rst +++ b/doc/scripts/advanced.rst @@ -1,4 +1,4 @@ -Running modules directly +Running Modules Directly ======================== ``run_fastsurfer.sh`` runs all modules for you. These pages describe the scripts of the individual modules, for experts diff --git a/doc/scripts/index.rst b/doc/scripts/index.rst index 48f63fd88..4afc69da2 100644 --- a/doc/scripts/index.rst +++ b/doc/scripts/index.rst @@ -1,13 +1,14 @@ Command Reference ================= -All options of FastSurfer's commands: ``run_fastsurfer.sh`` and its wrappers for longitudinal, batch, SLURM and BIDS -processing, the scripts that run a single module directly, utilities, and the scripts for training the networks. To get -started, see the :doc:`User Guide `. +All options of FastSurfer's commands: the launcher ``fastsurfer``, ``run_fastsurfer.sh`` and its wrappers for +longitudinal, batch, SLURM and BIDS processing, the scripts that run a single module directly, utilities, and the +scripts for training the networks. To get started, see the :doc:`User Guide `. .. toctree:: :maxdepth: 2 + LAUNCHER.md RUN_FASTSURFER.md long_fastsurfer.rst BATCH.md diff --git a/doc/scripts/recon_surf.rst b/doc/scripts/recon_surf.rst index 7e9c7d378..1154cf3f5 100644 --- a/doc/scripts/recon_surf.rst +++ b/doc/scripts/recon_surf.rst @@ -1,4 +1,4 @@ -Surface pipeline: recon-surf.sh +Surface Pipeline: recon-surf.sh =============================== .. note:: diff --git a/doc/scripts/recon_surfreg.rst b/doc/scripts/recon_surfreg.rst index 911e6a96e..9463eee24 100644 --- a/doc/scripts/recon_surfreg.rst +++ b/doc/scripts/recon_surfreg.rst @@ -1,4 +1,4 @@ -Surface registration: recon-surfreg.sh +Surface Registration: recon-surfreg.sh ====================================== ``recon-surfreg.sh`` adds the spherical registration (``?h.sphere`` and ``?h.sphere.reg``) to a subject that was