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: Support for multiple --output-spaces #75

Merged
merged 9 commits into from
Apr 25, 2019
10 changes: 10 additions & 0 deletions .circleci/ds054_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ smriprep/sub-100185/anat/sub-100185_desc-preproc_T1w.json
smriprep/sub-100185/anat/sub-100185_desc-preproc_T1w.nii.gz
smriprep/sub-100185/anat/sub-100185_dseg.nii.gz
smriprep/sub-100185/anat/sub-100185_from-MNI152Lin_to-T1w_mode-image_xfm.h5
smriprep/sub-100185/anat/sub-100185_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5
smriprep/sub-100185/anat/sub-100185_from-orig_to-T1w_mode-image_xfm.txt
smriprep/sub-100185/anat/sub-100185_from-T1w_to-MNI152Lin_mode-image_xfm.h5
smriprep/sub-100185/anat/sub-100185_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5
smriprep/sub-100185/anat/sub-100185_label-CSF_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_label-GM_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_label-WM_probseg.nii.gz
Expand All @@ -26,5 +28,13 @@ smriprep/sub-100185/anat/sub-100185_space-MNI152Lin_dseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152Lin_label-CSF_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152Lin_label-GM_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152Lin_label-WM_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_desc-brain_mask.json
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_desc-preproc_T1w.json
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_dseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_label-CSF_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_label-GM_probseg.nii.gz
smriprep/sub-100185/anat/sub-100185_space-MNI152NLin2009cAsym_label-WM_probseg.nii.gz
smriprep/sub-100185.html
/tmp/ds054/derivatives
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,6 @@ ENV PATH=$ANTSPATH:$PATH
# Installing SVGO
RUN npm install -g svgo

# Installing bids-validator
RUN npm install -g bids-validator@1.1.0

# Installing and setting up miniconda
RUN curl -sSLO https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh && \
bash Miniconda3-4.5.11-Linux-x86_64.sh -b -p /usr/local/miniconda && \
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
indexed_gzip>=0.8.8
nilearn!=0.5.0,!=0.5.1
git+https://github.com/poldracklab/niworkflows.git@0c09ee3d430875b86e670951575d29372812a213#egg=niworkflows
templateflow<0.2.0a0,>=0.1.3
niworkflows<0.10.0a0,>=0.9.0a0
templateflow<0.2.0a0,>=0.1.7
8 changes: 2 additions & 6 deletions smriprep/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
__email__ = 'code@oscaresteban.es'
__status__ = 'Prototype'
__url__ = 'https://github.com/poldracklab/smriprep'
__package__ = 'smriprep'
__description__ = ("sMRIPrep (Structural MRI PREprocessing) pipeline")
__longdesc__ = """\
The workflow is based on `Nipype <https://nipype.readthedocs.io>`_ and encompases a large
Expand Down Expand Up @@ -50,18 +49,16 @@
'matplotlib>=2.2.0',
'nibabel>=2.2.1',
'nipype>=1.1.6',
'niworkflows',
'niworkflows<0.10.0a0,>=0.9.0a0',
'numpy',
'packaging',
'pybids',
'pyyaml',
'templateflow<0.2.0a0,>=0.1.3',
'templateflow<0.2.0a0,>=0.1.7',
]


LINKS_REQUIRES = [
'git+https://github.com/poldracklab/niworkflows.git@'
'0c09ee3d430875b86e670951575d29372812a213#egg=niworkflows',
]

TESTS_REQUIRES = [
Expand All @@ -83,7 +80,6 @@
'duecredit': ['duecredit'],
'datalad': ['datalad'],
'resmon': ['psutil>=5.4.0'],
'sentry': ['sentry-sdk>=0.5.3'],
}
EXTRA_REQUIRES['docs'] = EXTRA_REQUIRES['doc']

Expand Down
9 changes: 3 additions & 6 deletions smriprep/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ def get_parser():
parser.add_argument('--version', action='version', version='smriprep v{}'.format(__version__))

g_bids = parser.add_argument_group('Options for filtering BIDS queries')
g_bids.add_argument('--skip-bids-validation', '--skip_bids_validation', action='store_true',
default=False,
help='assume the input dataset is BIDS compliant and skip the validation')
g_bids.add_argument('--participant-label', '--participant_label', action='store', nargs='+',
help='a space delimited list of participant identifiers or a single '
'identifier (the sub- prefix can be removed)')
Expand Down Expand Up @@ -261,13 +258,13 @@ def _warn_redirect(message, category, filename, lineno, file=None, line=None):
str(Path(output_dir) / 'smriprep' / 'desc-aparcaseg_dseg.tsv'))
logger.log(25, 'sMRIPrep finished without errors')
finally:
from pkg_resources import resource_filename as pkgrf
from niworkflows.viz.reports import generate_reports

from ..utils.bids import write_derivative_description

logger.log(25, 'Writing reports for participants: %s', ', '.join(subject_list))
# Generate reports phase
errno += generate_reports(subject_list, output_dir, work_dir, run_uuid,
config=pkgrf('smriprep', 'data/reports/config.json'))
packagename='smriprep')
write_derivative_description(bids_dir, str(Path(output_dir) / 'smriprep'))
sys.exit(int(errno > 0))

Expand Down
14 changes: 8 additions & 6 deletions smriprep/workflows/anatomical.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,17 @@ def init_anat_preproc_wf(
('probability_maps', 't1_tpms')]),
])

seg_rpt = pe.Node(ROIsPlot(colors=['magenta', 'b'], levels=[1.5, 2.5]),
name='seg_rpt')

vol_spaces = [k for k in output_spaces.keys()
if not k.startswith('fs')]
# 6. Spatial normalization
template = list(output_spaces.keys())[0]
anat_norm_wf = init_anat_norm_wf(
debug=debug,
omp_nthreads=omp_nthreads,
reportlets_dir=reportlets_dir,
template=template)
template_list=vol_spaces)
workflow.connect([
(inputnode, anat_norm_wf, [
(('t1w', fix_multi_T1w_source_name), 'inputnode.orig_t1w'),
Expand All @@ -304,9 +308,6 @@ def init_anat_preproc_wf(
('outputnode.tpl_seg', 'tpl_seg'),
('outputnode.tpl_tpms', 'tpl_tpms')]),
])

seg_rpt = pe.Node(ROIsPlot(colors=['magenta', 'b'], levels=[1.5, 2.5]),
name='seg_rpt')
anat_reports_wf = init_anat_reports_wf(
reportlets_dir=reportlets_dir, freesurfer=freesurfer)
workflow.connect([
Expand All @@ -330,13 +331,14 @@ def init_anat_preproc_wf(
anat_derivatives_wf = init_anat_derivatives_wf(
bids_root=bids_root,
freesurfer=freesurfer,
template=template,
output_dir=output_dir,
)

workflow.connect([
(anat_template_wf, anat_derivatives_wf, [
('outputnode.t1w_valid_list', 'inputnode.source_files')]),
(anat_norm_wf, anat_derivatives_wf, [
('outputnode.template', 'inputnode.template')]),
(outputnode, anat_derivatives_wf, [
('warped', 'inputnode.t1_2_tpl'),
('forward_transform', 'inputnode.t1_2_tpl_forward_transform'),
Expand Down
4 changes: 2 additions & 2 deletions smriprep/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,12 @@ def init_single_subject_wf(

ds_report_summary = pe.Node(
DerivativesDataSink(base_directory=reportlets_dir,
suffix='summary'),
desc='summary', keep_dtype=True),
name='ds_report_summary', run_without_submitting=True)

ds_report_about = pe.Node(
DerivativesDataSink(base_directory=reportlets_dir,
suffix='about'),
desc='about', keep_dtype=True),
name='ds_report_about', run_without_submitting=True)

# Preprocessing of T1w (includes registration to MNI)
Expand Down
90 changes: 55 additions & 35 deletions smriprep/workflows/norm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
.. autofunction:: init_anat_norm_wf

"""
from collections import defaultdict
from nipype.pipeline import engine as pe
from nipype.interfaces import utility as niu

from nipype.interfaces.ants.base import Info as ANTsInfo

from templateflow.api import get as get_template, get_metadata, templates
from templateflow.api import get_metadata, templates
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from niworkflows.interfaces.ants import ImageMath
from niworkflows.interfaces.registration import RobustMNINormalizationRPT
Expand All @@ -24,7 +25,7 @@ def init_anat_norm_wf(
debug,
omp_nthreads,
reportlets_dir,
template,
template_list,
):
"""
An individual spatial normalization workflow using ``antsRegistration``.
Expand All @@ -38,7 +39,7 @@ def init_anat_norm_wf(
debug=False,
omp_nthreads=1,
reportlets_dir='.',
template='MNI152NLin2009cAsym',
template_list=['MNI152NLin2009cAsym', 'MNI152NLin6Asym'],
)

**Parameters**
Expand All @@ -50,8 +51,8 @@ def init_anat_norm_wf(
Maximum number of threads an individual process may use.
reportlets_dir : str
Directory in which to save reportlets.
template : str
TemplateFlow identifier (e.g. ``MNI152NLin6Asym``) that specifies the target
template_list : list of str
List of TemplateFlow identifiers (e.g. ``MNI152NLin6Asym``) that specifies the target
template for spatial normalization. In the future, this parameter should accept
also paths to custom/private templates with TemplateFlow's organization.

Expand Down Expand Up @@ -90,42 +91,57 @@ def init_anat_norm_wf(
workflow.

"""
templateflow = template in templates()

if not templateflow:
templateflow = templates()
if any(template not in templateflow for template in template_list):
raise NotImplementedError(
'This is embarrassing - custom templates are not (yet) supported.'
'Please make sure none of the options already available via TemplateFlow '
'fit your needs.')

template_meta = get_metadata(template)
template_refs = ['@%s' % template.lower()]
workflow = Workflow('anat_norm_wf')

if template_meta.get('RRID', None):
template_refs += ['RRID:%s' % template_meta['RRID']]

workflow = Workflow('anat_norm_wf_%s' % template)
workflow.__desc__ = """\
Spatial normalization to the *{template_name}* [{template_refs};
TemplateFlow ID: {template}] was performed through nonlinear
registration with `antsRegistration` (ANTs {ants_ver}), using
brain-extracted versions of both T1w reference and the T1w template.
Volume-based spatial normalization to {targets} ({targets_id}) was performed through
nonlinear registration with `antsRegistration` (ANTs {ants_ver}),
using brain-extracted versions of both T1w reference and the T1w template.
The following template{tpls} selected for spatial normalization:
""".format(
ants_ver=ANTsInfo.version() or '(version unknown)',
template=template,
template_name=template_meta['Name'],
template_refs=', '.join(template_refs),
targets='%s standard space%s' % (defaultdict(
'several'.format, {1: 'one', 2: 'two', 3: 'three', 4: 'four'})[len(template_list)],
's' * (len(template_list) != 1)),
targets_id=', '.join(template_list),
tpls=(' was', 's were')[len(template_list) != 1]
)

# Append template citations to description
for template in template_list:
template_meta = get_metadata(template)
template_refs = ['@%s' % template.lower()]

if template_meta.get('RRID', None):
template_refs += ['RRID:%s' % template_meta['RRID']]

workflow.__desc__ += """\
*{template_name}* [{template_refs}; TemplateFlow ID: {template}]""".format(
template=template,
template_name=template_meta['Name'],
template_refs=', '.join(template_refs))
workflow.__desc__ += (', ', '.')[template == template_list[-1]]

inputnode = pe.Node(niu.IdentityInterface(fields=[
'moving_image', 'moving_mask', 'moving_segmentation', 'moving_tpms',
'lesion_mask', 'orig_t1w']),
'lesion_mask', 'orig_t1w', 'template']),
name='inputnode')
inputnode.iterables = [('template', template_list)]
outputnode = pe.Node(niu.IdentityInterface(
fields=['warped', 'forward_transform', 'reverse_transform',
'tpl_mask', 'tpl_seg', 'tpl_tpms', 'template']),
name='outputnode')
outputnode.inputs.template = template

fixed_tpl = pe.Node(niu.Function(function=_templateflow_ds),
name='fixed_tpl', run_without_submitting=True)

# With the improvements from poldracklab/niworkflows#342 this truncation is now necessary
trunc_mov = pe.Node(ImageMath(operation='TruncateImageIntensity', op2='0.01 0.999 256'),
Expand All @@ -147,21 +163,17 @@ def init_anat_norm_wf(
interpolation='BSpline'),
iterfield=['input_image'], name='tpl_tpms')

# TODO isolate the spatial normalization workflow #############
ref_img = str(get_template(template, resolution=1, desc=None, suffix='T1w',
extensions=['.nii', '.nii.gz']))

registration.inputs.template = template
tpl_mask.inputs.reference_image = ref_img
tpl_seg.inputs.reference_image = ref_img
tpl_tpms.inputs.reference_image = ref_img

workflow.connect([
(inputnode, fixed_tpl, [('template', 'template')]),
(inputnode, registration, [('template', 'template')]),
(inputnode, trunc_mov, [('moving_image', 'op1')]),
(inputnode, registration, [
('moving_mask', 'moving_mask'),
('lesion_mask', 'lesion_mask')]),
(inputnode, tpl_mask, [('moving_mask', 'input_image')]),
(fixed_tpl, tpl_mask, [('out', 'reference_image')]),
(fixed_tpl, tpl_seg, [('out', 'reference_image')]),
(fixed_tpl, tpl_tpms, [('out', 'reference_image')]),
(trunc_mov, registration, [
('output_image', 'moving_image')]),
(registration, tpl_mask, [('composite_transform', 'transforms')]),
Expand All @@ -176,17 +188,25 @@ def init_anat_norm_wf(
(tpl_mask, outputnode, [('output_image', 'tpl_mask')]),
(tpl_seg, outputnode, [('output_image', 'tpl_seg')]),
(tpl_tpms, outputnode, [('output_image', 'tpl_tpms')]),
(inputnode, outputnode, [('template', 'template')]),
])

# Store report
ds_t1_2_tpl_report = pe.Node(
DerivativesDataSink(base_directory=reportlets_dir, space=template,
suffix='t1w'),
DerivativesDataSink(base_directory=reportlets_dir, keep_dtype=True),
name='ds_t1_2_tpl_report', run_without_submitting=True)

workflow.connect([
(inputnode, ds_t1_2_tpl_report, [('orig_t1w', 'source_file')]),
(inputnode, ds_t1_2_tpl_report, [
('template', 'space'),
('orig_t1w', 'source_file')]),
(registration, ds_t1_2_tpl_report, [('out_report', 'in_file')]),
])

return workflow


def _templateflow_ds(template, resolution=1, desc=None, suffix='T1w',
extensions=('.nii', '.nii.gz')):
from templateflow.api import get as get_template
return str(get_template(template, resolution=resolution, desc=desc,
suffix=suffix, extensions=extensions))
Loading