Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
db57dbc
Add a launcher that runs FastSurfer in its container image
m-reuter Oct 11, 2026
cf0e303
Point Apple silicon Macs to the macOS package and check Docker's memo…
m-reuter Oct 11, 2026
5ca25e9
Run the newest release in the launcher unless --image_version names a…
m-reuter Oct 11, 2026
0739b85
Tell launcher users how far an older version is behind, and recommend…
m-reuter Oct 11, 2026
751088a
Handle GPU lists and --viewagg_device in the launcher, and fix five r…
m-reuter Oct 12, 2026
1a949d4
Let the launcher pick the best supported GPU when none is named
m-reuter Oct 12, 2026
799d88d
Reject downward GPU ranges and mount subject-specific output paths in…
m-reuter Oct 12, 2026
817b26e
Allow GPU lists and ranges in the launcher only with --brun
m-reuter Oct 13, 2026
dde6b40
Build launcher images atomically, follow symlinked inputs and stop on…
m-reuter Oct 13, 2026
ddabd86
Replace --image_version with one --image flag, prefer installed image…
m-reuter Oct 13, 2026
fa627b4
Count installed images of older types that run on the machine in the …
m-reuter Oct 13, 2026
e6d3cab
Pass Ctrl-C through to FastSurfer in Docker and build Apptainer image…
m-reuter Oct 13, 2026
1f24b33
Show local time in Docker logs and suggest the installed newest relea…
m-reuter Oct 13, 2026
3766183
Select a supported GPU for release images named with --image in the l…
m-reuter Oct 13, 2026
e85b4c6
Check that the launcher's GPU table covers the current CUDA builds
m-reuter Oct 13, 2026
065ca38
Name expired browser logins in the launcher's hint for failed Apptain…
m-reuter Oct 13, 2026
a025cbf
Build official images in Apptainer without stored Docker Hub logins
m-reuter Oct 13, 2026
03497f1
Say there are no Docker images instead of no release in the launcher
m-reuter Oct 13, 2026
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
53 changes: 53 additions & 0 deletions doc/overview/CONTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,59 @@ define access to data and hardware such as graphics cards, and the options of Fa
the container passes on to `run_fastsurfer.sh`. It is useful to think of the image as a "hard drive" and the container
as a "simulated computer inside the computer".

The launcher
------------
`fastsurfer_container.py` writes and runs the container command for you. It takes the same flags as
`run_fastsurfer.sh` and needs only Python 3.8 or newer, not FastSurfer itself. It

- uses Apptainer, Singularity or Docker, whichever is installed (choose one with `--runtime`),
- picks the image that runs on your NVIDIA GPU and driver, or the CPU image without one, see
[which image fits your GPU](install/LINUX.md#nvidia-gpus); with several GPUs and no
`--device cuda:<index>`, it uses the newest one with enough memory and says which,
- downloads the image once (for Apptainer, into `$HOME/my_singularity_images`),
- shares the files and directories the flags name with the container, at the same paths, so relative
paths work too,
- passes the FreeSurfer license from `$FS_LICENSE` if `--fs_license` is not given, and runs the
container as you.

Download it, and run FastSurfer:

```bash
curl -fsSLO https://raw.githubusercontent.com/Deep-MI/FastSurfer/stable/tools/fastsurfer_container.py
python3 fastsurfer_container.py \
--t1 $HOME/my_mri_data/subjectX/t1_weighted.nii.gz \
--sid subjectX --sd $HOME/my_fastsurfer_analysis \
--3T --threads 4
```

The first run downloads the newest FastSurfer release. Later runs use the image already installed,
also one of an earlier release or CUDA version, as long as it runs on the machine, and also when a
newer release comes out: the launcher then says so, and `--update` switches to it. It
says which image it runs, e.g. `cu132-v2.6.0`. To keep a study on exactly that image, pass it with
`--image cu132-v2.6.0`: the image type matters as well as the version, because the CPU and the GPU
images do not compute exactly the same numbers.

`--image` also takes a version alone (`--image 2.6.0`, the image type is then chosen for the
machine), your own Docker image, or an Apptainer `.sif` file. If several FastSurfer images are
installed, the launcher asks which one to use. It stops if an installed image differs from the
release of the same name, e.g. a local build; `--update` replaces it with the release. For an older
version, it says how many releases it is behind. A GPU that the newest images no longer support runs
them on the CPU. The image of an older release may still support that GPU, but the launcher
recommends the newest release on the CPU, or a machine with a newer GPU, over an old version.

With `--brun`, it runs `brun_fastsurfer.sh` for several subjects; the paths in a subjects list have
to be absolute. `--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.

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.

On a Mac with Apple silicon, use the [macOS package](install/MACOS.md) instead: the images are built for
Intel and AMD processors, which such a Mac only emulates, much more slowly and without its GPU. The
launcher therefore stops there and points to the package, unless you pass `--runtime docker`. With
Docker, it also warns if Docker may use less memory than FastSurfer needs.

Using Apptainer (or Singularity)
--------------------------------
[Apptainer](https://apptainer.org) is the open-source continuation of Singularity, and both run the same images.
Expand Down
59 changes: 59 additions & 0 deletions test/lint/test_launcher_builds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2026 DeepMI Lab, German Center for Neurodegenerative Diseases (DZNE), Bonn
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Guard the copy of the CUDA images in tools/fastsurfer_container.py.

The launcher runs with any system Python and without FastSurfer installed, so it cannot import
FastSurferCNN/gpu_support.py and keeps its own copy of the CUDA builds and their minimum drivers.
A copy left behind when the CUDA versions change makes it pick an image that is not released.
"""

import importlib.util
import sys
import tomllib
from pathlib import Path

FASTSURFER_HOME = Path(__file__).parent.parent.parent


def _load(name: str, path: Path):
# from the path, because the lint job installs nothing but pytest; registered in sys.modules,
# which dataclasses need
spec = importlib.util.spec_from_file_location(name, path)
module = sys.modules.setdefault(name, importlib.util.module_from_spec(spec))
spec.loader.exec_module(module)
return module


launcher = _load("fastsurfer_container", FASTSURFER_HOME / "tools" / "fastsurfer_container.py")
gpu_support = _load("gpu_support_for_lint", FASTSURFER_HOME / "FastSurferCNN" / "gpu_support.py")


def test_launcher_builds_match_gpu_support():
assert launcher.NEWEST_BUILD == gpu_support.NEWEST_BUILD
assert launcher.LEGACY_BUILD == gpu_support.LEGACY_BUILD
for (major, _), _ in (launcher.NEWEST_BUILD, launcher.LEGACY_BUILD):
assert launcher.MIN_DRIVER[major] == gpu_support.MIN_DRIVER[major]


def test_launcher_capabilities_cover_builds():
# the GPU range of each build comes from its PyTorch, so a new build needs its entry checked by hand
assert set(launcher.MIN_CAPABILITY) == {launcher.NEWEST_BUILD[1], launcher.LEGACY_BUILD[1]}


def test_launcher_default_build_matches_pyproject():
with open(FASTSURFER_HOME / "pyproject.toml", "rb") as fp:
cuda = tomllib.load(fp)["tool"]["cuda"]["version"]
assert launcher.NEWEST_BUILD[0] == tuple(int(v) for v in cuda.split("."))
Loading
Loading