Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 4 additions & 7 deletions doc/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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`.
53 changes: 3 additions & 50 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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/<stable|dev>/
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:
Expand All @@ -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.
Expand Down Expand Up @@ -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 </developer/docker>`."
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}"
),
}

2 changes: 1 addition & 1 deletion doc/developer/GIT-HOOKS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Git hooks
Git Hooks
=========

The FastSurfer team has developed a pre-commit hook script to help implement
Expand Down
2 changes: 1 addition & 1 deletion doc/developer/docker.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Building Docker images
Building Docker Images
----------------------

.. include:: ../../tools/Docker/README.md
Expand Down
8 changes: 4 additions & 4 deletions doc/overview/CONTAINERS.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion doc/overview/OUTPUT_FILES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Output files
Output Files
============
Segmentation module
-------------------
Expand Down
2 changes: 1 addition & 1 deletion doc/overview/install/NATIVE.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 13 additions & 0 deletions doc/scripts/LAUNCHER.md
Original file line number Diff line number Diff line change
@@ -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: /../
```
2 changes: 1 addition & 1 deletion doc/scripts/advanced.rst
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 4 additions & 3 deletions doc/scripts/index.rst
Original file line number Diff line number Diff line change
@@ -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 </overview/index>`.
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 </overview/index>`.

.. toctree::
:maxdepth: 2

LAUNCHER.md
RUN_FASTSURFER.md
long_fastsurfer.rst
BATCH.md
Expand Down
2 changes: 1 addition & 1 deletion doc/scripts/recon_surf.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Surface pipeline: recon-surf.sh
Surface Pipeline: recon-surf.sh
===============================

.. note::
Expand Down
2 changes: 1 addition & 1 deletion doc/scripts/recon_surfreg.rst
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion doc/sphinx_ext/fix_links/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
31 changes: 2 additions & 29 deletions doc/sphinx_ext/fix_links/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import re
from collections.abc import Collection
from functools import wraps
from os.path import relpath
from pathlib import Path
Expand All @@ -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):
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down
Loading