diff --git a/docs/changes/newsfragments/342.misc b/docs/changes/newsfragments/342.misc new file mode 100644 index 0000000000..4a479d1ac6 --- /dev/null +++ b/docs/changes/newsfragments/342.misc @@ -0,0 +1 @@ +Add support for accessing FreeSurfer via Docker wrapper along with ``mri_binarize``, ``mri_pretess``, ``mri_mc`` and ``mris_convert`` by `Synchon Mandal`_ diff --git a/docs/installation.rst b/docs/installation.rst index 26f78e902d..dcb475af98 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -163,3 +163,36 @@ Or, alternatively, you can execute this command which will update the .. code-block:: bash junifer setup ants-docker | grep "PATH=" | xargs | >> ~/.bashrc + +FreeSurfer +---------- + +To install FreeSurfer, you can always follow the `FreeSurfer official instructions +`_. Additionally, you can +also follow the following steps to install and configure the FreeSurfer Docker container +in your local system. + +1. Install Docker. You can follow the + `Docker official instructions `_. +2. Pull the FreeSurfer Docker image from + `Docker Hub FreeSurfer `_: + +.. code-block:: bash + + docker pull freesurfer/freesurfer + +3. Add the Junifer FreeSurfer scripts to your PATH environment variable. Run the + following command: + +.. code-block:: bash + + junifer setup freesurfer-docker + +Take the last line and copy it to your ``.bashrc`` or ``.zshrc`` file. + +Or, alternatively, you can execute this command which will update the +``~/.bashrc`` for you: + +.. code-block:: bash + + junifer setup freesurfer-docker | grep "PATH=" | xargs | >> ~/.bashrc diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 7b04c5543c..0608f32a01 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -588,3 +588,29 @@ def ants_docker() -> None: # pragma: no cover export PATH="$PATH:{ants_wrappers_path}" """ click.secho(msg, fg="blue") + + +@setup.command("freesurfer-docker") +def freesurfer_docker() -> None: # pragma: no cover + """Configure FreeSurfer-Docker wrappers.""" + import junifer + + pkg_path = Path(junifer.__path__[0]) # type: ignore + fs_wrappers_path = pkg_path / "api" / "res" / "freesurfer" + msg = f""" + Installation instructions for FreeSurfer-Docker wrappers: + + 1. Install Docker: https://docs.docker.com/get-docker/ + + 2. Get the FreeSurfer-Docker image by running this on the command line: + + docker pull freesurfer/freesurfer + + 3. Get license from: https://surfer.nmr.mgh.harvard.edu/registration.html . + You can skip this step if you already have one. + + 4. Add this line to the ~/.bashrc or ~/.zshrc file: + + export PATH="$PATH:{fs_wrappers_path}" + """ + click.secho(msg, fg="blue") diff --git a/junifer/api/res/freesurfer/mri_binarize b/junifer/api/res/freesurfer/mri_binarize new file mode 100644 index 0000000000..5f59bcc7cf --- /dev/null +++ b/junifer/api/res/freesurfer/mri_binarize @@ -0,0 +1,3 @@ +#!/bin/bash + +run_freesurfer_docker.sh mri_binarize "$@" diff --git a/junifer/api/res/freesurfer/mri_mc b/junifer/api/res/freesurfer/mri_mc new file mode 100644 index 0000000000..1b280dec9c --- /dev/null +++ b/junifer/api/res/freesurfer/mri_mc @@ -0,0 +1,3 @@ +#!/bin/bash + +run_freesurfer_docker.sh mri_mc "$@" diff --git a/junifer/api/res/freesurfer/mri_pretess b/junifer/api/res/freesurfer/mri_pretess new file mode 100644 index 0000000000..b9423c9c00 --- /dev/null +++ b/junifer/api/res/freesurfer/mri_pretess @@ -0,0 +1,3 @@ +#!/bin/bash + +run_freesurfer_docker.sh mri_pretess "$@" diff --git a/junifer/api/res/freesurfer/mris_convert b/junifer/api/res/freesurfer/mris_convert new file mode 100644 index 0000000000..39b34c6ba1 --- /dev/null +++ b/junifer/api/res/freesurfer/mris_convert @@ -0,0 +1,3 @@ +#!/bin/bash + +run_freesurfer_docker.sh mris_convert "$@" diff --git a/junifer/api/res/freesurfer/run_freesurfer_docker.sh b/junifer/api/res/freesurfer/run_freesurfer_docker.sh new file mode 100644 index 0000000000..e8d1d587db --- /dev/null +++ b/junifer/api/res/freesurfer/run_freesurfer_docker.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +corrected_args=() +docker_args=() +mounts=0 + +FS_LICENSE="${FS_LICENSE:=$HOME/freesurfer_license.txt}" + +if [ -f "${FS_LICENSE}" ]; then + >&2 echo "Using freesurfer license from ${FS_LICENSE}" +else + >&2 echo "Freesurfer license not found at ${FS_LICENSE}. You can either set FS_LICENSE to the path of the license file or place the license file at +${FS_LICENSE}" + exit 1 +fi + +# Map the license path to the container by binding +license_path=$(realpath "${FS_LICENSE}") +license_path_fname=$(basename "${FS_LICENSE}") +host_dir=$(dirname "${license_path}") +((mounts+=1)) +container_dir="/data/mount_${mounts}" +docker_args+=("-v ${host_dir}:${container_dir}") +export DOCKERENV_FS_LICENSE=${container_dir}/${license_path_fname} + +for var in "$@" +do +if [ -d "${var}" ]; then + echo "$var is a directory" >&2 + var=$(realpath "${var}") + host_dir=$(dirname "${var}") + ((mounts+=1)) + container_dir="/data/mount_${mounts}" + docker_args+=("-v ${host_dir}:${container_dir}") + var=${container_dir} + elif [ -f "${var}" ] || [[ "${var}" == /* ]]; then + if [ -f "${var}" ]; then + echo "$var is a file" >&2 + var=$(realpath "${var}") + else + echo "$var is a prefix" >&2 + fi + fname=$(basename "${var}") + host_dir=$(dirname "${var}") + ((mounts+=1)) + container_dir="/data/mount_${mounts}" + docker_args+=("-v ${host_dir}:${container_dir}") + var=${container_dir}/${fname} + fi + corrected_args+=("${var}") +done + +echo "Docker args: ${docker_args[*]}" >&2 +echo "Corrected args for FreeSurfer: ${corrected_args[*]}" >&2 + +cwd=$(pwd) +cmd="docker run --rm ${docker_args[*]} -v ${cwd}:${cwd} -w ${cwd} freesurfer/freesurfer ${corrected_args[*]}" +echo "Running command: ${cmd}" >&2 +${cmd} + +unset DOCKERENV_FS_LICENSE diff --git a/junifer/pipeline/utils.py b/junifer/pipeline/utils.py index 0dfd8ed692..712fc600f2 100644 --- a/junifer/pipeline/utils.py +++ b/junifer/pipeline/utils.py @@ -37,7 +37,7 @@ def check_ext_dependencies( If ``name`` is mandatory and is not found. """ - valid_ext_dependencies = ("afni", "fsl", "ants") + valid_ext_dependencies = ("afni", "fsl", "ants", "freesurfer") if name not in valid_ext_dependencies: raise_error( "Invalid value for `name`, should be one of: " @@ -52,6 +52,9 @@ def check_ext_dependencies( # Check for ants elif name == "ants": found = _check_ants(**kwargs) + # Check for freesurfer + elif name == "freesurfer": + found = _check_freesurfer(**kwargs) # Check if the dependency is mandatory in case it's not found if not found and not optional: @@ -245,3 +248,63 @@ def _check_ants(commands: Optional[List[str]] = None) -> bool: f"{commands_found_results}" ) return ants_found + + +def _check_freesurfer(commands: Optional[List[str]] = None) -> bool: + """Check if FreeSurfer is present in the system. + + Parameters + ---------- + commands : list of str, optional + The commands to specifically check for from FreeSurfer. If None, only + the basic FreeSurfer help would be looked up, else, would also + check for specific commands (default None). + + Returns + ------- + bool + Whether FreeSurfer is found or not. + + """ + completed_process = subprocess.run( + "recon-all -help", + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + shell=True, # is unsafe but kept for resolution via PATH + check=False, + ) + fs_found = completed_process.returncode == 0 + + # Check for specific commands + if fs_found and commands is not None: + if not isinstance(commands, list): + commands = [commands] + # Store command found results + commands_found_results = {} + # Set all commands found flag to True + all_commands_found = True + # Check commands' existence + for command in commands: + command_process = subprocess.run( + [command], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + shell=True, # is unsafe but kept for resolution via PATH + check=False, + ) + command_found = command_process.returncode == 0 + commands_found_results[command] = ( + "found" if command_found else "not found" + ) + # Set flag to trigger warning + all_commands_found = all_commands_found and command_found + # One or more commands were missing + if not all_commands_found: + warn_with_log( + "FreeSurfer is installed but some of the required commands " + "were not found. These are the results: " + f"{commands_found_results}" + ) + return fs_found