Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes/newsfragments/342.misc
Original file line number Diff line number Diff line change
@@ -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`_
33 changes: 33 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 26 additions & 0 deletions junifer/api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
3 changes: 3 additions & 0 deletions junifer/api/res/freesurfer/mri_binarize
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

run_freesurfer_docker.sh mri_binarize "$@"
3 changes: 3 additions & 0 deletions junifer/api/res/freesurfer/mri_mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

run_freesurfer_docker.sh mri_mc "$@"
3 changes: 3 additions & 0 deletions junifer/api/res/freesurfer/mri_pretess
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

run_freesurfer_docker.sh mri_pretess "$@"
3 changes: 3 additions & 0 deletions junifer/api/res/freesurfer/mris_convert
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

run_freesurfer_docker.sh mris_convert "$@"
61 changes: 61 additions & 0 deletions junifer/api/res/freesurfer/run_freesurfer_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash

corrected_args=()
docker_args=()
mounts=0

FS_LICENSE="${FS_LICENSE:=$HOME/freesurfer_license.txt}"
synchon marked this conversation as resolved.
Show resolved Hide resolved

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
65 changes: 64 additions & 1 deletion junifer/pipeline/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: "
Expand All @@ -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:
Expand Down Expand Up @@ -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
Loading