@@ -128,6 +128,8 @@ def init_bold_reg_wf(
128128 Affine transform from T1 space to BOLD space (ITK format)
129129 fallback
130130 Boolean indicating whether BBR was rejected (mri_coreg registration returned)
131+ metadata
132+ Output metadata from the registration workflow
131133
132134 See Also
133135 --------
@@ -154,7 +156,7 @@ def init_bold_reg_wf(
154156 )
155157
156158 outputnode = pe .Node (
157- niu .IdentityInterface (fields = ['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' ]),
159+ niu .IdentityInterface (fields = ['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' , 'metadata' ]),
158160 name = 'outputnode' ,
159161 )
160162
@@ -188,6 +190,7 @@ def init_bold_reg_wf(
188190 ('outputnode.itk_bold_to_t1' , 'itk_bold_to_t1' ),
189191 ('outputnode.itk_t1_to_bold' , 'itk_t1_to_bold' ),
190192 ('outputnode.fallback' , 'fallback' ),
193+ ('outputnode.metadata' , 'metadata' ),
191194 ]),
192195 ]) # fmt:skip
193196
@@ -268,11 +271,14 @@ def init_bbreg_wf(
268271 Affine transform from T1 space to BOLD space (ITK format)
269272 fallback
270273 Boolean indicating whether BBR was rejected (mri_coreg registration returned)
274+ metadata
275+ Output metadata from the registration workflow
271276
272277 """
273278 from nipype .interfaces .freesurfer import BBRegister
274279 from niworkflows .engine .workflows import LiterateWorkflow as Workflow
275280 from niworkflows .interfaces .nitransforms import ConcatenateXFMs
281+ from niworkflows .interfaces .utility import DictMerge
276282
277283 from fmriprep .interfaces .patches import FreeSurferSource , MRICoreg
278284
@@ -309,7 +315,7 @@ def init_bbreg_wf(
309315 name = 'inputnode' ,
310316 )
311317 outputnode = pe .Node (
312- niu .IdentityInterface (['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' ]),
318+ niu .IdentityInterface (['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' , 'metadata' ]),
313319 name = 'outputnode' ,
314320 )
315321
@@ -341,6 +347,8 @@ def init_bbreg_wf(
341347 dof = bold2anat_dof ,
342348 contrast_type = 't2' ,
343349 out_lta_file = True ,
350+ # Bug in nipype prevents using init_cost_file=True
351+ init_cost_file = 'bbregister.initcost' ,
344352 ),
345353 name = 'bbregister' ,
346354 mem_gb = 12 ,
@@ -357,6 +365,12 @@ def init_bbreg_wf(
357365 merge_ltas = pe .Node (niu .Merge (2 ), name = 'merge_ltas' , run_without_submitting = True )
358366 concat_xfm = pe .Node (ConcatenateXFMs (inverse = True ), name = 'concat_xfm' )
359367
368+ # Set up GeneratedBy metadata and add a merge node for cost, if available
369+ gen_by = pe .Node (niu .Merge (2 ), run_without_submitting = True , name = 'gen_by' )
370+ select_gen = pe .Node (niu .Select (index = 0 ), run_without_submitting = True , name = 'select_gen' )
371+ metadata = pe .Node (niu .Merge (2 ), run_without_submitting = True , name = 'metadata' )
372+ merge_meta = pe .Node (DictMerge (), run_without_submitting = True , name = 'merge_meta' )
373+
360374 workflow .connect ([
361375 (inputnode , merge_ltas , [('fsnative2t1w_xfm' , 'in2' )]),
362376 # Wire up the co-registration alternatives
@@ -365,10 +379,20 @@ def init_bbreg_wf(
365379 (merge_ltas , concat_xfm , [('out' , 'in_xfms' )]),
366380 (concat_xfm , outputnode , [('out_xfm' , 'itk_bold_to_t1' )]),
367381 (concat_xfm , outputnode , [('out_inv' , 'itk_t1_to_bold' )]),
382+ # Wire up the metadata alternatives
383+ (gen_by , select_gen , [('out' , 'inlist' )]),
384+ (select_gen , metadata , [('out' , 'in1' )]),
385+ (metadata , merge_meta , [('out' , 'in_dicts' )]),
386+ (merge_meta , outputnode , [('out_dict' , 'metadata' )]),
368387 ]) # fmt:skip
369388
370389 # Do not initialize with header, use mri_coreg
371390 if bold2anat_init != 'header' :
391+ gen_by .inputs .in2 = {
392+ 'GeneratedBy' : [
393+ {'Name' : 'mri_coreg' , 'Version' : mri_coreg .interface .version or '<unknown>' }
394+ ]
395+ }
372396 workflow .connect ([
373397 (inputnode , mri_coreg , [('subjects_dir' , 'subjects_dir' ),
374398 ('subject_id' , 'subject_id' ),
@@ -400,6 +424,26 @@ def init_bbreg_wf(
400424 (bbregister , transforms , [('out_lta_file' , 'in1' )]),
401425 ]) # fmt:skip
402426
427+ gen_by .inputs .in1 = {
428+ 'GeneratedBy' : [
429+ {'Name' : 'bbregister' , 'Version' : bbregister .interface .version or '<unknown>' }
430+ ]
431+ }
432+
433+ costs = pe .Node (niu .Merge (2 ), run_without_submitting = True , name = 'costs' )
434+ select_cost = pe .Node (niu .Select (index = 0 ), run_without_submitting = True , name = 'select_cost' )
435+ read_cost = pe .Node (niu .Function (function = _read_cost ), name = 'read_cost' )
436+
437+ workflow .connect ([
438+ (bbregister , costs , [
439+ ('min_cost_file' , 'in1' ),
440+ ('init_cost_file' , 'in2' ),
441+ ]),
442+ (costs , select_cost , [('out' , 'inlist' )]),
443+ (select_cost , read_cost , [('out' , 'cost_file' )]),
444+ (read_cost , metadata , [('out' , 'in2' )]),
445+ ]) # fmt:skip
446+
403447 # Short-circuit workflow building, use boundary-based registration
404448 if use_bbr is True :
405449 outputnode .inputs .fallback = False
@@ -413,6 +457,8 @@ def init_bbreg_wf(
413457 (transforms , compare_transforms , [('out' , 'lta_list' )]),
414458 (compare_transforms , outputnode , [('out' , 'fallback' )]),
415459 (compare_transforms , select_transform , [('out' , 'index' )]),
460+ (compare_transforms , select_gen , [('out' , 'index' )]),
461+ (compare_transforms , select_cost , [('out' , 'index' )]),
416462 ]) # fmt:skip
417463
418464 return workflow
@@ -493,6 +539,8 @@ def init_fsl_bbr_wf(
493539 Affine transform from T1 space to BOLD space (ITK format)
494540 fallback
495541 Boolean indicating whether BBR was rejected (rigid FLIRT registration returned)
542+ metadata
543+ Output metadata from the registration workflow
496544
497545 """
498546 from nipype .interfaces .freesurfer import MRICoreg
@@ -532,7 +580,7 @@ def init_fsl_bbr_wf(
532580 name = 'inputnode' ,
533581 )
534582 outputnode = pe .Node (
535- niu .IdentityInterface (['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' ]),
583+ niu .IdentityInterface (['itk_bold_to_t1' , 'itk_t1_to_bold' , 'fallback' , 'metadata' ]),
536584 name = 'outputnode' ,
537585 )
538586
@@ -549,6 +597,9 @@ def init_fsl_bbr_wf(
549597 'T2w intermediate for FSL is not implemented, registering with T1w instead.'
550598 )
551599
600+ metadata = pe .Node (niu .Merge (2 ), run_without_submitting = True , name = 'metadata' )
601+ select_meta = pe .Node (niu .Select (index = 0 ), run_without_submitting = True , name = 'select_meta' )
602+
552603 # Mask T1w_preproc with T1w_mask to make T1w_brain
553604 mask_t1w_brain = pe .Node (ApplyMask (), name = 'mask_t1w_brain' )
554605
@@ -565,6 +616,12 @@ def init_fsl_bbr_wf(
565616 mem_gb = DEFAULT_MEMORY_MIN_GB ,
566617 )
567618
619+ metadata .inputs .in2 = {
620+ 'GeneratedBy' : [
621+ {'Name' : 'mri_coreg' , 'Version' : mri_coreg .interface .version or '<unknown>' }
622+ ]
623+ }
624+
568625 workflow .connect ([
569626 (inputnode , mask_t1w_brain , [
570627 ('t1w_preproc' , 'in_file' ),
@@ -578,6 +635,9 @@ def init_fsl_bbr_wf(
578635 ('out_xfm' , 'itk_bold_to_t1' ),
579636 ('out_inv' , 'itk_t1_to_bold' ),
580637 ]),
638+ # Wire up the metadata alternatives
639+ (metadata , select_meta , [('out' , 'inlist' )]),
640+ (select_meta , outputnode , [('out' , 'metadata' )]),
581641 ]) # fmt:skip
582642
583643 # Short-circuit workflow building, use rigid registration
@@ -604,6 +664,10 @@ def init_fsl_bbr_wf(
604664 LOGGER .warning ('FSLDIR unset - using packaged BBR schedule' )
605665 flt_bbr .inputs .schedule = data .load ('flirtsch/bbr.sch' )
606666
667+ metadata .inputs .in1 = {
668+ 'GeneratedBy' : [{'Name' : 'flirt' , 'Version' : flt_bbr .interface .version or '<unknown>' }]
669+ }
670+
607671 workflow .connect ([
608672 (inputnode , wm_mask , [('t1w_dseg' , 'in_seg' )]),
609673 (inputnode , flt_bbr , [('in_file' , 'in_file' )]),
@@ -658,6 +722,8 @@ def init_fsl_bbr_wf(
658722 (transforms , select_transform , [('out' , 'inlist' )]),
659723 (compare_transforms , select_transform , [('out' , 'index' )]),
660724 (select_transform , xfm2itk , [('out' , 'in_xfm' )]),
725+ # Select metadata
726+ (compare_transforms , select_meta , [('out' , 'index' )]),
661727 ]) # fmt:skip
662728
663729 return workflow
@@ -749,3 +815,10 @@ def _conditional_downsampling(in_file, in_mask, zoom_th=4.0):
749815 nb .Nifti1Image (newmaskdata , newmask .affine , hdr ).to_filename (out_mask )
750816
751817 return str (out_file ), str (out_mask )
818+
819+
820+ def _read_cost (cost_file ) -> dict [str , float ]:
821+ """Read a cost from a file."""
822+ # Cost file contains mincost, WM intensity, Ctx intensity, Pct Contrast
823+ with open (cost_file ) as fobj :
824+ return {'FinalCost' : float (fobj .read ().split ()[0 ])}
0 commit comments