diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..5b94359216
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "esbonio.server.enabled": false
+}
\ No newline at end of file
diff --git a/.zenodo.json b/.zenodo.json
index eec1b4536c..b6c6e29c67 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -47,6 +47,11 @@
"orcid": "0000-0002-5312-6729",
"type": "Researcher"
},
+ {
+ "affiliation": "Beckman Institute for Advanced Science & Technology, University of Illinois at Urbana-Champaign, IL, USA",
+ "name": "Camacho, Paul B.",
+ "orcid": "0000-0001-9048-7307",
+ },
{
"affiliation": "Department of Pediatrics, University of Minnesota, MN, USA",
"name": "Feczko, Eric",
diff --git a/Dockerfile b/Dockerfile
index b19b2deea6..54a0d7005c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,6 +33,12 @@ RUN python -m build /src/fmriprep
FROM ubuntu:jammy-20221130
# Prepare environment
+ENV DEBIAN_FRONTEND="noninteractive"
+ENV LANG=C.UTF-8
+ARG PYTHON_VERSION=3.8
+ARG CONDA_FILE=Miniconda3-py38_4.11.0-Linux-x86_64.sh
+ENV DEBIAN_FRONTEND=noninteractive
+
RUN apt-get update && \
apt-get install -y --no-install-recommends \
apt-utils \
@@ -41,6 +47,9 @@ RUN apt-get update && \
bzip2 \
ca-certificates \
curl \
+ wget \
+ upx \
+ file \
git \
gnupg \
libtool \
@@ -51,15 +60,45 @@ RUN apt-get update && \
xvfb && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
-ENV DEBIAN_FRONTEND="noninteractive" \
- LANG="en_US.UTF-8" \
- LC_ALL="en_US.UTF-8"
+# git clone stable branch of FastSurfer
+RUN cd /opt && mkdir /fastsurfer \
+ && git clone -b v2.0.4 https://github.com/Deep-MI/FastSurfer.git \
+ && cp /opt/FastSurfer/fastsurfer_env_gpu.yml /fastsurfer/fastsurfer_env_gpu.yml
+
+# Copy fastsurfer from git folder
+RUN cp -R /opt/FastSurfer/* /fastsurfer/ && rm -rf /opt/FastSurfer
+
+# Install conda
+RUN wget --no-check-certificate -qO ~/miniconda.sh https://repo.continuum.io/miniconda/$CONDA_FILE && \
+ chmod +x ~/miniconda.sh && \
+ ~/miniconda.sh -b -p /opt/conda && \
+ rm ~/miniconda.sh
# Installing freesurfer
COPY docker/files/freesurfer7.3.2-exclude.txt /usr/local/etc/freesurfer7.3.2-exclude.txt
RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/7.3.2/freesurfer-linux-ubuntu22_amd64-7.3.2.tar.gz \
| tar zxv --no-same-owner -C /opt --exclude-from=/usr/local/etc/freesurfer7.3.2-exclude.txt
+# Install required packages for freesurfer to run
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ tcsh \
+ time \
+ bc \
+ gawk \
+ libgomp1 && \
+ apt clean && \
+ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+# Add FreeSurfer Environment variables
+ENV OS=Linux \
+ FS_OVERRIDE=0 \
+ FIX_VERTEX_AREA= \
+ SUBJECTS_DIR=/opt/freesurfer/subjects \
+ FSF_OUTPUT_FORMAT=nii.gz \
+ FREESURFER_HOME=/opt/freesurfer \
+ PYTHONUNBUFFERED=0 \
+ PATH=/opt/freesurfer/bin:$PATH
+
# Simulate SetUpFreeSurfer.sh
ENV FSL_DIR="/opt/fsl-6.0.5.1" \
OS="Linux" \
@@ -89,7 +128,6 @@ RUN apt-get update -qq \
libgl1-mesa-dev \
libgl1-mesa-dri \
libglu1-mesa-dev \
- libgomp1 \
libice6 \
libxcursor1 \
libxft2 \
@@ -98,6 +136,7 @@ RUN apt-get update -qq \
libxrender1 \
libxt6 \
sudo \
+ curl \
wget \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
@@ -167,21 +206,16 @@ RUN GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyr
# AFNI latest (neurodocker build)
RUN apt-get update -qq \
&& apt-get install -y -q --no-install-recommends \
- apt-utils \
ed \
gsl-bin \
libglib2.0-0 \
- libglu1-mesa-dev \
libglw1-mesa \
- libgomp1 \
libjpeg62 \
libpng12-0 \
libxm4 \
libxp6 \
netpbm \
- tcsh \
xfonts-base \
- xvfb \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& curl -sSL --retry 5 -o /tmp/multiarch.deb http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/multiarch-support_2.27-3ubuntu1.5_amd64.deb \
@@ -281,10 +315,16 @@ RUN /opt/conda/bin/python fetch_templates.py && \
find $HOME/.cache/templateflow -type d -exec chmod go=u {} + && \
find $HOME/.cache/templateflow -type f -exec chmod go=u {} +
+# Installing FastSurfer gpu dependencies
+RUN conda env update -n base --file /fastsurfer/fastsurfer_env_gpu.yml
+
# Installing sMRIPREP
COPY --from=src /src/fmriprep/dist/*.whl .
RUN /opt/conda/bin/python -m pip install --no-cache-dir $( ls *.whl )[all]
+# Installing nibabel version 4.0.2
+RUN /opt/conda/bin/python -m pip install --no-cache-dir nibabel==4.0.2
+
RUN find $HOME -type d -exec chmod go=u {} + && \
find $HOME -type f -exec chmod go=u {} + && \
rm -rf $HOME/.npm $HOME/.conda $HOME/.empty
@@ -294,6 +334,12 @@ RUN find $HOME -type d -exec chmod go=u {} + && \
ENV FREESURFER="/opt/freesurfer"
ENV IS_DOCKER_8395080871=1
+ENV FASTSURFER_HOME=/fastsurfer
+ENV PATH="$PATH:/fastsurfer"
+
+# Download all remote network checkpoints already
+ENV PYTHONPATH=/fastsurfer:$PYTHONPATH
+RUN cd /fastsurfer ; python3 FastSurferCNN/download_checkpoints.py --all
RUN ldconfig
WORKDIR /tmp
diff --git a/docker/files/freesurfer7.3.2-exclude.txt b/docker/files/freesurfer7.3.2-exclude.txt
index a817a062b1..09714a0bc1 100644
--- a/docker/files/freesurfer7.3.2-exclude.txt
+++ b/docker/files/freesurfer7.3.2-exclude.txt
@@ -4,20 +4,10 @@ freesurfer/average/3T18yoSchwartzReactN32_as_orig.4dfp.img
freesurfer/average/3T18yoSchwartzReactN32_as_orig.4dfp.img.rec
freesurfer/average/3T18yoSchwartzReactN32_as_orig.4dfp.mat
freesurfer/average/3T18yoSchwartzReactN32_as_orig.lst
-freesurfer/average/711-2B_as_mni_average_305.4dfp.hdr
-freesurfer/average/711-2B_as_mni_average_305.4dfp.ifh
-freesurfer/average/711-2B_as_mni_average_305.4dfp.img
-freesurfer/average/711-2B_as_mni_average_305.4dfp.img.rec
-freesurfer/average/711-2B_as_mni_average_305_mask.4dfp.hdr
-freesurfer/average/711-2B_as_mni_average_305_mask.4dfp.img.rec
-freesurfer/average/711-2C_as_mni_average_305.4dfp.hdr
-freesurfer/average/711-2C_as_mni_average_305.4dfp.img.rec
-freesurfer/average/711-2C_as_mni_average_305.4dfp.mat
freesurfer/average/aseg+spmhead+vermis+pons.ixi.gca
freesurfer/average/BrainstemSS
freesurfer/average/Buckner_JNeurophysiol11_MNI152
freesurfer/average/Choi_JNeurophysiol12_MNI152
-freesurfer/average/colortable_desikan_killiany.txt
freesurfer/average/face.gca
freesurfer/average/HippoSF
freesurfer/average/label_scales.dat
@@ -41,7 +31,6 @@ freesurfer/average/mni305.cor.subfov1.mgz
freesurfer/average/mni305.cor.subfov1.reg
freesurfer/average/mni305.cor.subfov2.mgz
freesurfer/average/mni305.cor.subfov2.reg
-freesurfer/average/mni305.mask.cor.mgz
freesurfer/average/mni_average_305.4dfp.hdr
freesurfer/average/mni_average_305.4dfp.ifh
freesurfer/average/mni_average_305.4dfp.img
@@ -420,7 +409,6 @@ freesurfer/bin/mri_make_template
freesurfer/bin/mri_map_cpdat
freesurfer/bin/mri_maps2csd
freesurfer/bin/mri_mark_temporal_lobe
-freesurfer/bin/mri_mc
freesurfer/bin/mri_mcsim
freesurfer/bin/mri_mergelabels
freesurfer/bin/mri_mi
@@ -504,7 +492,6 @@ freesurfer/bin/mris_gradient
freesurfer/bin/mris_hausdorff_dist
freesurfer/bin/mris_image2vtk
freesurfer/bin/mri_simulate_atrophy
-freesurfer/bin/mris_info
freesurfer/bin/mris_init_global_tractography
freesurfer/bin/mris_intensity_profile
freesurfer/bin/mris_interpolate_warp
@@ -548,7 +535,6 @@ freesurfer/bin/mris_rf_label
freesurfer/bin/mris_rf_train
freesurfer/bin/mris_rotate
freesurfer/bin/mris_sample_label
-freesurfer/bin/mris_sample_parc
freesurfer/bin/mris_seg2annot
freesurfer/bin/mris_segment
freesurfer/bin/mris_segmentation_stats
@@ -749,11 +735,9 @@ freesurfer/bin/xhemireg
freesurfer/bin/xhemi-tal
freesurfer/bin/xsanatreg
freesurfer/bin/zero_lt_4dfp
-freesurfer/DefectLUT.txt
freesurfer/diffusion
freesurfer/docs/xml
freesurfer/FreeSurferEnv.csh
-freesurfer/FreeSurferEnv.sh
freesurfer/fsfast
freesurfer/lib/bem/ic0.tri
freesurfer/lib/bem/ic1.tri
@@ -861,11 +845,8 @@ freesurfer/python/lib/python3.8/site-packages/torch
freesurfer/python/lib/python3.8/site-packages/**/tests
freesurfer/python/**/__pycache__
freesurfer/python/share
-freesurfer/SegmentNoLUT.txt
freesurfer/sessions
freesurfer/SetUpFreeSurfer.csh
-freesurfer/SetUpFreeSurfer.sh
-freesurfer/Simple_surface_labels2009.txt
freesurfer/sources.sh
freesurfer/subjects/bert
freesurfer/subjects/cvs_avg35
diff --git a/docs/installation.rst b/docs/installation.rst
index a4c6d204b1..148241d396 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -148,5 +148,6 @@ the ``smriprep`` package:
- ANTs_ (version 2.2.0 - NeuroDocker build)
- AFNI_ (version Debian-16.2.07)
- `C3D `_ (version 1.0.0)
-- FreeSurfer_ (version 6.0.1)
+- FreeSurfer_ (version 7.2.0)
- `bids-validator `_ (version 1.1.0)
+- FastSurfer_ (version 2.0.1)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 20f6906786..fe5957b1cf 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -10,4 +10,4 @@ sphinx
sphinx_rtd_theme
sphinxcontrib-apidoc ~= 0.3.0
templateflow
-networkx != 2.8.1
+networkx != 2.8.1
\ No newline at end of file
diff --git a/smriprep/cli/run.py b/smriprep/cli/run.py
index 4a588283f8..2db66800f7 100644
--- a/smriprep/cli/run.py
+++ b/smriprep/cli/run.py
@@ -222,14 +222,21 @@ def get_parser():
dest="hires",
help="disable sub-millimeter (hires) reconstruction",
)
- g_surfs_xor = g_surfs.add_mutually_exclusive_group()
+ g_surfs_xor = g_surfs.add_mutually_exclusive_group()
g_surfs_xor.add_argument(
"--fs-no-reconall",
action="store_false",
dest="run_reconall",
help="disable FreeSurfer surface preprocessing.",
)
+ g_surfs_xor.add_argument(
+ "--fastsurfer",
+ action="store_true",
+ default=False,
+ dest="fastsurfer",
+ help="enable FastSurfer surface preprocessing.",
+ )
g_other = parser.add_argument_group("Other options")
g_other.add_argument(
@@ -394,6 +401,17 @@ def _warn_redirect(message, category, filename, lineno, file=None, line=None):
from templateflow import api
from niworkflows.utils.misc import _copy_any
+ dseg_tsv = str(api.get("fsaverage", suffix="dseg", extension=[".tsv"]))
+ _copy_any(
+ dseg_tsv, str(Path(output_dir) / "smriprep" / "desc-aseg_dseg.tsv")
+ )
+ _copy_any(
+ dseg_tsv, str(Path(output_dir) / "smriprep" / "desc-aparcaseg_dseg.tsv")
+ )
+ elif opts.fastsurfer:
+ from templateflow import api
+ from niworkflows.utils.misc import _copy_any
+
dseg_tsv = str(api.get("fsaverage", suffix="dseg", extension=[".tsv"]))
_copy_any(
dseg_tsv, str(Path(output_dir) / "smriprep" / "desc-aseg_dseg.tsv")
@@ -584,6 +602,7 @@ def build_workflow(opts, retval):
freesurfer=opts.run_reconall,
fs_subjects_dir=opts.fs_subjects_dir,
hires=opts.hires,
+ fastsurfer=opts.fastsurfer,
layout=layout,
longitudinal=opts.longitudinal,
low_mem=opts.low_mem,
diff --git a/smriprep/data/boilerplate.bib b/smriprep/data/boilerplate.bib
index 9acffc48dc..730f47c74c 100644
--- a/smriprep/data/boilerplate.bib
+++ b/smriprep/data/boilerplate.bib
@@ -74,6 +74,34 @@ @article{fs_reconall
year = 1999
}
+@article{fastsurfer,
+title = {FastSurfer - A fast and accurate deep learning based neuroimaging pipeline},
+journal = {NeuroImage},
+volume = {219},
+pages = {117012},
+year = {2020},
+issn = {1053-8119},
+doi = {https://doi.org/10.1016/j.neuroimage.2020.117012},
+url = {https://www.sciencedirect.com/science/article/pii/S1053811920304985},
+author = {Leonie Henschel and Sailesh Conjeti and Santiago Estrada and Kersten Diers and Bruce Fischl and Martin Reuter},
+keywords = {Freesurfer, Computational neuroimaging, Deep learning, Structural MRI, Artificial intelligence},
+abstract = {Traditional neuroimage analysis pipelines involve computationally intensive, time-consuming optimization steps, and thus, do not scale well to large cohort studies with thousands or tens of thousands of individuals. In this work we propose a fast and accurate deep learning based neuroimaging pipeline for the automated processing of structural human brain MRI scans, replicating FreeSurfer’s anatomical segmentation including surface reconstruction and cortical parcellation. To this end, we introduce an advanced deep learning architecture capable of whole-brain segmentation into 95 classes. The network architecture incorporates local and global competition via competitive dense blocks and competitive skip pathways, as well as multi-slice information aggregation that specifically tailor network performance towards accurate segmentation of both cortical and subcortical structures. Further, we perform fast cortical surface reconstruction and thickness analysis by introducing a spectral spherical embedding and by directly mapping the cortical labels from the image to the surface. This approach provides a full FreeSurfer alternative for volumetric analysis (in under 1 min) and surface-based thickness analysis (within only around 1 h runtime). For sustainability of this approach we perform extensive validation: we assert high segmentation accuracy on several unseen datasets, measure generalizability and demonstrate increased test-retest reliability, and high sensitivity to group differences in dementia.}
+}
+
+@article{fastsufrer_hires,
+title = {FastSurferVINN: Building resolution-independence into deep learning segmentation methods—A solution for HighRes brain MRI},
+journal = {NeuroImage},
+volume = {251},
+pages = {118933},
+year = {2022},
+issn = {1053-8119},
+doi = {https://doi.org/10.1016/j.neuroimage.2022.118933},
+url = {https://www.sciencedirect.com/science/article/pii/S1053811922000623},
+author = {Leonie Henschel and David Kügler and Martin Reuter},
+keywords = {Computational neuroimaging, Deep learning, Structural MRI, Artificial intelligence, High-resolution},
+abstract = {Leading neuroimaging studies have pushed 3T MRI acquisition resolutions below 1.0 mm for improved structure definition and morphometry. Yet, only few, time-intensive automated image analysis pipelines have been validated for high-resolution (HiRes) settings. Efficient deep learning approaches, on the other hand, rarely support more than one fixed resolution (usually 1.0 mm). Furthermore, the lack of a standard submillimeter resolution as well as limited availability of diverse HiRes data with sufficient coverage of scanner, age, diseases, or genetic variance poses additional, unsolved challenges for training HiRes networks. Incorporating resolution-independence into deep learning-based segmentation, i.e., the ability to segment images at their native resolution across a range of different voxel sizes, promises to overcome these challenges, yet no such approach currently exists. We now fill this gap by introducing a Voxel-size Independent Neural Network (VINN) for resolution-independent segmentation tasks and present FastSurferVINN, which (i) establishes and implements resolution-independence for deep learning as the first method simultaneously supporting 0.7–1.0 mm whole brain segmentation, (ii) significantly outperforms state-of-the-art methods across resolutions, and (iii) mitigates the data imbalance problem present in HiRes datasets. Overall, internal resolution-independence mutually benefits both HiRes and 1.0 mm MRI segmentation. With our rigorously validated FastSurferVINN we distribute a rapid tool for morphometric neuroimage analysis. The VINN architecture, furthermore, represents an efficient resolution-independent segmentation method for wider application.}
+}
+
@article{mindboggle,
author = {Klein, Arno and Ghosh, Satrajit S. and Bao, Forrest S. and Giard, Joachim and Häme, Yrjö and Stavsky, Eliezer and Lee, Noah and Rossa, Brian and Reuter, Martin and Neto, Elias Chaibub and Keshavan, Anisha},
doi = {10.1371/journal.pcbi.1005350},
diff --git a/smriprep/interfaces/fastsurfer.py b/smriprep/interfaces/fastsurfer.py
new file mode 100644
index 0000000000..9833eaf483
--- /dev/null
+++ b/smriprep/interfaces/fastsurfer.py
@@ -0,0 +1,399 @@
+# -*- coding: utf-8 -*-
+# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
+# vi: set ft=python sts=4 ts=4 sw=4 et:
+"""
+The FastSufer module provides basic functions
+for running FastSurfer VINN and surface processing.
+
+"""
+from nipype.interfaces.base import (
+ CommandLine,
+ Directory,
+ CommandLineInputSpec,
+ OutputMultiPath,
+ BaseInterfaceInputSpec,
+ File,
+)
+from nipype.interfaces.base.traits_extension import traits
+from nipype.interfaces.io import FSSourceOutputSpec, FreeSurferSource
+
+
+class FastSInputSpec(CommandLineInputSpec):
+ r"""
+ Required arguments
+ ==================
+
+ subjects_dir
+ Output directory
+ subject_id
+ Subject ID for directory inside ``sd`` to be created
+ t1
+ T1 full head input, not bias corrected, global path.
+ The 'network was trained with conformed images
+ UCHAR, 256 x 256 x 256, 1 mm voxels and standard slice orientation.
+ These specifications are checked in the ``eval.py`` script and the image
+ is automatically conformed if it does not comply.
+ fs_license
+ Path to FreeSurfer license key file.
+
+
+ Optional arguments
+ ==================
+
+ Network specific arguments
+ --------------------------
+ seg
+ Global path with filename of segmentation
+ (where and under which name to store it).
+ Default location
+ ``$SUBJECTS_DIR/$sid/mri/aparc.DKTatlas+aseg.deep.mgz``
+ weights_sag
+ Pretrained weights of sagittal network.
+ Default
+ ``../checkpoints/Sagittal_Weights_FastSurferCNN/ckpts/Epoch_30_training_state.pkl``
+ weights_ax
+ Pretrained weights of axial network.
+ Default
+ ``../checkpoints/Axial_Weights_FastSurferCNN/ckpts/Epoch_30_training_state.pkl``
+ weights_cor
+ Pretrained weights of coronal network.
+ Default
+ ``../checkpoints/Coronal_Weights_FastSurferCNN/ckpts/Epoch_30_training_state.pkl``
+ seg_log
+ Name and location for the log-file for the segmentation (FastSurferCNN).
+ Default '$SUBJECTS_DIR/$sid/scripts/deep-seg.log'
+ clean_seg
+ Flag to clean up FastSurferCNN segmentation
+ run_viewagg_on
+ Define where the view aggregation should be run on.
+ By default, the program checks if you have enough memory to run
+ the view aggregation on the gpu. The total memory is considered for this decision.
+ If this fails, or you actively overwrote the check with setting
+ ``run_viewagg_on cpu``, view agg is run on the cpu.
+ Equivalently, if you define ``--run_viewagg_on gpu``, view agg will be run on the gpu
+ (no memory check will be done).
+ no_cuda
+ Flag to disable CUDA usage in FastSurferCNN (no GPU usage, inference on CPU).
+ batch
+ Batch size for inference. Default 16. Lower this to reduce memory requirement.
+
+
+ Surface pipeline arguments
+ --------------------------
+ fstess
+ Use ``mri_tesselate`` instead of marching cube (default) for surface creation
+ fsqsphere
+ Use FreeSurfer default instead of
+ ndovel spectral spherical projection for qsphere
+ fsaparc
+ Use FS aparc segmentations in addition to DL prediction
+ (slower in this case and usually the mapped ones from the DL prediction are fine)
+ no_surfreg
+ Skip creating Surface-Atlas ``sphere.reg`` registration with FreeSurfer
+ (for cross-subject correspondence or other mappings)
+ parallel
+ Run both hemispheres in parallel
+ threads
+ Set openMP and ITK threads
+
+
+ Other
+ ----
+ py
+ which python version to use.
+ Default ``python3.8``
+ seg_only
+ only run FastSurferCNN
+ (generate segmentation, do not run the surface pipeline)
+ surf_only
+ only run the surface pipeline ``recon_surf``.
+ The segmentation created by FastSurferCNN must already exist in this case.
+
+
+ """
+ subjects_dir = Directory(
+ exists=True,
+ argstr="--sd %s",
+ hash_files=False,
+ desc="Subjects directory",
+ genfile=True,
+ )
+ subject_id = traits.String(
+ argstr="--sid %s",
+ mandatory=True,
+ desc="Subject ID"
+ )
+ T1_files = File(
+ exists=True,
+ mandatory=False,
+ argstr="--t1 %s",
+ desc="T1 full head input (not bias corrected, global path)"
+ )
+ fs_license = File(
+ exists=True,
+ argstr="--fs_license %s",
+ desc="Path to FreeSurfer license key file."
+ )
+ seg = File(
+ exists=True,
+ argstr="--seg %s",
+ desc="Pre-computed segmentation file"
+ )
+ weights_sag = File(
+ exists=True,
+ mandatory=False,
+ argstr="--weights_sag %s",
+ desc="Pretrained weights of sagittal network"
+ )
+ weights_ax = File(
+ exists=True,
+ mandatory=False,
+ argstr="--weights_ax %s",
+ desc="Pretrained weights of axial network"
+ )
+ weights_cor = File(
+ exists=True,
+ mandatory=False,
+ argstr="--weights_cor %s",
+ desc="Pretrained weights of coronal network"
+ )
+ seg_log = File(
+ exists=True,
+ mandatory=False,
+ argstr="--seg_log %s",
+ desc="Name and location for the log-file for the segmentation (FastSurferCNN)."
+ )
+ clean_seg = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--clean_seg",
+ desc="Flag to clean up FastSurferCNN segmentation"
+ )
+ run_viewagg_on = File(
+ exists=True,
+ mandatory=False,
+ argstr="--run_viewagg_on %s",
+ desc="Define where the view aggregation should be run on."
+ )
+ no_cuda = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--no_cuda",
+ desc="Flag to disable CUDA usage in FastSurferCNN (no GPU usage, inference on CPU)"
+ )
+ batch = traits.Int(
+ 16,
+ usedefault=True,
+ mandatory=False,
+ argstr="--batch %d",
+ desc="Batch size for inference. default=16. Lower this to reduce memory requirement"
+ )
+ fstess = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--fstess",
+ desc="Use mri_tesselate instead of marching cube (default) for surface creation"
+ )
+ fsqsphere = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--fsqsphere",
+ desc="Use FreeSurfer default instead of novel spectral spherical projection for qsphere"
+ )
+ fsaparc = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--fsaparc",
+ desc="Use FS aparc segmentations in addition to DL prediction"
+ )
+ no_surfreg = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--no_surfreg",
+ desc="""Skip creating Surface-Atlas (sphere.reg) registration with FreeSurfer
+ (for cross-subject correspondence or other mappings)"""
+ )
+ parallel = traits.Bool(
+ True,
+ usedefault=True,
+ mandatory=False,
+ argstr="--parallel",
+ desc="Run both hemispheres in parallel"
+ )
+ threads = traits.Int(
+ 4,
+ usedefault=True,
+ mandatory=False,
+ argstr="--threads %d",
+ desc="Set openMP and ITK threads to"
+ )
+ py = traits.String(
+ "python3.8",
+ usedefault=True,
+ mandatory=False,
+ argstr="--py %s",
+ desc="which python version to use. default=python3.6"
+ )
+ seg_only = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--seg_only",
+ desc="only run FastSurferCNN (generate segmentation, do not surface)"
+ )
+ surf_only = traits.Bool(
+ False,
+ mandatory=False,
+ argstr="--surf_only",
+ desc="only run the surface pipeline recon_surf."
+ )
+
+
+class FastSurfSourceInputSpec(BaseInterfaceInputSpec):
+ sd = Directory(
+ exists=True,
+ argstr="--sd %s",
+ mandatory=False,
+ desc="Subjects directory"
+ )
+ sid = traits.String(
+ exists=True,
+ argstr="--sid %s",
+ mandatory=False,
+ desc="Subject ID"
+ )
+ t1 = File(
+ exists=True,
+ mandatory=False,
+ argstr="--t1 %s",
+ desc="T1 full head input (not bias corrected, global path)"
+ )
+
+
+class FastSurferSourceOutputSpec(FSSourceOutputSpec):
+ orig_nu = File(
+ exists=True,
+ desc="Base image conformed to Fastsurfer space and nonuniformity corrected",
+ loc="mri"
+ )
+ wm_asegedit = OutputMultiPath(
+ File(exists=True),
+ desc="Edited white matter volume post-aseg",
+ loc="mri",
+ altkey="mri_nu_correct.mni"
+ )
+ wmparc_mapped = OutputMultiPath(
+ exists=True,
+ loc="mri",
+ desc="DKT atlas mapped Aparc into subcortical white matter",
+ altkey="wmparc.DKTatlas.mapped"
+ )
+ mni_log = OutputMultiPath(
+ File(exists=True),
+ desc="Non-uniformity correction log",
+ loc="mri",
+ altkey="mri_nu_correct.mni"
+ )
+ mni_log_bak = OutputMultiPath(
+ File(exists=True),
+ desc="Non-uniformity correction log bak",
+ loc="mri",
+ altkey="mri_nu_correct.mni"
+ )
+ defects = OutputMultiPath(
+ File(exists=True),
+ desc="Defects",
+ loc="surf",
+ altkey=["defect_borders", "defect_chull", "defect_labels"],
+ )
+ nofix = OutputMultiPath(
+ File(exists=True),
+ desc="Pre-tessellation original surface",
+ loc="surf",
+ altkey=["nofix"],
+ )
+ aparc_ctab = OutputMultiPath(
+ File(exists=True),
+ loc="label",
+ altkey="aparc.annot.mapped.ctab",
+ desc="Aparc parcellation annotation ctab file",
+ )
+ mapped_024 = OutputMultiPath(
+ File(exists=True),
+ loc="label",
+ altkey="mapped*024",
+ desc="Mapped label files",
+ )
+ cortex = OutputMultiPath(
+ File(exists=True),
+ loc="label",
+ altkey="cortex",
+ desc="Cortex class label files",
+ )
+ aparc_dkt_aseg = OutputMultiPath(
+ File(exists=True),
+ loc="mri",
+ altkey="aparc.DKTatlas*aseg*",
+ desc="Aparc parcellation from DKT atlas projected into aseg volume",
+ )
+ segment_dat = OutputMultiPath(
+ File(exists=True),
+ loc="mri",
+ altkey="segment_dat",
+ desc="Segmentation .dat files",
+ )
+ filled_pretess = OutputMultiPath(
+ File(exists=True),
+ loc="mri",
+ altkey="filled*pretess*",
+ desc="Pre-tessellation filled volume files",
+ )
+ preaparc = OutputMultiPath(
+ File(exists=True),
+ loc="surf",
+ altkey="preaparc",
+ desc="Pre-Aparc files",
+ )
+ w_g_stats = OutputMultiPath(
+ File(exists=True),
+ loc="stats",
+ altkey="w*g.pct",
+ desc="White minus gray statistics files"
+ )
+ aseg_presurf_stats = OutputMultiPath(
+ File(exists=True),
+ loc="stats",
+ altkey="aseg.presurf.hypos",
+ desc="Automated segmentation pre-surface recon statistics files"
+ )
+ subjects_dir = Directory(
+ exists=True,
+ argstr="--sd %s",
+ desc="Subjects directory"
+ )
+ subject_id = traits.String(
+ exists=True,
+ argstr="--sid %s",
+ desc="Subject ID"
+ )
+
+
+class FastSurferSource(FreeSurferSource):
+ """Generates FastSurfer subject info from their directories.
+
+ """
+
+ output_spec = FastSurferSourceOutputSpec
+
+
+class FastSurfer(CommandLine):
+ """Wraps FastSurfer command for segmentation and surface processing.
+
+ """
+
+ input_spec = FastSInputSpec
+ output_spec = FastSurferSourceOutputSpec
+ _cmd = 'run_fastsurfer.sh'
+
+ def _list_outputs(self):
+ outputs = self.output_spec().get()
+ return outputs
diff --git a/smriprep/interfaces/reports.py b/smriprep/interfaces/reports.py
index 7db4f92c1b..0ba0a77e81 100644
--- a/smriprep/interfaces/reports.py
+++ b/smriprep/interfaces/reports.py
@@ -37,6 +37,8 @@
)
from nipype.interfaces import freesurfer as fs
from nipype.interfaces.io import FSSourceInputSpec as _FSSourceInputSpec
+from smriprep.interfaces import fastsurfer as fastsurf
+from smriprep.utils.misc import check_fastsurfer
from nipype.interfaces.mixins import reporting
from niworkflows.interfaces.reportlets.base import _SVGReportCapableInputSpec
@@ -48,6 +50,7 @@
\t\tStructural images: {n_t1s:d} T1-weighted {t2w}
\t\tStandard spaces: {output_spaces}
\t\tFreeSurfer reconstruction: {freesurfer_status}
+\t\tFreeSurfer reconstruction: {fastsurfer_status}
\t
"""
@@ -99,6 +102,7 @@ class SubjectSummary(SummaryInterface):
input_spec = _SubjectSummaryInputSpec
output_spec = _SubjectSummaryOutputSpec
+ fastsurfer_bool = False
def _run_interface(self, runtime):
if isdefined(self.inputs.subject_id):
@@ -106,15 +110,28 @@ def _run_interface(self, runtime):
return super(SubjectSummary, self)._run_interface(runtime)
def _generate_segment(self):
+ fastsurfer_status = "Not run"
if not isdefined(self.inputs.subjects_dir):
freesurfer_status = "Not run"
+ fastsurfer_status = "Not run"
else:
- recon = fs.ReconAll(
+ fastsurfer_bool = check_fastsurfer(
subjects_dir=self.inputs.subjects_dir,
- subject_id=self.inputs.subject_id,
- T1_files=self.inputs.t1w,
- flags="-noskullstrip",
- )
+ subject_id=self.inputs.subject_id)
+ if fastsurfer_bool is True:
+ recon = fastsurf.FastSCommand(
+ sd=self.inputs.subjects_dir,
+ sid=self.inputs.subject_id,
+ t1=self.inputs.t1w,
+ )
+ fastsurfer_status = "Run by sMRIPrep"
+ else:
+ recon = fs.ReconAll(
+ subjects_dir=self.inputs.subjects_dir,
+ subject_id=self.inputs.subject_id,
+ T1_files=self.inputs.t1w,
+ flags="-noskullstrip",
+ )
if recon.cmdline.startswith("echo"):
freesurfer_status = "Pre-existing directory"
else:
@@ -136,6 +153,7 @@ def _generate_segment(self):
t2w=t2w_seg,
output_spaces=output_spaces,
freesurfer_status=freesurfer_status,
+ fastsurfer_status=fastsurfer_status,
)
diff --git a/smriprep/utils/misc.py b/smriprep/utils/misc.py
index 99d1458866..4145517381 100644
--- a/smriprep/utils/misc.py
+++ b/smriprep/utils/misc.py
@@ -95,3 +95,40 @@ def fs_isRunning(subjects_dir, subject_id, mtime_tol=86400, logger=None):
if logger:
logger.warn(f'Removed "IsRunning*" files found under {subj_dir}')
return subjects_dir
+
+
+def check_fastsurfer(subjects_dir, subject_id, logger=None):
+ """
+ Checks FreeSurfer subjects dir for presence of files in mri/ with names \
+ indicating processing with FastSurfer.
+
+ Parameters
+ ----------
+ subjects_dir : os.PathLike or None
+ Existing FreeSurfer subjects directory
+ subject_id : str
+ Subject label
+
+ Returns
+ -------
+ fastsurfer_bool : Boolean
+
+ """
+ from pathlib import Path
+
+ fastsurfer_bool = False
+
+ if subjects_dir is None:
+ return fastsurfer_bool
+ subj_dir = Path(subjects_dir) / subject_id
+ if not subj_dir.exists():
+ return fastsurfer_bool
+
+ fastsurferfiles = tuple(subj_dir.glob("mri/*deep*mgz"))
+ if not fastsurferfiles:
+ return fastsurfer_bool
+ else:
+ if logger:
+ logger.warn(f'Evidence of FastSurfer processing found in {subj_dir}')
+ fastsurfer_bool = True
+ return fastsurfer_bool
diff --git a/smriprep/workflows/anatomical.py b/smriprep/workflows/anatomical.py
index 571e011dc1..78e4ecdb64 100644
--- a/smriprep/workflows/anatomical.py
+++ b/smriprep/workflows/anatomical.py
@@ -33,7 +33,7 @@
)
from nipype.interfaces.ants.base import Info as ANTsInfo
-from nipype.interfaces.ants import DenoiseImage, N4BiasFieldCorrection
+from nipype.interfaces.ants import N4BiasFieldCorrection
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
@@ -48,10 +48,15 @@
from niworkflows.utils.misc import fix_multi_T1w_source_name, add_suffix
from niworkflows.anat.ants import init_brain_extraction_wf, init_n4_only_wf
from ..utils.bids import get_outputnode_spec
-from ..utils.misc import apply_lut as _apply_bids_lut, fs_isRunning as _fs_isRunning
+from ..utils.misc import (
+ apply_lut as _apply_bids_lut,
+ fs_isRunning as _fs_isRunning,
+ check_fastsurfer as _check_fastsurfer
+)
from .norm import init_anat_norm_wf
from .outputs import init_anat_reports_wf, init_anat_derivatives_wf
-from .surfaces import init_anat_ribbon_wf, init_surface_recon_wf, init_morph_grayords_wf
+from .surfaces import init_surface_recon_wf, init_fastsurf_recon_wf, init_anat_ribbon_wf, init_morph_grayords_wf
+from .surfaces import init_anat_ribbon_wf, init_surface_recon_wf
LOGGER = logging.getLogger("nipype.workflow")
@@ -62,6 +67,7 @@ def init_anat_preproc_wf(
freesurfer,
hires,
longitudinal,
+ fastsurfer,
t1w,
t2w,
omp_nthreads,
@@ -100,6 +106,7 @@ def init_anat_preproc_wf(
freesurfer=True,
hires=True,
longitudinal=False,
+ fastsurfer=False,
t1w=['t1w.nii.gz'],
t2w=[],
omp_nthreads=1,
@@ -125,6 +132,10 @@ def init_anat_preproc_wf(
longitudinal : :obj:`bool`
Create unbiased structural template, regardless of number of inputs
(may increase runtime)
+ fastsurfer : :obj:`bool`
+ Enable FastSurfer surface reconstruction (shorter runtime than
+ the FreeSurfer workflow). Uses similar input variables and output
+ file structure as FreeSurfer.
t1w : :obj:`list`
List of T1-weighted structural images.
omp_nthreads : :obj:`int`
@@ -241,6 +252,7 @@ def init_anat_preproc_wf(
# Connect reportlets workflows
anat_reports_wf = init_anat_reports_wf(
freesurfer=freesurfer,
+ fastsurfer=fastsurfer,
output_dir=output_dir,
)
# fmt:off
@@ -457,10 +469,11 @@ def _check_img(img):
])
# fmt:on
- # Write outputs ############################################3
+ # Write outputs #
anat_derivatives_wf = init_anat_derivatives_wf(
bids_root=bids_root,
freesurfer=freesurfer,
+ fastsurfer=fastsurfer,
num_t1w=num_t1w,
t2w=t2w,
output_dir=output_dir,
@@ -513,7 +526,7 @@ def _check_img(img):
(fast2bids, outputnode, [('out', 't1w_tpms')]),
])
# fmt:on
- if not freesurfer: # Flag --fs-no-reconall is set - return
+ if not freesurfer and not fastsurfer: # Flag --fs-no-reconall is set - return
# fmt:off
workflow.connect([
(brain_extraction_wf, buffernode, [
@@ -529,10 +542,50 @@ def _check_img(img):
)
fs_isrunning.inputs.logger = LOGGER
- # 5. Surface reconstruction (--fs-no-reconall not set)
- surface_recon_wf = init_surface_recon_wf(
- name="surface_recon_wf", omp_nthreads=omp_nthreads, hires=hires
+ # check for FastSurfer .mgz files and
+ # touch mri/aseg.auto_noCCseg.label_intensities.txt
+ # to prevent failure in surfaces.py (temporary fix - DEPRECATED)
+ check_fastsurfer = pe.Node(
+ niu.Function(function=_check_fastsurfer), overwrite=True, name="check_fastsurfer"
)
+ check_fastsurfer.inputs.logger = LOGGER
+
+ # Select which surface reconstruction workflow based on CLI arguments
+ if freesurfer and (not fastsurfer):
+ surface_recon_wf = init_surface_recon_wf(
+ name="surface_recon_wf", omp_nthreads=omp_nthreads, hires=hires)
+ # fmt:off
+ workflow.connect([
+ (inputnode, fs_isrunning, [
+ ('subjects_dir', 'subjects_dir'),
+ ('subject_id', 'subject_id')]),
+ (inputnode, surface_recon_wf, [
+ ('t2w', 'inputnode.t2w'),
+ ('flair', 'inputnode.flair'),
+ ('subject_id', 'inputnode.subject_id')]),
+ (fs_isrunning, surface_recon_wf, [('out', 'inputnode.subjects_dir')]),
+ (surface_recon_wf, anat_reports_wf, [
+ ('outputnode.subject_id', 'inputnode.subject_id'),
+ ('outputnode.subjects_dir', 'inputnode.subjects_dir'),
+ ]),
+ ])
+ # fmt:on
+ elif fastsurfer:
+ surface_recon_wf = init_fastsurf_recon_wf(
+ name="surface_recon_wf", omp_nthreads=omp_nthreads, hires=hires)
+ # fmt:off
+ workflow.connect([
+ (inputnode, surface_recon_wf, [
+ ('subject_id', 'inputnode.subject_id'),
+ ('subjects_dir', 'inputnode.subjects_dir')]),
+ (surface_recon_wf, anat_reports_wf, [
+ ('outputnode.subject_id', 'inputnode.subject_id'),
+ ('outputnode.subjects_dir', 'inputnode.subjects_dir'),
+ ]),
+ ])
+ # fmt:on
+
+ # Identical connections
applyrefined = pe.Node(fsl.ApplyMask(), name="applyrefined")
if t2w:
@@ -568,9 +621,9 @@ def _check_img(img):
workflow.connect([
(inputnode, t2w_template_wf, [('t2w', 'inputnode.anat_files')]),
(t2w_template_wf, bbreg, [('outputnode.anat_ref', 'source_file')]),
- (surface_recon_wf, bbreg, [
- ('outputnode.subject_id', 'subject_id'),
- ('outputnode.subjects_dir', 'subjects_dir'),
+ (inputnode, bbreg, [
+ ('subject_id', 'subject_id'),
+ ('subjects_dir', 'subjects_dir'),
]),
(bbreg, coreg_xfms, [('out_lta_file', 'in1')]),
(surface_recon_wf, coreg_xfms, [('outputnode.fsnative2t1w_xfm', 'in2')]),
@@ -587,15 +640,8 @@ def _check_img(img):
# Anatomical ribbon file using HCP signed-distance volume method
anat_ribbon_wf = init_anat_ribbon_wf()
# fmt:off
+
workflow.connect([
- (inputnode, fs_isrunning, [
- ('subjects_dir', 'subjects_dir'),
- ('subject_id', 'subject_id')]),
- (inputnode, surface_recon_wf, [
- ('t2w', 'inputnode.t2w'),
- ('flair', 'inputnode.flair'),
- ('subject_id', 'inputnode.subject_id')]),
- (fs_isrunning, surface_recon_wf, [('out', 'inputnode.subjects_dir')]),
(anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]),
(brain_extraction_wf, surface_recon_wf, [
(('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'),
@@ -622,13 +668,9 @@ def _check_img(img):
(applyrefined, buffernode, [('out_file', 't1w_brain')]),
(surface_recon_wf, buffernode, [
('outputnode.out_brainmask', 't1w_mask')]),
- (surface_recon_wf, anat_reports_wf, [
- ('outputnode.subject_id', 'inputnode.subject_id'),
- ('outputnode.subjects_dir', 'inputnode.subjects_dir')]),
(surface_recon_wf, anat_derivatives_wf, [
('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'),
- ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'),
- ]),
+ ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc')]),
(outputnode, anat_derivatives_wf, [
('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'),
('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'),
@@ -639,7 +681,6 @@ def _check_img(img):
("outputnode.anat_ribbon", "inputnode.anat_ribbon"),
]),
])
- # fmt:on
if cifti_output:
morph_grayords_wf = init_morph_grayords_wf(grayord_density=cifti_output)
@@ -730,28 +771,19 @@ def init_anat_template_wf(
name="outputnode",
)
- # 0. Denoise and reorient T1w image(s) to RAS and resample to common voxel space
+ # 0. Reorient T1w image(s) to RAS and resample to common voxel space
anat_ref_dimensions = pe.Node(TemplateDimensions(), name="anat_ref_dimensions")
- denoise = pe.MapNode(
- DenoiseImage(noise_model="Rician", num_threads=omp_nthreads),
- iterfield="input_image",
- name="denoise",
- )
anat_conform = pe.MapNode(Conform(), iterfield="in_file", name="anat_conform")
# fmt:off
workflow.connect([
(inputnode, anat_ref_dimensions, [('anat_files', 't1w_list')]),
- (anat_ref_dimensions, denoise, [('t1w_valid_list', 'input_image')]),
(anat_ref_dimensions, anat_conform, [
+ ('t1w_valid_list', 'in_file'),
('target_zooms', 'target_zooms'),
- ('target_shape', 'target_shape'),
- ]),
- (denoise, anat_conform, [('output_image', 'in_file')]),
- (anat_ref_dimensions, outputnode, [
- ('out_report', 'out_report'),
- ('t1w_valid_list', 'anat_valid_list'),
- ]),
+ ('target_shape', 'target_shape')]),
+ (anat_ref_dimensions, outputnode, [('out_report', 'out_report'),
+ ('t1w_valid_list', 'anat_valid_list')]),
])
# fmt:on
diff --git a/smriprep/workflows/base.py b/smriprep/workflows/base.py
index 6e6f921be5..d3720398a7 100644
--- a/smriprep/workflows/base.py
+++ b/smriprep/workflows/base.py
@@ -50,6 +50,7 @@ def init_smriprep_wf(
hires,
layout,
longitudinal,
+ fastsurfer,
low_mem,
omp_nthreads,
output_dir,
@@ -87,6 +88,7 @@ def init_smriprep_wf(
hires=True,
layout=BIDSLayout('.'),
longitudinal=False,
+ fastsurfer=False,
low_mem=False,
omp_nthreads=1,
output_dir='.',
@@ -117,6 +119,8 @@ def init_smriprep_wf(
longitudinal : :obj:`bool`
Treat multiple sessions as longitudinal (may increase runtime)
See sub-workflows for specific differences
+ fastsurfer : :obj:`bool`
+ Enable FastSurfer segmentation and surface reconstruction
low_mem : :obj:`bool`
Write uncompressed .nii files in some cases to reduce memory usage
omp_nthreads : :obj:`int`
@@ -162,6 +166,20 @@ def init_smriprep_wf(
if fs_subjects_dir is not None:
fsdir.inputs.subjects_dir = str(fs_subjects_dir.absolute())
+ if fastsurfer:
+ freesurfer = False
+ fastsurfdir = pe.Node(
+ BIDSFreeSurferDir(
+ derivatives=output_dir,
+ freesurfer_home=os.getenv("FREESURFER_HOME"),
+ spaces=spaces.get_fs_spaces(),
+ ),
+ name="fastsurfdir_run_%s" % run_uuid.replace("-", "_"),
+ run_without_submitting=True
+ )
+ if fs_subjects_dir is not None:
+ fastsurfdir.inputs.subjects_dir = str(fs_subjects_dir.absolute())
+
for subject_id in subject_list:
single_subject_wf = init_single_subject_wf(
debug=debug,
@@ -170,6 +188,7 @@ def init_smriprep_wf(
hires=hires,
layout=layout,
longitudinal=longitudinal,
+ fastsurfer=fastsurfer,
low_mem=low_mem,
name="single_subject_%s_wf" % subject_id,
omp_nthreads=omp_nthreads,
@@ -181,7 +200,7 @@ def init_smriprep_wf(
subject_id=subject_id,
bids_filters=bids_filters,
)
-
+
single_subject_wf.config["execution"]["crashdump_dir"] = os.path.join(
output_dir, "smriprep", "sub-" + subject_id, "log", run_uuid
)
@@ -191,6 +210,10 @@ def init_smriprep_wf(
smriprep_wf.connect(
fsdir, "subjects_dir", single_subject_wf, "inputnode.subjects_dir"
)
+ elif fastsurfer:
+ smriprep_wf.connect(
+ fastsurfdir, "subjects_dir", single_subject_wf, "inputnode.subjects_dir"
+ )
else:
smriprep_wf.add_nodes([single_subject_wf])
@@ -205,6 +228,7 @@ def init_single_subject_wf(
hires,
layout,
longitudinal,
+ fastsurfer,
low_mem,
name,
omp_nthreads,
@@ -244,6 +268,7 @@ def init_single_subject_wf(
hires=True,
layout=BIDSLayout('.'),
longitudinal=False,
+ fastsurfer=False,
low_mem=False,
name='single_subject_wf',
omp_nthreads=1,
@@ -271,6 +296,8 @@ def init_single_subject_wf(
longitudinal : :obj:`bool`
Treat multiple sessions as longitudinal (may increase runtime)
See sub-workflows for specific differences
+ fastsurfer : :obj:`bool`
+ Enable FastSurfer segmentation and surface reconstruction
low_mem : :obj:`bool`
Write uncompressed .nii files in some cases to reduce memory usage
name : :obj:`str`
@@ -351,6 +378,9 @@ def init_single_subject_wf(
Path(output_dir) / "smriprep", subject_id, std_spaces, freesurfer
)
+ if fastsurfer:
+ freesurfer = False
+
inputnode = pe.Node(
niu.IdentityInterface(fields=["subjects_dir"]), name="inputnode"
)
@@ -405,6 +435,7 @@ def init_single_subject_wf(
freesurfer=freesurfer,
hires=hires,
longitudinal=longitudinal,
+ fastsurfer=fastsurfer,
name="anat_preproc_wf",
t1w=subject_data["t1w"],
t2w=subject_data["t2w"],
diff --git a/smriprep/workflows/outputs.py b/smriprep/workflows/outputs.py
index 80d907d648..e5f4d6357b 100644
--- a/smriprep/workflows/outputs.py
+++ b/smriprep/workflows/outputs.py
@@ -31,7 +31,7 @@
BIDS_TISSUE_ORDER = ("GM", "WM", "CSF")
-def init_anat_reports_wf(*, freesurfer, output_dir, name="anat_reports_wf"):
+def init_anat_reports_wf(*, freesurfer, fastsurfer, output_dir, name="anat_reports_wf"):
"""
Set up a battery of datasinks to store reports in the right location.
@@ -39,6 +39,8 @@ def init_anat_reports_wf(*, freesurfer, output_dir, name="anat_reports_wf"):
----------
freesurfer : :obj:`bool`
FreeSurfer was enabled
+ fastsurfer : :obj:`bool`
+ FastSurfer was enabled
output_dir : :obj:`str`
Directory in which to save derivatives
name : :obj:`str`
@@ -171,7 +173,7 @@ def init_anat_reports_wf(*, freesurfer, output_dir, name="anat_reports_wf"):
])
# fmt:on
- if freesurfer:
+ if freesurfer or fastsurfer:
from ..interfaces.reports import FSSurfaceReport
recon_report = pe.Node(FSSurfaceReport(), name="recon_report")
@@ -200,6 +202,7 @@ def init_anat_derivatives_wf(
*,
bids_root,
freesurfer,
+ fastsurfer,
num_t1w,
t2w,
output_dir,
@@ -217,6 +220,8 @@ def init_anat_derivatives_wf(
Root path of BIDS dataset
freesurfer : :obj:`bool`
FreeSurfer was enabled
+ fastsurfer : :obj:`bool`
+ FastSurfer was enabled
num_t1w : :obj:`int`
Number of T1w images
output_dir : :obj:`str`
@@ -574,7 +579,7 @@ def init_anat_derivatives_wf(
)
# fmt:on
- if not freesurfer:
+ if (not freesurfer) and (not fastsurfer):
return workflow
# T2w coregistration requires FreeSurfer surfaces, so only try to save if freesurfer
diff --git a/smriprep/workflows/surfaces.py b/smriprep/workflows/surfaces.py
index 2027d3cf97..cd2dbf76f1 100644
--- a/smriprep/workflows/surfaces.py
+++ b/smriprep/workflows/surfaces.py
@@ -24,7 +24,8 @@
Surface preprocessing workflows.
**sMRIPrep** uses FreeSurfer to reconstruct surfaces from T1w/T2w
-structural images.
+structural images or FastSurfer for a machine learning-accelerated
+alternative using only T1w structural images.
"""
import typing as ty
@@ -39,6 +40,7 @@
)
from ..interfaces.freesurfer import ReconAll, MakeMidthickness
+from ..interfaces import fastsurfer as fastsurf
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from niworkflows.interfaces.freesurfer import (
@@ -48,6 +50,283 @@
PatchedRobustRegister as RobustRegister,
RefineBrainMask,
)
+
+from nipype import logging
+# import os
+
+LOGGER = logging.getLogger("nipype.workflow")
+
+
+def init_fastsurf_recon_wf(*, omp_nthreads, hires, name="fastsurf_recon_wf"):
+ r"""
+ Reconstruct anatomical surfaces using FastSurfer VINN and ``recon_surf``,
+ an alternative to FreeSurfer's ``recon-all``.
+
+ First, FastSurfer VINN creates a segmentation using the T1w structural image.
+ This is followed by FastSurfer ``recon_surf`` processing of the surface.
+
+ For example, a subject with only one session with a T1w image
+ would be processed by the following command::
+
+ $ /opt/FastSurfer/run_fastsurfer.sh \
+ --t1 /sub-/anat/sub-_T1w.nii.gz \
+ --sid sub- --sd