[ENH]: Add support for FreeSurfer via Docker #342

Merged
synchon merged 5 commits from feat/docker-freesurfer-support into main 2024-05-14 13:15:40 +00:00
9 changed files with 197 additions and 1 deletions

View file

@ -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`_

View file

@ -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
<https://surfer.nmr.mgh.harvard.edu/fswiki/DownloadAndInstall>`_. 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 <https://docs.docker.com/get-docker/>`_.
2. Pull the FreeSurfer Docker image from
`Docker Hub FreeSurfer <https://hub.docker.com/r/freesurfer/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

View file

@ -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")

View file

@ -0,0 +1,3 @@
#!/bin/bash
run_freesurfer_docker.sh mri_binarize "$@"

View file

@ -0,0 +1,3 @@
#!/bin/bash
run_freesurfer_docker.sh mri_mc "$@"

View file

@ -0,0 +1,3 @@
#!/bin/bash
run_freesurfer_docker.sh mri_pretess "$@"

View file

@ -0,0 +1,3 @@
#!/bin/bash
run_freesurfer_docker.sh mris_convert "$@"

View file

@ -0,0 +1,61 @@
#!/bin/bash
corrected_args=()
docker_args=()
mounts=0
FS_LICENSE="${FS_LICENSE:=$HOME/freesurfer_license.txt}"
fraimondo commented 2024-05-14 13:14:10 +00:00 (Migrated from github.com)

I see what you did here :P

I see what you did here :P
synchon commented 2024-05-14 13:15:14 +00:00 (Migrated from github.com)

lol :P

lol :P
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

View file

@ -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