diff --git a/.dockerignore b/.dockerignore index dd85a0e17..05aa36f38 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,13 +1,14 @@ **/.git **/.github checkpoints -Singularity doc test venv Tutorial **/*.md -Docker/Dockerfile* +/tools/Docker/Dockerfile* +/tools/macos_build +/tools/export_pip-r.sh *.txt venv /srun_fastsurfer.sh diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 0c025831f..030baed11 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -54,7 +54,7 @@ runs: image_name="fastsurfer:cpu-${v/+/_}" fi if [[ -z "$(which python)" ]] ; then echo "ERROR: Cannot find python!" ; fi - cmd=(python "$FASTSURFER_HOME/Docker/build.py" --device cpu --tag "$image_name" --target "${{ inputs.target }}") + cmd=(python "$FASTSURFER_HOME/tools/Docker/build.py" --device cpu --tag "$image_name" --target "${{ inputs.target }}") if [[ "${{ inputs.freesurfer-build-image }}" != "rebuild" ]] ; then cmd+=(--freesurfer_build_image "${{ inputs.freesurfer-build-image }}") fi diff --git a/.github/workflows/QUICKTEST.md b/.github/workflows/QUICKTEST.md index b4db637dd..afb1188fa 100644 --- a/.github/workflows/QUICKTEST.md +++ b/.github/workflows/QUICKTEST.md @@ -22,7 +22,7 @@ This job sets up the necessary environments for the workflow. It depends on the ### Build Singularity Image -This job builds a Docker image and converts it to a Singularity image. It depends on the successful completion of the `prepare-job`. The Docker image is built using a Python script `Docker/build.py` with the `--device cuda --tag fastsurfer_gpu:cuda` flags. The Docker image is then converted to a Singularity image. +This job builds a Docker image and converts it to a Singularity image. It depends on the successful completion of the `prepare-job`. The Docker image is built using a Python script `tools/Docker/build.py` with the `--device cuda --tag fastsurfer_gpu:cuda` flags. The Docker image is then converted to a Singularity image. ### Run FastSurfer diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fde08ee95..646027a85 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,55 +1,50 @@ -name: MAN deploy-docker +name: MAN release/deploy on: -# release: -# types: -# - published + release: + types: + - published workflow_dispatch: jobs: - deploy-gpu: - runs-on: ubuntu-latest + deploy-mac: + runs-on: macos-14 timeout-minutes: 120 + + env: + RELEASE_ASSETS: true + strategy: + matrix: + arch: [intel, arm] steps: + - name: Get repository name. + run: echo "FASTSURFER_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV - name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Login to Docker - uses: docker/login-action@v2 + - name: Set up python environment + uses: actions/setup-python@v6 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Build Docker image GPU - run: python Docker/build.py --device cuda --tag ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:gpu-${{ github.event.release.tag_name }} - - name: Add additional tags + python-version: '3.10' + - name: install dependencies run: | - docker tag ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:gpu-${{ github.event.release.tag_name }} ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:gpu-latest - docker tag ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:gpu-${{ github.event.release.tag_name }} ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:latest - - name: Push Docker image GPU - run: docker push --all-tags ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:gpu-${{ github.event.release.tag_name }} - deploy-cpu: - runs-on: ubuntu-latest - timeout-minutes: 120 - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Login to Docker - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Build Docker image CPU - run: python Docker/build.py --device cpu --tag ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:cpu-${{ github.event.release.tag_name }} - - name: Add additional tags + python -m pip install py2app tomli + + # update system to the newest version, not pin pointing package version on purpose + brew update + brew upgrade || true + brew install aria2 + - name: package app for ${{ matrix.arch }} + run: tools/macos_build/build_release_package.sh ${{ matrix.arch }} + - name: Move assets. + if: env.RELEASE_ASSETS == 'true' run: | - docker tag ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:cpu-${{ github.event.release.tag_name }} ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:cpu-latest - - name: Push Docker image CPU - run: docker push --all-tags ${{ secrets.DOCKERHUB_USERNAME }}/fastsurfer:cpu-${{ github.event.release.tag_name }} + mkdir assets + mv tools/macos_build/installer/* assets/ + - name: Upload release assets. + uses: softprops/action-gh-release@v2 + if: env.RELEASE_ASSETS == 'true' + with: + files: ${{ env.FASTSURFER_DIR }}/assets/* diff --git a/.github/workflows/quicktest.yaml b/.github/workflows/quicktest.yaml index 5620dc4b0..03c4a31f4 100644 --- a/.github/workflows/quicktest.yaml +++ b/.github/workflows/quicktest.yaml @@ -32,7 +32,7 @@ on: default: build-cached type: string freesurfer-build-image: - description: 'FreeSurfer build image to build with ("" (default) => deepmi/fastsurfer-build:freesurferXXX; extract version from Docker/install_fs_pruned.sh)' + description: 'FreeSurfer build image to build with ("" (default) => deepmi/fastsurfer-build:freesurferXXX; extract version from pyproject.toml)' type: string permissions: @@ -139,10 +139,11 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ steps.parse.outputs.DOCKER_IMAGE }} - - name: Setup Python 3.10 + - name: Setup Python 3.11 uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.11' + # python 3.11 is needed to read pyproject.toml (tomllib) architecture: 'x64' # no cache is needed, no installations and no cache is faster # cache: 'pip' # caching pip dependencies @@ -152,12 +153,9 @@ jobs: shell: bash id: parse-version run: | - # get the FreeSurfer version from install_fs_pruned.sh + # get the FreeSurfer version from pyproject.toml { - fslink="$(grep "^fslink=" ./Docker/install_fs_pruned.sh)" - fslink="${fslink:7}" - if [[ "${fslink:0:1}" == '"' ]] || [[ "${fslink:0:1}" == "'" ]] ; then fslink="${fslink:1:-1}" ; fi - fs_version="$(basename "$(dirname "$fslink")")" + fs_version="$(python ./tools/read_toml.py --file ./pyproject.toml --key tool.freesurfer.version)" fs_version_short="${fs_version//\./}" echo "FS_VERSION=$fs_version" echo "FS_VERSION_SHORT=$fs_version_short" diff --git a/.gitignore b/.gitignore index efb8526c3..41f7a2b41 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /BUILD.info -/Docker/BUILD.info -/Docker/custom-ssl.crt +/tools/Docker/BUILD.info +/tools/Docker/custom-ssl.crt /.idea/** /rough_work/** @@ -9,3 +9,5 @@ __pycache__/ *.py[cod] *$py.class */stub + +.DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ab992d5c8..000000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - - docker - -env: - - IMAGETAG="-t fastsurfer:gpu-beta -f ./Docker/Dockerfile" - - IMAGETAG="-t fastsurfer:cpu-beta -f ./Docker/Dockerfile_CPU" - - IMAGETAG="-t fastsurfer:segonly-gpu-beta -f ./Docker/Dockerfile_FastSurferCNN" - - IMAGETAG="-t fastsurfer:segonly-cpu-beta -f ./Docker/Dockerfile_FastSurferCNN_CPU" - - IMAGETAG="-t fastsurfer:surfonly-cpu-beta -f ./Docker/Dockerfile_reconsurf" - -script: - #- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - - docker build --rm=true $IMAGETAG . diff --git a/FastSurferCNN/inference.py b/FastSurferCNN/inference.py index fa0a812b7..ed2a4bb68 100644 --- a/FastSurferCNN/inference.py +++ b/FastSurferCNN/inference.py @@ -382,7 +382,7 @@ def eval( # add prediction logits into the output (same as multiplying probabilities) ii[index_of_current_plane] = slice(start_index, end_index) - out[tuple(ii)].add_(pred, alpha=self.alpha.get(plane, 0.4)) + out[tuple(ii)].add_(pred.half(), alpha=self.alpha.get(plane, 0.4)) start_index = end_index except: diff --git a/README.md b/README.md index 379b47134..eda2acd25 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,13 @@ Notwithstanding module-specific limitations, resolution should be between 1mm an ## Getting started ### Installation -There are two ways to run FastSurfer (links are to installation instructions): +There are three ways to run FastSurfer (links are to installation instructions): -1. In a container ([Singularity](doc/overview/INSTALL.md#singularity) or [Docker](doc/overview/INSTALL.md#docker)) (OS: [Linux](doc/overview/INSTALL.md#linux), [Windows](doc/overview/INSTALL.md#windows), [MacOS on Intel](doc/overview/INSTALL.md#docker-currently-only-supported-for-intel-cpus)), -2. As a [native install](doc/overview/INSTALL.md#native-ubuntu-2004-or-ubuntu-2204) (all OS for segmentation part). +1. For Linux and Windows users, we recommend running FastSurfer in a container [Singularity/Apptainer](doc/overview/INSTALL.md#singularity) or [Docker](doc/overview/INSTALL.md#docker): (OS: [Linux](doc/overview/INSTALL.md#linux), [Windows](doc/overview/INSTALL.md#windows), [MacOS on Intel](doc/overview/INSTALL.md#docker-currently-only-supported-for-intel-cpus)), +2. for macOS users, we recommend [installing the FastSurfer package](doc/overview/INSTALL.md#package), and +3. for developers, the native install gives full control (only documented for [Linux](doc/overview/INSTALL.md#native-ubuntu-2004-or-ubuntu-2204)). -We recommended you use Singularity or Docker on a Linux host system with a GPU. The images we provide on [DockerHub](https://hub.docker.com/r/deepmi/fastsurfer) conveniently include everything needed for FastSurfer. You will also need a [FreeSurfer license](https://surfer.nmr.mgh.harvard.edu/fswiki/License) file for the [Surface pipeline](#surface-reconstruction). We have detailed per-OS Installation instructions in the [INSTALL.md](doc/overview/INSTALL.md) file. +The images we provide on [DockerHub](https://hub.docker.com/r/deepmi/fastsurfer) conveniently include everything needed for FastSurfer. You will also need a [FreeSurfer license](https://surfer.nmr.mgh.harvard.edu/fswiki/License) file for the [Surface pipeline](#surface-reconstruction). We have detailed per-OS Installation instructions in the [INSTALL.md](doc/overview/INSTALL.md) file. ### Usage @@ -76,11 +77,11 @@ All installation methods use the `run_fastsurfer.sh` call interface (replace `*f ``` The `--nv` flag is needed to allow FastSurfer to run on the GPU (otherwise FastSurfer will run on the CPU). - The `--no-home` flag tells singularity to not mount the home directory (see [Singularity documentation](Singularity/README.md#mounting-home) for more info). + The `--no-home` flag tells singularity to not mount the home directory (see [Singularity documentation](doc/overview/SINGULARITY.md#mounting-home) for more info). The `-B` flag is used to tell singularity, which folders FastSurfer can read and write to. - See also __[Example 2](doc/overview/EXAMPLES.md#example-2-fastsurfer-singularity)__ for a full singularity FastSurfer run command and [the Singularity documentation](Singularity/README.md#fastsurfer-singularity-image-usage) for details on more singularity flags. + See also __[Example 2](doc/overview/EXAMPLES.md#example-2-fastsurfer-singularity)__ for a full singularity FastSurfer run command and [the Singularity documentation](doc/overview/SINGULARITY.md#fastsurfer-singularity-image-usage) for details on more singularity flags. (b) For __docker__, the syntax is ``` @@ -96,7 +97,7 @@ All installation methods use the `run_fastsurfer.sh` call interface (replace `*f The `-v` flag is used to tell docker, which folders FastSurfer can read and write to. - See also __[Example 1](doc/overview/EXAMPLES.md#example-1-fastsurfer-docker)__ for a full FastSurfer run inside a Docker container and [the Docker documentation](Docker/README.md#docker-flags) for more details on the docker flags including `--rm` and `--user`. + See also __[Example 1](doc/overview/EXAMPLES.md#example-1-fastsurfer-docker)__ for a full FastSurfer run inside a Docker container and [the Docker documentation](tools/Docker/README.md#docker-flags) for more details on the docker flags including `--rm` and `--user`. 2. For a __native install__, you need to activate your FastSurfer environment (e.g. `conda activate fastsurfer_gpu`) and make sure you have added the FastSurfer path to your `PYTHONPATH` variable, e.g. `export PYTHONPATH=$(pwd)`. diff --git a/Tutorial/Complete_FastSurfer_Tutorial.ipynb b/Tutorial/Complete_FastSurfer_Tutorial.ipynb index 94c8cd00f..ee3230266 100644 --- a/Tutorial/Complete_FastSurfer_Tutorial.ipynb +++ b/Tutorial/Complete_FastSurfer_Tutorial.ipynb @@ -1634,7 +1634,7 @@ "\n", "For [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html) you are creating a [conda environment](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-environments) containing all the dependencies (fastsurfer_gpu or fastsurfer_cpu). You need to activate this environment with conda activate. To deactivate it, simply type conda deactivate. You have to activate the environment every time you want to run FastSurfer code.\n", "\n", - "For [docker](https://docs.docker.com/), you are simply downloading the fastsurfer image (deepmi/fastsurfer:gpu-v2.0.0). Alternatively, you could also download deepmi/fastsurfer:segonly-gpu-v2.0.0, deepmi/fastsurfer:segonly-cpu-v2.0.0, deepmi/fastsurfer:cpu-v2.0.0 or deepmi/fastsurfer:surfonly-cpu-v2.0.0. The first part of the name indicates what you are running (fastsurfer from the deepmi website). The tag is split into two parts. The first defines what part of the pipeline you can run with it (segonly=only segmentation, surfonly=only surface pipeline, no prefix=both options can be run) and the second defines on what you run it (cpu or gpu). If you want to modify the image, you can find the Dockerfiles the images are based on in our github page (in the [Docker folder](https://github.com/Deep-MI/FastSurfer/blob/stable/Docker/)). The [README](https://github.com/Deep-MI/FastSurfer/blob/stable/Docker/README.md) there also contains information on how to build the docker from source." + "For [docker](https://docs.docker.com/), you are simply downloading the fastsurfer image (deepmi/fastsurfer:gpu-v2.0.0). Alternatively, you could also download deepmi/fastsurfer:segonly-gpu-v2.0.0, deepmi/fastsurfer:segonly-cpu-v2.0.0, deepmi/fastsurfer:cpu-v2.0.0 or deepmi/fastsurfer:surfonly-cpu-v2.0.0. The first part of the name indicates what you are running (fastsurfer from the deepmi website). The tag is split into two parts. The first defines what part of the pipeline you can run with it (segonly=only segmentation, surfonly=only surface pipeline, no prefix=both options can be run) and the second defines on what you run it (cpu or gpu). If you want to modify the image, you can find the Dockerfiles the images are based on in our github page (in the [Docker folder](https://github.com/Deep-MI/FastSurfer/blob/dev/tools/Docker/)). The [README](https://github.com/Deep-MI/FastSurfer/blob/dev/tools/Docker/README.md) there also contains information on how to build the docker from source." ] }, { @@ -2192,7 +2192,7 @@ "\n", "For [conda](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html) you are creating a [conda environment](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-environments) containing all the dependencies (fastsurfer_gpu or fastsurfer_cpu). You need to activate this environment with conda activate. To deactivate it, simply type conda deactivate. You have to activate the environment every time you want to run FastSurfer code.\n", "\n", - "For [docker](https://docs.docker.com/), you are simply downloading the fastsurfer image (deepmi/fastsurfer:gpu-v2.0.0. Alternatively, you could also download deepmi/fastsurfer:segonly-gpu-v2.0.0, deepmi/fastsurfer:segonly-cpu-v2.0.0, deepmi/fastsurfer:cpu-v2.0.0 or deepmi/fastsurfer:surfonly-cpu-v2.0.0. The first part of the name indicates what you are running (fastsurfer from the deepmi website). The tag is split into two parts. The first defines what part of the pipeline you can run with it (segonly=only segmentation, surfonly=only surface pipeline, no prefix=both options can be run) and the second defines on what you run it (cpu or gpu). If you want to modify the image, you can find the Dockerfiles the images are based on in our github page (in the [Docker folder](https://github.com/Deep-MI/FastSurfer/blob/stable/Docker/)). The [README](https://github.com/Deep-MI/FastSurfer/blob/stable/Docker/README.md) there also contains information on how to build the docker from source." + "For [docker](https://docs.docker.com/), you are simply downloading the fastsurfer image (deepmi/fastsurfer:gpu-v2.0.0. Alternatively, you could also download deepmi/fastsurfer:segonly-gpu-v2.0.0, deepmi/fastsurfer:segonly-cpu-v2.0.0, deepmi/fastsurfer:cpu-v2.0.0 or deepmi/fastsurfer:surfonly-cpu-v2.0.0. The first part of the name indicates what you are running (fastsurfer from the deepmi website). The tag is split into two parts. The first defines what part of the pipeline you can run with it (segonly=only segmentation, surfonly=only surface pipeline, no prefix=both options can be run) and the second defines on what you run it (cpu or gpu). If you want to modify the image, you can find the Dockerfiles the images are based on in our github page (in the [Docker folder](https://github.com/Deep-MI/FastSurfer/blob/dev/tools/Docker/)). The [README](https://github.com/Deep-MI/FastSurfer/blob/dev/tools/Docker/README.md) there also contains information on how to build the docker from source." ] }, { diff --git a/doc/conf.py b/doc/conf.py index a9c70804b..be1ed6383 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -271,7 +271,7 @@ def linkcode_resolve(domain, info): "^/?(.*)#(.*)ubuntu-(\\d{2})(\\d{2})": ("/\\1#\\2ubuntu-\\3-\\4",), f"{_up}readme{_end}": ("/index.rst\\1", "/overview/intro.rst\\1"), "^/overview/intro(#.*)?$": ("/overview/index.rst\\2",), - f"{_up}(singularity|docker)/readme{_end}": ("/overview/\\1.rst\\2",), + f"{_up}/tools/docker/readme{_end}": ("/overview/docker.rst\\2",), f"{_up}({_re_script_dirs})/readme{_end}": ("/scripts/\\1.rst\\2",), f"{_up}license": ("/overview/license.rst",), } diff --git a/doc/images/fastsurfer.png b/doc/images/fastsurfer.png new file mode 100644 index 000000000..5d74c972b Binary files /dev/null and b/doc/images/fastsurfer.png differ diff --git a/doc/overview/EXAMPLES.md b/doc/overview/EXAMPLES.md index eb082c885..930ee33fb 100644 --- a/doc/overview/EXAMPLES.md +++ b/doc/overview/EXAMPLES.md @@ -39,10 +39,10 @@ Note, that the paths following `--fs_license`, `--t1`, and `--sd` are __inside__ A directory with the name as specified in `--sid` (here subjectX) will be created in the output directory if it does not exist. So in this example output will be written to /home/user/my_fastsurfer_analysis/subjectX/ . Make sure the output directory is empty, to avoid overwriting existing files. -If you do not have a GPU, you can also run our CPU-Docker by dropping the `--gpus all` flag and specifying `--device cpu` at the end as a FastSurfer flag, see also [FastSurfer's docker documentation](../../Docker/README.md) for more details. +If you do not have a GPU, you can also run our CPU-Docker by dropping the `--gpus all` flag and specifying `--device cpu` at the end as a FastSurfer flag, see also [FastSurfer's docker documentation](../../tools/Docker/README.md) for more details. ## Example 2: FastSurfer Singularity -After building the Singularity image (see below or [these instructions](../../Singularity/README.md)), you also need to register at the FreeSurfer website (https://surfer.nmr.mgh.harvard.edu/registration.html) to acquire a valid license (for free) - same as when using Docker. This license needs to be passed to the script via the `--fs_license` flag. This is not necessary if you want to run the segmentation only. +After building the Singularity image (see below or [these instructions](SINGULARITY.md)), you also need to register at the FreeSurfer website (https://surfer.nmr.mgh.harvard.edu/registration.html) to acquire a valid license (for free) - same as when using Docker. This license needs to be passed to the script via the `--fs_license` flag. This is not necessary if you want to run the segmentation only. To run FastSurfer on a given subject using the Singularity image with GPU access, execute the following commands from a directory where you want to store singularity images. This will create a singularity image from our Dockerhub image and execute it: diff --git a/doc/overview/INSTALL.md b/doc/overview/INSTALL.md index 7a7c480a5..8c671e776 100644 --- a/doc/overview/INSTALL.md +++ b/doc/overview/INSTALL.md @@ -22,9 +22,9 @@ Assuming you have singularity installed already (by a system admin), you can bui ```bash singularity build fastsurfer-gpu.sif docker://deepmi/fastsurfer:latest ``` -Additionally, [the Singularity README](../../Singularity/README.md) contains detailed directions for building your own Singularity images from Docker. +Additionally, [the Singularity README](SINGULARITY.md) contains detailed directions for building your own Singularity images from Docker. -[Example 2](EXAMPLES.md#example-2-fastsurfer-singularity) explains how to run FastSurfer (for the full pipeline you will also need a FreeSurfer .license file!) and you can find details on how to build your own images here: [Docker](../../Docker/README.md) and [Singularity](../../Singularity/README.md). +[Example 2](EXAMPLES.md#example-2-fastsurfer-singularity) explains how to run FastSurfer (for the full pipeline you will also need a FreeSurfer .license file!) and you can find details on how to build your own images here: [Docker](../../tools/Docker/README.md) and [Singularity](SINGULARITY.md). ### Docker @@ -35,7 +35,7 @@ This is very similar to Singularity. Assuming you have Docker installed (by a sy docker pull deepmi/fastsurfer:latest ``` -[Example 1](EXAMPLES.md#example-1-fastsurfer-docker) explains how to run FastSurfer (for the full pipeline you will also need a FreeSurfer .license file!) and you can find details on how to [build your own image](https://github.com/Deep-MI/FastSurfer/blob/dev/Docker/README.md). +[Example 1](EXAMPLES.md#example-1-fastsurfer-docker) explains how to run FastSurfer (for the full pipeline you will also need a FreeSurfer .license file!) and you can find details on how to [build your own image](../../tools/Docker/README.md). If you are using the **rootless mode**, you have to install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) and follow the [configuration for the rootless mode](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#rootless-mode). Otherwise, running FastSurfer with Docker will give you this error message ```docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]]```. @@ -74,10 +74,11 @@ If you are using pip, make sure pip is updated as older versions will fail. We recommend to install conda as your python environment. If you don't have conda on your system, an admin needs to install it: ```bash -wget --no-check-certificate -qO ~/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-py38_4.11.0-Linux-x86_64.sh -chmod +x ~/miniconda.sh -sudo ~/miniconda.sh -b -p /opt/conda && \ -rm ~/miniconda.sh +FORGE_VERSION=25.9.1-0 # find the recent miniforge version at https://github.com/conda-forge/miniforge/releases +wget --no-check-certificate -qO ~/miniforge.sh https://github.com/conda-forge/miniforge/releases/download/${FORGE_VERSION}/Miniforge3-${FORGE_VERSION}-Linux-x86_64.sh +chmod +x ~/miniforge.sh +sudo ~/miniforge.sh -b -p /opt/miniforge && \ +rm ~/miniforge.sh ``` #### 3. FastSurfer @@ -97,7 +98,7 @@ conda env create -f ./env/fastsurfer.yml conda activate fastsurfer ``` -If you do not have an NVIDIA GPU, you can create appropriate ymls on the fly with `python ./Docker/install_env.py -m $MODE -i ./env/FastSurfer.yml -o ./fastsurfer_$MODE.yml`. Here `$MODE` can be for example `cpu`, see also `python ./Docker/install_env.py --help` for other options like rocm or cuda versions. Finally, replace `./env/fastsurfer.yml` with your custom environment file `./fastsurfer_$MODE.yml`. +If you do not have an NVIDIA GPU, you can create appropriate ymls on the fly with `python ./tools/Docker/install_env.py -m $MODE -i ./env/FastSurfer.yml -o ./fastsurfer_$MODE.yml`. Here `$MODE` can be for example `cpu`, see also `python ./tools/Docker/install_env.py --help` for other options like rocm or cuda versions. Finally, replace `./env/fastsurfer.yml` with your custom environment file `./fastsurfer_$MODE.yml`. If you only want to run the surface pipeline, use `./env/fastsurfer_reconsurf.yml`. Next, add the fastsurfer directory to the python path (make sure you have changed into it already): @@ -129,7 +130,7 @@ We have successfully run the segmentation on an AMD GPU (Radeon Pro W6600) using Build the Docker container with ROCm support. ```bash -python Docker/build.py --device rocm --tag my_fastsurfer:rocm +python tools/Docker/build.py --device rocm --tag my_fastsurfer:rocm ``` You will need to add a couple of flags to your docker run command for AMD, see [Example 1](EXAMPLES.md#example-1-fastsurfer-docker) for `**other-docker-flags**` or `**fastsurfer-flags**`: @@ -141,7 +142,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --device=/dev/ ``` Note, that this docker image is experimental, uses a different Python version and python packages, so results can differ from our validation results. Please do visual QC. -## MacOS +## MacOS Processing on Mac CPUs is possible. On Apple Silicon, you can even use the GPU by passing ```--device mps```. @@ -164,59 +165,31 @@ docker pull deepmi/fastsurfer:latest Continue with the example in [Example 1](EXAMPLES.md#example-1-fastsurfer-docker). +### Package -### Native - -On modern Macs with the Apple Silicon M1 or M2 ARM-based chips, we recommend a native installation as it runs much faster than Docker in our tests. Access to the built-in AI accelerator (MPS) is also only available on native installations. A native installation also works on older Intel chips. - -#### 1. Dependency packages -If you do not have git, python3.10 or bash (at least 3.2) you can install these via the packet manager brew. -This installs brew and then git and python3.10: +#### 1. Requirements +FastSurfer requires pre-installed python3.10+ and bash (at least 3.2). +You can install these via the packet manager brew. +To install brew and then python3.10, execute the following in a Terminal: ```sh /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew install git python@3.10 +brew install python@3.10 ``` -#### 2. Python -Create a python environment, activate it, and upgrade pip: - -```sh -python3.10 -m venv $HOME/python-envs/fastsurfer -source $HOME/python-envs/fastsurfer/bin/activate -python3.10 -m pip install --upgrade pip -``` +### 2. FastSurfer package +From version 2.5 onward, FastSurfer ships a macOS installer package, which you can download from +[github](https://github.com/Deep-MI/FastSurfer/releases/). +There are package installers for both the Apple M-chip architecture (`arm64`) and for legacy Intel chips (`x86_64`). +To install, double-click the installer and follow the installer instructions. -#### 3. FastSurfer and Requirements -Clone FastSurfer: -```sh -git clone --branch stable https://github.com/Deep-MI/FastSurfer.git -cd FastSurfer -export PYTHONPATH="${PYTHONPATH}:$PWD" -``` +After installation, you can find the FastSurfer applet, its source code, and selected FreeSurfer executables in the `/Applications` folder. -Install the FastSurfer requirements -```sh -python3.10 -m pip install -r requirements.mac.txt -``` - -If this step fails, you may need to edit ```requirements.mac.txt``` and exclude version numbers that produce conflicts or break our code. -On newer M1 Macs, we also had issues with the h5py package, which could be solved by using brew for help (not sure this is needed any longer): - -```sh -brew install hdf5 -export HDF5_DIR="$(brew --prefix hdf5)" -pip3 install --no-binary=h5py h5py -``` - -You can also download all network checkpoint files at this point already: -```sh -python3.10 FastSurferCNN/download_checkpoints.py --all -``` +### 3. Launching FastSurfer -Once all dependencies are installed, you can run the FastSurfer segmentation only by calling ```./run_fastsurfer.sh --seg_only ....``` with the appropriate command line flags, see the [commandline documentation](../../README.md#usage). +To launch a configured FastSurfer terminal session, start the FastSurfer applet from Applications. -To run the full pipeline, install and source also the supported FreeSurfer version according to their [Instructions](https://surfer.nmr.mgh.harvard.edu/fswiki/rel7downloads). There is a freesurfer email list, if you run into problems during this step. Note, that currently FreeSurfer for MacOS supports no ARM, but only Intel, so on modern M-chips it will be slow due to the emulation. This is why we recommend using a Linux host system to run FastSurfer on larger datasets. +Once the terminal opens, it is already configured and you can easily run the full FastSurfer pipeline by typing and executing `run_fastsurfer.sh `, where you replace `` with the appropriate [commandline flags of FastSurfer](../../README.md#usage). #### 4. Apple AI Accelerator support On modern M-Chips you can try the Apple Silicon AI Accelerator by setting `PYTORCH_ENABLE_MPS_FALLBACK` and passing `--device mps` for the segmentation module to make use of the fast GPU: @@ -277,7 +250,7 @@ docker pull deepmi/fastsurfer:latest Now you can run Fastsurfer the same way as described in [Example 1](EXAMPLES.md#example-1-fastsurfer-docker), for example: ```bash -docker run --gpus all +docker run --gpus all \ -v C:/Users/user/my_mri_data:/data \ -v C:/Users/user/my_fastsurfer_analysis:/output \ -v C:/Users/user/my_fs_license_dir:/fs_license \ diff --git a/doc/overview/MACOS.md b/doc/overview/MACOS.md new file mode 100644 index 000000000..397f41e62 --- /dev/null +++ b/doc/overview/MACOS.md @@ -0,0 +1,15 @@ +Running FastSurfer as MacOS package +=================================== + +[Installation Instructions](INSTALL.md#macos) + +If you want to run only segmentation (replace placeholders starting with "<" and ending with ">", see https://deep-mi.org/FastSurfer/stable): + +```bash +run_fastsurfer.sh --seg_only --sd --sid --t1 +``` +To run the full FastSurfer: + +```bash +run_fastsurfer.sh --device mps --sd --sid --t1 --fs_license +``` diff --git a/Singularity/README.md b/doc/overview/SINGULARITY.md similarity index 96% rename from Singularity/README.md rename to doc/overview/SINGULARITY.md index 9638d9bd0..a379241c2 100644 --- a/Singularity/README.md +++ b/doc/overview/SINGULARITY.md @@ -1,4 +1,4 @@ -# FastSurfer Singularity Support +# Singularity Support For use on HPCs (or in other cases where Docker is not available or preferred) you can easily create a Singularity image from the Docker image. Singularity uses its own image format, so the Docker images must be converted (we publish our releases as docker images available on [Dockerhub](https://hub.docker.com/r/deepmi/fastsurfer/tags)). @@ -12,7 +12,7 @@ Singularity images are files - usually with the extension `.sif`. Here, we save If you want to pick a specific FastSurfer version, you can also change the tag (`latest`) in `deepmi/fastsurfer:latest` to any tag. For example to use the cpu image hosted on [Dockerhub](https://hub.docker.com/r/deepmi/fastsurfer/tags) use the tag `cpu-latest`. ## Building your own FastSurfer Singularity Image -To build a custom FastSurfer Singularity image, the `Docker/build.py` script supports a flag for direct conversion. +To build a custom FastSurfer Singularity image, the `tools/Docker/build.py` script supports a flag for direct conversion. Simply add `--singularity /home/user/my_singlarity_images/fastsurfer-myimage.sif` to the call, which first builds the image with Docker and then converts the image to Singularity. If you want to manually convert the local Docker image `fastsurfer:myimage`, run: @@ -21,7 +21,7 @@ If you want to manually convert the local Docker image `fastsurfer:myimage`, run singularity build /home/user/my_singlarity_images/fastsurfer-myimage.sif docker-daemon://fastsurfer:myimage ``` -For more information on how to create your own Docker images, see our [Docker guide](../Docker/README.md). +For more information on how to create your own Docker images, see our [Docker guide](../../tools/Docker/README.md). ## FastSurfer Singularity Image Usage diff --git a/doc/overview/docker.rst b/doc/overview/docker.rst index 3cc4057e5..12328cd7a 100644 --- a/doc/overview/docker.rst +++ b/doc/overview/docker.rst @@ -1,7 +1,7 @@ Docker Support -------------- -.. include:: ../../Docker/README.md +.. include:: ../../tools/Docker/README.md :parser: fix_links.parser :relative-docs: . :relative-images: diff --git a/doc/overview/index.rst b/doc/overview/index.rst index c03b89c15..56831f195 100644 --- a/doc/overview/index.rst +++ b/doc/overview/index.rst @@ -11,7 +11,8 @@ User Guide FLAGS.md OUTPUT_FILES.md docker - singularity + SINGULARITY.md + MACOS.md EDITING.md LONG.md SECURITY.md diff --git a/doc/overview/singularity.rst b/doc/overview/singularity.rst deleted file mode 100644 index afb11cb84..000000000 --- a/doc/overview/singularity.rst +++ /dev/null @@ -1,8 +0,0 @@ -Singularity Support -------------------- - -.. include:: ../../Singularity/README.md - :parser: fix_links.parser - :relative-docs: . - :relative-images: - :start-line: 1 diff --git a/pyproject.toml b/pyproject.toml index 41da9da8c..438bf23c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,14 +101,22 @@ documentation = 'https://fastsurfer.org' source = 'https://github.com/Deep-MI/FastSurfer' tracker = 'https://github.com/Deep-MI/FastSurfer/issues' +[tool.freesurfer] +version = "7.4.1" +# The following URLs use the {version} placeholder, which should be replaced +# with the actual version string using Python's .format(version=...). +[tool.freesurfer.urls] +linux = "https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/{version}/freesurfer-linux-ubuntu22_amd64-{version}.tar.gz" +macOS = "https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/{version}/freesurfer-macOS-darwin_x86_64-{version}.tar.gz" + [tool.setuptools] -packages = ['FastSurferCNN','CerebNet','recon_surf'] +packages = ['FastSurferCNN','CerebNet','recon_surf','HypVINN'] [tool.pydocstyle] convention = 'numpy' ignore-decorators = '(copy_doc|property|.*setter|.*getter|pyqtSlot|Slot)' match = '^(?!setup|__init__|test_).*\.py' -match-dir = '^FastSurferCNN.*,^CerebNet.*,^recon-surf.*' +match-dir = '^FastSurferCNN.*,^CerebNet.*,^recon-surf.*,^HypVINN.*' add_ignore = 'D100,D104,D107' [tool.ruff] diff --git a/recon_surf/README.md b/recon_surf/README.md index 281c8bb89..b6a4322b9 100644 --- a/recon_surf/README.md +++ b/recon_surf/README.md @@ -92,7 +92,7 @@ Check [Dockerhub](https://hub.docker.com/r/deepmi/fastsurfer/tags) to find out t * The `-B` commands mount your output, and directory with the FreeSurfer license file into the Singularity container. Inside the container these are visible under the name following the colon (in this case /data, /output, and /fs_license). -* The `--no-home` command disables the automatic mount of the users home directory (see [Best Practice](../Singularity/README.md#mounting-home)) +* The `--no-home` command disables the automatic mount of the users home directory (see [Best Practice](../doc/overview/SINGULARITY.md#mounting-home)) The `--t1` and `--asegdkt_segfile` flags point to the already existing conformed T1 input and segmentation from the segmentation module. Also other files from that pipeline will be reused (e.g. the `mask.mgz`, `orig_nu.mgz`). The diff --git a/Docker/Dockerfile b/tools/Docker/Dockerfile similarity index 93% rename from Docker/Dockerfile rename to tools/Docker/Dockerfile index 70930c596..999f73ac6 100644 --- a/Docker/Dockerfile +++ b/tools/Docker/Dockerfile @@ -63,19 +63,19 @@ ENV DEBIAN_FRONTEND=noninteractive # Install packages needed for build RUN apt-get update && apt-get install -y --no-install-recommends \ + aria2 \ ca-certificates \ file \ git \ - upx \ - wget && \ + upx && \ apt clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ARG FORGE_VERSION=25.3.1-0 # Install conda -RUN wget --no-check-certificate -qO ~/miniforge.sh \ - https://github.com/conda-forge/miniforge/releases/download/${FORGE_VERSION}/Miniforge3-${FORGE_VERSION}-Linux-x86_64.sh && \ +RUN aria2c -x 8 -s 8 -c --check-certificate=false -o ~/miniforge.sh \ + https://github.com/conda-forge/miniforge/releases/download/${FORGE_VERSION}/Miniforge3-${FORGE_VERSION}-Linux-x86_64.sh && \ chmod +x ~/miniforge.sh && \ ~/miniforge.sh -b -p /opt/miniforge && \ rm ~/miniforge.sh @@ -87,7 +87,7 @@ FROM build_base AS build_common # get install scripts into docker ARG MAMBA_SSL_CERTIFICATE="" -COPY ./env/fastsurfer.yml ./Docker/conda_pack.sh ./Docker/install_env.py ${MAMBA_SSL_CERTIFICATE} /install/ +COPY ./env/fastsurfer.yml ./tools/Docker/conda_pack.sh ./tools/Docker/install_env.py ${MAMBA_SSL_CERTIFICATE} /install/ SHELL ["/bin/bash", "--login", "-c", "-e"] # Install conda for gpu @@ -116,7 +116,7 @@ RUN python /install/install_env.py -m ${DEVICE} -i /install/fastsurfer.yml -o /i FROM build_base AS build_freesurfer # get install scripts into docker -COPY ./Docker/install_fs_pruned.sh /install/ +COPY ./tools/build/install_fs_pruned.sh ./tools/build/link_fs.sh /install/ SHELL ["/bin/bash", "--login", "-c"] ARG FREESURFER_URL=default @@ -124,8 +124,8 @@ ARG FREESURFER_URL=default # install freesurfer and point to new python location RUN /install/install_fs_pruned.sh /opt --upx --url $FREESURFER_URL && \ rm /opt/freesurfer/bin/fspython && \ - rm -R /install && \ - ln -s /venv/bin/python3 /opt/freesurfer/bin/fspython + /install/link_fs.sh /venv/bin/python3 /opt/freesurfer && \ + rm -R /install # ======================================================= @@ -201,7 +201,7 @@ ENV PYTHONPATH=/fastsurfer:/opt/freesurfer/python/packages \ RUN cd /fastsurfer ; python3 FastSurferCNN/download_checkpoints.py --all && \ python3 -m compileall * && \ python3 FastSurferCNN/version.py --sections +git+checkpoints+pip \ - --build_cache Docker/BUILD.info -o BUILD.info + --build_cache tools/Docker/BUILD.info -o BUILD.info # TODO: SBOM info of FastSurfer and FreeSurfer are missing, it is unclear how to add # those at the moment, as the buildscanner syft does not find simple package.json @@ -221,7 +221,7 @@ RUN cd /fastsurfer ; python3 FastSurferCNN/download_checkpoints.py --all && \ # the script entrypoint ensures that our conda env is active USER nonroot WORKDIR "/fastsurfer" -ENTRYPOINT ["/fastsurfer/Docker/entrypoint.sh","/fastsurfer/run_fastsurfer.sh"] +ENTRYPOINT ["/fastsurfer/tools/Docker/entrypoint.sh","/fastsurfer/run_fastsurfer.sh"] CMD ["--help"] FROM runtime AS runtime_cuda diff --git a/Docker/README.md b/tools/Docker/README.md similarity index 94% rename from Docker/README.md rename to tools/Docker/README.md index d26dcfcfc..6c8b46afe 100644 --- a/Docker/README.md +++ b/tools/Docker/README.md @@ -46,13 +46,13 @@ docker run --gpus all -v /home/user/my_mri_data:/data \ * The `--t1` points to the t1-weighted MRI image to analyse (full path, with mounted name inside docker: /home/user/my_mri_data => /data) * The `--sid` is the subject ID name (output folder name) * The `--sd` points to the output directory (its mounted name inside docker: /home/user/my_fastsurfer_analysis => /output) -* [more flags](../doc/overview/FLAGS.md#fastsurfer-flags) +* [more flags](../../doc/overview/FLAGS.md#fastsurfer-flags) Note, that the paths following `--fs_license`, `--t1`, and `--sd` are __inside__ the container, not global paths on your system, so they should point to the places where you mapped these paths above with the `-v` arguments. A directory with the name as specified in `--sid` (here subjectX) will be created in the output directory (specified via `--sd`). So in this example output will be written to /home/user/my_fastsurfer_analysis/subjectX/ . Make sure the output directory is empty, to avoid overwriting existing files. -All other available flags are identical to the ones explained on the main page [README](../README.md). +All other available flags are identical to the ones explained on the main page [README](../../README.md). ### Docker Best Practice * Do not mount the user home directory into the docker container as the home directory. @@ -78,7 +78,7 @@ Note, for many HPC users with limited GPUs or with very large datasets, it may b Also note, in order to run our Docker containers on a Mac, users need to increase docker memory to 10 GB by overwriting the settings under Docker Desktop --> Preferences --> Resources --> Advanced (slide the bar under Memory to 10 GB; see: [Change Docker Desktop settings on Mac](https://docs.docker.com/desktop/settings/mac/) for details). For the new Apple silicon chips (M1,etc), we noticed that a native install runs much faster than docker, because the Apple Accelerator (use the experimental MPS device via `--device mps`) can be used. There is no support for MPS-based acceleration through docker at the moment. ### General build settings -The build script `build.py` supports additional args, targets and options, see `python Docker/build.py --help`. +The build script `build.py` supports additional args, targets and options, see `python tools/Docker/build.py --help`. Note, that the build script's main function is to select parameters for build args, but also create the FastSurfer-root/BUILD.info file, which will be used by FastSurfer to document the version (including git hash of the docker container). This BUILD.info file must exist for the docker build to be successful. In general, if you specify `--dry_run` the command will not be executed but sent to stdout, so you can run `python build.py --device cuda --dry_run | bash` as well. Note, that build.py uses some dependencies from FastSurfer, so you will need to set the PYTHONPATH environment variable to the FastSurfer root (include of `FastSurferCNN` must be possible) and we only support Python 3.10. @@ -158,7 +158,7 @@ docker run --rm --security-opt seccomp=unconfined \ In conflict with the official ROCm documentation (above), we also needed to add the group render `--group-add render` (in addition to `--group-add video`). -Note, we tested on an AMD Radeon Pro W6600, which is [not officially supported](https://docs.amd.com/en/latest/release/gpu_os_support.html), but setting `HSA_OVERRIDE_GFX_VERSION=10.3.0` [inside docker did the trick](https://en.opensuse.org/SDB:AMD_GPGPU#Using_CUDA_code_with_ZLUDA_and_ROCm): +Note, we tested on an AMD Radeon Pro W6600, which is [not officially supported](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/reference/system-requirements.html#supported-gpus), but setting `HSA_OVERRIDE_GFX_VERSION=10.3.0` [inside docker did the trick](https://en.opensuse.org/SDB:AMD_GPGPU#Using_CUDA_code_with_ZLUDA_and_ROCm): ```bash docker run --rm --security-opt seccomp=unconfined \ @@ -192,7 +192,7 @@ To build a docker image with attestation and provenance, i.e. Software Bill Of M 3. Attestation files are not supported by the standard docker image storage driver. Therefore, images cannot be tested locally. There are two solutions to this limitation. 1. Directly push to the registry: - Add `--action push` to the build script (the default is `--action load`, which loads the created image into the current docker context, and for the image name, also add the registry name. For example `... python Docker/build.py ... --attest --action push --tag docker.io//fastsurfer:latest`. + Add `--action push` to the build script (the default is `--action load`, which loads the created image into the current docker context, and for the image name, also add the registry name. For example `... python tools/Docker/build.py ... --attest --action push --tag docker.io//fastsurfer:latest`. 2. [Install the containerd image storage driver](https://docs.docker.com/storage/containerd/#enable-containerd-image-store-on-docker-engine), which supports attestation: To implement this on Linux, make sure your docker daemon config file `/etc/docker/daemon.json` includes ```json { @@ -202,7 +202,7 @@ To build a docker image with attestation and provenance, i.e. Software Bill Of M } ``` Also note, that the image storage location with containerd is not defined by the docker config file `/etc/docker/daemon.json`, but by the containerd config `/etc/containerd/config.toml`, which will likely not exist. You can [create a default config](https://github.com/containerd/containerd/blob/main/docs/getting-started.md#customizing-containerd) file with `containerd config default > /etc/containerd/config.toml`, in this config file edit the `"root"`-entry (default value is `/var/lib/containerd`). -4. Finally, you can now build the FastSurfer image with `python Docker/build.py ... --attest`. This will add the additional flags to the docker build command. +4. Finally, you can now build the FastSurfer image with `python tools/Docker/build.py ... --attest`. This will add the additional flags to the docker build command. ## Setting the ssl_verify parameter of mamba @@ -222,7 +222,7 @@ build_dir=$HOME/FastSurfer-build img=deepmi/fastsurfer # the version can be identified with: $build_dir/run_fastsurfer.sh --version version=2.4.3 -# the cuda and rocm version can be identified with: python $build_dir/Docker/build.py --help | grep -E ^[[:space:]]+--device +# the cuda and rocm version can be identified with: python $build_dir/tools/Docker/build.py --help | grep -E ^[[:space:]]+--device cuda=126 cudas=("cuda118" "cuda124" "cuda$cuda") rocm=6.2.4 @@ -236,7 +236,7 @@ all_tags=("latest" "gpu-latest" "cuda-v$version" "rocm-v$version" "cpu-latest") # build all distinct images for dev in cpu "${rocms[@]}" "${cudas[@]}" do - python3 Docker/build.py --tag $img:$dev-v$version --freesurfer_build_image $img-build:freesurfer741 --attest --device $dev + python3 tools/Docker/build.py --tag $img:$dev-v$version --freesurfer_build_image $img-build:freesurfer741 --attest --device $dev all_tags+=("$dev-v$version") done # labels that are just references diff --git a/Docker/build.py b/tools/Docker/build.py similarity index 95% rename from Docker/build.py rename to tools/Docker/build.py index 4ab7a5ade..94247bed4 100755 --- a/Docker/build.py +++ b/tools/Docker/build.py @@ -21,10 +21,22 @@ import logging import os import subprocess +import sys from collections.abc import Sequence from pathlib import Path from typing import Literal, cast, get_args +# python 3.11 supports tomllib, but we have tomli in fastsurfer +if sys.version_info >= (3, 11): + import tomllib +else: + try: + import tomli as tomllib + except ImportError as e: + raise RuntimeError( + "The FastSurfer build script requires tomli or python 3.11 to load the pyproject.toml file." + ) from e + logger = logging.getLogger(__name__) Target = Literal['runtime', 'build_common', 'build_conda', 'build_freesurfer', @@ -60,10 +72,13 @@ class DEFAULTS: # torch 2.0.1 comes compiled with cu117, cu118, and rocm5.4.2 # torch 2.4 comes compiled with cu118, cu121, cu124 and rocm6.1 # torch 2.6 comes compiled with cu118, cu124, cu126 and rocm6.2.4 + CUDA="cu126" + CUDA_VERSION="12.6" + ROCM="rocm6.2.4" MapDeviceType: dict[AllDeviceType, DeviceType] = dict( ((d, d) for d in get_args(DeviceType)), - rocm="rocm6.2.4", - cuda="cu126", + rocm=ROCM, + cuda=CUDA, ) BUILD_BASE_IMAGE = "ubuntu:22.04" RUNTIME_BASE_IMAGE = "ubuntu:22.04" @@ -195,12 +210,12 @@ def make_parser() -> argparse.ArgumentParser: parser.add_argument( "--device", - choices=["cpu", "cuda", "cu118", "cu124", "cu126", "rocm", "rocm6.2.4"], + choices=list(get_args(AllDeviceType)), required=True, - help="""selection of internal build stages to build for a specific platform.
- - cuda: defaults to cu126, cuda 12.6
+ help=f"""selection of internal build stages to build for a specific platform.
+ - cuda: defaults to {DEFAULTS.CUDA}, cuda {DEFAULTS.CUDA_VERSION}
- cpu: only cpu support
- - rocm: defaults to rocm6.2.4 (experimental)""", + - rocm: defaults to {DEFAULTS.ROCM} (experimental)""", ) parser.add_argument( "--tag", @@ -648,6 +663,9 @@ def main( kwargs["cache_to"] = cache.format_cache_from() fastsurfer_home = Path(fastsurfer_home) if fastsurfer_home else default_home() + # read the freesurfer download url from pyproject.toml + with open(fastsurfer_home / "pyproject.toml", "rb") as fp: + pyproject_freesurfer = tomllib.load(fp)["tool"]["freesurfer"] if target not in get_args(Target): raise ValueError(f"Invalid target: {target}") @@ -659,7 +677,10 @@ def main( if device.startswith("cu") and target == "runtime": target = "runtime_cuda" kwargs["target"] = target - kwargs["build_arg"] = [f"DEVICE={DEFAULTS.MapDeviceType.get(device, 'cpu')}"] + kwargs["build_arg"] = [ + f"DEVICE={DEFAULTS.MapDeviceType.get(device, 'cpu')}", + f"FREESURFER_URL={pyproject_freesurfer['urls']['linux'].format(version=pyproject_freesurfer['version'])}" + ] if debug: kwargs["build_arg"].append("DEBUG=true") build_arg_list = [ @@ -677,14 +698,14 @@ def main( if ssl_verify is False: kwargs["build_arg"].append("MAMBA_SSL_VERIFY=") else: - _ssl_cert = "./Docker/custom-ssl.crt" + _ssl_cert = "tools/Docker/custom-ssl.crt" if (fastsurfer_home / _ssl_cert).exists(): (fastsurfer_home / _ssl_cert).unlink() from shutil import copy2 copy2(ssl_verify, fastsurfer_home / _ssl_cert) kwargs["build_arg"].append(f"MAMBA_SSL_CERTIFICATE={_ssl_cert}") kwargs["build_arg"].append(f"MAMBA_SSL_VERIFY=/install/{Path(_ssl_cert).name}") - build_filename = fastsurfer_home / "Docker/BUILD.info" + build_filename = fastsurfer_home / "tools" / "Docker" / "BUILD.info" if has_git(): version_sections = "+git" else: @@ -729,7 +750,7 @@ def main( logger.info("Version info added to the docker image:") logger.info(build_info["content"]) - dockerfile = fastsurfer_home / "Docker" / "Dockerfile" + dockerfile = fastsurfer_home / "tools" / "Docker" / "Dockerfile" try: docker_build_image( image_tag, @@ -759,12 +780,9 @@ def default_home() -> Path: Returns ------- Path - The FASTSURFER_HOME-path. + The FastSurfer root path belonging to this build.py file. """ - if "FASTSURFER_HOME" in os.environ: - return Path(os.environ["FASTSURFER_HOME"]) - else: - return Path(__file__).parent.parent + return Path(__file__).parents[2] if __name__ == "__main__": diff --git a/Docker/conda_pack.sh b/tools/Docker/conda_pack.sh similarity index 100% rename from Docker/conda_pack.sh rename to tools/Docker/conda_pack.sh diff --git a/Docker/entrypoint.sh b/tools/Docker/entrypoint.sh similarity index 100% rename from Docker/entrypoint.sh rename to tools/Docker/entrypoint.sh diff --git a/Docker/install_env.py b/tools/Docker/install_env.py similarity index 100% rename from Docker/install_env.py rename to tools/Docker/install_env.py diff --git a/Docker/install_fs_pruned.sh b/tools/build/install_fs_pruned.sh similarity index 84% rename from Docker/install_fs_pruned.sh rename to tools/build/install_fs_pruned.sh index 0346be306..c89617860 100755 --- a/Docker/install_fs_pruned.sh +++ b/tools/build/install_fs_pruned.sh @@ -11,16 +11,19 @@ # all supported recon-surf flags (--hires, --fsaparc etc.). -# Link where to find the FreeSurfer tarball: -fslink="https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/7.4.1/freesurfer-linux-ubuntu22_amd64-7.4.1.tar.gz" - +if [[ -z "${BASH_SOURCE[0]}" ]]; then THIS_SCRIPT="$0" +else THIS_SCRIPT="${BASH_SOURCE[0]}" +fi +# Link where to find the FreeSurfer tarball: +fslink="default" if [[ "$#" -lt 1 ]]; then echo - echo "Usage: install_fs_prunded install_dir [--upx] [--url freesurfer_download_url]" + echo "Usage: install_fs_pruned.sh install_dir [--upx] [--url freesurfer_download_url]" echo echo "--upx is optional, if passed, fs/bin will be packed" - echo "--url is optional, if passed, freesurfer will be downloaded from it instead of $fslink" + echo "--url is recommended! This is the download link for freesurfer." + echo " The link can be found in pyproject.toml:tool.freesurfer.url!" echo exit 2 fi @@ -35,23 +38,39 @@ upx="false" while [[ "$#" -ge 1 ]]; do lowercase=$(echo "$1" | tr '[:upper:]' '[:lower:]') case $lowercase in - --upx) - upx="true" - shift - ;; - --url) - if [[ "$2" != "default" ]]; then fslink=$2; fi - shift - shift - ;; - *) - echo "Invalid argument $1" - exit 1 - ;; + --upx) upx="true" ; shift ;; + --url) fslink=$2 ; shift ; shift ;; + *) echo "Invalid argument $1" ; exit 1 ;; esac done fss=$where/fs-tmp fsd=$where/freesurfer + +if [[ "$fslink" == "default" ]] +then + # --url not provided, try getting it from pyproject.toml +link=$(python3 <= (3, 11): import tomllib +else: + try: import tomli as tomllib + except Exception: sys.exit() + +for path in pathlib.Path("$THIS_SCRIPT").parents: + try: + if (path / "pyproject.toml").exists(): + with open(path / "pyproject.toml", "rb") as fp: dat = tomllib.load(fp)["tool"]["freesurfer"] + print(dat["urls"]["linux"].format(**dat)) + break + except Exception: + continue # ignore all errors +EOF +) + if [[ -n "$link" ]] ; then fslink="$link" + else echo "ERROR: Please provide the --url argument, could not find/parse pyproject.toml!" ; exit 1 + fi +fi + echo echo "Will install FreeSurfer to $fsd" echo @@ -79,6 +98,7 @@ function run_parallel () j=0 while [[ "$j" -lt "${#args}" ]] do + # shellcheck disable=SC2059 cmd=$(printf "$command" "${args[@]:$j:$num}") j=$((j + num)) $cmd & @@ -99,7 +119,22 @@ function run_parallel () # get FreeSurfer and unpack (some of it) echo "Downloading FS and unpacking portions ..." -wget --no-check-certificate -qO- $fslink | tar zxv --no-same-owner -C $where \ + +# temp freesurfer dl filename (to save the dl) +freesurfer_dl="freesurfer_$(date +%s).tar.gz" + +# dl aria2c if that exists, else wget or curl +if [[ -n "$(which aria2c)" ]] ; then dl=(aria2c -x 16 -s 16 -c --check-certificate=false -o "$freesurfer_dl" "$fslink" ) +elif [[ -n "$(which wget)" ]] ; then dl=(wget --no-check-certificate -qO- "$fslink" -O "$freesurfer_dl") +else dl=(curl -L --insecure "$fslink" -o "$freesurfer_dl") +fi + +if ! "${dl[@]}" ; then + echo "ERROR: Downloading FreeSurfer failed! This is not recoverable, see message above and retry!" + exit 1 +fi + +tar zxv --no-same-owner -C "$where" \ --exclude='freesurfer/average/*.gca' \ --exclude='freesurfer/average/Buckner_JNeurophysiol11_MNI152' \ --exclude='freesurfer/average/Choi_JNeurophysiol12_MNI152' \ @@ -148,21 +183,23 @@ wget --no-check-certificate -qO- $fslink | tar zxv --no-same-owner -C $where \ --exclude='freesurfer/subjects/rh.EC_average' \ --exclude='freesurfer/subjects/V1_average' \ --exclude='freesurfer/tktools' \ - --exclude='freesurfer/trctrain' + --exclude='freesurfer/trctrain' \ + -f "$freesurfer_dl" +rm "$freesurfer_dl" # rename download to tmp -mv $where/freesurfer $fss +mv "$where/freesurfer" "$fss" # mk directories -mkdir -p $fsd/average -mkdir -p $fsd/bin -mkdir -p $fsd/etc -mkdir -p $fsd/lib/bem -mkdir -p $fsd/python/scripts -mkdir -p $fsd/python/packages/fsbindings -mkdir -p $fsd/subjects/fsaverage/label -mkdir -p $fsd/subjects/fsaverage/surf +mkdir -p "$fsd/average" +mkdir -p "$fsd/bin" +mkdir -p "$fsd/etc" +mkdir -p "$fsd/lib/bem" +mkdir -p "$fsd/python/scripts" +mkdir -p "$fsd/python/packages/fsbindings" +mkdir -p "$fsd/subjects/fsaverage/label" +mkdir -p "$fsd/subjects/fsaverage/surf" # We need these copy_files=" @@ -392,15 +429,15 @@ echo for file in $copy_files do echo "copying $file" - cp -r $fss/$file $fsd/$file + cp -r "$fss/$file" "$fsd/$file" done # pack if desired with upx (do this before adding all the links if [[ "$upx" == "true" ]] ; then echo "finding executables in $fsd/bin/..." - exe=$(find $fsd/bin -exec file {} \; | grep ELF | cut -d: -f1) + exe=$(find "$fsd/bin" -exec file {} \; | grep ELF | cut -d: -f1) echo "packing $fsd/bin/ executables (this can take a while) ..." - run_parallel 8 "upx -9 %s %s %s %s" 4 $exe + run_parallel 8 "upx -9 %s %s %s %s" 4 "$exe" fi # Modify fsbindings Python package to allow calling scripts like asegstats2table directly: @@ -412,69 +449,8 @@ echo for file in $touch_files do echo "touching $file" - touch $fsd/$file -done - -# FS calls these for version info, but we don't need them -# so we link them to mri_info to save space. -link_files=" - bin/mri_and - bin/mri_aparc2aseg - bin/mri_ca_label - bin/mri_ca_normalize - bin/mri_ca_register - bin/mri_compute_overlap - bin/mri_compute_seg_overlap - bin/mri_em_register - bin/mri_fwhm - bin/mri_gcut - bin/mri_log_likelihood - bin/mri_motion_correct.fsl - bin/mri_normalize_tp2 - bin/mri_or - bin/mri_relabel_nonwm_hypos - bin/mri_remove_neck - bin/mri_stats2seg - bin/mri_surf2vol - bin/mri_surfcluster - bin/mri_voldiff - bin/mri_watershed - bin/mris_divide_parcellation - bin/mris_left_right_register - bin/mris_surface_stats - bin/mris_thickness - bin/mris_thickness_diff - bin/nu_correct - bin/tkregister2_cmdl" - -# create target for link with ERROR message if called -ltrg=$fsd/bin/not-here.sh -echo '#!/bin/bash -if [ "$1" == "-all-info" ]; then - echo "$0 not included ..." - exit 0 -fi -echo -echo "ERROR: The binary $0 is not included, your call is forwarded to not-here.sh" -echo -exit 1 -' > $ltrg -chmod a+x $ltrg -echo -for file in $link_files -do - echo "linking $file" - ln -s $ltrg $fsd/$file + touch "$fsd/$file" done -# use our python (not really needed in recon-all anyway) -p3=$(which python3) -if [ "$p3" == "" ]; then - echo "No python3 found, please install first!" - echo - exit 1 -fi -ln -s $p3 $fsd/bin/fspython - #cleanup -rm -rf $fss +rm -rf "$fss" diff --git a/tools/build/link_fs.sh b/tools/build/link_fs.sh new file mode 100755 index 000000000..1d737ad81 --- /dev/null +++ b/tools/build/link_fs.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# usage: link_fs.sh [ []] + +if [[ "$#" -gt 0 ]] && { [[ "${*/-h/}" != "$*" ]] || [[ "${*/--help/}" != "$*" ]] ; } ; then + echo "usage: $0 [ []]" + exit 0 +elif [[ "$#" == 1 ]] || [[ "$#" == 2 ]] +then + if [[ ! -e "$1" ]] ; then echo "ERROR: $1 does not exist!" ; exit 1 ; fi + PYTHON="$1" + if [[ "$#" == 2 ]] ; then FREESURFER_HOME="$2" ; fi +else + PYTHON=$(which python3) +fi +if [[ -z "$FREESURFER_HOME" ]] || [[ ! -d "$FREESURFER_HOME" ]] +then + echo "ERROR: FREESURFER_HOME not defined correctly!" + exit 1 +fi + +# FS calls these for version info, but we don't need them +# so we link them to not_here.sh (created below) to save space. +link_files=" + bin/mri_and + bin/mri_aparc2aseg + bin/mri_ca_label + bin/mri_ca_normalize + bin/mri_ca_register + bin/mri_compute_overlap + bin/mri_compute_seg_overlap + bin/mri_em_register + bin/mri_fwhm + bin/mri_gcut + bin/mri_log_likelihood + bin/mri_motion_correct.fsl + bin/mri_normalize_tp2 + bin/mri_or + bin/mri_relabel_nonwm_hypos + bin/mri_remove_neck + bin/mri_stats2seg + bin/mri_surf2vol + bin/mri_surfcluster + bin/mri_voldiff + bin/mri_watershed + bin/mris_divide_parcellation + bin/mris_left_right_register + bin/mris_surface_stats + bin/mris_thickness + bin/mris_thickness_diff + bin/nu_correct + bin/tkregister2_cmdl" + +# create target for link with ERROR message if called +ltrg=$FREESURFER_HOME/bin/not-here.sh +echo '#!/bin/bash +if [ "$1" == "-all-info" ]; then + echo "$0 not included ..." + exit 0 +fi +echo +echo "ERROR: The binary $0 is not included, your call is forwarded to not-here.sh" +echo +exit 1 +' > $ltrg +chmod a+x $ltrg +echo +for file in $link_files +do + echo "linking $file" + ln -s "$ltrg" "$FREESURFER_HOME/$file" +done + +# use our python (not really needed in recon-all anyway) +ln -sf "$PYTHON" "$FREESURFER_HOME/bin/fspython" diff --git a/env/export_pip-r.sh b/tools/export_pip-r.sh similarity index 100% rename from env/export_pip-r.sh rename to tools/export_pip-r.sh diff --git a/tools/macos_build/.gitignore b/tools/macos_build/.gitignore new file mode 100644 index 000000000..862dc5753 --- /dev/null +++ b/tools/macos_build/.gitignore @@ -0,0 +1,12 @@ +raw_package/ +installer/ +*.pkg +*.gz + +.eggs +build +scripts/postinstall +scripts_intell/postinstall +dist/ +resources/ +FastSurferPackageContent/ diff --git a/tools/macos_build/FastSurfer.py.template b/tools/macos_build/FastSurfer.py.template new file mode 100644 index 000000000..0c8bd2225 --- /dev/null +++ b/tools/macos_build/FastSurfer.py.template @@ -0,0 +1,12 @@ +from subprocess import run + +proc = run(["/usr/bin/osascript", +"-e", +"tell app \"Terminal\"", +"-e", +"do script \"source /Applications//macos_setup_fastsurfer.sh\"", +"-e", +"activate", +"-e", +"end tell" +]) diff --git a/tools/macos_build/README.md b/tools/macos_build/README.md new file mode 100644 index 000000000..dffda2000 --- /dev/null +++ b/tools/macos_build/README.md @@ -0,0 +1,36 @@ +# FastSurfer MacOS packaging +## Create MacOS package + +In order to build the MacOS package of FastSurfer, simply run: + +```bash +./build_release_package.sh +``` + +Script creates release package for MacOS, where `` is `arm` for arm64 arch based chips and `intel` for `x86_64` arch based chips. + +### Dependencies for the script + +Script is using py2app python dependency, which isn't installed through any requirements file of FastSurfer, so in order to run the script, make sure that py2app is installed. + +### Running the package + +After the script is executed, `installer` folder will be created along with the MacOS package of FastSurfer inside. +Run the package by opening it and follow instructions. + +After successful installation, FastSurfer applet and its source code will appear under the `/Applications` folder. + +### Run FastSurfer + +If you would want to run FastSurfer, you could either use terminal or FastSurfer applet. Though, running applet is recommended as it opens shell terminal and sets up environment for FastSurfer. + +#### FastSurfer Flags +* The `--fs_license` points to your FreeSurfer license. +* The `--t1` points to the t1-weighted MRI image to analyse (full, absolute path). +* The `--sid` is the subject ID name (folder name in output directory). +* The `--sd` points to the output directory. +* [more flags](../../doc/overview/FLAGS.md#fastsurfer-flags) + +A directory with the name as specified in `--sid` (here subjectX) will be created in the output directory (specified via `--sd`). So in this example output will be written to /home/user/my_fastsurfer_analysis/subjectX/ . Make sure the output directory is empty, to avoid overwriting existing files. + +All other available flags are identical to the ones explained on the main page [README](../../README.md). diff --git a/tools/macos_build/build_release_package.sh b/tools/macos_build/build_release_package.sh new file mode 100755 index 000000000..dca74a573 --- /dev/null +++ b/tools/macos_build/build_release_package.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +if [[ "$#" != 1 ]] || { [[ "$1" != "arm" ]] && [[ "$1" != "intel" ]] ; } ; then + echo + echo "Usage: build_release_package.sh " + echo + exit +fi + +if [[ -z "${BASH_SOURCE[0]}" ]]; then THIS_SCRIPT="$0" +else THIS_SCRIPT="${BASH_SOURCE[0]}" +fi +build_dir=$(dirname "$THIS_SCRIPT") +tools_dir=$(dirname "$build_dir") + +FASTSURFER_HOME=$(dirname "$tools_dir") # directory to fastsurfer +# version of the project +VERSION=$(python3 "$tools_dir/read_toml.py" --file "$FASTSURFER_HOME/pyproject.toml" --key project.version) +VERSION_NO_DOTS=${VERSION//./} +#version of the freesurfer +FREESURFER_VERSION=$(python3 "$tools_dir/read_toml.py" --file "$FASTSURFER_HOME/pyproject.toml" --key tool.freesurfer.version) +# freesurfer install url +URL_TO_FREESURFER_TEMP=$(python3 "$tools_dir/read_toml.py" --file "$FASTSURFER_HOME/pyproject.toml" --key tool.freesurfer.urls.macOS) +sub="{version}" +URL_TO_FREESURFER="${URL_TO_FREESURFER_TEMP//$sub/$FREESURFER_VERSION}" +ARCH_TYPE=$1 # chip architecture - arm or intel + +ARCH_TYPE_NAME="arm64" +if [[ "$ARCH_TYPE" = "intel" ]] ; then ARCH_TYPE_NAME="x86_64" ; fi + +RESOURCES_DIR="$build_dir/resources" +# name of the package displayed in the installer +PACKAGE_NAME=FastSurfer$VERSION_NO_DOTS-macos-darwin_${ARCH_TYPE_NAME} +# package identifier (f.e. com.mycompany.productid) +ID="org.deep-mi.FastSurfer.${VERSION_NO_DOTS}_${ARCH_TYPE_NAME}" +# install location for the content of the package +INSTALLATION_DIR="/Applications" +# raw package file to be created +OUTPUT_PKG="$build_dir/raw_package/$PACKAGE_NAME.pkg" +# installer to be created +INSTALLER_PKG="$build_dir/installer/$PACKAGE_NAME.pkg" + +# create temporary folder to package and copy FastSurfer over +STAGED_DIR="$build_dir/FastSurferPackageContent" +FASTSURFER_TO_PACKAGE="$STAGED_DIR/FastSurfer$VERSION" +mkdir -p "$STAGED_DIR" +rsync -av --progress "$FASTSURFER_HOME/" "$FASTSURFER_TO_PACKAGE" \ + --exclude requirements.txt \ + --exclude requirements.cpu.txt \ + --exclude tools \ + --exclude .git + +# install freesurfer into temp folder +"$tools_dir/build/install_fs_pruned.sh" "$STAGED_DIR" --upx --url "$URL_TO_FREESURFER" + +if [[ ! -d "$STAGED_DIR/freesurfer" ]] +then + echo "FreeSurfer install was unsuccessful!" + exit 1 +fi + +SCRIPTS_DIR="$tools_dir/macos_build/scripts" # directory with scripts executed during installation process (f.e. preinstall postinstall) +PYTHON_VERSION_TEMP=$(python3 "$tools_dir/read_toml.py" --file "$FASTSURFER_HOME/pyproject.toml" --key project.requires-python) +PYTHON_VERSION="${PYTHON_VERSION_TEMP#>=}" + +# substitute values in postinstall script +PATH_TO_FASTSURFER="$INSTALLATION_DIR/FastSurfer$VERSION" +HOMEBREW_DIR=$([[ "$ARCH_TYPE" = "arm" ]] && echo "/opt/homebrew" || echo "/usr/local") + +sed -e "s||${PATH_TO_FASTSURFER}|g" \ + -e "s||${PYTHON_VERSION}|g" \ + -e "s||$HOMEBREW_DIR|g" \ + < "$SCRIPTS_DIR/postinstall.sh.template" \ + > "$SCRIPTS_DIR/postinstall" + +chmod +x "$SCRIPTS_DIR/postinstall" + +# assemble resources +mkdir -p "$RESOURCES_DIR" +cp "$FASTSURFER_HOME/doc/images/fastsurfer.png" "$RESOURCES_DIR" +cp "$FASTSURFER_HOME/doc/overview/MACOS.md" "$RESOURCES_DIR" +cp "$FASTSURFER_HOME/LICENSE" "$RESOURCES_DIR/LICENSE.txt" + +# create fastsurfer applet +sed -e "s||FastSurfer${VERSION}|g" \ + < "$build_dir/FastSurfer.py.template" \ + > "$build_dir/FastSurfer.py" + +MPS_FALLBACK_VALUE=$([[ "$ARCH_TYPE" = "arm" ]] && echo 1 || echo 0) +sed -e "s||FastSurfer${VERSION}|g" \ + -e "s||${PYTHON_VERSION}|g" \ + -e "s||${MPS_FALLBACK_VALUE}|g" \ + < "$build_dir/macos_setup_fastsurfer.sh.template" \ + > "$build_dir/macos_setup_fastsurfer.sh" + +mv "$build_dir/macos_setup_fastsurfer.sh" "$FASTSURFER_TO_PACKAGE/" + +pushd "$build_dir" || exit 1 +python3 "setup.py" py2app --iconfile "${RESOURCES_DIR:$((${#build_dir} + 1))}/fastsurfer.png" +popd || exit 1 +mv "$build_dir/dist/FastSurfer.app" "$STAGED_DIR/FastSurfer$VERSION.app" + +rm -f "$build_dir/FastSurfer.py" +chmod -R 755 "$STAGED_DIR"/* + +# create raw package +mkdir -p "$build_dir/raw_package" +pkgbuild \ + --root "$STAGED_DIR" \ + --version "$VERSION" \ + --identifier "$ID" \ + --install-location "$INSTALLATION_DIR" \ + --scripts "$SCRIPTS_DIR" \ + "$OUTPUT_PKG" + +rm -f "$SCRIPTS_DIR/postinstall" + +# create distribution file template based on provided package +DISTRIBUTION_FILE="$RESOURCES_DIR/distribution.xml" + +productbuild --synthesize --package "$OUTPUT_PKG" "$DISTRIBUTION_FILE" + +# edit the distribution file +# set title to package name (f.e. package_name.pkg -> package_name) +python3 "$build_dir/edit_distribution.py" --file "$DISTRIBUTION_FILE" --title "$PACKAGE_NAME" + +# create installer package +mkdir -p "$build_dir/installer" +productbuild \ + --distribution "$DISTRIBUTION_FILE" \ + --resources "$RESOURCES_DIR" \ + --package-path $build_dir/raw_package \ + "$INSTALLER_PKG" + +# get rid of temporary folder +rm -rf "$STAGED_DIR" "$RESOURCES_DIR" "$build_dir/dist" "$build_dir/build" diff --git a/tools/macos_build/edit_distribution.py b/tools/macos_build/edit_distribution.py new file mode 100644 index 000000000..a634c4e8d --- /dev/null +++ b/tools/macos_build/edit_distribution.py @@ -0,0 +1,95 @@ +# Copyright 2024 Image Analysis 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. +import argparse +import sys +from pathlib import Path +from xml.etree import ElementTree + + +def make_parser() -> argparse.ArgumentParser: + """ + Create a command line interface and return command line options. + + Returns + ------- + options + Namespace object holding options. + """ + parser = argparse.ArgumentParser( + description="Script to edit provided distribution.xml file.", + ) + parser.add_argument( + "--file", "-f", + type=Path, + dest="file", + help="path to distribution file to edit", + required=True, + ) + parser.add_argument( + "--title", "-t", + type=str, + dest="title", + help="value for the title of the distribution file", + required=True + ) + return parser + + +def edit_distribution(distribution_file: Path | str, title: str) -> None: + """ + Take path to distribution file and edit it. + + Parameters + ---------- + distribution_file : Path, str + Path to distribution file. + title : str + Value for the title tag. + """ + dist_elementtree = ElementTree.parse(distribution_file) + + root_tag = dist_elementtree.getroot() + + title_tag = ElementTree.SubElement(root_tag, "title") + title_tag.text = title + + background_tag = ElementTree.SubElement(root_tag, "background") + background_tag.attrib["file"] = "fastsurfer.png" + background_tag.attrib["uti"] = "public.png" + background_tag.attrib["scaling"] = "proportional" + background_tag.attrib["alignment"] = "bottomleft" + + background_darkAqua_tag = ElementTree.SubElement(root_tag, "background-darkAqua") + background_darkAqua_tag.attrib = background_tag.attrib + + license_tag = ElementTree.SubElement(root_tag, "license") + license_tag.attrib["file"] = "LICENSE.txt" + license_tag.attrib["mime-type"] = "text/txt" + + conclusion_tag = ElementTree.SubElement(root_tag, "conclusion") + conclusion_tag.attrib["file"] = "MACOS.md" + conclusion_tag.attrib["mime-type"] = "text/txt" + + ElementTree.indent(dist_elementtree, ' ') + + dist_elementtree.write(distribution_file, encoding="utf-8", xml_declaration=True) + + +if __name__ == "__main__": + parser = make_parser() + args = parser.parse_args() + + print(f"Editing distribution file: {args.file} ...") + edit_distribution(args.file, args.title) + sys.exit(0) diff --git a/tools/macos_build/macos_setup_fastsurfer.sh.template b/tools/macos_build/macos_setup_fastsurfer.sh.template new file mode 100644 index 000000000..f0cff739a --- /dev/null +++ b/tools/macos_build/macos_setup_fastsurfer.sh.template @@ -0,0 +1,27 @@ +#!/bin/bash + +export FASTSURFER_HOME=/Applications/ +source $FASTSURFER_HOME/venv/bin/activate +export PYTHONPATH=$FASTSURFER_HOME/venv/lib/python/site-packages:$PYTHONPATH +export PYTORCH_ENABLE_MPS_FALLBACK= +export FREESURFER_HOME=/Applications/freesurfer +source $FREESURFER_HOME/SetUpFreeSurfer.sh > /dev/null +echo +echo "This is the FastSurfer console. Call \`run_fastsurfer.sh -t1 ...\` in here! " +echo "See also https://deep-mi.org/FastSurfer/stable/overview/MACOS.html" + +if ! grep -q "export PATH=\"$(brew --prefix)/opt/grep/libexec/gnubin:\$PATH\"" ~/.bash_profile; then + echo "export PATH=\"$(brew --prefix)/opt/grep/libexec/gnubin:\$PATH\"" >> ~/.bash_profile; + export PATH="$(brew --prefix)/opt/grep/libexec/gnubin:$PATH" + echo "GNU grep has been added to path" +fi + +if ! grep -q "export PATH=\"$FASTSURFER_HOME:\$PATH\"" ~/.bash_profile; then + echo "export PATH=\"$FASTSURFER_HOME:\$PATH\"" >> ~/.bash_profile; + export PATH="$FASTSURFER_HOME:$PATH" + echo "FastSurfer dir has been added to path" +fi + +if [[ -z "${FS_LICENSE}" ]]; then + echo "Set FS_LICENSE environmental variable" +fi diff --git a/tools/macos_build/scripts/link_fs.sh b/tools/macos_build/scripts/link_fs.sh new file mode 100755 index 000000000..1d737ad81 --- /dev/null +++ b/tools/macos_build/scripts/link_fs.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# usage: link_fs.sh [ []] + +if [[ "$#" -gt 0 ]] && { [[ "${*/-h/}" != "$*" ]] || [[ "${*/--help/}" != "$*" ]] ; } ; then + echo "usage: $0 [ []]" + exit 0 +elif [[ "$#" == 1 ]] || [[ "$#" == 2 ]] +then + if [[ ! -e "$1" ]] ; then echo "ERROR: $1 does not exist!" ; exit 1 ; fi + PYTHON="$1" + if [[ "$#" == 2 ]] ; then FREESURFER_HOME="$2" ; fi +else + PYTHON=$(which python3) +fi +if [[ -z "$FREESURFER_HOME" ]] || [[ ! -d "$FREESURFER_HOME" ]] +then + echo "ERROR: FREESURFER_HOME not defined correctly!" + exit 1 +fi + +# FS calls these for version info, but we don't need them +# so we link them to not_here.sh (created below) to save space. +link_files=" + bin/mri_and + bin/mri_aparc2aseg + bin/mri_ca_label + bin/mri_ca_normalize + bin/mri_ca_register + bin/mri_compute_overlap + bin/mri_compute_seg_overlap + bin/mri_em_register + bin/mri_fwhm + bin/mri_gcut + bin/mri_log_likelihood + bin/mri_motion_correct.fsl + bin/mri_normalize_tp2 + bin/mri_or + bin/mri_relabel_nonwm_hypos + bin/mri_remove_neck + bin/mri_stats2seg + bin/mri_surf2vol + bin/mri_surfcluster + bin/mri_voldiff + bin/mri_watershed + bin/mris_divide_parcellation + bin/mris_left_right_register + bin/mris_surface_stats + bin/mris_thickness + bin/mris_thickness_diff + bin/nu_correct + bin/tkregister2_cmdl" + +# create target for link with ERROR message if called +ltrg=$FREESURFER_HOME/bin/not-here.sh +echo '#!/bin/bash +if [ "$1" == "-all-info" ]; then + echo "$0 not included ..." + exit 0 +fi +echo +echo "ERROR: The binary $0 is not included, your call is forwarded to not-here.sh" +echo +exit 1 +' > $ltrg +chmod a+x $ltrg +echo +for file in $link_files +do + echo "linking $file" + ln -s "$ltrg" "$FREESURFER_HOME/$file" +done + +# use our python (not really needed in recon-all anyway) +ln -sf "$PYTHON" "$FREESURFER_HOME/bin/fspython" diff --git a/tools/macos_build/scripts/postinstall.sh.template b/tools/macos_build/scripts/postinstall.sh.template new file mode 100644 index 000000000..4cdbc9897 --- /dev/null +++ b/tools/macos_build/scripts/postinstall.sh.template @@ -0,0 +1,25 @@ +#!/bin/bash + +FASTSURFER_HOME="" +PYTHON="python" +SCRIPT_DIR="$(dirname $0)" + +if [[ ! -d "$FASTSURFER_HOME/venv" ]] +then + echo "Creating FastSurfer run environment" + "/bin/$PYTHON" -m venv --copies "$FASTSURFER_HOME/venv" +fi + +source "$FASTSURFER_HOME/venv/bin/activate" +"$PYTHON" -m pip install --upgrade pip +"$PYTHON" -m pip install -r "$FASTSURFER_HOME/requirements.mac.txt" + +export PYTHONPATH="${FASTSURFER_HOME}:${PYTHONPATH}" +"$PYTHON" "$FASTSURFER_HOME/FastSurferCNN/download_checkpoints.py" --all + +FREESURFER_HOME="/Applications/freesurfer" +"$SCRIPT_DIR/link_fs.sh" "$FASTSURFER_HOME/venv/bin/$PYTHON" "$FREESURFER_HOME" + +sed -i '' -e "s|(venv)|($(basename $FASTSURFER_HOME))|g" "$FASTSURFER_HOME/venv/bin/activate" + +xattr -dr com.apple.quarantine "$FREESURFER_HOME"/* diff --git a/tools/macos_build/setup.py b/tools/macos_build/setup.py new file mode 100644 index 000000000..62fe0a37d --- /dev/null +++ b/tools/macos_build/setup.py @@ -0,0 +1,22 @@ +""" +This is a setup.py script generated by py2applet + +Usage: + python setup.py py2app +""" + +import os + +from setuptools import setup + +build_dir = os.path.dirname(os.path.realpath(__file__)) +APP = [build_dir + '/FastSurfer.py'] +DATA_FILES = [] +OPTIONS = {} + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) diff --git a/tools/read_toml.py b/tools/read_toml.py new file mode 100644 index 000000000..3c6f9a83d --- /dev/null +++ b/tools/read_toml.py @@ -0,0 +1,93 @@ +#!python3 + +# Copyright 2024 Image Analysis 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. + +import argparse +import sys +from pathlib import Path + +# python 3.11 supports tomllib, but we have tomli in fastsurfer +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + + +def make_parser() -> argparse.ArgumentParser: + """ + Create a command line interface and return command line options. + + Returns + ------- + options + Namespace object holding options. + """ + parser = argparse.ArgumentParser( + description="Tool for extracting values from configuration file", + ) + parser.add_argument( + "--file", "-f", + type=Path, + dest="file", + help="configuration file", + required=True, + ) + parser.add_argument( + "--key", "-k", + type=str, + dest="key", + help="key to lookup", + required=True, + ) + return parser + + +def extract_value(config_file: Path | str, key: str) -> str: + """ + Read configuration file and return the value for the given key. + + Parameters + ---------- + config_file : Path, str + Path to configuration file. + key : str + Key to lookup. + + Returns + ------- + value + Value under the given key. + """ + with open(config_file, "rb") as config: + conf = tomllib.load(config) + try: + section, *keys = key.split('.') + value = conf[section] + for k in keys: + value = value[k] + return value + except KeyError as e: + raise ValueError(f"Key {key} not found in configuration file") from e + except tomllib.TOMLDecodeError as e: + print(e) + sys.exit(1) + + +if __name__ == "__main__": + parser = make_parser() + args = parser.parse_args() + + print(extract_value(args.file, args.key)) + sys.exit(0)