Skip to content

Commit

Permalink
Merge pull request #393 from bpinsard/enh/fs_long
Browse files Browse the repository at this point in the history
Add --fs-no-resume option to reuse existing freesurfer outputs without resuming (eg. longitudinal pipeline base)
  • Loading branch information
effigies authored Mar 22, 2024
2 parents c72116a + ba4611b commit e682dc7
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 33 deletions.
18 changes: 18 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,24 @@ jobs:
- store_artifacts:
path: /tmp/ds005/derivatives
destination: fasttrack
- run:
name: Check fs-no-resume using existing freesufer output
no_output_timeout: 5m
command: |
bash /tmp/src/smriprep/.circleci/ds005_run.sh --fs-no-resume
- run:
name: Clean working directory
when: on_fail
command: |
rm -rf /tmp/ds005/work/smriprep_wf/fsdir_run_*/
find /tmp/ds005/work \( -name "*.nii.gz" -or -name "*.nii" -or -name "*.gii" -or -name "*.h5" \) \
-exec sh -c 'rm -f {}; touch {}' \;
- store_artifacts:
path: /tmp/ds005/work
destination: fs_no_resume
- store_artifacts:
path: /tmp/ds005/derivatives
destination: fs_no_resume
ds054:
<<: *machine_defaults
environment:
Expand Down
8 changes: 8 additions & 0 deletions smriprep/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ def get_parser():
help='Path to existing FreeSurfer subjects directory to reuse. '
'(default: OUTPUT_DIR/freesurfer)',
)
g_fs.add_argument(
'--fs-no-resume',
action='store_true',
dest='fs_no_resume',
help='EXPERT: Import pre-computed FreeSurfer reconstruction without resuming. '
'The user is responsible for ensuring that all necessary files are present.',
)
g_fs.add_argument(
'--cifti-output',
nargs='?',
Expand Down Expand Up @@ -616,6 +623,7 @@ def build_workflow(opts, retval):
freesurfer=opts.run_reconall,
fs_subjects_dir=opts.fs_subjects_dir,
hires=opts.hires,
fs_no_resume=opts.fs_no_resume,
layout=layout,
longitudinal=opts.longitudinal,
low_mem=opts.low_mem,
Expand Down
8 changes: 8 additions & 0 deletions smriprep/workflows/anatomical.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def init_anat_preproc_wf(
cifti_output: ty.Literal['91k', '170k', False] = False,
name: str = 'anat_preproc_wf',
skull_strip_fixed_seed: bool = False,
fs_no_resume: bool = False,
):
"""
Stage the anatomical preprocessing steps of *sMRIPrep*.
Expand Down Expand Up @@ -184,6 +185,10 @@ def init_anat_preproc_wf(
Do not use a random seed for skull-stripping - will ensure
run-to-run replicability when used with --omp-nthreads 1
(default: ``False``).
fs_no_resume : bool
EXPERT: Import pre-computed FreeSurfer reconstruction without resuming.
The user is responsible for ensuring that all necessary files are present.
(default: ``False``).
Inputs
------
Expand Down Expand Up @@ -276,6 +281,7 @@ def init_anat_preproc_wf(
sloppy=sloppy,
omp_nthreads=omp_nthreads,
skull_strip_fixed_seed=skull_strip_fixed_seed,
fs_no_resume=fs_no_resume,
)
template_iterator_wf = init_template_iterator_wf(spaces=spaces, sloppy=sloppy)
ds_std_volumes_wf = init_ds_anat_volumes_wf(
Expand Down Expand Up @@ -459,6 +465,7 @@ def init_anat_fit_wf(
sloppy: bool = False,
name='anat_fit_wf',
skull_strip_fixed_seed: bool = False,
fs_no_resume: bool = False,
):
"""
Stage the anatomical preprocessing steps of *sMRIPrep*.
Expand Down Expand Up @@ -1042,6 +1049,7 @@ def init_anat_fit_wf(
name='surface_recon_wf',
omp_nthreads=omp_nthreads,
hires=hires,
fs_no_resume=fs_no_resume,
precomputed=precomputed,
)
if t2w or flair:
Expand Down
9 changes: 9 additions & 0 deletions smriprep/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def init_smriprep_wf(
freesurfer,
fs_subjects_dir,
hires,
fs_no_resume,
layout,
longitudinal,
low_mem,
Expand Down Expand Up @@ -89,6 +90,7 @@ def init_smriprep_wf(
freesurfer=True,
fs_subjects_dir=None,
hires=True,
fs_no_resume=False,
layout=BIDSLayout('.'),
longitudinal=False,
low_mem=False,
Expand Down Expand Up @@ -179,6 +181,7 @@ def init_smriprep_wf(
freesurfer=freesurfer,
derivatives=derivatives,
hires=hires,
fs_no_resume=fs_no_resume,
layout=layout,
longitudinal=longitudinal,
low_mem=low_mem,
Expand Down Expand Up @@ -215,6 +218,7 @@ def init_single_subject_wf(
derivatives,
freesurfer,
hires,
fs_no_resume,
layout,
longitudinal,
low_mem,
Expand Down Expand Up @@ -259,6 +263,7 @@ def init_single_subject_wf(
freesurfer=True,
derivatives=[],
hires=True,
fs_no_resume=False,
layout=BIDSLayout('.'),
longitudinal=False,
low_mem=False,
Expand Down Expand Up @@ -287,6 +292,9 @@ def init_single_subject_wf(
Enable FreeSurfer surface reconstruction (may increase runtime)
hires : :obj:`bool`
Enable sub-millimeter preprocessing in FreeSurfer
fs_no_resume : bool
Adjust pipeline to reuse base template
of an existing longitudinal freesurfer output
layout : BIDSLayout object
BIDS dataset layout
longitudinal : :obj:`bool`
Expand Down Expand Up @@ -419,6 +427,7 @@ def init_single_subject_wf(
precomputed=deriv_cache,
freesurfer=freesurfer,
hires=hires,
fs_no_resume=fs_no_resume,
longitudinal=longitudinal,
msm_sulc=msm_sulc,
name='anat_preproc_wf',
Expand Down
97 changes: 64 additions & 33 deletions smriprep/workflows/surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def init_surface_recon_wf(
*,
omp_nthreads: int,
hires: bool,
fs_no_resume: bool,
precomputed: dict,
name='surface_recon_wf',
):
Expand Down Expand Up @@ -130,14 +131,21 @@ def init_surface_recon_wf(
:simple_form: yes
from smriprep.workflows.surfaces import init_surface_recon_wf
wf = init_surface_recon_wf(omp_nthreads=1, hires=True, precomputed={})
wf = init_surface_recon_wf(
omp_nthreads=1,
hires=True,
fs_no_resume=False,
precomputed={})
Parameters
----------
omp_nthreads : int
Maximum number of threads an individual process may use
hires : bool
Enable sub-millimeter preprocessing in FreeSurfer
fs_no_resume : bool
use precomputed freesurfer without attempting to resume
(eg. for longitudinal base or fastsurfer)
Inputs
------
Expand Down Expand Up @@ -239,39 +247,62 @@ def init_surface_recon_wf(
name='sync',
)

if not fs_no_resume:
workflow.connect([
# Configuration
(inputnode, recon_config, [('t1w', 't1w_list'),
('t2w', 't2w_list'),
('flair', 'flair_list')]),
# Passing subjects_dir / subject_id enforces serial order
(inputnode, autorecon1, [('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id')]),
(autorecon1, skull_strip_extern, [('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id')]),
(skull_strip_extern, autorecon_resume_wf, [('subjects_dir', 'inputnode.subjects_dir'),
('subject_id', 'inputnode.subject_id')]),
# Reconstruction phases
(inputnode, autorecon1, [('t1w', 'T1_files')]),
(inputnode, fov_check, [('t1w', 'in_files')]),
(fov_check, autorecon1, [('out', 'flags')]),
(recon_config, autorecon1, [('t2w', 'T2_file'),
('flair', 'FLAIR_file'),
('hires', 'hires'),
# First run only (recon-all saves expert options)
('mris_inflate', 'mris_inflate')]),
(inputnode, skull_strip_extern, [('skullstripped_t1', 'in_brain')]),
(recon_config, autorecon_resume_wf, [('use_t2w', 'inputnode.use_T2'),
('use_flair', 'inputnode.use_FLAIR')]),
# Generate mid-thickness surfaces
(autorecon_resume_wf, get_surfaces, [
('outputnode.subjects_dir', 'subjects_dir'),
('outputnode.subject_id', 'subject_id'),
]),
(autorecon_resume_wf, save_midthickness, [
('outputnode.subjects_dir', 'base_directory'),
('outputnode.subject_id', 'container'),
]),
]) # fmt:skip
else:
# Pretend to be the autorecon1 node so fsnative2t1w_xfm gets run ASAP
fs_base_inputs = autorecon1 = pe.Node(nio.FreeSurferSource(), name='fs_base_inputs')

workflow.connect([
(inputnode, fs_base_inputs, [
('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id'),
]),
# Generate mid-thickness surfaces
(inputnode, get_surfaces, [
('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id'),
]),
(inputnode, save_midthickness, [
('subjects_dir', 'base_directory'),
('subject_id', 'container'),
]),
]) # fmt:skip

workflow.connect([
# Configuration
(inputnode, recon_config, [('t1w', 't1w_list'),
('t2w', 't2w_list'),
('flair', 'flair_list')]),
# Passing subjects_dir / subject_id enforces serial order
(inputnode, autorecon1, [('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id')]),
(autorecon1, skull_strip_extern, [('subjects_dir', 'subjects_dir'),
('subject_id', 'subject_id')]),
(skull_strip_extern, autorecon_resume_wf, [('subjects_dir', 'inputnode.subjects_dir'),
('subject_id', 'inputnode.subject_id')]),
# Reconstruction phases
(inputnode, autorecon1, [('t1w', 'T1_files')]),
(inputnode, fov_check, [('t1w', 'in_files')]),
(fov_check, autorecon1, [('out', 'flags')]),
(recon_config, autorecon1, [('t2w', 'T2_file'),
('flair', 'FLAIR_file'),
('hires', 'hires'),
# First run only (recon-all saves expert options)
('mris_inflate', 'mris_inflate')]),
(inputnode, skull_strip_extern, [('skullstripped_t1', 'in_brain')]),
(recon_config, autorecon_resume_wf, [('use_t2w', 'inputnode.use_T2'),
('use_flair', 'inputnode.use_FLAIR')]),
# Generate mid-thickness surfaces
(autorecon_resume_wf, get_surfaces, [
('outputnode.subjects_dir', 'subjects_dir'),
('outputnode.subject_id', 'subject_id'),
]),
(autorecon_resume_wf, save_midthickness, [
('outputnode.subjects_dir', 'base_directory'),
('outputnode.subject_id', 'container'),
]),
(get_surfaces, midthickness, [
('white', 'in_file'),
('graymid', 'graymid'),
Expand Down

0 comments on commit e682dc7

Please sign in to comment.