From f63af68990eca4e60f025e4de0465065999eeffe Mon Sep 17 00:00:00 2001 From: oesteban Date: Wed, 27 May 2020 09:41:15 -0700 Subject: [PATCH 1/3] FIX: Generate proper LTA transform prior BOLD sampling on surfaces This is a temporary patch before we go all the way in with NiTransforms in the sampling of BOLD on surfaces. The anatomical _fast-track_ required to expose the fsnative-to-T1w transform in the derivatives folder (which we were already doing in ITK format). When fMRIPrep ran without the fast-track, then the LTA transform would be directly passed in without conversions. The fast-track PR forced the implementation to use the ITK version. This, in conjunction with the little trick to stick the BOLD shape and zooms into the LTA (i.e., using ``lta_concatenate`` with an identity transform with those features, shape and zooms, as moving) resulted in an overly complex workflow that I partially implemented with NiTransforms. This PR gets rid of the concatenation with identity trick, using NiTransforms to generate a transform equivalent to the concatenated LTA we used to generate before the fast-track was introduced. Resolves: #2145 Assign: @mgxd Milestone: 20.1.0 Related: #2118, #2041, #2121. --- fmriprep/workflows/bold/base.py | 1 - fmriprep/workflows/bold/resampling.py | 44 +++++++++++++++------------ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index 56ee533c7..976bcfd70 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -736,7 +736,6 @@ def init_func_preproc_wf(bold_file): name='bold_surf_wf') workflow.connect([ (inputnode, bold_surf_wf, [ - ('t1w_preproc', 'inputnode.t1w_preproc'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm')]), diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index caf1b2ad4..fe0ece41b 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -69,13 +69,8 @@ def init_bold_surf_wf( BOLD series, resampled to FreeSurfer surfaces """ + from nipype.interfaces.io import FreeSurferSource from niworkflows.engine.workflows import LiterateWorkflow as Workflow - from niworkflows.interfaces.nitransforms import ConcatenateXFMs - - # See https://github.com/poldracklab/fmriprep/issues/768 - from niworkflows.interfaces.freesurfer import ( - PatchedLTAConvert as LTAConvert - ) from niworkflows.interfaces.surf import GiftiSetAnatomicalStructure workflow = Workflow(name=name) @@ -86,12 +81,16 @@ def init_bold_surf_wf( """.format(out_spaces=', '.join(['*%s*' % s for s in surface_spaces])) inputnode = pe.Node( - niu.IdentityInterface(fields=['source_file', 't1w_preproc', 'subject_id', 'subjects_dir', + niu.IdentityInterface(fields=['source_file', 'subject_id', 'subjects_dir', 't1w2fsnative_xfm']), name='inputnode') itersource = pe.Node(niu.IdentityInterface(fields=['target']), name='itersource') itersource.iterables = [('target', surface_spaces)] + get_fsnative = pe.Node(FreeSurferSource(), name='get_fsnative', + run_without_submitting=True) + tonii = pe.Node(fs.MRIConvert(out_type='niigz'), name='tonii') + def select_target(subject_id, space): """Get the target subject ID, given a source subject ID and a target space.""" return subject_id if space == 'fsnative' else space @@ -103,11 +102,8 @@ def select_target(subject_id, space): rename_src = pe.Node(niu.Rename(format_string='%(subject)s', keep_ext=True), name='rename_src', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) - resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile', out_lta=True), - name='resampling_xfm') - merge_xfm = pe.Node(niu.Merge(2), name="merge_xfm", run_without_submitting=True) - concat_xfm = pe.Node(ConcatenateXFMs(out_fmt="fs"), name="concat_xfm") - + itk2lta = pe.Node(niu.Function(function=_itk2lta), name="itk2lta", + run_without_submitting=True) sampler = pe.MapNode( fs.SampleToSurface( cortex_mask=True, @@ -127,20 +123,19 @@ def select_target(subject_id, space): joinsource='itersource', name='outputnode') workflow.connect([ + (inputnode, get_fsnative, [('subject_id', 'subject_id'), + ('subjects_dir', 'subjects_dir')]), (inputnode, targets, [('subject_id', 'subject_id')]), (inputnode, rename_src, [('source_file', 'in_file')]), - (inputnode, resampling_xfm, [('source_file', 'source_file'), - ('t1w_preproc', 'target_file')]), - (inputnode, concat_xfm, [('source_file', 'moving'), - ('t1w_preproc', 'reference')]), - (inputnode, merge_xfm, [('t1w2fsnative_xfm', 'in2')]), + (inputnode, itk2lta, [('source_file', 'src_file'), + ('t1w2fsnative_xfm', 'in_file')]), + (get_fsnative, tonii, [('T1', 'in_file')]), + (tonii, itk2lta, [('out_file', 'dst_file')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (itersource, targets, [('target', 'space')]), (itersource, rename_src, [('target', 'subject')]), - (resampling_xfm, merge_xfm, [('out_lta', 'in1')]), - (merge_xfm, concat_xfm, [('out', 'in_xfms')]), - (concat_xfm, sampler, [('out_xfm', 'reg_file')]), + (itk2lta, sampler, [('out', 'reg_file')]), (targets, sampler, [('out', 'target_subject')]), (rename_src, sampler, [('out_file', 'source_file')]), (update_metadata, outputnode, [('out_file', 'surfaces')]), @@ -774,3 +769,12 @@ def _is_native(in_value): in_value.get('resolution') == 'native' or in_value.get('res') == 'native' ) + + +def _itk2lta(in_file, src_file, dst_file): + import nitransforms as nt + from pathlib import Path + out_file = Path("out.lta").absolute() + nt.linear.load(in_file, fmt="itk", reference=src_file).to_filename( + out_file, moving=dst_file, fmt="fs") + return str(out_file) From 1d417a93fee4b26163af74fcd66eaa3de3dc0c09 Mon Sep 17 00:00:00 2001 From: oesteban Date: Wed, 27 May 2020 10:40:32 -0700 Subject: [PATCH 2/3] fix: account for LTAs coming in, if the fast-track is not used --- fmriprep/workflows/bold/resampling.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index fe0ece41b..95d36bd18 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -775,6 +775,9 @@ def _itk2lta(in_file, src_file, dst_file): import nitransforms as nt from pathlib import Path out_file = Path("out.lta").absolute() - nt.linear.load(in_file, fmt="itk", reference=src_file).to_filename( - out_file, moving=dst_file, fmt="fs") + nt.linear.load( + in_file, + fmt="fs" if in_file.endswith(".lta") else "itk", + reference=src_file).to_filename( + out_file, moving=dst_file, fmt="fs") return str(out_file) From 108ddf44a30d38592adfb29fa1386f5639e70e12 Mon Sep 17 00:00:00 2001 From: oesteban Date: Wed, 27 May 2020 11:03:11 -0700 Subject: [PATCH 3/3] enh: drop unnecessary conversion to nifti --- fmriprep/workflows/bold/resampling.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fmriprep/workflows/bold/resampling.py b/fmriprep/workflows/bold/resampling.py index 95d36bd18..fb735d2b3 100644 --- a/fmriprep/workflows/bold/resampling.py +++ b/fmriprep/workflows/bold/resampling.py @@ -89,7 +89,6 @@ def init_bold_surf_wf( get_fsnative = pe.Node(FreeSurferSource(), name='get_fsnative', run_without_submitting=True) - tonii = pe.Node(fs.MRIConvert(out_type='niigz'), name='tonii') def select_target(subject_id, space): """Get the target subject ID, given a source subject ID and a target space.""" @@ -129,8 +128,7 @@ def select_target(subject_id, space): (inputnode, rename_src, [('source_file', 'in_file')]), (inputnode, itk2lta, [('source_file', 'src_file'), ('t1w2fsnative_xfm', 'in_file')]), - (get_fsnative, tonii, [('T1', 'in_file')]), - (tonii, itk2lta, [('out_file', 'dst_file')]), + (get_fsnative, itk2lta, [('T1', 'dst_file')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (itersource, targets, [('target', 'space')]),