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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
/tools/macos_build
/srun_fastsurfer.sh
/requirements*.txt
!/requirements.txt
2 changes: 1 addition & 1 deletion long_fastsurfer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ function check_stage_dependencies() {
done
}

# Warning if run as root user
# Check for invalid docker/root user setup
check_allow_root "${brun_flags[@]}" # --allow_root must be passed to brun

# Validate required inputs based on which stages will run
Expand Down
10 changes: 9 additions & 1 deletion recon_surf/functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ function auto_detect_fs_license()
function check_allow_root()
{
# Will check, if --allow_root is in arguments (to this function) and print an error message
# as well as exit.
# as well as exit. Also checks for the default docker user, which indicates a missing --user mapping.
# Examples:
# check_allow_root --arg 0 -> message and exit
#
Expand All @@ -252,6 +252,14 @@ function check_allow_root()
echo " If you want to force running as root, you may pass --allow_root to $(basename "$BASH_ARGV0")."
exit 1
fi
elif [[ -n "${FASTSURFER_DOCKER_DEFAULT_USER:-}" ]] \
&& [[ "$(id -un 2> /dev/null || true)" == "$FASTSURFER_DOCKER_DEFAULT_USER" ]]
then
echo "ERROR: You are trying to run '$(basename "$BASH_ARGV0")' as the default FastSurfer docker user"
echo " '$FASTSURFER_DOCKER_DEFAULT_USER'. This usually means the container was started without mapping"
echo " your host user into the container."
echo " Please run docker with '-u \$(id -u):\$(id -g)' (see https://docs.docker.com/engine/reference/run/#user)."
exit 1
fi
}

Expand Down
195 changes: 90 additions & 105 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,120 +1,105 @@
#
# This file is autogenerated by kueglerd from fastsurfer:2.4.0-rc1
# This file is autogenerated by pollakc from fastsurfer-dev-3c52a21.sif
# by the following command from FastSurfer:
#
# env/export_pip-r.sh requirements.txt fastsurfer:2.4.0-rc1
# tools/export_pip-r.sh requirements.txt fastsurfer-dev-3c52a21.sif
#
# Which ran the following command:
# docker run --rm -u <user_id>:<group_id> --entrypoint /bin/bash fastsurfer:2.4.0-rc1 -c 'python --version && pip list --format=freeze --no-color --disable-pip-version-check --no-input --no-cache-dir'
# apptainer exec fastsurfer-dev-3c52a21.sif /bin/bash -c 'python --version && pip list --format=freeze --no-color --disable-pip-version-check --no-input --no-cache-dir'
#
#
# Image was configured for cu126
# using python version 3.10.16
# Backend-neutral pinned requirements for FastSurfer.
#
--extra-index-url https://download.pytorch.org/whl/cu126

# Python 3.10.16
absl-py==2.1.0
Brotli==1.1.0
cached-property==1.5.2
certifi==2024.12.14
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
contourpy==1.3.1
# Image was configured for cu118 using python version 3.12.12.
#
# This file pins the shared Python dependency set, but intentionally does not
# pin backend-specific PyTorch wheel variants, PyTorch index URLs, nvidia-*
# runtime packages, or triton. Select those at install time with uv's
# --torch-backend option, for example:
#
# uv pip compile --torch-backend=cu128 requirements.txt | uv pip sync --torch-backend=cu128 -
# uv pip compile --torch-backend=cpu requirements.txt | uv pip sync --torch-backend=cpu -
#
# To refresh this file from a container image, run:
#
# tools/export_pip-r.sh requirements.txt fastsurfer-dev-3c52a21.sif
#
absl-py==2.4.0
annotated-doc==0.0.4
certifi==2026.4.22
charset-normalizer==3.4.7
click==8.3.3
contourpy==1.3.3
cycler==0.12.1
Deprecated==1.2.18
filelock==3.17.0
fonttools==4.55.8
fsspec==2024.12.0
grpcio==1.67.1
h2==4.1.0
h5py==3.12.1
hpack==4.1.0
humanize==4.11.0
hyperframe==6.1.0
idna==3.10
imagecodecs==2024.12.30
imageio==2.37.0
importlib_metadata==8.6.1
importlib_resources==6.5.2
Jinja2==3.1.5
joblib==1.4.2
kiwisolver==1.4.7
lapy==1.2.0
lazy_loader==0.4
Markdown==3.6
markdown-it-py==3.0.0
MarkupSafe==3.0.2
matplotlib==3.10.0
Deprecated==1.3.1
einops==0.8.2
filelock==3.29.0
fonttools==4.62.1
fsspec==2026.4.0
glfw==2.10.0
grpcio==1.80.0
h5py==3.16.0
humanize==4.15.0
idna==3.15
ImageIO==2.37.3
jaxtyping==0.3.9
Jinja2==3.1.6
joblib==1.5.3
kiwisolver==1.5.0
lapy==1.6.0
lazy-loader==0.5
Markdown==3.10.2
markdown-it-py==4.2.0
MarkupSafe==3.0.3
matplotlib==3.10.9
mdurl==0.1.2
meshpy==2025.1.1
monai==1.5.2
mpmath==1.3.0
munkres==1.1.4
narwhals==1.24.1
networkx==3.4.2
neuroreg==0.6.0
nibabel==5.3.2
numpy==1.26.4
nvidia-cublas-cu12==12.6.4.1
nvidia-cuda-cupti-cu12==12.6.80
nvidia-cuda-nvrtc-cu12==12.6.77
nvidia-cuda-runtime-cu12==12.6.77
nvidia-cudnn-cu12==9.5.1.17
nvidia-cufft-cu12==11.3.0.4
nvidia-curand-cu12==10.3.7.77
nvidia-cusolver-cu12==11.7.1.2
nvidia-cusparse-cu12==12.5.4.2
nvidia-cusparselt-cu12==0.6.3
nvidia-nccl-cu12==2.21.5
nvidia-nvjitlink-cu12==12.6.85
nvidia-nvtx-cu12==12.6.77
packaging==24.2
pandas==2.2.3
pillow==11.1.0
pip==25.0
plotly==6.0.0
protobuf==5.29.5
psutil==6.1.1
pycparser==2.22
Pygments==2.19.1
pyparsing==3.2.1
PySide6==6.8.1
PySocks==1.7.1
multipledispatch==1.0.0
narwhals==2.21.0
networkx==3.6.1
neurolit==0.6.1
neuroreg==0.6.1
nibabel==5.4.2
numpy==2.4.4
packaging==26.2
pandas==3.0.3
pillow==12.2.0
pip==26.1.1
platformdirs==4.9.6
plotly==6.7.0
protobuf==7.34.1
psutil==7.2.2
Pygments==2.20.0
PyOpenGL==3.1.10
pyparsing==3.3.2
pyrr==0.10.3
python-dateutil==2.9.0.post0
pytz==2024.1
PyWavelets==1.8.0
PyYAML==6.0.2
requests==2.32.4
rich==13.9.4
scikit-image==0.25.1
scikit-learn==1.6.1
scikit-sparse==0.4.15
scipy==1.15.1
setuptools==78.1.1
PyYAML==6.0.3
requests==2.34.1
rich==15.0.0
scikit-image==0.26.0
scikit-learn==1.8.0
scipy==1.17.1
setuptools==82.0.1
shellingham==1.5.4
shiboken6==6.8.1
SimpleITK==2.4.1
simpleitk==2.5.5
six==1.17.0
sympy==1.13.1
tensorboard==2.18.0
tensorboard_data_server==0.7.0
threadpoolctl==3.5.0
tifffile==2025.1.10
torch==2.7.1+cpu
torchio==0.20.4
torchvision==0.21.0+cu126
tornado==6.5.1
tqdm==4.67.1
triton==3.2.0
typer==0.15.1
typing_extensions==4.12.2
tzdata==2025.1
unicodedata2==16.0.0
urllib3==2.5.0
Werkzeug==3.1.3
wheel==0.45.1
wrapt==1.17.2
sympy==1.14.0
tensorboard==2.20.0
tensorboard-data-server==0.7.2
threadpoolctl==3.6.0
tifffile==2026.5.2
torch==2.7.1
torchio==1.2.0
torchvision==0.22.1
tqdm==4.67.3
typer==0.25.1
typing_extensions==4.15.0
urllib3==2.7.0
wadler_lindig==0.1.7
Werkzeug==3.1.8
whippersnappy==2.1.0
wrapt==2.1.2
yacs==0.1.8
zipp==3.21.0
zstandard==0.23.0
2 changes: 1 addition & 1 deletion run_fastsurfer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ fi

source "${reconsurfdir}/functions.sh"

# Warning if run as root user
# Check for invalid docker/root user setup
check_allow_root "${allow_root[@]}"

# from now to the creation of the logfile, all messages are only written to the console and thus lost if the output is
Expand Down
19 changes: 16 additions & 3 deletions tools/Docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,25 @@ ARG FASTSURFER_VERSION
ARG GIT_HASH
ARG VENDOR
ARG BUILD_BASE_IMAGE
ARG PINNED_REQUIREMENTS=false

SHELL ["/bin/bash", "--login", "-e", "-c"]

# Install the project's dependencies using the lockfile and settings
# Install the project's dependencies using either pyproject.toml or the backend-neutral pinned requirements.
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=pyproject.toml,target=/install/pyproject.toml <<EOF
--mount=type=bind,source=pyproject.toml,target=/install/pyproject.toml \
--mount=type=bind,source=requirements.txt,target=/install/requirements.txt <<EOF
uv venv --python ${PYTHON_VERSION} --relocatable /venv --prompt fastsurfer
# activate the virtual environment
source /venv/bin/activate
# uv pip sync only installs "exactly" the packages in the lockfile, but does not resolve dependencies
# therefore, we first compile the environment to resolve dependencies, and then sync to install both directly and
# indirectly required packages
uv pip compile --torch-backend=${DEVICE} --extra container /install/pyproject.toml | uv pip sync --torch-backend=${DEVICE} -
if [[ "$PINNED_REQUIREMENTS" == "true" ]]; then
uv pip compile --torch-backend=${DEVICE} /install/requirements.txt | uv pip sync --torch-backend=${DEVICE} -
else
uv pip compile --torch-backend=${DEVICE} --extra container /install/pyproject.toml | uv pip sync --torch-backend=${DEVICE} -
fi
EOF

LABEL org.opencontainers.image.authors="$AUTHOR" \
Expand Down Expand Up @@ -290,6 +296,8 @@ EOF
# DO_NOT_SEARCH_FS_LICENSE_IN_FREESURFER_HOME=true deactivates the search for FS_LICENSE in FREESURFER_HOME
# XDG_DATA_HOME pins neurolit platformdirs data files to a deterministic,
# runtime-writable image path outside the temporary directories.
# FASTSURFER_DOCKER_DEFAULT_USER identifies the image user used when docker is
# run without an explicit --user mapping.
ENV OS=Linux \
FS_OVERRIDE=0 \
FIX_VERTEX_AREA="" \
Expand All @@ -303,6 +311,7 @@ ENV OS=Linux \
PATH=/venv/bin:/opt/freesurfer/bin:$PATH \
MPLCONFIGDIR=/tmp/matplotlib-config \
XDG_DATA_HOME=/var/.local/share \
FASTSURFER_DOCKER_DEFAULT_USER=nonroot \
DO_NOT_SEARCH_FS_LICENSE_IN_FREESURFER_HOME="true"

# Copy fastsurfer venv and pruned freesurfer from build images
Expand Down Expand Up @@ -338,6 +347,10 @@ SHELL ["/bin/bash", "--login", "-c"]

# Set FastSurfer workdir and entrypoint
# the script entrypoint ensures that our venv is active
RUN <<EOF
getent group nonroot > /dev/null || groupadd --system nonroot
id -u nonroot > /dev/null 2>&1 || useradd --system --gid nonroot --no-create-home --home-dir /nonexistent --shell /usr/sbin/nologin nonroot
EOF
USER nonroot
WORKDIR "/fastsurfer"
ENTRYPOINT ["/fastsurfer/tools/Docker/entrypoint.sh","/fastsurfer/run_fastsurfer.sh"]
Expand Down
6 changes: 4 additions & 2 deletions tools/Docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ docker run --gpus all -v $HOME/my_mri_data:/data \
### Docker Flags
* `--gpus`: This argument is used to access GPU resources. With it, you can also specify how many GPUs to use. In the example above, `all` will make every GPU available to FastSurfer in the Docker container. To use a single one (e.g. GPU 0), set `--gpus device=0`. To use multiple specific GPUs (e.g. GPU 0, 1 and 3), use `--gpus "device=0,1,3"`.
* `-v`: This argument defines which and how data is shared between the host system and the docker container. By default, no data is shared between the host and the container. `-v` is used to explicitly share data. It follows the format `-v <host folder>:<container folder>:<options>`. In its simplest form, `<host folder>` and `<container folder>` are the same and folders inside the container are the same as on the host. `:<options>` may be left out or `:ro` to indicate that files from this folder may not be modified by the docker container (readonly). The following files need to be shared: input files, output folder (subjects directory) and FreeSurfer license.
* `--user $(id -u):$(id -g)`: Which user the container runs as (relevant for file access, the user-id and group-id, **required**!). `$(id -u)` and `$(id -g)` determine the user and group, respectively. Running the docker container as root `--user 0:0` is strongly discouraged and must be combined with the FastSurfer flag `--allow_root`.
* `--user $(id -u):$(id -g)`: Which user the container runs as (relevant for file access, the user-id and group-id, **required**!). `$(id -u)` and `$(id -g)` determine the user and group, respectively. If this flag is omitted, FastSurfer exits with a message asking you to map your host user. Running the docker container as root `--user 0:0` is strongly discouraged and must be combined with the FastSurfer flag `--allow_root`.
* `--rm`: The flag takes care of removing the container (cleanup of the container) once the analysis finished (optional, but recommended).
* `-d`: You can add this flag to run in detached mode (no screen output, and you return to shell, optional).

Expand Down Expand Up @@ -87,6 +87,8 @@ In general, if you specify `--dry_run` the command will not be executed but sent

By default, the build script will tag your image as `"fastsurfer:[{device}-]{version_tag}"`, where `{version_tag}` is `{version-identifer from pyproject.toml}_{current git-hash}` and `{device}` is the value to `--device` (omitted for `cuda`), but a custom tag can be specified by `--tag {tag_name}`.

By default, the Python environment is resolved from `pyproject.toml`, which allows the latest compatible dependency versions. To build from the backend-neutral pinned `requirements.txt` instead, add `--pinned_requirements`. The selected `--device` is still passed to `uv --torch-backend`, so the same pinned requirements file can be used for CPU and supported CUDA variants while PyTorch backend wheels are selected during the build.

#### BuildKit
Note, we recommend using BuildKit to build docker images (e.g. `DOCKER_BUILDKIT=1` -- the build.py script already always adds this). To install BuildKit, run `wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/<version>/buildx-<version>.<platform>`, for example `wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.12.1/buildx-v0.12.1.linux-amd64`. See also https://github.com/docker/buildx#manual-download.

Expand All @@ -97,7 +99,7 @@ In order to build your own Docker image for FastSurfer (FastSurferCNN + recon-su
python tools/Docker/build.py --device cuda --tag my_fastsurfer:cuda
```

The build script allows more specific options, that specify different CUDA options as well (see `build.py --help`).
The build script allows more specific options, that specify different CUDA options as well (see `build.py --help`). Add `--pinned_requirements` to use the pinned `requirements.txt` dependency versions while still selecting the CUDA backend from `--device`.

For running the analysis, the command is the same as above for the prebuild option:
```bash
Expand Down
13 changes: 11 additions & 2 deletions tools/Docker/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ def make_parser() -> argparse.ArgumentParser:
action="store_true",
help="Also tag the resulting image as 'fastsurfer:dev'.",
)
parser.add_argument(
"--pinned_requirements",
action="store_true",
help="Build the Python environment from backend-neutral pinned requirements.txt "
"instead of resolving the latest compatible versions from pyproject.toml.",
)
# --save_image does not work as expected right now, it cannot be imported via
# docker load, but must be transferred to a registry...
# parser.add_argument(
Expand Down Expand Up @@ -542,7 +548,7 @@ def is_inline_cache(cache_kw):
"--attest", "type=sbom",
"--attest", "type=provenance",
])
if not default_builder_is_container:
if not default_builder_is_container and alternative_builder != "use_default":
args.extend(["--builder", alternative_builder])

kwargs_to_exclude = []
Expand Down Expand Up @@ -633,6 +639,7 @@ def main(
image_tag: str | None = None,
dry_run: bool = False,
tag_dev: bool = True,
pinned_requirements: bool = False,
fastsurfer_home: Path | None = None,
insecure: bool = False,
**keywords,
Expand All @@ -659,17 +666,19 @@ def main(
raise ValueError(f"Invalid target: {target}")
if device not in get_args(AllDeviceType):
raise ValueError(f"Invalid device: {device}")
mapped_device = DEFAULTS.MapDeviceType.get(device, "cpu")
if keywords.get("action", "load") == "push":
kwargs["action"] = "push"
# special case to add extra environment variables to better support AWS and ROCm
if device.startswith("cu") and target == "runtime":
target = "runtime_cuda"
kwargs["target"] = target
kwargs["build_arg"] = [
f"DEVICE={DEFAULTS.MapDeviceType.get(device, 'cpu')}",
f"DEVICE={mapped_device}",
f"FREESURFER_URL={pyproject_freesurfer['urls']['linux'].format(version=pyproject_freesurfer['version'])}",
f"FREESURFER_VERSION={pyproject_freesurfer['version']}",
f"INSECURE_FLAG={'--insecure' if insecure else ''}",
f"PINNED_REQUIREMENTS={'true' if pinned_requirements else 'false'}",
]
if debug:
kwargs["build_arg"].append("DEBUG=true")
Expand Down
Loading
Loading